Files
ppsspp/Core/HLE/sceResmgr.cpp
T
Henrik RydgårdandClaude Opus 5 e7e8d362f0 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 11:12:59 +02:00

77 lines
3.3 KiB
C++

// 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 <vector>
// 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<u8> 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>, "sceResmgr_9DC14891", 'i', "xix"},
};
void Register_sceResmgr() {
RegisterHLEModule("sceResmgr", ARRAY_SIZE(sceResmgr), sceResmgr);
}