Castlevania II - Belmont's Revenge:ROM map
The following article is a ROM map for Castlevania II - Belmont's Revenge.
Format for PRG-ROM is Bank:RAM address.
(r:symbol) marks the start of a callable subroutine. (RSTxy:symbol) marks a callable subroutine which can be called with the special quick-access RST commands. (t:symbol) marks the start of a data table.
Note that many functions take as an input the current entity in register d, and the entity property in register e. As a rule, entities are $20 bytes long and the first $20 bytes of every each page ($100 bytes) compose an entity.
X:7FFF all swappable banks store list their bank number as the last byte of the bank. 0:0000 (RST00:jumptable_entity_state) jumptable (indirect jump by entry in table following call to RST00 according to current entity's state variable) 0:0003 (r:jumptable) jumptable by A. 0:0008 (RST08:ld_from_table) hl <- (hl + 2a) 0:000C (r:ld_hl_hl) hl <- (hl) 0:0010 (RST10:entity_set_image) sets entity's image (see "Images" section below) 0:0018 (RST18:entity_set_timer) sets entity property $08 to A. This seems to be the entity's timer, such as how long until fire despawns or belmont's hitstun. 0:0020 (RST20:entity_get_x) e <- $17, a <- x position of entity. 0:0028 (RST28:add_hl_a) hl += a, then a <- l. 0:0030 (RST30:entity_inc_state) increments the state variable (D:01) of the current entity. 0:0038 (RST38:entity_get_y) get Y position of current entity. 0:01D9 (r:longcall_2_438D) stores current bank (read from $7FFF) on stack, calls routine 2:438D (write screen's worth of tiles to vram), then returns to caller bank. 0:0D23 (r:entity_set_state_and_substate) entity state <- b, substate (attacking) <- c 0:0D2A (r:belmont_apply_velocity) adds belmont's velocity to belmont's x position 0:2838 (r:standard_scanline_effect) ($dec1) <- 01, ($c898) <- 7e 0:283B (r:store_bc_scanline_effect) ($dec1) <- b, ($c898) < 0:2868 (r:load_substage_word_from_table) hl <- ((hl + stage) + substage*2) 0:286D (r:load_substage_byte_from_table) a <- ((hl + stage) + substage) 0:2873 (r:load_stage_data) hl <- (hl + stage); a <- substage. 0:2881 (r:add_bc_a) bc += a, then a <- c. 0:2886 (r_add_de_a) de += a, then a <- e 0:2986 (r:memcpy) directly copies bc bytes from hl to de. After this, hl and de point to the end of their respective buffers. Before calling, if de=hl+1, smears bc copies of [hl]. This can be a handy way to zero out a large buffer. 0:297B (r:leftshift_bc_4) leftshifts bc by 4. 0:2986 (r:lda_00) a <- $00 0:2989 (r:lda_06) a <- $06 0:298C (r:lda_7F) a <- $7F 0:298F (r:lda_06) a <- $80 0:2AC7 (r:wait_for_blank) Seems to wait to the end of the current blanking period (if applicable) and then to the start of the next. 0:2ca4 change this 2-byte word from 41c0 to 4280 to free up some extra space behind Belmont's head for sprite-hacking. Specifically, this means the map screen blank tile will be loaded from somewhere else. (You'll want to do the same at 0:2ce4 for the map screen as well.) 0:2E24 (r:transfer_buffers_to_vram) Transfers multiple buffers to vram. Before calling, ($ca81) should be set to the transfer scheme (see 0:2E57, below), and hl should point to a zero-terminated meta-buffer containing structs as follows: - 2 bytes (Big-Endian): destination vram address divided by 0x10 - 1 byte: destination length in units of 0x10 (source length depends on this and $ca81 scheme) - 1 byte: source bank - 2 bytes (Little-Endian) source address 0:2E57 (r:transfer_buffer_to_vram) transfers bc from buffer:hl into vram:de, using one of four schemes as selected by ($ca81): - 0: copy bytes from hl 1:1 (e.g. 0, 1, 2, 3, 4, 5...) - 1: duplicate every byte in hl (e.g. 0, 0, 1, 1, 2, 2, ...) - 2: even bytes from hl, odd bytes 0 (e.g. 0, 0, 1, 0, 2, 0, ...) - 3: odd bytes from hl, even bytes 0 (e.g 0, 0, 0, 1, 0, 2, ...) 0:35A9 (r:mbc_bankswap_1) loads swappable bank 1. 0:35AA (r:mbc_bankswap) loads swappable bank from cpu register a. 0:35AF (r:mbc_bankswap_2) loads swappable bank 2. 0:35B5 (r:mbc_bankswap_6) loads swappable bank 6. 0:35BB (r:mbc_bankswap_7) loads swappable bank 7. 0:35C1 (r:mbc_bankswap_3) loads swappable bank 3. 0:36B5 (r:get_tilechunk_address_from_de_plus_4ea2) hl <- 4x4 tile-chunk address (points to bank 2), indexed from [de+$(4ea2)] in bank 6 0:38E0 (r:entity_set_animation) Set animation (prop 0C,0B,0A) to (bc):0:(bc+1) (bc):0:(bc+1) 0:3DAC (r:entity_set_x_velocity_0) Entity x velocity <- 00. 0:3DAF (r:entity_set_x_velocity) Entity x velocity <- cb. 0:3DB4 (r:entity_set_y_velocity_0) Entity y velocity <- 00. 0:3DB7 (r:entity_set_y_velocity) Entity y velocity <- cb. 0:3DBA (r:write_word) (hl) <- cb 0:3DBE (r:entity_get_x_velocity) cb <- Entity x velocity. 0:3DC5 (r:entity_get_y_velocity) cb <- Entity x velocity. 0:3DC8 (r:read_word) cb <- (hl) 2:43ae (r:get_pointer_to_tiledata) de <- a*20 + (tiledata_ptr / ca92:ca93). 2:4407 (r:transfer_4x4_tiles_to_vram) input: bc points to top-left of 4x4 vram tiles, hl points to length-16 array. Also copies the tiles to to bc+$3C00 (i.e. $D000-$D800) 3:5242 (t:substage_misc_entities) (index via load_substage_data): copied (with some modification) to $d240/$d340. 3:58AC (t:substage_enemies) (index via load_substage_data): copied (with some modification) to $d440/$d540. 3:5D25 (t:substage_items) (index via load_substage_data): copied (with some modification) to $d640/$d740. 3:6bdc (r:clear_entity) clears the current entity (d:00-d:20) 3:6C49 (r:screen_coords_arithmetic) bc <- a plus bc in screen-radix. "screen-radix" here means that b represents screen dimensions ($A0 or $80 depending on vertical or horizontal), and c ranges between 0 and the screen dimension. Before calling, store screen dimension ($80 or $A0) in $(CBC3). 3:6D6B (r:inc_CBC4) increments $(CBC4) 6:421D (r:entity_update) Belmont frame update routine. Called from 0:05FA. 6:4235-4241 Belmont update jumptable 6:427F Belmont state 0 (standing) routine 6:4289 Belmont state 2 (crouching) routine 6:42E9 Belmont state 1 (walking) routine 6:4293 Belmont state 3 (jumping) routine 6:42A1 Belmont state 4 (??) routine 6:44A6 Belmont state 5 (??) routine 6:44D2 Belmont state 6 (??) routine 6:4510 Belmont state 7 (??) routine 6:461B (r:input_A_pressed) A gets zero or 10 depending on if the A button was pressed this frame. Status flags set 6:4621 (r:input_up_down_held) A and status flags 6:4627 (r:input_left_right_held) A and status flags 6:462D (r:input_B_pressed) A and status flags 6:4633 (r:input_B_held) A and status flags 6:4639 (r:input_down_held) A and status flags 6:464B (r:input_up_held) A and status flags 6:4651 (r:input_any_held) A and status flags 6:4801 (r:belmont_set_walk) Read left/right input and set Belmont to be walking left or right accordingly. 6:4817 (r:entity_move_face_right) Entity moves and faces right at velocity 0x90 6:4817 (r:entity_move_face_left) Entity moves and faces left at velocity -0x90
Bank swap routines:
- All are in bank 0, and all write to $2180 to change the bank. For some reason, bank 5 is always swapped inline rather than by function call.
- Entities' images are stored in property 0A. Editing this value will change the entity's image -- for example, the axe might appear as a torch, at least until the axe's animation restores the image to the next frame of axe animation. Below is a partially-complete table of images (Please contribute!).
0: Torch (frame 0) 1: Torch (frame 1) 2: Coin (frame 0) 3: Coin (frame 1) 4: Score Orb 5: Small heart 6: Large Heart 7: Wall meat 08: fire (frame 0) 09: fire (frame 1) 0A: 1-up 0B: holy water (icon) 0C: holy water (projectile) 0D-10: Axe/Cross (frames 0-3) 11-23: Belmont poses 24-29: whip
and so on.
Entity indices (stored in RAM addresses XY00, XY >= $C6) -- range from 0-7F.
00: (no entity) 01: Lantern/Item: Axe/Cross 02: Lantern/Item: Holy Water 03: Lantern/Item: Coin 04: Lantern/Item: Whip Upgrade Orb 05: Lantern/Item: Small Heart 06: Big Heart 07: (debris) 08: (debris with big heart?) 09: Rat? 0A: ? 0B: breakable block? 0C: Punaguchi 0D: Punaguchi 0E: Rat? 0F: ? (explodes when struck) 10-13: ? 14: vertically moving flame? 15-16: ? 17-1B: ? (palette cycles, and turns to flame when struck.) 1C: ? (ascends) 1D: ? (gravity) 1E: ? (descends) 1F: bat? 20: bat? 21: ? (sessile enemy) 22: Lantern/Item: orb that crashes the game 23-24: ? 25: eyeball spawner (right) 26: eyeball spawner (left) 27-29: ? 2A: (immediately explodes) 2B: ? (deals contact damage, but cannot be destroyed) 2C: ? (like 2B but larger and does more damage) 2D: ? (like 2D) 2E-2F: ? 30: ? (sessile enemy) 31: ? (like 2B) 32: ? 33: ? (descending enemy) 34: raven 35: whip upgrade orb 36: ? 37-3A: ? 3B: ? (sessile enemy, explodes) 3C: eyeball spawner (above) 3D-3E: ? 3F: ? (sessile enemy) 40: ? 41: jumping dagger-thrower 42-44: ? (like 2B) 45: Lantern/Item: boss start (Darkside) 46: ? 47: ? (enemy, drops and dies) 48-49: ? 4A: moving platform, contact damage? 4B-4D: ? 4E: background flame 50: moving platform, covers screen? 51: ? (sticks to top of screen) 52: ? 53: Lantern/Item: boss start (Angel mummy) 54: ? 55: ? (projectile?) 56: ? (moves across screen, leaving trail.) 57: like 56, but in reverse. 58-5A: ? 5B: Night Stalker 5C: ? (contact damage) 5D: ? 60: Lantern/Item: boss start? 61: ? 62: ? (palette cycles, deals damage) 64: ? (like 5C) 65-68: ? 69: Lantern/Item: boss start (Bone Serpent) 6A: Bone Serpent (part) 6B: ? (moves to constant position at top of screen) 6C: Bone Serpent (part) 6D: Bone Serpent (part) 6E: moves to side of screen 6F: Bone Serpent (part) 70: ? (strange glitching tiles) 71: ? 72: Lantern/Item: boss start (Soleil) 73: Lantern/Item: boss start 74: ? (circular movement) 75-78: ? 79-7E: (crashes game) 7F: ?