From 2e5edcd29856cc813b210c966cc8d4c7d5d00773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 28 Sep 2026 09:25:09 -0600 Subject: [PATCH] Savestate: Don't break event callbacks when a save fails CoreTiming::DoState replaced every event's callback with the anti-crash one in every mode, relying on each module's restore to put it back. A save that failed partway never got to those, and left the running game with events that break into the debugger. The missing-section fallbacks then also ran on the save: cheats and the mic re-registered events into the wrong slots, and achievements reset the runtime. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/CoreTiming.cpp | 16 ++++++++++------ Core/CwCheat.cpp | 11 +++++++---- Core/HLE/sceUsbMic.cpp | 10 ++++++---- Core/RetroAchievements.cpp | 5 ++++- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index d290a885ba..5708f075f0 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -544,13 +544,17 @@ void DoState(PointerWrap &p) { return; } - // These (should) be filled in later by the modules. - for (int i = 0; i < current; ++i) { - event_types[i].callback = AntiCrashCallback; - event_types[i].name = "INVALID EVENT"; + // These (should) be filled in later by the modules. Only when loading: a save that fails partway + // wouldn't get to all the restores, and would leave the running game with broken events. + if (p.mode == PointerWrap::MODE_READ) { + for (int i = 0; i < current; ++i) { + event_types[i].callback = AntiCrashCallback; + event_types[i].name = "INVALID EVENT"; + } + nextEventTypeRestoreId = n - 1; + usedEventTypes.clear(); } - nextEventTypeRestoreId = n - 1; - usedEventTypes.clear(); + // Needed in every pass, or each restore would look like a duplicate and get a new id. restoredEventTypes.clear(); if (s >= 3) { diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 2b02f85bd8..a0ae9f8e40 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -270,10 +270,13 @@ void __CheatDoState(PointerWrap &p) { // CoreTiming::DoState has already replaced the queue with the state's, which has no cheat // event in it, so without scheduling one here hleCheat never runs again and cheats (plus // the enable/disable polling) stay dead for the rest of the session. - CheatEvent = -1; - CoreTiming::RestoreRegisterEvent(CheatEvent, "CheatEvent", &hleCheat); - CoreTiming::RemoveEvent(CheatEvent); - CoreTiming::ScheduleEvent(msToCycles(GetRefreshMs()), CheatEvent, 0); + // (Not when a save failed earlier and every section after comes back missing.) + if (p.mode == PointerWrap::MODE_READ) { + CheatEvent = -1; + CoreTiming::RestoreRegisterEvent(CheatEvent, "CheatEvent", &hleCheat); + CoreTiming::RemoveEvent(CheatEvent); + CoreTiming::ScheduleEvent(msToCycles(GetRefreshMs()), CheatEvent, 0); + } return; } diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp index 7db1fe5a9b..da58ec9185 100644 --- a/Core/HLE/sceUsbMic.cpp +++ b/Core/HLE/sceUsbMic.cpp @@ -121,10 +121,12 @@ void __UsbMicShutdown() { void __UsbMicDoState(PointerWrap &p) { auto s = p.Section("sceUsbMic", 0, 3); if (!s) { - // Still need to restore the event. - eventMicBlockingResume = -1; - CoreTiming::RestoreRegisterEvent(eventMicBlockingResume, "MicBlockingResume", &__MicBlockingResume); - waitingThreads.clear(); + // Still need to restore the event (unless this is a save that failed earlier.) + if (p.mode == p.MODE_READ) { + eventMicBlockingResume = -1; + CoreTiming::RestoreRegisterEvent(eventMicBlockingResume, "MicBlockingResume", &__MicBlockingResume); + waitingThreads.clear(); + } return; } bool isMicStartedNow = Microphone::isMicStarted(); diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index a8f982b414..5519b25ee4 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -877,7 +877,10 @@ void Idle() { void DoState(PointerWrap &p) { auto sw = p.Section("Achievements", 0, 1); if (!sw) { - // Save state is missing the section. + // Save state is missing the section, or this is a save that failed earlier. + if (p.mode != PointerWrap::MODE_READ) { + return; + } // Reset the runtime. if (HasAchievementsOrLeaderboards()) { auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);