-
Notifications
You must be signed in to change notification settings - Fork 0
/
paint.asm
1258 lines (1039 loc) · 42.3 KB
/
paint.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
; Kalle Paint a.k.a. Qalle Paint (NES, ASM6)
; --- Constants ---------------------------------------------------------------
; Notes:
; - ppu_buf_*: PPU memory buffer: what to update in PPU memory on next VBlank.
; - 2-byte addresses: less significant byte first.
; - Boolean variables: $00-$7f = false, $80-$ff = true.
; RAM
user_pal equ $00 ; user palette (16 bytes; offsets 4/8/12 unused)
ppu_buf_addrhi equ $10 ; PPU buffer - high bytes of addresses (6 bytes)
ppu_buf_addrlo equ $16 ; PPU buffer - low bytes of addresses (6 bytes)
ppu_buf_values equ $1c ; PPU buffer - values (6 bytes)
vram_offset equ $22 ; offset to NT0 & vram_copy (2 bytes, 0-$3ff)
vram_copy_addr equ $24 ; address within vram_copy (2 bytes)
program_mode equ $26 ; program mode (see below)
run_main_loop equ $27 ; run main loop? (boolean)
pad_status equ $28 ; joypad status
prev_pad_status equ $29 ; joypad status on previous frame
ppu_buf_len equ $2a ; VRAM buffer length (0-6)
delay_left equ $2b ; cursor move delay left (paint mode)
brush_size equ $2c ; cursor type (paint mode; 0=small, 1=large)
paint_color equ $2d ; paint color (paint mode; 0-3)
cursor_x equ $2e ; cursor X position (paint/attribute editor; 0-63)
cursor_y equ $2f ; cursor Y position (paint/attribute editor; 0-59)
blink_timer equ $30 ; cursor blink timer (attribute/palette editor)
pal_ed_crsr_pos equ $31 ; cursor position (palette editor; 0-3)
pal_ed_subpal equ $32 ; selected subpalette (palette editor; 0-3)
ppu_ctrl_copy equ $33 ; copy of ppu_ctrl
str_ptr equ $34 ; pointer for reading string data (2 bytes)
vram_copy equ $0200 ; copy of name & attribute table 0 ($400 bytes)
sprite_data equ $0600 ; OAM page ($100 bytes)
; memory-mapped registers
; see https://wiki.nesdev.org/w/index.php/PPU_registers
ppu_ctrl equ $2000
ppu_mask equ $2001
ppu_status equ $2002
oam_addr equ $2003
ppu_scroll equ $2005
ppu_addr equ $2006
ppu_data equ $2007
dmc_freq equ $4010
oam_dma equ $4014
snd_chn equ $4015
joypad1 equ $4016
joypad2 equ $4017
; program modes
mode_title equ 0 ; title screen
mode_paint equ 1 ; paint mode
mode_attr_ed equ 2 ; attribute editor
mode_pal_ed equ 3 ; palette editor
; possible transitions between modes:
; reset -> title -> paint -> attr_ed -> pal_ed
; ^ |
; +-------------------+
; user interface colors
color_bg equ $0f ; background (black)
color_fg1 equ $28 ; foreground 1 (yellow) ("Pal X" text only)
color_fg2 equ $30 ; foreground 2 (white)
; --- iNES header -------------------------------------------------------------
; see https://wiki.nesdev.org/w/index.php/INES
base $0000
db "NES", $1a ; file id
db 1, 1 ; 16 KiB PRG ROM, 8 KiB CHR ROM
db %00000001, %00000000 ; NROM mapper, vertical NT mirroring
pad $0010, $00 ; unused
; --- Initialization ----------------------------------------------------------
base $c000 ; last 16 KiB of CPU address space
reset ; initialize the NES
; see https://wiki.nesdev.org/w/index.php/Init_code
sei ; ignore IRQs
cld ; disable decimal mode
ldx #%01000000
stx joypad2 ; disable APU frame IRQ
ldx #$ff
txs ; initialize stack pointer
inx
stx ppu_ctrl ; disable NMI
stx ppu_mask ; disable rendering
stx dmc_freq ; disable DMC IRQs
stx snd_chn ; disable sound channels
jsr wait_vbl_start ; wait until next VBlank starts
jsr init_ram ; initialize main RAM
jsr wait_vbl_start ; wait until next VBlank starts
jsr init_ppu_mem ; initialize PPU memory
jsr wait_vbl_start ; wait until next VBlank starts
jsr set_ppu_regs ; set ppu_scroll/ppu_ctrl/ppu_mask
jmp main_loop ; start main loop
wait_vbl_start bit ppu_status ; wait until next VBlank starts
- bit ppu_status
bpl -
rts
init_ram ; initialize main RAM
lda #$00 ; clear zero page
tax
- sta $00,x
inx
bne -
lda #<vram_copy ; clear vram_copy
sta vram_offset+0
lda #>vram_copy
sta vram_offset+1
lda #$00
tay
ldx #4
;
- sta (vram_offset),y
iny
bne -
inc vram_offset+1
dex
bne -
ldx #(16-1) ; copy initial user palette
- lda initial_pal,x
sta user_pal,x
dex
bpl -
ldx #(21*4-1) ; copy initial sprite data
- lda initial_spr_dat,x
sta sprite_data,x
dex
bpl -
ldx #(0*4) ; hide all sprites
ldy #64
jsr hide_sprites ; X = first byte index, Y = count
; this will be copied to prev_pad_status before the pad is
; read the first time and will prevent buttons from registering
; on the first frame
lda #%11111111
sta pad_status
; set misc vars
lda #mode_title
sta program_mode
lda #32
sta cursor_x
lda #28
sta cursor_y
lda #1
sta paint_color
; enable NMI, use PT1 for BG & sprites, use NT1
lda #%10011001
sta ppu_ctrl_copy
rts
init_ppu_mem ; initialize PPU memory
; copy initial palette (while still in VBlank)
ldy #$3f
lda #$00
jsr set_ppu_addr ; Y*$100+A -> address
tax
- lda initial_pal,x
sta ppu_data
inx
cpx #32
bne -
; clear NTs & ATs ($800 bytes)
;
ldy #$20
lda #$00
jsr set_ppu_addr ; Y*$100+A -> address
;
ldy #8
tax
- sta ppu_data
inx
bne -
dey
bne -
; write title screen strings to PT1 and return
;
; init source pointer & index to start of data minus one
lda #<(title_strs-$100)
sta str_ptr+0
lda #>(title_strs-$100)
sta str_ptr+1
ldy #$ff
;
-- jsr inc_str_ptr
lda (str_ptr),y ; PPU address high or terminator
bpl +
rts
+ sta ppu_addr
jsr inc_str_ptr
lda (str_ptr),y ; PPU address low
sta ppu_addr
;
- jsr inc_str_ptr
lda (str_ptr),y ; character or terminator
bmi --
sta ppu_data
jmp -
inc_str_ptr ; increment string read index & pointer
iny
bne +
inc str_ptr+1
+ rts
initial_pal ; initial palette
; background (default user palette; changes during runtime)
db color_bg, $30, $25, $35 ; reds ($30 changed after title)
db color_bg, $18, $28, $38 ; yellows
db color_bg, $1b, $2b, $3b ; greens
db color_bg, $12, $22, $32 ; blues
; sprites (* = changes during runtime)
db color_bg
db $00 ; unused
db $15 ; paint cursor *
db color_bg ; blinking cursors *
db color_bg
db color_bg ; palette editor - background
db color_fg1 ; palette editor - text 1
db color_fg2 ; palette editor - text 2
db color_bg
db color_bg ; palette editor - background
db color_bg ; palette editor - selected color #0 *
db $15 ; palette editor - selected color #1 *
db color_bg
db color_bg ; palette editor - background
db $25 ; palette editor - selected color #2 *
db $35 ; palette editor - selected color #3 *
initial_spr_dat ; initial sprite data (Y, tile, attributes, X for each sprite)
; paint mode
db $ff, $30, %00000000, 0 ; #0: cursor
; attribute editor
db $ff, $32, %00000000, 0 ; #1: cursor top left
db $ff, $32, %01000000, 0 ; #2: cursor top right
db $ff, $32, %10000000, 0 ; #3: cursor bottom left
db $ff, $32, %11000000, 0 ; #4: cursor bottom right
; palette editor
db $ff, $33, %00, 28*8 ; #5: cursor
db 23*8-1, $34, %01, 28*8 ; #6: "Pal" - left
db 23*8-1, $35, %01, 29*8 ; #7: "Pal" - right
db 23*8-1, $36, %01, 30*8 ; #8: subpalette number
db 24*8-1, $3a, %10, 28*8 ; #9: color 0 - square
db 25*8-1, $3b, %10, 28*8 ; #10: color 1 - square
db 26*8-1, $3a, %11, 28*8 ; #11: color 2 - square
db 27*8-1, $3b, %11, 28*8 ; #12: color 3 - square
db 24*8-1, $20|color_bg/16, %01, 29*8 ; #13: color 0 - 16s
db 24*8-1, $20|color_bg&15, %01, 30*8 ; #14: color 0 - 1s
db 25*8-1, $21, %01, 29*8 ; #15: color 1 - 16s
db 25*8-1, $25, %01, 30*8 ; #16: color 1 - 1s
db 26*8-1, $22, %01, 29*8 ; #17: color 2 - 16s
db 26*8-1, $25, %01, 30*8 ; #18: color 2 - 1s
db 27*8-1, $23, %01, 29*8 ; #19: color 3 - 16s
db 27*8-1, $25, %01, 30*8 ; #20: color 3 - 1s
macro str_at _y, _x ; string in NT1
dh $2400+(_y)*32+(_x)
dl $2400+(_y)*32+(_x)
endm
title_strs ; title screen strings; for each string:
; - PPU address high (negative=end of string data)
; - PPU address low
; - tile indexes in PT1 (negative=end of string);
; subtract 64 from uppercase ASCII
;
str_at 2, 10 ; "QALLE PAINT"
db "QALLE"-64, 0, "PAINT"-64
hex 80
;
str_at 5, 6 ; "PRESS START TO BEGIN"
db "PRESS"-64, 0, "START"-64, 0, "TO"-64, 0, "BEGIN"-64
hex 80
;
str_at 8, 3 ; "BUTTONS AFTER THIS SCREEN:"
db "BUTTONS"-64, 0, "AFTER"-64, 0, "THIS"-64, 0, "SCREEN"-64
db $1c
hex 80
;
str_at 10, 7 ; "SELECT=CYCLE MODES"
db "SELECT"-64, $1d, "CYCLE"-64, 0, "MODES"-64
hex 80
;
str_at 12, 9 ; "IN PAINT MODE:"
db "IN"-64, 0, "PAINT"-64, 0, "MODE"-64, $1c
hex 80
;
str_at 13, 7 ; "ARROWS=MOVE CURSOR"
db "ARROWS"-64, $1d, "MOVE"-64, 0, "CURSOR"-64
hex 80
;
str_at 14, 4 ; "START=CHANGE BRUSH SIZE"
db "START"-64, $1d, "CHANGE"-64, 0, "BRUSH"-64, 0, "SIZE"-64
hex 80
;
str_at 15, 6 ; "B=CHANGE BRUSH COLOR"
db "B"-64, $1d, "CHANGE"-64, 0, "BRUSH"-64, 0, "COLOR"-64
hex 80
;
str_at 16, 12 ; "A=PAINT"
db "A"-64, $1d, "PAINT"-64
hex 80
;
str_at 18, 4 ; "IN ATTRIBUTE EDIT MODE:"
db "IN"-64, 0, "ATTRIBUTE"-64, 0, "EDIT"-64, 0, "MODE"-64, $1c
hex 80
;
str_at 19, 7 ; "ARROWS=MOVE CURSOR"
db "ARROWS"-64, $1d, "MOVE"-64, 0, "CURSOR"-64
hex 80
;
str_at 20, 5 ; "B=PREVIOUS SUBPALETTE"
db "B"-64, $1d, "PREVIOUS"-64, 0, "SUBPALETTE"-64
hex 80
;
str_at 21, 7 ; "A=NEXT SUBPALETTE"
db "A"-64, $1d, "NEXT"-64, 0, "SUBPALETTE"-64
hex 80
;
str_at 23, 5 ; "IN PALETTE EDIT MODE:"
db "IN"-64, 0, "PALETTE"-64, 0, "EDIT"-64, 0, "MODE"-64, $1c
hex 80
;
str_at 24, 5 ; "U/D ARROW=MOVE CURSOR"
db "U"-64, $1b, "D"-64, 0, "ARROW"-64, $1d, "MOVE"-64, 0
db "CURSOR"-64
hex 80
;
str_at 25, 5 ; "L/R ARROW=CHANGE ONES"
db "L"-64, $1b, "R"-64, 0, "ARROW"-64, $1d, "CHANGE"-64, 0
db "ONES"-64
hex 80
;
str_at 26, 6 ; "B/A=CHANGE SIXTEENS"
db "B"-64, $1b, "A"-64, $1d, "CHANGE"-64, 0, "SIXTEENS"-64
hex 80
;
hex 80 ; string data terminator
; --- Main loop - common ------------------------------------------------------
main_loop ; wait until NMI routine has run and clear flag
bit run_main_loop
bpl main_loop
lsr run_main_loop
; store previous joypad status and read joypad
lda pad_status
sta prev_pad_status
jsr read_joypad
jsr upd_blink_color ; update color of blinking cursor
inc blink_timer ; advance timer
jsr jump_engine ; run code for this program mode
jmp main_loop
read_joypad ; read 1st joypad or Famicom expansion port controller
; see https://www.nesdev.org/wiki/Controller_reading_code
; bits: A, B, select, start, up, down, left, right
lda #1
sta joypad1
sta pad_status
lsr a
sta joypad1
- lda joypad1
and #%00000011
cmp #1
rol pad_status
bcc -
rts
upd_blink_color ; tell NMI routine to update color of blinking cursor
ldx #color_bg
lda blink_timer
and #(1<<4)
beq +
ldx #color_fg2
+ txa
ldy #(4*4+3)
jmp to_ppu_buf_pal ; A to PPU $3f00 + Y; ends with RTS
jump_engine ; jump to one sub depending on program mode
; note: RTS in the subs below will act like RTS in this sub
; see https://www.nesdev.org/wiki/Jump_table
; and https://www.nesdev.org/wiki/RTS_Trick
; push target address minus one, high byte first
ldx program_mode
lda jump_table_hi,x
pha
lda jump_table_lo,x
pha
; pull address, low byte first; jump to address plus one
rts
; jump table - high/low bytes
jump_table_hi dh title_scrn-1, paint_mode-1, attr_editor-1, palette_editor-1
jump_table_lo dl title_scrn-1, paint_mode-1, attr_editor-1, palette_editor-1
; --- Main loop - title screen (label prefix "ts_") ---------------------------
title_scrn ; if start pressed, switch to paint mode
lda prev_pad_status
bne +
lda pad_status
and #%00010000
beq +
;
; from now on, BG uses PT0 & NT0; keep NMI enabled and keep
; using PT1 for sprites
lda #%10001000
sta ppu_ctrl_copy
;
; replace color that was used for title text
lda #$15
sta user_pal+1
ldy #1
jsr to_ppu_buf_pal ; A to PPU $3f00 + Y
;
jsr to_paint_mode
+ rts ; return to main loop
; --- Main loop - paint mode (label prefix "pm_") -----------------------------
paint_mode ; if select/B/start pressed on previous frame, ignore them all
lda prev_pad_status
and #%01110000
bne +
; if B/select/start pressed, react to it and exit
lda pad_status
asl a
bmi pm_inc_color ; B button
asl a
bmi to_attr_editor ; select
asl a
bmi toggle_brush ; start
; note: we want to be able to accept a horizontal arrow,
; a vertical arrow and button A simultaneously
+ jsr pm_arrows ; d-pad arrow logic
bit pad_status ; paint if button A pressed
bpl +
jmp do_paint ; ends with RTS
+ rts ; return to main loop
pm_inc_color ; increment paint color (0 -> 1 -> 2 -> 3 -> 0)
ldx paint_color
inx
txa
and #%00000011
sta paint_color
jmp pm_upd_crsr_col ; update cursor color; ends with RTS
to_attr_editor ; switch to attribute editor
lda #$ff ; hide paint cursor
sta sprite_data+0+0
lda #%00111100 ; make cursor coordinates
and cursor_x ; a multiple of 4
sta cursor_x
lda #%00111100
and cursor_y
sta cursor_y
jsr ae_upd_cursor ; update cursor
lda #mode_attr_ed ; change program mode
sta program_mode
rts
toggle_brush ; toggle brush size
lda brush_size
eor #%00000001
sta brush_size
clc ; update cursor tile
adc #$30
sta sprite_data+0+1
lda brush_size ; if large brush, make coordinates even
beq + ; (makes logic a lot simpler)
lsr cursor_x
asl cursor_x
lsr cursor_y
asl cursor_y
+ jmp pm_upd_crsr_pos ; update cursor position; ends with RTS
pm_arrows ; d-pad arrow logic
lda pad_status
and #%00001111
bne +
sta delay_left ; none pressed: clear delay & exit
rts
+ lda delay_left
beq +
dec delay_left ; some pressed but delay > 0:
rts ; decrement delay & exit
+ jsr pm_horz_arrows ; some pressed and delay = 0:
jsr pm_vert_arrows ; accept input
lda #10 ; reinit delay
sta delay_left
jsr pm_upd_crsr_pos ; update cursor position
jmp pm_upd_crsr_col ; update cursor color (ends with RTS)
pm_horz_arrows ; check horizontal arrows
lda pad_status
lsr a
bcs + ; d-pad right
lsr a
bcs ++ ; d-pad left
rts
+ lda cursor_x ; cursor right
sec
adc brush_size
jmp +++
++ lda cursor_x ; cursor left
clc
sbc brush_size
+++ and #%00111111
sta cursor_x
rts
pm_vert_arrows ; check vertical arrows
lda pad_status
lsr a
lsr a
lsr a
bcs + ; d-pad down
lsr a
bcs ++ ; d-pad up
rts
+ lda cursor_y ; cursor down
sec
adc brush_size
cmp #60
bne +++
lda #0
jmp +++
++ lda cursor_y ; cursor up
clc
sbc brush_size
bpl +++
lda #60
clc
sbc brush_size
+++ sta cursor_y
rts
pm_upd_crsr_pos ; update position of paint cursor sprite
lda cursor_x ; X
asl a
asl a
sta sprite_data+0+3
lda cursor_y ; Y
asl a
asl a
sec
sbc #1
sta sprite_data+0+0
rts
pm_upd_crsr_col ; tell NMI routine to update color of paint cursor sprite
; (depends on paint_color and attribute data under cursor)
ldx paint_color ; color 0 is common to all subpalettes
beq ++
jsr get_attr_offset ; get vram_offset for attribute byte
jsr get_vramcopyadr ; get vram_copy_addr from vram_offset
jsr get_attr_bitpos ; 0/1/2/3 -> X
tax
ldy #0 ; read attribute byte
lda (vram_copy_addr),y
dex ; shift correct bit pair to pos 1-0
bmi +
- lsr a
lsr a
dex
bpl -
+ and #%00000011 ; clear other bits
asl a ; index to user palette -> X
asl a
ora paint_color
tax
++ lda user_pal,x ; cursor color
ldy #(4*4+2)
jmp to_ppu_buf_pal ; A to PPU $3f00 + Y; ends with RTS
do_paint ; edit one byte/tile in vram_copy and tell NMI routine to
; update it to VRAM
jsr get_tile_offset ; get vram_offset for name table
jsr get_vramcopyadr ; get vram_copy_addr from vram_offset
lda brush_size
beq +
ldx paint_color ; large brush; replace entire byte/tile
lda solid_tiles,x
jmp ++
+ ldy #0 ; small brush; read tile index
lda (vram_copy_addr),y
jsr replace_pixel ; replace pixel in tile index (A)
++ ldy #0 ; update byte in vram_copy
sta (vram_copy_addr),y
jmp to_ppu_buf_vram ; A -> $2000+vram_offset; ends with RTS
solid_tiles ; tiles of solid color 0-3
db %00000000, %01010101, %10101010, %11111111
get_tile_offset ; get VRAM offset for name table
; in: cursor_y = %00ABCDEF
; in: cursor_x = %00abcdef
; out: vram_offset = %000000AB %CDEabcde
lda cursor_y ; high byte
lsr a
lsr a
lsr a
lsr a
sta vram_offset+1
lda cursor_y ; low byte
and #%00001110
lsr a
lsr a
ror a
ror a
ora cursor_x
ror a
sta vram_offset+0
rts
replace_pixel ; replace a pixel (bit pair) in a tile index (A)
; Python equivalent (t = tile index, s = shift count):
; s = 6 - cursor_y * 4 - cursor_x * 2
; t &= ~(0b11 << s)
; t |= paint_color << s
pha ; store tile index
; AND mask index (%000000YX) -> X register
lda cursor_x
lsr a
lda cursor_y
rol a
and #%00000011
tax
; OR mask index (%0000YXCC) -> Y register
asl a
asl a
ora paint_color
tay
pla ; restore tile index
and pixel_and_masks,x ; clear bit pair
ora pixel_or_masks,y ; set bit pair
rts
pixel_and_masks db %00111111, %11001111, %11110011, %11111100
pixel_or_masks db %00000000, %01000000, %10000000, %11000000
db %00000000, %00010000, %00100000, %00110000
db %00000000, %00000100, %00001000, %00001100
db %00000000, %00000001, %00000010, %00000011
; --- Main loop - attribute editor (label prefix "ae_") -----------------------
attr_editor ; if any button pressed on previous frame, ignore all
lda prev_pad_status
bne +
lda pad_status ; react to buttons
bmi ae_change_pal ; A button
asl a
bmi ae_change_pal ; B button
asl a
bmi to_pal_editor ; select
asl a
asl a
bmi ae_cursor_up
asl a
bmi ae_cursor_down
asl a
bmi ae_cursor_left
bne ae_cursor_right
+ rts
to_pal_editor ; switch to palette editor
ldx #(1*4) ; hide attribute editor sprites (#1-#4)
ldy #4
jsr hide_sprites ; X = first byte index, Y = count
- lda initial_spr_dat,x ; show palette editor sprites (#5-#20)
sta sprite_data,x
inx
inx
inx
inx
cpx #(21*4)
bne -
lda #mode_pal_ed ; change program mode
sta program_mode
jmp pe_upd_crsr_spr ; update cursor sprite; ends with RTS
ae_change_pal ; decrement/increment attribute block subpalette (bit pair)
jsr get_attr_offset ; get vram_offset for attribute byte
jsr get_vramcopyadr ; get vram_copy_addr from vram_offset
jsr get_attr_bitpos ; 0/1/2/3 -> X
tax
ldy #0
lda (vram_copy_addr),y ; attribute byte to modify -> A
; add 4 to X if (LSB of bit pair set) XNOR (A pressed)
and bitpair_masks,x ; LSB of bit pair -> carry -> MSB of A
cmp #1
ror a
eor pad_status ; XOR with pad_status MSB (button A)
bmi + ; increment index if both/neither set
inx
inx
inx
inx
+ lda bitpair_masks,x ; flip LSB only or MSB too
eor (vram_copy_addr),y
sta (vram_copy_addr),y
jmp to_ppu_buf_vram ; A -> $2000 + vram_offset; ends w/ RTS
bitpair_masks ; AND/XOR masks for changing bit pairs
db %00000001, %00000100, %00010000, %01000000 ; LSB
db %00000011, %00001100, %00110000, %11000000 ; LSB & MSB
ae_cursor_up ; cursor up
lda cursor_y
sec
sbc #4
bpl +
lda #(60-4)
jmp +
ae_cursor_down ; cursor down
lda cursor_y
clc
adc #4
cmp #60
bne +
lda #0
+ sta cursor_y
jmp ae_upd_cursor ; update cursor
ae_cursor_left ; cursor left
lda cursor_x
sec
sbc #4
jmp +
ae_cursor_right ; cursor right
lda cursor_x
clc
adc #4
+ and #%00111111
sta cursor_x
; fall through
ae_upd_cursor ; update attribute editor cursor
lda cursor_x ; X position
asl a
asl a
sta sprite_data+1*4+3
sta sprite_data+3*4+3
clc
adc #8
sta sprite_data+2*4+3
sta sprite_data+4*4+3
lda cursor_y ; Y position
asl a
asl a
clc
adc #(8-1)
sta sprite_data+3*4+0
sta sprite_data+4*4+0
sec
sbc #8
sta sprite_data+1*4+0
sta sprite_data+2*4+0
rts
; --- Subs used in both paint mode and attribute editor -----------------------
get_attr_offset ; get VRAM offset for attribute byte
; in: cursor_y = %00ABCDEF
; in: cursor_x = %00abcdef
; out: vram_offset = $3c0 + %00ABCabc = %00000011 %11ABCabc
; high byte
lda #%00000011
sta vram_offset+1
; low byte
lda cursor_x
lsr a
lsr a
lsr a
tax ; %00000abc
lda cursor_y
and #%00111000 ; %00ABC000
ora attr_offset_tbl,x
sta vram_offset+0
rts
attr_offset_tbl db %11000000, %11000001, %11000010, %11000011
db %11000100, %11000101, %11000110, %11000111
get_vramcopyadr ; get address within vram_copy
; vram_copy + vram_offset -> vram_copy_addr
clc
lda #<vram_copy
adc vram_offset+0
sta vram_copy_addr+0
lda #>vram_copy
adc vram_offset+1
sta vram_copy_addr+1
rts
get_attr_bitpos ; get position within attribute byte
; in: cursor_y = %00ABCDEF
; in: cursor_x = %00abcdef
; out: A = %000000Dd
lda cursor_y
and #%00000100
lsr a
lsr a
pha ; %0000000D
lda cursor_x
and #%00000100 ; %00000d00
cmp #%00000100 ; %d -> carry
pla
rol a
rts
; --- Main loop - palette editor (label prefix "pe_") -------------------------
palette_editor ; if any button pressed on previous frame, ignore all
lda prev_pad_status
bne +
lda pad_status ; react to buttons
bmi pe_inc_16s ; A button
asl a
bmi pe_dec_16s ; B button
asl a
bmi to_paint_mode ; select
asl a
bmi pe_inc_subpal ; start
asl a
bmi pe_cursor_up
asl a
bmi pe_cursor_down
asl a
bmi pe_dec_ones
bne pe_inc_ones
+ rts
pe_inc_16s ; increment sixteens of color at cursor
jsr get_usrpal_offs ; offset -> A
tax
lda user_pal,x
clc
adc #$10
jmp +
pe_dec_16s ; decrement sixteens of color at cursor
jsr get_usrpal_offs ; offset -> A
tax
lda user_pal,x
sec
sbc #$10
+ and #%00111111
sta user_pal,x
jsr pe_upd_col_dgts ; update color digits
jmp pe_upd_palette ; update palette; ends with RTS
to_paint_mode ; switch to paint mode
ldx #(5*4) ; hide palette editor sprites (#5-#20)
ldy #16
jsr hide_sprites ; X = first byte index, Y = count
jsr pm_upd_crsr_pos ; update cursor position
jsr pm_upd_crsr_col ; update cursor color
lda #mode_paint
sta program_mode
rts ; return to main loop
pe_inc_subpal ; increment selected subpalette (0 -> 1 -> 2 -> 3 -> 0)
ldx pal_ed_subpal
inx
txa
and #%00000011
sta pal_ed_subpal
clc ; update tile of selected subpalette
adc #$36
sta sprite_data+8*4+1
jsr pe_upd_col_dgts ; update color digits
jmp pe_upd_palette ; update palette; ends with RTS
pe_cursor_up ; move cursor up
ldx pal_ed_crsr_pos
dex
jmp +
pe_cursor_down ; move cursor down
ldx pal_ed_crsr_pos
inx
+ txa
and #%00000011
sta pal_ed_crsr_pos
bpl pe_upd_crsr_spr ; update sprite; ends with RTS
pe_dec_ones ; decrement ones of color at cursor
jsr get_usrpal_offs ; offset -> X
tax
lda user_pal,x ; decremented color -> A