fix(tools): empty a reused audit work directory and report broken module links

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
JJenkxandClaude Opus 5.5 committed 2026-10-24 23:45:09 -04:00
1 parent aeaa3b58f0
commit 14f37caae6
2 files changed
+36 -2

No files matched your search

+5 -2
View File
@@ -12,7 +12,8 @@ import unwrap_self
DESCRIPTION = """Audit the imports of an extracted title before it is launched. Unwraps the dump's eboot.bin and the modules
it ships (sce_module/, sce_modules/ and prx/, Unity's Media/Modules/ and Media/Plugins/, and .prx files beside
eboot.bin) into a work directory, converts them with relinker --registry, and runs tools/import_audit.py on the
registries with the unwrapped modules as --modules. The dump is only read. Exit code: that of import_audit.py, or 2
registries with the unwrapped modules as --modules. The dump is only read; the source/ and output/ directories of a
reused --work directory are emptied first. Exit code: that of import_audit.py, or 2
when unwrapping or conversion fails."""
STANDARD_DIRECTORIES = ("sce_module", "sce_modules", "prx")
@@ -35,7 +36,7 @@ def stage(source, destination):
def modules_of(directory):
return sorted(path for path in directory.iterdir() if path.is_file() and path.suffix.lower() in MODULE_SUFFIXES)
return sorted(path for path in directory.iterdir() if (path.is_file() or path.is_symlink()) and path.suffix.lower() in MODULE_SUFFIXES)
def prepare(dump, source, log=print):
@@ -92,6 +93,8 @@ def main(argv=None):
with tempfile.TemporaryDirectory() as temporary:
work = Path(args.work) if args.work else Path(temporary)
source, output = work / "source", work / "output"
for generated in (source, output):
shutil.rmtree(generated, ignore_errors=True)
output.mkdir(parents=True, exist_ok=True)
try:
module_directories, extra = prepare(dump, source)
+31
View File
@@ -1,5 +1,6 @@
import contextlib
import io
import os
import subprocess
import sys
import tempfile
@@ -101,6 +102,36 @@ class AuditDumpTests(unittest.TestCase):
self.assertEqual(argv[:2], [str(output / "app.libc.prx.guest.prx.registry.json"), str(output / "app.registry.json")])
self.assertEqual(argv[2:], ["--libs", "libs-a", "--libs", "libs-b", "--modules", str(work / "source" / "sce_module"), "--json", "report.json"])
def test_a_reused_work_directory_does_not_audit_registries_left_by_an_earlier_run(self):
work = self.root / "work"
stale = work / "output" / "app.old.prx.guest.prx.registry.json"
stale.parent.mkdir(parents=True)
stale.write_text("[]")
(work / "source" / "sce_module").mkdir(parents=True)
(work / "source" / "sce_module" / "gone.prx").write_bytes(ELF)
def relink(command, capture_output, text):
(Path(command[-1]).parent / "app.registry.json").write_text("[]")
return subprocess.CompletedProcess(command, 0, "", "")
with mock.patch.object(subprocess, "run", side_effect=relink), mock.patch.object(audit_dump.import_audit, "main", return_value=0) as audit:
code, _ = self.run_main(self.dump, "--relinker", self.relinker, "--libs", "libs", "--work", work)
self.assertEqual(code, 0)
self.assertEqual(audit.call_args.args[0][:2], [str(work / "output" / "app.registry.json"), "--libs"])
self.assertFalse((work / "source" / "sce_module" / "gone.prx").exists())
def test_a_broken_module_link_fails_instead_of_being_skipped(self):
(self.dump / "sce_module").mkdir()
try:
os.symlink(self.dump / "sce_module" / "missing.prx", self.dump / "sce_module" / "libc.prx")
except OSError:
self.skipTest("symbolic links unavailable")
with mock.patch.object(subprocess, "run") as run:
code, errors = self.run_main(self.dump, "--relinker", self.relinker, "--libs", "libs")
self.assertEqual(code, 2)
self.assertIn("libc.prx", errors)
run.assert_not_called()
def test_a_relinker_failure_returns_2_with_its_message(self):
with mock.patch.object(subprocess, "run", return_value=subprocess.CompletedProcess([], 3, "", "bad input")), mock.patch.object(audit_dump.import_audit, "main") as audit:
code, errors = self.run_main(self.dump, "--relinker", self.relinker, "--libs", "libs")