From 4667f5e2da7f35246dd39ffedb33f479a65690f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 18 Aug 2026 17:01:55 +0200 Subject: [PATCH] HLE: implement sceKernelLoadModuleVSH - the VSH now draws its UI ModuleMgrForKernel/0xD5DDAB1F is how the VSH loads its own plugins. The XMB's interface lives in flash0:/vsh/module/*_plugin.prx and vshmain pulls those in through this kernel call rather than the user-mode sceKernelLoadModule, so the existing note that vshmain never imports sceKernelLoadModule was true but incomplete - it imports this instead, and it was unresolved. The consequence was quiet: vshmain got no module id back and then called sceKernelStartModule with id 0, which failed with UNKNOWN_MODULE. None of the plugins that populate the XMB ever ran. The scene still had its containers, which is why every frame set up render state per node and drew nothing inside them - the "6x render-state-setup, 0 draws" symptom this investigation has been chasing. Also implements 0xD86DD11B sceKernelSearchModuleByName, the other unresolved ModuleMgrForKernel import. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 --- Core/HLE/sceKernelModule.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index b4be647332..0f96b062dc 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2939,6 +2939,31 @@ static u32 sceKernelLoadModuleForLoadExecVSHDisc(const char *name, u32 flags, u3 return sceKernelLoadModule(name, flags, optionAddr); } +// How the VSH loads its own plugins - the XMB's UI lives in flash0:/vsh/module/*_plugin.prx and +// vshmain pulls them in through this, not through the user-mode sceKernelLoadModule. Without it +// the import went unresolved, so vshmain got no module id back and the sceKernelStartModule(0) +// that followed failed with UNKNOWN_MODULE - which is why the XMB rendered its containers but +// never had anything to draw inside them. +// +// The apitype the name refers to only affects what the loaded module may itself do; loading is +// otherwise the ordinary path, same as sceKernelLoadModuleForLoadExecVSHDisc above. +static u32 sceKernelLoadModuleVSH(const char *name, u32 flags, u32 optionAddr) { + return sceKernelLoadModule(name, flags, optionAddr); +} + +// Looks up an already-loaded module by the name in its module info, returning its uid. +static u32 sceKernelSearchModuleByName(const char *name) { + if (!name) + return hleLogError(Log::Loader, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "null name"); + for (SceUID moduleId : loadedModules) { + u32 error; + PSPModule *module = kernelObjects.Get(moduleId, error); + if (module && !strncmp(module->nm.name, name, ARRAY_SIZE(module->nm.name))) + return hleLogInfo(Log::Loader, module->GetUID()); + } + return hleLogWarning(Log::Loader, SCE_KERNEL_ERROR_UNKNOWN_MODULE, "module '%s' not found", name); +} + const HLEFunction ModuleMgrForUser[] = { {0X977DE386, &WrapU_CUU, "sceKernelLoadModule", 'x', "sxx" }, {0XB7F46618, &WrapU_UUU, "sceKernelLoadModuleByID", 'x', "xxx" }, @@ -2970,6 +2995,8 @@ const HLEFunction ModuleMgrForKernel[] = { {0x644395E2, &WrapU_UUU, "sceKernelGetModuleIdList", 'x', "xxx", HLE_KERNEL_SYSCALL }, {0X2E0911AA, &WrapU_U, "sceKernelUnloadModule", 'x', "x" , HLE_KERNEL_SYSCALL }, {0xD675EBB8, &WrapU_UUU, "sceKernelSelfStopUnloadModule", 'x', "xxx", HLE_KERNEL_SYSCALL }, + {0xD5DDAB1F, &WrapU_CUU, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL }, + {0xD86DD11B, &WrapU_C, "sceKernelSearchModuleByName", 'x', "s", HLE_KERNEL_SYSCALL }, }; void Register_ModuleMgrForUser() {