Files
BFM-decomp/cookbook/C0010.md
T

5.7 KiB
Raw Blame History

§8a rodata island in a flat OVERLAY — the tail sandwich, per matched jr-function (Phase 26 — PoC PROVEN)

The EXE's §8 was one central island. The overlays are different: gcc's switch jtbls sit in ONE contiguous .rodata block at the TAIL of the flat blob — between the .data globals and a tiny .data remnant (ov_SC01_077: island vram 0x801D7F9C..~0x801D9460, right after .data global D_801D7F94; layout = text → data-globals → rodata-jtbls → data-tiny). While every jr-function is INCLUDE_ASM the jtbls emit as in-place .data and the build is byte-fine. The moment you MATCH a jr-function, its C emits the jtbl into .rodata (which the overlay section_order:[.rodata,.text,.data,.bss] floats to the FRONT @0x80128158) and the raw copy is still in the data tail → duplicate + wrong address. The proven fix (byte-identical on func_8012ACE0, a 25-ins single-jtbl jr-function in ov_SC01_077):

  • Carve per matched fn. Split the […, data, tail] subseg around that function's jtbl(s) into […, data, tail] (globals + pre-carve jtbls, still raw .data) + [<off>, .rodata, <code-subseg-name>] (the fn's jtbl → migrates into its asm/nonmatchings/<subseg>/<fn>.s as .section .rodata; the name MUST match the code subseg the fn lives in, e.g. ov_SC01_077_a) + [<off2>, data, tail2] (post-carve jtbls + tail, raw). Other functions' jtbls STAY raw .data until they too are matched (per-fn carve, not whole-island).
  • Place via the parameterized ld_interleave (Phase-26: added --section .<binary> → derives the <binary>_TEXT/DATA/RODATA/DATA2/BSS symbol prefix; default .main = the EXE, byte-identical): it rewrites the overlay's output section to text → data(tail, --front tail.data.o) → rodata → data(tail2+trailing, --tail tail2.data.o --tail trailing.o) → bss. Wired into make extract via a per-binary <bin>_JTBL_INTERLEAVE var in config/overlays.mk (holds the --front/--tail basenames) + an ifneq ($(strip $(JTBL_INTERLEAVE)),) branch. GOTCHA: put NO trailing #comment on the JTBL_INTERLEAVE := line and $(strip) it — a trailing comment leaves whitespace → non-empty → the branch misfires on EVERY binary (ld_interleave then runs with EXE defaults → "front data object not found" on resident).
  • The C body needs canon_sig_reconcile before it will compile in the real TU (the raw draft hits conflicting types for <fn> vs the TU's forward decl + conflicting types for <typedef> vs a sibling; reconcile rewrites the def sig to canonical + uniquifies the draft's typedefs + block-scopes externs). Placement is orthogonal — reconcile first, then the carved jtbl lands byte-exact.
  • Alignment: gcc emits the jtbl .rdata .align 3 (8-byte). If the original jtbl address is 8-aligned (jtbl_801D8078, 0x…078) there is no pad and it lands exact. A 4-aligned original address (jtbl_801D8AFC) would force a 4-byte align pad → HANDLED (Phase 29): the §8e JTBL_PADS pad-spec filter (hit for real by jtbl_801D8144 in the func_80131340 bank).
  • rtu_match is NOT a whole-binary gate for jr-functions — it masks relocs AND excludes the §8 jtbl rodata, so it MATCHes a body whose switch is subtly wrong (e.g. func_80159C84's 2nd jtbl was 5 words vs the real 6 — a false-MATCH). Always confirm jr-function cracks with the whole-binary gate (which now works, via this carve).
  • ×134 automation (NEXT): each overlay sibling has the SAME jr-function at a per-overlay address with its own jtbl in its own tail → the carve config + the <bin>_JTBL_INTERLEAVE var must be generated per overlay from the sibling's jtbl address (a tool over family_sweep), then reconcile+template the body per sibling. The PoC proves the per-binary mechanism; the fleet rollout is the mechanical generator.

§8a-pad — a trailing .word 0x00000000 under a jtbl dlabel is .align PAD, not an entry (Phase 26 session 6, byte-proven)

⚠ CORRECTED by §8e (Phase 29): the "maspsx drops all .align" rationale below is FALSE (that continue is in an inventory-only pass; the output path passes .align verbatim). The TRIM itself remains correct — the pad belongs to the NEXT table's .align 3, absent when that owner isn't compiled in the same object. Multi-table spans now reproduce interior pads via the §8e JTBL_PADS spec filter.

This retroactively explains the §8a func_80159C84 "5 words vs the real 6" false-MATCH.

The raw dlabel jtbl_XXXXXXXX in asm/<ov>/data/*.data.s can span one word MORE than the switch has cases. That last .word 0x00000000 is the ORIGINAL TU's intra-rdata .align 3 padding — emitted when a jump table's entries end ≡4 mod 8 and another jtbl of the same TU follows. It cannot be a table entry: 0x00000000 is not a jump target.

  • The true entry count is the function's sltiu <n> range check, not the dlabel span. Byte-confirmed: func_8015AE2C → sltiu $v0, $v1, 0x7 = 7 entries, yet its raw dlabel spans 8 words.
  • maspsx drops all .align (maspsx.py:435), so a C-emitted jump table can NEVER reproduce the pad.
  • Therefore jtbl_carve must TRIM trailing zero words from the carve range, leaving the pad in the raw post-carve data piece. Carving to the next dlabel reserves 8 words while the compiled object supplies only 7 → the .rodata piece under-fills by 4 bytes → every later symbol shifts +4 (the same image corruption class as §41d: ~271k differing bytes from one missing word). Trimming is always safe.
  • Existing carves are parsed from the CONFIG (their end = the next piece's offset), not re-derived from the data asm, so the trim only affects NEW carves — committed banks are unaffected.