# 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. The 76 data blocks the tracer left behind are now accounted for one by one in **Pass 3 data-table inventory** below: every block that an instruction reads carries a name, a structure and the address of the instruction that reads it; every block that nothing reads is recorded as unreferenced data or as code the tracer could not reach, with the evidence. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. ## 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 | The ROM-resident tables are inventoried, with the reading instruction for each, in **Pass 3 data-table inventory** below. ## Pass 3 data-table inventory Every block the tracer left as `.byte` data is listed here, once. `Size` is the byte count of the block as the tracer left it; where a block holds more than one thing, the named region inside it is given with its own extent in the `Name` column. "Read by" gives the address of the instruction that reads the bytes; a block nothing reads says so and says why. Banks are listed separately, because banks 0/4, 1/5, 2/6 and 3/7 are visible in the same CPU window. The PRG offset in the audit comment is the offset inside `prg.bin` (bank 0 starts at $0000, bank 4 at $8000 and so on). ### Bank 0 - PRG0, file $0000-$1FFF, CPU $8000-$9FFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $8000-$8003 | 4 | - | unreached code: `nop / nop / stx $2C`, the head of the banked-call routine whose body the tracer reached at $8004 | not referenced by any instruction | | $8017-$801A | 4 | - | unreached code: `sta $2C / ora #$00`, the tail of a second bank-switch entry | not referenced by any instruction | | $8026-$8035 | 16 | `PrgBankNumberTable` | eight MMC3 PRG bank numbers ($00-$07), then the same eight with bit 4 set ($10-$17) | `lda $8026,x` at $801F (`SelectPrgBankByIndex`); the `sta $8026,x` at $8022 writes to ROM and is ignored | | $8088-$808E | 7 | - | unreached code | not referenced by any instruction | | $80C9-$811B | 83 | - | unreached code: decodes as 35 consecutive instructions | not referenced by any instruction | | $812A-$813E | 21 | - | unreached code: decodes as 9 consecutive instructions | not referenced by any instruction | | $8196-$81B0 | 27 | - | unreached code: decodes as 15 consecutive instructions | not referenced by any instruction | | $82D5-$82E8 | 20 | - | unreached code: decodes as 12 consecutive instructions | not referenced by any instruction | | $83D8-$840A | 51 | - | unreached code: decodes as 26 consecutive instructions | not referenced by any instruction | | $8452-$848B | 58 | - | unreached code: decodes as 27 consecutive instructions | not referenced by any instruction | | $854A-$8551 | 8 | `TileAttributeBitMaskTable` | four 2-bit-pair clear masks ($FC,$F3,$CF,$3F) then four set masks ($03,$0C,$30,$C0); the code applies them to the nametable byte at $04E2,Y | `ora $854E,x` at $8521 and `and $854A,x` at $8537 (`Sub_LoadZp19` / `WriteVramTiles_Loop3`) | | $85EC-$8605 | 26 | - | unreached code | not referenced by any instruction | | $8629-$8662 | 58 | - | unreached code: decodes as 25 consecutive instructions | not referenced by any instruction | | $8670-$8675 | 6 | - | unreached code | not referenced by any instruction | | $874F-$89FB | 685 | `ButtonBitMaskTable` ($876E-$8779) and `BankedHandlerPointerTable` ($89F4-$89FB); the rest is unreached code | masks: 12 controller-button bit masks ($08,$08,$04,$04,$02,$02,$01,$01,$80,$80,$40,$40). pointers: 4 x 16-bit handler addresses into bank 1 ($A70F,$A72F,$A74F,$A76F) | `cmp $876E,x` at $86FC; `lda $89F4,x` at $8A20 and `lda $89F5,x` at $8A26 | | $8A36-$8A5E | 41 | - | unreached code: decodes as 14 consecutive instructions | not referenced by any instruction | | $8AAA-$8AE0 | 55 | - | unreached code: decodes as 27 consecutive instructions | not referenced by any instruction | | $8B0F-$8E33 | 805 | - | unreached code: decodes as 296 consecutive instructions | not referenced by any instruction | | $8E42-$9083 | 578 | - | unreached code: decodes as 243 consecutive instructions | not referenced by any instruction | | $9090-$90A8 | 25 | `StagePrgBankNumberTable` ($9090-$9097); the rest is unreached code | eight MMC3 PRG bank numbers ($00-$07) | `lda $9090,x` at $9087 | | $90F7-$90FB | 5 | `DirectionAutoRepeatDelayTable` | five frame thresholds (1,3,2,4,2) for the per-direction hold counters at $6F | `cmp $90F7,x` at $90E3 | | $9234-$9620 | 1005 | `GameModeParameterTable` ($9234-$9239), `MenuTextPointerTable` ($923A-$9253), `MenuTextStrings` ($9254-$9299), `StageSelectPointerTable` ($929A-$92B3), `HighScoreNameTable` ($9403-$9484); the rest is sprite/animation data and unreached code | parameters: 6 small mode bytes ($08,$01,$07,$01,$05,$05). pointers: 13 x 16-bit each, into the string block. strings: $FF-terminated ASCII - "1PLAYER", "2PLAYERS", "1234EASY", "HARD", "STACKED", "123456789", "TUNE1", "TUNE2", "TUNE3", "START". high scores: 3-letter name plus score as ASCII, then "ABCDEFGHIJKLMNOPQRSTUVWXYZ@" | `lda $9234,x` at $91B6; `lda $923A,x` at $9102 and `lda $923B,x` at $9107; `lda $929A,x` at $910C and `lda $929B,x` at $9113; `lda $923E,x` at $911E and `lda $923F,x` at $9123; `lda $929E,x` at $9128 and `lda $929F,x` at $912F; `lda $9246,x` at $913A and `lda $9247,x` at $913F; `lda $92A6,x` at $9144 and `lda $92A7,x` at $914B; `lda $924C,x` at $9154 and `lda $924D,x` at $9159; `lda $92B2,x` at $9170 and `lda $92B3,x` at $9176; `lda $924E,x` at $9193 and `lda $924F,x` at $9198; `lda $92AC,x` at $919D and `lda $92AD,x` at $91A4; `lda $9403,x` at $8E36 (copied 130 bytes to $0100) | | $9674-$967B | 8 | `InitialOamSpriteTemplate` | two 4-byte OAM entries (Y, tile, attribute, X) copied into the OAM shadow at $0204 | `lda $9674,x` at $9650 | | $96DC-$96DE | 3 | - | unreached code | not referenced by any instruction | | $96F3-$9702 | 16 | - | unreached code | not referenced by any instruction | | $971B-$9785 | 107 | - | unreached code: decodes as 49 consecutive instructions | not referenced by any instruction | | $97E4-$97EB | 8 | - | unreached code | not referenced by any instruction | | $980E-$9819 | 12 | - | unreached code | not referenced by any instruction | | $9842-$9FFF | 1982 | - | unreached code: decodes as 234 consecutive instructions | not referenced by any instruction | ### Bank 1 - PRG1, file $2000-$3FFF, CPU $A000-$BFFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $A000-$AEC6 | 3783 | `SpriteDefinitionRecordTable` ($A000-$AE8F); $AE90-$AEC6 is unreached code | $FF-terminated lists of small values. The record bytes are tile indices and sprite-attribute values (all in $01-$7A), but they do not decode as readable English the way the plain-ASCII tables elsewhere in the image do ("0123456789" at $F1D6, the high-score names at $9403, "FRICKw" at $A640), so the exact field order is not established from this dump | not referenced by any instruction the tracer reached | | $AED6-$BB4E | 3193 | - | unreached code | not referenced by any instruction | | $BBC2-$BBF9 | 56 | `SpriteTemplatePointerTable` ($BBC2-$BBC5) and `ActorTemplatePointerTable` ($BBDE-$BBE1); the rest is sprite-template byte lists | pointers: 2 x 16-bit each - $BBC4/$BBC8 and $BBE0/$BBE4. The templates they point at start with the bytes $24 $23 and hold tile/attribute values | `lda $BBC2,x` at $BB94 and `lda $BBC3,x` at $BB99; `lda $BBDE,x` at $BB87 and `lda $BBDF,x` at $BB8C | | $BE05-$BEE3 | 223 | `PlayerActionScriptTable` | 111 two-byte records {compare value, controller-button mask} plus one trailing byte; the mask byte is one of $01,$02,$40,$80 and the compare value runs $01-$43 | `lda $BE05,x` at $BC24 and `lda $BE06,x` at $BC2B (`HandlePlayerInput`) | | $BF9F-$BFAE | 16 | `StageDataPointerTable` | 8 x 16-bit pointers into bank 2: $BFAF, $BFBA, $BFD6, $C00F, $C070, $C079, $C09E, $C0B9 | not referenced by any instruction | ### Bank 2 - PRG2, file $4000-$5FFF, CPU $C000-$DFFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $C046-$C060 | 27 | `SpriteTileTable` ($C046-$C04D); $C04E-$C060 is unreached code | eight tile indices, one per OAM slot ($A9,$21,$11,$F8,$D9,$23,$1B,$F8) | `lda $C046,y` at $C019 | | $C070-$C0F1 | 130 | - | unreached code | not referenced by any instruction | | $C137-$C13F | 9 | `AnimationPhaseTable` ($C137-$C13E) | eight animation-phase values in $00-$04 | `lda $C137,y` at $C100 | | $C16F-$C2A6 | 312 | - | unreached code | not referenced by any instruction | | $C2AC-$C2BE | 19 | `ActorScriptPointerTable` ($C2AC-$C2BD) | 9 x 16-bit pointers into bank 2: $C2BF, $C3C8, $C456, $C469, $C49D, $C4E7, $C505, $C724, $C77E. The last byte, $C2BE, is the PRG bank number ($01) | the table: not referenced by any instruction. $C2BE: `lda $C2BE,x` at $C2C9 and `sta $C2BE,x` at $C2CC, the value going to `PrgBankNumber` at $C2CF | | $C339-$C33D | 5 | - | unreached code | not referenced by any instruction | | $C40D-$C413 | 7 | - | unreached code | not referenced by any instruction | | $C420-$C455 | 54 | - | unreached code: decodes as 24 consecutive instructions | not referenced by any instruction | | $C469-$C504 | 156 | - | unreached code: decodes as 26 consecutive instructions | not referenced by any instruction | | $C540-$C543 | 4 | - | unreached code | not referenced by any instruction | | $C5A2-$C5D1 | 48 | `SpriteTileTablePlayer1` ($C5BA-$C5BD), `SpriteTileTablePlayer2` ($C5BE-$C5C5) and `SpriteTileTableAlt` ($C5CA-$C5D1); $C5A2-$C5B9 is unreached code | 4, 8 and 8 tile indices for the player sprites | `lda $C5BA,y` at $C519 and `lda $C5BA,y` at $C54A; the copy routine at $C5A2 reads $C5CA | | $C5FE-$C629 | 44 | - | unreached code | not referenced by any instruction | | $C697-$C6B6 | 32 | `NametableRowTilePattern` ($C697-$C6B4) | 30 tile indices written to VRAM as one nametable row: $FF, $24, then $21/$22 alternating, then $10,$11 | `lda $C697,y` at $C357; `lda $C6B5,x` at $C63D reads the last two | | $C6D0-$C7E9 | 282 | `StageScriptPointerTable` ($C6D0-$C6D7); the rest is unreached inline code the game jumps into | 4 x 16-bit pointers: $F837 and $F81B into the fixed bank, $05C6 and $05CB into RAM | `lda $C6D0` at $C3D0 and `lda $C6D1` at $C3D5; `lda $C6D2` at $C3ED and `lda $C6D3` at $C3F2; `lda $C6D4` at $C5F1 and `lda $C6D5` at $C5F6 | | $C873-$CBC3 | 849 | - | unreached code | not referenced by any instruction | | $CBFD-$CC10 | 20 | - | unreached code | not referenced by any instruction | | $CC50-$CC55 | 6 | `PlayerStatePointerTable` | 3 x 16-bit pointers: $CE40, $CE2B, $CE2B | `lda $CC50,x` at $CC1D, `lda $CC51,x` at $CC22 and `lda $CC52,x` at $CC27 | | $CFA8-$CFFC | 85 | `VramAddressTable` ($CFC5-$CFFC); $CFA8-$CFC4 is unreached code | 28 x 16-bit VRAM addresses ($22xx, $23xx and $2450) on a 4-byte stride | `lda $CFC5,y` at $CE9A and `lda $CFC5,y` at $CEA0; `lda $CFC5,x` at $CEE2 and `lda $CFC5,x` at $CEE8 | | $D040-$D041 | 2 | - | unreached code | not referenced by any instruction | | $D1A8-$D1AF | 8 | `ActorFrameCountTable` | eight frame counts (0,3,0,3,0,1,2,0) | `lda $D1A8,x` at $D150 | | $D40A-$D45D | 84 | - | unreached inline code: the game enters it at a computed offset, so it is code and not a table | `lda $D412,y` at $CE66 and $D3DA; `lda $D40A,y` at $D3AE; `lda $D40E,y` at $D3C4, $D56F; `lda $D40B,y` at $D3B4; `lda $D40F,y` at $D3CA, $D575 | | $D486-$D4ED | 104 | - | unreached code | not referenced by any instruction | | $D4F3-$D506 | 20 | `StageScriptPointerTableAlt` | 10 x 16-bit pointers into bank 2: $D507, $D2C0, $D615, $D0E7, $D537, $D416, $D5CF, $D1B0, $D21A, $D563 | not referenced by any instruction | | $D627-$DFFF | 2521 | - | $FF padding: the rest of bank 2 is never written | not referenced by any instruction | ### Bank 3 (the fixed bank) - PRG3, file $6000-$7FFF, CPU $E000-$FFFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $E000-$FEFF | 7936 | `DigitToTileTable` ($F1D6-$F1DF), `SpritePriorityTable` ($F206-$F215), `FixedActorScriptPointerTable` ($F216-$F233), `ActorSpriteAddressTable` ($F236-$F249), `StageLayoutTable` ($F260-$F27F), `FixedStageScriptPointerTable` ($F280-$F2A7), `FixedStageSelectPointerTable` ($F2FB-$F300), `ActorAnimationTable` ($F380-$F38F), `LevelScriptPointerTable` ($F390-$F39B), `SpriteTileAddressTable` ($F45C-$F46B), `LevelScriptPointerTableAlt` ($F6A7-$F6AA), `LevelScriptPointerTableAlt2` ($F72B-$F73A); $E000-$F1D5 is $FF padding and the rest is level and script data | digits: "0123456789". priorities: 10 sprite priority/attribute bytes. script pointers: 15 x 16-bit, into the script blocks at $F256-$F691. sprite addresses: 13 x 16-bit, mostly $F800. layout: 32 tile indices. stage script pointers: 20 x 16-bit. stage select pointers: 3 x 16-bit ($F2A8,$F301,$F307). animation: 16 counters. level script pointers: 6 x 16-bit ($F39C,$F3BC,$F3DC,$F3FC,$F41C,$F43C). tile addresses: 8 x 16-bit. level script pointers: 2 x 16-bit ($F6AB,$F6EB) and 8 x 16-bit ($F2044,$F2084,$F20C4,$F2104,$F2144,$F2184,$F21C4,$F2204) | `lda $F1D6,x` at $C66B; `lda $F1D6,y` at $CE55, $D378, $D382, $D39D; `lda $F1D7,y` at $C5DE, $C5EB; `lda $F206,y` at $8565; `lda $F216,y` at $8445 and $857D; `lda $F217,y` at $844B and $8583; `lda $F236,y` at $856E; `lda $F237,y` at $8574; `lda $F2FB,x` at $D161; `lda $F2FC,x` at $D166; `lda $F381,x` at $CDA8; `lda $F45C,x` at $C2E0, $C2EE, $C55F, $C56D; `lda $F45D,x` at $C2E5, $C2F3, $C564, $C572; `lda $F6A7,x` at $C64B; `lda $F6A8,x` at $C650; `lda $F72B,x` at $84C3; `lda $F72C,x` at $84C8. The tables that nothing reads are reached through the pointer tables beside them | | $FF68-$FF6F | 8 | - | unreached code | not referenced by any instruction | | $FF76-$FF7F | 10 | - | $FF padding | not referenced by any instruction | | $FF89-$FFFF | 119 | - | $FF padding; the last six bytes are the CPU vectors - NMI $808F, RESET $FF00, IRQ $8036 | read by the CPU's reset logic, not by an instruction | ### Bank 4 - PRG4, file $8000-$9FFF, CPU $8000-$9FFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $8000-$800C | 13 | - | unreached code: the head of the banked-call routine (`nop / nop / stx $2C / sta $2D / ...`) | not referenced by any instruction | | $8017-$801A | 4 | - | unreached code: `sta $2C / ora #$08`, the tail of a bank-switch entry | not referenced by any instruction | | $8026-$8087 | 98 | - | the bank's own copy of the PRG bank numbers at $8026-$8035 (the same $00-$07 / $10-$17 table as bank 0), then unreached code | `lda $8026,x` at $801F; `sta $8026,x` at $8022 | | $8095-$80A6 | 18 | `Bank4_BankedHandlerJumpTable` | 9 x 16-bit handler addresses inside bank 4 ($80A6,$8130,$A783,$A719,$AAF0,$A725,$8117,$AB4F,$8160) | `lda $8096,y` at $808C and `lda $8095,y` at $8090 (`Bank4_Sub_8088`) | | $8131-$8160 | 48 | - | unreached code | not referenced by any instruction | | $8189-$9FFF | 7799 | `Bank4_NametableSourcePointerTable` ($839A-$83BF) and `Bank4_NametableDestinationTable` ($83C0-$83CF); the rest is unreached code and data | source: 19 x 16-bit pointers. destination: 8 x 16-bit addresses read at a byte offset | `lda $839A,x` at $80AC and `lda $839B,x` at $80B1; `lda $83C0,x` at $80BB and `lda $83C1,x` at $80C0 | ### Bank 5 - PRG5, file $A000-$BFFF, CPU $A000-$BFFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $A000-$A719 | 1818 | `Bank5_SoundNotePeriodTable` ($A650-$A6BF); the rest is unreached sound code | 56 x 16-bit APU pulse periods, falling from $06B0 to $0038 - one octave and a half of notes | `lda $A650,x` at $AA17 and `lda $A650,x` at $AA1E, the value going to `APUPULSE1TIMERLO`/`TIMERHI` | | $A740-$A75A | 27 | - | unreached code | not referenced by any instruction | | $AA75-$AA79 | 5 | - | unreached code | not referenced by any instruction | | $AAA9-$AAF0 | 72 | `Bank5_ActorBitMaskTable` ($AAEC-$AAF0); the rest is unreached code | five single-bit masks ($01,$02,$04,$08 and the byte after) | `lda $AAEC,x` at $AB16 | | $AB5B-$BFFF | 5285 | `Bank5_StageHandlerPointerTable` ($AB5B-$AB7A); the rest is unreached code and data | 16 x 16-bit handler pointers into bank 5 ($B065,$B332,$BB4C,$AB9D,$B7AC,$AEE5,$B952,$BAA6,$C056,$AB79,$AB7F,$AB85,$AB8B,$AB91,$AB97,$4401) | `lda $AB5B,x` at $A72E and `lda $AB5C,x` at $A733; `lda $AB6D,x` at $AB07 and `lda $AB6E,x` at $AB0C | ### Bank 6 - PRG6, file $C000-$DFFF, CPU $C000-$DFFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $C000-$DFFF | 8192 | - | 61 bytes of tile/record data at $C000-$C03C (repeating two-byte groups $04,$19 / $04,$20 / $04,$25 / $04,$1E), then $FF padding for the rest of the bank | not referenced by any instruction | ### Bank 7 - PRG7, file $E000-$FFFF, CPU $E000-$FFFF | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $E000-$FF6F | 8048 | - | $FF padding apart from the bank-switch stub at $FF60-$FF6F | not referenced by any instruction | | $FF76-$FFFF | 138 | - | $FF padding; the last six bytes are a second copy of the vectors (NMI $8189, RESET $FF00, IRQ $8036) for a mapper that fixes the last bank instead | read by the CPU, not by an instruction | ### What the inventory settled * **The PRG bank number table** ($8026, banks 0 and 4) is one table, not two: eight MMC3 bank numbers followed by the same eight with bit 4 set. The `sta $8026,x` next to the `lda` writes to ROM and is ignored by the hardware, so the load is the whole point of the pair. * **The fixed bank holds the game's tables**, not the code banks: the digit table, the actor script pointers, the level layout and the sound tables all live in the bank that is always visible, which is why the code in banks 0, 2 and 4 can reach them with a plain absolute operand. * **The sprite tile tables are tiny and separate**: eight tiles for the player sprite, four for the second player, eight for the alternate set, and eight for the HUD. The code copies them into the OAM shadow at $0338. * **Two of the tables are read at a byte offset rather than an index**: the bank-4 destination table ($83C0) and the fixed bank's sprite-address table ($F45C) are byte arrays whose 16-bit values are picked up one byte at a time. * **Bank 5 carries the sound engine's note periods**, and the table is a plain 56-entry 16-bit pitch table - the same shape MUSCLE uses. * **The block at $A000 in bank 1 is not text.** Its bytes fall in the ASCII range and end with $FF, but they do not spell English the way "0123456789", "TOM1000" and "FRICKw" do elsewhere in the same image, so the record layout is recorded as unresolved rather than guessed at. ### Still open * The routine that consumes `SpriteDefinitionRecordTable` ($A000 in bank 1) was never reached, so the field order of those records is still unknown. * `StageDataPointerTable` ($BF9F), `ActorScriptPointerTable` ($C2AC) and `StageScriptPointerTableAlt` ($D4F3) are pointer tables that no traced instruction reads; the pointers are valid bank addresses, but which routine walks them is not established from this dump. * The 57 blocks listed above as "unreached code" decode cleanly as 6502 instructions. They are code the tracer could not reach, not data; the CDL capture the split came from did not observe them either. ## 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. --- ## Entry-point naming Every `jsr`/`jmp` target in this project now carries a semantic name; **none** is left in the address-derived `Sub_XXXX` form. The twelve that were still address-derived are named here: | Address | Name | What it does | |---------|------|--------------| | `$801B` (bank 4) | `Bank4_SelectPrgBankByIndex` | Switches the MMC3 to the bank number in X and refreshes the bank switch table at `$8026` | | `$8088` (bank 4) | `Bank4_DispatchHandlerByIndex` | Reads the handler index in `HandlerIndex` (`$2D`) and jumps through `Bank4_BankedHandlerJumpTable` (`$8095`) by pushing the target address and returning into it | | `$80C9` (bank 4) | `Bank4_WriteVramBlock` | Switches the PPU off, points PPUADDR at the block in `Zp11`/`Zp12` and writes the bytes of the block to PPUDATA | | `$8111` (bank 4) | `Bank4_IncrementVramPointer` | Advances the 16-bit block pointer in `Zp11`/`Zp12` by one byte | | `$8118` (bank 4) | `Bank4_WritePaletteBlock` | Points PPUADDR at `$3F00` and writes the 32 bytes of the palette block to PPUDATA | | `$A75B` (bank 5) | `Bank5_ResetSoundChannels` | Silences the APU, clears the 64-entry sound channel table at `$0711` and starts the sound script again | | `$A7FF` (bank 5) | `Bank5_UpdateActiveChannels` | Compares the active-channel mask in `ZpD6` with the APU status and the mask in `$0707`, and retires the channels the APU has finished | | `$A9C3` (bank 5) | `Bank5_StoreNotePeriod` | Takes the note byte out of the sound script and stores its period in the note table at `$0721` | | `$A9E9` (bank 5) | `Bank5_SetChannelPeriod` | Writes the period of one channel into `$0711` and adds it to the running period counters at `$0715`/`$0719` | | `$AA7A` (bank 5) | `Bank5_CheckSoundRequest` | Tests the sound request flag at `$07A1` and sets X to 1 when a request is pending | | `$AAF1` (bank 5) | `Bank5_RunSoundEngine` | Saves A, X and Y, tests the sound engine's mode byte at `$E3` and hands over to the script interpreter | | `$AB11` (bank 5) | `Bank5_SetApuChannelRegisters` | Reads the instrument record, turns the channel on in `ZpD6`, writes `$0F` to APUSTATUS and fills the five APU channel registers from the record |