mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 21:36:06 -04:00
fix(phase-26): jtbl_carve trims trailing .align pad words (§8a-pad)
A trailing `.word 0x00000000` under a jtbl dlabel is the ORIGINAL TU's intra-rdata .align 3 padding, NOT a table entry (0x00000000 is not a jump target). The true entry count is the fn's `sltiu <n>` bound: func_8015AE2C has sltiu 0x7 = 7 entries yet its raw dlabel spans 8 words. maspsx drops all .align, so a C-emitted jump table can never reproduce the pad. Carving to the next dlabel would reserve 8 words while the compiled object supplies 7 -> .rodata under-fills by 4 B -> every later symbol shifts +4 (the same image-corruption class as §41d). jtbl_range now trims trailing zero words, leaving the pad in the raw post-carve data piece. Retroactively explains the §8a func_80159C84 '5 words vs the real 6' false-MATCH. Existing carves are parsed from CONFIG, not re-derived, so committed banks are unaffected (verified: the 3 carved jtbls are absent from the raw data asm). The build never invokes jtbl_carve, so the fleet is inert to this change until the next bank. Found by the Fable5 crack of func_8015AE2C (562 ins x134, MATCH, pin-free).
This commit is contained in:
@@ -362,6 +362,25 @@ a 25-ins single-jtbl jr-function in ov_SC01_077):**
|
||||
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)
|
||||
|
||||
**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.
|
||||
|
||||
## §8b MULTI-jtbl per overlay — the `ld_interleave --order` sandwich + the same-subseg cases (Phase 26 session 4)
|
||||
Once ONE jr-function is banked in an overlay, banking a SECOND makes it multi-jtbl (§8a's single-carve breaks:
|
||||
`jtbl_family_bank.revert()` restores the committed config = already has carve #1). The generalization:
|
||||
|
||||
+46
-2
@@ -100,14 +100,58 @@ def all_data_labels(ov):
|
||||
return sorted(labels)
|
||||
|
||||
|
||||
def jtbl_words(ov, jtbl_hex):
|
||||
"""The raw `.word` values under `dlabel jtbl_<hex>`, in order."""
|
||||
pat = re.compile(rf"dlabel\s+jtbl_{jtbl_hex}\b", re.I)
|
||||
for p in glob.glob(os.path.join(REPO, "asm", ov, "data", "*.data.s")):
|
||||
lines = open(p).read().split("\n")
|
||||
for i, ln in enumerate(lines):
|
||||
if pat.search(ln):
|
||||
out = []
|
||||
for ln2 in lines[i + 1:]:
|
||||
m = re.search(r"\.word\s+(0x[0-9A-Fa-f]+)", ln2)
|
||||
if m:
|
||||
out.append(int(m.group(1), 16))
|
||||
continue
|
||||
if re.search(r"\b(?:dlabel|glabel|enddlabel)\b", ln2):
|
||||
break
|
||||
return out
|
||||
return []
|
||||
|
||||
|
||||
def jtbl_range(ov, jtbl_hex, labels, region_end_vram):
|
||||
"""(start_vram, end_vram) of a RAW jtbl_<hex>: end = next data dlabel, else the region end."""
|
||||
"""(start_vram, end_vram) of a RAW jtbl_<hex>: end = the next data dlabel, MINUS any trailing
|
||||
zero words.
|
||||
|
||||
A trailing `.word 0x00000000` under a jtbl dlabel is NOT a table entry — it is the original TU's
|
||||
intra-rdata **`.align 3` padding** (a jtbl whose entries end ≡4 mod 8, with another jtbl of the
|
||||
same TU following, gets one zero word of alignment fill). It cannot be an entry: 0x00000000 is
|
||||
not a jump target, and the function's `sltiu <n>` range check names the true entry count
|
||||
(byte-confirmed: `func_8015AE2C` → `sltiu 0x7` = 7 entries, yet the raw dlabel spans 8 words).
|
||||
|
||||
This matters because **maspsx drops `.align`**, so a C-emitted jump table can never reproduce the
|
||||
pad. Carving to the next dlabel would reserve 8 words while the compiled object supplies only 7 —
|
||||
under-filling the `.rodata` piece by 4 bytes and shifting every later symbol (the same +4 image
|
||||
corruption class as §41d). Trimming leaves the pad where it belongs: in the raw post-carve data
|
||||
piece. This also retroactively explains the §8a `func_80159C84` "5 words vs the real 6"
|
||||
false-MATCH."""
|
||||
start = int(jtbl_hex, 16)
|
||||
if start not in labels:
|
||||
sys.exit(f"jtbl_carve: jtbl_{jtbl_hex} not found in the raw data asm "
|
||||
f"(asm/{ov}/data/*.data.s) — already carved / stale asm? re-extract or --revert first")
|
||||
nxt = next((a for a in labels if a > start), None)
|
||||
return start, (nxt if nxt is not None else region_end_vram)
|
||||
end = nxt if nxt is not None else region_end_vram
|
||||
words = jtbl_words(ov, jtbl_hex)
|
||||
if words:
|
||||
n = len(words)
|
||||
while n > 0 and words[n - 1] == 0:
|
||||
n -= 1
|
||||
trimmed = start + n * 4
|
||||
if trimmed < end:
|
||||
print(f"jtbl_carve: jtbl_{jtbl_hex}: trimmed {(end - trimmed) // 4} trailing .align pad "
|
||||
f"word(s) — {n} real entries")
|
||||
end = trimmed
|
||||
return start, end
|
||||
|
||||
|
||||
def parse_config(ov):
|
||||
|
||||
Reference in New Issue
Block a user