Merge pull request #3850 from SandstromPL/fix/relinker-module-dir-root

fix(relinker): accept the input's directory as a module directory
This commit is contained in:
Dean 2026-10-12 16:07:27 +13:00 committed by GitHub
commit 26d87eec20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 3 deletions

View File

@ -102,6 +102,7 @@ std::vector<GuestArtifact> 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<GuestArtifact> 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<GuestArtifact> 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<GuestArtifact> 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");

View File

@ -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"),

View File

@ -56,7 +56,7 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath`
| `--rpath <path>` | 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 <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 <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 <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 <file>` | 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. |