Repository navigation
Expand file tree
/
Copy pathrun_job_manager_reference.py
More file actions
135 lines (124 loc) · 6.56 KB
/
Copy pathrun_job_manager_reference.py
File metadata and controls
135 lines (124 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from import_audit import read_host_imports
from unwrap_self import unwrap
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "core/relinker/relinker/tests"))
from guest_import_fixtures import executable
CASES = ("memory", "validation", "dispatch", "ordering", "nested", "limits", "node_pool", "priority", "shutdown", "worker_policy", "worker_wake", "worker_wait", "worker_count", "worker_spin")
def stage_libraries(directory, build, toolchain, dependencies):
providers = {path.name.lower(): path for path in (build / "core/libs/libs").glob("*.prx")}
providers.update({path.name.lower(): path for path in toolchain.glob("*.dll")})
pending = list(dependencies)
staged = set()
while pending:
name = pending.pop().lower()
if name in staged:
continue
staged.add(name)
source = providers.get(name)
if source is None:
if name.endswith(".prx"):
raise RuntimeError(f"Missing patched runtime {name}; build core/libs/libs/{name}")
continue
shutil.copy2(source, directory / source.name)
pending.extend(read_host_imports(source))
def run(args, directory):
libraries = directory / "libs"
libraries.mkdir()
helper = args.build / "tests/job_manager_reference.prx"
if not helper.is_file():
raise RuntimeError("Build the job_manager_reference target first")
shutil.copy2(helper, libraries / "jobref.prx")
subprocess.run([str(args.build / "core/libs/nid_patcher.exe"), "jobref", "--preserve-export-list",
str(args.build / "core/libs/libc.exports"), str(libraries / "jobref.prx")], check=True, capture_output=True)
module_hash = None
if args.module:
module_data = args.module.read_bytes()
module_hash = hashlib.sha256(module_data).hexdigest()
modules = directory / "sce_module"
modules.mkdir()
target = modules / "libSceJobManager.prx"
if module_data[:4] == b"\x7fELF":
shutil.copy2(args.module, target)
else:
unwrap(args.module, target)
source = directory / "input.elf"
source.write_bytes(executable("jobref.prx", symbol="Run_nid_no_patch"))
output = directory / "reference.exe"
options = ["--skip-sce-module"] if args.host else []
result = subprocess.run([str(args.relinker), "--windows", "--registry", *options, str(source), str(output)],
cwd=directory, capture_output=True, timeout=60)
(directory / "relink.log").write_bytes(result.stdout + result.stderr)
if result.returncode:
raise RuntimeError(f"Relinker failed ({result.returncode}); see {directory / 'relink.log'}")
dependencies = set(read_host_imports(helper)) | {"libc.prx"}
for registry in directory.glob("*.registry.json"):
dependencies.update(entry["library"] for entry in json.loads(registry.read_text())
if entry.get("library") not in ("jobref.prx", "libSceJobManager.prx"))
if args.host:
dependencies.add("libSceJobManager.prx")
stage_libraries(libraries, args.build, args.toolchain, dependencies)
environment = os.environ.copy()
environment["PATH"] = str(libraries) + os.pathsep + environment.get("PATH", "")
environment.pop("ANYPS5_JOB_MANAGER_HOST", None)
if args.host:
environment["ANYPS5_JOB_MANAGER_HOST"] = "1"
results = []
for case in args.cases:
environment["ANYPS5_JOB_MANAGER_CASE"] = case
try:
result = subprocess.run([str(output)], cwd=directory, env=environment,
capture_output=True, timeout=args.timeout)
log = result.stdout + result.stderr
status = "passed" if result.returncode == 0 else "failed"
code = result.returncode
except subprocess.TimeoutExpired as error:
log = (error.stdout or b"") + (error.stderr or b"")
status, code = "timeout", None
(directory / f"{case}.log").write_bytes(log)
results.append({"case": case, "status": status, "returncode": code})
print(json.dumps(results[-1]), flush=True)
summary = {"provider": "host" if args.host else "guest", "module_sha256": module_hash, "cases": results}
(directory / "results.json").write_text(json.dumps(summary, indent=2) + "\n")
return 0 if all(result["status"] == "passed" for result in results) else 1
def main():
parser = argparse.ArgumentParser(description="Run the CPU JobManager fixtures through Windows relinker startup using an owned module or the host fallback.")
provider = parser.add_mutually_exclusive_group(required=True)
provider.add_argument("--module", type=Path, help="Owned decrypted SELF or ELF libSceJobManager.prx")
provider.add_argument("--host", action="store_true", help="Validate the runner against the built host fallback")
parser.add_argument("--build", type=Path, required=True)
parser.add_argument("--relinker", type=Path, required=True)
parser.add_argument("--toolchain", type=Path, required=True, help="Directory containing the matching compiler runtime DLLs")
parser.add_argument("--output", type=Path, help="New directory for staged modules, per-case logs and results.json; defaults to a temporary directory")
parser.add_argument("--cases", nargs="+", choices=CASES, default=list(CASES))
parser.add_argument("--timeout", type=float, default=20)
args = parser.parse_args()
if sys.platform != "win32":
parser.error("The reference adapter currently supports Windows only")
if args.timeout <= 0 or args.timeout > 30:
parser.error("--timeout must be greater than zero and at most 30 seconds")
try:
for name in ("build", "relinker", "toolchain", "module"):
value = getattr(args, name)
if value is not None:
setattr(args, name, value.resolve(strict=True))
directory = args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix="anyps5-jobmanager-"))
if args.output:
directory.mkdir(parents=True, exist_ok=False)
print(f"Artifacts: {directory}", flush=True)
return run(args, directory)
except (OSError, RuntimeError, subprocess.SubprocessError) as error:
print(str(error), file=sys.stderr)
if isinstance(error, subprocess.CalledProcessError) and error.stderr:
print(error.stderr.decode(errors="replace"), file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())