Skip to content

Instantly share code, notes, and snippets.

@ISSOtm
Last active February 20, 2021 23:39
Show Gist options
  • Save ISSOtm/d0005f1b653e19c1c39f80756521fabb to your computer and use it in GitHub Desktop.
Save ISSOtm/d0005f1b653e19c1c39f80756521fabb to your computer and use it in GitHub Desktop.
CRC self-test code for Game Boy
INCLUDE "defines.asm"
IF !DEF(BANK_BYTE)
BANK_BYTE equ 7 ; Plan for the worst case
ENDC
IF BANK_BYTE > 8
FAIL "Unknown bank byte {BANK_BYTE}"
ENDC
NB_ROM_BANKS equ 2 << BANK_BYTE
SCREEN_AREA_WIDTH equ 16
SCREEN_AREA_HEIGHT equ 16
IF NB_ROM_BANKS > SCREEN_AREA_WIDTH * SCREEN_AREA_HEIGHT
FAIL "Impossible to display all {d:NB_ROM_BANKS} ROM bank CRCs!"
ENDC
CRC_POLYNOM equ $1021
SECTION UNION "9C00 tilemap", VRAM[_SCRN1],BANK[0]
wCRCLowTable:
ds 256
wCRCHighTable:
ds 256
wCRCSuccess:
ds NB_ROM_BANKS
SECTION "CRC check", ROM0[$150]
CRCs:
ds 2 * NB_ROM_BANKS ; The CRC16 check values will go here (in continuity with the two checksum bytes), **LITTLE-ENDIAN**
; Performs CRC verification
CheckCRC::
; We know we're at VBlank, so turn the LCD off, as we're using VRAM for work RAM
xor a
ldh [rLCDC], a
; xor a
ldh [rNR52], a ; Also kill the APU
ld a, AUDENA_ON
ldh [rNR52], a
ld a, AUDTERM_1_LEFT | AUDTERM_1_RIGHT ; Enable CH4 on all sides
ldh [rNR51], a
ld a, $77 ; Max volume on both sides, disabling VIN
ldh [rNR50], a
xor a
ldh [rNR11], a
; xor a
ldh [rNR13], a
ld a, $F0
ldh [rNR12], a
; Compute CRC table to speed up later calculations
ld de, wCRCHighTable
ld a, $8F
ldh [rNR14], a
.computeCRCTable
ld h, e
ld l, 0
ld c, 8
.bitLoop
add hl, hl
jr nc, .bitClear
ld a, l
xor LOW(CRC_POLYNOM)
ld l, a
ld a, h
xor HIGH(CRC_POLYNOM)
ld h, a
.bitClear
dec c
jr nz, .bitLoop
dec d
ld a, l
ld [de], a
inc d
ld a, h
ld [de], a
inc e
jr nz, .computeCRCTable
ld a, $80
ldh [rNR11], a
ld hl, $0000
ld c, l ; ld c, 0 ; Low byte of pointer to result
ld a, 1 ; Load dummy bank for bank 0
.CRCLoop
ld [rROMB0], a
and $3F
or $80
ldh [rNR14], a
ld b, $FF ; CRC low
ld e, b ; lb de, HIGH(wCRCHighTable), $FF ; CRC high & pointer to table
; Skip 2-byte "skip hack" below
; Carry is known to be clear from `or $80` above
db $DC ; call c, imm16
.skipBytes
ld l, LOW(CRCs + 2)
.feedByte
; Update CRC in `eb`
ld a, [hli]
xor e ; XOR with high byte
ld e, a ; Store back
dec d ; Switch to low byte table
ld a, [de] ; Get low byte mask
xor b ; XOR with low byte
ld b, a ; Store back
inc d ; Switch to high byte table
ld a, [de] ; Get high byte mask
xor e ; XOR with high byte
ld e, a ; Store back
; Check if we should skip
ld a, h
dec a
ld a, l
jr nz, .dontSkipBytes
cp LOW($014E)
jr z, .skipBytes
.dontSkipBytes
; Check if reached end of a bank
and a
jr nz, .feedByte
ld a, h
and $3F
jr nz, .feedByte
; Now check if the CRC matches
; Get pointer to current
ld a, c
add a, LOW(CRCs) >> 1
add a, a
ld l, a
adc a, HIGH(CRCs)
sub l
ld h, a
ld a, [hli]
cp b
jr nz, .CRCFail
ld a, [hli]
sub e
jr z, .CRCSuccess ; a = 0
.CRCFail
ld a, 1
.CRCSuccess
ld b, HIGH(wCRCSuccess)
ld [bc], a
; Prepare next bank
ld hl, $4000
inc c
IF NB_ROM_BANKS != 256
ld a, c
cp NB_ROM_BANKS
ENDC
jr nz, .CRCLoop
; Now, time to display the results!
; Tile 0 = success
; Tile 1 = fail
; Tile 2 = padding
ld sp, $9030
ld de, $0000
push de
ld de, $FE00
ld c, 7
xor a
.writeTileRow
push de
dec c
jr nz, .writeTileRow
ld c, 8
cpl ; Preserves Z
ld e, a
ld d, a
and a
jr nz, .writeTileRow
; Draw results to the tilemap
ld hl, _SCRN0
ld b, SCRN_Y_B
ld de, SCRN_VX_B - SCRN_X_B
ld a, 2
.writeFillerRow
ld c, SCRN_X_B
.writeFiller
ld [hli], a
dec c
jr nz, .writeFiller
add hl, de
dec b
jr nz, .writeFillerRow
ld hl, _SCRN0 + SCRN_VX_B + 1
ld de, wCRCSuccess
ld c, LOW(NB_ROM_BANKS)
.copyResults
ld a, [de]
ld [hli], a
inc e ; inc de
ld a, l
and -SCRN_VX_B
cp SCRN_X_B - 1
jr nz, .dontSkipRow
ld a, l
add a, SCRN_VX_B - SCRN_X_B + 2
ld l, a
adc a, h
sub l
ld h, a
.dontSkipRow
dec c
jr nz, .copyResults
ld a, BCPSF_AUTOINC
ldh [rBCPS], a
ld c, LOW(rBCPD)
ld de, -$294a ; RGB555 (10;10;10)
ld hl, $294a * 3
.writeColor
ld a, l
ldh [c], a
ld a, h
ldh [c], a
add hl, de
and a
jr nz, .writeColor
ld a, LCDCF_ON | LCDCF_BGON
ldh [rLCDC], a
ld a, %11100100
ldh [rBGP], a
xor a
ldh [rSCX], a
ldh [rSCY], a
; xor a
ldh [rIE], a
ei
; xor a
ldh [rNR52], a ; Also kill the APU
halt ; Lock up
#!/usr/bin/python3
import sys
# Generate CRC table
crc_table = []
for i in range(0x100):
value = i << 8
for _ in range(8):
value <<= 1
if value & 0x10000:
value ^= 0x1021
value &= 0xFFFF
crc_table.append(value)
crc = 0
def crc_init():
global crc
crc = 0xFFFF
def crc_feed(byte):
global crc
crc ^= byte << 8
crc ^= crc_table[crc >> 8]
def crc_range(start, len):
in_file.seek(start)
for b in in_file.read(len):
crc_feed(b)
def write_crc(bank):
in_file.seek(0x150 + bank * 2)
in_file.write(crc.to_bytes(2, "little", signed=False))
with open(sys.argv[1], "rb+") as in_file:
in_file.seek(0x148)
nb_banks = 2 << in_file.read(1)[0]
for bank in range(1, nb_banks):
crc_init()
crc_range(bank * 0x4000, 0x4000)
write_crc(bank)
# Now for bank 0
crc_init()
crc_range(0, 0x14E)
crc_range(0x152, 0x4000 - 0x152)
write_crc(0)
$(BINDIR)/game.gb: $(SRCDIR)/tools/crcify.py $(OBJS)
@$(MKDIR) -p $(@D)
$(RGBLINK) $(LDFLAGS) -o $@ $(OBJS)
$(RGBFIX) $(FIXFLAGS) $@
$(RGBASM) $(ASFLAGS) -D'BANK_BYTE=$$'$$(xxd -s 0x148 -l 1 -p $@) -o $(OBJDIR)/crc.o $(SRCDIR)/crc.asm
$(RGBLINK) $(LDFLAGS) -o $@ $(OBJS) \
&& $(RGBFIX) $(FIXFLAGS) $@ \
&& $(PY) $< $@ \
&& $(RGBFIX) $(FIXFLAGS) $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment