From 391ad8c95e9b942ff39705f2c3cd5359aef633b3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Mon, 12 Jun 2023 17:56:09 -0400 Subject: Implement in-memory savestates (#1693) * Refactor Savestate::Var{8,16,32,64} - They now delegate to VarArray - They're declared in the class header so they're likely to be inlined * First crack at refactoring Savestate to work in-memory - Well, third, but who's counting? * Implement Savestate::Finish * Remove the VersionMajor and VersionMinor fields - Instead, pull their values directly from the savestate buffer * Mark a new constructor as explicit * Rename Reset to Rewind * Fix a linebreak * Implement Savestate::Rewind * Add ROMManager::ClearBackupState * Refactor ROMManager to use the refactored Savestate * Capitalize "Least" - It was driving me nuts * Add a log call * Increase default Savestate buffer length to 32MB * Use C-style file I/O instead of C++-style - Dumping bytes to a file with C++'s standard library is a MONSTROUS PAIN IN THE ASS * Quote the savestate's file path for clarity * Write the savestate's length into the header * Add some extra logging calls * Fix section-loading * Remove the deprecated Savestate constructor * Convert a char* to a u32 with memcpy, not a cast * Fix section-handling in loads * Include in Savestate.h - This was causing a build error on Linux --- src/frontend/qt_sdl/ROMManager.cpp | 121 ++++++++++++++++++++++++++++--------- src/frontend/qt_sdl/ROMManager.h | 1 + 2 files changed, 93 insertions(+), 29 deletions(-) (limited to 'src/frontend/qt_sdl') diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 95337e1..80f4652 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #ifdef ARCHIVE_SUPPORT_ENABLED @@ -52,6 +53,7 @@ std::string BaseGBAAssetName = ""; SaveManager* NDSSave = nullptr; SaveManager* GBASave = nullptr; +std::unique_ptr BackupState = nullptr; bool SavestateLoaded = false; std::string PreviousSaveFile = ""; @@ -304,35 +306,62 @@ bool SavestateExists(int slot) bool LoadState(const std::string& filename) { - // backup - Savestate* backup = new Savestate("timewarp.mln", true); - NDS::DoSavestate(backup); - delete backup; + FILE* file = fopen(filename.c_str(), "rb"); + if (file == nullptr) + { // If we couldn't open the state file... + Platform::Log(Platform::LogLevel::Error, "Failed to open state file \"%s\"\n", filename.c_str()); + return false; + } - bool failed = false; + std::unique_ptr backup = std::make_unique(Savestate::DEFAULT_SIZE); + if (backup->Error) + { // If we couldn't allocate memory for the backup... + Platform::Log(Platform::LogLevel::Error, "Failed to allocate memory for state backup\n"); + fclose(file); + return false; + } + + if (!NDS::DoSavestate(backup.get()) || backup->Error) + { // Back up the emulator's state. If that failed... + Platform::Log(Platform::LogLevel::Error, "Failed to back up state, aborting load (from \"%s\")\n", filename.c_str()); + fclose(file); + return false; + } + // We'll store the backup once we're sure that the state was loaded. + // Now that we know the file and backup are both good, let's load the new state. - Savestate* state = new Savestate(filename, false); - if (state->Error) + // Get the size of the file that we opened + if (fseek(file, 0, SEEK_END) != 0) { - delete state; + Platform::Log(Platform::LogLevel::Error, "Failed to seek to end of state file \"%s\"\n", filename.c_str()); + fclose(file); + return false; + } + size_t size = ftell(file); + rewind(file); // reset the filebuf's position - // current state might be crapoed, so restore from sane backup - state = new Savestate("timewarp.mln", false); - failed = true; + // Allocate exactly as much memory as we need for the savestate + std::vector buffer(size); + if (fread(buffer.data(), size, 1, file) == 0) + { // Read the state file into the buffer. If that failed... + Platform::Log(Platform::LogLevel::Error, "Failed to read %u-byte state file \"%s\"\n", size, filename.c_str()); + fclose(file); + return false; } + fclose(file); // done with the file now - bool res = NDS::DoSavestate(state); - delete state; + // Get ready to load the state from the buffer into the emulator + std::unique_ptr state = std::make_unique(buffer.data(), size, false); - if (!res) - { - failed = true; - state = new Savestate("timewarp.mln", false); - NDS::DoSavestate(state); - delete state; + if (!NDS::DoSavestate(state.get()) || state->Error) + { // If we couldn't load the savestate from the buffer... + Platform::Log(Platform::LogLevel::Error, "Failed to load state file \"%s\" into emulator\n", filename.c_str()); + return false; } - if (failed) return false; + // The backup was made and the state was loaded, so we can store the backup now. + BackupState = std::move(backup); // This will clean up any existing backup + assert(backup == nullptr); if (Config::SavestateRelocSRAM && NDSSave) { @@ -351,15 +380,41 @@ bool LoadState(const std::string& filename) bool SaveState(const std::string& filename) { - Savestate* state = new Savestate(filename, true); - if (state->Error) + FILE* file = fopen(filename.c_str(), "wb"); + + if (file == nullptr) + { // If the file couldn't be opened... + return false; + } + + Savestate state; + if (state.Error) + { // If there was an error creating the state (and allocating its memory)... + fclose(file); + return false; + } + + // Write the savestate to the in-memory buffer + NDS::DoSavestate(&state); + + if (state.Error) { - delete state; + fclose(file); + return false; + } + + if (fwrite(state.Buffer(), state.Length(), 1, file) == 0) + { // Write the Savestate buffer to the file. If that fails... + Platform::Log(Platform::Error, + "Failed to write %d-byte savestate to %s\n", + state.Length(), + filename.c_str() + ); + fclose(file); return false; } - NDS::DoSavestate(state); - delete state; + fclose(file); if (Config::SavestateRelocSRAM && NDSSave) { @@ -374,14 +429,14 @@ bool SaveState(const std::string& filename) void UndoStateLoad() { - if (!SavestateLoaded) return; + if (!SavestateLoaded || !BackupState) return; + // Rewind the backup state and put it in load mode + BackupState->Rewind(false); // pray that this works // what do we do if it doesn't??? // but it should work. - Savestate* backup = new Savestate("timewarp.mln", false); - NDS::DoSavestate(backup); - delete backup; + NDS::DoSavestate(BackupState.get()); if (NDSSave && (!PreviousSaveFile.empty())) { @@ -514,6 +569,14 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) return realSize; } +void ClearBackupState() +{ + if (BackupState != nullptr) + { + BackupState = nullptr; + } +} + bool LoadROM(QStringList filepath, bool reset) { if (filepath.empty()) return false; diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h index efaed36..1ec0fe5 100644 --- a/src/frontend/qt_sdl/ROMManager.h +++ b/src/frontend/qt_sdl/ROMManager.h @@ -35,6 +35,7 @@ extern SaveManager* GBASave; QString VerifySetup(); void Reset(); bool LoadBIOS(); +void ClearBackupState(); bool LoadROM(QStringList filepath, bool reset); void EjectCart(); -- cgit v1.2.3