Merge pull request #4338 from ArjenS-LA/fix/diagnostic-input-preservation

fix(relinker): preserve inputs when writing Windows diagnostics
This commit is contained in:
Dean 2026-10-13 04:52:18 +13:00 committed by GitHub
commit 57f5ec232c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 70 additions and 1 deletions

View File

@ -128,6 +128,8 @@ int Run(const int argc, char* argv[]) {
checkOutputInputs(desktopEntry->IconPath, "Icon");
checkOutputInputs(desktopEntry->EntryPath, "Desktop entry");
}
if (args.windowsDiagnostics)
checkOutputInputs(std::filesystem::absolute("windows-diagnostics-imports.txt"), "Windows import diagnostics");
if (args.writeRegistry) {
const std::filesystem::path outFsPath = Io::NativePath(absPath);

View File

@ -8,6 +8,72 @@ from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture
from test_linux_desktop_entry_output import png
def check_diagnostic_inputs(relinker, work):
filename = "windows-diagnostics-imports.txt"
for bundled in (False, True):
for alias in ("direct", "hardlink", "symlink"):
for registry in (False, True):
case = work / f"diagnostics-{bundled}-{alias}-{registry}"
modules = case / "sce_module"
modules.mkdir(parents=True)
diagnostics = (modules if bundled and alias == "direct" else case) / filename
source = diagnostics if not bundled and alias == "direct" else case / "input.elf"
original = bytes(main_fixture())
source.write_bytes(original)
module = diagnostics if bundled and alias == "direct" else modules / "sample.prx"
module_original = bytes(guest_fixture(PLAIN_SITE))
module.write_bytes(module_original)
protected = module if bundled else source
if alias == "hardlink":
os.link(protected, diagnostics)
elif alias == "symlink":
try:
diagnostics.symlink_to(protected)
except OSError as error:
if os.name != "nt" or error.winerror != 1314:
raise
print("Diagnostic symbolic-link case skipped: Windows symlink privilege unavailable")
continue
output = case / "output.exe"
previous_output = b"previous executable"
output.write_bytes(previous_output)
registry_path = case / "output.registry.json"
previous_registry = b"previous registry"
registry_path.write_bytes(previous_registry)
options = [] if bundled else ["--skip-sce-module"]
if registry:
options.append("--registry")
result = subprocess.run([str(relinker), "--windows", "--windows-diagnostics", *options,
str(source), str(output)], cwd=diagnostics.parent,
capture_output=True, text=True, timeout=20)
owner = "an input module" if bundled else "the input executable"
assert result.returncode == 2 and f"Windows import diagnostics output would overwrite {owner}" in result.stderr, (result.stdout, result.stderr)
assert str(diagnostics) in result.stderr, result.stderr
assert source.read_bytes() == original and module.read_bytes() == module_original
assert diagnostics.read_bytes() == (module_original if bundled else original)
assert output.read_bytes() == previous_output and registry_path.read_bytes() == previous_registry
assert not (case / "app0").exists()
for enabled in (False, True):
for existing in (False, True):
case = work / f"diagnostics-valid-{enabled}-{existing}"
case.mkdir()
diagnostics = case / filename
source = case / "input.elf" if enabled else diagnostics
original = bytes(main_fixture())
source.write_bytes(original)
if enabled and existing:
diagnostics.write_bytes(b"previous diagnostics")
output = case / "output.exe"
options = ["--windows-diagnostics"] if enabled else []
result = subprocess.run([str(relinker), "--windows", "--skip-sce-module", *options,
str(source), str(output)], cwd=case,
capture_output=True, text=True, timeout=20)
assert result.returncode == 0 and output.read_bytes().startswith(b"MZ"), (result.stdout, result.stderr)
assert source.read_bytes() == original
assert diagnostics.read_bytes() == (b"" if enabled else original)
def check_registry_inputs(relinker, work, windows):
options = ["--windows"] if windows else []
for module_registry in (False, True):
@ -112,6 +178,7 @@ def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-input-preservation-") as directory:
work = Path(directory)
check_diagnostic_inputs(relinker, work)
check_desktop_inputs(relinker, work)
for windows in (False, True):
options = ["--windows"] if windows else []

View File

@ -35,7 +35,7 @@ relinker --macos source/input.elf eboot
Add `--to-intel` for Intel hosts, or `--to-rosetta` for Rosetta; a title that runs under Rosetta on Apple silicon is relinked with `--macos --to-rosetta`. `--to-intel` rewrites AMD-specific instructions found in the executable and bundled modules; it cannot translate AMD-specific instructions generated by a title at runtime, so such titles require an AMD host. The output format defaults to Linux ELF regardless of the filename; `.exe` alone does not select Windows.
The executable, registry, icon and desktop entry output files must not refer to the input executable or a bundled module being converted, including through a hard link or symbolic link. These collisions fail before any output is written. An existing output file can be replaced if it is separate from those inputs.
The executable, registry, Windows import diagnostics, icon and desktop entry output files must not refer to the input executable or a bundled module being converted, including through a hard link or symbolic link. These collisions fail before any output is written. An existing output file can be replaced if it is separate from those inputs.
## Options