mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-02 07:59:52 -04:00
f6f89781ff
- tools/rollout_o0_cluster.py (new) + Makefile O0_CLUSTER_OBJS -O0 wildcard: the -O0-cluster carve (0x13410..0x14834), adapting rollout_whale_o0.py to a 3-way <ov>/<ov>_o0/<ov>_o2b split - ov_SC07_010: carve byte-neutral -> family_sweep --hseq banked 9/9 -O0 exemplar-family members whole-binary (R22 clean-fleet 140/140). The Task-1 masked-MATCH swing verdict is now a BANKED FACT: -O0 cluster members DO bank at -O0 (§52b). Phase-20 'func_8013B7AC overlay-local' refuted. - THE WALL (byte-proven, TOOLING not compiler): the same carve on 006/007/011 byte-shifts the whole image (+0x20 %lo data-symbol shift, 34% diff) from a CLEAN build; boundaries verified as real fn-starts. Root cause = splat re-disassembly of a 3-way-split subseg that still holds INCLUDE_ASM stubs (the whale's stub-free _o0b shape avoids it). The Phase-20 '-O0 split infra' wall, root-caused. - DEFERRED (ROI): full -O0 fleet rollout (~1,233 / ~0.6pp) — 3/4 sampled walled + 134 jr-embedded + bigger levers (Task 3 core-cracks, Task 6 tiny-IMM ~5,566). decision-log R31 + cookbook §18-P29. - 140/140 byte-identical; dedup 1840/0; 0 NON_MATCHING (G4); main 143dbb89. Task 2 substantively done.
133 lines
6.5 KiB
Python
133 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Phase-29 T2 Arm A: carve the -O0 cluster (vram 0x8013B568..0x8013C98C, file 0x13410..0x14834)
|
|
into its own -O0 object per single-file overlay, so the h_seq family members of those 9 matched
|
|
-O0 exemplars can bank whole-binary (they masked-MATCH only at -O0 — the Task-1 swing verdict).
|
|
|
|
Background (the swing verdict, 2026-07-16): `family_sweep --hseq` compiled the -O0 cluster members
|
|
at -O2 (member stub files are -O2), so their remapped bodies masked-MATCH but fail the whole-binary
|
|
gate. gcc-2.7.2 has no per-function optimize pragma (opt is per-FILE), so — exactly like the whale
|
|
(rollout_whale_o0.py, §38) and ov_SC01_077's own _o0 split (Phase-19 §18) — each overlay must
|
|
compile the cluster in its own -O0 TU. This tool builds that split; the family sweep then fills it.
|
|
|
|
The cluster is a contiguous run of 16 fns (all INCLUDE_ASM stubs in an unswept overlay). We carve
|
|
<ov>.c into 3 objects (a single object's .text can't be split around a middle object):
|
|
- <ov>.c = before the cluster (0x0..0x13410), keeps name + nonmatchings/<ov> asm paths
|
|
- <ov>_o0.c = the -O0 cluster (0x13410..0x14834), asm paths -> nonmatchings/<ov>_o0
|
|
- <ov>_o2b.c = after the cluster (0x14834..data tail), asm paths -> nonmatchings/<ov>_o2b
|
|
The Makefile O0_CLUSTER_OBJS wildcard -O0-compiles every <ov>_o0.o. Byte-NEUTRAL by construction:
|
|
INCLUDE_ASM pastes the ORIGINAL bytes regardless of -O0/-O2, so a freshly-carved (all-stub) overlay
|
|
stays byte-identical — gate it before sweeping. Idempotent (skips carved overlays). The whole-binary
|
|
byte-gate (make check-all) is the sole arbiter (G3/P9). Usage:
|
|
rollout_o0_cluster.py <ov> [<ov> ...] # single-file overlays only (files=1); whale/jr-carved
|
|
# overlays keep the cluster inside jr_801380E0 (not handled)
|
|
"""
|
|
import sys, re, os
|
|
|
|
BASE = 0x80128158
|
|
LO_FOFF, HI_FOFF = 0x13410, 0x14834 # cluster vram 0x8013B568 .. 0x8013C98C
|
|
START_FN = "func_8013B568" # first fn of the cluster (0x13410)
|
|
LAST_FN = "func_8013C964" # last fn of the cluster (just below 0x14834)
|
|
CLUSTER_FNS = [ # the 16 contiguous -O0 fns (from ov_SC01_077_o0.c)
|
|
"func_8013B568", "func_8013B598", "func_8013B6A0", "func_8013B7AC",
|
|
"func_8013B7F4", "func_8013B83C", "func_8013BC7C", "func_8013BCDC",
|
|
"func_8013BD34", "func_8013BD74", "func_8013C08C", "func_8013C0F8",
|
|
"func_8013C360", "func_8013C414", "func_8013C938", "func_8013C964",
|
|
]
|
|
|
|
|
|
def header_end(lines):
|
|
"""Header = the leading #include block (+ trailing blanks). Stops at the first non-#include,
|
|
non-blank line (a DEFINE_func macro / decl), so the low-address dedup macros stay in `before`."""
|
|
last_inc = -1
|
|
for i, l in enumerate(lines):
|
|
s = l.strip()
|
|
if s.startswith("#include"):
|
|
last_inc = i
|
|
elif s and last_inc >= 0:
|
|
break
|
|
he = last_inc + 1
|
|
while he < len(lines) and lines[he].strip() == "":
|
|
he += 1
|
|
return he
|
|
|
|
|
|
def unit_start(lines, fn):
|
|
"""Line index where `fn`'s definition/stub begins (an INCLUDE_ASM stub or a `... fn(` def line)."""
|
|
for i, l in enumerate(lines):
|
|
if re.search(rf'INCLUDE_ASM\("[^"]*",\s*{fn}\)\s*;', l):
|
|
return i, "stub"
|
|
if re.search(rf'\b{fn}\s*\(', l) and not l.lstrip().startswith(("//", "*", "extern")) \
|
|
and "INCLUDE_ASM" not in l and (l.rstrip().endswith("{") or ")" in l and ";" not in l):
|
|
return i, "def"
|
|
return None, None
|
|
|
|
|
|
def split_overlay(ov):
|
|
cdir = f"src/{ov}"
|
|
main_c = f"{cdir}/{ov}.c"
|
|
o0_c, o2b_c = f"{cdir}/{ov}_o0.c", f"{cdir}/{ov}_o2b.c"
|
|
yaml = f"config/splat.{ov}.yaml"
|
|
if os.path.exists(o0_c):
|
|
return f"SKIP {ov} (o0 exists)"
|
|
if not (os.path.exists(main_c) and os.path.exists(yaml)):
|
|
return f"SKIP {ov} (missing .c or yaml)"
|
|
lines = open(main_c).read().split("\n")
|
|
|
|
# every cluster fn must be present in THIS file (single-file overlay) — else the cluster is
|
|
# carved elsewhere (jr-embedded overlay) and this tool must not touch it.
|
|
missing = [fn for fn in CLUSTER_FNS if not any(re.search(rf'\b{fn}\b', l) for l in lines)]
|
|
if missing:
|
|
return f"SKIP {ov} (cluster fns not in base .c: {missing[:3]}... — jr-embedded?)"
|
|
|
|
si, _ = unit_start(lines, START_FN)
|
|
li, lkind = unit_start(lines, LAST_FN)
|
|
if si is None or li is None:
|
|
return f"ERROR {ov}: boundary fn not found (start={si}, last={li})"
|
|
if lkind != "stub":
|
|
return f"SKIP {ov} ({LAST_FN} is matched inline — cluster not all-stub; carve by hand)"
|
|
# end = first non-blank line after the LAST_FN stub (keeps the after-region fn's forward externs
|
|
# with the after-region, not the cluster).
|
|
ei = li + 1
|
|
while ei < len(lines) and lines[ei].strip() == "":
|
|
ei += 1
|
|
if not (si < li < ei):
|
|
return f"ERROR {ov}: bad boundary order si={si} li={li} ei={ei}"
|
|
|
|
header = "\n".join(lines[:header_end(lines)]).rstrip("\n")
|
|
before, cluster, after = lines[:si], lines[si:ei], lines[ei:]
|
|
|
|
OLD = f'nonmatchings/{ov}"'
|
|
open(main_c, "w").write("\n".join(before).rstrip("\n") + "\n")
|
|
cluster_body = "\n".join(cluster).replace(OLD, f'nonmatchings/{ov}_o0"')
|
|
open(o0_c, "w").write(header + "\n\n" + cluster_body.rstrip("\n") + "\n")
|
|
after_body = "\n".join(after).replace(OLD, f'nonmatchings/{ov}_o2b"')
|
|
open(o2b_c, "w").write(header + "\n\n" + after_body.rstrip("\n") + "\n")
|
|
|
|
# carve the yaml code subseg [0x0, c, <ov>] -> before / o0 cluster / o2b after
|
|
y = open(yaml).read()
|
|
pat = re.compile(r'^(\s*)- \[0x0, c, ' + re.escape(ov) + r'\].*$', re.M)
|
|
m = pat.search(y)
|
|
if not m:
|
|
return f"ERROR {ov}: '- [0x0, c, {ov}]' not found in yaml"
|
|
ind = m.group(1)
|
|
repl = (f'{ind}- [0x0, c, {ov}] # -O2 before -O0 cluster (file 0x0..0x{LO_FOFF:X})\n'
|
|
f'{ind}- [0x{LO_FOFF:X}, c, {ov}_o0] # -O0 cluster 0x8013B568..0x8013C98C (0x{LO_FOFF:X}..0x{HI_FOFF:X})\n'
|
|
f'{ind}- [0x{HI_FOFF:X}, c, {ov}_o2b] # -O2 after -O0 cluster (0x{HI_FOFF:X}..data tail)')
|
|
open(yaml, "w").write(pat.sub(lambda _m: repl, y, count=1))
|
|
ncl = sum(1 for l in cluster if "INCLUDE_ASM" in l)
|
|
return f"OK {ov}: before {len(before)} / cluster {len(cluster)} ({ncl} stubs) / after {len(after)} lines"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
targets = sys.argv[1:]
|
|
if not targets:
|
|
print(__doc__)
|
|
sys.exit(2)
|
|
n_ok = 0
|
|
for ov in targets:
|
|
r = split_overlay(ov)
|
|
if r.startswith("OK"):
|
|
n_ok += 1
|
|
print(r)
|
|
print(f"--- {n_ok}/{len(targets)} carved ---")
|