diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a799649c..fdcab84a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ add_subdirectory(frontend/imgui) add_subdirectory(backend) add_subdirectory(backend/core) add_subdirectory(backend/core/interpreter) +add_subdirectory(backend/core/mem) add_subdirectory(backend/core/mmio) add_subdirectory(backend/core/registers) add_subdirectory(backend/core/rsp) @@ -75,4 +76,4 @@ file(REMOVE ${PROJECT_BINARY_DIR}/resources/shader.vert) target_link_libraries(kaizen PUBLIC frontend frontend-imgui - discord-rpc imgui nfd parallel-rdp backend fmt::fmt nlohmann_json::nlohmann_json core registers interpreter mmio rsp SDL2::SDL2main SDL2::SDL2) + discord-rpc imgui nfd parallel-rdp backend fmt::fmt nlohmann_json::nlohmann_json core registers interpreter mem mmio rsp SDL2::SDL2main SDL2::SDL2) diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index fa977b31..f89430dd 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -27,6 +27,7 @@ void Core::LoadROM(const std::string& rom_) { cpu.mem.mmio.si.pif.InitDevices(cpu.mem.saveType); cpu.mem.mmio.si.pif.LoadMempak(rom_); cpu.mem.mmio.si.pif.LoadEeprom(cpu.mem.saveType, rom_); + cpu.mem.flash.Load(cpu.mem.saveType, rom_); cpu.mem.mmio.si.pif.ExecutePIF(cpu.mem, cpu.regs); } diff --git a/src/backend/GameDB.cpp b/src/backend/GameDB.cpp index d0759ce1..a8cde05d 100644 --- a/src/backend/GameDB.cpp +++ b/src/backend/GameDB.cpp @@ -6,6 +6,7 @@ namespace n64 { void GameDB::match(Mem& mem) { ROM& rom = mem.rom; + bool found = false; std::for_each(std::execution::par, std::begin(gamedb), std::end(gamedb), [&](const auto& i) { bool matches_code = i.code == rom.code; bool matches_region = false; @@ -17,6 +18,7 @@ void GameDB::match(Mem& mem) { } if (matches_code) { + found = true; if (matches_region) { mem.saveType = i.saveType; mem.rom.gameNameDB = i.name; @@ -31,9 +33,11 @@ void GameDB::match(Mem& mem) { } }); - Util::debug("Did not match any Game DB entries. Code: {} Region: {}", mem.rom.code, mem.rom.header.countryCode[0]); + if(!found) { + Util::debug("Did not match any Game DB entries. Code: {} Region: {}", mem.rom.code, mem.rom.header.countryCode[0]); - mem.rom.gameNameDB = ""; - mem.saveType = SAVE_NONE; + mem.rom.gameNameDB = ""; + mem.saveType = SAVE_NONE; + } } } \ No newline at end of file diff --git a/src/backend/core/Interpreter.hpp b/src/backend/core/Interpreter.hpp index d0db0095..c6a1c480 100644 --- a/src/backend/core/Interpreter.hpp +++ b/src/backend/core/Interpreter.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include diff --git a/src/backend/core/Mem.cpp b/src/backend/core/Mem.cpp index d0df0a57..447a1a70 100644 --- a/src/backend/core/Mem.cpp +++ b/src/backend/core/Mem.cpp @@ -194,12 +194,22 @@ u32 Mem::Read32(n64::Registers ®s, u32 paddr) { return mmio.Read(paddr); case CART_REGION_1_2: return Util::ReadAccess(rom.cart, paddr & rom.mask); + case CART_REGION_2_2: + if(saveType == SAVE_NONE) { + return 0; + } else if (saveType == SAVE_SRAM_256k) { + return 0xffff'ffff; + } else if (saveType == SAVE_FLASH_1m) { + return flash.Read(paddr - CART_REGION_START_2_2); + } else { + Util::panic("Cartridge backup Read32 with unknown save type!");; + } case PIF_ROM_REGION: return Util::ReadAccess(si.pif.bootrom, paddr - PIF_ROM_REGION_START); case PIF_RAM_REGION: return be32toh(Util::ReadAccess(si.pif.ram, paddr - PIF_RAM_REGION_START)); case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF: - case 0x04900000 ... 0x0FFFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0; + case 0x04900000 ... 0x07FFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0; default: Util::panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})", paddr, (u64) regs.pc); } @@ -274,8 +284,26 @@ void Mem::Write8(Registers& regs, u32 paddr, u32 val) { Util::WriteAccess(si.pif.ram, paddr, htobe32(val)); si.pif.ProcessCommands(*this); break; - case SRAM_REGION: - Util::panic("SRAM Write8!"); + case CART_REGION_2_2: + if(flash.saveData) { + switch (saveType) { + case SAVE_NONE: + Util::panic("Accessing cartridge backup with save type SAVE_NONE"); + break; + case SAVE_EEPROM_4k: + case SAVE_EEPROM_16k: + Util::panic("Accessing cartridge backup with save type SAVE_EEPROM"); + break; + case SAVE_FLASH_1m: + flash.Write8(paddr - CART_REGION_START_2_2, val); + break; + case SAVE_SRAM_256k: + flash.saveData[paddr - CART_REGION_START_2_2] = val; + flash.saveDataDirty = true; + break; + } + } + break; case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF: case 0x04900000 ... 0x07FFFFFF: @@ -284,7 +312,7 @@ void Mem::Write8(Registers& regs, u32 paddr, u32 val) { case 0x80000000 ... 0xFFFFFFFF: break; default: - Util::panic("Unimplemented 8-bit write at address {:08X} with value {:0X} (PC = {:016X})", paddr, val, + Util::panic("Unimplemented 8-bit write at address {:08X} with value {:02X} (PC = {:016X})", paddr, val, (u64) regs.pc); } } @@ -323,8 +351,8 @@ void Mem::Write16(Registers& regs, u32 paddr, u32 val) { Util::WriteAccess(si.pif.ram, paddr - PIF_RAM_REGION_START, htobe32(val)); si.pif.ProcessCommands(*this); break; - case SRAM_REGION: - Util::panic("SRAM Write16!"); + case CART_REGION_2_2: + Util::panic("Backup Write16!"); case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF: case 0x04900000 ... 0x07FFFFFF: @@ -333,7 +361,7 @@ void Mem::Write16(Registers& regs, u32 paddr, u32 val) { case 0x80000000 ... 0xFFFFFFFF: break; default: - Util::panic("Unimplemented 16-bit write at address {:08X} with value {:0X} (PC = {:016X})", paddr, val, + Util::panic("Unimplemented 16-bit write at address {:08X} with value {:04X} (PC = {:016X})", paddr, val, (u64) regs.pc); } } @@ -384,8 +412,20 @@ void Mem::Write32(Registers& regs, u32 paddr, u32 val) { case PIF_ROM_REGION: case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break; - case 0x08000000 ... 0x0FFFFFFF: - Util::panic("SRAM Write32!"); + case CART_REGION_2_2: + if(flash.saveData) { + if (saveType == SAVE_FLASH_1m) { + flash.Write32(paddr - CART_REGION_START_2_2, val); + } else if (saveType == SAVE_SRAM_256k) { + break; + } else { + Util::panic("Invalid cartridge backup Write32 with save type {} (addr {:08X})", static_cast(saveType), + paddr); + } + } else { + Util::panic("Invalid write to cartridge backup if save data is not initialized!"); + } + break; default: Util::panic("Unimplemented 32-bit write at address {:08X} with value {:0X} (PC = {:016X})", paddr, val, (u64)regs.pc); } } @@ -426,8 +466,8 @@ void Mem::Write64(Registers& regs, u32 paddr, u64 val) { case 0x1FC00000 ... 0x1FC007BF: case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break; - case 0x08000000 ... 0x0FFFFFFF: - Util::panic("SRAM Write64!"); + case CART_REGION_2_2: + Util::panic("Backup Write64!"); default: Util::panic("Unimplemented 64-bit write at address {:08X} with value {:0X} (PC = {:016X})", paddr, val, (u64) regs.pc); diff --git a/src/backend/core/Mem.hpp b/src/backend/core/Mem.hpp index c5c1b485..0ae6be44 100644 --- a/src/backend/core/Mem.hpp +++ b/src/backend/core/Mem.hpp @@ -38,6 +38,83 @@ struct ROM { bool pal; }; +enum FlashState : u8 { + Idle, Erase, Write, Read, Status +}; + +struct Flash { + Flash() = default; + ~Flash() { + FILE* f = fopen(saveDataPath.c_str(), "wb"); + if(f) { + fwrite(saveData, 1, 1_mb, f); + fclose(f); + } + } + void Load(SaveType, fs::path); + FlashState state{}; + u64 status{}; + size_t eraseOffs{}; + size_t writeOffs{}; + u8 writeBuf[128]{}; + u8* saveData = nullptr; + bool saveDataDirty = false; + std::string saveDataPath{}; + + enum FlashCommands : u8 { + FLASH_COMMAND_EXECUTE = 0xD2, + FLASH_COMMAND_STATUS = 0xE1, + FLASH_COMMAND_SET_ERASE_OFFSET = 0x4B, + FLASH_COMMAND_ERASE = 0x78, + FLASH_COMMAND_SET_WRITE_OFFSET = 0xA5, + FLASH_COMMAND_WRITE = 0xB4, + FLASH_COMMAND_READ = 0xF0, + }; + + void CommandExecute(); + void CommandStatus(); + void CommandSetEraseOffs(u32); + void CommandErase(); + void CommandSetWriteOffs(u32); + void CommandWrite(); + void CommandRead(); + + FORCE_INLINE void Write32(u32 index, u32 val) { + if(index > 0) { + u8 cmd = val >> 24; + switch(cmd) { + case FLASH_COMMAND_EXECUTE: CommandExecute(); break; + case FLASH_COMMAND_STATUS: CommandStatus(); break; + case FLASH_COMMAND_SET_ERASE_OFFSET: CommandSetEraseOffs(val); break; + case FLASH_COMMAND_ERASE: CommandErase(); break; + case FLASH_COMMAND_SET_WRITE_OFFSET: CommandSetWriteOffs(val); break; + case FLASH_COMMAND_WRITE: CommandWrite(); break; + case FLASH_COMMAND_READ: CommandRead(); break; + default: Util::warn("Invalid flash command: {:02X}", cmd); + } + } else { + Util::warn("Ignoring write of {:08X} to flash status register", val); + } + } + + FORCE_INLINE void Write8(u32 index, u8 val) { + switch(state) { + case FlashState::Idle: Util::panic("Invalid FlashState::Idle with Write8"); + case FlashState::Status: Util::panic("Invalid FlashState::Status with Write8"); + case FlashState::Erase: Util::panic("Invalid FlashState::Erase with Write8"); + case FlashState::Write: + writeBuf[index] = val; + break; + case FlashState::Read: Util::panic("Invalid FlashState::Read with Write8"); + default: Util::warn("Invalid flash state on Write8: {:02X}", static_cast(state)); + } + } + + FORCE_INLINE u32 Read(u32 index) const { + return status >> 32; + } +}; + struct Mem { ~Mem() { free(sram); @@ -91,7 +168,8 @@ struct Mem { } uintptr_t writePages[PAGE_COUNT]{}, readPages[PAGE_COUNT]{}; ROM rom; - SaveType saveType; + SaveType saveType = SAVE_NONE; + Flash flash; private: friend struct SI; friend struct PI; diff --git a/src/backend/core/mem/CMakeLists.txt b/src/backend/core/mem/CMakeLists.txt new file mode 100644 index 00000000..ddfb7179 --- /dev/null +++ b/src/backend/core/mem/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SOURCES *.cpp) +file(GLOB HEADERS *.hpp) + +add_library(mem ${SOURCES} ${HEADERS}) \ No newline at end of file diff --git a/src/backend/core/mem/Flash.cpp b/src/backend/core/mem/Flash.cpp new file mode 100644 index 00000000..42024b4f --- /dev/null +++ b/src/backend/core/mem/Flash.cpp @@ -0,0 +1,84 @@ +#include + +namespace n64 { +void Flash::Load(SaveType saveType, fs::path path) { + if(saveType == SAVE_FLASH_1m) { + if(saveData) { + memset(saveData, 0xff, 1_mb); + } else { + saveData = (u8 *) calloc(1_mb, 1); + } + saveDataPath = path.replace_extension(".flash").string(); + FILE *f = fopen(saveDataPath.c_str(), "rb"); + if (!f) { + f = fopen(saveDataPath.c_str(), "wb"); + fwrite(saveData, 1, 1_mb, f); + fclose(f); + f = fopen(saveDataPath.c_str(), "rb"); + } + + fseek(f, 0, SEEK_END); + size_t actualSize = ftell(f); + fseek(f, 0, SEEK_SET); + if (actualSize != 1_mb) { + Util::panic("Corrupt flash!"); + } + + fread(saveData, 1, 1_mb, f); + fclose(f); + } +} + +void Flash::CommandExecute() { + Util::debug("Flash::CommandExecute"); + switch (state) { + case FlashState::Idle: + break; + case FlashState::Erase: + for (int i = 0; i < 128; i++) { + saveData[eraseOffs + i] = 0xFF; + } + saveDataDirty = true; + break; + case FlashState::Write: + for (int i = 0; i < 128; i++) { + saveData[writeOffs + i] = writeBuf[i]; + } + saveDataDirty = true; + break; + case FlashState::Read: + Util::panic("Execute command when flash in read state"); + break; + case FlashState::Status: + break; + } +} + +void Flash::CommandStatus() { + state = FlashState::Status; + status = 0x1111800100C20000; +} + +void Flash::CommandSetEraseOffs(u32 val) { + eraseOffs = (val & 0xffff) << 7; +} + +void Flash::CommandErase() { + state = FlashState::Erase; + status = 0x11118004F0000000; +} + +void Flash::CommandSetWriteOffs(u32 val) { + writeOffs = (val & 0xffff) << 7; +} + +void Flash::CommandWrite() { + state = FlashState::Write; + status = 0x1111800400C20000LL; +} + +void Flash::CommandRead() { + state = FlashState::Read; + status = 0x11118004F0000000; +} +} \ No newline at end of file