# Mermaids of Atlantis (U) *Imagineering, 1991 - "The Riddle of the Magic Bubble"* ## Status **Pass 3 complete.** The code/data split comes from a Mesen Code Data Logger capture, every routine and branch target carries a name, the zero-page aliases that the dump proves are applied, and `make` rebuilds the whole ROM byte for byte: ``` Byte-identical ROM verified. ``` `prg.s` is 8 075 bytes of disassembled code out of the 64 KB PRG image (12.3 %), in 494 routines. The rest of the image stays as `.byte` blocks with their ROM provenance: the bytes the emulator never reached are not guessed at. ## ROM | Property | Value | |----------|-------| | Format | iNES | | Mapper | 8 (MMC3) | | PRG | 64 KB (8 x 8 KB banks), at file offset $0010 | | CHR | 64 KB (8 x 8 KB banks) | | Mirroring | Vertical | | Trainer | none | | Reset vector | $FF00 | | NMI vector | $808F (see "The two vector tables") | | IRQ vector | $8036 | | ROM file | `Mermaids of Atlantis (U).nes` | | SHA-256 | `53f5d22650063aba89b91762f133d1b6b2680c505b193d88a472580d09701122` | ## The banking The cartridge is an MMC3 with 64 KB of PRG - eight 8 KB banks. The file is laid out in banks, and the game uses them like this: | Bank | File | CPU window | Role | |------|------|------------|------| | R0 | $0000-$1FFF | $8000-$9FFF | the game code, first half | | R1 | $2000-$3FFF | $A000-$BFFF | the game code, first half | | R2 | $4000-$5FFF | $C000-$DFFF | the game code, first half | | R3 | $6000-$7FFF | $E000-$FFFF | **the fixed bank**: the reset stub, the vectors, the text table | | R4 | $8000-$9FFF | $8000-$9FFF | the game code, second half | | R5 | $A000-$BFFF | $A000-$BFFF | the game code, second half | | R6 | $C000-$DFFF | $C000-$DFFF | unused | | R7 | $E000-$FFFF | $E000-$FFFF | a second copy of the fixed bank's tail | The mapper register is written through the fixed bank: `SwitchPrgBank` ($FF70) does `ora #$30; sta $4100`, so the mode bits are forced on and the bank number is the low three bits of A. `BankedCall` ($8004) is the game's own far-call: it stores the handler index in `HandlerIndex` ($2D), switches the PRG bank to the one in `PrgBankNumber` ($2C) and lets the caller fall into the new bank's code at the same CPU address. Because two banks can be visible at the same CPU address, the disassembly gives each bank its own label namespace: bank 0, 1, 2 and the fixed bank use plain names, banks 4 to 7 use the `Bank4_` .. `Bank7_` prefixes. Every bank has its own segment (`PRG0` .. `PRG7`) and its own `.org`, so the assembler knows the CPU address of every label while the linker still writes the bytes to the bank's file offset. ## The two vector tables The image carries the reset stub and the vectors **twice**: | File | NMI | RESET | IRQ | |------|-----|-------|-----| | $7FFA (R3, the fixed bank) | $808F | $FF00 | $8036 | | $FFFA (R7, the last bank) | $8189 | $FF00 | $8036 | The fixed bank is the one the CPU sees, so the live NMI vector is **$808F** and the NMI handler is `NmiHandler` at $808F in bank 0. The copy at the end of the image is what a mapper that fixes the *last* 8 KB of the PRG would use; it names an NMI handler at $8189, which is a `BRK` in the first half of the image. The two stubs are byte identical from $FF00 to $FF7F, and the fixed bank carries one routine the last bank does not: `SwitchPrgBankKeepA` at $FF80. ## Power-on flow ``` $FF00 Reset ldx #$FF / txs / sei / cld clears PPUCTRL and PPUMASK SwitchPrgBank ($30) - the first PRG bank WaitForBankSwitch - the bank switch counter $FF2F SelectChrBanks picks the CHR banks of the first stage $FF3E StartGame SwitchPrgBank ($30), StartBankSwitchDelay, jmp $8036 $8036 InitializeGame sei / cld / WaitForVBlank clears the PPU and the work RAM InitGameVariables - the sound, scroll and sprite registers $8083 IdleWaitLoop inc IdleCounter / jmp $8083 - the game waits for the NMI ``` From then on the frame handler does the work: ``` $808F NmiHandler StartVramWrite when the tile set changed UpdateNametable / UploadOam when the screen changed writes PPUCTRL, PPUSCROLL and PPUMASK ReadControllers - the pad bits UpdateGameState - the mode machine UpdateActorSprites / UpdateSprites BankedCall ($02) - the sound engine writes PPUCTRL and counts the frame ``` `$8036` is also the IRQ vector, so an interrupt re-runs the power-on path. ## Data tables | Table | Where | What it holds | |-------|-------|---------------| | the digit and tile table | $F1D6, in the fixed bank | "0123456789" and the tile indices of the font | | the bank switch table | $8026, in banks 0 and 4 | the bank numbers, eight per half | | the handler jump tables | $8095 (bank 4), $9234, $C2AC | the addresses the dispatchers jump to | | the sprite table | $0338 | the 16 sprite slots: tile, attributes, position | | the actor table | $0358, $03A8 | the actors and the bubble slots | | the player table | $0400 | the player's position and direction | | the level script | $05E2-$05E9 | the stage, the wave and the timers | | the sound tables | $0700-$07FF | the sound engine of the second half | ## Files | File | Purpose | |------|---------| | `prg.s` | the disassembly, one segment per bank | | `header.s` | the iNES header | | `chr.s` | the CHR image | | `prg.bin` | the 64 KB PRG image | | `chr.bin` | the 64 KB CHR image | | `rom.nes` | a copy of the original ROM | | `linker.cfg` | one memory area per bank, at the file offsets | | `symbols.inc` | the NES hardware register names | | `locals.inc` | the zero-page aliases | | `routinemap.tsv` | the hand-written Pass 2 names | | `routinedesc.tsv` | the hand-written routine descriptions | | `labelmap.tsv` | the full Pass 2 map (generated) | | `regen.sh` | rebuilds `prg.s` from the ROM and the CDL capture | | `Makefile` | the build and the byte-identity check | ## Rebuilding ``` make # build and verify against rom.nes ./regen.sh # re-derive prg.s from the ROM and a Mesen CDL capture ``` `regen.sh` needs a Mesen CDL capture of the game; the file is not kept in the repository. Take it with Mesen (load the ROM, play for a while, then `mesen_cdl_file(action='save', memoryType='NesPrgRom', filepath=...)`) and point `CDL=` at it.