diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 7de9ed967..bb4a36870 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -14,6 +14,7 @@ extern "C" void CxaFinalize_nid_no_patch(void* dsoHandle); extern "C" bool StopAtPrefixLetter_nid_no_patch(const char* str, char** endptr, int base); extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); +extern "C" std::filesystem::path RelinkedModulePath_nid_no_patch(const std::filesystem::path& path); extern "C" int SetWorkingDirectory_nid_no_patch(const char* resolved); extern "C" bool FinalPathComponentIsDot_nid_no_patch(const char* path); // Guest path aliases: a guest prefix such as "/savedata0" (a save-data mount point) resolves to a host diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index a2a57370b..3af0807f5 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -345,6 +345,13 @@ extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) { return Resolve(state, path); } +extern "C" std::filesystem::path RelinkedModulePath_nid_no_patch(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; +} + extern "C" int SetWorkingDirectory_nid_no_patch(const char* resolved) { if (resolved == nullptr) return 14; auto& state = Directories(); diff --git a/core/libs/prx/libkernel/File/include/NativeStat.hpp b/core/libs/prx/libkernel/File/include/NativeStat.hpp index 5715f0211..0d0e00369 100644 --- a/core/libs/prx/libkernel/File/include/NativeStat.hpp +++ b/core/libs/prx/libkernel/File/include/NativeStat.hpp @@ -6,6 +6,7 @@ namespace File { +std::filesystem::path PathOrRelinkedModule(const std::filesystem::path& nativePath); void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb); void FillFileStat(int nativeDescriptor, FileStat* sb); bool FillFileStatFromDescriptor(int fd, FileStat* sb); diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index 4495fb59f..5e6e304a7 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -125,6 +125,12 @@ static void CopyNativeStat(const NativeStat& st, FileStat* sb) { namespace File { +std::filesystem::path PathOrRelinkedModule(const std::filesystem::path& nativePath) { + std::error_code error; + if (std::filesystem::exists(std::filesystem::symlink_status(nativePath, error))) return nativePath; + return RelinkedModulePath_nid_no_patch(nativePath); +} + void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) { NativeStat st{}; if (DoStat(nativePath, &st) != 0) { diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index f9a6944ca..f3df2564d 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -208,6 +208,7 @@ int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { } std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); + if ((flags & (SCE_KERNEL_O_ACCMODE | SCE_KERNEL_O_CREAT)) == SCE_KERNEL_O_RDONLY) native = File::PathOrRelinkedModule(native); return OpenNative(native, flags, mode); } @@ -287,6 +288,7 @@ int APS5_VABI sceKernelStat(const char* path, FileStat* sb) { if (*path == '\0') return SceErrorFromErrno(2); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); + native = File::PathOrRelinkedModule(native); std::error_code error; constexpr int GuestEnotdir = 20; const auto status = std::filesystem::status(native, error); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 1c5a227a9..479f15e86 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -789,6 +789,7 @@ int APS5_VABI sceKernelCheckReachability(const char* path) { if (std::strlen(path) > 255) return SCE_KERNEL_ERROR_ENAMETOOLONG; std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); + native = File::PathOrRelinkedModule(native); std::error_code error; return std::filesystem::exists(native, error) ? 0 : SCE_KERNEL_ERROR_ENOENT; } diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index 769bf79ca..1dc372c38 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -162,13 +162,6 @@ static void InitializeDeferredModule(HMODULE native) { } #endif -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; @@ -182,7 +175,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 = RelinkedModulePath(ResolvePath_nid_no_patch(path)); + const auto resolved = RelinkedModulePath_nid_no_patch(ResolvePath_nid_no_patch(path)); module->native = LoadLibraryExW(resolved.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); if (module->native) InitializeDeferredModule(static_cast(module->native)); } @@ -192,7 +185,7 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { Error(message); return nullptr; } #else - const auto resolved = path ? RelinkedModulePath(ResolvePath_nid_no_patch(path)).string() : std::string{}; + const auto resolved = path ? RelinkedModulePath_nid_no_patch(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/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index d32843235..484b7ee24 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -174,8 +174,36 @@ static void TestParentTraversal() { std::filesystem::remove_all(root); } +static void TestRelinkedModuleNames() { + const auto root = std::filesystem::path("anyps5-relinked-names-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directories(root)); + const auto original = (root / "Plugin.prx").generic_string(); + const auto relinked = original + ".guest.prx"; + FileStat status{}; + Require(sceKernelStat(original.c_str(), &status) == static_cast(0x80020002u)); + Require(sceKernelCheckReachability(original.c_str()) == static_cast(0x80020002u)); + Require(sceKernelOpen(original.c_str(), 0, 0) == static_cast(0x80020002u)); + { std::ofstream file(relinked); file << "relinked"; } + Require(sceKernelStat(original.c_str(), &status) == 0 && status.st_size == 8); + Require(stat_nid_postfix(original.c_str(), &status) == 0 && status.st_size == 8); + Require(sceKernelCheckReachability(original.c_str()) == 0); + const int opened = sceKernelOpen(original.c_str(), 0, 0); + Require(opened >= 0 && sceKernelClose(opened) == 0); + Require(sceKernelStat((root / "Other.prx").generic_string().c_str(), &status) == static_cast(0x80020002u)); + Require(!std::filesystem::exists(original)); + { std::ofstream file(original); file << "original-bytes"; } + Require(sceKernelStat(original.c_str(), &status) == 0 && status.st_size == 14); + Require(std::filesystem::remove(original)); + const int created = sceKernelOpen(original.c_str(), 0x601, 0600); + Require(created >= 0 && sceKernelClose(created) == 0); + Require(std::filesystem::file_size(original) == 0 && std::filesystem::file_size(relinked) == 8); + std::filesystem::remove_all(root); +} + int main() { TestParentTraversal(); + TestRelinkedModuleNames(); Require(chmod_nid_postfix(nullptr, 0600) == -1 && *__error_nid_postfix() == 14); Require(sceKernelChmod_nid_postfix(nullptr, 0600) == static_cast(0x8002000eu)); Require(chmod_nid_postfix("", 0600) == -1 && *__error_nid_postfix() == 2);