Kajar notes of ChronoRET, rebuilding Chrono Trigger one matched routine at a time

31 July 2026: MainInit, the hardware setup

MainInit at $FD:C000 is matched: the 215 bytes the SNES runs right after the reset stub to put the hardware into a known state. The project now stands at 471 matched bytes.

What MainInit does

Out of reset the hardware is in a loosely defined state. MainInit puts everything into a safe, known setup before the game runs:

  1. CPU mode. Sets A to 8-bit (SEP #$20) and X/Y to 16-bit (REP #$10), puts the stack pointer at $06FF (about 1.75 KB of stack below the NMI handler area), zeroes the data bank (PLB) and puts the Direct Page at $4200, the CPU register area.
  2. CPU registers. Turns on FastROM (MEMSEL = $01, which raises ROM access speed from 2.68 MHz to 3.58 MHz). Turns off NMI, IRQ and DMA. Zeroes the multiply and divide registers.
  3. PPU setup. Moves the Direct Page to $2100, the PPU register area, so every PPU write can use a short 2-byte direct-page instruction instead of a 3-byte absolute one. Then the full PPU init: forced blank on; sprites set up (8×8 and 16×16 sizes, base tile 0 with a gap of 3 pages); Mode 1 backgrounds; all scroll registers zeroed; VRAM mode; Mode 7 identity matrix; window registers; all background and sprite layers enabled; color data register; display size.
  4. Jump to GameLoop. The last instruction is JML $C0:000E, a 24-bit jump to GameLoop in bank $C0.

Four boxes from top to bottom: the RESET vector, the reset stub at $00:FF00, MainInit at $FD:C000 and GameLoop at $C0:000E

Power-on to GameLoop: the RESET vector, the 7-byte reset stub, the 215 bytes of MainInit, and the long jump to bank $C0.

Display Mode 1

BGMODE is set to $09: Mode 1 plus BG3 high priority. Mode 1 gives three tiled backgrounds. Giving BG3 high priority is the usual Mode 1 trick for drawing text and UI on top of everything else, sprites included.

Matching with direct-page writes

Bank $00 used absolute register addresses ($2100, $4200 and so on). MainInit doesn't. It sets the Direct Page to $2100 and then uses short direct-page addresses, so STA $00 with DP=$2100 writes to $2100 = INIDISP. That is smaller (2 bytes instead of 3), and it is why the original PPU init is so compact.

Diagram: the direct page register plus a one-byte operand gives the hardware register address

With the Direct Page pointed at a register block, a 2-byte store reaches the hardware: DP $4200 plus $0D is MEMSEL, DP $2100 plus $00 is INIDISP. The bytes are the ones in MainInit.

For matching, that means using the direct-page offsets ($00, $01, $02...) instead of the named constants from hardware.inc. The register names go in the comments.

An include guard for hardware.inc

Adding bankFD.asm to main.asm produced a "label redefined" error. Both bank00.asm and bankFD.asm include hardware.inc, so asar saw every register name twice.

The fix is an include guard, the same idea as #pragma once in C:

65C816 Assembly
!hardware_inc ?= 0
if !hardware_inc == 0
!hardware_inc = 1
; ... all the register definitions ...
endif

?= sets the variable only if it isn't set yet. After the first include !hardware_inc = 1, and later includes skip the whole block.

Result

bankFD.asm matched byte-exact on the first try once the direct-page addressing was right. make diff reports 4,194,304 bytes identical. Total matched: 256 (bank $00) + 215 (bank $FD) = 471 bytes.

Next

The last instruction of MainInit, JML $C0:000E, points at GameLoop. That is the next target.

← Back to devlog