The technique in one box
Find a run of bytes that no instruction ever reads, find the exact moment in an existing routine where the new behaviour belongs, and overwrite two instructions there with a jump into the new code. The new code does its work, replays the two instructions it displaced, and jumps back. The hard part is the first step: a run of zeros is not the same as a run nobody reads.

At a glance

Host
A 1028-byte all-zero run in SCUS_942.54 rodata at 0x8007AB38 (always resident), plus four smaller read-watch-verified dead regions totalling 652 bytes
Detour
Two instructions at the hook site become j routine + nop; the routine replays them and jumps back
Edit class
In place, same size; consumes reclaimed rodata - a finite, shared resource
Mods
--equipment-drops · --flee-exp · --enemy-ally · --shiny-seru
Guards
Hook-site words must match the USA build; every target must be all-zero; no target may overlap a catalogued table; routine VAs must be word-aligned
Confidence
Confirmed - byte/disassembly oracles plus emulator playtests; no engine runtime oracle (the from-scratch engine cannot execute injected MIPS)
Oracles
equipment_drops_real.rs · flee_exp_real.rs · enemy_ally_real.rs · shiny_seru_real.rs

The detour

A two-instruction detour into the rodata gap and back retail routine … j routine nop next instruction injected routine (gap) do the new work replay displaced pair j next instruction guards hook words match USA build target bytes all zero outside every known table routine VA word-aligned the two displaced instructions live inside the routine, so the original semantics survive
The hook site is chosen where the new behaviour belongs and no live register is clobbered across the jump. The guards refuse a differently-laid-out image rather than corrupt it.

The four hooks

ModHook siteRoutineWhat it does
--equipment-drops0x8004F610 in the reward tally FUN_8004E568 (SCUS)0x8007AB80 + id tableRolls a per-battle chance; grants one random equipment id through the game's own add-item helper, on top of the normal drop.
--flee-exp0x801E5A10, escape teardown state 0x66 of FUN_801E295C (battle overlay, PROT 0898)0x8007AD00Sums the fled formation's EXP, scales by --flee-exp-pct, banks it into every member's cumulative-XP cell.
--enemy-ally0x80051990, after the monster-setup loop (SCUS)0x8007ACA0Rolls a chance and sets the Confuse/Charm bits (0x380 in +0x16E) on the frontmost enemy, so it attacks its own side. One companion edit widens the victory check's mask to 0x384 so a charmed enemy counts as down.
--shiny-serunine detours across battle setup, capture, damage and the menusfour verified-dead regions (below)A capturable enemy spawns with stats ×1.35; once captured, its per-Seru flag grants a permanent +35% damage.

Two design points are worth the read. Flee-EXP's hook is in the battle overlay (reloaded each battle) but its routine is in the executable, so it survives every overlay swap. Enemy ally never adds a fourth combatant - retail is hard-wired to three party slots - it reuses the state a confused party member already has.

"Zero is not dead"

The obvious test for a home - "find a run of zero bytes" - is wrong. A region can be solid zero in the file and still be read at runtime, because it is the unused padding of a table the game indexes by id: ask for an entry past the real end and the reader walks straight into the injected bytes.

A zero run that passes an all-zero check but is still read as table padding looks dead: every byte is zero entry 0 entry 1 entry 2 injected routine live table, indexed by id past the table's real end table[id] for a large id reads the code as data actually dead: read-watch verified injected routine no instruction ever reads it
An all-zero check is satisfied either way. The only safe home is one a read-watchpoint in a live emulator never fires on.

The authoritative test is not "is this in a table I know about?" but "does any instruction read this at runtime?" The final homes are chosen by arming a read-watchpoint on each candidate under PCSX-Redux and exercising the worst cases - an item use, a victory pose, a summon cast - and keeping only the runs nothing touches. The structural table-overlap guard stays as a first filter; the watchpoint is the proof.

The contended resource

Every code-hook feature and two data blobs share one 1028-byte run, and four small verified-dead regions hold the rest. Each tenant guards its slot as all-zero, so they compose - but the space is finite, and that ceiling is why a whole interactive screen goes elsewhere (tier F).

