From 6163bf5911a81aa754cbfd3bcda8f0d25d554516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 16:28:47 +0200 Subject: [PATCH] SaveState: Show which PPSSPP version saved a state when loading it fails The version comes from the file header and goes in the failure message's metadata line. A state from another version is the usual reason a load fails. Headless prints it too. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/Serialize/Serializer.cpp | 19 ++++++++++++------- Common/Serialize/Serializer.h | 2 +- Core/SaveState.cpp | 15 ++++++++++++++- headless/Headless.cpp | 8 ++++++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Common/Serialize/Serializer.cpp b/Common/Serialize/Serializer.cpp index b222d60be9..b8d8c67a1a 100644 --- a/Common/Serialize/Serializer.cpp +++ b/Common/Serialize/Serializer.cpp @@ -383,7 +383,12 @@ CChunkFileReader::Error CChunkFileReader::LoadFileHeader(File::IOFile &pFile, SC return ERROR_NONE; } -CChunkFileReader::Error CChunkFileReader::GetFileTitle(const Path &filename, std::string *title) { +// Not necessarily null-terminated. +static std::string HeaderGitVersion(const char (&gitVersion)[32]) { + return std::string(gitVersion, strnlen(gitVersion, sizeof(gitVersion))); +} + +CChunkFileReader::Error CChunkFileReader::GetFileTitle(const Path &filename, std::string *title, std::string *gitVersion) { if (!File::Exists(filename)) { ERROR_LOG(Log::SaveState, "ChunkReader: File doesn't exist"); return ERROR_BAD_FILE; @@ -391,7 +396,11 @@ CChunkFileReader::Error CChunkFileReader::GetFileTitle(const Path &filename, std File::IOFile pFile(filename, "rb"); SChunkHeader header; - return LoadFileHeader(pFile, header, title); + Error err = LoadFileHeader(pFile, header, title); + if (err == ERROR_NONE && gitVersion) { + *gitVersion = HeaderGitVersion(header.GitVersion); + } + return err; } CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::string *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) { @@ -460,11 +469,7 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st _buffer = buffer; } - if (header.GitVersion[31]) { - *gitVersion = std::string(header.GitVersion, 32); - } else { - *gitVersion = header.GitVersion; - } + *gitVersion = HeaderGitVersion(header.GitVersion); return ERROR_NONE; } diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h index 3b519fd461..b9e62c55ce 100644 --- a/Common/Serialize/Serializer.h +++ b/Common/Serialize/Serializer.h @@ -349,7 +349,7 @@ public: return ERROR_NONE; } - static Error GetFileTitle(const Path &filename, std::string *title); + static Error GetFileTitle(const Path &filename, std::string *title, std::string *gitVersion = nullptr); private: struct SChunkHeader diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 630d231119..89bde68874 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -763,6 +763,17 @@ int g_screenshotFailures; return copy; } + // Which build last saved the file, from its header. Shown when a load fails, since a state from + // another version is the usual suspect. + static std::string SavedByVersion(const Path &path) { + std::string title; + std::string gitVersion; + if (CChunkFileReader::GetFileTitle(path, &title, &gitVersion) != CChunkFileReader::ERROR_NONE || gitVersion.empty()) { + return ""; + } + return "PPSSPP " + gitVersion; + } + static bool HandleLoadFailure(bool wasRewinding, std::string *metadata) { if (wasRewinding) { WARN_LOG(Log::SaveState, "HandleLoadFailure - trying a rewind state."); @@ -918,10 +929,12 @@ int g_screenshotFailures; } else if (result == CChunkFileReader::ERROR_BROKEN_STATE) { HandleLoadFailure(false, &callbackMetadata); callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; - ERROR_LOG(Log::SaveState, "Load state failure: %s", errorString.c_str()); + callbackMetadata = SavedByVersion(op.path); + ERROR_LOG(Log::SaveState, "Load state failure: %s (saved by %s)", errorString.c_str(), callbackMetadata.c_str()); callbackResult = Status::FAILURE; } else { callbackMessage = sc->T(errorString, i18nLoadFailure); + callbackMetadata = SavedByVersion(op.path); callbackResult = Status::FAILURE; } break; diff --git a/headless/Headless.cpp b/headless/Headless.cpp index e5588c146f..04b392e985 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -1388,10 +1388,14 @@ int main(int argc, const char* argv[]) { if (stateToLoad) { g_stateLoadPending = true; // Queued now, actually applied by SaveState::Process() once the game is up and running. - SaveState::Load(Path(stateToLoad), -1, [](SaveState::Status status, std::string_view message, std::string_view) { + SaveState::Load(Path(stateToLoad), -1, [](SaveState::Status status, std::string_view message, std::string_view metadata) { // The message already reads as a full sentence, e.g. "Failed to load state: ". - fprintf(stderr, "%.*s\n", (int)message.size(), message.data()); + fprintf(stderr, "%.*s\n", STR_VIEW(message)); if (status == SaveState::Status::FAILURE) { + // Which version saved the state. + if (!metadata.empty()) { + fprintf(stderr, "%.*s\n", STR_VIEW(metadata)); + } g_stateLoadFailed = true; } g_stateLoadPending = false;