From 006e1768128d23f491e78d51c709ecf58d97ed2c Mon Sep 17 00:00:00 2001 From: Jota <139649225+jotitadev@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:20:14 -0300 Subject: [PATCH] fix(relinker): compare guest module directories by identity (cherry picked from commit 6e3b80d65bd47344b896af9cbc763033fe6d5be0) --- core/relinker/cli/include/NativeModules.hpp | 2 ++ core/relinker/cli/src/NativeModules.cpp | 7 ++++++- core/relinker/main.cpp | 2 +- .../relinker/relinker/tests/test_executables.py | 17 +++++++++++++++++ .../relinker/tests/test_guest_module_dir.py | 13 +++++++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/core/relinker/cli/include/NativeModules.hpp b/core/relinker/cli/include/NativeModules.hpp index bd49e3773b..66139266ca 100644 --- a/core/relinker/cli/include/NativeModules.hpp +++ b/core/relinker/cli/include/NativeModules.hpp @@ -10,6 +10,8 @@ struct NativeModules { std::vector Modules; }; +bool SameDirectory(const std::filesystem::path& first, const std::filesystem::path& second); + NativeModules ReadModules(const std::filesystem::path& inputPath, const std::filesystem::path& sceModulePath, const std::set& excludedModules, const std::vector& moduleDirectories); } diff --git a/core/relinker/cli/src/NativeModules.cpp b/core/relinker/cli/src/NativeModules.cpp index 4943091c64..f3839033c8 100644 --- a/core/relinker/cli/src/NativeModules.cpp +++ b/core/relinker/cli/src/NativeModules.cpp @@ -7,6 +7,11 @@ namespace Cli { +bool SameDirectory(const std::filesystem::path& first, const std::filesystem::path& second) { + if (std::filesystem::exists(first) && std::filesystem::exists(second)) return std::filesystem::equivalent(first, second); + return std::filesystem::weakly_canonical(first) == std::filesystem::weakly_canonical(second); +} + NativeModules ReadModules(const std::filesystem::path& inputPath, const std::filesystem::path& sceModulePath, const std::set& excludedModules, const std::vector& moduleDirectories) { using namespace Relinker; const auto root = std::filesystem::absolute(sceModulePath).lexically_normal(); @@ -32,7 +37,7 @@ NativeModules ReadModules(const std::filesystem::path& inputPath, const std::fil auto directory = (root / relative).lexically_normal(); if (!directory.has_filename()) directory = directory.parent_path(); if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + Io::Utf8Path(directory)); - if (std::find(directories.begin(), directories.end(), directory) != directories.end()) throw Domain::RelinkerException("Duplicate guest module directory: " + Io::Utf8Path(directory)); + if (std::any_of(directories.begin(), directories.end(), [&](const auto& existing) { return SameDirectory(existing, directory); })) throw Domain::RelinkerException("Duplicate guest module directory: " + Io::Utf8Path(directory)); directories.push_back(directory); runtimeDirectories.push_back(directory); } diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 6b967356a5..243e8d18b4 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -210,7 +210,7 @@ int Run(const int argc, char* argv[]) { throw Domain::RelinkerException("--executable must be a file inside the guest module parent directory: " + executable); const auto input = moduleRoot / relative; if (!std::filesystem::is_regular_file(input)) throw Domain::RelinkerException("--executable is not a file: " + Io::Utf8Path(input)); - if (std::find(moduleDirectories.begin(), moduleDirectories.end(), input.parent_path()) != moduleDirectories.end()) + if (std::any_of(moduleDirectories.begin(), moduleDirectories.end(), [&](const auto& directory) { return Cli::SameDirectory(directory, input.parent_path()); })) throw Domain::RelinkerException("--executable is in a guest module directory: " + executable); std::string name; for (const auto& part : relative) name += (name.empty() ? "" : ".") + Io::Utf8Path(part); diff --git a/core/relinker/relinker/tests/test_executables.py b/core/relinker/relinker/tests/test_executables.py index baff6b86c1..21569834d8 100644 --- a/core/relinker/relinker/tests/test_executables.py +++ b/core/relinker/relinker/tests/test_executables.py @@ -59,6 +59,23 @@ def main(): assert result.returncode == 2 and message in result.stderr, (options, result.stderr) assert not output.exists(), options + for index, (directory, executable) in enumerate((("Picker", "picker/picker.bin"), ("picker", "PICKER/picker.bin"), ("linked", "picker/picker.bin"))): + case = work / f"{windows}-identity-{index}" + case.mkdir() + insensitive = (work / f"{windows}-IDENTITY-{index}").exists() + if (directory == "linked" and os.name == "nt") or (executable != "picker/picker.bin" and not insensitive): + continue + if directory == "linked": + (case / "linked").symlink_to("picker", target_is_directory=True) + elif not insensitive: + (case / directory).mkdir() + result, output = convert(case, windows, ["--module-dir", directory, "--executable", executable]) + if insensitive or directory == "linked": + assert result.returncode == 2 and "in a guest module directory" in result.stderr, (directory, executable, result.stderr) + assert not output.exists(), (directory, executable) + else: + assert result.returncode == 0, (directory, executable, result.stdout, result.stderr) + case = work / f"{windows}-needed" (case / "Media" / "Modules").mkdir(parents=True) (case / "Media" / "Modules" / "needed.prx").write_bytes(module_with_symbol(True)) diff --git a/core/relinker/relinker/tests/test_guest_module_dir.py b/core/relinker/relinker/tests/test_guest_module_dir.py index 9fd27ed131..ae9aee4bf2 100644 --- a/core/relinker/relinker/tests/test_guest_module_dir.py +++ b/core/relinker/relinker/tests/test_guest_module_dir.py @@ -138,6 +138,19 @@ def main(): result, output = convert(case, windows, ["--module-dir", rejected]) assert result.returncode == 2 and message in result.stderr, (rejected, result.stderr) + aliases = [["SCE_MODULE"], ["Media/Plugins", "media/plugins"]] if (case / "MEDIA").exists() else [] + if os.name != "nt": + (case / "linked").symlink_to("sce_module", target_is_directory=True) + aliases.append(["linked"]) + for names in aliases: + result, output = convert(case, windows, [option for name in names for option in ("--module-dir", name)]) + assert result.returncode == 2 and "Duplicate guest module directory" in result.stderr, (names, result.stderr) + if not (case / "MEDIA").exists(): + (case / "SCE_MODULE").mkdir() + result, output = convert(case, windows, ["--module-dir", "SCE_MODULE"]) + assert result.returncode == 0, (result.stdout, result.stderr) + (case / "SCE_MODULE").rmdir() + result, output = convert(case, windows, ["--skip-sce-module", "--module-dir", "Media/Plugins"]) assert result.returncode != 0 and "conflicts with --skip-sce-module" in result.stderr, result.stderr