diff --git a/Core/Dialog/PSPSaveDialog.cpp b/Core/Dialog/PSPSaveDialog.cpp index b54a8ddd62..2ff51d7301 100755 --- a/Core/Dialog/PSPSaveDialog.cpp +++ b/Core/Dialog/PSPSaveDialog.cpp @@ -25,23 +25,23 @@ #include #include -#include "Common/Data/Text/I18n.h" #include "Common/Data/Encoding/Utf8.h" -#include "Common/Thread/ThreadUtil.h" - +#include "Common/Data/Text/I18n.h" #include "Common/File/FileUtil.h" #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Common/StringUtils.h" +#include "Common/Thread/ThreadUtil.h" +#include "Core/Dialog/PSPSaveDialog.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/Util/PPGeDraw.h" #include "Core/HLE/sceCtrl.h" #include "Core/HLE/sceUtility.h" +#include "Core/HW/MemoryStick.h" #include "Core/MemMapHelpers.h" #include "Core/Config.h" #include "Core/Reporting.h" -#include "Core/HW/MemoryStick.h" -#include "Core/Dialog/PSPSaveDialog.h" +#include "Core/SaveState.h" const static float FONT_SCALE = 0.55f; @@ -1048,6 +1048,7 @@ void PSPSaveDialog::ExecuteIOAction() { } break; case DS_SAVE_SAVING: + SaveState::NotifySaveData(); if (param.Save(param.GetPspParam(), GetSelectedSaveDirName()) == 0) { display = DS_SAVE_DONE; } else { @@ -1085,6 +1086,7 @@ void PSPSaveDialog::ExecuteNotVisibleIOAction() { break; case SCE_UTILITY_SAVEDATA_TYPE_SAVE: // Only save and exit case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE: + SaveState::NotifySaveData(); result = param.Save(param.GetPspParam(), GetSelectedSaveDirName()); break; case SCE_UTILITY_SAVEDATA_TYPE_SIZES: @@ -1129,6 +1131,7 @@ void PSPSaveDialog::ExecuteNotVisibleIOAction() { // TODO: Should reset the directory's other files. case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA: case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE: + SaveState::NotifySaveData(); result = param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE); if (result == SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE) { result = SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL; @@ -1136,6 +1139,7 @@ void PSPSaveDialog::ExecuteNotVisibleIOAction() { break; case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA: case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE: + SaveState::NotifySaveData(); result = param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE); break; case SCE_UTILITY_SAVEDATA_TYPE_READDATA: diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 568a1269d6..8875e01494 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -260,6 +260,8 @@ namespace SaveState // 4 hours of total gameplay since the virtual PSP started the game. static const u64 STALE_STATE_TIME = 4 * 3600 * 1000000ULL; static int saveStateGeneration = 0; + static int saveDataGeneration = 0; + static int lastSaveDataGeneration = 0; static std::string saveStateInitialGitVersion = ""; // TODO: Should this be configurable? @@ -289,6 +291,12 @@ namespace SaveState } else { saveStateGeneration = 1; } + if (s >= 3) { + // Keep track of savedata (not save states) too. + Do(p, saveDataGeneration); + } else { + saveDataGeneration = 0; + } // Gotta do CoreTiming first since we'll restore into it. CoreTiming::DoState(p); @@ -761,6 +769,37 @@ namespace SaveState return state < gitVer; } + static Status TriggerLoadWarnings(std::string &callbackMessage) { + auto sc = GetI18NCategory("Screen"); + + if (g_Config.bHideStateWarnings) + return Status::SUCCESS; + + 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."); + return Status::WARNING; + } + 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."); + return Status::WARNING; + } + // If the loaded state (saveDataGeneration) is older, the game may prevent saving again. + // This can happen with newer too, but ignore to/from 0 as a common likely safe case. + if (saveDataGeneration != lastSaveDataGeneration && saveDataGeneration != 0 && lastSaveDataGeneration != 0) { + if (saveDataGeneration < lastSaveDataGeneration) + callbackMessage = sc->T("Loaded. Game may refuse to save over newer savedata."); + else + callbackMessage = sc->T("Loaded. Game may refuse to save over different savedata."); + return Status::WARNING; + } + return Status::SUCCESS; + } + void Process() { if (g_Config.iRewindFlipFrequency != 0 && gpuStats.numFlips != 0) @@ -806,22 +845,12 @@ 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 = op.slot != LOAD_UNDO_SLOT ? slot_prefix + sc->T("Loaded State") : sc->T("State load undone"); - callbackResult = Status::SUCCESS; + callbackMessage = op.slot != LOAD_UNDO_SLOT ? sc->T("Loaded State") : sc->T("State load undone"); + callbackResult = TriggerLoadWarnings(callbackMessage); hasLoadedState = true; - if (!g_Config.bHideStateWarnings && 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 = slot_prefix + sc->T("Loaded. Save in game, restart, and load for less bugs."); - callbackResult = Status::WARNING; - } else if (!g_Config.bHideStateWarnings && IsOldVersion()) { - // Save states also preserve bugs from old PPSSPP versions, so warn. - callbackMessage = slot_prefix + sc->T("Loaded. Save in game, restart, and load for less bugs."); - callbackResult = Status::WARNING; - } + if (!slot_prefix.empty()) + callbackMessage = slot_prefix + callbackMessage; #ifndef MOBILE_DEVICE if (g_Config.bSaveLoadResetsAVdumping) { @@ -945,6 +974,11 @@ namespace SaveState } } + void NotifySaveData() { + saveDataGeneration++; + lastSaveDataGeneration = saveDataGeneration; + } + void Cleanup() { if (needsRestart) { PSP_Shutdown(); @@ -971,6 +1005,8 @@ namespace SaveState hasLoadedState = false; saveStateGeneration = 0; + saveDataGeneration = 0; + lastSaveDataGeneration = 0; saveStateInitialGitVersion.clear(); } diff --git a/Core/SaveState.h b/Core/SaveState.h index 40d72ca109..c9b7e663d1 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -100,6 +100,9 @@ namespace SaveState // Check if there's any save stating needing to be done. Normally called once per frame. void Process(); + // Notify save state code that new save data has been written. + void NotifySaveData(); + // Cleanup by triggering a restart if needed. void Cleanup(); };