SaveState: Show warning on old / long use state.

Using save states instead of in game saves causes bugs in games, and
preserves bugs from bad settings and old PPSSPP versions.

This tells users when they might be affected.
This commit is contained in:
Unknown W. Brackets committed 2018-06-14 18:29:55 -07:00
1 parent 6c494c3849
commit a5efb85ab7
7 files changed
+101 -37

No files matched your search

+76 -23
View File
@@ -23,6 +23,7 @@
#include "base/timeutil.h"
#include "i18n/i18n.h"
#include "thread/threadutil.h"
#include "util/text/parsers.h"
#include "Common/FileUtil.h"
#include "Common/ChunkFile.h"
@@ -240,6 +241,9 @@ namespace SaveState
static std::vector<Operation> pending;
static std::mutex mutex;
static bool hasLoadedState = false;
static const int STALE_STATE_USES = 10;
static int saveStateGeneration = 0;
static std::string saveStateInitialGitVersion = "";
// TODO: Should this be configurable?
static const int REWIND_NUM_STATES = 20;
@@ -252,10 +256,21 @@ namespace SaveState
void SaveStart::DoState(PointerWrap &p)
{
auto s = p.Section("SaveStart", 1);
auto s = p.Section("SaveStart", 1, 2);
if (!s)
return;
if (s >= 2) {
++saveStateGeneration;
p.Do(saveStateGeneration);
if (saveStateInitialGitVersion.empty())
saveStateInitialGitVersion = PPSSPP_GIT_VERSION;
p.Do(saveStateInitialGitVersion);
} else {
saveStateGeneration = 1;
saveStateInitialGitVersion = "v0.0.1";
}
// Gotta do CoreTiming first since we'll restore into it.
CoreTiming::DoState(p);
@@ -419,7 +434,7 @@ namespace SaveState
} else {
I18NCategory *sy = GetI18NCategory("System");
if (callback)
callback(false, sy->T("Failed to load state. Error in the file system."), cbUserData);
callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), cbUserData);
}
}
@@ -452,8 +467,8 @@ namespace SaveState
std::string fnUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_STATE_EXTENSION);
std::string shotUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_SCREENSHOT_EXTENSION);
if (!fn.empty()) {
auto renameCallback = [=](bool status, const std::string &message, void *data) {
if (status) {
auto renameCallback = [=](Status status, const std::string &message, void *data) {
if (status != Status::FAILURE) {
if (g_Config.bEnableStateUndo) {
DeleteIfExists(fnUndo);
RenameIfExists(fn, fnUndo);
@@ -476,7 +491,7 @@ namespace SaveState
} else {
I18NCategory *sy = GetI18NCategory("System");
if (callback)
callback(false, sy->T("Failed to save state. Error in the file system."), cbUserData);
callback(Status::FAILURE, sy->T("Failed to save state. Error in the file system."), cbUserData);
}
}
@@ -618,11 +633,30 @@ namespace SaveState
}
#endif
bool HasLoadedState()
{
bool HasLoadedState() {
return hasLoadedState;
}
bool IsStale() {
if (saveStateGeneration >= STALE_STATE_USES) {
// Don't show it every time.
return saveStateGeneration % 5 == 0;
}
return false;
}
bool IsOldVersion() {
if (saveStateInitialGitVersion.empty())
return false;
Version state(saveStateInitialGitVersion);
Version gitVer(PPSSPP_GIT_VERSION);
if (!state.IsValid() || !gitVer.IsValid())
return false;
return state < gitVer;
}
void Process()
{
#ifndef MOBILE_DEVICE
@@ -647,7 +681,8 @@ namespace SaveState
{
Operation &op = operations[i];
CChunkFileReader::Error result;
bool callbackResult;
Status callbackResult;
bool tempResult;
std::string callbackMessage;
std::string reason;
std::string title;
@@ -667,8 +702,22 @@ namespace SaveState
result = CChunkFileReader::Load(op.filename, PPSSPP_GIT_VERSION, state, &reason);
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = sc->T("Loaded State");
callbackResult = true;
callbackResult = Status::SUCCESS;
hasLoadedState = true;
if (IsStale()) {
// For anyone wondering why (too long to put on the screen in an osm):
// Using save states instead of saves simulates many hour play sessions.
// Sometimes this exposes game bugs that were rarely seen on real devices,
// because few people played on a real PSP for 10 hours straight.
callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs.");
callbackResult = Status::WARNING;
} else if (IsOldVersion()) {
// Save states also preserve bugs from old PPSSPP versions, so warn.
callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs.");
callbackResult = Status::WARNING;
}
#ifndef MOBILE_DEVICE
if (g_Config.bSaveLoadResetsAVdumping) {
if (g_Config.bDumpFrames) {
@@ -684,10 +733,10 @@ namespace SaveState
HandleFailure();
callbackMessage = i18nLoadFailure;
ERROR_LOG(SAVESTATE, "Load state failure: %s", reason.c_str());
callbackResult = false;
callbackResult = Status::FAILURE;
} else {
callbackMessage = sc->T(reason.c_str(), i18nLoadFailure);
callbackResult = false;
callbackResult = Status::FAILURE;
}
break;
@@ -703,7 +752,7 @@ namespace SaveState
result = CChunkFileReader::Save(op.filename, title, PPSSPP_GIT_VERSION, state);
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = sc->T("Saved State");
callbackResult = true;
callbackResult = Status::SUCCESS;
#ifndef MOBILE_DEVICE
if (g_Config.bSaveLoadResetsAVdumping) {
if (g_Config.bDumpFrames) {
@@ -719,16 +768,17 @@ namespace SaveState
HandleFailure();
callbackMessage = i18nSaveFailure;
ERROR_LOG(SAVESTATE, "Save state failure: %s", reason.c_str());
callbackResult = false;
callbackResult = Status::FAILURE;
} else {
callbackMessage = i18nSaveFailure;
callbackResult = false;
callbackResult = Status::FAILURE;
}
break;
case SAVESTATE_VERIFY:
callbackResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
if (callbackResult) {
tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE;
if (tempResult) {
INFO_LOG(SAVESTATE, "Verified save state system");
} else {
ERROR_LOG(SAVESTATE, "Save state system verification failed");
@@ -740,35 +790,36 @@ namespace SaveState
result = rewindStates.Restore();
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = sc->T("Loaded State");
callbackResult = true;
callbackResult = Status::SUCCESS;
hasLoadedState = true;
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
// Cripes. Good news is, we might have more. Let's try those too, better than a reset.
if (HandleFailure()) {
// Well, we did rewind, even if too much...
callbackMessage = sc->T("Loaded State");
callbackResult = true;
callbackResult = Status::SUCCESS;
hasLoadedState = true;
} else {
callbackMessage = i18nLoadFailure;
callbackResult = false;
callbackResult = Status::FAILURE;
}
} else {
callbackMessage = i18nLoadFailure;
callbackResult = false;
callbackResult = Status::FAILURE;
}
break;
case SAVESTATE_SAVE_SCREENSHOT:
callbackResult = TakeGameScreenshot(op.filename.c_str(), ScreenshotFormat::JPG, SCREENSHOT_DISPLAY);
if (!callbackResult) {
tempResult = TakeGameScreenshot(op.filename.c_str(), ScreenshotFormat::JPG, SCREENSHOT_DISPLAY);
callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE;
if (!tempResult) {
ERROR_LOG(SAVESTATE, "Failed to take a screenshot for the savestate! %s", op.filename.c_str());
}
break;
default:
ERROR_LOG(SAVESTATE, "Savestate failure: unknown operation type %d", op.type);
callbackResult = false;
callbackResult = Status::FAILURE;
break;
}
@@ -790,6 +841,8 @@ namespace SaveState
rewindStates.Clear();
hasLoadedState = false;
saveStateGeneration = 0;
saveStateInitialGitVersion.clear();
}
void Shutdown()