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 1/7] 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; From b9a79b2375661da795b14d0d461f1591c0053d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 17:29:01 +0200 Subject: [PATCH 2/7] Screenshot: Fix use-after-free when a savestate screenshot retries The failure callback ran from inside g_pendingScreenshot. The savestate one schedules a retry, which overwrote g_pendingScreenshot (destroying the running callback and the path it was reading), and the caller then wiped the retry anyway, so the screenshot never got taken. Move the pending screenshot out before calling back. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Screenshot.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index 6b2a68c8c7..bc12dd37ca 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -53,6 +53,13 @@ struct PendingScreenshot { PendingScreenshot g_pendingScreenshot; +// The callback may schedule a retry, so the pending screenshot has to be out of the way before it runs. +static void FailPendingScreenshot() { + PendingScreenshot pending = std::move(g_pendingScreenshot); + g_pendingScreenshot = {}; + pending.callback(ScreenshotResult::ScreenshotNotPossible); +} + class JPEGFileStream : public jpge::output_stream { public: JPEGFileStream(const Path &filename) { @@ -329,16 +336,14 @@ bool ScreenshotNotifyEndOfFrame(Draw::DrawContext *draw) { if (g_display.rotation != DisplayRotation::ROTATE_0) { GPUDebugBuffer temp; if (!::GetOutputFramebuffer(draw, temp)) { - g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible); - g_pendingScreenshot = {}; + FailPendingScreenshot(); return false; } _dbg_assert_msg_(temp.IsBackBuffer(), "temp isn't backbuffer??"); buf = ApplyRotation(temp, g_display.rotation); } else { if (!::GetOutputFramebuffer(draw, buf)) { - g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible); - g_pendingScreenshot = {}; + FailPendingScreenshot(); return false; } } @@ -359,16 +364,14 @@ bool ScreenshotNotifyPostGameRender(Draw::DrawContext *draw) { GPUDebugBuffer buf; if (!gpu) { ERROR_LOG(Log::System, "Can't take screenshots when GPU not running"); - g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible); - g_pendingScreenshot = {}; + FailPendingScreenshot(); return false; } const int maxRes = g_pendingScreenshot.maxRes; if (!gpu->GetCurrentFramebuffer(buf, g_pendingScreenshot.type == ScreenshotType::Render ? GPU_DBG_FRAMEBUF_RENDER : GPU_DBG_FRAMEBUF_DISPLAY, maxRes)) { - g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible); - g_pendingScreenshot = {}; + FailPendingScreenshot(); return false; } From e3916c0acc9f545a6179d3910ce767f1a9b16d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 17:29:01 +0200 Subject: [PATCH 3/7] SaveState: Fix rewind ring and savestate slot bugs Rewind: - first_ never wrapped, so after about 40 snapshots rewinding past the oldest state jumped back to the newest ones. Track a count instead. - States whose base had been replaced stayed in the ring, and each one failed a rewind with an empty error. Drop them when the base goes. - Rewinding after pausing for longer than the snapshot interval restored a snapshot taken just before the rewind ran. - Rewind snapshots (and verification) counted as saves for IsStale(). Slots: - Loading an empty slot replaced the load undo backup before failing. - A refused save (hardcore mode) rotated the slot's screenshots first. - Refused or dropped operations never called their callback, so auto-load didn't notice it had failed. - The slot listing went stale after a save until something rescanned. Also: Verify always failed (its write pass never set the buffer start), and corrupt or truncated files were reported as being from an older version. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/Serialize/Serializer.h | 13 +++++-- Core/SaveState.cpp | 69 +++++++++++++++++++++++++++++------ Core/SaveState.h | 4 +- Core/SaveStateRewind.cpp | 39 ++++++++++++++------ Core/SaveStateRewind.h | 7 +++- 5 files changed, 101 insertions(+), 31 deletions(-) diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h index b9e62c55ce..d002e64b30 100644 --- a/Common/Serialize/Serializer.h +++ b/Common/Serialize/Serializer.h @@ -292,7 +292,9 @@ public: template static Error Load(const Path &filename, std::string *gitVersion, T& _class, std::string *failureReason) { - *failureReason = "LoadStateWrongVersion"; + // LoadFile names the failures it can tell apart. A truncated or corrupt file isn't one of them, + // and isn't necessarily from another version either. + failureReason->clear(); u8 *ptr = nullptr; size_t sz; @@ -334,13 +336,16 @@ public: std::vector buffer(sz); // Step 2: Dump the state. - ptr = &buffer[0]; - p.SetMode(PointerWrap::MODE_WRITE); + p.RewindForWrite(buffer.data()); _class.DoState(p); + if (!p.CheckAfterWrite()) { + return ERROR_BROKEN_STATE; + } // Step 3: Verify the state. - ptr = &buffer[0]; + ptr = buffer.data(); p.SetMode(PointerWrap::MODE_VERIFY); + p.SetReadEnd(buffer.data() + sz); _class.DoState(p); if (p.error == PointerWrap::ERROR_FAILURE) { diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 89bde68874..47a9314598 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include #include #include @@ -98,9 +99,13 @@ static StateRingbuffer rewindStates; // on some Android devices when we allow many save states. static double g_lastRescan = 0.0; static std::map> g_files; // the value is the mod-time. +// Set when a slot save finishes. That's on the emu thread, so the rescan is left to the next reader. +static std::atomic g_filesStale; struct SaveStart { void DoState(PointerWrap &p); + // Rewind snapshots and verification don't count towards IsStale(). + bool countsAsSave = true; }; enum class OperationType { @@ -127,6 +132,7 @@ int g_screenshotFailures; CChunkFileReader::Error SaveToRam(std::vector &data) { SaveStart state; + state.countsAsSave = false; return CChunkFileReader::MeasureAndSavePtr(state, &data); } @@ -154,7 +160,7 @@ int g_screenshotFailures; if (s >= 2) { // This only increments on save, of course (once, not also in the measuring pass.) - if (p.mode == p.MODE_WRITE) { + if (p.mode == p.MODE_WRITE && countsAsSave) { ++saveStateGeneration; } Do(p, saveStateGeneration); @@ -201,16 +207,26 @@ int g_screenshotFailures; pspFileSystem.DoState(p); } + // For an operation that was refused after a warning was already shown, so there's no message. + // The callback still has to hear about it (auto-load tracks failure that way). + static void CallbackRefused(const Callback &callback) { + if (callback) { + callback(Status::FAILURE, "", ""); + } + } + bool Enqueue(const SaveState::Operation &op) { // These two both refuse the operation, and both say so - dropping it silently just // looks like the hotkey didn't work. Callers that ask the same questions themselves // return before getting here, so nothing shows a message twice. if (NetworkWarnUserIfOnlineAndCantSavestate()) { + CallbackRefused(op.callback); return false; } // Hardcore mode bans loading savestates outright, and saving too unless the user opted // back into that. if (Achievements::WarnUserIfHardcoreModeActive(op.type == OperationType::Save)) { + CallbackRefused(op.callback); return false; } @@ -245,14 +261,17 @@ int g_screenshotFailures; return Enqueue(Operation(OperationType::Save, filename, slot, callback)); } - void Verify(Callback callback) { - Enqueue(Operation(OperationType::Verify, Path(), -1, callback)); + bool Verify(Callback callback) { + return Enqueue(Operation(OperationType::Verify, Path(), -1, callback)); } - void Rewind(Callback callback) { + bool Rewind(Callback callback) { + // Or a snapshot can be taken just before the rewind runs (after a long pause, say), and the + // rewind then restores that. + rewindStates.NotifyState(); if (coreState == CoreState::CORE_RUNTIME_ERROR) Core_Break(BreakReason::SavestateRewind, 0); - Enqueue(Operation(OperationType::Rewind, Path(), -1, callback)); + return Enqueue(Operation(OperationType::Rewind, Path(), -1, callback)); } bool CanRewind() { @@ -407,11 +426,20 @@ int g_screenshotFailures; } void LoadSlot(std::string_view gamePrefix, int slot, Callback callback) { - if (NetworkWarnUserIfOnlineAndCantSavestate()) { + // Enqueue() would refuse too, but only after the load undo backup below has replaced the old one. + if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(false)) { + CallbackRefused(callback); return; } Path fn = GenerateSaveSlotPath(gamePrefix, slot, STATE_EXTENSION); + if (!fn.empty() && !File::Exists(fn)) { + if (callback) { + auto sc = GetI18NCategory(I18NCat::SCREEN); + callback(Status::FAILURE, sc->T("LoadStateDoesntExist", sc->T("Failed to load state")), ""); + } + return; + } if (!fn.empty()) { // This add only 1 extra state, should we just always enable it? if (g_Config.bEnableStateUndo) { @@ -474,7 +502,9 @@ int g_screenshotFailures; } void SaveSlot(std::string_view gamePrefix, int slot, Callback callback) { - if (NetworkWarnUserIfOnlineAndCantSavestate()) { + // Enqueue() would refuse too, but only after the screenshots below have been rotated. + if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(true)) { + CallbackRefused(callback); return; } Path fn = GenerateSaveSlotPath(gamePrefix, slot, STATE_EXTENSION); @@ -494,6 +524,7 @@ int g_screenshotFailures; DeleteIfExists(fn); } File::Rename(fn.WithExtraExtension(".tmp"), fn); + g_filesStale = true; } if (callback) { callback(status, message, ""); @@ -531,6 +562,7 @@ int g_screenshotFailures; // Swap them so they can undo again to redo. Mistakes happen. SwapIfExists(shotUndo, shot); SwapIfExists(fnUndo, fn); + Rescan(gamePrefix); return true; } @@ -585,7 +617,14 @@ int g_screenshotFailures; g_lastRescan = time_now_d(); } + static void RescanIfStale(std::string_view gamePrefix) { + if (g_filesStale.exchange(false)) { + Rescan(gamePrefix); + } + } + static bool SaveStateFileExists(std::string_view gamePrefix, int slot, const char *extension) { + RescanIfStale(gamePrefix); return g_files.find(GenerateSaveSlotFilename(gamePrefix, slot, extension)) != g_files.end(); } @@ -651,6 +690,7 @@ int g_screenshotFailures; } int GetNewestSlot(std::string_view gamePrefix) { + RescanIfStale(gamePrefix); int newestSlot = -1; int64_t newestTime = 0; for (int i = 0; i < g_Config.iSaveStateSlotCount; i++) { @@ -667,6 +707,7 @@ int g_screenshotFailures; } int GetOldestSlot(std::string_view gamePrefix) { + RescanIfStale(gamePrefix); int oldestSlot = -1; int64_t oldestTime = INT64_MAX; for (int i = 0; i < g_Config.iSaveStateSlotCount; i++) { @@ -868,6 +909,10 @@ int g_screenshotFailures; if (!__KernelIsRunning()) { ERROR_LOG(Log::SaveState, "Savestate failure: Unable to load without kernel, this should never happen."); + // needsProcess is already clear, so anything queued would only run after the next Enqueue. + for (const auto &op : Flush()) { + CallbackRefused(op.callback); + } return; } @@ -882,6 +927,7 @@ int g_screenshotFailures; // identification has finished and hardcore mode has come up. if (Achievements::WarnUserIfHardcoreModeActive(op.type == OperationType::Save)) { WARN_LOG(Log::SaveState, "Dropping queued savestate operation - hardcore mode is active"); + CallbackRefused(op.callback); continue; } @@ -933,7 +979,7 @@ int g_screenshotFailures; 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); + callbackMessage = errorString.empty() ? std::string(i18nLoadFailure) : std::string(sc->T(errorString, i18nLoadFailure)); callbackMetadata = SavedByVersion(op.path); callbackResult = Status::FAILURE; } @@ -977,10 +1023,9 @@ int g_screenshotFailures; case OperationType::Verify: { - // Its write pass counts as a save, which it isn't. - const int generation = saveStateGeneration; - int tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE; - saveStateGeneration = generation; + SaveStart verifyState; + verifyState.countsAsSave = false; + int tempResult = CChunkFileReader::Verify(verifyState) == CChunkFileReader::ERROR_NONE; callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE; if (tempResult) { INFO_LOG(Log::SaveState, "Verified save state system"); diff --git a/Core/SaveState.h b/Core/SaveState.h index cfbda4ec1a..3f1baf75a5 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -100,11 +100,11 @@ namespace SaveState { // For testing / automated tests. Runs a save state verification pass (async.) // Warning: callback will be called on a different thread. - void Verify(Callback callback = Callback()); + bool Verify(Callback callback = Callback()); // To go back to a previous snapshot (only if enabled.) // Warning: callback will be called on a different thread. - void Rewind(Callback callback = Callback()); + bool Rewind(Callback callback = Callback()); // Returns true if there are rewind snapshots available. bool CanRewind(); diff --git a/Core/SaveStateRewind.cpp b/Core/SaveStateRewind.cpp index f0fa7538b7..8129f4d509 100644 --- a/Core/SaveStateRewind.cpp +++ b/Core/SaveStateRewind.cpp @@ -18,9 +18,7 @@ CChunkFileReader::Error StateRingbuffer::Save() { std::lock_guard guard(lock_); - int n = next_++ % size_; - if ((next_ % size_) == first_) - ++first_; + const int n = next_; std::vector *compressBuffer = &buffer_; CChunkFileReader::Error err; @@ -39,14 +37,32 @@ CChunkFileReader::Error StateRingbuffer::Save() { if (err == CChunkFileReader::ERROR_NONE) { ScheduleCompress(&states_[n].stateBuffer, compressBuffer, &bases_[base_]); states_[n].savedTime = time_now_d(); + baseMapping_[n] = baseGeneration_[base_]; + next_ = (next_ + 1) % size_; + if (count_ < size_) { + count_++; + } } else { states_[n].clear(); } - baseMapping_[n] = baseGeneration_[base_]; + // States compressed against a base that has since been replaced can't be decoded any more. + // They're the oldest ones, so drop them from the end. + while (count_ > 0) { + const int oldest = (next_ - count_ + size_) % size_; + if (BaseAlive(baseMapping_[oldest])) { + break; + } + states_[oldest].clear(); + count_--; + } return err; } +bool StateRingbuffer::BaseAlive(int generation) const { + return generation >= 0 && baseGeneration_[generation % ARRAY_SIZE(bases_)] == generation; +} + CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std::string *metadata) { // The newest state may still be compressing. if (compressThread_.joinable()) @@ -58,20 +74,21 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std:: if (Empty()) return CChunkFileReader::ERROR_BAD_FILE; - int n = (--next_ + size_) % size_; + const int n = (next_ - 1 + size_) % size_; + next_ = n; + count_--; if (states_[n].empty()) return CChunkFileReader::ERROR_BAD_FILE; auto pa = GetI18NCategory(I18NCat::PAUSE); const int generation = baseMapping_[n]; - const int baseSlot = generation < 0 ? -1 : generation % (int)ARRAY_SIZE(bases_); - if (baseSlot < 0 || baseGeneration_[baseSlot] != generation) { - // The base this state was compressed against has since been overwritten, so it can't be - // decoded any more. Only two bases are kept, but the state ring is longer. + if (!BaseAlive(generation)) { + // Save() drops these, so this shouldn't happen. WARN_LOG(Log::SaveState, "Rewind: state %d was compressed against a base that's gone", n); return CChunkFileReader::ERROR_BAD_FILE; } + const int baseSlot = generation % (int)ARRAY_SIZE(bases_); static std::vector buffer; LockedDecompress(buffer, states_[n].stateBuffer, bases_[baseSlot]); @@ -103,7 +120,7 @@ void StateRingbuffer::ScheduleCompress(std::vector *result, const std::vecto void StateRingbuffer::Compress(std::vector &result, const std::vector &state, const std::vector &base) { std::lock_guard guard(lock_); // Bail if we were cleared before locking. - if (first_ == 0 && next_ == 0) + if (count_ == 0) return; double start_time = time_now_d(); @@ -159,8 +176,8 @@ void StateRingbuffer::Clear() { // This lock is mainly for shutdown. std::lock_guard guard(lock_); - first_ = 0; next_ = 0; + count_ = 0; for (auto &b : bases_) { b.clear(); } diff --git a/Core/SaveStateRewind.h b/Core/SaveStateRewind.h index da6a2c1afa..dfae060b8b 100644 --- a/Core/SaveStateRewind.h +++ b/Core/SaveStateRewind.h @@ -35,7 +35,7 @@ public: void Clear(); bool Empty() const { - return next_ == first_; + return count_ == 0; } void Process(); @@ -44,6 +44,8 @@ public: double NextStateTimestamp() const; private: + bool BaseAlive(int generation) const; + const int BLOCK_SIZE = 8192; const int REWIND_NUM_STATES = 20; // TODO: Instead, based on size of compressed state? @@ -59,8 +61,9 @@ private: void clear() { stateBuffer.clear(); savedTime = 0.0; } }; - int first_ = 0; + // The slot the next save goes in, and how many slots before it hold states we can restore. int next_ = 0; + int count_ = 0; int size_; std::vector states_; From 79c2b9b0fd6c98833bafbd3dec5795d912d2fb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 17:29:01 +0200 Subject: [PATCH 4/7] Debugger: Add game.state.rewind and game.state.verify Same as game.state.load/save, and the wait response now carries the metadata string (for a rewind, how long ago the snapshot was taken). Also fixes Broadcast() erasing a waiting entry while holding its mutex, which could free the mutex while locked (the debug CRT aborts on it). Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Debugger/WebSocket/GameSubscriber.cpp | 81 +++++++++++++++++----- docs/WebSocketDebugger.md | 2 +- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index 909942b4d6..9d3601888f 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.cpp +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -57,10 +57,18 @@ struct SaveStateResult { bool done = false; SaveState::Status status = SaveState::Status::FAILURE; std::string message; + std::string metadata; +}; + +enum class StateOp { + LOAD, + SAVE, + REWIND, + VERIFY, }; struct WebSocketGameState : public DebuggerSubscriber { - void StateLoadSave(DebuggerRequest &req, bool save); + void StateOperation(DebuggerRequest &req, StateOp op); void Broadcast(net::WebSocketServer *ws) override; private: @@ -74,23 +82,32 @@ private: } // namespace -// Load a savestate file (game.state.load), or save one (game.state.save) +// Load a savestate file (game.state.load), save one (game.state.save), rewind to the newest +// rewind snapshot (game.state.rewind), or check that saving and reading back the current state +// agree (game.state.verify) // // The operation is queued, and happens at the end of the next frame the CPU runs, so follow this // with something that runs it, like cpu.runUntilTime. // +// Rewind snapshots are only taken with the RewindSnapshotInterval setting on. Each rewind uses up +// the snapshot it restores, so repeating it goes further back. +// // Parameters: -// - path: string, path to the .ppst file. +// - path: string, path to the .ppst file (only for load and save). // - wait: optional boolean, respond when the operation has finished rather than when queued. // // Response (same event name) once queued, or an error if the network or achievements hardcore -// mode refuses it. With wait, it's sent once done instead, with a message string, or an error -// if the load or save failed. -void WebSocketGameState::StateLoadSave(DebuggerRequest &req, bool save) { - const char *event = save ? "game.state.save" : "game.state.load"; +// mode refuses it, or there's nothing to rewind to. With wait, it's sent once done instead, with +// message and metadata strings (for a rewind, how long ago the snapshot was taken), or an error +// if the operation failed. +void WebSocketGameState::StateOperation(DebuggerRequest &req, StateOp op) { + static const char *const events[] = { "game.state.load", "game.state.save", "game.state.rewind", "game.state.verify" }; + const char *event = events[(int)op]; std::string path; bool wait = false; - if (!req.ParamString("path", &path) || !req.ParamBool("wait", &wait, DebuggerParamType::OPTIONAL)) + if ((op == StateOp::LOAD || op == StateOp::SAVE) && !req.ParamString("path", &path)) + return; + if (!req.ParamBool("wait", &wait, DebuggerParamType::OPTIONAL)) return; // The queued operation holds the only other reference, so Broadcast() can also tell when it's @@ -98,7 +115,7 @@ void WebSocketGameState::StateLoadSave(DebuggerRequest &req, bool save) { std::shared_ptr result; if (wait) result = std::make_shared(); - auto callback = [event, result](SaveState::Status status, std::string_view message, std::string_view) { + auto callback = [event, result](SaveState::Status status, std::string_view message, std::string_view metadata) { if (status == SaveState::Status::FAILURE) { ERROR_LOG(Log::System, "%s: %.*s", event, STR_VIEW(message)); } @@ -106,22 +123,43 @@ void WebSocketGameState::StateLoadSave(DebuggerRequest &req, bool save) { std::lock_guard guard(result->lock); result->status = status; result->message = message; + result->metadata = metadata; result->done = true; } }; bool running = false; + bool canRewind = true; bool queued = false; Core_RunOnCPUThread([&] { running = PSP_GetBootState() == BootState::Complete; - if (running) { - queued = save ? SaveState::Save(Path(path), -1, callback) : SaveState::Load(Path(path), -1, callback); + if (!running) { + return; + } + switch (op) { + case StateOp::LOAD: + queued = SaveState::Load(Path(path), -1, callback); + break; + case StateOp::SAVE: + queued = SaveState::Save(Path(path), -1, callback); + break; + case StateOp::REWIND: + canRewind = SaveState::CanRewind(); + if (canRewind) { + queued = SaveState::Rewind(callback); + } + break; + case StateOp::VERIFY: + queued = SaveState::Verify(callback); + break; } }); if (!running) return req.Fail("Game not running"); + if (!canRewind) + return req.Fail("No rewind snapshots"); if (!queued) { - return req.Fail(save ? "Could not queue savestate save" : "Could not queue savestate load"); + return req.Fail(std::string("Could not queue ") + event); } if (!wait) { @@ -136,17 +174,22 @@ void WebSocketGameState::Broadcast(net::WebSocketServer *ws) { for (auto it = waiting_.begin(); it != waiting_.end(); ) { // Check this first: if the callback ran, it was done before letting go. bool dropped = it->result.use_count() == 1; - std::lock_guard guard(it->result->lock); - if (!it->result->done && !dropped) { + // Our own reference, since erasing the entry below must not free the mutex we're holding. + std::shared_ptr result = it->result; + std::lock_guard guard(result->lock); + if (!result->done && !dropped) { ++it; continue; } - bool failed = !it->result->done || it->result->status == SaveState::Status::FAILURE; + bool failed = !result->done || result->status == SaveState::Status::FAILURE; JsonWriter j; j.begin(); j.writeString("event", failed ? "error" : it->event); - j.writeString("message", it->result->done ? it->result->message : "Savestate operation was dropped"); + j.writeString("message", result->done ? result->message : "Savestate operation was dropped"); + if (result->done) { + j.writeString("metadata", result->metadata); + } if (failed) { j.writeInt("level", (int)LogLevel::LERROR); } @@ -173,8 +216,10 @@ DebuggerSubscriber *WebSocketGameInit(DebuggerEventHandlerMap &map) { auto p = new WebSocketGameState(); map["game.reset"] = &WebSocketGameReset; map["game.status"] = &WebSocketGameStatus; - map["game.state.load"] = [p](DebuggerRequest &req) { p->StateLoadSave(req, false); }; - map["game.state.save"] = [p](DebuggerRequest &req) { p->StateLoadSave(req, true); }; + map["game.state.load"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::LOAD); }; + map["game.state.save"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::SAVE); }; + map["game.state.rewind"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::REWIND); }; + map["game.state.verify"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::VERIFY); }; map["game.speed.get"] = &WebSocketGameSpeedGet; map["game.speed.set"] = &WebSocketGameSpeedSet; map["version"] = &WebSocketVersion; diff --git a/docs/WebSocketDebugger.md b/docs/WebSocketDebugger.md index 3ca6b81899..a756c9226a 100644 --- a/docs/WebSocketDebugger.md +++ b/docs/WebSocketDebugger.md @@ -271,7 +271,7 @@ file - this is just an index. | Category | Events | File | |---|---|---| -| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file, applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `version` | `GameSubscriber.cpp` | +| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file, applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.state.rewind` (the same for the newest rewind snapshot, which it uses up; needs `RewindSnapshotInterval` set, and the `wait` response's `metadata` says how long ago the snapshot was taken), `game.state.verify` (the same for checking that saving the current state and reading it back agree), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `version` | `GameSubscriber.cpp` | | CPU core | `cpu.stepping`, `cpu.resume`, `cpu.status` (reports `ticks` plus `us`, emulated microseconds, and `clockHz` - use `us` to line up with wall-clock timings, since games change the clock frequency and the ticks-per-second ratio isn't fixed), `cpu.getAllRegs`, `cpu.getReg`, `cpu.setReg`, `cpu.evaluate` | `CPUCoreSubscriber.cpp` | | Stepping | `cpu.stepInto`, `cpu.stepOver`, `cpu.stepOut`, `cpu.runUntil`, `cpu.runUntilTime` (run until a point in emulated time - `us` absolute or `relativeUs` from now - and break there; this is how to get a scripted repro reproducibly "N seconds into the game" instead of polling `cpu.status` in a loop), `cpu.nextHLE` | `SteppingSubscriber.cpp` | | Breakpoints | `cpu.breakpoint.add/update/remove/list`, `memory.breakpoint.add/update/remove/list`, `cpu.regBreakpoint.add/update/remove/list` (break when a register is written to, by any instruction anywhere - currently GPRs only; interpreter-only, no effect under a JIT backend) | `BreakpointSubscriber.cpp` | From b75f487dc5cb29351c51bad2a7cbe749f350089d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 17:50:48 +0200 Subject: [PATCH 5/7] SaveState: Report a state that fails to load as being from another version When the file reads fine but a section doesn't, that's almost always a state from another version, so say that (LoadStateWrongVersion), with the version that saved it and the failing section on the second line. LoadPtr() now returns just the section title, for callers to phrase. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/Serialize/Serializer.h | 6 +++--- Core/SaveState.cpp | 13 ++++++++----- UI/EmuScreen.cpp | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h index d002e64b30..255de352d0 100644 --- a/Common/Serialize/Serializer.h +++ b/Common/Serialize/Serializer.h @@ -200,7 +200,8 @@ public: ERROR_BAD_ALLOC, }; - // May fail badly if ptr doesn't point to valid data. + // May fail badly if ptr doesn't point to valid data. On failure, errorString is the title of the + // section that failed. template static Error LoadPtr(u8 *ptr, size_t size, T &_class, std::string *errorString) { @@ -211,8 +212,7 @@ public: if (p.error != p.ERROR_FAILURE) { return ERROR_NONE; } else { - std::string badSectionTitle = p.GetBadSectionTitle() ? p.GetBadSectionTitle() : "(unknown bad section)"; - *errorString = std::string("Failure at ") + badSectionTitle; + *errorString = p.GetBadSectionTitle() ? p.GetBadSectionTitle() : "unknown section"; return ERROR_BROKEN_STATE; } } diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 47a9314598..82a890a2ba 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -974,9 +974,12 @@ int g_screenshotFailures; g_lastSaveTime = time_now_d(); } else if (result == CChunkFileReader::ERROR_BROKEN_STATE) { HandleLoadFailure(false, &callbackMetadata); - callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; - callbackMetadata = SavedByVersion(op.path); - ERROR_LOG(Log::SaveState, "Load state failure: %s (saved by %s)", errorString.c_str(), callbackMetadata.c_str()); + // The file was fine, its contents weren't: almost always a state from another version. + // errorString is the section that failed. + callbackMessage = sc->T("LoadStateWrongVersion", i18nLoadFailure); + const std::string version = SavedByVersion(op.path); + callbackMetadata = version.empty() ? "(" + errorString + ")" : version + " (" + errorString + ")"; + ERROR_LOG(Log::SaveState, "Load state failure at %s (saved by %s)", errorString.c_str(), version.c_str()); callbackResult = Status::FAILURE; } else { callbackMessage = errorString.empty() ? std::string(i18nLoadFailure) : std::string(sc->T(errorString, i18nLoadFailure)); @@ -1052,11 +1055,11 @@ int g_screenshotFailures; hasLoadedState = true; Core_ResetException(); } else { - callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; + callbackMessage = std::string(i18nLoadFailure) + " (" + errorString + ")"; callbackResult = Status::FAILURE; } } else { - callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; + callbackMessage = i18nLoadFailure; callbackResult = Status::FAILURE; } break; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 2727e5e1af..8eb5a6efe6 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1768,7 +1768,7 @@ ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) { } else if (PSP_CoreParameter().frozen) { std::string errorString; if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) { - ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str()); + ERROR_LOG(Log::SaveState, "Failed to load freeze state (failure at %s). Unfreezing.", errorString.c_str()); PSP_CoreParameter().frozen = false; } } From ca7257770207fe9091340b03bfac3630f9e98197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 17:50:48 +0200 Subject: [PATCH 6/7] Rewind: Keep the restored state, and measure "seconds ago" in game time A rewind used up the state it restored, so rewinding, playing on a bit and rewinding again went further back than the state you just came from. Now the state stays, and only a rewind within 0.8 seconds of the last one steps back past it. "Seconds ago" was wall-clock time since the snapshot, which counted pauses, fast-forward and time spent in states rewound to earlier. It's now emulated time from where the rewinding started. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Debugger/WebSocket/GameSubscriber.cpp | 5 +- Core/SaveStateRewind.cpp | 63 ++++++++++++++++------ Core/SaveStateRewind.h | 15 +++++- docs/WebSocketDebugger.md | 2 +- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index 9d3601888f..c925b724ec 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.cpp +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -89,8 +89,9 @@ private: // The operation is queued, and happens at the end of the next frame the CPU runs, so follow this // with something that runs it, like cpu.runUntilTime. // -// Rewind snapshots are only taken with the RewindSnapshotInterval setting on. Each rewind uses up -// the snapshot it restores, so repeating it goes further back. +// Rewind snapshots are only taken with the RewindSnapshotInterval setting on. A rewind restores the +// newest snapshot and keeps it, so a later rewind comes back to it, but one within 0.8 seconds of +// the last steps further back. // // Parameters: // - path: string, path to the .ppst file (only for load and save). diff --git a/Core/SaveStateRewind.cpp b/Core/SaveStateRewind.cpp index 8129f4d509..224767d137 100644 --- a/Core/SaveStateRewind.cpp +++ b/Core/SaveStateRewind.cpp @@ -4,6 +4,7 @@ #include "Core/SaveState.h" #include "Core/SaveStateRewind.h" #include "Core/Core.h" +#include "Core/CoreTiming.h" #include "Core/Config.h" namespace SaveState { @@ -36,7 +37,7 @@ CChunkFileReader::Error StateRingbuffer::Save() { if (err == CChunkFileReader::ERROR_NONE) { ScheduleCompress(&states_[n].stateBuffer, compressBuffer, &bases_[base_]); - states_[n].savedTime = time_now_d(); + states_[n].emuTime = CoreTiming::GetGlobalTimeUs() / 1000000.0; baseMapping_[n] = baseGeneration_[base_]; next_ = (next_ + 1) % size_; if (count_ < size_) { @@ -45,6 +46,8 @@ CChunkFileReader::Error StateRingbuffer::Save() { } else { states_[n].clear(); } + // The state a rewind restored isn't the newest any more, so the next rewind comes back to this one. + newestRestored_ = false; // States compressed against a base that has since been replaced can't be decoded any more. // They're the oldest ones, so drop them from the end. @@ -74,18 +77,26 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std:: if (Empty()) return CChunkFileReader::ERROR_BAD_FILE; + // Restoring a state doesn't use it up: rewind, play on a bit, rewind again, and you're back at the + // same state. Rewinding again quickly steps further back instead (except from the oldest state). + const double now = time_now_d(); + const double emuNow = CoreTiming::GetGlobalTimeUs() / 1000000.0; + const bool quick = newestRestored_ && now - lastRestoreTime_ < STEP_BACK_SECONDS; + if (quick) { + if (count_ > 1) { + DropNewest(); + } + } else if (!retryingAfterFailure_) { + rewindFromEmuTime_ = emuNow; + } + retryingAfterFailure_ = false; + const int n = (next_ - 1 + size_) % size_; - next_ = n; - count_--; - if (states_[n].empty()) - return CChunkFileReader::ERROR_BAD_FILE; - - auto pa = GetI18NCategory(I18NCat::PAUSE); - const int generation = baseMapping_[n]; - if (!BaseAlive(generation)) { - // Save() drops these, so this shouldn't happen. - WARN_LOG(Log::SaveState, "Rewind: state %d was compressed against a base that's gone", n); + if (states_[n].empty() || !BaseAlive(generation)) { + // Save() drops states whose base is gone, so this shouldn't happen. + WARN_LOG(Log::SaveState, "Rewind: state %d can't be decoded", n); + DropNewest(); return CChunkFileReader::ERROR_BAD_FILE; } const int baseSlot = generation % (int)ARRAY_SIZE(bases_); @@ -93,19 +104,39 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std:: static std::vector buffer; LockedDecompress(buffer, states_[n].stateBuffer, bases_[baseSlot]); CChunkFileReader::Error error = LoadFromRam(buffer, errorString); - *metadata = pa->T("Rewind"); + if (error != CChunkFileReader::ERROR_NONE) { + // Don't come back to it. HandleLoadFailure() tries the next one, and after a partial load the + // clock is meaningless, so that keeps rewindFromEmuTime_. + DropNewest(); + retryingAfterFailure_ = true; + return error; + } + newestRestored_ = true; + lastRestoreTime_ = now; - if (states_[n].savedTime) { + auto pa = GetI18NCategory(I18NCat::PAUSE); + *metadata = pa->T("Rewind"); + // In emulated time, from where the rewinding started: wall-clock time would count pauses, + // fast-forward, and the time spent in states rewound to earlier. + const double secondsBack = rewindFromEmuTime_ - states_[n].emuTime; + if (secondsBack >= 0.0) { auto di = GetI18NCategory(I18NCat::DIALOG); metadata->append(" ("); - metadata->append(ApplySafeSubstitutions(di->T("%1 seconds ago"), static_cast(time_now_d() - states_[n].savedTime))); + metadata->append(ApplySafeSubstitutions(di->T("%1 seconds ago"), (int)(secondsBack + 0.5))); metadata->append(")"); } - rewindLastTime_ = time_now_d(); + rewindLastTime_ = now; return error; } +void StateRingbuffer::DropNewest() { + next_ = (next_ - 1 + size_) % size_; + states_[next_].clear(); + count_--; + newestRestored_ = false; +} + void StateRingbuffer::ScheduleCompress(std::vector *result, const std::vector *state, const std::vector *base) { if (compressThread_.joinable()) compressThread_.join(); @@ -193,6 +224,8 @@ void StateRingbuffer::Clear() { buffer_.clear(); base_ = -1; baseUsage_ = 0; + newestRestored_ = false; + retryingAfterFailure_ = false; rewindLastTime_ = time_now_d(); } diff --git a/Core/SaveStateRewind.h b/Core/SaveStateRewind.h index dfae060b8b..66626f28f8 100644 --- a/Core/SaveStateRewind.h +++ b/Core/SaveStateRewind.h @@ -45,20 +45,24 @@ public: private: bool BaseAlive(int generation) const; + void DropNewest(); const int BLOCK_SIZE = 8192; const int REWIND_NUM_STATES = 20; // TODO: Instead, based on size of compressed state? const int BASE_USAGE_INTERVAL = 15; + // A rewind this soon after the last one steps back past the state that one restored. + const double STEP_BACK_SECONDS = 0.8; typedef std::vector StateBuffer; struct RewindState { StateBuffer stateBuffer; - double savedTime; + // CoreTiming's clock when the state was taken, in seconds. + double emuTime = 0.0; bool empty() const { return stateBuffer.empty(); } - void clear() { stateBuffer.clear(); savedTime = 0.0; } + void clear() { stateBuffer.clear(); emuTime = 0.0; } }; // The slot the next save goes in, and how many slots before it hold states we can restore. @@ -81,6 +85,13 @@ private: int baseUsage_ = 0; double rewindLastTime_ = 0.0f; + + // Whether the newest state is the one the last rewind restored, and when that was. + bool newestRestored_ = false; + double lastRestoreTime_ = 0.0; + // CoreTiming's clock when the current run of rewinds started, for "seconds ago". + double rewindFromEmuTime_ = 0.0; + bool retryingAfterFailure_ = false; }; } // namespace SaveState diff --git a/docs/WebSocketDebugger.md b/docs/WebSocketDebugger.md index a756c9226a..0754ede886 100644 --- a/docs/WebSocketDebugger.md +++ b/docs/WebSocketDebugger.md @@ -271,7 +271,7 @@ file - this is just an index. | Category | Events | File | |---|---|---| -| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file, applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.state.rewind` (the same for the newest rewind snapshot, which it uses up; needs `RewindSnapshotInterval` set, and the `wait` response's `metadata` says how long ago the snapshot was taken), `game.state.verify` (the same for checking that saving the current state and reading it back agree), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `version` | `GameSubscriber.cpp` | +| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file, applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.state.rewind` (the same for the newest rewind snapshot, which it keeps, so only a rewind within 0.8 seconds of the last steps further back; needs `RewindSnapshotInterval` set, and the `wait` response's `metadata` says how far back that is in emulated time), `game.state.verify` (the same for checking that saving the current state and reading it back agree), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `version` | `GameSubscriber.cpp` | | CPU core | `cpu.stepping`, `cpu.resume`, `cpu.status` (reports `ticks` plus `us`, emulated microseconds, and `clockHz` - use `us` to line up with wall-clock timings, since games change the clock frequency and the ticks-per-second ratio isn't fixed), `cpu.getAllRegs`, `cpu.getReg`, `cpu.setReg`, `cpu.evaluate` | `CPUCoreSubscriber.cpp` | | Stepping | `cpu.stepInto`, `cpu.stepOver`, `cpu.stepOut`, `cpu.runUntil`, `cpu.runUntilTime` (run until a point in emulated time - `us` absolute or `relativeUs` from now - and break there; this is how to get a scripted repro reproducibly "N seconds into the game" instead of polling `cpu.status` in a loop), `cpu.nextHLE` | `SteppingSubscriber.cpp` | | Breakpoints | `cpu.breakpoint.add/update/remove/list`, `memory.breakpoint.add/update/remove/list`, `cpu.regBreakpoint.add/update/remove/list` (break when a register is written to, by any instruction anywhere - currently GPRs only; interpreter-only, no effect under a JIT backend) | `BreakpointSubscriber.cpp` | From 2fbb67f20754897b50f0006a9a3b705d9918da93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 17:55:56 +0200 Subject: [PATCH 7/7] lang: A savestate that fails to load is from a newer version, not an older one States from older versions load fine. The string is now shown when a section of the state fails to load, which in practice means a newer build saved it. Co-Authored-By: Claude Opus 5.5 (1M context) --- assets/lang/ar_AE.ini | 2 +- assets/lang/az_AZ.ini | 2 +- assets/lang/be_BY.ini | 2 +- assets/lang/bg_BG.ini | 2 +- assets/lang/ca_ES.ini | 2 +- assets/lang/cz_CZ.ini | 2 +- assets/lang/da_DK.ini | 2 +- assets/lang/de_DE.ini | 2 +- assets/lang/en_US.ini | 2 +- assets/lang/es_ES.ini | 2 +- assets/lang/fa_IR.ini | 2 +- assets/lang/fi_FI.ini | 2 +- assets/lang/fr_FR.ini | 2 +- assets/lang/gl_ES.ini | 2 +- assets/lang/gr_EL.ini | 2 +- assets/lang/he_IL.ini | 2 +- assets/lang/hr_HR.ini | 2 +- assets/lang/hu_HU.ini | 2 +- assets/lang/id_ID.ini | 2 +- assets/lang/it_IT.ini | 2 +- assets/lang/ja_JP.ini | 2 +- assets/lang/jv_ID.ini | 2 +- assets/lang/km_KH.ini | 2 +- assets/lang/ko_KR.ini | 2 +- assets/lang/ku_SO.ini | 2 +- assets/lang/lo_LA.ini | 2 +- assets/lang/lt-LT.ini | 2 +- assets/lang/ms_MY.ini | 2 +- assets/lang/nl_NL.ini | 2 +- assets/lang/nn_NO.ini | 2 +- assets/lang/no_NO.ini | 2 +- assets/lang/pl_PL.ini | 2 +- assets/lang/pt_BR.ini | 2 +- assets/lang/pt_PT.ini | 2 +- assets/lang/ro_RO.ini | 2 +- assets/lang/ru_RU.ini | 2 +- assets/lang/sv_SE.ini | 2 +- assets/lang/tg_PH.ini | 2 +- assets/lang/tr_TR.ini | 2 +- assets/lang/uk_UA.ini | 2 +- assets/lang/vi_VN.ini | 2 +- assets/lang/zh_CN.ini | 2 +- assets/lang/zh_TW.ini | 2 +- 43 files changed, 43 insertions(+), 43 deletions(-) diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 45be54e435..9d6c4d7f56 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -1295,7 +1295,7 @@ Loaded. Game may refuse to save over different savedata. = تم التحميل. Loaded. Game may refuse to save over newer savedata. = تم التحميل. قد ترفض اللعبة الحفظ فوق بيانات حفظ أحدث. # AI translated Loaded. Save in game, restart, and load for less bugs. = تم التحميل. احفظ داخل اللعبة، ثم أعد التشغيل وحمّل الحفظ لتقليل المشاكل. # AI translated LoadStateDoesntExist = Failed to load state: Savestate doesn't exist! -LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP! +LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP! norewind = No rewind save states available. Playing = جاري اللعب PressESC = ‎إضغط خروج للدخول علي قائمة التوقف المؤقت. diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index d11bdec180..7dbf173bff 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -1283,7 +1283,7 @@ Loaded. Game may refuse to save over different savedata. = Yükləndi. Oyun, ba Loaded. Game may refuse to save over newer savedata. = Yükləndi. Oyun, yeni qorunuş veriləni üzərinə qorumağı rədd edə bilər. Loaded. Save in game, restart, and load for less bugs. = Yükləndi. Daha az yanlış üçün, oyunda qorunun, yenidən başladın və yükləyin. LoadStateDoesntExist = Durum yüklənişi uğursuz oldu: Qorunmuş durum yoxdur! -LoadStateWrongVersion = Durum yüklənişi uğursuz oldu: Qorunmuş durum köhnə PPSSPP sürümü üçündür! +LoadStateWrongVersion = Durum yüklənişi uğursuz oldu: Qorunmuş durum daha yeni PPSSPP sürümündəndir! norewind = Geri çəkiş durum qorunuşu əlçatan deyil. Playing = Oynayır PressESC = Ara seçməsini açmaq üçün ESC'ni basın. diff --git a/assets/lang/be_BY.ini b/assets/lang/be_BY.ini index 699ba0448e..e607bed1aa 100644 --- a/assets/lang/be_BY.ini +++ b/assets/lang/be_BY.ini @@ -1282,7 +1282,7 @@ Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, r Loaded. Game may refuse to save over different savedata. = Loaded. Game may refuse to save over different savedata. Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. LoadStateDoesntExist = Failed to load state: Savestate doesn't exist! -LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP! +LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP! norewind = No rewind save states available. Playing = Playing PressESC = Press ESC to open the pause menu. diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index 5575de55e2..4ad5622910 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Неуспех при зареждане на състояние: Записа не съществува! -LoadStateWrongVersion = Неуспех при зареждане на състояние: Записа е за по-стара версия на PPSSPP! +LoadStateWrongVersion = Неуспех при зареждане на състояние: Записа е от по-нова версия на PPSSPP! norewind = No rewind save states available. Playing = Playing PressESC = Натиснете ESC за да отворите pause menu-то. diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 4f9c71fff3..8331d6fbba 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Carregat. El joc pot Loaded. Game may refuse to save over newer savedata. = Carregat. El joc pot no guardar sobre dades més actuals. Loaded. Save in game, restart, and load for less bugs. = Carregat. Guarda la partida, reinicia, i recarrega per a menys errors. LoadStateDoesntExist = Error en carregar l'estat: el fitxer no existeix. -LoadStateWrongVersion = Error en carregar l'estat: el fitxer és d'una versió anterior del PPSSPP. +LoadStateWrongVersion = Error en carregar l'estat: el fitxer és d'una versió més nova del PPSSPP. norewind = No hi ha estats desats de rebobinatge disponibles. Playing = Reproduint... PressESC = Prem Esc per obrir el menú de pausa. diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index d6f52df49f..7943e8a642 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Nepodařilo se načíst stav: Uložený stav neexistuje! -LoadStateWrongVersion = Nepodařilo se načíst stav: Stav je pro starší verzi PPSSPP! +LoadStateWrongVersion = Nepodařilo se načíst stav: Stav je z novější verze PPSSPP! norewind = Nejsou dostupné žádné uložené stavy pro přetočení. Playing = Playing PressESC = Stiskněte ESC k otevření nabídky při pozastavení. diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index 0bfbaa64db..1db6afe343 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Kunne ikke hente status: Spil-status eksisterer ikke! -LoadStateWrongVersion = Kunne ikke hente stauts: Spil-status er for en ældre version af PPSSPP! +LoadStateWrongVersion = Kunne ikke hente stauts: Spil-status er fra en nyere version af PPSSPP! norewind = Ingen status for fortrydelse er tilgængelig. Playing = Playing PressESC = Tryk ESC for at åbne pause menu. diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index fadfada9c0..7a1a7bb064 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Geladen. Das Spiel ka Loaded. Game may refuse to save over newer savedata. = Geladen. Das Spiel weigert sich möglicherweise, über neuere gespeicherte Daten zu speichern. Loaded. Save in game, restart, and load for less bugs. = Geladen. Im Spiel speichern, neu starten und laden, um Fehler zu vermeiden. LoadStateDoesntExist = Fehler beim Laden des Spielstands: Spielstand existiert nicht! -LoadStateWrongVersion = Fehler beim Laden des Spielstands: Spielstand ist für eine alte Version von PPSSPP! +LoadStateWrongVersion = Fehler beim Laden des Spielstands: Spielstand stammt von einer neueren Version von PPSSPP! norewind = Keine Rückspulfunktion verfügbar Playing = Spiele PressESC = Drücke ESC, um das Pausemenü zu öffnen diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 22a6f6220a..b366446e0b 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -1313,7 +1313,7 @@ Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, r Loaded. Game may refuse to save over different savedata. = Loaded. Game may refuse to save over different savedata. Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. LoadStateDoesntExist = Failed to load state: Savestate doesn't exist! -LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP! +LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP! norewind = No rewind save states available. Playing = Playing PressESC = Press ESC to open the pause menu. diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index b4d596282a..ad20e28c1f 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -1288,7 +1288,7 @@ Loaded. Game may refuse to save over different savedata. = Cargado. El juego pue Loaded. Game may refuse to save over newer savedata. = Cargado. El juego puede negarse a guardar datos guardados más recientes. Loaded. Save in game, restart, and load for less bugs. = Cargado. Guarda el juego, reinicia y carga para evitar errores. LoadStateDoesntExist = Error al cargar el estado: ¡El estado guardado no existe! -LoadStateWrongVersion = Error al cargar el estado: ¡El estado guardado es para una versión anterior de PPSSPP! +LoadStateWrongVersion = Error al cargar el estado: ¡El estado guardado es de una versión más reciente de PPSSPP! norewind = No hay estados de rebobinado guardados disponibles. Playing = Reproduciendo... PressESC = Presiona Esc para abrir el menú de pausa. diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 36b621e7ee..647b1f7eb6 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -1284,7 +1284,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = ‎!خطا در بارگیری: هیچ ذخیره ی سریعی پیدا نشد -LoadStateWrongVersion = ‎!است PPSSPP خطا در بارگیری: ذخیره موجود برای ورژن‌های پایین تر +LoadStateWrongVersion = ‎!است PPSSPP خطا در بارگیری: ذخیره موجود برای ورژن‌های بالاتر norewind = No rewind save states available. Playing = Playing PressESC = ‎.را برای منوی سریع فشار دهید ESC دکمه diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 6fceac4290..50475c43e0 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Ladattu. Peli saattaa Loaded. Game may refuse to save over newer savedata. = Ladattu. Peli saattaa kieltäytyä tallentamasta uudelle tallennustiedolle. Loaded. Save in game, restart, and load for less bugs. = Ladattu. Tallenna pelissä, käynnistä uudelleen ja lataa peli, vähentääksesi virheitä. LoadStateDoesntExist = Tilatallennuksen lataaminen epäonnistui: Tallennusta ei ole olemassa! -LoadStateWrongVersion = Tilatallennuksen lataaminen epäonnistui: Tallennus on vanhemmalle PPSSPP-versiolle! +LoadStateWrongVersion = Tilatallennuksen lataaminen epäonnistui: Tallennus on uudemmasta PPSSPP-versiosta! norewind = Takaisinkelauksen tilatallennuksia ei ole saatavilla. Playing = Playing PressESC = Press ESC to open the pause menu. diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index d96eaa5473..2106b560c8 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -1314,7 +1314,7 @@ Loaded. Game may refuse to save over different savedata. = Chargé. Le jeu peut Loaded. Game may refuse to save over newer savedata. = Chargé. Le jeu peut refuser d'écraser les données de sauvegarde plus récentes. Loaded. Save in game, restart, and load for less bugs. = Chargé.\nSauvegarder dans le jeu, redémarrer,\net charger la sauvegarde pour moins de bugs. LoadStateDoesntExist = Échec du chargement de l'état :\nl'état n'existe pas ! -LoadStateWrongVersion = Échec du chargement de l'état :\nl'état appartient à une ancienne version de PPSSPP ! +LoadStateWrongVersion = Échec du chargement de l'état :\nl'état provient d'une version plus récente de PPSSPP ! norewind = Pas d'instantané de rembobinage disponible. Playing = En jeu PressESC = Appuyer sur "Échap" pour ouvrir le menu Pause. diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index 1c41744cf5..4892284904 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Erro ó cargar o estado: non existe o arquivo. -LoadStateWrongVersion = Erro ó cargar o estado: o arquivo é dunha versión anterior de PPSSPP. +LoadStateWrongVersion = Erro ó cargar o estado: o arquivo é dunha versión máis nova de PPSSPP. norewind = Non hai estados gardados de rebobinado dispoñibles. Playing = Playing PressESC = Presiona Esc para abrir o menú de pausa. diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index c6e6345c99..8aa3b3fbdf 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Φορτώθηκε. Αποθηκεύστε στο παιχνίδι, κάντε επανεκκίνηση και φορτώστε για λιγότερα σφάλματα. LoadStateDoesntExist = Σφάλμα φόρτωσης σημείου: Το σημείο αποθήκευσης δεν υπάρχει! -LoadStateWrongVersion = Σφάλμα φόρτωσης σημείου: Το σημείο αποθήκευσης είναι για μία παλαιότερη έκδοση του PPSSPP! +LoadStateWrongVersion = Σφάλμα φόρτωσης σημείου: Το σημείο αποθήκευσης είναι από μία νεότερη έκδοση του PPSSPP! norewind = Κανένα σημείο αποθήκευσης rewind διαθέσιμο. Playing = Παίζει PressESC = Πατήστε το ESC για να ανοίξετε το μενού παύσης. diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index ef72614653..6ba4aa3c28 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -1284,7 +1284,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = לא הצליח לטעון את המצב: המצב שמור אינו קיים! -LoadStateWrongVersion = לא הצליח לטעון את המצב: המצב הוא שמירה שמיועדת לגרסה ישנה יותר של PPSSPP! +LoadStateWrongVersion = לא הצליח לטעון את המצב: המצב הוא שמירה מגרסה חדשה יותר של PPSSPP! norewind = No rewind save states available. Playing = Playing PressESC = לחץ על ESC כדי לפתוח את התפריט. diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 39ff871c01..31b3cb74f7 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Učitano. Spremi u igri, ponovo pokreni, i učitaj za manje bugova. LoadStateDoesntExist = Nije moguće učitati state: Savestate ne postoji! -LoadStateWrongVersion = Nije moguće učitati state: Savestate je za stariju verziju PPSSPP-a! +LoadStateWrongVersion = Nije moguće učitati state: Savestate je iz novije verzije PPSSPP-a! norewind = Rewind save state-ovi nisu dostupni. Playing = Playing PressESC = Pritisni ESC za otvaranje izbornika. diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 536d306656..36f83bcee8 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Betöltve. Lehet, hog Loaded. Game may refuse to save over newer savedata. = Betöltve. Lehet, hogy a játék nem fog felülírni újabb mentéseket. Loaded. Save in game, restart, and load for less bugs. = Betöltve. Ments a játékban, indítsd újra, és töltsd be a mentést kevesebb hibáért! LoadStateDoesntExist = Hiba az állapotmentés betöltése közben: Az állapotmentés nem létezik! -LoadStateWrongVersion = Hiba az állapotmentés betöltése közben: Az állapotmentés egy régebbi PPSSPP verzióhoz való! +LoadStateWrongVersion = Hiba az állapotmentés betöltése közben: Az állapotmentés egy újabb PPSSPP verzióból származik! norewind = Nem érhető el visszatekerési állapotmentés. Playing = Lejátszás PressESC = Nyomd meg az ESC-et a menühöz. diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 6fa00d3af1..ff6ba037c4 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Termuat. Game mungkin Loaded. Game may refuse to save over newer savedata. = Termuat. Game mungkin menolak untuk menyimpan lebih dari simpanan data yang lebih baru. Loaded. Save in game, restart, and load for less bugs. = Termuat. Simpan dalam game, mulai ulang, dan muat untuk mengurangi kesalahan. LoadStateDoesntExist = Gagal memuat save state game: Savestate tidak ada! -LoadStateWrongVersion = Gagal memuat save state game: Savestate ini untuk versi lama PPSSPP! +LoadStateWrongVersion = Gagal memuat save state game: Savestate ini dari versi PPSSPP yang lebih baru! norewind = Tidak ada save state lama yang tersedia. Playing = Bermain PressESC = Tekan ESC untuk membuka menu jeda. diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index 6852599cc2..2bb61a1ea0 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -1290,7 +1290,7 @@ GLToolsWarning = AVVISO: rilevato GLTools, potrebbe causare problemi In menu = Nei menu Loaded State = Stato Caricato LoadStateDoesntExist = Caricamento stato fallito: lo stato non esiste! -LoadStateWrongVersion = Caricamento stato fallito: lo stato è di una versione precedente di PPSSPP! +LoadStateWrongVersion = Caricamento stato fallito: lo stato è di una versione più recente di PPSSPP! norewind = Nessun riavvolgimento degli stati salvati disponibile. Playing = Sta giocando PressESC = Premere ESC per aprire il Menu Pausa diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index ab10097db7..bacb369bd6 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = ロードしました Loaded. Game may refuse to save over newer savedata. = ロードしました。最新のセーブデータがある場合セーブが拒否される場合があります。 Loaded. Save in game, restart, and load for less bugs. = ロードしました。バグの発生を減らすためにゲーム中でセーブを行い、再起動してロードしてください。 LoadStateDoesntExist = ステートのロードに失敗しました: セーブステートがありません。 -LoadStateWrongVersion = ステートのロードに失敗しました: 古いバージョンのセーブステートです。 +LoadStateWrongVersion = ステートのロードに失敗しました: 新しいバージョンのセーブステートです。 norewind = 巻き戻すセーブステートがありません。 Playing = プレイ中 PressESC = ESCを押すと一時停止メニューを開きます。 diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index 761bdc515b..ba9e6915a1 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -1282,7 +1282,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Gagal memuat: File simpen-cepet ora kasedhiya! -LoadStateWrongVersion = Gagal memuat: File simpen-cepet iki kanggi versi lawas PPSSPP! +LoadStateWrongVersion = Gagal memuat: File simpen-cepet iki saka versi PPSSPP sing luwih anyar! norewind = No rewind save states available. Playing = Playing PressESC = Tekan ESC kanggo ing Menu. diff --git a/assets/lang/km_KH.ini b/assets/lang/km_KH.ini index 0cef007602..2ec333f025 100644 --- a/assets/lang/km_KH.ini +++ b/assets/lang/km_KH.ini @@ -1280,7 +1280,7 @@ Loaded. Save in game, restart, and load for less bugs. =ផ្ទុក។ រ Loaded. Game may refuse to save over different savedata. =ផ្ទុក។ ហ្គេមអាចបដិសេធមិនរក្សាទុកទិន្នន័យរក្សាទុកផ្សេងៗគ្នា។ Loaded. Game may refuse to save over newer savedata. =ផ្ទុក។ ហ្គេមអាចបដិសេធមិនរក្សាទុកទិន្នន័យរក្សាទុកថ្មីជាងនេះ។ LoadStateDoesntExist =បរាជ័យក្នុងការផ្ទុកស្ថានភាព៖ Savestate មិនមានទេ! -LoadStateWrongVersion =បរាជ័យក្នុងការផ្ទុកស្ថានភាព៖ Savestate គឺសម្រាប់កំណែចាស់របស់ PPSSPP! +LoadStateWrongVersion =បរាជ័យក្នុងការផ្ទុកស្ថានភាព៖ Savestate គឺមកពីកំណែថ្មីជាងរបស់ PPSSPP! norewind =មិន​មាន​ស្ថានភាព​រក្សា​ទុក​ថយក្រោយ​ទេ។ Playing =កំពុងលេង PressESC =ចុច ESC ដើម្បីបើកម៉ឺនុយផ្អាក។ diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 06a92d24d7..25040f3e6f 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -1287,7 +1287,7 @@ Loaded. Save in game, restart, and load for less bugs. = 불러왔습니다. 버 Loaded. Game may refuse to save over different savedata. = 불러왔습니다. 게임이 다른 저장 데이터에 대한 저장을 거부할 수 있습니다. Loaded. Game may refuse to save over newer savedata. = 불러왔습니다. 게임이 최신 저장 데이터에 대한 저장을 거부할 수 있습니다. LoadStateDoesntExist = 상태 불러오기 실패: 저장상태가 존재하지 않습니다! -LoadStateWrongVersion = 상태 불러오기 실패: 저장상태는 이전 버전의 PPSSPP용입니다! +LoadStateWrongVersion = 상태 불러오기 실패: 저장상태는 더 새로운 버전의 PPSSPP에서 만들어졌습니다! norewind = 되감기 저장 상태를 사용할 수 없습니다. Playing = 게임 중 PressESC = 일시 중지 메뉴를 열려면 ESC를 누르세요. diff --git a/assets/lang/ku_SO.ini b/assets/lang/ku_SO.ini index f1597b28ff..25a5262fab 100644 --- a/assets/lang/ku_SO.ini +++ b/assets/lang/ku_SO.ini @@ -1296,7 +1296,7 @@ Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, r Loaded. Game may refuse to save over different savedata. = Loaded. Game may refuse to save over different savedata. Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. LoadStateDoesntExist = Failed to load state: Savestate doesn't exist! -LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP! +LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP! norewind = No rewind save states available. Playing = Playing PressESC = Press ESC to open the pause menu. diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index 29481fe0b2..983f459a9d 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -1282,7 +1282,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = ລົ້ມເຫຼວໃນການໂຫຼດໄຟລ໌ບັນທຶກ: ໄຟລ໌ບັນທຶກບໍ່ຖືກຕ້ອງ! -LoadStateWrongVersion = ລົ້ມເຫຼວໃນການໂຫຼດໄຟລ໌ບັນທຶກ: ໄຟລ໌ບັນທຶກແມ່ນເວີຊັ່ນເກົ່າຂອງ PPSSPP! +LoadStateWrongVersion = ລົ້ມເຫຼວໃນການໂຫຼດໄຟລ໌ບັນທຶກ: ໄຟລ໌ບັນທຶກແມ່ນມາຈາກເວີຊັ່ນໃໝ່ກວ່າຂອງ PPSSPP! norewind = ບໍ່ມີຂໍ້ມູນໄຟລ໌ບັນທຶກແບບຍ້ອນຫຼັງ. Playing = Playing PressESC = ກົດ ESC ເພື່ອເປີດເມນູຢຸດຊົ່ວຄາວ diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 5f0bf9c38c..eb9bbd4aba 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -1284,7 +1284,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Nepavyko pakrauti statuso: išsaugojimo statusas neegzistuoja! -LoadStateWrongVersion = Nepavyko pakrauti statuso: išsaugojimo statusas skirtas senesniai PPSSPP versijai! +LoadStateWrongVersion = Nepavyko pakrauti statuso: išsaugojimo statusas yra iš naujesnės PPSSPP versijos! norewind = No rewind save states available. Playing = Playing PressESC = Paspauskite ESC, kad atidarytumėte pauzės meniu. diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 35ca80b8d9..414a282780 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Dimuatkan. Permainan Loaded. Game may refuse to save over newer savedata. = Dimuatkan. Permainan mungkin enggan menyimpan di atas data simpanan baharu. Loaded. Save in game, restart, and load for less bugs. = Dimuatkan. Simpan dalam permainan, mula semula, dan muat untuk mengurangkan bug. LoadStateDoesntExist = Gagal memuatkan state: Savestate tidak ditemui! -LoadStateWrongVersion = Gagal memuatkan state: Savestate adalah untuk versi PPSSPP yang lebih lama! +LoadStateWrongVersion = Gagal memuatkan state: Savestate adalah daripada versi PPSSPP yang lebih baharu! norewind = Tiada keadaan simpanan putar semula tersedia. Playing = Dimainkan PressESC = Tekan ESC untuk membuka menu jeda. diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 8adbecca86..569c06313d 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Laden mislukt: Savestate bestaat niet! -LoadStateWrongVersion = Laden mislukt: Savestate is voor een oudere versie van PPSSPP! +LoadStateWrongVersion = Laden mislukt: Savestate is van een nieuwere versie van PPSSPP! norewind = Geen savestates beschikbaar voor terugspoelen. Playing = Playing PressESC = Druk op ESC om het pauzemenu te openen diff --git a/assets/lang/nn_NO.ini b/assets/lang/nn_NO.ini index 103d0c0db2..c1a954ae46 100644 --- a/assets/lang/nn_NO.ini +++ b/assets/lang/nn_NO.ini @@ -1312,7 +1312,7 @@ Loaded. Save in game, restart, and load for less bugs. = Lasta inn. Lagre i spel Loaded. Game may refuse to save over different savedata. = Lasta inn. Spelet kan nekte å lagre over andre lagringsdata. Loaded. Game may refuse to save over newer savedata. = Lasta inn. Spelet kan nekte å lagre over nyare lagringsdata. LoadStateDoesntExist = Klarte ikkje å laste inn tilstand: Lagringstilstanden finst ikkje! -LoadStateWrongVersion = Klarte ikkje å laste inn tilstand: Lagringstilstanden er for ein eldre versjon av PPSSPP! +LoadStateWrongVersion = Klarte ikkje å laste inn tilstand: Lagringstilstanden er frå ein nyare versjon av PPSSPP! norewind = Ingen tilbakespolingstilstandar tilgjengelege. Playing = Spelar PressESC = Trykk ESC for å opne pausemenyen. diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 0727ae754f..f9f3bd5bb1 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -1312,7 +1312,7 @@ Loaded. Save in game, restart, and load for less bugs. = Lastet inn. Lagre i spi Loaded. Game may refuse to save over different savedata. = Lastet inn. Spillet kan nekte å lagre over andre lagringsdata. Loaded. Game may refuse to save over newer savedata. = Lastet inn. Spillet kan nekte å lagre over nyere lagringsdata. LoadStateDoesntExist = Mislyktes å laste: Lagret fil eksisterer ikke! -LoadStateWrongVersion = Mislyktes å laste: Lagret fil er for en eldre versjon av PPSSPP! +LoadStateWrongVersion = Mislyktes å laste: Lagret fil er fra en nyere versjon av PPSSPP! norewind = Ingen tilbakespolingstilstander tilgjengelig. Playing = Spiller PressESC = Trykk ESC for å åpne pausemenyen. diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 166375b331..7c46c7ad49 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -1295,7 +1295,7 @@ Loaded. Game may refuse to save over different savedata. = Wczytano. Gra może o Loaded. Game may refuse to save over newer savedata. = Wczytano. Gra może odmówić nadpisywania nowszych zapisów. Loaded. Save in game, restart, and load for less bugs. = Wczytano. By zmniejszyć ryzyko potencjalnych problemów, zapisz stan w grze, zrestartuj ją i wczytaj. LoadStateDoesntExist = Nie można wczytać stanu: Zapis stanu nie istnieje! -LoadStateWrongVersion = Nie można wczytać stanu: Zapis stanu przeznaczony jest dla starszej wersji PPSSPP! +LoadStateWrongVersion = Nie można wczytać stanu: Zapis stanu pochodzi z nowszej wersji PPSSPP! norewind = Brak zapisów stanów potrzebnych do przewinięcia. Playing = W grze PressESC = Naciśnij klawisz ESC, aby otworzyć menu pauzy. diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 7588e871a5..bd16107488 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -1310,7 +1310,7 @@ Loaded. Save in game, restart, and load for less bugs. = Carregado. Salve no jog Loaded. Game may refuse to save over different savedata. = Carregado. O jogo pode recusar a salvar sobre os dados diferentes dos salvamentos. Loaded. Game may refuse to save over newer savedata. = Carregado. O jogo pode recusar a salvar sobre os dados mais novos dos salvamentos. LoadStateDoesntExist = Falhou em carregar o state: O state salvo não existe! -LoadStateWrongVersion = Falhou em carregar o state: O state salvo é de uma versão mais velha do PPSSPP! +LoadStateWrongVersion = Falhou em carregar o state: O state salvo é de uma versão mais nova do PPSSPP! norewind = Não há states salvos disponíveis pra retroceder. Playing = Jogando PressESC = Pressione ESC pra abrir o menu da pausa. diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index a6d0f49f7a..0b87266242 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -1312,7 +1312,7 @@ Loaded. Save in game, restart, and load for less bugs. = Carregado. Salva no jog Loaded. Game may refuse to save over different savedata. = Carregado. O jogo poderá recusar-se a salvar sobre dados salvos diferentes. Loaded. Game may refuse to save over newer savedata. = Carregado. O jogo poderá recusar-se a salvar sobre dados salvos mais novos. LoadStateDoesntExist = Erro ao carregar o estado: O Estado salvo não existe! -LoadStateWrongVersion = Erro ao carregar o estado: O Estado salvo é de uma versão mais antiga da PPSSPP! +LoadStateWrongVersion = Erro ao carregar o estado: O Estado salvo é de uma versão mais recente da PPSSPP! norewind = Não há estados salvos disponíveis para retroceder. Playing = A jogar PressESC = Pressione a tecla Esc para abrir o menu de pausa. diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 35456b12bb..8daa4783c1 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs. LoadStateDoesntExist = Eroare la încărcare de stare: Starea nu există! -LoadStateWrongVersion = Eroare la încărcare de stare: Starea e pt. o versiune PPSSPP mai veche! +LoadStateWrongVersion = Eroare la încărcare de stare: Starea e de la o versiune PPSSPP mai nouă! norewind = Fără stări salvare disponibile. Playing = Playing PressESC = Apasă ESC pt. a deschide meniul pauză. diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index eb7c1aba2c..4c9c6947db 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Загружено. Loaded. Game may refuse to save over newer savedata. = Загружено. Игра может отказаться сохранять поверх более новых сохранений. Loaded. Save in game, restart, and load for less bugs. = Загружено. Сохранитесь в игре, перезапустите игру и загрузите для исправления ошибок. LoadStateDoesntExist = Не удалось загрузить состояние: файл не существует! -LoadStateWrongVersion = Не удалось загрузить состояние: файл состояния от старой версии PPSSPP! +LoadStateWrongVersion = Не удалось загрузить состояние: файл состояния от более новой версии PPSSPP! norewind = Нет доступных состояний для возврата. Playing = В игре PressESC = Нажмите ESC, чтобы открыть меню паузы. diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 358fd00894..d28feba7c4 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -1289,7 +1289,7 @@ Loaded. Game may refuse to save over different savedata. = Inläst. Spelet kan v Loaded. Game may refuse to save over newer savedata. = Inläst. Spelet kan vägra att skriva över nyare sparade data. Loaded. Save in game, restart, and load for less bugs. = Inläst. Spara i spelet, starta om och läs in igen för att minska risken för fel. LoadStateDoesntExist = Tillståndet kunde inte läsas in: det sparade tillståndet finns inte! -LoadStateWrongVersion = Tillståndet kunde inte läsas in: det är från en annan PPSSPP-version! +LoadStateWrongVersion = Tillståndet kunde inte läsas in: det är från en nyare PPSSPP-version! norewind = Det finns inga sparade tillstånd att spola tillbaka till. Playing = Spelar PressESC = Tryck på Esc för att öppna pausmenyn. diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index 24bbd1fbaf..e0de3b9abe 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -1285,7 +1285,7 @@ Loaded. Game may refuse to save over different savedata. = Nakarga. Maaaring tum Loaded. Game may refuse to save over newer savedata. = Nakarga. Maaaring tumangging mag-save ang laro sa mas bagong savedata. Loaded. Save in game, restart, and load for less bugs. = Nakarga. Mag-save sa laro, mag-restart, at mag-load para maiwasan ang mga bugs. LoadStateDoesntExist = May Problema sa load state: Walang Savestate! -LoadStateWrongVersion = May Problema sa load state: Ang savestate na eto ay para sa lumang bersiyon ng PPSSPP! +LoadStateWrongVersion = May Problema sa load state: Ang savestate na eto ay mula sa mas bagong bersiyon ng PPSSPP! norewind = Walang available na rewind save states. Playing = Naglalaro PressESC = Pindutin ang ESC para mabuksan ang pause menu. diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 9e385ab9f0..4c37913f9c 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -1288,7 +1288,7 @@ Loaded. Game may refuse to save over different savedata. = Yüklendi. Oyun belki Loaded. Game may refuse to save over newer savedata. = Yüklendi. Oyun belki yeni verilerin üzerine kaydetmeyi reddedebilir. Loaded. Save in game, restart, and load for less bugs. = Yüklendi. Daha az bug için oyunda kaydedin, yeniden başlatın ve yükleyin. LoadStateDoesntExist = Durum yüklenemedi: Durum kaydı yerinde yok! -LoadStateWrongVersion = Durum yüklenemedi: Durum kaydı eski bir PPSSPP versiyonu için uygun! +LoadStateWrongVersion = Durum yüklenemedi: Durum kaydı daha yeni bir PPSSPP versiyonundan! norewind = Geriye sarma durumu bulunamadı. Playing = Oynatılıyor... PressESC = PPSSPP menüsünü açmak için ESC'ye basın. diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index 9af12b25d6..5843c5b0bc 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = Завантажено. Збережіться в грі, перезапустіть гру і завантажте для виправлення помилок. LoadStateDoesntExist = Не вдається завантажити стан: файла не існує! -LoadStateWrongVersion = Не вдається завантажити стан: файл стану від старої версії PPSSPP! +LoadStateWrongVersion = Не вдається завантажити стан: файл стану від новішої версії PPSSPP! norewind = Немає доступних збережень для повернення. Playing = У грі PressESC = Натисніть ESC для переходу в меню diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index 0ad85e0c61..41710cf750 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata. Loaded. Save in game, restart, and load for less bugs. = loaded. lưu trong game, khởi động lại và tải để ít lỗi hơn. LoadStateDoesntExist = Không load được: không có file save! -LoadStateWrongVersion = Không load được: file save là của một phiên bản cũ của PPSSPP! +LoadStateWrongVersion = Không load được: file save là của một phiên bản mới hơn của PPSSPP! norewind = Không tua lại được, trạng thái không có sẵn.. Playing = Playing PressESC = Nhấn ESC để mở menu. diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index b9244ba0f9..abb0ca6e25 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -1288,7 +1288,7 @@ Loaded. Game may refuse to save over different savedata. = 已读取,游戏可 Loaded. Game may refuse to save over newer savedata. = 已读取,游戏可能无法保存在进度提前的存档。 Loaded. Save in game, restart, and load for less bugs. = 已读取,请在游戏中存档并重启, 再次读档以减少bug。 LoadStateDoesntExist = 即时存档读取失败:存档不存在 -LoadStateWrongVersion = 即时存档读取失败:存档是旧版本PPSSPP的 +LoadStateWrongVersion = 即时存档读取失败:存档来自较新版本的PPSSPP norewind = 没有可用的倒带自动即时存档。 Playing = 游戏中 PressESC = 按下ESC键打开暂停菜单。 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index d1b81a04af..9c4befac0f 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -1287,7 +1287,7 @@ Loaded. Save in game, restart, and load for less bugs. = 已載入,在遊戲 Loaded. Game may refuse to save over different savedata. = 已載入,遊戲可能會拒絕覆寫不同的存檔 Loaded. Game may refuse to save over newer savedata. = 已載入,遊戲可能會拒絕覆寫較新的存檔 LoadStateDoesntExist = 無法載入存檔:存檔不存在! -LoadStateWrongVersion = 無法載入存檔:存檔僅適用於舊版 PPSSPP! +LoadStateWrongVersion = 無法載入存檔:存檔來自較新版本的 PPSSPP! norewind = 沒有可用的倒轉存檔 Playing = 正在遊玩 PressESC = 按下 ESC 以開啟暫停選單