aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJesse Talavera-Greenberg <jesse@jesse.tg>2023-06-12 17:56:09 -0400
committerGitHub <noreply@github.com>2023-06-12 23:56:09 +0200
commit391ad8c95e9b942ff39705f2c3cd5359aef633b3 (patch)
tree7718e0b201ebddf66ec1ca74f0866fe9adbeb32c /src
parentca7fb4f55e8fdad53993ba279b073f97f453c13c (diff)
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 <cstring> in Savestate.h - This was causing a build error on Linux
Diffstat (limited to 'src')
-rw-r--r--src/GPU3D.cpp2
-rw-r--r--src/NDS.cpp11
-rw-r--r--src/Savestate.cpp399
-rw-r--r--src/Savestate.h78
-rw-r--r--src/frontend/qt_sdl/ROMManager.cpp121
-rw-r--r--src/frontend/qt_sdl/ROMManager.h1
6 files changed, 412 insertions, 200 deletions
diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp
index 5c48575..145b689 100644
--- a/src/GPU3D.cpp
+++ b/src/GPU3D.cpp
@@ -562,7 +562,7 @@ void DoSavestate(Savestate* file)
file->Bool32(&poly->IsShadowMask);
file->Bool32(&poly->IsShadow);
- if (file->IsAtleastVersion(4, 1))
+ if (file->IsAtLeastVersion(4, 1))
file->Var32((u32*)&poly->Type);
else
poly->Type = 0;
diff --git a/src/NDS.cpp b/src/NDS.cpp
index b5c00db..e37a194 100644
--- a/src/NDS.cpp
+++ b/src/NDS.cpp
@@ -815,7 +815,10 @@ bool DoSavestate(Savestate* file)
u32 console;
file->Var32(&console);
if (console != ConsoleType)
+ {
+ Log(LogLevel::Error, "savestate: Expected console type %d, got console type %d. cannot load.\n", ConsoleType, console);
return false;
+ }
}
file->VarArray(MainRAM, MainRAMMaxSize);
@@ -870,7 +873,11 @@ bool DoSavestate(Savestate* file)
file->VarArray(DMA9Fill, 4*sizeof(u32));
- if (!DoSavestate_Scheduler(file)) return false;
+ if (!DoSavestate_Scheduler(file))
+ {
+ Platform::Log(Platform::LogLevel::Error, "savestate: failed to %s scheduler state\n", file->Saving ? "save" : "load");
+ return false;
+ }
file->Var32(&SchedListMask);
file->Var64(&ARM9Timestamp);
file->Var64(&ARM9Target);
@@ -937,6 +944,8 @@ bool DoSavestate(Savestate* file)
}
#endif
+ file->Finish();
+
return true;
}
diff --git a/src/Savestate.cpp b/src/Savestate.cpp
index 0aa0ba3..546c16e 100644
--- a/src/Savestate.cpp
+++ b/src/Savestate.cpp
@@ -17,12 +17,16 @@
*/
#include <stdio.h>
+#include <cassert>
+#include <cstring>
#include "Savestate.h"
#include "Platform.h"
using Platform::Log;
using Platform::LogLevel;
+static const char* SAVESTATE_MAGIC = "MELN";
+
/*
Savestate format
@@ -46,253 +50,332 @@ using Platform::LogLevel;
* different minor means adjustments may have to be made
*/
-// TODO: buffering system! or something of that sort
-// repeated fread/fwrite is slow on Switch
-
-Savestate::Savestate(const std::string& filename, bool save)
+Savestate::Savestate(void *buffer, u32 size, bool save) :
+ Error(false),
+ Saving(save),
+ CurSection(NO_SECTION),
+ buffer(static_cast<u8 *>(buffer)),
+ buffer_offset(0),
+ buffer_length(size),
+ buffer_owned(false),
+ finished(false)
{
- const char* magic = "MELN";
-
- Error = false;
-
- if (save)
+ if (Saving)
{
- Saving = true;
- file = Platform::OpenLocalFile(filename, "wb");
- if (!file)
- {
- Log(LogLevel::Error, "savestate: file %s doesn't exist\n", filename.c_str());
- Error = true;
- return;
- }
-
- VersionMajor = SAVESTATE_MAJOR;
- VersionMinor = SAVESTATE_MINOR;
-
- fwrite(magic, 4, 1, file);
- fwrite(&VersionMajor, 2, 1, file);
- fwrite(&VersionMinor, 2, 1, file);
- fseek(file, 8, SEEK_CUR); // length to be fixed later
+ WriteSavestateHeader();
}
else
{
- Saving = false;
- file = Platform::OpenFile(filename, "rb");
- if (!file)
- {
- Log(LogLevel::Error, "savestate: file %s doesn't exist\n", filename.c_str());
- Error = true;
- return;
- }
-
- u32 len;
- fseek(file, 0, SEEK_END);
- len = (u32)ftell(file);
- fseek(file, 0, SEEK_SET);
+ // Ensure that the file starts with "MELN"
+ u32 read_magic = 0;
+ Var32(&read_magic);
- u32 buf = 0;
-
- fread(&buf, 4, 1, file);
- if (buf != ((u32*)magic)[0])
+ if (read_magic != *((u32*)SAVESTATE_MAGIC))
{
- Log(LogLevel::Error, "savestate: invalid magic %08X\n", buf);
+ Log(LogLevel::Error, "savestate: expected magic number %#08x (%s), got %#08x\n",
+ *((u32*)SAVESTATE_MAGIC),
+ SAVESTATE_MAGIC,
+ read_magic
+ );
Error = true;
return;
}
- VersionMajor = 0;
- VersionMinor = 0;
-
- fread(&VersionMajor, 2, 1, file);
- if (VersionMajor != SAVESTATE_MAJOR)
+ u16 major = 0;
+ Var16(&major);
+ if (major != SAVESTATE_MAJOR)
{
- Log(LogLevel::Error, "savestate: bad version major %d, expecting %d\n", VersionMajor, SAVESTATE_MAJOR);
+ Log(LogLevel::Error, "savestate: bad version major %d, expecting %d\n", major, SAVESTATE_MAJOR);
Error = true;
return;
}
- fread(&VersionMinor, 2, 1, file);
- if (VersionMinor > SAVESTATE_MINOR)
+ u16 minor = 0;
+ Var16(&minor);
+ if (minor > SAVESTATE_MINOR)
{
- Log(LogLevel::Error, "savestate: state from the future, %d > %d\n", VersionMinor, SAVESTATE_MINOR);
+ Log(LogLevel::Error, "savestate: state from the future, %d > %d\n", minor, SAVESTATE_MINOR);
Error = true;
return;
}
- buf = 0;
- fread(&buf, 4, 1, file);
- if (buf != len)
+ u32 read_length = 0;
+ Var32(&read_length);
+ if (read_length != buffer_length)
{
- Log(LogLevel::Error, "savestate: bad length %d\n", buf);
+ Log(LogLevel::Error, "savestate: expected a length of %d, got %d\n", buffer_length, read_length);
Error = true;
return;
}
- fseek(file, 4, SEEK_CUR);
+ // The next 4 bytes are reserved
+ buffer_offset += 4;
}
-
- CurSection = -1;
}
-Savestate::~Savestate()
+
+Savestate::Savestate(u32 initial_size) :
+ Error(false),
+ Saving(true), // Can't load from an empty buffer
+ CurSection(NO_SECTION),
+ buffer(nullptr),
+ buffer_offset(0),
+ buffer_length(initial_size),
+ buffer_owned(true),
+ finished(false)
{
- if (Error) return;
+ buffer = static_cast<u8 *>(malloc(buffer_length));
- if (Saving)
+ if (buffer == nullptr)
{
- if (CurSection != 0xFFFFFFFF)
- {
- u32 pos = (u32)ftell(file);
- fseek(file, CurSection+4, SEEK_SET);
-
- u32 len = pos - CurSection;
- fwrite(&len, 4, 1, file);
+ Log(LogLevel::Error, "savestate: failed to allocate %d bytes\n", buffer_length);
+ Error = true;
+ return;
+ }
- fseek(file, pos, SEEK_SET);
- }
+ WriteSavestateHeader();
+}
- fseek(file, 0, SEEK_END);
- u32 len = (u32)ftell(file);
- fseek(file, 8, SEEK_SET);
- fwrite(&len, 4, 1, file);
+Savestate::~Savestate()
+{
+ if (Saving && !finished && !buffer_owned && !Error)
+ { // If we haven't finished saving, and there hasn't been an error...
+ Finish();
+ // No need to close the active section for an owned buffer,
+ // it's about to be thrown out.
}
- if (file) fclose(file);
+ if (buffer_owned)
+ {
+ free(buffer);
+ }
}
void Savestate::Section(const char* magic)
{
- if (Error) return;
+ if (Error || finished) return;
if (Saving)
{
- if (CurSection != 0xFFFFFFFF)
- {
- u32 pos = (u32)ftell(file);
- fseek(file, CurSection+4, SEEK_SET);
+ // Go back to the current section's header and write the length
+ CloseCurrentSection();
- u32 len = pos - CurSection;
- fwrite(&len, 4, 1, file);
+ CurSection = buffer_offset;
- fseek(file, pos, SEEK_SET);
- }
+ // Write the new section's magic number
+ VarArray((void*)magic, 4);
- CurSection = (u32)ftell(file);
+ // The next 4 bytes are the length, which we'll come back to later.
+ u32 zero = 0;
+ Var32(&zero);
- fwrite(magic, 4, 1, file);
- fseek(file, 12, SEEK_CUR);
+ // The 8 bytes afterward are reserved, so we skip them.
+ Var32(&zero);
+ Var32(&zero);
}
else
{
- fseek(file, 0x10, SEEK_SET);
+ u32 section_offset = FindSection(magic);
- for (;;)
+ if (section_offset != NO_SECTION)
{
- u32 buf = 0;
-
- fread(&buf, 4, 1, file);
- if (buf != ((u32*)magic)[0])
- {
- if (buf == 0)
- {
- Log(LogLevel::Error, "savestate: section %s not found. blarg\n", magic);
- return;
- }
-
- buf = 0;
- fread(&buf, 4, 1, file);
- fseek(file, buf-8, SEEK_CUR);
- continue;
- }
-
- fseek(file, 12, SEEK_CUR);
- break;
+ buffer_offset = section_offset;
+ }
+ else
+ {
+ Log(LogLevel::Error, "savestate: section %s not found. blarg\n", magic);
+ Error = true;
}
}
}
-void Savestate::Var8(u8* var)
+void Savestate::Bool32(bool* var)
{
- if (Error) return;
-
+ // for compatibility
if (Saving)
{
- fwrite(var, 1, 1, file);
+ u32 val = *var;
+ Var32(&val);
}
else
{
- fread(var, 1, 1, file);
+ u32 val;
+ Var32(&val);
+ *var = val != 0;
}
}
-void Savestate::Var16(u16* var)
+void Savestate::VarArray(void* data, u32 len)
{
- if (Error) return;
+ if (Error || finished) return;
+
+ assert(buffer_offset <= buffer_length);
if (Saving)
{
- fwrite(var, 2, 1, file);
+ if (buffer_offset + len > buffer_length)
+ { // If writing the given data would take us past the buffer's end...
+ Log(LogLevel::Warn, "savestate: %u-byte write would exceed %u-byte savestate buffer\n", len, buffer_length);
+
+ if (!(buffer_owned && Resize(buffer_length * 2 + len)))
+ { // If we're not allowed to resize this buffer, or if we are but failed...
+ Log(LogLevel::Error, "savestate: Failed to write %d bytes to savestate\n", len);
+ Error = true;
+ return;
+ }
+ // The buffer's length is doubled, plus however much memory is needed for this write.
+ // This way we can write the data and reduce the chance of needing to resize again.
+ }
+
+ memcpy(buffer + buffer_offset, data, len);
}
else
{
- fread(var, 2, 1, file);
+ if (buffer_offset + len > buffer_length)
+ { // If reading the requested amount of data would take us past the buffer's edge...
+ Log(LogLevel::Error, "savestate: %u-byte read would exceed %u-byte savestate buffer\n", len, buffer_length);
+ Error = true;
+ return;
+
+ // Can't realloc here.
+ // Not only do we not own the buffer pointer (when loading a state),
+ // but we can't magically make the desired data appear.
+ }
+
+ memcpy(data, buffer + buffer_offset, len);
}
+
+ buffer_offset += len;
}
-void Savestate::Var32(u32* var)
+void Savestate::Finish()
{
- if (Error) return;
+ if (Error || finished) return;
+ CloseCurrentSection();
+ WriteStateLength();
+ finished = true;
+}
- if (Saving)
- {
- fwrite(var, 4, 1, file);
- }
- else
- {
- fread(var, 4, 1, file);
- }
+void Savestate::Rewind(bool save)
+{
+ Error = false;
+ Saving = save;
+ CurSection = NO_SECTION;
+
+ buffer_offset = 0;
+ finished = false;
}
-void Savestate::Var64(u64* var)
+void Savestate::CloseCurrentSection()
{
- if (Error) return;
+ if (CurSection != NO_SECTION && !finished)
+ { // If we're in the middle of writing a section...
- if (Saving)
- {
- fwrite(var, 8, 1, file);
- }
- else
- {
- fread(var, 8, 1, file);
+ // Go back to the section's header
+ // Get the length of the section we've written thus far
+ u32 section_length = buffer_offset - CurSection;
+
+ // Write the length in the section's header
+ // (specifically the first 4 bytes after the magic number)
+ memcpy(buffer + CurSection + 4, &section_length, sizeof(section_length));
+
+ CurSection = NO_SECTION;
}
}
-void Savestate::Bool32(bool* var)
+bool Savestate::Resize(u32 new_length)
{
- // for compability
- if (Saving)
- {
- u32 val = *var;
- Var32(&val);
+ if (!buffer_owned)
+ { // If we're not allowed to resize this buffer...
+ Log(LogLevel::Error, "savestate: Buffer is externally-owned, cannot resize it\n");
+ return false;
}
- else
- {
- u32 val;
- Var32(&val);
- *var = val != 0;
+
+ u32 old_length = buffer_length;
+ void* resized = realloc(buffer, new_length);
+ if (!resized)
+ { // If the buffer couldn't be expanded...
+ Log(LogLevel::Error, "savestate: Failed to resize owned savestate buffer from %dB to %dB\n", old_length, new_length);
+ return false;
}
+
+ u32 length_diff = new_length - old_length;
+ buffer = static_cast<u8 *>(resized);
+ buffer_length = new_length;
+
+ Log(LogLevel::Debug, "savestate: Expanded %uB savestate buffer to %uB\n", old_length, new_length);
+ // Zero out the newly-allocated memory (to ensure we don't introduce a security hole)
+ memset(buffer + old_length, 0, length_diff);
+ return true;
}
-void Savestate::VarArray(void* data, u32 len)
+void Savestate::WriteSavestateHeader()
{
- if (Error) return;
+ // The magic number
+ VarArray((void *) SAVESTATE_MAGIC, 4);
- if (Saving)
- {
- fwrite(data, len, 1, file);
- }
- else
- {
- fread(data, len, 1, file);
- }
+ // The major and minor versions
+ u16 major = SAVESTATE_MAJOR;
+ Var16(&major);
+
+ u16 minor = SAVESTATE_MINOR;
+ Var16(&minor);
+
+ // The next 4 bytes are the file's length, which will be filled in at the end
+ u32 zero = 0;
+ Var32(&zero);
+
+ // The following 4 bytes are reserved
+ Var32(&zero);
}
+
+void Savestate::WriteStateLength()
+{
+ // Not to be confused with the buffer length.
+ // The buffer might not be full,
+ // so we don't want to write out the extra stuff.
+ u32 state_length = buffer_offset;
+
+ // Write the length in the header
+ memcpy(buffer + 0x08, &state_length, sizeof(state_length));
+}
+
+u32 Savestate::FindSection(const char* magic) const
+{
+ if (!magic) return NO_SECTION;
+
+ // Start looking at the savestate's beginning, right after its global header
+ // (we can't start from the current offset because then we'd lose the ability to rearrange sections)
+
+ for (u32 offset = 0x10; offset < buffer_length;)
+ { // Until we've found the desired section...
+
+ // Get this section's magic number
+ char read_magic[4] = {0};
+ memcpy(read_magic, buffer + offset, sizeof(read_magic));
+
+ if (memcmp(read_magic, magic, sizeof(read_magic)) == 0)
+ { // If this is the right section...
+ return offset + 16; // ...return the offset of the first byte of the section after the header
+ }
+
+ // Haven't found our section yet. Let's move on to the next one.
+
+ u32 section_length_offset = offset + sizeof(read_magic);
+ if (section_length_offset >= buffer_length)
+ { // If trying to read the section length would take us past the file's end...
+ break;
+ }
+
+ // First we need to find out how big this section is...
+ u32 section_length = 0;
+ memcpy(&section_length, buffer + section_length_offset, sizeof(section_length));
+
+ // ...then skip it. (The section length includes the 16-byte header.)
+ offset += section_length;
+ }
+
+ // We've reached the end of the file without finding the requested section...
+ Log(LogLevel::Error, "savestate: section %s not found. blarg\n", magic);
+ return NO_SECTION;
+} \ No newline at end of file
diff --git a/src/Savestate.h b/src/Savestate.h
index a2216ce..0aef517 100644
--- a/src/Savestate.h
+++ b/src/Savestate.h
@@ -19,6 +19,7 @@
#ifndef SAVESTATE_H
#define SAVESTATE_H
+#include <cstring>
#include <string>
#include <stdio.h>
#include "types.h"
@@ -29,37 +30,92 @@
class Savestate
{
public:
- Savestate(const std::string& filename, bool save);
+ static constexpr u32 DEFAULT_SIZE = 32 * 1024 * 1024; // 32 MB
+ Savestate(void* buffer, u32 size, bool save);
+ explicit Savestate(u32 initial_size = DEFAULT_SIZE);
+
~Savestate();
bool Error;
bool Saving;
- u32 VersionMajor;
- u32 VersionMinor;
u32 CurSection;
void Section(const char* magic);
- void Var8(u8* var);
- void Var16(u16* var);
- void Var32(u32* var);
- void Var64(u64* var);
+ void Var8(u8* var)
+ {
+ VarArray(var, sizeof(*var));
+ }
+
+ void Var16(u16* var)
+ {
+ VarArray(var, sizeof(*var));
+ }
+
+ void Var32(u32* var)
+ {
+ VarArray(var, sizeof(*var));
+ }
+
+ void Var64(u64* var)
+ {
+ VarArray(var, sizeof(*var));
+ }
void Bool32(bool* var);
void VarArray(void* data, u32 len);
- bool IsAtleastVersion(u32 major, u32 minor)
+ void Finish();
+
+ // TODO rewinds the stream
+ void Rewind(bool save);
+
+ bool IsAtLeastVersion(u32 major, u32 minor)
{
- if (VersionMajor > major) return true;
- if (VersionMajor == major && VersionMinor >= minor) return true;
+ u16 major_version = MajorVersion();
+ if (MajorVersion() > major) return true;
+ if (major_version == major && MinorVersion() >= minor) return true;
return false;
}
+ void* Buffer() { return buffer; }
+ [[nodiscard]] const void* Buffer() const { return buffer; }
+
+ [[nodiscard]] u32 BufferLength() const { return buffer_length; }
+
+ [[nodiscard]] u32 Length() const { return buffer_offset; }
+
+ [[nodiscard]] u16 MajorVersion() const
+ {
+ // major version is stored at offset 0x04
+ u16 major = 0;
+ memcpy(&major, buffer + 0x04, sizeof(major));
+ return major;
+ }
+
+ [[nodiscard]] u16 MinorVersion() const
+ {
+ // minor version is stored at offset 0x06
+ u16 minor = 0;
+ memcpy(&minor, buffer + 0x06, sizeof(minor));
+ return minor;
+ }
+
private:
- FILE* file;
+ static constexpr u32 NO_SECTION = 0xffffffff;
+ void CloseCurrentSection();
+ bool Resize(u32 new_length);
+ void WriteSavestateHeader();
+ void WriteStateLength();
+ u32 FindSection(const char* magic) const;
+ u8* buffer;
+ u32 buffer_offset;
+ u32 buffer_length;
+ bool buffer_owned;
+ bool finished;
};
#endif // SAVESTATE_H
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 <string>
#include <utility>
+#include <fstream>
#include <zstd.h>
#ifdef ARCHIVE_SUPPORT_ENABLED
@@ -52,6 +53,7 @@ std::string BaseGBAAssetName = "";
SaveManager* NDSSave = nullptr;
SaveManager* GBASave = nullptr;
+std::unique_ptr<Savestate> 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<Savestate> backup = std::make_unique<Savestate>(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<u8> 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<Savestate> state = std::make_unique<Savestate>(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();