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:
Jesse Talavera-Greenberg
2023-06-12 17:56:09 -04:00
committed by GitHub
parent ca7fb4f55e
commit 391ad8c95e
6 changed files with 437 additions and 225 deletions

View File

@ -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;