From 9fec7d7fb0f68582f177140fc8025d9def304115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Oct 2026 17:55:37 +0200 Subject: [PATCH] VSH: A game launched from the XMB exits back to it On a PSP, sceKernelExitGame in a game the XMB launched goes back to the XMB. Ours ended the session. In a session booted as vshmain.prx, an exit from anything but the VSH itself now LoadExecs vshmain.prx again; the shell then plays its opening and comes back on the Game column. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceKernel.cpp | 8 ++++++++ Core/HLE/sceKernelModule.cpp | 17 +++++++++++++++++ Core/HLE/sceKernelModule.h | 3 +++ 3 files changed, 28 insertions(+) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index fc82813739..edfe17bb94 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -355,6 +355,10 @@ std::string __KernelStateSummary() { void sceKernelExitGame() { INFO_LOG(Log::sceKernel, "sceKernelExitGame"); + if (__KernelExitToVSH(currentMIPS)) { + hleNoLogVoid(); + return; + } __KernelSwitchOffThread("game exited"); Core_Stop(); @@ -365,6 +369,10 @@ void sceKernelExitGame() { void sceKernelExitGameWithStatus() { INFO_LOG(Log::sceKernel, "sceKernelExitGameWithStatus"); + if (__KernelExitToVSH(currentMIPS)) { + hleNoLogVoid(); + return; + } __KernelSwitchOffThread("game exited"); Core_Stop(); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 411d8e20df..773921cd48 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2678,6 +2678,23 @@ int sceKernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr) { return hleLogDebug(Log::sceModule, 0); } +bool __KernelExitToVSH(MIPSState *mips) { + // A session that started in the XMB, running a game it launched. The XMB itself exiting (power + // off) still ends the session. + if (PSP_CoreParameter().fileToStart.GetFilename() != "vshmain.prx" || g_runningVSH) { + return false; + } + std::string error_string; + if (!__KernelLoadExec(mips, "flash0:/vsh/module/vshmain.prx", 0, &error_string)) { + ERROR_LOG(Log::sceModule, "Couldn't return to the VSH: %s", error_string.c_str()); + return false; + } + if (gpu) { + gpu->Reinitialize(); + } + return true; +} + static u32 KernelLoadModuleFromData(const char *name, std::vector &fileData, u32 flags, const SceKernelLMOption *lmoption, bool *tryLoadExec, const u8 *secureId = nullptr, const u32 *fuseId = nullptr); // What sceKernelLoadModule does once the file has been read: decrypt the module, place it in memory diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index 0490a1aa70..39c6a7547b 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -244,6 +244,9 @@ bool __KernelLoadGEDump(MIPSState *mips, std::string_view base_filename, std::st bool __KernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr, std::string *error_string); // The VSH (the XMB) is what's running, not a game it launched. bool __KernelIsRunningVSH(); +// What sceKernelExitGame does in a game the XMB launched, as on a PSP: back to the XMB. False if this +// session didn't start in the XMB. +bool __KernelExitToVSH(MIPSState *mips); // Exposed so sceVshBridge can reuse it directly for vshKernelLoadModuleBufferVSH, matching JPCSP. SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr); bool __KernelLoadExecFromBuffer(MIPSState *mips, const u8 *data, size_t size, u32 paramPtr, std::string *error_string);