VATenantSizeKind
0x8007AB40Seru Bell name string (--unused-items, the string variant)10 Bdata
0x8007AB80bonus-equipment routine + id table (--equipment-drops)~128 Bcode
0x8007ACA0enemy-ally charm routine (--enemy-ally)~76 Bcode
0x8007AD00run-away-EXP routine (--flee-exp)~237 Bcode
0x8007AE00arena 1: shiny Seru / Super-Art menu / arts-AP hooks (mutually exclusive)256 Bcode
0x8007AF00Seru-trade config blob (--seru-trade, the config variant) - engine-facing, inert on hardware24 Bdata
0x80077728gap 1: shiny-Seru setup, capture, bitmap, banner string256 Bcode + data
0x8007AFF8arena 2: a dead pocket between two sound tables72 Bcode
0x80078A88slot 6: padding between the 0x80078xxx tables68 Bcode

Shiny Seru alone occupies 618 of the 652 bytes across the four verified-dead regions, which is why it is mutually exclusive with the features that need the same arenas.

How we know

ClaimEvidenceReference
Reward tally runs once per battleFUN_8004E568 gates on actor+0x6CE == 0; normal drop granted through FUN_800421D4 at 0x8004F608level-up
Escape teardown is state 0x66Battle-action machine FUN_801E295C; a failed run goes 0x65 → 0x50 insteadbattle-action
Cumulative XP at record +0x5C8Written by the tally, read by the level processor FUN_801E9504save-record
Charm bits 0x380 flip a target's sideAction-machine target selection on actor+0x16E; victory check andi v0,v0,0x4 at 0x801E6638battle-action
Party is hard-wired to three slotsSetup loop FUN_800513F0 bounded < 3; meshes and HUD exist for slots 0-2 onlybattle
The four dead regions are unreadPCSX-Redux read-watchpoints across item use, victory pose and summon castpcsx-redux-automation
Hand-assembled bytes are what the oracle expectsDisc oracles decode the detour + routine + table and check every touched sectorrandomizer
History: the three tables that read the injected bytes

Three earlier homes passed the all-zero check and were still read at runtime as table padding: the victory mouth-override table at 0x80077E80 (a garbled mouth during the victory pose), the battle-overlay move-power table at 0x801F4F5C (six attack ids read code as power and texture values), and the SsAPI sound tables around 0x800794F0 (an item-use sound never reported done, so the tutorial fight froze on a Healing Leaf). The first two were caught by the table-overlap guard; the third table was not yet catalogued. Read-watch verification replaced "is it in a known table" as the acceptance test.

History: the shiny flag in the level byte

Retail keeps a captured Seru as an (id, level) pair with no spare field. An early design hid the shiny flag in the unused high bit of the level byte; one shared routine (FUN_800402F4, spell level-up + display) read the level unmasked and rendered the "grew to level N" box blank. The flag moved to a parallel per-Seru byte array in a reclaimed run of the character record, with a small hook mirroring the game's insert-at-front spell-list shift. A free bit in a live field is free only if you control every reader - and on a sealed disc you usually do not.

Details: the R3000 traps
  • Load-delay slot. A value loaded by lw is not usable in the very next instruction; a replayed displaced pair that depended on that spacing must keep it.
  • Jump alignment. j drops the target's low two bits, so a routine placed at an unaligned zero-run start jumps into the middle of a word. Every routine VA is rounded up to a word boundary; only byte-addressed data may sit unaligned.
  • Capturable set at patch time. No retail field says "this enemy is a Seru", so the shiny allowlist is built from the disc's own monster names - every enemy named after a player Seru-magic - and baked into a bitmap the setup routine indexes by id.

The from-scratch engine pays none of this: there the same feature is a set of (character, spell) pairs and a ×1.35 in the damage kernel.

Details: adding a tier-E mod
  1. Find a hook site at the right control-flow moment; record the two words it displaces.
  2. Choose a home: all-zero, outside every catalogued table, read-watch-verified, word-aligned, and sized for the routine.
  3. Replay the displaced pair inside the routine; guard every write as dead space.
  4. Add a disc-gated oracle. If the routine is screen-sized, it has outgrown the gap - go to tier F.

See also