From de93a9492ca5a27b4e72320b0e7ad1ea5bee28ef Mon Sep 17 00:00:00 2001 From: Merijn Date: Mon, 5 Oct 2026 19:41:47 +0200 Subject: [PATCH] feat(libkernel): load the relinked module for a guest module path dlopen and sceKernelLoadStartModule passed the guest path to the host loader, which cannot load the original ELF module. When a relinked module .guest.prx exists beside it, that module is loaded instead; for a module the executable already loaded at startup the host loader returns the same module. PPSA12544 loads /app0/Media/Modules/Il2CppUserAssemblies.prx at startup and resolves IL2CPP functions through it. Co-Authored-By: Claude Opus 5.5 --- .../prx/libkernel/Module/src/DynamicLoader.cpp | 12 ++++++++++-- core/libs/tests/GuestDynamicLoader.cpp | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index e1091def8..ef4790b78 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/General.hpp" #include #include +#include #include #include #include @@ -57,6 +58,13 @@ char* APS5_VABI dlerror_nid_postfix() { pendingError = false; return loaderError.data(); } +static std::filesystem::path RelinkedModulePath(const std::filesystem::path& path) { + auto relinked = path; + relinked += ".guest.prx"; + std::error_code error; + return std::filesystem::is_regular_file(relinked, error) ? relinked : path; +} + void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { if ((flags & ~0x103) || (flags & 3) == 0 || (flags & 3) == 3) { Error("dlopen: unsupported flags"); return nullptr; @@ -70,7 +78,7 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { module->owned = false; } else { if (!*path) { Error("dlopen: empty module path"); return nullptr; } - const auto resolved = ResolvePath_nid_no_patch(path); + const auto resolved = RelinkedModulePath(ResolvePath_nid_no_patch(path)); module->native = LoadLibraryExW(resolved.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); } if (!module->native) { @@ -79,7 +87,7 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { Error(message); return nullptr; } #else - const auto resolved = path ? ResolvePath_nid_no_patch(path).string() : std::string{}; + const auto resolved = path ? RelinkedModulePath(ResolvePath_nid_no_patch(path)).string() : std::string{}; const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) | ((flags & 0x100) ? RTLD_GLOBAL : RTLD_LOCAL); module->native = ::dlopen(path ? resolved.c_str() : nullptr, nativeFlags); diff --git a/core/libs/tests/GuestDynamicLoader.cpp b/core/libs/tests/GuestDynamicLoader.cpp index 2fa27e917..04b369b73 100644 --- a/core/libs/tests/GuestDynamicLoader.cpp +++ b/core/libs/tests/GuestDynamicLoader.cpp @@ -1,5 +1,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include +#include +#include #include extern "C" { void* APS5_VABI dlopen_nid_postfix(const char*, int); @@ -37,4 +39,20 @@ int main(int argc, char** argv) { Require(add && add(2, 3) == 5); Require(dlclose_nid_postfix(second) == 0); Require(dlclose_nid_postfix(second) == -1); + + const std::filesystem::path guest = "anyps5-relinked-module-for-test.prx"; + auto relinked = guest; + relinked += ".guest.prx"; + { + std::ofstream elf(guest, std::ios::binary); + elf.write("\x7f" "ELF", 4); + } + std::filesystem::copy_file(argv[1], relinked, std::filesystem::copy_options::overwrite_existing); + void* redirected = dlopen_nid_postfix(guest.string().c_str(), 2); + Require(redirected != nullptr); + add = reinterpret_cast(dlsym_nid_postfix(redirected, "GuestModuleAdd")); + Require(add && add(40, 2) == 42); + Require(dlclose_nid_postfix(redirected) == 0); + std::filesystem::remove(guest); + std::filesystem::remove(relinked); }