Files
BFM-decomp/tools/psyq_identify.py
T
Drew T 038e7de532 feat(phase-7): LZSS cross-jump barrier breakthrough + libgs byte-verified — session E checkpoint
- LZSS: defeated gcc 2.7.2 -O2 cross-jump-merge of the twin state-save tails with a
  zero-byte __asm__ __volatile__("" ::: "memory") barrier (find_cross_jump bails on
  ASM_INPUT; ground-truthed vs gcc-2.7.2.3 jump.c by a web-research subagent).
  LzssDecodeSector now 122 instructions (correct structure); ~3 regalloc/scheduling
  swaps remain -> C kept under #ifdef NON_MATCHING, default build = stub = BYTE-IDENTICAL
- surgical rodata carve (lean LZSS path, no libgs needed): splat carves ONLY
  jtbl_80072A38 ([0x63238,.rodata,800] bounded by [0x6324C,data,6324C]); ld_interleave
  wired into `make extract` as the .data->.rodata->.data sandwich (TAIL_DATA=6324C.data.o)
- libgs: 31/32 used objects byte-verified IDENTICAL to real PsyQ libgs 4.0 (0 conflicts,
  69 externals); GS_001 deferred (psyq-obj-parser .bss-common scattering); 6-block
  resegmentation wiring pending. psyq_identify.py now skips data-only objects (GLOBAL.o)
- cookbook: §3a (web-research compiler internals — escalation tier above the permuter)
  + §5a (the cross-jump barrier idiom) — both reusable
- build BYTE-IDENTICAL (143dbb89f34491258bbc27810d0a12ec8b43a8dd) throughout
2026-06-15 01:47:18 -06:00

92 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Locate where PsyQ library objects are linked in the target EXE.
For each ELF .o (converted from a PsyQ .LIB member), extract its `.text` and the
relocation offsets, build a relocation-masked word pattern (relocated immediate
fields zeroed), and scan the EXE text for the single position where every
NON-relocated word matches. That position is the object's link address in the EXE
(or "absent" if the EXE doesn't link it). This is the placement map the library
linker step consumes.
Usage: psyq_identify.py <elf_dir> [text_lo_vram text_hi_vram]
(defaults to the BFM .text window 0x80010000..0x800629DC)
"""
import struct, subprocess, re, sys, glob, os
EXE = "extracted/retail/SLUS_007.26"
VRAM_BASE = 0x8000F800
ELF_DIR = sys.argv[1] if len(sys.argv) > 1 else ".run/obj40/libcd"
TLO = int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x80010000
THI = int(sys.argv[3], 0) if len(sys.argv) > 3 else 0x800629DC
b = open(EXE, "rb").read()
text = b[TLO - VRAM_BASE: THI - VRAM_BASE]
twords = [struct.unpack_from("<I", text, i)[0] for i in range(0, len(text), 4)]
def obj_text_pattern(o):
"""Return (words, mask) for the object's .text; mask[i]=0 on relocated/jump words.
A data-only object (no `.text` section — e.g. libgs GLOBAL.o, which defines only
globals) makes `objdump -j .text` exit non-zero; treat that as an empty .text so the
caller's `no-.text` path handles it instead of crashing."""
p = subprocess.run(["mipsel-linux-gnu-objdump", "-dr", "-j", ".text", o],
capture_output=True, text=True)
if p.returncode != 0:
return [], []
d = p.stdout
words, mask = [], []
pending_reloc = False
for line in d.splitlines():
mi = re.match(r"\s+([0-9a-f]+):\s+([0-9a-f]{8})\s", line)
if mi:
w = int(mi.group(2), 16)
words.append(w)
# mask jal/j (opcode 2/3) always (R_MIPS_26 target is link-resolved)
mask.append(0 if (w >> 26) in (2, 3) else 0xFFFFFFFF)
elif "R_MIPS" in line and words:
# relocation annotation follows its instruction line -> mask low 16 (hi/lo/pc16)
if "_26" in line:
mask[-1] = 0
else:
mask[-1] = 0xFFFF0000
return words, mask
def find(words, mask):
n = len(words)
if n < 2:
return None # too short to anchor uniquely
hits = []
for s in range(0, len(twords) - n + 1):
ok = True
for i in range(n):
if (twords[s + i] & mask[i]) != (words[i] & mask[i]):
ok = False; break
if ok:
hits.append(s)
if len(hits) > 1:
break
if len(hits) == 1:
return TLO + hits[0] * 4
return ("ambiguous" if len(hits) > 1 else None)
results = []
for o in sorted(glob.glob(os.path.join(ELF_DIR, "*.o"))):
words, mask = obj_text_pattern(o)
if not words:
results.append((os.path.basename(o), "no-.text", len(words))); continue
r = find(words, mask)
results.append((os.path.basename(o), r, len(words)))
found = [(n, a, l) for n, a, l in results if isinstance(a, int)]
found.sort(key=lambda x: x[1])
print(f"{ELF_DIR}: {len(found)}/{len(results)} objects located in EXE text "
f"[0x{TLO:X}..0x{THI:X}]")
for n, a, l in found:
print(f" 0x{a:08X} {n:18s} ({l} ins)")
absent = [n for n, a, l in results if a is None]
amb = [n for n, a, l in results if a == "ambiguous"]
if absent:
print(f" not linked by EXE ({len(absent)}): {', '.join(absent[:12])}{' …' if len(absent)>12 else ''}")
if amb:
print(f" ambiguous ({len(amb)}): {', '.join(amb)}")