Skip to content

Commit

Permalink
Merge pull request #14679 from iota97/undo-load
Browse files Browse the repository at this point in the history
Savestate load undo
  • Loading branch information
hrydgard authored Aug 6, 2021
2 parents f9b31ed + 157bbb9 commit 86c5a17
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 19 deletions.
1 change: 1 addition & 0 deletions Core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("SaveLoadResetsAVdumping", &g_Config.bSaveLoadResetsAVdumping, false),
ConfigSetting("StateSlot", &g_Config.iCurrentStateSlot, 0, true, true),
ConfigSetting("EnableStateUndo", &g_Config.bEnableStateUndo, &DefaultEnableStateUndo, true, true),
ConfigSetting("StateLoadUndoGame", &g_Config.sStateLoadUndoGame, "NA", true, false),
ConfigSetting("RewindFlipFrequency", &g_Config.iRewindFlipFrequency, 0, true, true),

ConfigSetting("ShowOnScreenMessage", &g_Config.bShowOnScreenMessages, true, true, false),
Expand Down
1 change: 1 addition & 0 deletions Core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ struct Config {
int iRewindFlipFrequency;
bool bUISound;
bool bEnableStateUndo;
std::string sStateLoadUndoGame;
int iAutoLoadSaveState; // 0 = off, 1 = oldest, 2 = newest, >2 = slot number + 3
bool bEnableCheats;
bool bReloadCheats;
Expand Down
90 changes: 72 additions & 18 deletions Core/SaveState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
#include "Core/HLE/__sceAudio.h"
#endif

// Slot number is visual only, -2 will display special message
constexpr int LOAD_UNDO_SLOT = -2;

namespace SaveState
{
struct SaveStart
Expand Down Expand Up @@ -412,18 +415,19 @@ namespace SaveState
return filename.GetFilename() + " " + sy->T("(broken)");
}

Path GenerateSaveSlotFilename(const Path &gameFilename, int slot, const char *extension)
{
std::string GenerateFullDiscId(const Path &gameFilename) {
std::string discId = g_paramSFO.GetValueString("DISC_ID");
std::string discVer = g_paramSFO.GetValueString("DISC_VERSION");
std::string fullDiscId;
if (discId.empty()) {
discId = g_paramSFO.GenerateFakeID();
discVer = "1.00";
}
fullDiscId = StringFromFormat("%s_%s", discId.c_str(), discVer.c_str());
return StringFromFormat("%s_%s", discId.c_str(), discVer.c_str());
}

std::string filename = StringFromFormat("%s_%d.%s", fullDiscId.c_str(), slot, extension);
Path GenerateSaveSlotFilename(const Path &gameFilename, int slot, const char *extension)
{
std::string filename = StringFromFormat("%s_%d.%s", GenerateFullDiscId(gameFilename).c_str(), slot, extension);
return GetSysDirectory(DIRECTORY_SAVESTATE) / filename;
}

Expand All @@ -437,18 +441,6 @@ namespace SaveState
g_Config.iCurrentStateSlot = (g_Config.iCurrentStateSlot + 1) % NUM_SLOTS;
}

void LoadSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData)
{
Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
if (!fn.empty()) {
Load(fn, slot, callback, cbUserData);
} else {
auto sy = GetI18NCategory("System");
if (callback)
callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), cbUserData);
}
}

static void DeleteIfExists(const Path &fn) {
// Just avoiding error messages.
if (File::Exists(fn)) {
Expand All @@ -471,6 +463,62 @@ namespace SaveState
}
}

void LoadSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData)
{
Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
if (!fn.empty()) {
// This add only 1 extra state, should we just always enable it?
if (g_Config.bEnableStateUndo) {
Path backup = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME;

auto saveCallback = [=](Status status, const std::string &message, void *data) {
if (status != Status::FAILURE) {
DeleteIfExists(backup);
File::Rename(backup.WithExtraExtension(".tmp"), backup);
g_Config.sStateLoadUndoGame = GenerateFullDiscId(gameFilename);
} else {
ERROR_LOG(SAVESTATE, "Saving load undo state failed: %s", message.c_str());
}
Load(fn, slot, callback, cbUserData);
};

if (!backup.empty()) {
Save(backup.WithExtraExtension(".tmp"), LOAD_UNDO_SLOT, saveCallback, cbUserData);
} else {
ERROR_LOG(SAVESTATE, "Saving load undo state failed. Error in the file system.");
Load(fn, slot, callback, cbUserData);
}
} else {
Load(fn, slot, callback, cbUserData);
}
} else {
auto sy = GetI18NCategory("System");
if (callback)
callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), cbUserData);
}
}

