-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.asm
446 lines (381 loc) · 8.93 KB
/
main.asm
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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
; main program code
;
; Formatting:
; - Width: 132 Columns
; - Tab Size: 4, using tab
; - Comments: Column 57
; reset handler
Reset:
; lda FDS_CTRL_MIRROR ; get setting previously used by FDS BIOS
; and #$f7 ; and set for vertical mirroring
; sta FDS_CTRL
lda #$00 ; clear RAM
tax
@clrmem:
sta $00,x
cpx #4 ; preserve BIOS stack variables at $0100~$0103
bcc :+
sta $100,x
:
sta $200,x
sta $300,x
sta $400,x
sta $500,x
sta $600,x
sta $700,x
inx
bne @clrmem
jsr InitFileHeader
jsr InitNametables
lda #$fd ; set VRAM buffer size to max value ($0302~$03ff)
sta VRAM_BUFFER_SIZE
lda #%10000000 ; enable NMIs & change background pattern map access
sta PPU_CTRL
sta PPU_CTRL_MIRROR
Main:
jsr ProcessBGMode
jsr WaitForNMI
beq Main ; back to main loop
; "NMI" routine which is entered to bypass the BIOS check
Bypass:
lda #$00 ; disable NMIs since we don't need them anymore
sta PPU_CTRL
lda #<NonMaskableInterrupt ; put real NMI handler in NMI vector 3
sta NMI_3
lda #>NonMaskableInterrupt
sta NMI_3+1
lda #$35 ; tell the FDS that the BIOS "did its job"
sta RST_FLAG
lda #$ac
sta RST_TYPE
jmp ($fffc) ; jump to reset FDS
; NMI handler
NonMaskableInterrupt:
bit NMIRunning ; exit if NMI is already in progress
bmi InterruptRequest
sec
ror NMIRunning ; set flag for NMI in progress
pha ; back up A/X/Y
txa
pha
tya
pha
lda NMIReady ; check if ready to do NMI logic (i.e. not a lag frame)
beq NotReady
lda NeedDraw ; transfer Data to PPU if required
beq :+
jsr WriteVRAMBuffer ; transfer data from VRAM buffer at $0302
jsr SetScroll ; reset scroll after PPUADDR writes
dec NeedDraw
:
lda NeedPPUMask ; write PPUMASK if required
beq :+
lda PPU_MASK_MIRROR
sta PPU_MASK
dec NeedPPUMask
:
dec NMIReady
jsr ReadOrDownPads ; read controllers + expansion port
NotReady:
jsr SetScroll ; remember to set scroll on lag frames
pla ; restore X/Y/A
tay
pla
tax
pla
asl NMIRunning ; clear flag for NMI in progress before exiting
; IRQ handler (unused for now)
InterruptRequest:
rti
EnableRendering:
lda #%00001010 ; enable background and queue it for next NMI
.byte $2c ; [skip 2 bytes]
DisableRendering:
lda #%00000000 ; disable background and queue it for next NMI
UpdatePPUMask:
sta PPU_MASK_MIRROR
lda #$01
sta NeedPPUMask
rts
InitNametables:
lda #$20 ; top-left
jsr InitNametable
lda #$24 ; top-right
InitNametable:
ldx #$00 ; clear nametable & attributes for high address held in A
ldy #$00
jmp VRAMFill
NumToChars: ; converts A into hex chars and puts them in X/Y
pha
and #$0f
tay
lda NybbleToChar,y
tay
pla
lsr
lsr
lsr
lsr
tax
lda NybbleToChar,x
tax
rts
NybbleToChar:
.byte "0123456789ABCDEF"
; uses CRC32 calculation routine from https://www.nesdev.org/wiki/Calculate_CRC32
CheckBIOS:
lda #$00 ; init pointer
sta BIOSPtr
tay
lda #$e0
sta BIOSPtr+1
@crc32init:
ldx #3
lda #$ff
@c3il:
sta testcrc+0,x
dex
bpl @c3il
@CalcCRC32:
lda (BIOSPtr),y
@crc32:
ldx #8
eor testcrc+0
sta testcrc+0
@c32l:
lsr testcrc+3
ror testcrc+2
ror testcrc+1
ror testcrc+0
bcc @dc32
lda #$ed
eor testcrc+3
sta testcrc+3
lda #$b8
eor testcrc+2
sta testcrc+2
lda #$83
eor testcrc+1
sta testcrc+1
lda #$20
eor testcrc+0
sta testcrc+0
@dc32:
dex
bne @c32l
inc BIOSPtr
bne :+
inc BIOSPtr+1
:
bne @CalcCRC32 ; will fall through once BIOSPtr == $0000
@crc32end:
ldx #3
@c3el:
lda #$ff
eor testcrc+0,x
sta testcrc+0,x
dex
bpl @c3el
lda testcrc+3
jsr NumToChars
stx CRC32+0
sty CRC32+1
lda testcrc+2
jsr NumToChars
stx CRC32+2
sty CRC32+3
lda testcrc+1
jsr NumToChars
stx CRC32+4
sty CRC32+5
lda testcrc+0
jsr NumToChars
stx CRC32+6
sty CRC32+7
rts
WaitForNMI:
inc NMIReady
:
lda NMIReady
bne :-
rts
; Jump table for main logic
ProcessBGMode:
lda BGMode
jsr JumpEngine
.addr BGInit
.addr DumpPrep
.addr DumpBIOS
.addr DumpSuccess
.addr DoNothing
; Initialise background to display the program name and FDS BIOS revision
BGInit:
jsr CheckBIOS
jsr DisableRendering
jsr WaitForNMI
jsr VRAMStructWrite
.addr BGData
inc BGMode
jmp EnableRendering ; remember to enable rendering for the next NMI
; Print a message before dumping the BIOS
DumpPrep:
lda #$21
ldx #$8A
ldy #PrepMsgLength
jsr PrepareVRAMString
.addr PrepMsg
sta StringStatus ; save status to check later
inc BGMode ; next mode
lda #$01 ; queue VRAM transfer for next NMI
sta NeedDraw
rts
PrepMsg:
.byte "Dumping..."
PrepMsgLength=*-PrepMsg
; Dump the BIOS to 1KiB disk files & show error messages if necessary
DumpBIOS:
jsr VINTWait ; wait a frame and disable NMIs just to be safe
lda FileNum ; this should resume after the last valid file
jsr WriteFile
.addr DiskID
.addr FileHeader
bne PrintError
lda FileNum
cmp #$0B ; check if dump all done
beq FinishDump ; end loop
inc FileNum ; update file number
ldx #$08
inc FileHeader,x ; update file name
ldx #$0C
lda FileHeader,x ; get size
ldx #$0A
adc FileHeader,x
sta FileHeader,x ; update start addr
ldx #$0F
sta FileHeader,x ; update start addr
bne DumpBIOS ; [unconditional branch]
FinishDump:
inc BGMode ; go to next mode on successful write
EnableNMI:
lda PPU_CTRL_MIRROR ; enable NMI
ora #%10000000
bit PPU_STATUS ; in case this was called with the vblank flag set
sta PPU_CTRL_MIRROR ; write to mirror first for thread safety
sta PPU_CTRL
rts
; Print the error message and wait for an inserted disk before retrying
PrintError:
jsr NumToChars
stx ErrorNum
sty ErrorNum+1
lda #$21
ldx #$95
ldy #ErrorMsgLength
jsr PrepareVRAMString
.addr ErrorMsg
sta StringStatus ; save status to check later
lda #$01 ; queue VRAM transfer for next NMI
sta NeedDraw
jsr EnableNMI ; but enable NMI first
jsr WaitForNMI
SideError:
lda FDS_DRIVE_STATUS
and #$01
beq SideError ; wait until disk is ejected
Insert:
lda FDS_DRIVE_STATUS
and #$01
bne Insert ; wait until disk is inserted
lda #$21 ; clear error message
ldx #$95
ldy #BlankMsgLength
jsr PrepareVRAMString
.addr BlankMsg
sta StringStatus ; save status to check later
lda #$01 ; queue VRAM transfer for next NMI
sta NeedDraw
jsr WaitForNMI
jmp DumpBIOS ; then retry the file write
InitFileHeader:
ldx #$10
@loop:
lda FileHeaderR,x
sta FileHeader,x
dex
bpl @loop
lda #$04
sta FileNum
rts
BlankMsg:
.byte " "
BlankMsgLength=*-BlankMsg
ErrorMsg:
.byte "Err. "
ErrorNum:
.byte "00"
ErrorMsgLength=*-ErrorMsg
DiskID:
.byte $00 ; manufacturer
.byte "DUM" ; yes, I know...
.byte $20 ; normal disk
.byte $00 ; game version
.byte $00 ; side
.byte $00 ; disk
.byte $00 ; disk type
.byte $00 ; unknown
FileHeaderR:
.byte $FF
.byte "DISKSYS0"
.word __FILE4_DAT_RUN__
.word __FILE4_DAT_SIZE__
.byte 0 ; PRG
.word __FILE4_DAT_RUN__
.byte $00
; Display a success message
DumpSuccess:
lda #$21
ldx #$95
ldy #SuccessMsgLength
jsr PrepareVRAMString
.addr SuccessMsg
sta StringStatus ; save status to check later
inc BGMode ; next mode
lda #$01 ; queue VRAM transfer for next NMI
sta NeedDraw
rts
SuccessMsg:
.byte "OK!"
SuccessMsgLength=*-SuccessMsg
; Once the dump is done, stay in this state forever
DoNothing:
rts
BGData: ; VRAM transfer structure
Palettes:
.byte $3f, $00 ; destination address (BIG endian)
.byte %00000000 | PaletteSize ; d7=increment mode (+1), d6=transfer mode (copy), length
; Just write to all of the entries so PPUADDR safely leaves the palette RAM region
; (palette entries will never be changed anyway, so we might as well set them all)
PaletteData:
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20
.byte $0f, $00, $10, $20 ; PPUADDR ends at $3F20 before the next write (avoids rare palette corruption)
PaletteSize=*-PaletteData
TextData:
.byte $20, $89 ; destination address (BIG endian)
.byte %00000000 | Text1Length ; d7=increment mode (+1), d6=transfer mode (copy), length
Chars1:
.byte "FDS-BIOS-Dumper"
Text1Length=*-Chars1
.byte $20, $a9 ; destination address (BIG endian)
.byte %00000000 | Text2Length ; d7=increment mode (+1), d6=transfer mode (copy), length
Chars2:
.byte "CRC32: "
CRC32:
.byte "FFFFFFFF"
Text2Length=*-Chars2
.byte $ff ; terminator