The technique in one box
The game's menus are overlays - code paged into RAM per mode. The shop menu's overlay contains a ~3.8 KB run of bytes that no instruction or pointer references. A new screen parked there is present exactly when a shop is open, reloads with the overlay, costs nothing from the executable's contended gap, and composes with every tier-E feature. The rest is reversing the shop picker well enough to give it a fourth row.

At a glance

Host
Menu overlay, PROT 0899, load base 0x801CE818; dead region 0x801E74E0–0x801E83E0
Picker edits
Row clamp 3→4, box height 0x26→0x34, a label-draw detour, a confirm-dispatch detour, a per-frame entry detour
The swap
Character record Seru id at +0x13D, level at +0x161; records at 0x80084708, stride 0x414; count never changes
Offers
64-bucket schedule, a pure function of the seed; kernel legaia_asset::seru_trade shared with the from-scratch engine
Edit class
In place, same size, entirely inside 0899 plus a 24-byte engine-facing config blob
Confidence
Confirmed - hardware-confirmed render → slide → cursor → confirm → swap
Oracle
seru_trade_real.rs

Where the shop menu lives

Almost no gameplay code sits in the executable. Menus, battle, field scripts and minigames are overlays paged into the 0x801C0000+ window, and several overlays share the same address range at different times: the field overlay (0897) and the menu overlay (0899) both load at 0x801CE818, so one address is a string in one and a stat aggregator in the other. The static overlay pipeline separates them structurally and verifies 0899's disc bytes against live RAM, so the on-disc entry can be reasoned about as the loaded code.

Three facts about the picker

The Buy / Sell / Quit chooser is what gets extended, so it is what gets reversed. A fourth row needs exactly three load-bearing facts, each read from the overlay's disassembly at its recovered base and checked against the live menu.

FactWhereEdit
The row count is a literal immediateaddiu a1, zero, 3 at 0x801DB0983 → 4, so the cursor can reach a fourth line
The window is a sprite definition with a height byte+0xA of the def, at 0x801E49E20x26 → 0x34 (one row of 14 px), or the new row draws outside its box
Draw order is text first, box lastrenderer FUN_801D4868the fourth label must be drawn before the box or it vanishes behind it

The dead region

The Seru-trade screen inside PROT 0899's reference-free dead region menu overlay, PROT 0899, as loaded live code + data (picker, renderer, tables) dead region - referenced by nothing 0x801CE818 0x801E74E0 0x801E83E0 trade handler 3 stubs strings offer table a compile-time assertion proves the pieces never overlap and end below the region ceiling
Nothing lands in the executable's rodata gap, so the vendor composes with every gap-based feature.
VAContents
0x801E74E0trade-screen handler: render, input, slide, swap
0x801E7B00entry-detour stub - per-frame gate on the active flag
0x801E7B60dispatch-detour stub - routes the Trade row to its sub-mode
0x801E7C20row-4 label-draw stub (text first, per the native order)
0x801E7D20"Trade" / "SERU TRADE" / "Trade?" / "Yes" / "No"
0x801E7D60seed-derived offer schedule: 64 buckets × 3 bytes
0x801E7E20trade-active flag

Splicing it in

1
Four rows
Clamp 3→4, box height 0x26→0x34, one detour in the renderer body to the row-4 label stub.
picker
2
Route Trade
A detour at the confirm dispatch (0x801DB0C8) sends row 2 into the trade sub-mode and sets the active flag.
dispatch stub
3
Enter each frame
The entry detour (0x801DAFD4) checks the flag and jumps to the handler - a sub-mode of the shop, no scene warp, no overlay reload.
entry stub
4
Run the screen
Re-poll the pad, slide the window in over 10 frames, move a line cursor over one row per owner of the wanted Seru, draw with the game's own text / number / box / cursor primitives. Cross confirms via a real "Trade?" Yes/No; Circle backs out.
handler
5
Swap
Rewrite the owner's spell list in place: id at +0x13D, level at +0x161. One-for-one, never a duplicate.
handler

Every piece is written into 0899 through patch_prot_entry(899, …), each target guarded as all-zero dead space, so an already-patched or unexpected disc is rejected rather than corrupted.

Deterministic offers

  • Each of 64 buckets holds one (want, give, give_level) triple; the level is rolled on a curve - 1-3 common (70%), 4-6 rare (25%), 7-9 very rare (5%) - so a high-level Seru is a jackpot.
  • The bucket is (play_time / period + vendor_offset) & 63. The play counter advances about once per frame, so the period is frame-denominated (~9 minutes) and the full cycle takes ~9.6 hours.
  • vendor_offset is derived from the armed shop record (stock count + ids + name bytes, mod 64), so every merchant shows its own offer while sharing one on-disc schedule.
  • The bucket expands to one line per member who owns the wanted Seru, excluding anyone who already owns the give-back. Ids are the player Seru-magic block 0x81..=0x95.

The same kernel feeds the from-scratch engine through a 24-byte SeruTradeConfig blob in the executable (the config variant) - the bridge from the disc patcher to the reimplementation, and inert on real hardware.

How we know

ClaimEvidenceReference
0899 is the menu overlay at 0x801CE818Static jal base recovery; RAM byte-match of a 0x15E8C-byte clean prefix across six menu-open save statesstatic-overlay-pipeline
Picker clamp, box height, draw orderRenderer FUN_801D4868 and its dispatcher FUN_801DAFD4, disassembled at the recovered baseshop
The dead region is unreferencedAddress-reference scan over the based image (no literal, lui/addiu, jal, j or branch reaches it); all-zero in live RAMaddress-reference-scan
Spell list layout +0x13C / +0x13D / +0x161Typed save-record accessors; the engine's apply_trade mirrors the same offsetssave-record
Play counter at 0x80084570 ticks per frameRuntime capturememory-map
The screen works end to endHardware playtest; disc oracle decodes the embedded schedule back to the kernel's offers and checks the rodata gap is untouchedseru_trade_real.rs
Details: the handler's primitives

The handler uses only the game's own routines: pad poll FUN_8001822C, text FUN_80036888, number formatter FUN_80034B78, window box FUN_8002C69C, animated cursor FUN_8002B994. The slide is a signed offset stepping 0x18 px per frame from -0xF0 to 0 - exactly ten frames. The screen names both sides of the offer in its header and shows a "No <want> available" line when nobody qualifies.

Details: roadmap features on this rung
  • Enemy battle assist - a defeated enemy joins as a non-controllable ally actor.
  • Seru racing - a race state machine, odds and betting hosted in a minigame overlay's dead space.
  • Muscle Dome spectate + betting - watch two NPCs fight and wager beforehand.
  • Blood Moon - a periodic overworld event (red tint, stronger enemies, better loot).

Adding a tier-F mod: find a reference-free dead region in the resident host overlay (all-zero in live RAM), keep routine + data under the ceiling with a compile-time disjointness assertion, guard every write as dead space, add a disc-gated oracle.

See also