1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
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.
|