Tier A - static-table overwrites
The foundation of the ladder. Flip bytes in a fixed-width table that already exists in the executable or a raw overlay - no compression, no relocation, no new code. It looks like the cheat-code tier, and the write is trivial. The work is entirely in the reversing: pinning the exact table base, stride, and which byte in each record is the field you mean, in a stripped binary where “no static caller” does not mean dead.
Mechanism at a glance
- Target
- Fixed-width tables in
SCUS_942.54or a raw overlay's data section - Edit class
- in-place / same-size; structural bytes (1-based ids, masks, slot types) left untouched
- The work
- locate the table via its consumer function, then byte-validate every field against the curated game-data tables
- Tables
- steal
DAT_80077828· spell MPDAT_800754C8+3· equip-bonusDAT_80074F68· move power + element affinity (PROT 0898) · prices0x80074368+2· casino (PROT 08990x15D00) - Cross-check
cross_table_integrity_real.rs+ per-feature oracles
The reversing: pinning a table in a stripped binary
None of these tables is labelled. Each was located by tracing its consumer in Ghidra - the routine that reads it at runtime - back to the lui/addiu pair that forms the base address, then walking the access pattern to recover the stride and the byte offset of each field. The steal table surfaces from the steal resolver, the spell costs from the cast-MP deduction, the move powers from the damage kernel, the prices from the shop pricer. Two recurring traps make this harder than it sounds:
- “No static caller” ≠ dead. Most game logic lives in RAM overlays, so a table read only by overlay code shows zero references in the executable. The reader has to be found in the captured overlay, not assumed absent.
- LUI+ADDIU base pairs aren't auto-resolved. Ghidra's reference manager doesn't connect a split 32-bit address, so a direct xref on the table address returns nothing even when it's heavily used. The base is recovered by scanning for the
lui/addiuwriters, not by querying references.
Once the base, stride, and field offset are known, each table is byte-validated field-by-field against the curated gamedata tables - the ground-truth labels mined from walkthroughs. That validation is what turns “a plausible address” into a confirmed table, and it's the reason a same-size column shuffle is safe: the randomizer moves only the value column and leaves every structural byte in place.
Worked examples
Steal items (the field-order trap)
The per-monster steal table is DAT_80077828 + monster_id*2 (1-based id, 2-byte stride). Its layout is [steal_chance, steal_item] - chance first, item second, the reverse of the drop field's order in the monster record. The randomizer overwrites only the item byte at +1; the chance is preserved, so what an enemy carries changes but how often it can be stolen does not. The mechanic was pinned from a live steal capture and is byte-exact against the published steal table (steal-table.md).
Spell MP costs & equipment bonuses (column within a record)
Spell costs sit at +3 of the 12-byte spell record (DAT_800754C8, spell-table.md); the equip-bonus table is DAT_80074F68 on an 8-byte stride (equipment-table.md). Both edits permute a single column within a record class - the cost byte across costed spells, the stat tuple within a slot category - without touching the surrounding fields (the capture class, the equip-character mask, the slot type).
Move power & element affinity (a static table inside an overlay)
Not every “static” table lives in the executable. The special-attack power table and the 8×8 element-affinity matrix are fixed tables in the battle overlay's data section (PROT 0898). The technique is identical - same-size in-place overwrite of a located table - the only difference is that the patch targets a PROT overlay entry rather than the executable.
Composition
Tier A is the most composable tier: every edit is same-size and self-contained, so none can disturb another's offsets and all of them combine freely with the higher tiers. Two specializations ride on the same shape:
- New-game seed stamp (variant H). Starting level overwrites the new-game roster-seed literals - the displayed-level byte, the growth-curve stat rows, the lead's cumulative-XP words - all same-size SCUS edits, just aimed at the boot seed rather than a gameplay table.
- String injection (variant I). Naming an unused item repoints its name-table pointer (a tier-A edit) and writes the new string into a reserved zero gap - that second write shares the executable's rodata gap, the contended region the tier E map lays out.
Mods using this technique
- Steal items (
--steals) · Spell MP costs (--spell-cost) · Equipment stat bonuses (--equip-bonus) - Special-attack power (
--move-power) · Element-affinity matrix (--element-affinity) - Item prices (
--shopspricing) · Casino prize exchange (--casino) - Starting level (
--starting-level, variant H) · Unused-item naming (--unused-items, variant I)
Adding a tier-A mod: locate the table via its consumer function (overlay sweep + lui/addiu scan, not an xref query), byte-validate every field against gamedata, move only the value column, and add a disc-gated oracle.