From 41b5074a985154176f8f96cbfa6d86f6162eba33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2026 11:09:43 -0600 Subject: [PATCH] Utility: Release volatile memory on a forced shutdown, fix dialog savestates - The Yugioh savedata workaround force-stopped the other dialog without releasing the volatile memory it held, so the savedata helper then waited for it forever. - Screenshot's ShutdownStart and Update succeeded when no screenshot was running, putting it back into SHUTDOWN. - Savestates: Netconf didn't keep its request address, and a DNS json download going on was gone after a load, so it could wait for it forever; it now fetches the json again (it's cached). NpSignin didn't keep its request address either. Both restart their timeouts instead of timing out at once. With states from before, they keep the current address as they used to. - Loading a state from before NpSignin, GameSharing or HtmlViewer were saved resets them rather than keeping this session's state (including the HtmlViewer's memory block), and without Shutdown's side effects, which would write to the loaded memory and release its volatile lock. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Dialog/PSPDialog.cpp | 9 +++++++++ Core/Dialog/PSPDialog.h | 3 +++ Core/Dialog/PSPHtmlViewerDialog.cpp | 8 ++++++++ Core/Dialog/PSPHtmlViewerDialog.h | 1 + Core/Dialog/PSPNetconfDialog.cpp | 20 +++++++++++++++++--- Core/Dialog/PSPNpSigninDialog.cpp | 8 ++++++-- Core/Dialog/PSPScreenshotDialog.cpp | 11 +++++++++++ Core/Dialog/PSPScreenshotDialog.h | 1 + Core/HLE/sceUtility.cpp | 13 ++++++++++++- 9 files changed, 68 insertions(+), 6 deletions(-) diff --git a/Core/Dialog/PSPDialog.cpp b/Core/Dialog/PSPDialog.cpp index c87a35dc1a..b1cd357cbe 100644 --- a/Core/Dialog/PSPDialog.cpp +++ b/Core/Dialog/PSPDialog.cpp @@ -273,6 +273,15 @@ u32 PSPDialog::CalcFadedColor(u32 inColor) const { return (inColor & 0x00FFFFFF) | (alpha << 24); } +void PSPDialog::ResetState() { + status = SCE_UTILITY_STATUS_NONE; + pendingStatus = SCE_UTILITY_STATUS_NONE; + pendingStatusTicks = 0; + volatileLocked_ = false; + isFading = false; + fadeValue = 0; +} + void PSPDialog::DoState(PointerWrap &p) { auto s = p.Section("PSPDialog", 1, 3); if (!s) diff --git a/Core/Dialog/PSPDialog.h b/Core/Dialog/PSPDialog.h index 7272225533..48a46300a5 100644 --- a/Core/Dialog/PSPDialog.h +++ b/Core/Dialog/PSPDialog.h @@ -51,6 +51,9 @@ public: virtual int Update(int animSpeed) = 0; virtual int Shutdown(bool force = false); virtual void DoState(PointerWrap &p); + // For loading a state that doesn't have this dialog: back to NONE, without anything Shutdown does + // on the way (writing results, releasing volatile memory), which would hit the loaded state. + virtual void ResetState(); virtual pspUtilityDialogCommon *GetCommonParam() { // This is returned properly by the derived classes (or should be...). return nullptr; diff --git a/Core/Dialog/PSPHtmlViewerDialog.cpp b/Core/Dialog/PSPHtmlViewerDialog.cpp index 391a4d5185..1c9559e5c0 100644 --- a/Core/Dialog/PSPHtmlViewerDialog.cpp +++ b/Core/Dialog/PSPHtmlViewerDialog.cpp @@ -191,6 +191,14 @@ int PSPHtmlViewerDialog::Shutdown(bool force) { return 0; } +void PSPHtmlViewerDialog::ResetState() { + PSPDialog::ResetState(); + // The block belonged to this session's memory, not the loaded one. + workMemory_ = 0; + started_ = false; + params_ = 0; +} + void PSPHtmlViewerDialog::DoState(PointerWrap &p) { PSPDialog::DoState(p); diff --git a/Core/Dialog/PSPHtmlViewerDialog.h b/Core/Dialog/PSPHtmlViewerDialog.h index aa57f54ac4..5a5868a68b 100644 --- a/Core/Dialog/PSPHtmlViewerDialog.h +++ b/Core/Dialog/PSPHtmlViewerDialog.h @@ -35,6 +35,7 @@ public: int Update(int animSpeed) override; int Shutdown(bool force = false) override; void DoState(PointerWrap &p) override; + void ResetState() override; pspUtilityDialogCommon *GetCommonParam() override; // Until one has started, all the HtmlViewer calls return WRONG_TYPE. diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp index b361a0e886..64426ce490 100644 --- a/Core/Dialog/PSPNetconfDialog.cpp +++ b/Core/Dialog/PSPNetconfDialog.cpp @@ -344,7 +344,8 @@ int PSPNetconfDialog::Update(int animSpeed) { EndDraw(); } - if (ReadStatus() == SCE_UTILITY_STATUS_FINISHED || pendingStatus == SCE_UTILITY_STATUS_FINISHED) + const bool finished = ReadStatus() == SCE_UTILITY_STATUS_FINISHED || pendingStatus == SCE_UTILITY_STATUS_FINISHED; + if (finished && Memory::IsValidAddress(requestAddr)) Memory::Memcpy(requestAddr, &request, std::min((u32)request.common.size, (u32)sizeof(request)), "NetConfDialogParam"); return 0; @@ -365,7 +366,7 @@ int PSPNetconfDialog::Shutdown(bool force) { void PSPNetconfDialog::DoState(PointerWrap &p) { PSPDialog::DoState(p); - auto s = p.Section("PSPNetconfigDialog", 0, 2); + auto s = p.Section("PSPNetconfigDialog", 0, 3); if (!s) return; @@ -380,9 +381,22 @@ void PSPNetconfDialog::DoState(PointerWrap &p) { scanStep = 0; connResult = -1; } + if (s >= 3) { + Do(p, requestAddr); + Do(p, showNoWlanNotice_); + } else if (p.mode == p.MODE_READ) { + // requestAddr is kept: most likely the same address in the same game. + showNoWlanNotice_ = !g_Config.bEnableWlan; + } if (p.mode == p.MODE_READ) { - startTime = 0; + // The connect timeout starts over. The DNS config the json gave isn't in the state, so get + // it again (it's cached). + startTime = (u64)(time_now_d() * 1000000.0); + jsonReady_ = false; + if (ReadStatus() != SCE_UTILITY_STATUS_NONE) { + StartInfraJsonDownload(); + } } } diff --git a/Core/Dialog/PSPNpSigninDialog.cpp b/Core/Dialog/PSPNpSigninDialog.cpp index 064c54fb11..c97f6e39d7 100644 --- a/Core/Dialog/PSPNpSigninDialog.cpp +++ b/Core/Dialog/PSPNpSigninDialog.cpp @@ -185,16 +185,20 @@ int PSPNpSigninDialog::Shutdown(bool force) { void PSPNpSigninDialog::DoState(PointerWrap &p) { PSPDialog::DoState(p); - auto s = p.Section("PSPNpSigninDialog", 1, 1); + auto s = p.Section("PSPNpSigninDialog", 1, 2); if (!s) return; Do(p, request); Do(p, step); //Do(p, npSigninResult); + // Older states keep this session's requestAddr: most likely the same address in the same game. + if (s >= 2) { + Do(p, requestAddr); + } if (p.mode == p.MODE_READ) { - startTime = 0; + startTime = (u64)(time_now_d() * 1000000.0); } } diff --git a/Core/Dialog/PSPScreenshotDialog.cpp b/Core/Dialog/PSPScreenshotDialog.cpp index dcef0c30c5..85b48d4f7f 100644 --- a/Core/Dialog/PSPScreenshotDialog.cpp +++ b/Core/Dialog/PSPScreenshotDialog.cpp @@ -81,6 +81,9 @@ int PSPScreenshotDialog::Init(u32 paramAddr) { } int PSPScreenshotDialog::Update(int animSpeed) { + if (ReadStatus() == SCE_UTILITY_STATUS_NONE) { + return SCE_ERROR_UTILITY_INVALID_STATUS; + } UpdateCommon(); if (UseAutoStatus()) { if (ReadStatus() == SCE_UTILITY_STATUS_INITIALIZE) { @@ -98,6 +101,14 @@ int PSPScreenshotDialog::Update(int animSpeed) { return 0; } +int PSPScreenshotDialog::Shutdown(bool force) { + // TODO: Only from FINISHED, like the other dialogs? Update moves on to SHUTDOWN by itself here. + if (!force && ReadStatus() == SCE_UTILITY_STATUS_NONE) { + return SCE_ERROR_UTILITY_INVALID_STATUS; + } + return PSPDialog::Shutdown(force); +} + int PSPScreenshotDialog::ContStart() { // Based on JPCSP http://code.google.com/p/jpcsp/source/detail?r=3381 if (ReadStatus() != SCE_UTILITY_STATUS_SCREENSHOT_UNKNOWN) diff --git a/Core/Dialog/PSPScreenshotDialog.h b/Core/Dialog/PSPScreenshotDialog.h index c5eb2c0c06..48d6a1679d 100644 --- a/Core/Dialog/PSPScreenshotDialog.h +++ b/Core/Dialog/PSPScreenshotDialog.h @@ -29,6 +29,7 @@ public: int Init(u32 paramAddr); int Update(int animSpeed) override; + int Shutdown(bool force = false) override; int ContStart(); void DoState(PointerWrap &p) override; pspUtilityDialogCommon *GetCommonParam() override; diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 054b2b0a5e..77f643ab9b 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -609,13 +609,21 @@ void __UtilityDoState(PointerWrap &p) { lastSaveStateVersion = -1; } else { lastSaveStateVersion = s.Version(); + if (p.mode == PointerWrap::MODE_READ) { + npSigninDialog->ResetState(); + } } + // Dialogs an older state doesn't have mustn't keep this session's state. if (s >= 8) { gameSharingDialog->DoState(p); + } else if (p.mode == PointerWrap::MODE_READ) { + gameSharingDialog->ResetState(); } if (s >= 9) { htmlViewerDialog->DoState(p); + } else if (p.mode == PointerWrap::MODE_READ) { + htmlViewerDialog->ResetState(); } if (!hasAccessThread && accessThread) { @@ -790,6 +798,9 @@ static int UtilityFinishDialog(int type) { static int sceUtilitySavedataInitStart(u32 paramAddr) { if (UtilityDialogBusy() && currentDialogType != UtilityDialogType::SAVEDATA && PSP_CoreParameter().compat.flags().YugiohSaveFix) { WARN_LOG_REPORT(Log::sceUtility, "Yugioh Savedata Correction (state=%d)", lastSaveStateVersion); + PSPDialog *other = CurrentDialog(currentDialogType); + // Its own lock first, so the unlock below only takes one its thread held. + other->FinishVolatile(); if (accessThread) { accessThread->Terminate(); delete accessThread; @@ -799,7 +810,7 @@ static int sceUtilitySavedataInitStart(u32 paramAddr) { // Try to unlock in case other dialog was shutting down. KernelVolatileMemUnlock(0); } - CurrentDialog(currentDialogType)->Shutdown(true); + other->Shutdown(true); } return hleLogDebug(Log::sceUtility, UtilityInitStart(UtilityDialogType::SAVEDATA, saveDialog, paramAddr));