diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index bce513f08..ef884b660 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -8,7 +8,12 @@ #include #include #include +#include #include +#include +#include +#include +#include namespace Relinker { @@ -29,6 +34,19 @@ std::vector ReadNeededNames(const Domain::SysVDynamicSection& dynam return names; } +std::string FoldFilename(std::string name) { + for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); + return name; +} + +std::string ModuleStem(std::string name, const bool windows) { + if (windows) name = FoldFilename(std::move(name)); + for (const std::string_view suffix : {".debug_prx", ".sprx", ".prx"}) { + if (name.size() > suffix.size() && name.ends_with(suffix)) return name.substr(0, name.size() - suffix.size()); + } + return {}; +} + } std::vector GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool macos, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath, const std::set& excludedModules) const { @@ -68,22 +86,45 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } const auto neededNames = ReadNeededNames(dynamic); std::set missingNeeded; + std::map neededAliases; for (const auto& name : neededNames) { if (excludedModules.contains(name)) continue; - if (std::none_of(paths.begin(), paths.end(), [&](const auto& path) { return path.filename().string() == name; })) missingNeeded.insert(name); + if (std::any_of(paths.begin(), paths.end(), [&](const auto& path) { return path.filename().string() == name || (windows && FoldFilename(path.filename().string()) == FoldFilename(name)); })) continue; + const auto stem = ModuleStem(name, windows); + const auto alias = stem.empty() ? paths.end() : std::find_if(paths.begin(), paths.end(), [&](const auto& path) { return ModuleStem(path.filename().string(), windows) == stem; }); + if (alias != paths.end()) neededAliases.emplace(name, *alias); + else missingNeeded.insert(name); } if (!missingNeeded.empty()) { std::map found; + std::map foundByStem; + const auto record = [](std::map& matches, const std::string& name, const std::filesystem::path& path) { + if (!matches.emplace(name, path).second) throw Domain::RelinkerException("Ambiguous needed module: " + matches.at(name).string() + " and " + path.string()); + }; for (auto it = std::filesystem::recursive_directory_iterator(root); it != std::filesystem::recursive_directory_iterator(); ++it) { if (it->is_directory() && std::find(directories.begin(), directories.end(), it->path()) != directories.end()) { it.disable_recursion_pending(); continue; } + if (!it->is_regular_file()) continue; const auto name = it->path().filename().string(); - if (!it->is_regular_file() || !missingNeeded.contains(name) || !isElf(it->path())) continue; - if (!found.emplace(name, it->path()).second) throw Domain::RelinkerException("Ambiguous needed module: " + found.at(name).string() + " and " + it->path().string()); + const auto stem = ModuleStem(name, windows); + std::vector stemMatches; + if (!stem.empty()) { + for (const auto& needed : missingNeeded) if (needed != name && ModuleStem(needed, windows) == stem) stemMatches.push_back(needed); + } + if ((!missingNeeded.contains(name) && stemMatches.empty()) || !isElf(it->path())) continue; + if (missingNeeded.contains(name)) record(found, name, it->path()); + for (const auto& needed : stemMatches) record(foundByStem, needed, it->path()); + } + for (const auto& name : missingNeeded) { + const auto exact = found.find(name); + const auto byStem = foundByStem.find(name); + if (exact == found.end() && byStem == foundByStem.end()) continue; + const auto& path = exact != found.end() ? exact->second : byStem->second; + if (exact == found.end()) neededAliases.emplace(name, path); + if (std::find(paths.begin(), paths.end(), path) == paths.end()) paths.push_back(path); } - for (const auto& [name, path] : found) paths.push_back(path); } Io::FileReader reader; std::map discovered; @@ -177,12 +218,20 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const auto [found, inserted] = guestNames.emplace(name, index); if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name); } - if (windows) windowsGuestFiles.emplace(foldFilename(images[index].SourcePath.filename().string()), index); + if (windows) windowsGuestFiles.emplace(FoldFilename(images[index].SourcePath.filename().string()), index); + } + for (const auto& [name, path] : neededAliases) { + if (guestNames.contains(name)) continue; + const auto image = std::find_if(images.begin(), images.end(), [&](const auto& candidate) { return candidate.SourcePath == path; }); + if (image == images.end()) throw Domain::RelinkerException("Needed module alias has no guest image: " + name); + const auto index = static_cast(image - images.begin()); + const auto [found, inserted] = guestNames.emplace(name, index); + if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name); } const auto findGuest = [&](const std::string& name) { const auto exact = guestNames.find(name); if (exact != guestNames.end() || !windows) return exact; - const auto file = windowsGuestFiles.find(foldFilename(name)); + const auto file = windowsGuestFiles.find(FoldFilename(name)); return file == windowsGuestFiles.end() ? guestNames.end() : guestNames.emplace(name, file->second).first; }; const auto resolveIdentity = [&](const std::string& name) { diff --git a/core/relinker/relinker/tests/test_guest_needed_modules.py b/core/relinker/relinker/tests/test_guest_needed_modules.py index 5d3fd88dd..e25310eb5 100644 --- a/core/relinker/relinker/tests/test_guest_needed_modules.py +++ b/core/relinker/relinker/tests/test_guest_needed_modules.py @@ -12,9 +12,9 @@ from test_guest_module_directories import module_with_symbol, needed_libraries NEEDED = b"needed.prx" -def executable_with_needed(): +def executable_with_needed(needed=NEEDED): image = main_fixture() - strings = b"\0" + NEEDED + b"\0" + strings = b"\0" + needed + b"\0" image[0x4800:0x4800 + len(strings)] = strings tags = [] for position in range(0x4600, 0x4600 + 0x200, 16): @@ -35,10 +35,10 @@ def main(): with tempfile.TemporaryDirectory(prefix="anyps5-needed-modules-") as directory: work = Path(directory) - def convert(case, windows): + def convert(case, windows, needed=NEEDED): (case / "sce_module").mkdir(parents=True, exist_ok=True) source = case / "input.elf" - source.write_bytes(executable_with_needed()) + source.write_bytes(executable_with_needed(needed)) output = case / ("output.exe" if windows else "output.elf") result = subprocess.run([str(relinker), *(["--windows"] if windows else []), str(source), str(output)], capture_output=True, text=True, timeout=30) @@ -63,6 +63,41 @@ def main(): needed = needed_libraries(output.read_bytes()) assert needed == ["$ORIGIN/app0/Media/Modules/needed.prx.guest.prx"], needed + for directory in ("Media/Modules", "sce_module"): + case = work / f"{windows}-debug-name-{directory.replace('/', '-')}" + (case / directory).mkdir(parents=True) + (case / directory / "needed.prx").write_bytes(module_with_symbol(True)) + result, output = convert(case, windows, b"needed.debug_prx") + assert result.returncode == 0, (result.stdout, result.stderr) + artifact = case / "app0" / directory / "needed.prx.guest.prx" + assert list((case / "app0").rglob("*.guest.prx")) == [artifact], list((case / "app0").rglob("*")) + if windows and os.name == "nt": + run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30) + assert run.returncode == 42, (run.returncode, run.stdout, run.stderr) + if not windows: + needed = needed_libraries(output.read_bytes()) + assert needed == [f"$ORIGIN/app0/{directory}/needed.prx.guest.prx"], needed + + if windows: + case = work / "debug-name-case" + (case / "prx").mkdir(parents=True) + (case / "prx" / "foo-bar.prx").write_bytes(module_with_symbol(True)) + result, output = convert(case, windows, b"Foo-Bar.debug_prx") + assert result.returncode == 0, (result.stdout, result.stderr) + artifact = case / "app0" / "prx" / "foo-bar.prx.guest.prx" + assert list((case / "app0").rglob("*.guest.prx")) == [artifact], list((case / "app0").rglob("*")) + if os.name == "nt": + run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30) + assert run.returncode == 42, (run.returncode, run.stdout, run.stderr) + + case = work / f"{windows}-debug-name-ambiguous" + (case / "first").mkdir(parents=True) + (case / "first" / "needed.prx").write_bytes(module_with_symbol(True)) + (case / "first" / "needed.sprx").write_bytes(module_with_symbol(True)) + result, output = convert(case, windows, b"needed.debug_prx") + assert result.returncode == 2 and "Ambiguous needed module" in result.stderr, result.stderr + assert not output.exists(), output + case = work / f"{windows}-ambiguous" for name in ("first", "second"): (case / name).mkdir(parents=True)