diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index d51c4c160..f6c8f8a49 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -102,6 +102,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path continue; } if (!entry.is_regular_file()) continue; + if (std::filesystem::equivalent(entry.path(), inputPath)) continue; if (isElf(entry.path())) paths.push_back(entry.path()); } } @@ -288,6 +289,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const auto deferred = [&](const std::size_t index) { return !required[index] && std::find(runtimeDirectories.begin(), runtimeDirectories.end(), images[index].SourcePath.parent_path().lexically_normal()) != runtimeDirectories.end(); }; + const auto appDirectory = [&](const GuestImage& image) { + const auto directory = image.SourcePath.parent_path().lexically_relative(root).generic_string(); + return directory == "." ? std::string("app0") : "app0/" + directory; + }; if (!windows) { for (auto& image : images) { for (std::size_t index = 1; index < image.Symbols.size(); ++index) { @@ -324,7 +329,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size()); Io::AppendString(dynamic.DynStrData, name); }; - for (const auto index : order) if (!windows && !deferred(index)) addNeeded("$ORIGIN/app0/" + images[index].SourcePath.parent_path().lexically_relative(root).generic_string() + "/" + images[index].OutputName); + for (const auto index : order) if (!windows && !deferred(index)) addNeeded("$ORIGIN/" + appDirectory(images[index]) + "/" + images[index].OutputName); for (const auto& name : hostLibraries) addNeeded(name); if (!windows && runPath != "$ORIGIN" && !runPath.starts_with("$ORIGIN/") && !std::filesystem::path(runPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); const auto outputDirectory = std::filesystem::absolute(outputPath).parent_path().lexically_normal(); @@ -350,7 +355,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path image.Bytes[header.Offset + offset + 1] = 0x0B; } } - const auto relativeDirectory = "app0/" + image.SourcePath.parent_path().lexically_relative(root).generic_string(); + const auto relativeDirectory = appDirectory(image); const auto destination = outputDirectory / relativeDirectory; const auto target = destination / image.OutputName; if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output"); diff --git a/core/relinker/relinker/tests/test_guest_module_dir.py b/core/relinker/relinker/tests/test_guest_module_dir.py index 4ab73d8af..b333399ac 100644 --- a/core/relinker/relinker/tests/test_guest_module_dir.py +++ b/core/relinker/relinker/tests/test_guest_module_dir.py @@ -91,6 +91,35 @@ def main(): else: assert "$ORIGIN/app0/Media/Modules/needed.prx.guest.prx" in needed_libraries(output.read_bytes()), needed_libraries(output.read_bytes()) + case = work / f"{windows}-root" + case.mkdir() + (case / "root.prx").write_bytes(module_with_symbol(True)) + (case / "readme.txt").write_text("not ELF") + result, output = convert(case, windows, ["--module-dir", "."]) + assert result.returncode == 0, (result.stdout, result.stderr) + artifact = case / "app0" / "root.prx.guest.prx" + assert list((case / "app0").rglob("*.guest.prx")) == [artifact], list(case.rglob("*.guest.prx")) + assert artifact.read_bytes().startswith(b"MZ" if windows else b"\x7fELF"), artifact + assert " root.prx.guest.prx\n" in result.stdout, result.stdout + if windows: + assert "__aps5_guest_initialize" in pe_export_names(artifact.read_bytes()) + else: + assert not needed_libraries(output.read_bytes()), needed_libraries(output.read_bytes()) + 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) + + case = work / f"{windows}-root-needed" + case.mkdir() + (case / "needed.prx").write_bytes(module_with_symbol(True)) + result, output = convert(case, windows, ["--module-dir", "."], executable_with_needed()) + assert result.returncode == 0, (result.stdout, result.stderr) + needed = case / "app0" / "needed.prx.guest.prx" + if windows: + assert "__aps5_guest_initialize" not in pe_export_names(needed.read_bytes()) + else: + assert "$ORIGIN/app0/needed.prx.guest.prx" in needed_libraries(output.read_bytes()), needed_libraries(output.read_bytes()) + case = work / f"{windows}-plugins" for rejected, message in ((str(plugins), "inside the guest module parent directory"), ("../outside", "inside the guest module parent directory"), diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 3ff968d17..12accb979 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -56,7 +56,7 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath` | `--rpath ` | Set the system library search path. Quote `$ORIGIN` to prevent shell expansion, for example `--rpath '$ORIGIN/libs'` in Bash or PowerShell. Linux guest modules require an absolute path or a path beginning with `$ORIGIN`. Windows requires a nonempty ASCII path and supports `$ORIGIN` as the executable directory. | | `--autorun` | Run the output after conversion, print its exit code, and wait for Enter. Adds executable permissions for Linux output. Requires the target OS and prepared runtime layout. | | `--sce-module-path ` | Parent of the module directories, not the module directory itself. Default: input ELF parent; relative input paths resolve from the process working directory. Explicit absolute path: used directly. Explicit relative path (including `.` and `..`): relative to the process working directory. Output path has no effect. Requires one nonempty value; repeats, nonexistent or nondirectory paths, access failures, and combination with `--skip-sce-module` are errors. | -| `--module-dir ` | Relink the ELF modules of a directory under the module parent directory, besides `sce_module`/`sce_modules`/`prx`; outputs keep the relative path. A module the executable or another module needs is initialized at startup, any other when the title loads it with `sceKernelLoadStartModule`, with its start arguments (for example Unity plugins in `Media/Plugins`). Repeat for multiple directories. Conflicts with `--skip-sce-module`. | +| `--module-dir ` | Relink the ELF modules of a directory under the module parent directory, besides `sce_module`/`sce_modules`/`prx`; outputs keep the relative path. A module the executable or another module needs is initialized at startup, any other when the title loads it with `sceKernelLoadStartModule`, with its start arguments (for example Unity plugins in `Media/Plugins`). Repeatable; `.` is the parent itself, without the input ELF. Conflicts with `--skip-sce-module`. | | `--skip-sce-module` | Deprecated. Disable bundled module processing and module directory checks. Conflicts with explicit `--sce-module-path`. | | `--exclude-sce-module ` | Deprecated. Exclude a bundled module by exact filename, not path. Repeat for multiple files; a missing filename is an error. Conflicts with `--skip-sce-module`. | | `--skip-syscall-check` | Deprecated. Disable syscall scanning in the executable and bundled modules. |