From e7e8d362f0b7bc2a03e7fbdf28d04a722927b822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Aug 2026 01:33:08 +0200 Subject: [PATCH] Add sceResmgr, which is what the VSH uses to decrypt the XMB index Found the cause of the red error screen the VSH ends on. Every resource load in the boot succeeds - fonts, all the plugin RCOs, topmenu_icon.rco - and then: sceIoOpen(flash0:/vsh/etc/index_02g.dat) -> fd 8 sceIoRead(8, 092a2d40, 496) sceIoClose(8) unresolved import sceResmgr/9dc14891, called from 'vsh_module' sceKernelExitDeleteThread(1) index_02g.dat is the index of what the XMB displays, and it is encrypted (it starts "PSPsysGP"). sceResmgr_9DC14891 decrypts it. There was no sceResmgr module at all, so the call trapped, the index stayed encrypted, and the ScePafJob thread building the top menu exited - a shell with everything loaded and nothing to show. This adds the module and the three tags it needs (0x0B2B90F0/91F0/92F0, keys and code 0x5C) to PrxDecrypter. It is not the whole fix yet: pspDecryptPRX() tries decryption types 0, 1, 2, 5 and 6, and this needs type 9, which JPCSP passes explicitly. So the call is now reached and fails cleanly with a logged error instead of trapping, but does not yet decrypt. Type 9 is a variant of type 2 and is the next job; the notes in docs/VSHBootInvestigation.md say where it is in JPCSP and how to check a port (159 bytes out, starting "release:"). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 --- Core/CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 +++ Core/ELF/PrxDecrypter.cpp | 12 ++++- Core/HLE/HLETables.cpp | 3 ++ Core/HLE/sceImpose.cpp | 47 +++++++++++++++--- Core/HLE/sceResmgr.cpp | 76 +++++++++++++++++++++++++++++ Core/HLE/sceResmgr.h | 20 ++++++++ UWP/CoreUWP/CoreUWP.vcxproj | 2 + UWP/CoreUWP/CoreUWP.vcxproj.filters | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 12 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 Core/HLE/sceResmgr.cpp create mode 100644 Core/HLE/sceResmgr.h diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 316cec432c..0e938539bc 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -538,6 +538,8 @@ add_library(Core STATIC HLE/proAdhocServer.cpp HLE/sceOpenPSID.cpp HLE/sceOpenPSID.h + HLE/sceResmgr.cpp + HLE/sceResmgr.h HLE/sceP3da.cpp HLE/sceP3da.h HLE/sceMt19937.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 574123cdad..f493c40f25 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -645,6 +645,7 @@ + @@ -1155,6 +1156,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 7b7bf9c5cf..c26c14f463 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -375,6 +375,9 @@ HLE\Libraries + + HLE\Libraries + HLE\Libraries @@ -1698,6 +1701,9 @@ HLE\Libraries + + HLE\Libraries + HLE\Libraries diff --git a/Core/ELF/PrxDecrypter.cpp b/Core/ELF/PrxDecrypter.cpp index 795dab1865..c199970e6f 100644 --- a/Core/ELF/PrxDecrypter.cpp +++ b/Core/ELF/PrxDecrypter.cpp @@ -293,8 +293,13 @@ static const u32 g_keyUPDATER_PSAR[] = { 0x62E86C81, 0x03299B96, 0x73AFAE5A, 0x40A05320, 0x10664BE8, 0xE5B76A99, 0x29E0DD70, 0xEA602428, 0x2042AE30, 0x946F8D32, 0xA29E5F71, 0x7C0C7FD5}; -struct TAG_INFO -{ +// Used by sceResmgr_9DC14891, which is how the VSH decrypts flash0:/vsh/etc/index_XXg.dat - the +// XMB item index, and the only thing on the boot path that needs these. +static const u8 keys_9DC14891_1[] = {0x39, 0xF7, 0xDF, 0x19, 0xD7, 0x10, 0xEA, 0x9F, 0x02, 0xDB, 0x3F, 0xB1, 0x10, 0x9F, 0x26, 0x6B}; +static const u8 keys_9DC14891_2[] = {0x46, 0x1D, 0xC9, 0xC2, 0x1D, 0x44, 0xA6, 0x68, 0xF2, 0x06, 0x37, 0xBF, 0x62, 0xCD, 0x11, 0x9E}; +static const u8 keys_9DC14891_3[] = {0x11, 0x0D, 0x1A, 0x4C, 0x8A, 0x19, 0x17, 0xDC, 0xD0, 0x5A, 0x65, 0x47, 0xA5, 0x03, 0x85, 0x22}; + +struct TAG_INFO { u32 tag; // 4 byte value at offset 0xD0 in the PRX file const u32 *key; // "step1_result" use for XOR step u8 code; @@ -347,6 +352,9 @@ struct TAG_INFO2 static const TAG_INFO2 g_tagInfo2[] = { + { 0x0B2B90F0, keys_9DC14891_1, 0x5C }, + { 0x0B2B91F0, keys_9DC14891_2, 0x5C }, + { 0x0B2B92F0, keys_9DC14891_3, 0x5C }, { 0x4C9494F0, keys660_k1, 0x43 }, { 0x4C9495F0, keys660_k2, 0x43 }, { 0x4C9490F0, keys660_k3, 0x43 }, diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index 03b4318dfe..d76e9eb3ac 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -58,6 +58,7 @@ #include "sceNp.h" #include "sceMpeg.h" #include "sceOpenPSID.h" +#include "sceResmgr.h" #include "sceP3da.h" #include "sceParseHttp.h" #include "sceParseUri.h" @@ -332,6 +333,8 @@ void RegisterAllModules() { Register_sceHprm_driver(); Register_sceChkreg(); Register_sceVshBridge(); + Register_sceResmgr(); + // add new modules here. // Not ready to enable this due to apparent softlocks in Patapon 3. diff --git a/Core/HLE/sceImpose.cpp b/Core/HLE/sceImpose.cpp index d01be2228a..7ac71c349a 100644 --- a/Core/HLE/sceImpose.cpp +++ b/Core/HLE/sceImpose.cpp @@ -153,7 +153,7 @@ static int sceImpose_driver_B497314D(int param, u32 resultAddr) { // The settings sceImposeGetParam/SetParam address, as a bitfield - sceImposeChanges reports which // of them have been changed since it was last asked, so the VSH can refresh only what moved. -enum { +enum : u32 { PSP_IMPOSE_MAIN_VOLUME = 0x1, PSP_IMPOSE_BACKLIGHT_BRIGHTNESS = 0x2, PSP_IMPOSE_EQUALIZER_MODE = 0x4, @@ -162,16 +162,33 @@ enum { PSP_IMPOSE_TIME_FORMAT = 0x20, PSP_IMPOSE_DATE_FORMAT = 0x40, PSP_IMPOSE_LANGUAGE = 0x80, + PSP_IMPOSE_00000100 = 0x100, PSP_IMPOSE_BACKLIGHT_OFF_INTERVAL = 0x200, PSP_IMPOSE_SOUND_REDUCTION = 0x400, + // Named after their own values, as in JPCSP - real meanings unknown. + PSP_IMPOSE_20000000 = 0x20000000, + PSP_IMPOSE_80000001 = 0x80000001, + PSP_IMPOSE_80000002 = 0x80000002, + PSP_IMPOSE_80000003 = 0x80000003, + PSP_IMPOSE_80000004 = 0x80000004, + PSP_IMPOSE_80000005 = 0x80000005, + PSP_IMPOSE_80000006 = 0x80000006, + PSP_IMPOSE_80000007 = 0x80000007, + PSP_IMPOSE_80000008 = 0x80000008, + PSP_IMPOSE_80000009 = 0x80000009, + PSP_IMPOSE_8000000A = 0x8000000A, + PSP_IMPOSE_8000000B = 0x8000000B, }; // Returns the current value of one setting. Values follow JPCSP's, which are the plausible -// defaults for a console with nothing unusual configured. Anything we don't model reads as 0 -// rather than failing - the VSH polls this every frame for its indicators, so returning an error -// would be far more disruptive than returning a quiet default. +// defaults for a console with nothing unusual configured. +// +// The split between the last two cases matters and matches JPCSP: a parameter that exists but that +// we don't model reads as 0, while one that isn't a parameter at all is rejected. Answering 0 to +// everything tells a caller probing for a feature that the feature is there and switched off, +// which is a different thing from "no such setting". static int sceImposeGetParam(int param) { - switch (param) { + switch ((u32)param) { case PSP_IMPOSE_MAIN_VOLUME: return hleLogDebug(Log::sceUtility, 30); // Full, on a 0-30 scale. case PSP_IMPOSE_BACKLIGHT_BRIGHTNESS: @@ -182,8 +199,26 @@ static int sceImposeGetParam(int param) { case PSP_IMPOSE_SOUND_REDUCTION: case PSP_IMPOSE_BACKLIGHT_OFF_INTERVAL: return hleLogDebug(Log::sceUtility, 0); + case PSP_IMPOSE_EQUALIZER_MODE: + case PSP_IMPOSE_TIME_FORMAT: + case PSP_IMPOSE_DATE_FORMAT: + case PSP_IMPOSE_LANGUAGE: + case PSP_IMPOSE_00000100: + case PSP_IMPOSE_20000000: + case PSP_IMPOSE_80000001: + case PSP_IMPOSE_80000002: + case PSP_IMPOSE_80000003: + case PSP_IMPOSE_80000004: + case PSP_IMPOSE_80000005: + case PSP_IMPOSE_80000006: + case PSP_IMPOSE_80000007: + case PSP_IMPOSE_80000008: + case PSP_IMPOSE_80000009: + case PSP_IMPOSE_8000000A: + case PSP_IMPOSE_8000000B: + return hleLogDebug(Log::sceUtility, 0, "param %08x not modelled", param); default: - return hleLogDebug(Log::sceUtility, 0, "unmodelled param %08x", param); + return hleLogWarning(Log::sceUtility, SCE_KERNEL_ERROR_INVALID_MODE, "invalid param %08x", param); } } diff --git a/Core/HLE/sceResmgr.cpp b/Core/HLE/sceResmgr.cpp new file mode 100644 index 0000000000..e816c4ddd5 --- /dev/null +++ b/Core/HLE/sceResmgr.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include + +// MemMap.h first: PrxDecrypter.h's PSP_Header is written in u32_le and friends, and doesn't pull +// in the header that defines them. +#include "Core/MemMap.h" +#include "Core/Debugger/MemBlockInfo.h" +#include "Core/ELF/PrxDecrypter.h" +#include "Core/HLE/FunctionWrappers.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/sceResmgr.h" + +// The resource manager. Games never touch this - it exists for the VSH, which uses the one +// function below to decrypt flash0:/vsh/etc/index_XXg.dat, the index of what the XMB shows. With +// this unresolved the call traps, the index stays encrypted, the job thread building the top menu +// gives up, and the shell ends on an error screen instead. + +// Decrypts a buffer in place. Named after its NID, as in JPCSP - the real name isn't known, though +// the index file is the only caller we've seen. +// +// Parameters: +// - bufferAddr: encrypted data in, decrypted data out. +// - bufferSize: how much is there. +// - resultLengthAddr: receives the decrypted length, which is smaller - the header goes away. +static int sceResmgr_9DC14891(u32 bufferAddr, int bufferSize, u32 resultLengthAddr) { + if (bufferSize < 0 || !Memory::IsValidRange(bufferAddr, bufferSize)) + return hleLogError(Log::sceMisc, -1, "bad buffer"); + + u8 *buffer = Memory::GetPointerWriteUnchecked(bufferAddr); + + // Already plaintext - the check JPCSP uses, and cheap insurance against decrypting twice. + if (bufferSize >= 8 && !memcmp(buffer, "release:", 8)) { + if (Memory::IsValidAddress(resultLengthAddr)) + Memory::WriteUnchecked_U32(bufferSize, resultLengthAddr); + return hleLogDebug(Log::sceMisc, 0, "already decrypted"); + } + + // Decrypting in place is fine for the caller, but pspDecryptPRX walks the input while writing + // the output, so give it somewhere separate to write and copy back on success. On failure the + // guest buffer is left exactly as it was. + std::vector decrypted(bufferSize); + const int decryptedSize = pspDecryptPRX(buffer, decrypted.data(), bufferSize); + if (decryptedSize <= 0) { + return hleLogError(Log::sceMisc, -1, "failed to decrypt %d bytes", bufferSize); + } + + memcpy(buffer, decrypted.data(), decryptedSize); + NotifyMemInfo(MemBlockFlags::WRITE, bufferAddr, decryptedSize, "sceResmgrDecrypt"); + if (Memory::IsValidAddress(resultLengthAddr)) + Memory::WriteUnchecked_U32(decryptedSize, resultLengthAddr); + return hleLogInfo(Log::sceMisc, 0, "decrypted %d bytes to %d", bufferSize, decryptedSize); +} + +const HLEFunction sceResmgr[] = { + {0X9DC14891, &WrapI_UIU, "sceResmgr_9DC14891", 'i', "xix"}, +}; + +void Register_sceResmgr() { + RegisterHLEModule("sceResmgr", ARRAY_SIZE(sceResmgr), sceResmgr); +} diff --git a/Core/HLE/sceResmgr.h b/Core/HLE/sceResmgr.h new file mode 100644 index 0000000000..c8f911d5fc --- /dev/null +++ b/Core/HLE/sceResmgr.h @@ -0,0 +1,20 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +void Register_sceResmgr(); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 0bf4da7707..75b29f0a7d 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -227,6 +227,7 @@ + @@ -506,6 +507,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 7b40a69b42..918fc06cb4 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -136,6 +136,7 @@ + @@ -562,6 +563,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 6467be4647..02f45dcd91 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -761,6 +761,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HLE/sceNetInet.cpp \ $(SRC)/Core/HLE/sceNetResolver.cpp \ $(SRC)/Core/HLE/sceOpenPSID.cpp \ + $(SRC)/Core/HLE/sceResmgr.cpp \ $(SRC)/Core/HLE/sceP3da.cpp \ $(SRC)/Core/HLE/sceMt19937.cpp \ $(SRC)/Core/HLE/sceParseHttp.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 69f4c31415..fb201a2080 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -812,6 +812,7 @@ SOURCES_CXX += \ $(COREDIR)/HLE/proAdhocServer.cpp \ $(COREDIR)/HLE/proAdhoc.cpp \ $(COREDIR)/HLE/sceOpenPSID.cpp \ + $(COREDIR)/HLE/sceResmgr.cpp \ $(COREDIR)/HLE/sceP3da.cpp \ $(COREDIR)/HLE/sceMt19937.cpp \ $(COREDIR)/HLE/sceMd5.cpp \