mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
57e5f970c8
* draw_waves Usage advertised [--no-main], which argparse never defined (the flags are --main / --only-main, and main is excluded by default), and omitted --exclude-file, which is now a PREREQUISITE that refuses a stale list. * jr_isolate's STATUS block still declared the tool BLOCKED on split_src_region with the blocker unbuilt. Five defects were fixed this session and it runs the full chain to completion; what remains is a duplicate-definition class at assembly. Says so, and points at §431 as the cheaper route than finishing the item model. * ld_interleave's layout diagram — the first thing anyone reads — showed the pre-S72 three-piece island with 6324C.data.o. main's island is SEVEN pieces driven by --order; --front/--tail is the overlay form now. * jtbl_rodata_pads described a stored-spec-only filter and advertised guards that no longer all exist; --derive serves main since S72.
136 lines
7.0 KiB
Python
136 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Isolate a jr-function into its own code subseg (Phase-26 §8 multi-jtbl, the non-contiguous case).
|
|
|
|
Two matched jr-functions in ONE code object emit their jtbls CONTIGUOUS in that object's .rodata
|
|
(gcc source order). That is byte-correct only if their jtbls are adjacent in the overlay's .rodata
|
|
island. When an UNMATCHED jtbl sits between them, the object can't reproduce the island layout ->
|
|
jtbl_carve refuses (non-contiguous same-subseg). This tool splits the containing code subseg so the
|
|
target function becomes its own object (the whale `_o0b` precedent), preserving every other matched C
|
|
body (H5, via split_src_region.py trim/inject), after which its jtbl carves independently.
|
|
|
|
[s_off, c, SUB] -> [s_off, c, SUB] (functions < func)
|
|
[f_off, c, <ov>_jr_<addr>] (func alone -> its own object)
|
|
[f_end, c, <ov>_after_<addr>](functions >= func's end)
|
|
|
|
The carve subseg names are re-derived by jtbl_carve from each function's ADDRESS against the current
|
|
code subsegs, so after this split jtbl_carve automatically points every carve at its new object -- this
|
|
tool only restructures code subsegs; it never edits a `.rodata` carve. Idempotent (no-op if already
|
|
isolated). Self-contained: does the config split, the source trim/inject, and the re-extract.
|
|
|
|
jr_isolate.py <ov> --func func_XXXX
|
|
|
|
STATUS (P31 S73): the Phase-26 blocker is GONE. `split_src_region.py` could not partition an
|
|
overlay `.c` because it demanded an address for every top-level item; five defects were fixed
|
|
(address-less preamble runs coalesce FORWARD into the item below them; coalesce carries
|
|
(addr, name, text) captured BEFORE the merge; `item_name` strips comments; it anchors on a
|
|
DEFINITION, not on a leading `extern` — which used to return the name "void"; and it accepts
|
|
an INDENTED top-level definition). This tool now runs the full chain to completion on
|
|
`ov_SC02_005` — config split, source trim, re-extract, inject.
|
|
|
|
NOT DONE: the resulting object still fails to ASSEMBLE on a remaining duplicate-definition
|
|
class, so no overlay has been split with it yet. Before sinking more time into the item
|
|
model, consider cookbook §431 instead — cut the file verbatim at line boundaries and let the
|
|
COMPILER enumerate what crosses. That is what worked first time on main, and it does not
|
|
depend on this tool being correct.
|
|
"""
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import jtbl_carve # cfg_path, func_subseg, overlay_vram_base, PIECE_RE
|
|
|
|
REPO = jtbl_carve.REPO
|
|
|
|
|
|
def func_end_vram(ov, func):
|
|
"""func's end vram = its last .text instruction's vram + 4 (from the nonmatchings .s)."""
|
|
sub = jtbl_carve.func_subseg(ov, func)
|
|
s = open(os.path.join(REPO, "asm", ov, "nonmatchings", sub, f"{func}.s")).read()
|
|
# instruction lines: /* <fileoff> <VRAM> <bytes> */ <mnemonic>
|
|
addrs = [int(m, 16) for m in re.findall(
|
|
r"/\*\s*[0-9A-Fa-f]+\s+([0-9A-Fa-f]{8})\s+[0-9A-Fa-f]{8}\s*\*/\s*\S", s)]
|
|
if not addrs:
|
|
sys.exit(f"jr_isolate: no instruction addresses in {func}.s")
|
|
return max(addrs) + 4
|
|
|
|
|
|
def sh(cmd, **kw):
|
|
return subprocess.run(cmd, cwd=REPO, **kw)
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("ov")
|
|
ap.add_argument("--func", required=True, help="func_XXXXXXXX to isolate into its own code subseg")
|
|
a = ap.parse_args()
|
|
ov, func = a.ov, a.func
|
|
m = re.fullmatch(r"func_([0-9A-Fa-f]{8})", func)
|
|
if not m:
|
|
sys.exit("jr_isolate: --func must be func_XXXXXXXX")
|
|
faddr_hex = m.group(1)
|
|
base = jtbl_carve.overlay_vram_base(ov)
|
|
cfg = jtbl_carve.cfg_path(ov)
|
|
txt = open(cfg).read()
|
|
|
|
jr_name = f"{ov}_jr_{faddr_hex}"
|
|
after_name = f"{ov}_after_{faddr_hex}"
|
|
# IDEMPOTENCY MUST CHECK THE LAST STEP, NOT THE FIRST (P31 S72). This tool writes the CONFIG
|
|
# split, then splits the SOURCE, then re-extracts. Keying "already isolated" on the config
|
|
# alone means any failure in between leaves a half-applied tree in which the tool believes it
|
|
# is finished and short-circuits forever — measured here: the first run died in
|
|
# split_src_region, and the retry (after that bug was fixed) printed "already isolated" over a
|
|
# source file that had never been split. Require the SOURCE to exist too, and say which half
|
|
# is missing so the state is recoverable instead of mysterious.
|
|
src_c = os.path.join(REPO, "src", ov, f"{jr_name}.c")
|
|
if f", c, {jr_name}]" in txt and not os.path.exists(src_c):
|
|
sys.exit(f"jr_isolate {ov}: HALF-APPLIED — config/{os.path.basename(cfg)} already names "
|
|
f"`{jr_name}` but {os.path.relpath(src_c, REPO)} does not exist. Revert the config "
|
|
f"(git checkout -- {os.path.relpath(cfg, REPO)}) and re-run, or finish the source "
|
|
f"split by hand. Refusing rather than reporting success over a broken tree (R43).")
|
|
if f", c, {jr_name}]" in txt:
|
|
print(f"jr_isolate {ov}: {func} already isolated ({jr_name})")
|
|
return
|
|
|
|
sub = jtbl_carve.func_subseg(ov, func) # current containing code subseg
|
|
f_vram = int(faddr_hex, 16)
|
|
f_end = func_end_vram(ov, func)
|
|
f_off, f_end_off = f_vram - base, f_end - base
|
|
|
|
# find the [s_off, c, SUB] config line for the containing subseg
|
|
line_re = re.compile(rf"^([ \t]*)- \[(0x[0-9A-Fa-f]+),\s*c,\s*{re.escape(sub)}\]\s*(?:#.*)?$", re.M)
|
|
mm = line_re.search(txt)
|
|
if not mm:
|
|
sys.exit(f"jr_isolate: no `[..., c, {sub}]` line in {cfg}")
|
|
indent, s_off = mm.group(1), int(mm.group(2), 16)
|
|
if not (s_off <= f_off < f_end_off):
|
|
sys.exit(f"jr_isolate: {func} (0x{f_off:x}..0x{f_end_off:x}) not inside subseg {sub} (@0x{s_off:x})")
|
|
|
|
block = "\n".join([
|
|
f"{indent}- [{hex(s_off)}, c, {sub}]",
|
|
f"{indent}- [{hex(f_off)}, c, {jr_name}] # Phase-26 §8 jr isolation (jr_isolate.py)",
|
|
f"{indent}- [{hex(f_end_off)}, c, {after_name}]",
|
|
])
|
|
txt = line_re.sub(lambda _: block, txt, count=1)
|
|
open(cfg, "w").write(txt)
|
|
|
|
# source: keep functions < f_vram in SUB.c, move real-C >= f_end to a frag for the after file
|
|
sub_c = f"src/{ov}/{sub}.c"
|
|
frag = f".run/isolate_{ov}_{func}.frag"
|
|
syms = f"config/symbols.{ov}.txt"
|
|
sh([sys.executable, "tools/split_src_region.py", "--symbols", syms,
|
|
"trim", sub_c, hex(f_vram), hex(f_end), frag], check=True)
|
|
# regenerate: splat emits jr_name.c (func's stub) + after_name.c (stubs for >= f_end)
|
|
if sh(["make", "--no-print-directory", "extract", f"BINARY={ov}"]).returncode:
|
|
sys.exit(f"jr_isolate: extract failed for {ov}")
|
|
# restore the moved matched C into the freshly-generated after file
|
|
sh([sys.executable, "tools/split_src_region.py", "--symbols", syms,
|
|
"inject", f"src/{ov}/{after_name}.c", frag], check=True)
|
|
print(f"jr_isolate {ov}: {func} -> {jr_name}; remainder >= 0x{f_end:x} -> {after_name}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|