-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.asm
1089 lines (878 loc) · 18.5 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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
; asmsyntax=ca65
.include "nes2header.inc"
nes2mapper 1
nes2prg 16 * 16 * 1024 ; 256k PRG
nes2chr 0
nes2chrram 1 * 8 * 1024 ; 8k CHR RAM
nes2wram 1 * 8 * 1024
nes2mirror 'V'
nes2tv 'N'
nes2end
.feature leading_dot_in_identifiers
.feature underline_in_numbers
.feature addrsize
.importzp main_BOARD_DATA_WIDTH, main_BOARD_DATA_HEIGHT
.include "font.map.i"
.charmap ' ', $FF ; The only one that can't really be auto-generated
; Remove this line to disable debug code
;DEBUG = 1
.ifdef DEBUG
.out "DEBUG turned on"
.endif
.include "macros.asm"
.ifdef ROW26
BOARD_WIDTH = 26
BOARD_HEIGHT = 12
BOARD_OFFSET_Y = 32
BOARD_OFFSET_X = 24
.else
; I've only got map data at a width of 24 for now
BOARD_WIDTH = 24
BOARD_HEIGHT = 12
BOARD_OFFSET_Y = 32
BOARD_OFFSET_X = 32
.endif
.ifdef PAL
FPS = 50
.else
FPS = 60
.endif
CHILD_OFFSET_Y = 64 ; Is this a good spot for the child board?
CHILD_OFFSET_X = 80
CHILD_BOARD_HEIGHT = 6
CHILD_BOARD_WIDTH = 12
; Button Constants
BUTTON_A = 1 << 7
BUTTON_B = 1 << 6
BUTTON_SELECT = 1 << 5
BUTTON_START = 1 << 4
BUTTON_UP = 1 << 3
BUTTON_DOWN = 1 << 2
BUTTON_LEFT = 1 << 1
BUTTON_RIGHT = 1 << 0
; Scene chunk types
CHUNK_RLE = 1 << 5
CHUNK_RAW = 2 << 5
CHUNK_ADDR = 3 << 5
CHUNK_SPR = 4 << 5
CHUNK_DONE = 0 ; no more chunks
.enum InitIDs
Title
Game
Credits
LevelSelect
GameOver
;ScreenTest
GameWon
.endenum
.enum SceneIDs
Intro
.endenum
.enum ChrData
Credits
Game
Title
Hex
Game2
LevelSelect
LevelSelectUi
Tv
TvLower
.endenum
; Each command might take more than one frame to
; perform all its actions. The commands should be
; implemented in a way that they can be executed
; across multiple frames in the NMI. They should
; also have the ability to run outside of the NMI
; if the PPU has been turned off in a previous
; command.
.enum SceneCmd
EOD = 0
; Turn off screen and draw a full screen Is
; followed by the page ID that contains the data
; and the address of the start data.
DrawFullScene
; Wait a given number of seconds.
WaitSeconds
; Wait a given number of frames.
WaitFrames
; Takes no arguments. If set, pressing START
; will skip to the next scmd_GotoInit command.
SetSkippable
SetUnskippable
; Draw null terminated text
; Arguments: [start PPU address] [text] $00
DrawText
; Draw null terminated text from the table defined with SetDialogueTable
; Arguments: [start PPU address] [text] $00
DrawTextFromTable
; Clears out two full rows of tiles. Takes two
; arguments, one for each tile ID to start from.
ClearText
; Turn off the PPU and enable commands to draw
; to the screen outside of NMI
TurnOffPPU
; Turn the PPU back on and disable drawing
; outside of the NMI. This command should
; probably end in a WaitForNMI call.
TurnOnPPU
; Fill the nametable with the next byte
FillNametable
; Load CHR data onto the PPU
LoadChr
; Blank out the remaining sprites
PadSprites
; Set a single palette. First byte is
; destination palette ID. 0-3 are
; background, 4-7 are sprites.
SetPalette
; TODO: rewrite these functions
;TvAttr
;StaticAttr
; Clear an attribute table to $00
ClearAttr0
ClearAttr1
ClearAttr2
ClearAttr3
; Set a pointer to a function to run during
; the frame and NMI code, respectively.
SetFramePointer
SetNMIPointer
; Run an arbitrary function immediately.
; Expects an RTS.
RunFunction
SetNametable0
SetNametable1
SetNametable2
SetNametable3
; Set the Init routine to jump to with GotoInit
SetExitRoutine
; Set an anchor on screen to start drawing text.
; This defines the starting nametable address as
; well as a set of 16 tiles to use for the text.
; Arguments: [NT address] [Anchor ID] [Start Tile ID]
PrepareText
; Sets the table used with DrawTextFromTable.
; Argument: [address of table index]
; FIXME: is this really needed? Currently hardcoded to
; the table at DialogueIndex
;SetDialogueTable
; End a scene execution and jump to the given
; Init function. Indexed with values from the
; data_Inits table
GotoInit; = $FF
.endenum
.include "menu_ram.asm"
.include "text-engine-ram.asm"
.segment "VECTORS"
.word NMI_Instr
.word RESET
.word IRQ
.segment "ZEROPAGE"
AddressPointer0: .res 2
AddressPointer1: .res 2
AddressPointer2: .res 2
AddressPointer3: .res 2
AddressPointer4: .res 2
AddressPointer5: .res 2
AddressPointer6: .res 2
; First two bytes are frame code, second are NMI
sf_AnimPointers: .res 4
sf_AnimBank: .res 1
sf_Nametable: .res 1
sf_ExitRoutine: .res 1
sf_TextStartTiles: .res 2 ; Tile IDs where the two text areas start
sf_DialogueTable: .res 2
sf_DialogueSwitch: .res 1 ; used in sf_DrawText and sf_DrawTextFromTable
sf_LiveSwitch: .res 1 ; Only draw text in NMI
Sleeping: .res 1
TmpW: .res 1
TmpX: .res 1
TmpY: .res 1
TmpZ: .res 1
IdxA: .res 1
IdxB: .res 1
IdxC: .res 1
IdxD: .res 1
PpuControl: .res 1
LastBank: .res 1
IgnoreInput: .res 1
PaletteBuffer: .res 4*4
PaletteBufferSprites: .res 4*4
zp_BrickAddress: .res 2
FlipFlop: .res 1
btnX: .res 1
btnY: .res 1
.segment "MAINRAM"
ChrWriteDest: .res 1 ; $00 or $80. picks pattern table to write to.
ChrWriteTileCount: .res 1
controller1: .res 1
controller1_Old: .res 1
controller2: .res 1
controller2_Old: .res 1
; Each level is a bit
CompletedLevels: .res 2
LastSpriteOffset: .res 1
sf_Skippable: .res 1
sf_Frames: .res 1
sf_Seconds: .res 1
sf_PpuOn: .res 1
sf_Scroll: .res 1
.segment "NMIRAM"
NMI_Instr: .res 1
NMI_Pointer: .res 2
NMI_RTI = $40
NMI_JMP = $4C
.segment "OAM"
Sprites: .res 256
.segment "WRAM"
;; Just the mini map data for bricks that have been interacted with
.segment "PAGE00"
.byte 0
;; Game Code
.include "game.asm"
.segment "PAGE01"
.byte 1
;.include "map_data.i"
.assert BOARD_WIDTH = main_BOARD_DATA_WIDTH, error, "Board data width does not match code!"
.assert BOARD_HEIGHT = main_BOARD_DATA_HEIGHT, error, "Board data height does not match code!"
.out .sprintf("Board Width: %d", BOARD_WIDTH)
.out .sprintf("Board Height: %d", BOARD_HEIGHT)
.segment "PAGE02"
.byte 2
.include "screen-data.i.idx"
.include "title.asm"
.include "level-select.asm"
.include "gameover.asm"
.include "screen-data.i"
.segment "PAGE03"
.byte 3
.segment "PAGE04"
.byte 4
.segment "PAGE05"
.byte 5
.segment "PAGE06"
.byte 6
.segment "PAGE07"
.byte 7
.segment "PAGE08"
.byte 8
.segment "PAGE09"
.byte 9
.segment "PAGE10"
.byte 10
.segment "PAGE11"
.byte 11
;; Minimap data
.segment "PAGE12"
.byte 12
;; Overworld map Data
TitleData:
.incbin "title.chr";, 0, (16 * 160) + (16 * 12) ; (bytes/tile * tile count)
TitleData_Count = ((* - TitleData) / 16) & $FF
LevelSelectTileData:
.incbin "level-select.chr"
LevelSelectTileData_Count = 0
LevelSelectUiTileData:
.incbin "level-select-ui.chr"
LevelSelectUiTileData_Count = (* - LevelSelectUiTileData) / 16
.out .sprintf("LevelSelectUiTileData_Count: %d", LevelSelectUiTileData_Count)
.segment "PAGE13"
.byte 13
;; Occupied by credit data
.segment "PAGE14"
.byte 14
CreditsChrData:
.incbin "credits.chr"
CreditsChrData_Count = 0 ; Zero so it wraps around
GameChrData:
; FIXME: generate the tile count for this stuff
; (and the lookup table below)
.incbin "game.chr", 0, (16 * 16 * 6) ; bytes/tile * tiles/row * rows
GameChrData_Count = (* - GameChrData) / 16
HexTileData:
.incbin "hex.chr", 0, (16 * 16)
HexTileData_Count = (* - HexTileData) / 16
TvTileData:
;.incbin "title.chr", 0, (16 * 32) ; (bytes/tile * tile count)
.incbin "tv.chr"
TvTileData_Count = ((* - TvTileData) / 16) & $FF
TvTileDataLower:
.incbin "tv-lower.chr"
TvTileDataLower_Count = (* - TvTileDataLower) / 16
.out .sprintf("CreditsChrData_Count: %d", CreditsChrData_Count)
.out .sprintf("GameChrData_Count: %d", GameChrData_Count)
.out .sprintf("TitleData_Count: %d", TitleData_Count)
.out .sprintf("HexTileData_Count: %d", HexTileData_Count)
.out .sprintf("LevelSelectTileData_Count: %d", LevelSelectTileData_Count)
.out .sprintf("TvTileData_Count: %d", TvTileData_Count)
.out .sprintf("TvTileDataLower_Count: %d", TvTileDataLower_Count)
.segment "PAGE_FIXED"
.byte 15
IRQ:
rti
RESET:
sei ; Disable IRQs
cld ; Disable decimal mode
ldx #$40
stx $4017 ; Disable APU frame IRQ
ldx #$FF
txs ; Setup new stack
inx ; Now X = 0
stx $2000 ; disable NMI
stx $2001 ; disable rendering
stx $4010 ; disable DMC IRQs
: ; First wait for VBlank to make sure PPU is ready.
bit $2002 ; test this bit with ACC
bpl :- ; Branch on result plus
: ; Clear RAM
lda #$00
sta $0000, x
sta $0100, x
sta $0200, x
sta $0300, x
sta $0400, x
sta $0500, x
sta $0600, x
sta $0700, x
inx
bne :- ; loop if != 0
; set NMI to just RTI
lda #NMI_RTI
sta NMI_Instr
: ; Second wait for vblank. PPU is ready after this
bit $2002
bpl :-
lda #$88
sta $2000
jsr MMC1_Init
lda SceneIDs::Intro
jmp RunScene
;lda InitIDs::Title
;jmp JumpToInit
; Maybe change this to use a sleeping flag
; This can probably break if NMI goes long
WaitForNMI:
lda NMI_Instr
cmp #NMI_RTI
bne :+
.NMI_Set NMI_Bare
: bit Sleeping
bpl :-
lda #0
sta Sleeping
rts
WaitForSpriteZero:
; Wait for VBlank to end
: bit $2002
bvs :-
; Wait for sprite zero hit
: bit $2002
bvc :-
rts
NMI_Bare:
pha
lda #$FF
sta Sleeping
pla
rti
MMC1_Init:
; Set flag
jsr MMC1_Select_Vert
; select CHR 0
lda #%00000000
sta $A000
lsr a
sta $A000
lsr a
sta $A000
lsr a
sta $A000
lsr a
sta $A000
rts
MMC1_Select_Vert:
; control stuff
; vertical mirroring, switchable $8000, fixed $C000, chr 8k
; %0000 1110
lda #%00001110
jmp MMC1_Setup
MMC1_Select_Horiz:
lda #%00001111
MMC1_Setup:
sta $8000
lsr a
sta $8000
lsr a
sta $8000
lsr a
sta $8000
lsr a
sta $8000
rts
MMC1_Select_Page:
sta $E000
lsr a
sta $E000
lsr a
sta $E000
lsr a
sta $E000
lsr a
sta $E000
rts
WriteSprites:
bit $2002
lda #$00
sta $2003
lda #$02
sta $4014
rts
; Write all eight palettes to the PPU directly from ROM space
; Uses AddressPointer3 as input
WritePaletteData:
bit $2002
lda #$3F
sta $2006
lda #$00
sta $2006
ldy #0
:
lda (AddressPointer3), y
sta $2007
iny
cpy #16
bne :-
rts
; TODO: Add an offset parameter?
LoadChrData:
ldx $8000 ; Load the bank ID
; A holds index to Index_ChrData
asl a
asl a
tay
; Load up data pointer
lda Index_ChrData+0, y
sta AddressPointer4+0
lda Index_ChrData+1, y
sta AddressPointer4+1
; Load up tile count
lda Index_ChrData+2, y
sta ChrWriteTileCount
; Load the destination pattern table, and mapper
; page that contains the CHR data.
lda Index_ChrData+3, y
and #$80
beq :+
lda #$10
jmp :++
:
lda #0
:
sta AddressPointer5+1
lda #0
sta AddressPointer5+0
;sta ChrWriteDest
; Right half contains page of data
lda Index_ChrData+3, y
and #$0F
jsr MMC1_Select_Page
jsr WriteChrData
; Go back to the original page
txa
jsr MMC1_Select_Page
rts
; Writes CHR data directly to RAM from ROM space
; Input:
; AddressPointer4 source data location
; AddressPointer5 destination address
; ChrWriteTileCount number of tiles
WriteChrData:
bit $2002
lda AddressPointer5+1
sta $2006
lda AddressPointer5+0
sta $2006
@copyLoop:
ldy #0
;
; Copy single tile
: lda (AddressPointer4), y
sta $2007
iny
cpy #16
bne :-
;
; Increment pointer to next tile
lda AddressPointer4
clc
adc #16
sta AddressPointer4
bcc :+
inc AddressPointer4+1
:
dec ChrWriteTileCount
bne @copyLoop
rts
FillNametable0:
sta TmpX
bit $2002
lda #$20
sta $2006
jmp utils_FillNametable
FillNametable1:
sta TmpX
bit $2002
lda #$24
sta $2006
jmp utils_FillNametable
FillNametable2:
sta TmpX
bit $2002
lda #$28
sta $2006
jmp utils_FillNametable
FillNametable3:
sta TmpX
bit $2002
lda #$2C
sta $2006
jmp utils_FillNametable
utils_FillNametable:
lda #00
sta $2006
ldx #30
lda TmpX
@loop2:
.repeat 32
sta $2007
.endrepeat
dex
bne @loop2
rts
ClearAttrTable0:
bit $2002
lda #$23
sta $2006
lda #0
pha
jmp utils_ClearAttrTable
ClearAttrTable1:
bit $2002
lda #$27
sta $2006
lda #0
pha
jmp utils_ClearAttrTable
ClearAttrTable2:
bit $2002
lda #$2B
sta $2006
lda #0
pha
jmp utils_ClearAttrTable
ClearAttrTable3:
bit $2002
lda #$2F
sta $2006
lda #0
pha
jmp utils_ClearAttrTable
FillAttrTable0:
pha
bit $2002
lda #$23
sta $2006
jmp utils_ClearAttrTable
utils_ClearAttrTable:
lda #$C0
sta $2006
ldx #8
;lda #$00
pla
@loop:
.repeat 8
sta $2007
.endrepeat
dex
bne @loop
rts
ClearSprites:
; clear sprites
lda #$FF
ldx #0
:
sta Sprites, x
inx
bne :-
rts
; Was a button pressed this frame?
ButtonPressedP1:
sta btnX
lda IgnoreInput
beq :+
;dec IgnoreInput
lda #0
rts
:
lda btnX
and controller1
sta btnY
lda controller1_Old
and btnX
cmp btnY
bne btnPress_stb
; no button change
rts
ButtonPressedP2:
sta btnX
lda IgnoreInput
beq :+
dec IgnoreInput
lda #0
rts
:
lda btnX
and controller2
sta btnY
lda controller2_Old
and btnX
cmp btnY
bne btnPress_stb
; no button change
rts
btnPress_stb:
; button released
lda btnY
bne btnPress_stc
rts
btnPress_stc:
; button pressed
lda #1
rts
; Player input
ReadControllers:
lda controller1
sta controller1_Old
lda controller2
sta controller2_Old
; Freeze input
lda #1
sta $4016
lda #0
sta $4016
LDX #$08
@player1:
lda $4016
lsr A ; Bit0 -> Carry
rol controller1 ; Bit0 <- Carry
dex
bne @player1
ldx #$08
@player2:
lda $4017
lsr A ; Bit0 -> Carry
rol controller2 ; Bit0 <- Carry
dex
bne @player2
rts
IncPointer0:
inc AddressPointer0
bne :+
inc AddressPointer0+1
: rts
Clear_NonGlobalZp:
ldx #$80
lda #0
:
sta $00, x
inx
bne :-
rts
; Clears ram from $0400 to $07FC
ClearRam:
lda #$00
sta AddressPointer0
lda #$03
sta AddressPointer0+1
jsr clr_ram
inc AddressPointer0+1 ; $0400
jsr clr_ram
inc AddressPointer0+1 ; $0500
jsr clr_ram
inc AddressPointer0+1 ; $0600
jsr clr_ram
inc AddressPointer0+1
ldy #$FC
:
.repeat 3
sta (AddressPointer0), y
dey
.endrepeat
bne :-
sta (AddressPointer0), y
rts
; clears one page
clr_ram:
ldy #0
lda #0
: sta (AddressPointer0), y
iny
bne :-
rts
Clear_ExtendedRam:
ldx #2
ldy #$00
ldx #$20
lda #$60
sta AddressPointer0+1
lda #$00
sta AddressPointer0
@loop:
.repeat 32
sta (AddressPointer0), y
iny
.endrepeat
bne @loop
inc AddressPointer0+1
dex
bne @loop
rts
; Jumps to an init in the init table. ID in A. Use the
; InitIDs enum for IDs.
JumpToInit:
; multiply A by 5
tay
ldx data_Mult5, y
; grab the address pointer
lda data_Inits+3, x
sta AddressPointer0
lda data_Inits+4, x
sta AddressPointer0+1
; swap banks
lda data_Inits+2, x
jsr MMC1_Select_Page
; Reset stack
ldx #$FF
txs
jmp (AddressPointer0)
WritePalettes:
lda #PPU_CTRL_NMI
sta $2000
bit $2002
lda #$3F
sta $2006
lda #$00
sta $2006
.repeat 32, i
lda PaletteBuffer+i
sta $2007
.endrepeat
rts
; Binary value in A, ASCII Hex values output in TmpX and TmpY
BinToHex:
sta TmpX
lda #0
sta TmpY
@tens:
lda TmpX
cmp #$10
bcs @addtens
jmp @done
@addtens:
inc TmpY
sec
sbc #$10
sta TmpX
jmp @tens
@done:
rts
; Writes a blank tile to ID $FF
WriteBlankTile:
bit $2002
lda #$0F
sta $2006
lda #$F0
sta $2006
lda #0
.repeat 16
sta $2007
.endrepeat
rts
Pal_Tv:
.byte $0F, $10, $00, $0F
.byte $0F, $2A, $0F, $0A
data_Inits: