Last active
February 6, 2021 23:28
-
-
Save daltonclaybrook/6218ec624ce0d8c524fda7fe11139f47 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
include "hardware.inc" ; https://github.com/gbdev/hardware.inc | |
; Constants | |
LCD_ON_BIT EQU 7 | |
LCD_BG_TILE_DATA_BIT EQU 4 | |
RUBY_TOP_VRAM EQU _VRAM | |
; Define a 2-byte RGB color | |
; | |
; /1 = R, /2 = G, /3 = B | |
; Each color component is 5 bits (0-31) | |
; `RGB 31, 31, 31` == white | |
RGB: MACRO | |
dw (\3 << 10 | \2 << 5 | \1) | |
ENDM | |
section "VBlank Interrupt", rom0[$40] | |
reti | |
section "STAT Interrupt", rom0[$48] | |
jp CheckHBlank | |
section "Header", rom0[$100] | |
; Execution always begins at $100, but the header starts at $104 | |
; so we need to jump quickly to another location. | |
EntryPoint:: | |
di | |
jp Start | |
; The cartridge header is located here, which contains things like | |
; the game title, Nintendo logo, and GBC support flag. | |
; This instruction fills this space with zeros, and the `rgbfix` | |
; program will overwrite with the correct values later. | |
ds $150 - $104 | |
section "Main", rom0[$150] | |
Start:: | |
; At this moment, the `a` register contains a special value if we're running on a CGB, so | |
; we preserve this state by pushing it onto the stack | |
ld b, a | |
push bc ; preserve b which contains CGB state | |
call DisableLCD | |
pop bc ; reload bc | |
call LoadPalette | |
call CopyRubyTopIntoVRAM | |
call CopyRubyBottomIntoVRAM | |
call CopyTopTilesToBGMap | |
call CopyBottomTilesToBGMap | |
call EnableSTATInterrupt | |
call EnableLCD | |
.gameLoop | |
halt | |
jr .gameLoop | |
DisableLCD:: | |
ld b, SCRN_Y ; Game Boy screen is 144 pixels tall. Any value over this and we're in V-Blank period. | |
.loop | |
ld a, [rLY] ; read y coordinate register | |
cp b | |
jr c, .loop ; if no carry occurs, we're in the vblank period | |
ld a, [rLCDC] | |
res LCD_ON_BIT, a ; turn off LCD | |
ld [rLCDC], a | |
ret | |
EnableLCD:: | |
ld a, [rLCDC] | |
set LCD_ON_BIT, a ; turn on LCD | |
ld [rLCDC], a | |
ret | |
EnableSTATInterrupt:: | |
ld a, %00001000 ; H-blank interrupt on stat register | |
ld [rSTAT], a | |
ld a, %00000011 ; LCD STAT & VBlank interrupts | |
ld [rIE], a ; enable interrupts | |
reti ; return and enable interrupts | |
LoadPalette:: | |
ld a, $11 ; a value of $11 indicates CGB | |
cp b ; if a == b, this is a CGB | |
jp z, LoadCGBPalette | |
jp LoadDMGPalette | |
LoadCGBPalette:: | |
ld a, %10000000 ; 7th bit is set to tell palette register to auto-increment. | |
ld [rBCPS], a | |
ld de, rBCPD | |
ld hl, Palette | |
ld c, 8 ; 8 bytes per palette, 2 bytes per color | |
.loop | |
ld a, [hli] | |
ld [de], a ; we're writing to this same register over and over. Under the hood, the palette index is being auto-incremented | |
dec c | |
jr nz, .loop | |
ret | |
LoadDMGPalette:: | |
; ld a, %00011011 ; black, dark gray, light gray, white | |
ld a, %11100100 ; white, light gray, dark gray, black | |
ld [rBGP], a | |
ret | |
CopyRubyTopIntoVRAM:: | |
xor a | |
ld hl, RUBY_TOP_VRAM | |
ld de, RubyTop | |
ld bc, RubyTopEnd - RubyTop ; count down | |
call StartVRAMCopy | |
ret | |
; The tile data at $8800-$97FF is accessed differently than the one at $8000-$8FFF | |
; BG map tile index 0 refers to the tile at $9000. Indexes over 127 wrap around to | |
; tiles $8800 and above. This function follows these same rules by copying the first | |
; tile into $9000 and eventually wrapping around. | |
CopyRubyBottomIntoVRAM:: | |
xor a ; VRAM bank 0 | |
ld hl, $9000 ; the first destination for ruby tiles | |
ld de, RubyBottom | |
ld bc, $800 ; the counter to use in the first copy. This is 1/3rd of tile RAM. | |
call StartVRAMCopy | |
xor a ; VRAM bank 0 | |
ld hl, $8c00 ; the second destination | |
ld de, RubyBottom + $800 ; start where we left off | |
ld bc, RubyBottomEnd - (RubyBottom + $800) ; the count of remaining tiles | |
call StartVRAMCopy | |
ret | |
; @param a - VRAM bank index | |
; @param de - pointer to start of bytes | |
; @param bc - count of bytes | |
; @param hl - Address of first VRAM location | |
StartVRAMCopy:: | |
ld [rVBK], a ; set the bank number to register a | |
.loop | |
ld a, [de] | |
ld [hli], a | |
inc de | |
dec bc | |
ld a, b | |
or c ; `dec bc` above does not set flags like `dec b` does (bummer). We have to do this dance to see if bc is zero. | |
jr nz, .loop | |
ret | |
; Populates the top half of the BG map with tiles. This procedure is simpler than the | |
; bottom half because of the way the tile data is stored in VRAM. | |
CopyTopTilesToBGMap:: | |
xor a ; first tile and bank number | |
ld [rVBK], a ; set the bank number to register a | |
ld hl, _SCRN0 ; start drawing to top half of screen | |
ld d, SCRN_Y / 2 ; row countdown to stop drawing. we're drawing half the screen. | |
.startNextRow | |
ld c, 20 ; 20 tiles per row | |
.startNextTile | |
ld [hli], a | |
inc a ; next tile index | |
dec c ; decrememnt row counter | |
jr nz, .startNextTile | |
dec d | |
jr z, .finish ; if row countdown is zero, we are finished drawing the screen | |
push de | |
ld de, 12 ; add 12 to hl on each loop to wrap to next line | |
add hl, de ; wrap map to next line | |
pop de | |
jr .startNextRow | |
.finish | |
ret | |
; Populates the bottom half of the BG map with tiles. This procedure is more complicated than | |
; the top half because the pointer to tile data must skip over 64 tiles that are part of the | |
; top image | |
CopyBottomTilesToBGMap:: | |
ld hl, _SCRN0 + $120 ; bottom half of the screen | |
ld b, 128 ; 128 tiles in the bottom 3rd of VRAM | |
ld d, SCRN_Y / 2 ; row countdown to stop drawing. we're drawing half the screen. | |
xor a ; start with first tile | |
.startNextRow | |
ld c, 20 ; 20 tiles per row | |
.startNextTile | |
ld [hli], a | |
inc a ; next tile index | |
dec b ; decrement tile section counter | |
jr nz, .wrapRowCheck | |
add a, 64 ; skip over 64 tiles that are part of the top image | |
.wrapRowCheck | |
dec c ; decrememnt row counter | |
jr nz, .startNextTile | |
dec d | |
jr z, .finish ; if row countdown is zero, we are finished drawing the screen | |
push de | |
ld de, 12 ; add 12 to hl on each loop to wrap to next line | |
add hl, de ; wrap map to next line | |
pop de | |
jr .startNextRow | |
.finish | |
ret | |
CheckHBlank:: | |
push af | |
push bc | |
push de | |
push hl | |
ld a, [rSTAT] | |
and %11 ; mask off mode bits and sets zero flag if result is zero | |
jr nz, .finish ; mode is not zero, finish up | |
ld a, [rLY] | |
cp 143 ; 143 is last line before vblank. Switch to top tiles. | |
jr nz, .checkMidScreen | |
ld a, [rLCDC] | |
set LCD_BG_TILE_DATA_BIT, a ; set BG tile data select bit for tile range $8000-$8fff | |
ld [rLCDC], a | |
jr .finish | |
.checkMidScreen | |
; if next line is 72 (half of screen height), switch to upper tile set | |
cp 71 ; 71 is last line of top section. Switch to bottom tiles. | |
jr nz, .finish | |
ld a, [rLCDC] | |
res LCD_BG_TILE_DATA_BIT, a ; unset BG tile data select bit for tile range $8800-$97ff | |
ld [rLCDC], a | |
.finish | |
pop hl | |
pop de | |
pop bc | |
pop af | |
reti | |
section "Data", rom0 | |
RubyTop:: | |
incbin "ruby-top.2bpp" | |
RubyTopEnd:: | |
RubyBottom:: | |
incbin "ruby-bottom.2bpp" | |
RubyBottomEnd:: | |
Palette:: | |
RGB 29, 29, 25 | |
RGB 27, 19, 23 | |
RGB 1, 19, 21 | |
RGB 17, 0, 10 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment