-
Notifications
You must be signed in to change notification settings - Fork 13
/
gingerbread.asm
1355 lines (1108 loc) · 31.5 KB
/
gingerbread.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
; GingerBread is a kind of standard library for Game Boy games written in assembly
; using the RGBDS system. It intends to provide basic functionality that almost
; every game will need in one form or another. It is meant to be used alongside the
; book "Game Boy Assembly Programming for the Modern Game Developer", available
; here: https://teamlampoil.se/book/gbasmdev.pdf
; The book also works as a form of documentation for this library.
; --- ROM Header ---
; Before importing gingerbread.asm, you can specify the following options to affect the game header
IF !DEF(GAME_NAME)
GAME_NAME EQUS "GINGERBREAD"
ENDC
IF !DEF(GBC_SUPPORT)
H_GBC_CODE EQU $0
ELSE
H_GBC_CODE EQU $80
ENDC
IF !DEF(SGB_SUPPORT)
H_SGB_CODE EQU $0
ELSE
H_SGB_CODE EQU $3
ENDC
IF !DEF(ROM_SIZE)
ROM_SIZE EQU 0
ENDC
IF !DEF(RAM_SIZE)
RAM_SIZE EQU 1
ENDC
SECTION "header",ROM0[$0104]
; "Nintendo" logo. If this is modified, the game won't start on a real Gameboy.
DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D
DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E
; The header, specifying ROM details.
DB "{GAME_NAME}" ; $134 - Title of the game, in uppercase ASCII. Should be exactly 15 characters (padded with 0s if necessary)
REPT 15-STRLEN("{GAME_NAME}")
DB 0
ENDR
DB H_GBC_CODE ; $143 - GBC functionality (0 for no, $80 for "black cart" and $C0 for GBC only)
DB 0,0 ; $144 - Licensee code (not important)
DB H_SGB_CODE ; $146 - SGB Support indicator (0 means no support, 3 means there is SGB support in the game)
DB $1B ; $147 - Cart type ($1B means MBC5 with RAM and battery save)
DB ROM_SIZE ; $148 - ROM Size, 0 means 32 kB, 1 means 64 kB and so on up to 2 MB
DB RAM_SIZE ; $149 - RAM Size, 0 means no RAM, 1 means 2 kB, 2 -> 8 kB, 3 -> 32 kB, 4 -> 128 kB
DB 1 ; $14a - Destination code (0 means Japan, 1 mean non-Japan, doesn't matter)
DB $33 ; $14b - Old licensee code, needs to be $33 for SGB to work
DB 0 ; $14c - Mask ROM version
DB 0 ; $14d - Complement check (important, RGBDS takes care of this)
DW 0 ; $14e - Checksum (not important, RGBDS takes care of this)
; --- Hardware constants ---
; General
ROM_BANK_SWITCH EQU $2000
SAVEDATA EQU $0000
MBC5_RAMB EQU $4000
; Key status
KEY_START EQU %10000000
KEY_SELECT EQU %01000000
KEY_B EQU %00100000
KEY_A EQU %00010000
KEY_DOWN EQU %00001000
KEY_UP EQU %00000100
KEY_LEFT EQU %00000010
KEY_RIGHT EQU %00000001
; Graphics palettes (monochrome)
BG_PALETTE EQU $FF47
SPRITE_PALETTE_1 EQU $FF48
SPRITE_PALETTE_2 EQU $FF49
; GBC palettes
GBC_BG_PALETTE_INDEX EQU $FF68
GBC_BG_PALETTE EQU $FF69
GBC_SPRITE_PALETTE_INDEX EQU $FF6A
GBC_SPRITE_PALETTE EQU $FF6B
GBC_VRAM_BANK_SWITCH EQU $FF4F
; Scrolling: Set these to nonzero values to scroll the screen across the 256x256 rendering surface
SCROLL_X EQU $FF43
SCROLL_Y EQU $FF42
; They see me scrollin'... They hatin'...
; Memory ranges
TILEDATA_START EQU $8000 ; up to $97FF
BACKGROUND_MAPDATA_START EQU $9800 ; up to $9BFF
WINDOW_MAPDATA_START EQU $9C00 ; up to $9FFF
SAVEDATA_START EQU $A000 ; up to $BFFF
RAM_START EQU $C000 ; up to $E000, only write to data after USER_RAM_START as GingerBread uses some RAM before this for sprites etc.
SPRITES_START EQU $C100 ; up to $C1A0
USER_RAM_START EQU $C200 ; up to $E000
HRAM_START EQU $F800 ; up to $FFFE
OAMRAM_START EQU $FE00 ; up to $FE9F
AUD3WAVERAM_START EQU $FF30 ; $FF30-$FF3F
DMACODE_START EQU $FF80
SPRITES_LENGTH EQU $A0
STATF_LYC EQU %01000000 ; LYCEQULY Coincidence (Selectable)
STATF_MODE10 EQU %00100000 ; Mode 10
STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank)
STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank)
STATF_LYCF EQU %00000100 ; Coincidence Flag
STATF_HB EQU %00000000 ; H-Blank
STATF_VB EQU %00000001 ; V-Blank
STATF_OAM EQU %00000010 ; OAM-RAM is used by system
STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system
STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe
rSTAT EQU $FF41
; Interrupts
rIF EQU $FF0F
rIE EQU $FFFF
IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13
IEF_SERIAL EQU %00001000 ; Serial I/O transfer end
IEF_TIMER EQU %00000100 ; Timer Overflow
IEF_LCDC EQU %00000010 ; LCDC
IEF_VBLANK EQU %00000001 ; V-Blank
; LCD stuff
rLCDC EQU $FF40
LCDCF_OFF EQU %00000000 ; LCD Control Operation
LCDCF_ON EQU %10000000 ; LCD Control Operation
LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select
LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select
LCDCF_WINOFF EQU %00000000 ; Window Display
LCDCF_WINON EQU %00100000 ; Window Display
LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select
LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select
LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select
LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select
LCDCF_OBJ8 EQU %00000000 ; OBJ Construction
LCDCF_OBJ16 EQU %00000100 ; OBJ Construction
LCDCF_OBJOFF EQU %00000000 ; OBJ Display
LCDCF_OBJON EQU %00000010 ; OBJ Display
LCDCF_BGOFF EQU %00000000 ; BG Display
LCDCF_BGON EQU %00000001 ; BG Display
; Sound stuff
SOUND_VOLUME EQU $FF24
SOUND_OUTPUTS EQU $FF25
SOUND_ONOFF EQU $FF26
; Channel 1 (square with sweep and enevelope effects)
SOUND_CH1_START EQU $FF10 ; bit 7: unused, bits 6-4: sweep time, bit 3: sweep frequency increase/decrease, bits 2-0: number of sweep shifts
SOUND_CH1_LENGTH EQU $FF11 ; bits 7-6: wave duty, bits 5-0: length of sound data
SOUND_CH1_ENVELOPE EQU $FF12 ; bits 7-4: start value for envelope, bit 3: envelope decrease/increase, bits 2-0: number of envelope sweeps
SOUND_CH1_LOWFREQ EQU $FF13 ; bits 7-0: lower 8 bits of the sound frequency
SOUND_CH1_HIGHFREQ EQU $FF14 ; bit 7: restart channel, bit 6: use length, bits 5-3: unused, bits 2-0: highest 3 bits of frequency
; Channel 2 (square with enevelope effect, with no sweep effect)
SOUND_CH2_START EQU $FF15 ; Not used but you can write zeroes here
SOUND_CH2_LENGTH EQU $FF16 ; bits 7-6: wave duty, bits 5-0: length of sound data
SOUND_CH2_ENVELOPE EQU $FF17 ; bits 7-4: start value for envelope, bit 3: envelope decrease/increase, bits 2-0: number of envelope sweeps
SOUND_CH2_LOWFREQ EQU $FF18 ; bits 7-0: lower 8 bits of the sound frequency
SOUND_CH2_HIGHFREQ EQU $FF19 ; bit 7: restart channel, bit 6: use length, bits 5-3: unused, bits 2-0: highest 3 bits of frequency
; Channel 3 (custom wave)
SOUND_CH3_START EQU $FF1A ; bit 7: on/off, bits 6-0: unused
SOUND_CH3_LENGTH EQU $FF1B ; bits 7-0: length of sound
SOUND_CH3_VOLUME EQU $FF1C ; bits 6-5: audio volume (%00 is mute, %01 is loudest, %10 is pretty quiet and %11 is very quiet)
SOUND_CH3_LOWFREQ EQU $FF1D ; bits 7-0: lower 8 bits of the sound frequency
SOUND_CH3_HIGHFREQ EQU $FF1E ; bit 7: restart channel, bit 6: use length, bits 5-3: unused, bits 2-0: highest 3 bits of frequency
; Channel 4 (noise)
SOUND_CH4_START EQU $FF1F ; Not used but you can write zeroes here
SOUND_CH4_LENGTH EQU $FF20 ; bits 5-0: length of sound
SOUND_CH4_ENVELOPE EQU $FF21 ; bits 7-4: start value for envelope, bit 3: envelope decrease/increase, bits 2-0: number of envelope sweeps
SOUND_CH4_POLY EQU $FF22 ; bits 7-4: polynomial counter, bit 3: number of steps (15 or 7), bits 2-0: ratio of frequency division (%000 gives highest frequency, %111 the lowest)
SOUND_CH4_OPTIONS EQU $FF23 ; bit 7: restart channel, bit 6: use length
; Wave table for Channel 3
SOUND_WAVE_TABLE_START EQU $FF30
SOUND_WAVE_TABLE_STOP EQU $FF3F
; O RLY?
rLY EQU $FF44
rDMA EQU $FF46
; --- GingerBread RAM variables ---
; GingerBread writes a few variables in RAM between $C100 and $C1FF. Let your own RAM usage start at $C200 to make sure none of your code messes with GingerBread
; $C100 - $C1A0 are used for sprites, so these start after that. The space between $C000-$C1000 is used by GBT-Player.
SECTION "GingerBread RAM variables",WRAM0[$C1A1]
RUNNING_ON_SGB: DS 1
RUNNING_ON_GBC: DS 1
; --- Standard functions ---
SECTION "GingerBreadKeypad",ROM0
; Reads current keypad status, stores into A register, where each bit corresponds to one key being pressed or not
; Keys are in the following order: Start - Select - B - A - Down - Up - Left - Right
; The constants KEY_START etc. corresponds to the values obtained here if only that key is pressed.
; The code is copied from the Gameboy Programming Manual, http://www.chrisantonellis.com/files/gameboy/gb-programming-manual.pdf
ReadKeys:
push bc
; Read D-pad
ld a, $20
ld [$FF00], a
ld a, [$FF00]
ld a, [$FF00]
cpl
and %00001111
ld b, a
; Read buttons (Start, Select, B, A)
ld a, $10
ld [$FF00], a
ld a, [$FF00]
ld a, [$FF00]
ld a, [$FF00]
ld a, [$FF00]
ld a, [$FF00]
ld a, [$FF00]
cpl
and %00001111
; Combine D-pad with buttons, store in B
swap a
or b
ld b, a
ld a, $30
ld [$FF00], a
; Return the stored result
ld a, b
pop bc
ret
Section "GingerBreadSound",ROM0
; Enables audio on all channels, at maximum output volume.
; Overwrites AF
EnableAudio:
ld a, %11111111
ld [SOUND_VOLUME], a ; Max out the audio volume
ld [SOUND_OUTPUTS], a ; Output all channels to both left/right speakers (when using headphones)
ld [SOUND_ONOFF], a ; Turn audio on
ret
; Use this if your game doesn't use audio, to save some battery life
; Overwrites AF
DisableAudio:
xor a
ld [SOUND_VOLUME], a ; Turn off the audio volume
ld [SOUND_OUTPUTS], a ; Output no channels to no left/right speakers (when using headphones)
ld [SOUND_ONOFF], a ; Turn audio off
ret
; HL should point to a table which first contains a DW with either SOUND_CH1_START, SOUND_CH2_START, SOUND_CH3_START or SOUND_CH4_START
; followed by five values to be written to those addresses (see comments by the definitions of those constants)
; Overwrites AF and HL
PlaySoundHL:
push de
; Read channel start into DE
ld a, [hl+]
ld e, a
ld a, [hl+]
ld d, a
; Read data from table and feed into the channel start
ld a, [hl+]
ld [de], a
inc de
ld a, [hl+]
ld [de], a
inc de
ld a, [hl+]
ld [de], a
inc de
ld a, [hl+]
ld [de], a
inc de
ld a, [hl]
ld [de], a
pop de
ret
Section "GingerBreadMemory",ROM0
WaitForNonBusyLCD: MACRO
ld a,[rSTAT]
and STATF_BUSY
jr nz,@-4 ; Jumps up 4 bytes in the code (two lines in this case)
ENDM
WaitForNonBusyLCDSafeA: MACRO
push af
WaitForNonBusyLCD
pop af
ENDM
; Copies data in a way that is safe to use when reading/writing to/from VRAM while LCD is on (but slower than mCopy)
; HL - memory position of the start of the copying source
; DE - memory position of the start of the copying destination
; BC - the number of bytes to be copied
mCopyVRAM:
inc b
inc c
jr .skip
.loop:
di
; This "WaitForNonBusyLCD" here, along with the disabled interrupts, makes it safe to read/write to/from VRAM when LCD is on
; Essentially, we're waiting for the LCD to be non-busy before reading/writing. If we don't do this, we can
; read/write when the LCD is busy which results in corrupted data.
WaitForNonBusyLCD
ld a, [hl+]
ld [de], a
ei
inc de
.skip:
dec c
jr nz, .loop
dec b
jr nz, .loop
ret
; Copies data in a way that is NOT safe to use when reading/writing to/from VRAM while LCD is on (but faster than mCopyVRAM)
; HL - memory position of the start of the copying source
; DE - memory position of the start of the copying destination
; BC - the number of bytes to be copied
mCopy:
inc b
inc c
jr .skip
.loop:
ld a, [hl+]
ld [de], a
inc de
.skip:
dec c
jr nz, .loop
dec b
jr nz, .loop
ret
; Sets data to a constant value in a way that is safe to use when writing to VRAM while LCD is on (but slower than mSet)
; A - constant value to set
; HL - memory position of the start of the copying destination
; BC - the number of bytes to be written
mSetVRAM:
inc b
inc c
jr .skip
.loop:
di
WaitForNonBusyLCDSafeA
ld [hl+], a
ei
.skip:
dec c
jr nz, .loop
dec b
jr nz, .loop
ret
; Sets data to a constant value in a way that is NOT safe to use when writing to VRAM while LCD is on (but faster than mSetVRAM)
; A - constant value to set
; HL - memory position of the start of the copying destination
; BC - the number of bytes to be written
mSet:
inc b
inc c
jr .skip
.loop:
ld [hl+], a
.skip:
dec c
jr nz, .loop
dec b
jr nz, .loop
ret
; --- Text and number display ---
; Draws text until a specific end character appears, using X/Y coordinates, which can be a bit slower than RenderTextToEndByPosition
; B - tile number of end character
; C - zero if drawn to background, non-zero if drawn to window
; D - X position
; E - Y position
; HL - address to start of the text to write, make sure it contains the end character somewhere
RenderTextToEnd:
; Convert position coordinates to position number
push hl
xor a
ld h, a
ld l, a
call XYtoPosition
; Put position number at DE and then restore HL
ld d, h
ld e, l
pop hl
call RenderTextToEndByPosition
ret
; Draws text until a specific end character appears, using a position number which is faster than RenderTextToEnd if the number is precomputed at compile time
; B - tile number of end character
; C - zero if drawn to background, non-zero if drawn to window
; DE - position number to start writing at
; HL - address to start of the text to write, make sure it contains the end character somewhere
RenderTextToEndByPosition:
; For now, HL will store the address to write to, which we'll have to compute
push hl
call InitializePositionForBackgroundOrWindow
add hl, de
; Move this address onto DE so we can get the text address back
ld d, h
ld e, l
pop hl
di
; Start writing
ld a, [hl]
.draw:
WaitForNonBusyLCDSafeA
ld [de], a
inc de
; Check if the next character is the end character
inc hl
ld a, [hl]
cp b
jr nz, .draw
reti
; Draws text until a certain number of characters have been written, with positions as X/Y coordinates. This might be a bit slow for repeated use every frame.
; B - number of characters to write
; C - drawing to background (0) or window (1)
; D - X position
; E - Y position
; HL - address to start of text to write
RenderTextToLength:
; Convert position coordinates to position number
push hl
xor a
ld h, a
ld l, a
call XYtoPosition
; Put position number at DE and then restore HL
ld d, h
ld e, l
pop hl
call RenderTextToLengthByPosition
ret
; Draws text until a certain number of characters have been writtens, with position numbers using the formula pos = x + y*32
; If position numbers are precomputed at compile time, this will execute faster than RenderTextToLength
; B - number of characters to write
; C - drawing to background (zero) or window (non-zero)
; DE - position number
; HL - address to start of text to write
RenderTextToLengthByPosition:
push hl
; For now, HL will store the position to write to
call InitializePositionForBackgroundOrWindow
; Add starting position onto background/window
add hl, de
; Now store this onto DE so we can get the read address back again
ld d, h
ld e, l
pop hl
di
.draw:
; Write characters until B is zero, decreasing it every time
ld a, [hl+]
WaitForNonBusyLCDSafeA ; Writing to VRAM needs to be timed
ld [de], a
inc de
dec b
; Is B zero? If so we should stop
ld a, b
cp 0
jr z, .finish
jr .draw
.finish:
ei
ret
; Internal function
; Converts X and Y coordinates to a single position number by the formula pos = x + 32*y
; D - X position
; E - Y position
; Output is added onto HL (which may be non-zero initially)
; Overwrites A
XYtoPosition:
; Addition of 16-bit numbers require a full other 16-bit number to add. So we use BC for that here
push bc
; Add X-position
ld c, d
ld b, 0
; Now BC contains the X value as a 16-bit number
add hl, bc
; Add Y-position if y>0
ld a, e
cp 0
jr z, .end
ld c, e
; Each line on the background/window is 32 tiles long, so to convert this to number of lines, we add the Y value 32 times)
REPT 32
add hl, bc
ENDR
.end:
pop bc
ret
; Draws two decimal (base 10) numbers, stored in a single 8-bit number (for example $42 would represent 42)
; A - The two numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; D - X position to write
; E - Y position to write
RenderTwoDecimalNumbers:
push af
push hl
; Reset HL
xor a
ld h, a
ld l, a
call XYtoPosition
; The ByPosition call below expects the position number (now on HL) to be on DE
ld d, h
ld e, l
pop hl
pop af
call RenderTwoDecimalNumbersByPosition
ret
; Internal function
; Sets HL to either the start of background map data or window map data, depending on C
; C - zero for background, non-zero for window
InitializePositionForBackgroundOrWindow:
ld a, c
cp 0
jr nz, .useWindow
.useBackground:
ld hl, BACKGROUND_MAPDATA_START
ret
.useWindow:
ld hl, WINDOW_MAPDATA_START
ret
; Draws two decimal (base 10) numbers, stored in a single 8-bit number (for example $42 would represent 42)
; Unlike RenderTwoDecimalNumbers, the position input here is a position number. This executes faster if the number is precomputed
; and is thus recommended if the game displays lots of text and/or numbers every frame.
; A - The two numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; DE - Position number
RenderTwoDecimalNumbersByPosition:
push hl ; Use HL for temporary storage
push af ; To store the original two numbers to write
; Set HL to base address for background/window depending on value in C
call InitializePositionForBackgroundOrWindow
.draw:
; To get the correct position, we add the position number onto HL
add hl, de
pop af
; We don't need C anymore so we can use it to temporarily store the two numbers to write
ld c, a
; Get the leftmost number first
and %11110000
swap a
; Convert to tile number
add b
di
; Write the number
WaitForNonBusyLCDSafeA
ld [hl+], a
; Get the rightmost number
ld a, c
and %00001111
; Convert to tile number
add b
; Write the number
WaitForNonBusyLCDSafeA
ld [hl], a
pop hl
reti
; Draws four decimal (base 10) numbers, stored in a 16-bit number (for example $1234 would represent 1234)
; HL - The four numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; D - X position to write
; E - Y position to write
RenderFourDecimalNumbers:
; Write the leftmost numbers first
ld a, h
push bc
push de
push hl
call RenderTwoDecimalNumbers
pop hl
pop de
pop bc
; Move "x" two steps to the right
inc d
inc d
; Then draw the rightmost numbers
ld a, l
call RenderTwoDecimalNumbers
ret
; Draws four decimal (base 10) numbers, stored in a 16-bit number (for example $1234 would represent 1234)
; Unlike RenderFourDecimalNumbers, this function uses position numbers computed by pos = x + 32*y which will be
; faster if this number is precomputed.
; HL - The four numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; DE - Position number of first number
RenderFourDecimalNumbersByPosition:
ld a, h ; The leftmost two numbers
push bc
push de
push hl
call RenderTwoDecimalNumbersByPosition
pop hl
pop de
pop bc
; Move position two steps to the right
inc de
inc de
ld a, l ; The rightmost two numbers
call RenderTwoDecimalNumbersByPosition
ret
; --- Save data ---
; Allows save data to become accessible to read and write. Note that save data is disabled by default. It also must be supported by
; your cartidge and game header for this to work.
EnableSaveData:
ld a, $0A
ld [SAVEDATA], a
ret
; Disables save data. Do this as soon as you are done using SRAM, to prevent data loss in case of a crash.
DisableSaveData:
xor a
ld [SAVEDATA], a
ret
; Assuming your game uses MBC5, having different numbers on A (between $00 and $0F) will activate different
; save data banks. Do this before running EnableSaveData.
ChooseSaveDataBank:
ld [MBC5_RAMB], a
ret
; --- Game Boy Color functionality ---
IF DEF(GBC_SUPPORT)
SECTION "GBC commands",ROM0
GBCEarlyExit: MACRO
ld a, [RUNNING_ON_GBC]
cp 0
ret z
ENDM
; HL - address pointing to a table of colors
; A - palette byte to start writing at (each palette is eight bytes, two for each of the four colors, and there are eight palettes)
; B - number of bytes to write
GBCApplyBackgroundPalettes:
; We use auto-increment for simplicity
or %10000000
ld [GBC_BG_PALETTE_INDEX], a
.writeByte:
ld a, [hl]
ld [GBC_BG_PALETTE], a
inc hl
; Have we finished writing?
dec b
ld a, b
cp 0 ; Is B equal to zero?
ret z
jr .writeByte ; keep going
; HL - address pointing to a table of colors
; A - palette byte to start writing at (each palette is eight bytes, two for each of the four colors, and there are eight palettes)
; B - number of bytes to write
GBCApplySpritePalettes:
; We use auto-increment for simplicity
or %10000000
ld [GBC_SPRITE_PALETTE_INDEX], a
di ; To prevent an interrupt for interrupting while we try to write to VRAM in the limited time we have
.writeByte:
WaitForNonBusyLCD ; Unlike background palettes, sprite palettes can only be written during H-Blank
ld a, [hl]
ld [GBC_SPRITE_PALETTE], a
inc hl
; Have we finished writing?
dec b
ld a, b
cp 0 ; Is B equal to zero?
jr z, .finish
jr .writeByte ; keep going
.finish:
ei
ret
ENDC ; End of GBC functionality
; --- Super Game Boy functionality ---
IF DEF(SGB_SUPPORT)
SECTION "SGB Messages",ROMX,BANK[1]
SGB_OUT_ADDRESS EQU $FF00
SGB_SEND_ZERO EQU %00100000
SGB_SEND_ONE EQU %00010000
SGB_SEND_RESET EQU %00000000
SGB_SEND_NULL EQU %00110000
SGB_FREEZE:
DB %10111001 ; MASK_EN command, length one
DB 1 ; Freeze current image
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
SGB_UNFREEZE:
DB %10111001 ; MASK_EN command, length one
DB 0 ; Unfreeze
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
SGB_MLTREQ1:
DB %10001001
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
SGB_MLTREQ2:
DB %10001001
DB 1
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
SGB_VRAMTRANS_TILEDATA1:
DB %10011001 ; CHR_TRN, length one
DB 0 ; lower tiles (we can have another set of 128 tiles by setting this to one)
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
SGB_VRAMTRANS_TILEDATA2:
DB %10011001 ; CHR_TRN, length one
DB 1 ; upper tiles
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
SGB_VRAMTRANS_TILEMAP:
DB %10100001 ; PCT_TRN, length one
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
; According to some sources, these commands should be sent to the SGB as an initialization. It doesn't seem mandatory though?
SGB_INIT1:
DB $79,$5D,$08,$00,$0B,$8C,$D0,$F4,$60,$00,$00,$00,$00,$00,$00,$00
SGB_INIT2:
DB $79,$52,$08,$00,$0B,$A9,$E7,$9F,$01,$C0,$7E,$E8,$E8,$E8,$E8,$E0
SGB_INIT3:
DB $79,$47,$08,$00,$0B,$C4,$D0,$16,$A5,$CB,$C9,$05,$D0,$10,$A2,$28
SGB_INIT4:
DB $79,$3C,$08,$00,$0B,$F0,$12,$A5,$C9,$C9,$C8,$D0,$1C,$A5,$CA,$C9
SGB_INIT5:
DB $79,$31,$08,$00,$0B,$0C,$A5,$CA,$C9,$7E,$D0,$06,$A5,$CB,$C9,$7E
SGB_INIT6:
DB $79,$26,$08,$00,$0B,$39,$CD,$48,$0C,$D0,$34,$A5,$C9,$C9,$80,$D0
SGB_INIT7:
DB $79,$1B,$08,$00,$0B,$EA,$EA,$EA,$EA,$EA,$A9,$01,$CD,$4F,$0C,$D0
SGB_INIT8:
DB $79,$10,$08,$00,$0B,$4C,$20,$08,$EA,$EA,$EA,$EA,$EA,$60,$EA,$EA
SECTION "SGB commands on bank0",ROM0
SGBAbsolutelyFirstInit:
call SGBStrangeInit
ret
CheckIfSGB:
call CheckSGB
jr nc, .CISGB_notSGB
; If we get here, then we are running SGB
ld a, 1
ld [RUNNING_ON_SGB], a
jr .CISGB_end
.CISGB_notSGB
xor a
ld [RUNNING_ON_SGB], a
jr .CISGB_end
.CISGB_end
ret
SGBEarlyExit: MACRO
ld a, [RUNNING_ON_SGB]
cp 0
ret z
ENDM
SECTION "SGB commands on bank1",ROMX,BANK[1]
SGBStrangeInit:
ld hl, SGB_INIT1
call SGBSendData
ld hl, SGB_INIT2
call SGBSendData
ld hl, SGB_INIT3
call SGBSendData
ld hl, SGB_INIT4
call SGBSendData
ld hl, SGB_INIT5
call SGBSendData
ld hl, SGB_INIT6
call SGBSendData