From 00c9a2d389400116388aad5a6695831aec976035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 30 Sep 2026 09:58:37 -0600 Subject: [PATCH] Utility: A savedata shutdown ends at priority 0x20 On hardware the last part of a savedata shutdown runs at priority 0x20, whatever the dialog's own thread priorities, so a caller at 0x20 gets the CPU back first and sees SHUTDOWN, and one at 0x21 or worse only sees NONE (pspautotests utility/savedata/shutdownstatus). We ended it at the access thread's priority, so Freak Out, which calls ShutdownStart from 0x20 and waits for SHUTDOWN, sat at 'Please press START' forever. NFL Street 3, which calls it from 111 and then InitStart straight away, still gets NONE. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceUtility.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 60811a7c47..538c811a43 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -736,6 +736,11 @@ void UtilityDialogShutdown(UtilityDialogType type, int delayUs, int accessPriori // The windows aren't this regular, but close. int partDelay = delayUs / 4; const int dialogPriority = PhasePriority(graphicsPriority, accessPriority); + // A savedata shutdown ends at priority 0x20, whatever the dialog's thread priorities: a caller at + // 0x20 gets the CPU back first and sees SHUTDOWN, one at 0x21 or worse only NONE + // (pspautotests utility/savedata/shutdownstatus). Freak Out calls ShutdownStart from 0x20 and + // waits for SHUTDOWN; NFL Street 3 calls it from 111 and then InitStart straight away. + const int finalPriority = type == UtilityDialogType::SAVEDATA ? 0x20 : accessPriority; const u32_le insts[] = { // Make sure we don't discard/deadbeef 'em. (u32_le)MIPS_MAKE_ORI(MIPS_REG_S0, MIPS_REG_A0, 0), @@ -749,7 +754,7 @@ void UtilityDialogShutdown(UtilityDialogType type, int delayUs, int accessPriori (u32_le)MIPS_MAKE_ORI(MIPS_REG_A0, MIPS_REG_S0, 0), (u32_le)MIPS_MAKE_SYSCALL("sceUtility", "__UtilityWorkUs"), - // Cleaning up at accessThread priority, then the status goes to NONE. + // Cleaning up at accessThread priority. (u32_le)MIPS_MAKE_ORI(MIPS_REG_A0, MIPS_REG_ZERO, 0), (u32_le)MIPS_MAKE_ORI(MIPS_REG_A1, MIPS_REG_ZERO, accessPriority), (u32_le)MIPS_MAKE_SYSCALL("ThreadManForUser", "sceKernelChangeThreadPriority"), @@ -758,13 +763,18 @@ void UtilityDialogShutdown(UtilityDialogType type, int delayUs, int accessPriori (u32_le)MIPS_MAKE_ORI(MIPS_REG_A0, MIPS_REG_S0, 0), (u32_le)MIPS_MAKE_SYSCALL("sceUtility", "__UtilityWorkUs"), + // Then the status goes to NONE, from priority finalPriority (see below). + (u32_le)MIPS_MAKE_ORI(MIPS_REG_A0, MIPS_REG_ZERO, 0), + (u32_le)MIPS_MAKE_ORI(MIPS_REG_A1, MIPS_REG_ZERO, finalPriority), + (u32_le)MIPS_MAKE_SYSCALL("ThreadManForUser", "sceKernelChangeThreadPriority"), + (u32_le)MIPS_MAKE_ORI(MIPS_REG_A0, MIPS_REG_ZERO, (int)type), (u32_le)MIPS_MAKE_JR_RA(), (u32_le)MIPS_MAKE_SYSCALL("sceUtility", "__UtilityFinishDialog"), }; - // Starting the thread reschedules normally, so a caller with worse priority than both phases sees - // NONE by the time ShutdownStart returns. + // Starting the thread reschedules normally, so a caller with worse priority than every phase + // only runs again once the status is NONE. CleanupDialogThreads(true); accessThread = new HLEHelperThread("ScePafJob", insts, (uint32_t)ARRAY_SIZE(insts), accessPriority, 0x200); accessThread->Start(partDelay, 0);