mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-30 07:31:59 -04:00
038e7de532
- 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
92 lines
3.6 KiB
Python
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)}")
|