diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index dbe5afe2..56e8af69 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -90,4 +90,29 @@ void Core::Run(float volumeL, float volumeR) { scheduler.tick(frameCycles, mem, regs); } } + +void Core::Serialize() { + auto sMEM = cpu->mem.Serialize(); + auto sCPU = cpu->Serialize(); + auto sVER = std::vector{KAIZEN_VERSION >> 8, KAIZEN_VERSION >> 4, KAIZEN_VERSION & 0xFF}; + memSize = sMEM.size(); + cpuSize = sCPU.size(); + verSize = sVER.size(); + serialized[slot].insert(serialized[slot].begin(), sVER.begin(), sVER.end()); + serialized[slot].insert(serialized[slot].end(), sMEM.begin(), sMEM.end()); + serialized[slot].insert(serialized[slot].end(), sCPU.begin(), sCPU.end()); +} + +void Core::Deserialize() { + std::vector dVER(serialized[slot].begin(), serialized[slot].begin() + verSize); + if(dVER[0] != (KAIZEN_VERSION >> 8) + || dVER[1] != (KAIZEN_VERSION >> 4) + || dVER[2] != (KAIZEN_VERSION & 0xFF)) { + Util::panic("PROBLEMI!"); + } + + cpu->mem.Deserialize(std::vector(serialized[slot].begin() + verSize, serialized[slot].begin() + verSize + memSize)); + cpu->Deserialize(std::vector(serialized[slot].begin() + verSize + memSize, serialized[slot].begin() + verSize + memSize + cpuSize)); + serialized[slot].erase(serialized[slot].begin(), serialized[slot].end()); +} } diff --git a/src/backend/Core.hpp b/src/backend/Core.hpp index 3ddba612..5558e1d0 100644 --- a/src/backend/Core.hpp +++ b/src/backend/Core.hpp @@ -14,6 +14,8 @@ struct Core { void Stop(); void LoadROM(const std::string&); void Run(float volumeL, float volumeR); + void Serialize(); + void Deserialize(); void TogglePause() { pause = !pause; } [[nodiscard]] VI& GetVI() { return cpu->mem.mmio.vi; } @@ -25,5 +27,8 @@ struct Core { bool romLoaded = false; std::string rom; std::unique_ptr cpu; + std::vector serialized[10]{}; + int memSize, cpuSize, verSize; + int slot = 0; }; } diff --git a/src/backend/core/BaseCPU.hpp b/src/backend/core/BaseCPU.hpp index 35769162..51fa9c1a 100644 --- a/src/backend/core/BaseCPU.hpp +++ b/src/backend/core/BaseCPU.hpp @@ -12,6 +12,8 @@ struct BaseCPU { } virtual bool ShouldServiceInterrupt() = 0; virtual void CheckCompareInterrupt() = 0; + virtual std::vector Serialize() = 0; + virtual void Deserialize(const std::vector&) = 0; Registers regs; Mem mem; }; diff --git a/src/backend/core/Interpreter.cpp b/src/backend/core/Interpreter.cpp index 58484579..88a60058 100644 --- a/src/backend/core/Interpreter.cpp +++ b/src/backend/core/Interpreter.cpp @@ -55,4 +55,18 @@ int Interpreter::Step() { return 1; } + +std::vector Interpreter::Serialize() { + std::vector res{}; + + res.resize(sizeof(Registers)); + + memcpy(res.data(), ®s, sizeof(Registers)); + + return res; +} + +void Interpreter::Deserialize(const std::vector &data) { + memcpy(®s, data.data(), sizeof(Registers)); +} } \ No newline at end of file diff --git a/src/backend/core/Interpreter.hpp b/src/backend/core/Interpreter.hpp index e1aeca6d..4ba4deae 100644 --- a/src/backend/core/Interpreter.hpp +++ b/src/backend/core/Interpreter.hpp @@ -15,6 +15,9 @@ private: #define check_address_error(mask, vaddr) (((!regs.cop0.is_64bit_addressing) && (s32)(vaddr) != (vaddr)) || (((vaddr) & (mask)) != 0)) bool ShouldServiceInterrupt() override; void CheckCompareInterrupt() override; + std::vector Serialize() override; + void Deserialize(const std::vector&) override; + void cop2Decode(u32); void special(u32); void regimm(u32); diff --git a/src/backend/core/JIT.hpp b/src/backend/core/JIT.hpp index 452e8857..399fc067 100644 --- a/src/backend/core/JIT.hpp +++ b/src/backend/core/JIT.hpp @@ -101,6 +101,9 @@ private: return mem.Read8(regs, addr); } + std::vector Serialize() override { return {}; } + void Deserialize(const std::vector&) override { } + void cop2Decode(u32); void special(u32); void regimm(u32); diff --git a/src/backend/core/MMIO.cpp b/src/backend/core/MMIO.cpp index 229b6129..3cb328fa 100644 --- a/src/backend/core/MMIO.cpp +++ b/src/backend/core/MMIO.cpp @@ -44,4 +44,80 @@ void MMIO::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { Util::panic("Unhandled mmio write at addr {:08X} with val {:08X}", addr, val); } } + +std::vector MMIO::Serialize() { + std::vector res{}; + + auto sPIF = si.pif.Serialize(); + constexpr u32 rdpSize = sizeof(DPC) + + 0xFFFFF + + RDRAM_SIZE; + res.resize( + rdpSize + + sizeof(RSP) + + sizeof(MI) + + sizeof(VI) + + sizeof(SI) + + sizeof(PI) + + sizeof(RI) + + sizeof(AI) + + sizeof(u32)*2 + + sizeof(SIStatus)); + + u32 index = 0; + memcpy(res.data(), &rsp, sizeof(RSP)); + index += sizeof(RSP); + memcpy(res.data() + index, &rdp.dpc, sizeof(DPC)); + index += sizeof(DPC); + memcpy(res.data() + index, rdp.cmd_buf, 0xFFFFF); + index += 0xFFFFF; + memcpy(res.data() + index, rdp.rdram, RDRAM_SIZE); + index += RDRAM_SIZE; + memcpy(res.data() + index, &mi, sizeof(MI)); + index += sizeof(MI); + memcpy(res.data() + index, &vi, sizeof(VI)); + index += sizeof(VI); + memcpy(res.data() + index, &ai, sizeof(AI)); + index += sizeof(AI); + memcpy(res.data() + index, &pi, sizeof(PI)); + index += sizeof(PI); + memcpy(res.data() + index, &ri, sizeof(RI)); + index += sizeof(RI); + memcpy(res.data() + index, &si.dramAddr, sizeof(u32)); + index += sizeof(u32); + memcpy(res.data() + index, &si.pifAddr, sizeof(u32)); + index += sizeof(u32); + memcpy(res.data() + index, &si.status, sizeof(SIStatus)); + + res.insert(res.end(), sPIF.begin(), sPIF.end()); + + return res; +} + +void MMIO::Deserialize(const std::vector &data) { + u32 index = 0; + memcpy(&rsp, data.data(), sizeof(RSP)); + index += sizeof(RSP); + memcpy(&rdp.dpc, data.data() + index, sizeof(DPC)); + index += sizeof(DPC); + memcpy(rdp.cmd_buf, data.data() + index, 0xFFFFF); + index += 0xFFFFF; + memcpy(rdp.rdram, data.data() + index, RDRAM_SIZE); + index += RDRAM_SIZE; + memcpy(&mi, data.data() + index, sizeof(MI)); + index += sizeof(MI); + memcpy(&vi, data.data() + index, sizeof(VI)); + index += sizeof(VI); + memcpy(&ai, data.data() + index, sizeof(AI)); + index += sizeof(AI); + memcpy(&pi, data.data() + index, sizeof(PI)); + index += sizeof(PI); + memcpy(&ri, data.data() + index, sizeof(RI)); + index += sizeof(RI); + memcpy(&si.dramAddr, data.data() + index, sizeof(u32)); + index += sizeof(u32); + memcpy(&si.pifAddr, data.data() + index, sizeof(u32)); + index += sizeof(u32); + memcpy(&si.status, data.data() + index, sizeof(SIStatus)); +} } diff --git a/src/backend/core/MMIO.hpp b/src/backend/core/MMIO.hpp index 0abeb377..da09610f 100644 --- a/src/backend/core/MMIO.hpp +++ b/src/backend/core/MMIO.hpp @@ -27,5 +27,7 @@ struct MMIO { u32 Read(u32); void Write(Mem&, Registers&, u32, u32); + std::vector Serialize(); + void Deserialize(const std::vector&); }; } diff --git a/src/backend/core/Mem.cpp b/src/backend/core/Mem.cpp index 1973f5db..f169e7a8 100644 --- a/src/backend/core/Mem.cpp +++ b/src/backend/core/Mem.cpp @@ -601,4 +601,25 @@ void Mem::Write64(Registers& regs, u32 paddr, u64 val) { } } } + +std::vector Mem::Serialize() { + std::vector res{}; + + auto sMMIO = mmio.Serialize(); + auto sFLASH = flash.Serialize(); + mmioSize = sMMIO.size(); + flashSize = sFLASH.size(); + + res.insert(res.begin(), sMMIO.begin(), sMMIO.end()); + res.insert(res.end(), sFLASH.begin(), sFLASH.end()); + res.insert(res.end(), sram.begin(), sram.end()); + + return res; +} + +void Mem::Deserialize(const std::vector& data) { + mmio.Deserialize(std::vector(data.begin(), data.begin() + mmioSize)); + flash.Deserialize(std::vector(data.begin() + mmioSize, data.begin() + mmioSize + flashSize)); + memcpy(sram.data(), data.data() + mmioSize + flashSize, sram.size()); +} } \ No newline at end of file diff --git a/src/backend/core/Mem.hpp b/src/backend/core/Mem.hpp index 5c8339cb..5bd4543c 100644 --- a/src/backend/core/Mem.hpp +++ b/src/backend/core/Mem.hpp @@ -73,6 +73,45 @@ struct Flash { void CommandWrite(); void CommandRead(); + FORCE_INLINE std::vector Serialize() { + std::vector res{}; + + res.resize( + sizeof(state) + + sizeof(status) + + sizeof(eraseOffs) + + sizeof(writeOffs) + + 128); + + u32 index = 0; + memcpy(res.data() + index, &state, sizeof(state)); + index += sizeof(state); + memcpy(res.data() + index, &status, sizeof(status)); + index += sizeof(status); + memcpy(res.data() + index, &eraseOffs, sizeof(eraseOffs)); + index += sizeof(eraseOffs); + memcpy(res.data() + index, &writeOffs, sizeof(writeOffs)); + index += sizeof(writeOffs); + memcpy(res.data() + index, writeBuf, 128); + + res.insert(res.begin(), flash.begin(), flash.end()); + + return res; + } + + FORCE_INLINE void Deserialize(const std::vector& data) { + u32 index = 0; + memcpy(&state, data.data() + index, sizeof(state)); + index += sizeof(state); + memcpy(&status, data.data() + index, sizeof(status)); + index += sizeof(status); + memcpy(&eraseOffs, data.data() + index, sizeof(eraseOffs)); + index += sizeof(eraseOffs); + memcpy(&writeOffs, data.data() + index, sizeof(writeOffs)); + index += sizeof(writeOffs); + memcpy(writeBuf, data.data() + index, 128); + } + FORCE_INLINE void Write32(u32 index, u32 val) { if(index > 0) { u8 cmd = val >> 24; @@ -141,6 +180,9 @@ struct Mem { return mmio.rdp.rdram; } + std::vector Serialize(); + void Deserialize(const std::vector&); + u8 Read8(Registers&, u32); u16 Read16(Registers&, u32); u32 Read32(Registers&, u32); @@ -194,6 +236,7 @@ private: mio::mmap_sink sram; u8 isviewer[ISVIEWER_SIZE]{}; std::string sramPath{}; + int mmioSize, flashSize; FORCE_INLINE bool IsROMPAL() { static const char pal_codes[] = {'D', 'F', 'I', 'P', 'S', 'U', 'X', 'Y'}; diff --git a/src/backend/core/interpreter/instructions.cpp b/src/backend/core/interpreter/instructions.cpp index 12c8d9eb..9213069f 100644 --- a/src/backend/core/interpreter/instructions.cpp +++ b/src/backend/core/interpreter/instructions.cpp @@ -988,5 +988,4 @@ void Interpreter::ctc2(u32) { void Interpreter::cfc2(u32) { } - } \ No newline at end of file diff --git a/src/backend/core/mmio/PIF.cpp b/src/backend/core/mmio/PIF.cpp index afb2f4ff..a109d473 100644 --- a/src/backend/core/mmio/PIF.cpp +++ b/src/backend/core/mmio/PIF.cpp @@ -627,4 +627,30 @@ void PIF::ExecutePIF(Mem& mem, Registers& regs) { DoPIFHLE(mem, regs, pal, cicType); } + +std::vector PIF::Serialize() { + std::vector res{}; + res.resize( + 6*sizeof(JoybusDevice) + + PIF_BOOTROM_SIZE + + PIF_RAM_SIZE + + mempak.size() + + eeprom.size() + + sizeof(int)); + + u32 index = 0; + memcpy(res.data() + index, joybusDevices, 6*sizeof(JoybusDevice)); + index += 6*sizeof(JoybusDevice); + memcpy(res.data() + index, bootrom, PIF_BOOTROM_SIZE); + index += PIF_BOOTROM_SIZE; + memcpy(res.data() + index, ram, PIF_RAM_SIZE); + index += PIF_RAM_SIZE; + memcpy(res.data() + index, mempak.data(), mempak.size()); + index += mempak.size(); + memcpy(res.data() + index, eeprom.data(), eeprom.size()); + index += eeprom.size(); + memcpy(res.data() + index, &channel, sizeof(int)); + + return res; +} } \ No newline at end of file diff --git a/src/backend/core/mmio/PIF.hpp b/src/backend/core/mmio/PIF.hpp index d4acdace..da45543e 100644 --- a/src/backend/core/mmio/PIF.hpp +++ b/src/backend/core/mmio/PIF.hpp @@ -122,6 +122,7 @@ struct PIF { void MempakWrite(u8*, u8*); void EepromRead(const u8*, u8*, const Mem&) const; void EepromWrite(const u8*, u8*, const Mem&); + std::vector Serialize(); bool gamepadConnected = false, mempakOpen = false; SDL_GameController* gamepad{}; diff --git a/src/common.hpp b/src/common.hpp index 879cbef7..0ba6cad6 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -18,6 +18,7 @@ using m128i = __m128i; #define FORCE_INLINE inline __attribute__((always_inline)) constexpr u32 N64_CPU_FREQ = 93750000; +constexpr u16 KAIZEN_VERSION = 0x010; static FORCE_INLINE constexpr u32 GetCyclesPerFrame(bool pal) { if (pal) { diff --git a/src/frontend/App.cpp b/src/frontend/App.cpp index 214a2bce..e9a0c75a 100644 --- a/src/frontend/App.cpp +++ b/src/frontend/App.cpp @@ -41,6 +41,64 @@ void App::Run() { case SDLK_o: { OpenROMDialog(window, core); } break; + case SDLK_F1: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 0; + } + } break; + case SDLK_F2: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 1; + } + } break; + case SDLK_F3: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 2; + } + } break; + case SDLK_F4: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 3; + } + } break; + case SDLK_F5: { + if(core.romLoaded) { + if(event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 4; + } else { + core.Deserialize(); + } + } + } break; + case SDLK_F6: { + if(core.romLoaded) { + if(event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 5; + } else { + core.Serialize(); + } + } + } break; + case SDLK_F7: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 6; + } + } break; + case SDLK_F8: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 7; + } + } break; + case SDLK_F9: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 8; + } + } break; + case SDLK_F10: { + if(core.romLoaded && event.key.keysym.mod == KMOD_SHIFT) { + core.slot = 9; + } + } break; } break; case SDL_DROPFILE: { diff --git a/src/frontend/Language.hpp b/src/frontend/Language.hpp index 6c4ee8bc..e6474ce0 100644 --- a/src/frontend/Language.hpp +++ b/src/frontend/Language.hpp @@ -15,6 +15,10 @@ enum StringID { EMULATION_ITEM_PAUSE, EMULATION_ITEM_RESUME, EMULATION_ITEM_SETTINGS, + EMULATION_ITEM_LOAD_STATE, + EMULATION_ITEM_SAVE_STATE, + EMULATION_MENU_STATES, + STATES_ITEM_SLOT, SETTINGS_CATEGORY_CPU, SETTINGS_CATEGORY_AUDIO, SETTINGS_CATEGORY_INTERFACE, @@ -38,6 +42,10 @@ static const std::map english = { {EMULATION_ITEM_PAUSE, "Pause"}, {EMULATION_ITEM_RESUME, "Resume"}, {EMULATION_ITEM_SETTINGS, "Settings"}, + {EMULATION_ITEM_LOAD_STATE, "Load state..."}, + {EMULATION_ITEM_SAVE_STATE, "Save state..."}, + {EMULATION_MENU_STATES, "Select save slot"}, + {STATES_ITEM_SLOT, "Slot {}"}, {SETTINGS_CATEGORY_CPU, "CPU"}, {SETTINGS_CATEGORY_AUDIO, "Audio"}, {SETTINGS_CATEGORY_INTERFACE, "Interface"}, @@ -60,6 +68,10 @@ static const std::map italian = { {EMULATION_ITEM_PAUSE, "Pausa"}, {EMULATION_ITEM_RESUME, "Riprendi"}, {EMULATION_ITEM_SETTINGS, "Opzioni"}, + {EMULATION_ITEM_LOAD_STATE, "Carica stato..."}, + {EMULATION_ITEM_SAVE_STATE, "Salva stato..."}, + {EMULATION_MENU_STATES, "Seleziona slot"}, + {STATES_ITEM_SLOT, "Slot {}"}, {SETTINGS_CATEGORY_CPU, "CPU"}, {SETTINGS_CATEGORY_AUDIO, "Audio"}, {SETTINGS_CATEGORY_INTERFACE, "Interfaccia"}, diff --git a/src/frontend/imgui/Window.cpp b/src/frontend/imgui/Window.cpp index e04e7c4e..b0751f7f 100644 --- a/src/frontend/imgui/Window.cpp +++ b/src/frontend/imgui/Window.cpp @@ -219,6 +219,27 @@ void Window::RenderMainMenuBar(n64::Core &core) { } SDL_SetWindowTitle(window, windowTitle.c_str()); } + + if(ImGui::BeginMenu(GET_TRANSLATED_STRING(Language::EMULATION_MENU_STATES))) { + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 0).c_str())) { core.slot = 0; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 1).c_str())) { core.slot = 1; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 2).c_str())) { core.slot = 2; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 3).c_str())) { core.slot = 3; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 4).c_str())) { core.slot = 4; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 5).c_str())) { core.slot = 5; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 6).c_str())) { core.slot = 6; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 7).c_str())) { core.slot = 7; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 8).c_str())) { core.slot = 8; } + if(ImGui::MenuItem(fmt::format(GET_TRANSLATED_STRING(Language::STATES_ITEM_SLOT), 9).c_str())) { core.slot = 9; } + ImGui::EndMenu(); + } + + if(ImGui::MenuItem(GET_TRANSLATED_STRING(Language::EMULATION_ITEM_LOAD_STATE), "F5", false, core.romLoaded)) { + core.Deserialize(); + } + if(ImGui::MenuItem(GET_TRANSLATED_STRING(Language::EMULATION_ITEM_SAVE_STATE), "F6", false, core.romLoaded)) { + core.Serialize(); + } if (ImGui::MenuItem(GET_TRANSLATED_STRING(Language::EMULATION_ITEM_SETTINGS))) { showSettings = true; }