bool UndoLoad(const Path &gameFilename, Callback callback, void *cbUserData)
{
if (g_Config.sStateLoadUndoGame != GenerateFullDiscId(gameFilename)) {
auto sy = GetI18NCategory("System");
if (callback)
callback(Status::FAILURE, sy->T("Error: load undo state is from a different game"), cbUserData);
return false;
}

Path fn = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME;
if (!fn.empty()) {
Load(fn, LOAD_UNDO_SLOT, callback, cbUserData);
return true;
} else {
auto sy = GetI18NCategory("System");
if (callback)
callback(Status::FAILURE, sy->T("Failed to load state for load undo. Error in the file system."), cbUserData);
return false;
}
}

void SaveSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData)
{
Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
Expand Down Expand Up @@ -541,6 +589,12 @@ namespace SaveState
return File::Exists(fn);
}

bool HasUndoLoad(const Path &gameFilename)
{
Path fn = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME;
return File::Exists(fn) && g_Config.sStateLoadUndoGame == GenerateFullDiscId(gameFilename);
}

bool operator < (const tm &t1, const tm &t2) {
if (t1.tm_year < t2.tm_year) return true;
if (t1.tm_year > t2.tm_year) return false;
Expand Down Expand Up @@ -752,7 +806,7 @@ namespace SaveState
// Use the state's latest version as a guess for saveStateInitialGitVersion.
result = CChunkFileReader::Load(op.filename, &saveStateInitialGitVersion, state, &errorString);
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = slot_prefix + sc->T("Loaded State");
callbackMessage = op.slot != LOAD_UNDO_SLOT ? slot_prefix + sc->T("Loaded State") : sc->T("State load undone");
callbackResult = Status::SUCCESS;
hasLoadedState = true;

Expand Down
5 changes: 5 additions & 0 deletions Core/SaveState.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ namespace SaveState
static const char *UNDO_STATE_EXTENSION = "undo.ppst";
static const char *UNDO_SCREENSHOT_EXTENSION = "undo.jpg";

static const char *LOAD_UNDO_NAME = "load_undo.ppst";

void Init();
void Shutdown();

Expand All @@ -45,9 +47,11 @@ namespace SaveState
void SaveSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData = 0);
void LoadSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData = 0);
bool UndoSaveSlot(const Path &gameFilename, int slot);
bool UndoLoad(const Path &gameFilename, Callback callback, void *cbUserData = 0);
// Checks whether there's an existing save in the specified slot.
bool HasSaveInSlot(const Path &gameFilename, int slot);
bool HasUndoSaveInSlot(const Path &gameFilename, int slot);
bool HasUndoLoad(const Path &gameFilename);
bool HasScreenshotInSlot(const Path &gameFilename, int slot);

int GetCurrentSlot();
Expand All @@ -57,6 +61,7 @@ namespace SaveState
int GetOldestSlot(const Path &gameFilename);

std::string GetSlotDateAsString(const Path &gameFilename, int slot);
std::string GenerateFullDiscId(const Path &gameFilename);
Path GenerateSaveSlotFilename(const Path &gameFilename, int slot, const char *extension);

std::string GetTitle(const Path &filename);
Expand Down
16 changes: 15 additions & 1 deletion UI/PauseScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,15 @@ void GamePauseScreen::CreateViews() {
}
leftColumnItems->Add(new Spacer(0.0));

LinearLayout *buttonRow = leftColumnItems->Add(new LinearLayout(ORIENT_HORIZONTAL));
if (g_Config.bEnableStateUndo) {
UI::Choice *loadUndoButton = buttonRow->Add(new Choice(pa->T("Undo last state load")));
loadUndoButton->SetEnabled(SaveState::HasUndoLoad(gamePath_));
loadUndoButton->OnClick.Handle(this, &GamePauseScreen::OnLoadUndo);
}

if (g_Config.iRewindFlipFrequency > 0) {
UI::Choice *rewindButton = leftColumnItems->Add(new Choice(pa->T("Rewind")));
UI::Choice *rewindButton = buttonRow->Add(new Choice(pa->T("Rewind")));
rewindButton->SetEnabled(SaveState::CanRewind());
rewindButton->OnClick.Handle(this, &GamePauseScreen::OnRewind);
}
Expand Down Expand Up @@ -492,6 +499,13 @@ UI::EventReturn GamePauseScreen::OnRewind(UI::EventParams &e) {
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnLoadUndo(UI::EventParams &e) {
SaveState::UndoLoad(gamePath_, &AfterSaveStateAction);

TriggerFinish(DR_CANCEL);
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnCwCheat(UI::EventParams &e) {
screenManager()->push(new CwCheatScreen(gamePath_));
return UI::EVENT_DONE;
Expand Down
1 change: 1 addition & 0 deletions UI/PauseScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class GamePauseScreen : public UIDialogScreenWithGameBackground {
UI::EventReturn OnReportFeedback(UI::EventParams &e);

UI::EventReturn OnRewind(UI::EventParams &e);
UI::EventReturn OnLoadUndo(UI::EventParams &e);

UI::EventReturn OnScreenshotClicked(UI::EventParams &e);
UI::EventReturn OnCwCheat(UI::EventParams &e);
Expand Down

0 comments on commit 86c5a17

Please sign in to comment.