mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 23:24:32 -04:00
3788a89b3c
The first real PsyQ library is sourced from real SDK objects in the byte-identical
build, replacing the libcd-region asm stubs. Tasks 2'.1/2'.2/2'.3 — the whole
PsyQ-library-into-build mechanism, proven on the simplest library (libcd).
- psyq_link.py: per-object byte-link engine — recovers externals from the EXE's
resolved relocations (R_MIPS_26 / HI16+LO16); weakens psyq-obj-parser's
mislabelled common-style .bss globals so a strong --defsym wins
- psyq_link_lib.py: whole-library verify — 18/18 libcd objects byte-identical
- psyq_link_region.py: region link via NOLOAD data placement (no carving) +
weaken-all-.bss; gap-aware per-object placement
- psyq_integrate.py: build wiring — splat resegment swap + per-object .text at
vram + NOLOAD data; external resolution (func_<addr> aliases, symbols.us.txt
jump-table names, recovered St*/CD_* globals, all weakened .bss commons);
idempotent; conditional on tools/psyq/ (fresh clone builds via stubs)
- split_src_region.py: H5-safe src/*.c split at vram boundaries (preserves
matched C + NON_MATCHING blocks)
- splat.us.exe.yaml: 5-way text split [pre][libcd1][gap][libcd2][post]
- src/800.c trimmed to <0x80043088; src/{800b,gap,libcd1,libcd2}.c splat stubs
- Makefile: libcd integration in the $(OUT) recipe (conditional + idempotent)
- cookbook §9.1/9.2/9.3 (the reusable mechanism for libgs/Gen2); CURRENT_PHASE
session-D log; ≥3-session green bar A/B/C/+D
- make clean && extract && build && check -> 143dbb89... BYTE-IDENTICAL
99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Link every used object of a PsyQ library and byte-verify each against the BFM EXE.
|
|
|
|
Driver over `psyq_identify.py` (placement) + `psyq_link.link_object` (per-object byte link).
|
|
For a library's ELF-object dir it:
|
|
1. runs psyq_identify to get {object: text_vram} for the objects the EXE actually links,
|
|
2. links each object at its vram, recovering externals from the EXE relocations,
|
|
3. byte-compares each `.text`, prints a PASS/FAIL table + summary,
|
|
4. aggregates recovered externals across objects and flags any inconsistency (the same
|
|
symbol recovered to two addresses = a bug to investigate),
|
|
5. writes a JSON report to .run/psyq_link.<lib>.json (consumed by the build-wiring step 2'.3).
|
|
|
|
Usage: psyq_link_lib.py <elf_dir> [text_lo text_hi] e.g. .run/obj40/libcd
|
|
"""
|
|
import json, os, re, subprocess, sys
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from psyq_link import link_object # noqa: E402
|
|
|
|
EXE = "extracted/retail/SLUS_007.26"
|
|
|
|
|
|
def placement(elf_dir, lo, hi):
|
|
cmd = ["python3", "tools/psyq_identify.py", elf_dir]
|
|
if lo and hi:
|
|
cmd += [lo, hi]
|
|
out = subprocess.check_output(cmd, text=True)
|
|
placed = {}
|
|
for ln in out.splitlines():
|
|
m = re.match(r"\s+0x([0-9A-Fa-f]+)\s+(\S+\.o)\s+\((\d+) ins\)", ln)
|
|
if m:
|
|
placed[m.group(2)] = int(m.group(1), 16)
|
|
return placed, out
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
sys.exit(__doc__)
|
|
elf_dir = sys.argv[1]
|
|
lo = sys.argv[2] if len(sys.argv) > 2 else None
|
|
hi = sys.argv[3] if len(sys.argv) > 3 else None
|
|
lib = os.path.basename(elf_dir.rstrip("/"))
|
|
|
|
placed, idout = placement(elf_dir, lo, hi)
|
|
print(idout.strip())
|
|
print(f"\n=== linking {len(placed)} located {lib} objects ===")
|
|
|
|
exe = open(EXE, "rb").read()
|
|
results, all_ext = [], {}
|
|
conflicts = []
|
|
npass = 0
|
|
for name, vram in sorted(placed.items(), key=lambda kv: kv[1]):
|
|
obj = os.path.join(elf_dir, name)
|
|
r = link_object(obj, vram, name=name, exe_bytes=exe)
|
|
results.append(r)
|
|
tag = "PASS" if r["ok"] else "FAIL"
|
|
if r["ok"]:
|
|
npass += 1
|
|
extra = ""
|
|
if not r["ok"]:
|
|
extra = " " + (r.get("error") or f"{r.get('ndiff','?')} word(s) diff @ "
|
|
f"{hex(r['first_diff']) if r.get('first_diff') else '(size)'}")
|
|
print(f" [{tag}] 0x{vram:08X} {name:12s} ({r['tsize']:5d} B){extra}")
|
|
for s, a in r["externals"].items():
|
|
if s in all_ext and all_ext[s] != a:
|
|
conflicts.append((s, all_ext[s], a, name))
|
|
all_ext.setdefault(s, a)
|
|
|
|
print(f"\n{npass}/{len(results)} objects byte-identical (.text)")
|
|
if conflicts:
|
|
print(" !! external address conflicts:")
|
|
for s, a0, a1, nm in conflicts:
|
|
print(f" {s}: 0x{a0:08X} vs 0x{a1:08X} (at {nm})")
|
|
print(f" {len(all_ext)} distinct externals recovered across the library")
|
|
|
|
rep = {
|
|
"lib": lib,
|
|
"elf_dir": elf_dir,
|
|
"objects": [
|
|
{"name": r["name"], "text_vram": r["text_vram"], "tsize": r["tsize"],
|
|
"rdata_vram": r["rdata_vram"] if isinstance(r["rdata_vram"], int) else None,
|
|
"data_vram": r["data_vram"] if isinstance(r["data_vram"], int) else None,
|
|
"ok": r["ok"], "externals": {s: a for s, a in r["externals"].items()},
|
|
"unrecovered": r["unrecovered"]}
|
|
for r in results
|
|
],
|
|
"externals_union": all_ext,
|
|
"conflicts": [{"sym": s, "a": a0, "b": a1, "at": nm} for s, a0, a1, nm in conflicts],
|
|
"npass": npass, "ntotal": len(results),
|
|
}
|
|
os.makedirs(".run", exist_ok=True)
|
|
path = f".run/psyq_link.{lib}.json"
|
|
json.dump(rep, open(path, "w"), indent=1)
|
|
print(f" report -> {path}")
|
|
sys.exit(0 if npass == len(results) else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|