From c0c715ac2e66dae1b894d9bc56a8c75d422b3202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 8 Sep 2026 11:43:49 -0600 Subject: [PATCH] Load injected firmware modules at the top of user memory, not the bottom The flash0 PRXes we swap in for our HLE took the lowest free block, which sits right where a game's own EBOOT wants to go. That pushes the game up, shifting every address in it - invalidating cheats and RetroAchievements - and for a game whose EBOOT has to load at a fixed low address it fails outright: Tekken 6 wants 0x08804018 and got "block taken", so it didn't boot at all. Give KernelLoadModule a fromTop flag and use it for the modules we inject. The game keeps its normal load address and the firmware sits out of the way at the top. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup --- Core/HLE/sceKernelModule.cpp | 4 ++-- Core/HLE/sceKernelModule.h | 6 +++++- Core/HLE/sceUtility.cpp | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 8abad4708b..e5979dbe57 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1921,14 +1921,14 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load return module; } -SceUID KernelLoadModule(const std::string &filename, std::string *error_string) { +SceUID KernelLoadModule(const std::string &filename, std::string *error_string, bool fromTop) { std::vector buffer; if (pspFileSystem.ReadEntireFile(filename, buffer) < 0) return SCE_KERNEL_ERROR_NOFILE; u32 error = SCE_KERNEL_ERROR_ILLEGAL_OBJECT; u32 magic; - PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, false, error_string, &magic, filename, error); + PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, fromTop, error_string, &magic, filename, error); if (module == nullptr) return error; diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index 6fcbbcfd5b..c1f0c5d359 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -247,7 +247,11 @@ bool KernelFindImportByStubAddr(u32 stubAddr, std::string *importModuleName, u32 bool DescribeModuleAddress(u32 address, char *buffer, size_t bufferSize); int __KernelGPUReplay(); void __KernelReturnFromModuleFunc(); -SceUID KernelLoadModule(const std::string &filename, std::string *error_string); +// fromTop puts the module at the top of the user partition instead of the bottom. Use it for +// firmware modules we inject before the game loads - taking the bottom pushes the game's own ELF +// up, which shifts every address in it and invalidates cheats and achievements, and outright +// fails for a game like Tekken 6 whose EBOOT must load at a fixed low address. +SceUID KernelLoadModule(const std::string &filename, std::string *error_string, bool fromTop = false); int __KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait); u32 __KernelStopUnloadSelfModuleWithOrWithoutStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr, bool WithStatus); u32 sceKernelFindModuleByUID(u32 uid); diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 90102697cc..a6a9ba32fc 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -108,7 +108,7 @@ static void NotifyLoadStatusMp4(int state, u32 loadAddr, u32 totalSize) { continue; } std::string error; - SceUID id = KernelLoadModule(paths[i], &error); + SceUID id = KernelLoadModule(paths[i], &error, true); if (id < 0) { ERROR_LOG(Log::sceUtility, "sceMp4 HLE is disabled, but %s wouldn't load (%s) - " "the game will get unresolved imports", paths[i], error.c_str());