-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathblink.s
More file actions
429 lines (373 loc) · 11.8 KB
/
Copy pathblink.s
File metadata and controls
429 lines (373 loc) · 11.8 KB
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
;;;
;; Make a character blink by performing bank switching on the PPU.
;;
;; This is done by using the MMC3 chip. Read more about this mapper here:
;; https://www.nesdev.org/wiki/MMC3. The MMC3 is a pretty advanced chip, so
;; first go over the `basics/` directory for a better understanding on easier
;; topics. Most importantly, take a look at examples like `basics/persist.s` or
;; `basics/unrom.s`, which also perform bank switching albeit with simpler
;; hardware.
;;
;; This example basically makes use of the bank switching capabilities of the
;; MMC3 chip, so the handling of IRQs is left to other examples (e.g. see
;; `scroll/roulette.s`).
;;
;; On the context of the MMC3 chip, the CPU ROM space is divided into 4 regions
;; of 8KB each. Two of these regions are swappable, the rest are fixed. This is
;; configured when performing bank switching itself. The MMC3 chip has 8
;; registers which hold which bank to go for any given memory address. Because
;; there's only two swappable banks on the CPU memory space, only two registers
;; are given for the CPU: R6 and R7. Bank switching will be a matter of setting
;; which memory bank corresponds to these two registers. The CHR ROM is similar
;; but in there the space is divided into 6 regions (hence 6 registers): 2 of
;; 2KB and 4 of 1KB. Which regions are 2KB and which 1KB is also configured when
;; doing the bank switching itself. Again, for more in-depth views on all of
;; this, check out the NesDev wiki. This example tries to be detailed whenever
;; bank switching happens.
;;
;; With that being said, some games like Megaman 5/6 performed some background
;; effects by simply performing bank switching on the CHR ROM space (e.g.
;; animating leaves from palm trees). This can easily be achieved by simply
;; performing a bank switch on the PPU space periodically with two similar
;; segments. On this example the character blinks because we periodically switch
;; the bank on the first half of the first pattern table between
;; shared/diskun{0,1}.chr. The only difference between these two files are the
;; top sprites for the character. Notice that the code doesn't have to manually
;; specify which sprite ID to look on each iteration: it's the same ID all the
;; time, it's just that the data underneath each ID subtly changes.
;; Include helpful definitions.
.include "../shared/mmc3.s"
;; Variables used on this example.
.scope Vars
zp_counter = $00
zp_last_bank = $01
.endscope
.segment "HEADER"
.byte 'N', 'E', 'S', $1A
.byte $10 ; 16 * 16 PRG-ROM (256KB)
.byte $10 ; 16 * 8 CHR-ROM (128KB)
.byte $42, $08 ; Mapper 4, battery present, iNES 2.0 header
.res 8, 0
.segment "VECTORS"
.addr nmi, reset, irq
;;; NOTE: lots of banks, all of them empty since we don't need them :)
.segment "PRG0_00"
.byte $FF
.segment "PRG0_01"
.byte $FF
.segment "PRG0_02"
.byte $FF
.segment "PRG0_03"
.byte $FF
.segment "PRG0_04"
.byte $FF
.segment "PRG0_05"
.byte $FF
.segment "PRG0_06"
.byte $FF
.segment "PRG0_07"
.byte $FF
.segment "PRG0_08"
.byte $FF
.segment "PRG0_09"
.byte $FF
.segment "PRG0_0A"
.byte $FF
.segment "PRG0_0B"
.byte $FF
.segment "PRG0_0C"
.byte $FF
.segment "PRG0_0D"
.byte $FF
.segment "PRG0_0E"
.byte $FF
.segment "PRG1_00"
.byte $FF
.segment "PRG1_01"
.byte $FF
.segment "PRG1_02"
.byte $FF
.segment "PRG1_03"
.byte $FF
.segment "PRG1_04"
.byte $FF
.segment "PRG1_05"
.byte $FF
.segment "PRG1_06"
.byte $FF
.segment "PRG1_07"
.byte $FF
.segment "PRG1_08"
.byte $FF
.segment "PRG1_09"
.byte $FF
.segment "PRG1_0A"
.byte $FF
.segment "PRG1_0B"
.byte $FF
.segment "PRG1_0C"
.byte $FF
.segment "PRG1_0D"
.byte $FF
.segment "PRG1_0E"
.byte $FF
;;; NOTE: the first fixed PRG bank will simply contain utilities for moving the
;;; player around.
.segment "FIXED"
.include "../shared/diskun.s"
.include "../shared/clear.s"
;;; NOTE: the main bulk of this example. Comments only for the parts which are
;;; specific to this example.
.segment "TAIL"
.proc reset
sei
cld
ldx #$40
stx $4017
ldx #$FF
txs
inx
stx $2000
stx $2001
stx $4010
;;;
;; NOTE: Setup MMC3
;; Just like we saw with the MMC1 chip on `basics/persist.s`, mirroring is
;; configurable (i.e. not soldered in hardware as with the regular NROM).
;; There's a register you can set for this, tied to even addresses between
;; $A000-$BFFE.
;;
;; Set mirroring to vertical (0).
lda #$00
sta MMC3::MIRRORING
;; Interrupts are a whole topic of their own on the MMC3 chip. Here we just
;; disable them and leave it for other examples (e.g. see
;; `scroll/roulette.s`). Again, this is tied to a register mapped to a
;; region in memory (even addresses on $E000-$FFFE in this case).
sta MMC3::IRQ_DISABLE
;; PRG RAM can be protected from writes, but this is a feature that many
;; emulators choose to discard because it might conflict with the MMC6 chip
;; support. Long story short, here we just enable PRG RAM and leave it at
;; that.
lda #$80
sta MMC3::RAM_PROTECT
;; PRG banking setup. PRG-ROM is divided into 4 banks of 8KB each. The last
;; two banks are set to be fixed (otherwise the values below should have set
;; bit 7 each to make other regions fixed and others swappable, see the
;; documentation on NesDev). The first two banks are controlled by registers
;; 6 and 7. In this case, we will simply set the first two banks for these
;; two regions.
BANK_REGISTER_SET 6, 0
BANK_REGISTER_SET 7, 1
;; CHR banking setup. The PPU is divided into 8 banks 1KB each. Hence, we
;; have to fill up this 8KB of the usual CHR-ROM space through banking. The
;; first two registers controlling bank switching on the PPU, R0 and R1,
;; actually hold control over 2KB. Hence, the first two registers actually
;; span 4 banks (that is, the first pattern table on the PPU). We have to
;; account for that when writing into the first two registers (and that's
;; why they are set to 0 and 2 respectively). Other than that, R2-R5 hold
;; which bank goes into the other remaining regions which are 1KB each
;; (hence, the second pattern table is controlled via R2-R5). Because of
;; this, we can simply assign one 1KB bank to each register.
;;
;; To sum things up, on this setup the first pattern table is equally
;; divided by 2 and it's controlled by R0 and R1 respectively. The second
;; pattern table is equally divided by 4 and it's controlled by R2-R5.
BANK_REGISTER_SET 0, 0
BANK_REGISTER_SET 1, 2
BANK_REGISTER_SET 2, 4
BANK_REGISTER_SET 3, 5
BANK_REGISTER_SET 4, 6
BANK_REGISTER_SET 5, 7
;; NOTE: and from here on initialization proceeds as usual.
bit $2002
@vblankwait1:
bit $2002
bpl @vblankwait1
ldx #0
lda #0
@ram_reset_loop:
sta $000, x
sta $100, x
sta $300, x
sta $400, x
sta $500, x
sta $600, x
sta $700, x
inx
bne @ram_reset_loop
lda #$EF
@sprite_reset_loop:
sta $200, x
inx
bne @sprite_reset_loop
lda #$00
sta $2003
lda #$02
sta $4014
@vblankwait2:
bit $2002
bpl @vblankwait2
lda #$3F
sta $2006
lda #$00
sta $2006
lda #$0F
ldx #$20
@palettes_reset_loop:
sta $2007
dex
bne @palettes_reset_loop
jmp main
.endproc
;;; NOTE: mainly as usual except that a bit of game loop has been added to
;;; handle the blinking state.
.proc main
;; The code will iterate between banks 4 and 6 on the pattern table, as
;; that's where sprites are located (check the CHARS segment for more info).
lda #4
sta Vars::zp_last_bank
CLEAR_SCREEN
jsr Diskun::init_palettes
jsr init_sprites
cli
;; NOTE: This looks like other examples, but here having background tiles on
;; the first pattern table and sprite tiles on the second pattern table is
;; not a matter of personal taste, but there are technical reasons for it.
;; If you check how IRQs work on the MMC3 chip:
;; https://www.nesdev.org/wiki/MMC3#IRQ_Specifics; you will find that on 8x8
;; tile mode having this arrangement actually spares us from a hardware bug.
;; Long story short, placing background tiles first and sprite tiles second
;; make scanline IRQs reliable. Hence, at least on all MMC3 examples, this
;; will be guaranteed.
lda #%10001000
sta $2000
lda #%00011110
sta $2001
@main_game_loop:
READ_JOYPAD1
jsr Diskun::update
lda #%10000000
ora $20
sta $20
@wait_for_render:
bit $20
bmi @wait_for_render
;; NOTE: let there be a game logic :D
;; Is the counter already at the limit? If not just restart the game loop.
lda Vars::zp_counter
cmp #$20
bne @main_game_loop
;; Reset the counter
lda #0
sta Vars::zp_counter
;; The whole trick is done on R2, which points to $1000, where the sprite
;; tiles begin. Hence, select it.
lda #2
sta MMC3::BANK_SELECT
;; The value for the register is either 4 or 6 depending on its last value.
lda Vars::zp_last_bank
cmp #4
beq :+
lda #4
jmp @set
:
lda #6
@set:
;; Save which is the bank being used both internally and onto the MMC3 chip.
sta Vars::zp_last_bank
sta MMC3::BANK_DATA
jmp @main_game_loop
.endproc
;; NOTE: from here on nothing remarkable in comparison to other examples.
.proc init_sprites
NUM_SPRITES = 4
lda #$40
sta Diskun::m_screen_y
lda #$46
sta Diskun::m_screen_x
ldx #$00
@load_sprites_loop:
lda initial_sprite_data, x
sta $0200, x
inx
cpx #(4 * NUM_SPRITES)
bne @load_sprites_loop
rts
initial_sprite_data:
;; $200-$20F
.byte $40, $01, %00000000, $46
.byte $40, $01, %01000000, $4E
.byte $48, $11, %00000000, $46
.byte $48, $11, %01000000, $4E
.endproc
;;; NOTE: nothing to highlight here other than the counter is increased on each
;;; NMI.
.proc nmi
bit $20
bpl @next
pha
txa
pha
tya
pha
;; Increase the counter for the blinking.
inc Vars::zp_counter
jsr Diskun::nmi_update
lda #$00
sta $2003
lda #$02
sta $4014
bit $2002
lda #$00
sta $2005
sta $2005
lda #%01111111
and $20
sta $20
pla
tay
pla
tax
pla
@next:
rti
.endproc
;;; NOTE: IRQ is disabled when setting up the MMC3 chip for this example.
.proc irq
rti
.endproc
;;; NOTE: The header for this game advertises 128KB for CHR-ROM. This is wildly
;;; too much for this example, but it's a reasonable size for an MMC3 game.
;;; Considering only one pair of pattern tables are available at any given
;;; moment (8KB), this means that we need space for 128 / 8 = 16 pairs of
;;; pattern tables here. For this example the first 8KB are really only used,
;;; and the rest are left with a default value ($00).
.segment "CHARS"
;; As explained when initializing the PPUCTRL register ($2000), it's actually
;; important to place background tiles first and sprite tiles second on the
;; MMC3. This is guaranteed here by setting the first pattern table as empty (we
;; have no background on this example, really). The second pattern table is then
;; filled with `diskun0.chr` and `diskun1.chr`, which are 2KB each. Hence, the
;; 4th CHR bank contains the regular character, and the 6th CHR bank contains
;; the blinking version. You can see these values used when initializing
;; `Vars::zp_last_bank`, or when performing bank switching.
.res $1000, $00
.incbin "../assets/diskun0.chr" ; First half of the second pattern table has the default diskun character.
.incbin "../assets/diskun1.chr" ; Second half of the second pattern table simply has the blinking version.
;; The 15 other 8KB portions are left empty.
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00