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
This commit is contained in:
parent
ca7fb4f55e
commit
391ad8c95e
|
@ -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;
|
||||
|
|
11
src/NDS.cpp
11
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,230 +50,148 @@ 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)
|
||||
// Ensure that the file starts with "MELN"
|
||||
u32 read_magic = 0;
|
||||
Var32(&read_magic);
|
||||
|
||||
if (read_magic != *((u32*)SAVESTATE_MAGIC))
|
||||
{
|
||||
Log(LogLevel::Error, "savestate: file %s doesn't exist\n", filename.c_str());
|
||||
Log(LogLevel::Error, "savestate: expected magic number %#08x (%s), got %#08x\n",
|
||||
*((u32*)SAVESTATE_MAGIC),
|
||||
SAVESTATE_MAGIC,
|
||||
read_magic
|
||||
);
|
||||
Error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
u32 len;
|
||||
fseek(file, 0, SEEK_END);
|
||||
len = (u32)ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
u32 buf = 0;
|
||||
|
||||
fread(&buf, 4, 1, file);
|
||||
if (buf != ((u32*)magic)[0])
|
||||
u16 major = 0;
|
||||
Var16(&major);
|
||||
if (major != SAVESTATE_MAJOR)
|
||||
{
|
||||
Log(LogLevel::Error, "savestate: invalid magic %08X\n", buf);
|
||||
Log(LogLevel::Error, "savestate: bad version major %d, expecting %d\n", major, SAVESTATE_MAJOR);
|
||||
Error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
VersionMajor = 0;
|
||||
VersionMinor = 0;
|
||||
|
||||
fread(&VersionMajor, 2, 1, file);
|
||||
if (VersionMajor != SAVESTATE_MAJOR)
|
||||
u16 minor = 0;
|
||||
Var16(&minor);
|
||||
if (minor > SAVESTATE_MINOR)
|
||||
{
|
||||
Log(LogLevel::Error, "savestate: bad version major %d, expecting %d\n", VersionMajor, SAVESTATE_MAJOR);
|
||||
Log(LogLevel::Error, "savestate: state from the future, %d > %d\n", minor, SAVESTATE_MINOR);
|
||||
Error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fread(&VersionMinor, 2, 1, file);
|
||||
if (VersionMinor > SAVESTATE_MINOR)
|
||||
u32 read_length = 0;
|
||||
Var32(&read_length);
|
||||
if (read_length != buffer_length)
|
||||
{
|
||||
Log(LogLevel::Error, "savestate: state from the future, %d > %d\n", VersionMinor, SAVESTATE_MINOR);
|
||||
Log(LogLevel::Error, "savestate: expected a length of %d, got %d\n", buffer_length, read_length);
|
||||
Error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
buf = 0;
|
||||
fread(&buf, 4, 1, file);
|
||||
if (buf != len)
|
||||
{
|
||||
Log(LogLevel::Error, "savestate: bad length %d\n", buf);
|
||||
Error = true;
|
||||
return;
|
||||
}
|
||||
// The next 4 bytes are reserved
|
||||
buffer_offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
fseek(file, 4, SEEK_CUR);
|
||||
|
||||
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)
|
||||
{
|
||||
buffer = static_cast<u8 *>(malloc(buffer_length));
|
||||
|
||||
if (buffer == nullptr)
|
||||
{
|
||||
Log(LogLevel::Error, "savestate: failed to allocate %d bytes\n", buffer_length);
|
||||
Error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CurSection = -1;
|
||||
WriteSavestateHeader();
|
||||
}
|
||||
|
||||
Savestate::~Savestate()
|
||||
{
|
||||
if (Error) return;
|
||||
|
||||
if (Saving)
|
||||
{
|
||||
if (CurSection != 0xFFFFFFFF)
|
||||
{
|
||||
u32 pos = (u32)ftell(file);
|
||||
fseek(file, CurSection+4, SEEK_SET);
|
||||
|
||||
u32 len = pos - CurSection;
|
||||
fwrite(&len, 4, 1, file);
|
||||
|
||||
fseek(file, pos, SEEK_SET);
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
u32 len = (u32)ftell(file);
|
||||
fseek(file, 8, SEEK_SET);
|
||||
fwrite(&len, 4, 1, file);
|
||||
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)
|
||||
// Go back to the current section's header and write the length
|
||||
CloseCurrentSection();
|
||||
|
||||
CurSection = buffer_offset;
|
||||
|
||||
// Write the new section's magic number
|
||||
VarArray((void*)magic, 4);
|
||||
|
||||
// The next 4 bytes are the length, which we'll come back to later.
|
||||
u32 zero = 0;
|
||||
Var32(&zero);
|
||||
|
||||
// The 8 bytes afterward are reserved, so we skip them.
|
||||
Var32(&zero);
|
||||
Var32(&zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 section_offset = FindSection(magic);
|
||||
|
||||
if (section_offset != NO_SECTION)
|
||||
{
|
||||
u32 pos = (u32)ftell(file);
|
||||
fseek(file, CurSection+4, SEEK_SET);
|
||||
|
||||
u32 len = pos - CurSection;
|
||||
fwrite(&len, 4, 1, file);
|
||||
|
||||
fseek(file, pos, SEEK_SET);
|
||||
buffer_offset = section_offset;
|
||||
}
|
||||
|
||||
CurSection = (u32)ftell(file);
|
||||
|
||||
fwrite(magic, 4, 1, file);
|
||||
fseek(file, 12, SEEK_CUR);
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(file, 0x10, SEEK_SET);
|
||||
|
||||
for (;;)
|
||||
else
|
||||
{
|
||||
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;
|
||||
Log(LogLevel::Error, "savestate: section %s not found. blarg\n", magic);
|
||||
Error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savestate::Var8(u8* var)
|
||||
{
|
||||
if (Error) return;
|
||||
|
||||
if (Saving)
|
||||
{
|
||||
fwrite(var, 1, 1, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
fread(var, 1, 1, file);
|
||||
}
|
||||
}
|
||||
|
||||
void Savestate::Var16(u16* var)
|
||||
{
|
||||
if (Error) return;
|
||||
|
||||
if (Saving)
|
||||
{
|
||||
fwrite(var, 2, 1, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
fread(var, 2, 1, file);
|
||||
}
|
||||
}
|
||||
|
||||
void Savestate::Var32(u32* var)
|
||||
{
|
||||
if (Error) return;
|
||||
|
||||
if (Saving)
|
||||
{
|
||||
fwrite(var, 4, 1, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
fread(var, 4, 1, file);
|
||||
}
|
||||
}
|
||||
|
||||
void Savestate::Var64(u64* var)
|
||||
{
|
||||
if (Error) return;
|
||||
|
||||
if (Saving)
|
||||
{
|
||||
fwrite(var, 8, 1, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
fread(var, 8, 1, file);
|
||||
}
|
||||
}
|
||||
|
||||
void Savestate::Bool32(bool* var)
|
||||
{
|
||||
// for compability
|
||||
// for compatibility
|
||||
if (Saving)
|
||||
{
|
||||
u32 val = *var;
|
||||
|
@ -285,14 +207,175 @@ void Savestate::Bool32(bool* var)
|
|||
|
||||
void Savestate::VarArray(void* data, u32 len)
|
||||
{
|
||||
if (Error) return;
|
||||
if (Error || finished) return;
|
||||
|
||||
assert(buffer_offset <= buffer_length);
|
||||
|
||||
if (Saving)
|
||||
{
|
||||
fwrite(data, len, 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(data, len, 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::Finish()
|
||||
{
|
||||
if (Error || finished) return;
|
||||
CloseCurrentSection();
|
||||
WriteStateLength();
|
||||
finished = true;
|
||||
}
|
||||
|
||||
void Savestate::Rewind(bool save)
|
||||
{
|
||||
Error = false;
|
||||
Saving = save;
|
||||
CurSection = NO_SECTION;
|
||||
|
||||
buffer_offset = 0;
|
||||
finished = false;
|
||||
}
|
||||
|
||||
void Savestate::CloseCurrentSection()
|
||||
{
|
||||
if (CurSection != NO_SECTION && !finished)
|
||||
{ // If we're in the middle of writing a section...
|
||||
|
||||
// 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, §ion_length, sizeof(section_length));
|
||||
|
||||
CurSection = NO_SECTION;
|
||||
}
|
||||
}
|
||||
|
||||
bool Savestate::Resize(u32 new_length)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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::WriteSavestateHeader()
|
||||
{
|
||||
// The magic number
|
||||
VarArray((void *) SAVESTATE_MAGIC, 4);
|
||||
|
||||
// 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(§ion_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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
bool failed = false;
|
||||
|
||||
Savestate* state = new Savestate(filename, false);
|
||||
if (state->Error)
|
||||
{
|
||||
delete state;
|
||||
|
||||
// current state might be crapoed, so restore from sane backup
|
||||
state = new Savestate("timewarp.mln", false);
|
||||
failed = true;
|
||||
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 res = NDS::DoSavestate(state);
|
||||
delete state;
|
||||
|
||||
if (!res)
|
||||
{
|
||||
failed = true;
|
||||
state = new Savestate("timewarp.mln", false);
|
||||
NDS::DoSavestate(state);
|
||||
delete state;
|
||||
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 (failed) 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.
|
||||
|
||||
// Get the size of the file that we opened
|
||||
if (fseek(file, 0, SEEK_END) != 0)
|
||||
{
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
// 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 (!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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
delete state;
|
||||
FILE* file = fopen(filename.c_str(), "wb");
|
||||
|
||||
if (file == nullptr)
|
||||
{ // If the file couldn't be opened...
|
||||
return false;
|
||||
}
|
||||
|
||||
NDS::DoSavestate(state);
|
||||
delete state;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -35,6 +35,7 @@ extern SaveManager* GBASave;
|
|||
QString VerifySetup();
|
||||
void Reset();
|
||||
bool LoadBIOS();
|
||||
void ClearBackupState();
|
||||
|
||||
bool LoadROM(QStringList filepath, bool reset);
|
||||
void EjectCart();
|
||||
|
|
Loading…
Reference in New Issue