Tier A - static-table overwrites
What a monster lets you steal, what a spell costs in MP, which element beats which, what a shop charges, what the casino sells: on a randomizer seed all of these change, and every one is a single byte flipped in a fixed-width table the game already carries. The write is trivial. The work is knowing exactly which byte - in a stripped binary with no symbol names.
At a glance
- Target
- Fixed-width tables in
SCUS_942.54(always resident) or in a raw overlay's data section (PROT 0898 battle, PROT 0899 menu) - Edit class
- In place, same size - no compression, no relocation, no new code
- Mods
--steals·--spell-cost·--equip-bonus·--move-power·--element-affinity· item prices ·--casino·--starting-level(variant: stamp) ·--unused-items(variant: string)- Ground truth
- The curated game-data tables - every located table is byte-validated against them before it is trusted
- Confidence
- Confirmed - each table pinned from its reader and cross-checked field-by-field
- Oracles
cross_table_integrity_real.rsplus one disc-round-trip test per feature
How a table is pinned
None of these tables is labelled. Each is located from its consumer - the routine that reads it while the game runs - and validated against the curated tables before a single byte is written.
lui/addiu pair; scan for the pair, since a plain cross-reference finds nothing.The tables
| Table | Where | Stride | Field moved | Mod |
|---|---|---|---|---|
| Steal | SCUS DAT_80077828 | 2 B | item byte at +1; chance at +0 preserved | --steals |
| Spell record | SCUS DAT_800754C8 | 12 B | MP cost at +3, permuted across costed spells | --spell-cost |
| Equipment bonus | SCUS DAT_80074F68 | 8 B | stat tuple, permuted within a slot category; equip mask + slot type preserved | --equip-bonus |
| Item record | SCUS DAT_80074368 | 12 B | price u16 at +2 | item prices |
| Move power | PROT 0898 (battle overlay) file 0x26744 | 26 B | power / behaviour bytes - move-power | --move-power |
| Element affinity | PROT 0898 | 8×8 cells | damage-scale percentages permuted (100 = neutral, 0 = immune) | --element-affinity |
| Casino prizes | PROT 0899 (menu overlay) file 0x15D00 | 8 B | whole record shuffled, so a prize keeps its coin price and progression gate | --casino |
Two of the seven live in overlays - the code the game pages into RAM per mode - rather than in the executable. The technique is identical; only the file the write targets differs.
Two variants that ride on the same shape
- New-game seed stamp (the stamp variant,
--starting-level). The executable holds the roster the game seeds at New Game. Overwriting its displayed-level byte, growth-curve rows and the lead's cumulative-XP words is a same-size edit aimed at the boot template instead of a gameplay table - new-game table. - String injection (the string variant,
--unused-items). Naming an unused item re-points its name-table pointer (a tier-A write) and writes the new string into a reserved zero run - the executable's rodata gap, the contended region the tier E map lays out.
Tier A composes with everything: every edit is same-size and self-contained, so none can disturb another's offsets.
How we know
| Claim | Evidence | Reference |
|---|---|---|
Steal table base, stride, [chance, item] order | Live steal capture (monster 13 entry at 0x80077842 matches the banner) + byte-exact match against the published steal table, every id | steal-table |
Spell MP cost at +3 | Cast-MP deduction in the battle-action machine FUN_801E295C, state 0x28 | spell-table |
| Equipment bonus stride 8 | Slot walker FUN_801CF650 reads five slots at that stride | equipment-table |
Item price at +2 | Verified live against shop prices (War God Band 21000) | item-table |
| Move power / affinity in PROT 0898 | Damage kernel reads via FUN_801dd0ac, indexed by the actor's queued action byte | move-power, battle-formulas |
| Casino prize table in PROT 0899 | Prize exchange debits the coin bank, not gold; four 0x60-byte blocks at VA 0x801E4518 | randomizer |
Details: why a cross-reference query finds nothing
Two properties of the binary make step 2 above necessary. Most game logic lives in RAM overlays loaded at 0x801C0000+, so a table read only by overlay code has zero references in the executable - "no static caller" never means dead. And a MIPS 32-bit address is assembled from two instructions (lui for the high half, addiu for the low half); Ghidra's reference manager does not join them, so a direct cross-reference on the table address returns nothing even when it is read every frame. The base is recovered by scanning for the writer pair instead - Ghidra workflow.
Details: adding a tier-A mod
- Locate the table through its consumer (overlay sweep +
lui/addiuscan). - Decode the whole table and byte-validate every field against
gamedata. - Move only the value column; preserve every structural byte.
- Add a disc-gated oracle under
crates/patcher/tests/that re-decodes the patched image and checks the multiset is preserved and every touched sector is EDC/ECC-valid.