Vault

Gemfire (SNES) fully decompiled — the 2nd KOEI SNES title, engine to application

The 2nd KOEI SNES title reverse-engineered (Gemfire, 1992/SNES), taken all the way down: the shared bytecode VM, 82 syscalls, the native kernel, ~773 game strings, and all 591 bytecode routines across 11 WRAM overlay modules — every symbol named. Repeating the ROTK2-SNES experiment on a second title did what a second pass is for: it surfaced a systematic error in the first.

Links: KOEI’s portable VM, proven from both sides (the ROTK2-SNES experiment this repeats) · SNES cartridge teardown (method entry) · L’Empereur — the two-tier turn (the NES $E2E3 trampoline this generalizes) · KOEI AI/combat evolution · method → The Rosetta Stone Method. Code: Gemfire-snes-decompiler (title), koei-snes (shared engine), snes-decompiler (CPU substrate).

The architecture, confirmed a third way

Gemfire is the same portable bytecode VM (byte-identical opcode numbers to ROTK2) running WRAM overlay modules over a resident native kernel. What this decomposition added:

The module map — a shared library + mode overlays

root is the resident shared library (146 routines: UI, records, print, dispatch); the overlays are thin mode-specific handlers that compose it. All bytecode (0 kernel-resident) lives in these 11 modules:

Module Routines Role
root 146 resident library — main, call_overlay, print_string, record accessors, draw_*
command 28 field-turn command menu
comusr1 / comusr2 31 / 31 user actions — war+domestic+system / diplomacy+personnel+items
comcmp 40 the strategic AI — assess allegiances → score vs ROM weight tables → dispatch → execute
settei 20 new-game setup (scenario / family / advisor)
event 63 calendar/turn engine + story events + 5 seasonal disasters
senzen / sensou / sengo 31 / 136 / 45 pre-battle deploy / battle engine / post-battle resolution
ending 20 victory cutscene player

Data model (back-inferred by data-walk): lord_table $C331 (stride 36), unit_table $C0B1, castle_table $C039, battle_troops $C795, active_lord_ids $C773; game_state_flag $C771, cur_month $C007; AI allegiance_table $FDAA + decision_slots $FDD0; battle_grid $FF00. ROM: AI weights $8056/dispatch $8066, combat strength $A862, 5×5 range templates $AAD8.

The payoff: the second pass caught the first’s mistake

Comparing the independently-named root against ROTK2-SNES’s, only the low-level primitives matched byte-for-byte (record accessors, do_syscall, num_to_ascii) — root’s application logic is title-specific, exactly the portable-VM thesis. But the compare validated the derivation and exposed a systematic bug in ROTK2’s decompiler: it walked at offset 0, decoding each routine’s 5-byte trampoline as bytecode, so its names were mis-shifted (rec_ptr_F424 is actually a rec_idx; calc32_2459 has no 32-bit ops). Backporting the trampoline profile makes ROTK2 walk 180 routines cleanly — and ROTK2 was then re-walked in full (all 26 code modules, ~1,316 routines, incl. the hex tactical engine + 13 sub-overlays that had never been decompiled; 4 data blobs documented). The offset-0 damage varied severe (comroot/AI: 110 corrections + 67 undiscovered routines) to none — you cannot tell which without re-walking. This is “accumulated state IS the verification layer” made concrete: doing the walk a second time is how the errors surface.

⚠ Correction (2026-09-30): the third pass caught what both earlier passes shared

The Aerobiz (SNES) recon is the third KOEI SNES title. It re-derived the VM’s operand sizes from the handler code itself, reading every handler and executing it on a 65C816 emulator, rather than taking them from the shared toolchain. It found that $D8 is jz a16, with a 2-byte absolute operand, not brz r8.

The second pass above could catch ROTK2’s title-specific error. It could not catch this one, because both titles ran through the same tool, so the error was common-mode. The toolchain is fixed in koei-snes. Re-grading this title’s names is deferred and itemized in the koei-snes re-grade backlog.

The layered source — the deliverable both titles now ship

The symbol tables were the hard part; with them, the readable source falls out in three layers, mirroring how the program is built: assembly (native.asm — the native 65C816 kernel/VM engine), bytecode (<module>.src — per-module labelled listing, call.n/hostcall/immediates resolved by name), and C (<module>.c — the register-machine lifted to expressions). Gemfire ships 11 modules + native.asm; ROTK2 ships 29 + native.asm (its stale offset-0 .src regenerated). One driver — koei-snes/tools/gen_layers.py — produces all of it, reading each module’s ROM location and WRAM base from the ROM’s own $00:F800 resource archive (16-byte [name][bank][addr][len] entries + the module’s JMP base+3 header) rather than any hand-kept table: the manifest is derived from ground truth, so it can’t drift, and the same driver ports to every future KOEI SNES title. (It also re-proved cbmdata1 is data — 0 trampolines in the blob — not the “194 routines” the pre-fix tools imagined.)

The syscall delta (Gemfire vs ROTK2-SNES)

82 syscalls vs 69 — the ~13 additions are almost entirely a battle-animation / window-effect layer (sys_effect_request/_place, scroll-path builders, WBGLOG/WOBJLOG window logic, HDMA windows), matching the archive’s sntanm1-5/sendisp battle-anime blobs. The strategy brain is the shared VM; the additions are facelift — the portable-VM thesis holding at the syscall level too.

Tags

reverse-engineering · snes · koei · game-design