Level Maps ---------- There are 13 primary maps (one per level, plus the WELL DONE screen). Some levels also have secondary map data, which is used for changing parts of the map during the level (e.g. the disappearing girders on level 1). Here's the map for the first level, as a disassembly: level01_map: .byte $FE,$33,$9C,$FD,$04,$00,$44,$05 ; A300 FE 33 9C FD 04 00 44 05 .3....D. .byte $06,$04,$15,$0A,$74,$15,$0A,$24 ; A308 06 04 15 0A 74 15 0A 24 ....t..$ .byte $22,$02,$74,$22,$02,$24,$25,$16 ; A310 22 02 74 22 02 24 25 16 ".t".$%. .byte $04,$45,$04,$44,$45,$06,$8C,$45 ; A318 04 45 04 44 45 06 8C 45 .E.DE..E .byte $04,$04,$55,$08,$34,$55,$0E,$7C ; A320 04 04 55 08 34 55 0E 7C ..U.4U.| .byte $55,$08,$FD,$04,$FF,$34,$09,$04 ; A328 55 08 FD 04 FF 34 09 04 U....4.. .byte $5C,$44,$0A,$FD,$04,$01,$5C,$06 ; A330 5C 44 0A FD 04 01 5C 06 \D....\. .byte $04,$1C,$3B,$0A,$FE,$5F,$9C,$FD ; A338 04 1C 3B 0A FE 5F 9C FD ..;.._.. .byte $00,$04,$0C,$41,$05,$8C,$41,$05 ; A340 00 04 0C 41 05 8C 41 05 ...A..A. .byte $24,$01,$05,$74,$01,$05,$4C,$01 ; A348 24 01 05 74 01 05 4C 01 $..t..L. .byte $15,$FE,$C9,$9C,$06,$18,$0A,$99 ; A350 15 FE C9 9C 06 18 0A 99 ........ .byte $18,$0A,$FE,$DA,$9C,$1D,$38,$06 ; A358 18 0A FE DA 9C 1D 38 06 ......8. .byte $81,$38,$06,$FE,$B3,$9C ; A360 81 38 06 FE B3 9C .8.... level01_map_bombs: .byte $04,$12,$01,$38,$02,$01,$64,$02 ; A366 04 12 01 38 02 01 64 02 ...8..d. .byte $01,$98,$12,$01,$44,$22,$01,$58 ; A36E 01 98 12 01 44 22 01 58 ....D".X .byte $22,$01,$04,$42,$01,$98,$42,$01 ; A376 22 01 04 42 01 98 42 01 "..B..B. .byte $04,$52,$01,$38,$52,$01,$64,$52 ; A37E 04 52 01 38 52 01 64 52 .R.8R.dR .byte $01,$98,$52,$01,$FF ; A386 01 98 52 01 FF ..R.. A300: FE 33 9C selects a shape (in this case, a girder) A303: FD 04 00 sets the X and Y delta (4 pixels X, 0 lines Y) A306: 44 is the X starting position A307: 05 is the Y starting position A308: 06 is the number of copies to draw (length of line) A309: 04 is another X position (same shape type, still drawing girders) A30A: 15 is another Y position (same shape type) A30B: 0A is the number of copies to draw ...more x/y/length tuples here, all drawn with dx=4, dy=0, aka horizontally from left to right. A32A: $FD,$04,$FF sets a new X/Y delta. now we're drawing left and up, for the diagonal girders (aka ramps) that run SW to NE. A333: $FD,$04,$01 is X/Y delta, drawing left and down, for the ramps that run NW to SE (tilted the other way from the previous set). A33C: $FE,$5F,$9C selects a different shape (ladders). There are more $FE's to select ladders, up/down ropes, and bombs... The bombs *must* come last! They have their own label here, level01_map_bombs. This is because the level_desc structure has a 2nd map data pointer to the bombs, used by bomb_pickup for determining the location of the bomb that was just picked up. Most levels don't even use the position, but it's always stored at $06DC/$06DD for use by the bomb_sub (if there is one). so eventually, I need ca65 macros that resemble instructions that assemble into map data. Something like: gfx_shape $9C33 gfx_delta $04,$00 gfx_draw $44,$05,$06 gfx_draw $04,$15,$0A ...notionally the drawing engine is a little CPU with 3 16-bit registers: a program counter (stored at $C0), gfx (the graphic object it draws) and delta (2 8-bit signed ints). It has 5 opcodes: $FC - gfx_jump (operand = 16-bit target address) $FD - gfx_delta (operand = 2 8-bit signed ints) $FE - gfx_shape (operand = 16-bit address of shape, see below) $FF - gfx_end (done drawing) Anything else - gfx_draw (opcode is X start, next 2 bytes are Y and length) All the opcodes other than gfx_end are 3 bytes. Write macros for ca65 and a disassembler in perl. Hand disassembly of level01_map: level01_map: gfx_shape sh_girder gfx_delta $04,$00 ; draw left to right (or to the east), 4 pixel steps gfx_draw $44,$05,$06 ; these are all horizontal platforms gfx_draw $04,$15,$0A gfx_draw $74,$15,$0A gfx_draw $24,$22,$02 gfx_draw $74,$22,$02 gfx_draw $24,$25,$16 gfx_draw $04,$45,$04 gfx_draw $44,$45,$06 gfx_draw $8C,$45,$04 gfx_draw $04,$55,$08 gfx_draw $34,$55,$0E gfx_draw $7C,$55,$08 gfx_delta $04,$FF ; diagonally up/right (northeast), 4px east, 1px north gfx_draw $34,$09,$04 gfx_draw $5C,$44,$0A gfx_delta $04,$01 ; diagonally down/right (southeast), 4px east, 1px south gfx_draw $5C,$06,$04 gfx_draw $1C,$3B,$0A gfx_shape sh_ladder ; we're done drawing girders gfx_delta $00,$04 ; draw downwards (south), 4px steps gfx_draw $0C,$41,$05 gfx_draw $24,$01,$05 gfx_draw $74,$01,$05 gfx_draw $4C,$01,$15 gfx_shape sh_up_rope gfx_draw $06,$18,$0A ; notice no gfx_delta here, keep using previous delta! gfx_draw $99,$18,$0A gfx_shape sh_down_rope gfx_draw $1D,$38,$06 ; again, no gfx_delta needed gfx_draw $81,$38,$06 gfx_shape sh_bomb level01_map_bombs: ; always need a label at the start of the bombs gfx_draw $04,$12,$01 ; no gfx_delta, wouldn't matter as length is 1 for these. gfx_draw $38,$02,$01 gfx_draw $64,$02,$01 gfx_draw $98,$12,$01 gfx_draw $44,$22,$01 gfx_draw $58,$22,$01 gfx_draw $04,$42,$01 gfx_draw $98,$42,$01 gfx_draw $04,$52,$01 gfx_draw $38,$52,$01 gfx_draw $64,$52,$01 gfx_draw $98,$52,$01 gfx_end ; that's all, folks! level01 also has 4 sets of map_change instructions, triggered when various bombs are picked up: l01_map_change_0: gfx_shape $9C49 ; sh_blank_4x4 gfx_delta $04,$01 gfx_draw $64,$08,$02 gfx_end l01_map_change_1: gfx_shape $9C49 ; sh_blank_4x4 gfx_delta $04,$FF gfx_draw $34,$09,$02 gfx_end l01_map_change_2: gfx_shape $9C49 ; sh_blank_4x4 gfx_delta $04,$00 gfx_draw $2C,$25,$05 gfx_end l01_map_change_3: gfx_shape $9C49 ; sh_blank_4x4 gfx_delta $04,$00 gfx_draw $60,$25,$05 gfx_end As you can see, these just erase parts of platforms (even if you can't tell from looking at the data, you can tell from playing the game). Shapes ------ Actual graphics data (girder sections, ladders, etc) is stored less efficiently. I'm not going to call these "sprites" because they don't move, and I'm not going to call them "tiles" because they aren't (they can be placed at arbitrary X/Y positions, not limited to a character map style grid). So I'll refer them as a "shape". Each shape is at least one pixel wide and at least 1 pixel tall. Pixels are 2 bits wide (4-color GR.7 mode), and are stored unpacked (2 bits per byte). Here's what the girder section looks like: girder: .byte $04,$00,$00,$01,$01,$01,$01,$04 ; 9C33 04 00 00 01 01 01 01 04 ........ .byte $00,$01,$01,$00,$01,$00,$04,$00 ; 9C3B 00 01 01 00 01 00 04 00 ........ .byte $02,$01,$01,$01,$01,$FF,$xx,$xx ; 9C43 02 01 01 01 01 FF xx xx ........ That's from the disassembly, the xx's are "don't care" because they're actually part of the next shape in the table. Here's the same thing, laid out in a more human-readable way [*] girder: girder_row0: girder_row0_width: .byte $04 girder_row0_x_offset: .byte $00 girder_row0_y_offset: .byte $00 girder_row0_pixels: .byte $01,$01,$01,$01 girder_row1: girder_row1_width: .byte $04 girder_row1_x_offset: .byte $00 girder_row1_y_offset: .byte $01 girder_row1_pixels: .byte $01,$00,$01,$00 girder_row2: girder_row2_width: .byte $04 girder_row2_x_offset: .byte $00 girder_row2_y_offset: .byte $02 girder_row2_pixels: .byte $01,$01,$01,$01 girder_end: .byte $FF [*] Getting da65 to emit this would be possible but unwieldy, it'll have to wait until I've mapped out all the 'unknown' code/data sections and can start manually editing the disassembly to turn it into proper source. This makes a shape like this: XXXX X X XXXX ...which you'll recognize as a girder segment, if you squint a little. Notice the use of $FF as an end marker. If it occurs in place of a width, the dm_draw_gfx routine knows it's done, and exits immediately. Here's the next shape in the table: blank: blank_row0: blank_row0_width: .byte $04 blank_row0_x_offset: .byte $00 blank_row0_y_offset: .byte $00 blank_row0_pixels: .byte $00,$00,$00,$00 blank_row1: blank_row1_width: .byte $04 blank_row1_x_offset: .byte $00 blank_row1_y_offset: .byte $01 blank_row1_pixels: .byte $00,$00,$00,$00 blank_row2: blank_row2_width: .byte $04 blank_row2_x_offset: .byte $00 blank_row2_y_offset: .byte $02 blank_row2_pixels: .byte $00,$00,$00,$00 blank_end: .byte $FF This is a 4x4 block of empty pixels. It's probably used to erase parts of the level (e.g. the disappearing platforms on level 1). Next: xxx: xxx_row0: xxx_row0_width: .byte $02 xxx_row0_x_offset: .byte $00 xxx_row0_y_offset: .byte $00 xxx_row0_pixels: .byte $02,$02 xxx_row1_width: .byte $02 xxx_row1_x_offset: .byte $06 xxx_row1_y_offset: .byte $00 xxx_row1_pixels: .byte $02,$02 xxx_row2_width: .byte $02 xxx_row2_x_offset: .byte $00 xxx_row2_y_offset: .byte $01 xxx_row2_pixels: .byte $02,$02 xxx_row3_width: .byte $02 xxx_row3_x_offset: .byte $06 xxx_row3_y_offset: .byte $01 xxx_row3_pixels: .byte $02,$02 xxx_row4_width: .byte $08 xxx_row4_x_offset: .byte $00 xxx_row4_y_offset: .byte $02 xxx_row4_pixels: .byte $02,$02,$02,$02,$02,$02,$02,$02 xxx_row5_width: .byte $02 xxx_row5_x_offset: .byte $00 xxx_row5_y_offset: .byte $03 xxx_row5_pixels: .byte $02,$02 xxx_row6_width: .byte $02 xxx_row6_x_offset: .byte $06 xxx_row6_y_offset: .byte $03 xxx_row6_pixels: .byte $02,$02 xxx_end: .byte $FF In this one, all the pixels are $02. Most of the shapes will be like this, only using one color.