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..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; } } @@ -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) { @@ -349,7 +354,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/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index 909942b4d6..c925b724ec 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,33 @@ 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. 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. +// - 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 +116,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 +124,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 +175,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 +217,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/Core/SaveState.cpp b/Core/SaveState.cpp index 630d231119..82a890a2ba 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++) { @@ -763,6 +804,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."); @@ -857,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; } @@ -871,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; } @@ -917,11 +974,16 @@ int g_screenshotFailures; g_lastSaveTime = time_now_d(); } 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()); + // 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 = sc->T(errorString, i18nLoadFailure); + callbackMessage = errorString.empty() ? std::string(i18nLoadFailure) : std::string(sc->T(errorString, i18nLoadFailure)); + callbackMetadata = SavedByVersion(op.path); callbackResult = Status::FAILURE; } break; @@ -964,10 +1026,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"); @@ -994,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/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..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 { @@ -18,9 +19,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; @@ -38,15 +37,35 @@ 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_) { + count_++; + } } 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; - 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,37 +77,66 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std:: if (Empty()) return CChunkFileReader::ERROR_BAD_FILE; - int n = (--next_ + size_) % size_; - if (states_[n].empty()) - return CChunkFileReader::ERROR_BAD_FILE; - - auto pa = GetI18NCategory(I18NCat::PAUSE); + // 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_; 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. - 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_); 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(); @@ -103,7 +151,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 +207,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(); } @@ -176,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 da6a2c1afa..66626f28f8 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,23 +44,30 @@ public: double NextStateTimestamp() const; 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; } }; - 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_; @@ -78,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/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; } 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; } } 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 以開啟暫停選單 diff --git a/docs/WebSocketDebugger.md b/docs/WebSocketDebugger.md index 3ca6b81899..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.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` | diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 7dd2871a7a..317503e890 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -1390,10 +1390,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;