diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 87d8cbb845..dffde2d7a9 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -31,6 +31,7 @@ #include "Common/Thread/ThreadUtil.h" #include "Common/Data/Text/Parsers.h" #include "Common/System/System.h" +#include "Common/System/OSD.h" #include "Common/File/FileUtil.h" #include "Common/File/DirListing.h" @@ -424,6 +425,23 @@ int g_screenshotFailures; } } + // A session booted into the XMB inserts and ejects discs, which changes the mounts, and keeps + // doing so for a game launched from it. MetaFileSystem's state pairs its sections with mounts by + // position and doesn't record which disc was in, so a state from such a session could fail to + // load, or load one disc's file state onto another. + static bool IsVSHSession() { + return PSP_CoreParameter().fileToStart.GetFilename() == "vshmain.prx"; + } + + static bool WarnUserIfVSHSession() { + if (!IsVSHSession()) { + return false; + } + auto pa = GetI18NCategory(I18NCat::PAUSE); + g_OSD.Show(OSDType::MESSAGE_INFO, pa->T("Save states are not available in the XMB"), 3.0f, "savevsh"); + return true; + } + 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 @@ -438,6 +456,11 @@ int g_screenshotFailures; CallbackRefused(op.callback); return false; } + // Verify saves and loads back in place, with the mounts as they are. + if (op.type != OperationType::Verify && WarnUserIfVSHSession()) { + CallbackRefused(op.callback); + return false; + } std::lock_guard guard(mutex); g_pendingOperations.push_back(op); @@ -660,7 +683,7 @@ int g_screenshotFailures; void LoadSlot(std::string_view gamePrefix, int slot, Callback callback) { // Enqueue() would refuse too, but only after the load undo backup below has replaced the old one. - if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(false)) { + if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(false) || WarnUserIfVSHSession()) { CallbackRefused(callback); return; } @@ -716,7 +739,7 @@ int g_screenshotFailures; void SaveSlot(std::string_view gamePrefix, int slot, Callback callback) { // Enqueue() would refuse too, but only after the screenshots below have been rotated. - if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(true)) { + if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(true) || WarnUserIfVSHSession()) { CallbackRefused(callback); return; } @@ -1481,7 +1504,7 @@ int g_screenshotFailures; // NOTE: This can cause ending of the current renderpass, due to the readback needed for the screenshot. void Process(bool takeRewindSnapshots) { - if (takeRewindSnapshots) { + if (takeRewindSnapshots && !IsVSHSession()) { rewindStates.Process(); } RunCompletions(true); diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index ee3673dbf6..09581d5c3c 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -1082,6 +1082,7 @@ Insert UMD = Insert UMD Load State = Load state Rewind = Rewind Save State = Save state +Save states are not available in the XMB = Save states are not available in the XMB Settings = Settings Switch UMD = Switch UMD Undo last load = Undo last load diff --git a/docs/WebSocketDebugger.md b/docs/WebSocketDebugger.md index 2258345e4a..6f9f9cae0e 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 given as `path`, or a `slot` of the running game, 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), `game.umd.insert` / `game.umd.eject` (put a disc image, given as `path`, in an empty drive, or take it out: for the XMB booted with `--vsh` and no game, which then lists the disc and can launch it), `version` | `GameSubscriber.cpp` | +| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file given as `path`, or a `slot` of the running game, 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; all but this one are refused in a session booted into the XMB, whose discs come and go), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `game.umd.insert` / `game.umd.eject` (put a disc image, given as `path`, in an empty drive, or take it out: for the XMB booted with `--vsh` and no game, which then lists the disc and can launch it), `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` |