diff --git a/BootROMs/README.md b/BootROMs/README.md new file mode 100644 index 0000000..8d95605 --- /dev/null +++ b/BootROMs/README.md @@ -0,0 +1 @@ +Open Source DMG and CGB boot Roms from [https://github.com/LIJI32/SameBoy/](https://github.com/LIJI32/SameBoy/) \ No newline at end of file diff --git a/BootROMs/SameBoyLogo.png b/BootROMs/SameBoyLogo.png new file mode 100644 index 0000000..5025cf4 Binary files /dev/null and b/BootROMs/SameBoyLogo.png differ diff --git a/BootROMs/agb_boot.asm b/BootROMs/agb_boot.asm new file mode 100644 index 0000000..95a2c78 --- /dev/null +++ b/BootROMs/agb_boot.asm @@ -0,0 +1,2 @@ +AGB EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/BootROMs/agb_boot.bin b/BootROMs/agb_boot.bin new file mode 100644 index 0000000..0e5f9ce Binary files /dev/null and b/BootROMs/agb_boot.bin differ diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm new file mode 100644 index 0000000..0472cbe --- /dev/null +++ b/BootROMs/cgb_boot.asm @@ -0,0 +1,1188 @@ +; SameBoy CGB bootstrap ROM +; Todo: use friendly names for HW registers instead of magic numbers +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + ld hl, $8000 + call ClearMemoryPage + ld a, 2 + ld c, $70 + ld [c], a +; Clear RAM Bank 2 (Like the original boot ROM + ld h, $D0 + xor a + call ClearMemoryPage + ld [c], a + +; Clear chosen input palette + ldh [InputPalette], a +; Clear title checksum + ldh [TitleChecksum], a + +; Clear OAM + ld h, $fe + ld c, $a0 +.clearOAMLoop + ldi [hl], a + dec c + jr nz, .clearOAMLoop + +; Init Audio + ld a, $80 + ldh [$26], a + ldh [$11], a + ld a, $f3 + ldh [$12], a + ldh [$25], a + ld a, $77 + ldh [$24], a + + call InitWaveform + +; Init BG palette + ld a, $fc + ldh [$47], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. +; These tiles are not used, but are required for DMG compatibility. This is done +; by the original CGB Boot ROM as well. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRowTwice + inc de + ld a, e + cp $34 ; End of logo + jr nz, .loadLogoLoop + call ReadTrademarkSymbol + +; Clear the second VRAM bank + ld a, 1 + ldh [$4F], a + xor a + ld hl, $8000 + call ClearMemoryPage + call LoadTileset + + ld b, 3 +IF DEF(FAST) + xor a + ldh [$4F], a +ELSE +; Load Tilemap + ld hl, $98C2 + ld d, 3 + ld a, 8 + +.tilemapLoop + ld c, $10 + +.tilemapRowLoop + + push af + ; Switch to second VRAM Bank + ld a, 1 + ldh [$4F], a + ld [hl], 8 + ; Switch to back first VRAM Bank + xor a + ldh [$4F], a + pop af + ldi [hl], a + add d + dec c + jr nz, .tilemapRowLoop + sub 47 + push de + ld de, $10 + add hl, de + pop de + dec b + jr nz, .tilemapLoop + + dec d + jr z, .endTilemap + dec d + + ld a, $38 + ld l, $a7 + ld bc, $0107 + jr .tilemapRowLoop +.endTilemap +ENDC + + ; Expand Palettes + ld de, AnimationColors + ld c, 8 + ld hl, BgPalettes + xor a +.expandPalettesLoop: +IF !DEF(FAST) + cpl +ENDC + ; One white + ldi [hl], a + ldi [hl], a + +IF DEF(FAST) + ; 3 more whites + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a +ELSE + ; The actual color + ld a, [de] + inc de + ldi [hl], a + ld a, [de] + inc de + ldi [hl], a + + xor a + ; Two blacks + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a +ENDC + + dec c + jr nz, .expandPalettesLoop + + ld hl, BgPalettes + call LoadBGPalettes64 + + ; Turn on LCD + ld a, $91 + ldh [$40], a + +IF !DEF(FAST) + call DoIntroAnimation + +; Wait ~0.75 seconds + ld b, 45 + call WaitBFrames + + ; Play first sound + ld a, $83 + call PlaySound + ld b, 5 + call WaitBFrames + ; Play second sound + ld a, $c1 + call PlaySound + +; Wait ~0.5 seconds + ld a, 30 + ldh [WaitLoopCounter], a + +.waitLoop + call GetInputPaletteIndex + call WaitFrame + ld hl, WaitLoopCounter + dec [hl] + jr nz, .waitLoop +ELSE + ld a, $c1 + call PlaySound +ENDC + call Preboot + +; Will be filled with NOPs + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [$50], a + +SECTION "MoreStuff", ROM0[$200] + +; Game Palettes Data +TitleChecksums: + db $00 ; Default + db $88 ; ALLEY WAY + db $16 ; YAKUMAN + db $36 ; BASEBALL, (Game and Watch 2) + db $D1 ; TENNIS + db $DB ; TETRIS + db $F2 ; QIX + db $3C ; DR.MARIO + db $8C ; RADARMISSION + db $92 ; F1RACE + db $3D ; YOSSY NO TAMAGO + db $5C ; + db $58 ; X + db $C9 ; MARIOLAND2 + db $3E ; YOSSY NO COOKIE + db $70 ; ZELDA + db $1D ; + db $59 ; + db $69 ; TETRIS FLASH + db $19 ; DONKEY KONG + db $35 ; MARIO'S PICROSS + db $A8 ; + db $14 ; POKEMON RED, (GAMEBOYCAMERA G) + db $AA ; POKEMON GREEN + db $75 ; PICROSS 2 + db $95 ; YOSSY NO PANEPON + db $99 ; KIRAKIRA KIDS + db $34 ; GAMEBOY GALLERY + db $6F ; POCKETCAMERA + db $15 ; + db $FF ; BALLOON KID + db $97 ; KINGOFTHEZOO + db $4B ; DMG FOOTBALL + db $90 ; WORLD CUP + db $17 ; OTHELLO + db $10 ; SUPER RC PRO-AM + db $39 ; DYNABLASTER + db $F7 ; BOY AND BLOB GB2 + db $F6 ; MEGAMAN + db $A2 ; STAR WARS-NOA + db $49 ; + db $4E ; WAVERACE + db $43 | $80 ; + db $68 ; LOLO2 + db $E0 ; YOSHI'S COOKIE + db $8B ; MYSTIC QUEST + db $F0 ; + db $CE ; TOPRANKINGTENNIS + db $0C ; MANSELL + db $29 ; MEGAMAN3 + db $E8 ; SPACE INVADERS + db $B7 ; GAME&WATCH + db $86 ; DONKEYKONGLAND95 + db $9A ; ASTEROIDS/MISCMD + db $52 ; STREET FIGHTER 2 + db $01 ; DEFENDER/JOUST + db $9D ; KILLERINSTINCT95 + db $71 ; TETRIS BLAST + db $9C ; PINOCCHIO + db $BD ; + db $5D ; BA.TOSHINDEN + db $6D ; NETTOU KOF 95 + db $67 ; + db $3F ; TETRIS PLUS + db $6B ; DONKEYKONGLAND 3 +; For these games, the 4th letter is taken into account +FirstChecksumWithDuplicate: + ; Let's play hangman! + db $B3 ; ???[B]???????? + db $46 ; SUP[E]R MARIOLAND + db $28 ; GOL[F] + db $A5 ; SOL[A]RSTRIKER + db $C6 ; GBW[A]RS + db $D3 ; KAE[R]UNOTAMENI + db $27 ; ???[B]???????? + db $61 ; POK[E]MON BLUE + db $18 ; DON[K]EYKONGLAND + db $66 ; GAM[E]BOY GALLERY2 + db $6A ; DON[K]EYKONGLAND 2 + db $BF ; KID[ ]ICARUS + db $0D ; TET[R]IS2 + db $F4 ; ???[-]???????? + db $B3 ; MOG[U]RANYA + db $46 ; ???[R]???????? + db $28 ; GAL[A]GA&GALAXIAN + db $A5 ; BT2[R]AGNAROKWORLD + db $C6 ; KEN[ ]GRIFFEY JR + db $D3 ; ???[I]???????? + db $27 ; MAG[N]ETIC SOCCER + db $61 ; VEG[A]S STAKES + db $18 ; ???[I]???????? + db $66 ; MIL[L]I/CENTI/PEDE + db $6A ; MAR[I]O & YOSHI + db $BF ; SOC[C]ER + db $0D ; POK[E]BOM + db $F4 ; G&W[ ]GALLERY + db $B3 ; TET[R]IS ATTACK +ChecksumsEnd: + +PalettePerChecksum: +; | $80 means game requires DMG boot tilemap + db 0 ; Default Palette + db 4 ; ALLEY WAY + db 5 ; YAKUMAN + db 35 ; BASEBALL, (Game and Watch 2) + db 34 ; TENNIS + db 3 ; TETRIS + db 31 ; QIX + db 15 ; DR.MARIO + db 10 ; RADARMISSION + db 5 ; F1RACE + db 19 ; YOSSY NO TAMAGO + db 36 ; + db 7 | $80 ; X + db 37 ; MARIOLAND2 + db 30 ; YOSSY NO COOKIE + db 44 ; ZELDA + db 21 ; + db 32 ; + db 31 ; TETRIS FLASH + db 20 ; DONKEY KONG + db 5 ; MARIO'S PICROSS + db 33 ; + db 13 ; POKEMON RED, (GAMEBOYCAMERA G) + db 14 ; POKEMON GREEN + db 5 ; PICROSS 2 + db 29 ; YOSSY NO PANEPON + db 5 ; KIRAKIRA KIDS + db 18 ; GAMEBOY GALLERY + db 9 ; POCKETCAMERA + db 3 ; + db 2 ; BALLOON KID + db 26 ; KINGOFTHEZOO + db 25 ; DMG FOOTBALL + db 25 ; WORLD CUP + db 41 ; OTHELLO + db 42 ; SUPER RC PRO-AM + db 26 ; DYNABLASTER + db 45 ; BOY AND BLOB GB2 + db 42 ; MEGAMAN + db 45 ; STAR WARS-NOA + db 36 ; + db 38 ; WAVERACE + db 26 ; + db 42 ; LOLO2 + db 30 ; YOSHI'S COOKIE + db 41 ; MYSTIC QUEST + db 34 ; + db 34 ; TOPRANKINGTENNIS + db 5 ; MANSELL + db 42 ; MEGAMAN3 + db 6 ; SPACE INVADERS + db 5 ; GAME&WATCH + db 33 ; DONKEYKONGLAND95 + db 25 ; ASTEROIDS/MISCMD + db 42 ; STREET FIGHTER 2 + db 42 ; DEFENDER/JOUST + db 40 ; KILLERINSTINCT95 + db 2 ; TETRIS BLAST + db 16 ; PINOCCHIO + db 25 ; + db 42 ; BA.TOSHINDEN + db 42 ; NETTOU KOF 95 + db 5 ; + db 0 ; TETRIS PLUS + db 39 ; DONKEYKONGLAND 3 + db 36 ; + db 22 ; SUPER MARIOLAND + db 25 ; GOLF + db 6 ; SOLARSTRIKER + db 32 ; GBWARS + db 12 ; KAERUNOTAMENI + db 36 ; + db 11 ; POKEMON BLUE + db 39 ; DONKEYKONGLAND + db 18 ; GAMEBOY GALLERY2 + db 39 ; DONKEYKONGLAND 2 + db 24 ; KID ICARUS + db 31 ; TETRIS2 + db 50 ; + db 17 ; MOGURANYA + db 46 ; + db 6 ; GALAGA&GALAXIAN + db 27 ; BT2RAGNAROKWORLD + db 0 ; KEN GRIFFEY JR + db 47 ; + db 41 ; MAGNETIC SOCCER + db 41 ; VEGAS STAKES + db 0 ; + db 0 ; MILLI/CENTI/PEDE + db 19 ; MARIO & YOSHI + db 34 ; SOCCER + db 23 ; POKEBOM + db 18 ; G&W GALLERY + db 29 ; TETRIS ATTACK + +Dups4thLetterArray: + db "BEFAARBEKEK R-URAR INAILICE R" + +; We assume the last three arrays fit in the same $100 byte page! + +PaletteCombinations: +palette_comb: MACRO ; Obj0, Obj1, Bg + db (\1) * 8, (\2) * 8, (\3) *8 +ENDM +raw_palette_comb: MACRO ; Obj0, Obj1, Bg + db (\1) * 2, (\2) * 2, (\3) * 2 +ENDM + palette_comb 4, 4, 29 + palette_comb 18, 18, 18 + palette_comb 20, 20, 20 + palette_comb 24, 24, 24 + palette_comb 9, 9, 9 + palette_comb 0, 0, 0 + palette_comb 27, 27, 27 + palette_comb 5, 5, 5 + palette_comb 12, 12, 12 + palette_comb 26, 26, 26 + palette_comb 16, 8, 8 + palette_comb 4, 28, 28 + palette_comb 4, 2, 2 + palette_comb 3, 4, 4 + palette_comb 4, 29, 29 + palette_comb 28, 4, 28 + palette_comb 2, 17, 2 + palette_comb 16, 16, 8 + palette_comb 4, 4, 7 + palette_comb 4, 4, 18 + palette_comb 4, 4, 20 + palette_comb 19, 19, 9 + raw_palette_comb 4 * 4 - 1, 4 * 4 - 1, 11 * 4 + palette_comb 17, 17, 2 + palette_comb 4, 4, 2 + palette_comb 4, 4, 3 + palette_comb 28, 28, 0 + palette_comb 3, 3, 0 + palette_comb 0, 0, 1 + palette_comb 18, 22, 18 + palette_comb 20, 22, 20 + palette_comb 24, 22, 24 + palette_comb 16, 22, 8 + palette_comb 17, 4, 13 + raw_palette_comb 28 * 4 - 1, 0 * 4, 14 * 4 + raw_palette_comb 28 * 4 - 1, 4 * 4, 15 * 4 + palette_comb 19, 22, 9 + palette_comb 16, 28, 10 + palette_comb 4, 23, 28 + palette_comb 17, 22, 2 + palette_comb 4, 0, 2 + palette_comb 4, 28, 3 + palette_comb 28, 3, 0 + palette_comb 3, 28, 4 + palette_comb 21, 28, 4 + palette_comb 3, 28, 0 + palette_comb 25, 3, 28 + palette_comb 0, 28, 8 + palette_comb 4, 3, 28 + palette_comb 28, 3, 6 + palette_comb 4, 28, 29 + ; SameBoy "Exclusives" + palette_comb 30, 30, 30 ; CGA + palette_comb 31, 31, 31 ; DMG LCD + palette_comb 28, 4, 1 + palette_comb 0, 0, 2 + +Palettes: + dw $7FFF, $32BF, $00D0, $0000 + dw $639F, $4279, $15B0, $04CB + dw $7FFF, $6E31, $454A, $0000 + dw $7FFF, $1BEF, $0200, $0000 + dw $7FFF, $421F, $1CF2, $0000 + dw $7FFF, $5294, $294A, $0000 + dw $7FFF, $03FF, $012F, $0000 + dw $7FFF, $03EF, $01D6, $0000 + dw $7FFF, $42B5, $3DC8, $0000 + dw $7E74, $03FF, $0180, $0000 + dw $67FF, $77AC, $1A13, $2D6B + dw $7ED6, $4BFF, $2175, $0000 + dw $53FF, $4A5F, $7E52, $0000 + dw $4FFF, $7ED2, $3A4C, $1CE0 + dw $03ED, $7FFF, $255F, $0000 + dw $036A, $021F, $03FF, $7FFF + dw $7FFF, $01DF, $0112, $0000 + dw $231F, $035F, $00F2, $0009 + dw $7FFF, $03EA, $011F, $0000 + dw $299F, $001A, $000C, $0000 + dw $7FFF, $027F, $001F, $0000 + dw $7FFF, $03E0, $0206, $0120 + dw $7FFF, $7EEB, $001F, $7C00 + dw $7FFF, $3FFF, $7E00, $001F + dw $7FFF, $03FF, $001F, $0000 + dw $03FF, $001F, $000C, $0000 + dw $7FFF, $033F, $0193, $0000 + dw $0000, $4200, $037F, $7FFF + dw $7FFF, $7E8C, $7C00, $0000 + dw $7FFF, $1BEF, $6180, $0000 + ; SameBoy "Exclusives" + dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1 + dw $4778, $3290, $1D87, $0861 ; DMG LCD + +KeyCombinationPalettes + db 1 ; Right + db 48 ; Left + db 5 ; Up + db 8 ; Down + db 0 ; Right + A + db 40 ; Left + A + db 43 ; Up + A + db 3 ; Down + A + db 6 ; Right + B + db 7 ; Left + B + db 28 ; Up + B + db 49 ; Down + B + ; SameBoy "Exclusives" + db 51 ; Right + A + B + db 52 ; Left + A + B + db 53 ; Up + A + B + db 54 ; Down + A + B + +TrademarkSymbol: + db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SameBoyLogo: + incbin "SameBoyLogo.rle" + +AnimationColors: + dw $7FFF ; White + dw $774F ; Cyan + dw $22C7 ; Green + dw $039F ; Yellow + dw $017D ; Orange + dw $241D ; Red + dw $6D38 ; Purple + dw $7102 ; Blue +AnimationColorsEnd: + +DMGPalettes: + dw $7FFF, $32BF, $00D0, $0000 + +; Helper Functions +DoubleBitsAndWriteRowTwice: + call .twice +.twice +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +WaitBFrames: + call GetInputPaletteIndex + call WaitFrame + dec b + jr nz, WaitBFrames + ret + +PlaySound: + ldh [$13], a + ld a, $87 + ldh [$14], a + ret + +; Clear from HL to HL | 0x2000 +ClearMemoryPage: + ldi [hl], a + bit 5, h + jr z, ClearMemoryPage + ret + +ReadTwoTileLines: + call ReadTileLine +; c = $f0 for even lines, $f for odd lines. +ReadTileLine: + ld a, [de] + and c + ld b, a + inc e + inc e + ld a, [de] + dec e + dec e + and c + swap a + or b + bit 0, c + jr z, .dontSwap + swap a +.dontSwap + inc hl + ldi [hl], a + swap c + ret + + +ReadCGBLogoHalfTile: + call .do_twice +.do_twice + call ReadTwoTileLines + inc e + ld a, e + ret + +LoadTileset: +; Copy SameBoy Logo + ld de, SameBoyLogo + ld hl, $8080 +.sameboyLogoLoop + ld a, [de] + inc de + + ld b, a + and $0f + jr z, .skipLiteral + ld c, a + +.literalLoop + ld a, [de] + ldi [hl], a + inc hl + inc de + dec c + jr nz, .literalLoop +.skipLiteral + swap b + ld a, b + and $0f + jr z, .sameboyLogoEnd + ld c, a + ld a, [de] + inc de + +.repeatLoop + ldi [hl], a + inc hl + dec c + jr nz, .repeatLoop + jr .sameboyLogoLoop + +.sameboyLogoEnd +; Copy (unresized) ROM logo + ld de, $104 +.CGBROMLogoLoop + ld c, $f0 + call ReadCGBLogoHalfTile + add a, 22 + ld e, a + call ReadCGBLogoHalfTile + sub a, 22 + ld e, a + cp $1c + jr nz, .CGBROMLogoLoop + inc hl + ; fallthrough +ReadTrademarkSymbol: + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + ret + +LoadObjPalettes: + ld c, $6A + jr LoadPalettes + +LoadBGPalettes64: + ld d, 64 + +LoadBGPalettes: + ld e, 0 + ld c, $68 + +LoadPalettes: + ld a, $80 + or e + ld [c], a + inc c +.loop + ld a, [hli] + ld [c], a + dec d + jr nz, .loop + ret + +DoIntroAnimation: + ; Animate the intro + ld a, 1 + ldh [$4F], a + ld d, 26 +.animationLoop + ld b, 2 + call WaitBFrames + ld hl, $98C0 + ld c, 3 ; Row count +.loop + ld a, [hl] + cp $F ; Already blue + jr z, .nextTile + inc [hl] + and $7 + jr z, .nextLine ; Changed a white tile, go to next line +.nextTile + inc hl + jr .loop +.nextLine + ld a, l + or $1F + ld l, a + inc hl + dec c + jr nz, .loop + dec d + jr nz, .animationLoop + ret + +Preboot: +IF !DEF(FAST) + ld b, 32 ; 32 times to fade +.fadeLoop + ld c, 32 ; 32 colors to fade + ld hl, BgPalettes +.frameLoop + push bc + call BrightenColor + pop bc + dec c + jr nz, .frameLoop + + call WaitFrame + call WaitFrame + ld hl, BgPalettes + call LoadBGPalettes64 + dec b + jr nz, .fadeLoop +ENDC + call ClearVRAMViaHDMA + ; Select the first bank + xor a + ldh [$4F], a + cpl + ldh [$00], a + call ClearVRAMViaHDMA + + ; Final values for CGB mode + ld de, $ff56 + ld l, $0d + + ld a, [$143] + bit 7, a + call z, EmulateDMG + bit 7, a + + ldh [$4C], a + ldh a, [TitleChecksum] + ld b, a + + jr z, .skipDMGForCGBCheck + ldh a, [InputPalette] + and a + jr nz, .emulateDMGForCGBGame +.skipDMGForCGBCheck +IF DEF(AGB) + ; Set registers to match the original AGB-CGB boot + ; AF = $1100, C = 0 + xor a + ld c, a + add a, $11 + ld h, c + ld b, 1 +ELSE + ; Set registers to match the original CGB boot + ; AF = $1180, C = 0 + xor a + ld c, a + ld a, $11 + ld h, c + ; B is set to the title checksum +ENDC + ret + +.emulateDMGForCGBGame + call EmulateDMG + ldh [$4C], a + ld a, $1 + ret + +EmulateDMG: + ld a, 1 + ldh [$6C], a ; DMG Emulation + call GetPaletteIndex + bit 7, a + call nz, LoadDMGTilemap + and $7F + ld b, a + ldh a, [InputPalette] + and a + jr z, .nothingDown + ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down + ld c ,a + ld b, 0 + add hl, bc + ld a, [hl] + jr .paletteFromKeys +.nothingDown + ld a, b +.paletteFromKeys + call WaitFrame + call LoadPalettesFromIndex + ld a, 4 + ; Set the final values for DMG mode + ld d, 0 + ld e, $8 + ld l, $7c + ret + +GetPaletteIndex: + ld hl, $14B + ld a, [hl] ; Old Licensee + cp $33 + jr z, .newLicensee + dec a ; 1 = Nintendo + jr nz, .notNintendo + jr .doChecksum +.newLicensee + ld l, $44 + ld a, [hli] + cp "0" + jr nz, .notNintendo + ld a, [hl] + cp "1" + jr nz, .notNintendo + +.doChecksum + ld l, $34 + ld c, $10 + xor a + +.checksumLoop + add [hl] + inc l + dec c + jr nz, .checksumLoop + ld b, a + + ; c = 0 + ld hl, TitleChecksums + +.searchLoop + ld a, l + sub LOW(ChecksumsEnd) ; use sub to zero out a + ret z + ld a, [hli] + cp b + jr nz, .searchLoop + + ; We might have a match, Do duplicate/4th letter check + ld a, l + sub FirstChecksumWithDuplicate - TitleChecksums + jr c, .match ; Does not have a duplicate, must be a match! + ; Has a duplicate; check 4th letter + push hl + ld a, l + add Dups4thLetterArray - FirstChecksumWithDuplicate - 1 ; -1 since hl was incremented + ld l, a + ld a, [hl] + pop hl + ld c, a + ld a, [$134 + 3] ; Get 4th letter + cp c + jr nz, .searchLoop ; Not a match, continue + +.match + ld a, l + add PalettePerChecksum - TitleChecksums - 1; -1 since hl was incremented + ld l, a + ld a, b + ldh [TitleChecksum], a + ld a, [hl] + ret + +.notNintendo + xor a + ret + +LoadPalettesFromIndex: ; a = index of combination + ld b, a + ; Multiply by 3 + add b + add b + + ld hl, PaletteCombinations + ld b, 0 + ld c, a + add hl, bc + + ; Obj Palettes + ld e, 0 +.loadObjPalette + ld a, [hli] + push hl + ld hl, Palettes + ld b, 0 + ld c, a + add hl, bc + ld d, 8 + call LoadObjPalettes + pop hl + bit 3, e + jr nz, .loadBGPalette + ld e, 8 + jr .loadObjPalette +.loadBGPalette + ;BG Palette + ld a, [hli] + ld hl, Palettes + ld b, 0 + ld c, a + add hl, bc + ld d, 8 + jp LoadBGPalettes + +BrightenColor: + ld a, [hli] + ld e, a + ld a, [hld] + ld d, a + ; RGB(1,1,1) + ld bc, $421 + + ; Is blue maxed? + ld a, e + and $1F + cp $1F + jr nz, .blueNotMaxed + res 0, c +.blueNotMaxed + + ; Is green maxed? + ld a, e + and $E0 + cp $E0 + jr nz, .greenNotMaxed + ld a, d + and $3 + cp $3 + jr nz, .greenNotMaxed + res 5, c +.greenNotMaxed + + ; Is red maxed? + ld a, d + and $7C + cp $7C + jr nz, .redNotMaxed + res 2, b +.redNotMaxed + + ; Add de to bc + push hl + ld h, d + ld l, e + add hl, bc + ld d, h + ld e, l + pop hl + + ld a, e + ld [hli], a + ld a, d + ld [hli], a + ret + +ClearVRAMViaHDMA: + ld hl, $FF51 + + ; Src + ld a, $88 + ld [hli], a + xor a + ld [hli], a + + ; Dest + ld a, $98 + ld [hli], a + ld a, $A0 + ld [hli], a + + ; Do it + ld [hl], $12 + ret + +GetInputPaletteIndex: + ld a, $20 ; Select directions + ldh [$00], a + ldh a, [$00] + cpl + and $F + ret z ; No direction keys pressed, no palette + push bc + ld c, 0 + +.directionLoop + inc c + rra + jr nc, .directionLoop + + ; c = 1: Right, 2: Left, 3: Up, 4: Down + + ld a, $10 ; Select buttons + ldh [$00], a + ldh a, [$00] + cpl + rla + rla + and $C + add c + ld b, a + ldh a, [InputPalette] + ld c, a + ld a, b + ldh [InputPalette], a + cp c + pop bc + ret z ; No change, don't load + ; Slide into change Animation Palette + +ChangeAnimationPalette: + push af + push hl + push bc + push de + ld hl, KeyCombinationPalettes - 1 ; Input palettes are 1-based, 0 means nothing down + ld c ,a + ld b, 0 + add hl, bc + ld a, [hl] + ld b, a + ; Multiply by 3 + add b + add b + + ld hl, PaletteCombinations + 2; Background Palette + ld b, 0 + ld c, a + add hl, bc + ld a, [hl] + ld hl, Palettes + 1 + ld b, 0 + ld c, a + add hl, bc + ld a, [hld] + cp $7F ; Is white color? + jr nz, .isWhite + inc hl + inc hl +.isWhite + push af + ld a, [hli] + push hl + ld hl, BgPalettes ; First color, all palette + call ReplaceColorInAllPalettes + pop hl + ldh [BgPalettes + 2], a ; Second color, first palette + + ld a, [hli] + push hl + ld hl, BgPalettes + 1 ; First color, all palette + call ReplaceColorInAllPalettes + pop hl + ldh [BgPalettes + 3], a ; Second color, first palette + pop af + jr z, .isNotWhite + inc hl + inc hl +.isNotWhite + ld a, [hli] + ldh [BgPalettes + 7 * 8 + 2], a ; Second color, 7th palette + ld a, [hli] + ldh [BgPalettes + 7 * 8 + 3], a ; Second color, 7th palette + ld a, [hli] + ldh [BgPalettes + 4], a ; Third color, first palette + ld a, [hl] + ldh [BgPalettes + 5], a ; Third color, first palette + + call WaitFrame + ld hl, BgPalettes + call LoadBGPalettes64 + ; Delay the wait loop while the user is selecting a palette + ld a, 30 + ldh [WaitLoopCounter], a + pop de + pop bc + pop hl + pop af + ret + +ReplaceColorInAllPalettes: + ld de, 8 + ld c, 8 +.loop + ld [hl], a + add hl, de + dec c + jr nz, .loop + ret + +LoadDMGTilemap: + push af + call WaitFrame + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + pop af + ret + +InitWaveform: + ld hl, $FF30 +; Init waveform + xor a + ld c, $10 +.waveformLoop + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop + ret + +SECTION "ROMMax", ROM0[$900] + ; Prevent us from overflowing + ds 1 + +SECTION "HRAM", HRAM[$FF80] +TitleChecksum: + ds 1 +BgPalettes: + ds 8 * 4 * 2 +InputPalette: + ds 1 +WaitLoopCounter: + ds 1 diff --git a/BootROMs/cgb_boot.bin b/BootROMs/cgb_boot.bin new file mode 100644 index 0000000..0f2a183 Binary files /dev/null and b/BootROMs/cgb_boot.bin differ diff --git a/BootROMs/cgb_boot.mif b/BootROMs/cgb_boot.mif new file mode 100644 index 0000000..c74e353 --- /dev/null +++ b/BootROMs/cgb_boot.mif @@ -0,0 +1,107 @@ +-- http://srecord.sourceforge.net/ +-- +-- Generated automatically by srec_cat -o --mif +-- +DEPTH = 2304; +WIDTH = 8; +ADDRESS_RADIX = HEX; +DATA_RADIX = HEX; +CONTENT BEGIN +0000: 31 FE FF 21 00 80 CD EF 05 3E 02 0E 70 E2 26 D0 AF CD EF 05 E2 E0 C1 E0; +0018: 80 26 FE 0E A0 22 0D 20 FC 3E 80 E0 26 E0 11 3E F3 E0 12 E0 25 3E 77 E0; +0030: 24 CD 8F 08 3E FC E0 47 11 04 01 21 10 80 1A 47 CD BA 05 13 7B FE 34 20; +0048: F5 CD 54 06 3E 01 E0 4F AF 21 00 80 CD EF 05 CD 18 06 06 03 21 C2 98 16; +0060: 03 3E 08 0E 10 F5 3E 01 E0 4F 36 08 AF E0 4F F1 22 82 0D 20 F0 D6 2F D5; +0078: 11 10 00 19 D1 05 20 E3 15 28 0A 15 3E 38 2E A7 01 07 01 18 D8 11 A2 05; +0090: 0E 08 21 81 FF AF 2F 22 22 1A 13 22 1A 13 22 AF 22 22 22 22 0D 20 EF 21; +00A8: 81 FF CD 65 06 3E 91 E0 40 CD 76 06 06 2D CD DE 05 3E 83 CD E8 05 06 05; +00C0: CD DE 05 3E C1 CD E8 05 3E 1E E0 C2 CD E3 07 CD D2 05 21 C2 FF 35 20 F4; +00D8: CD 9F 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E0 50 00 00 00 00 00 00 00 00; +0108: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +0120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +0138: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +0150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +0168: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +0180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +0198: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +01B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +01C8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +01E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +01F8: 00 00 00 00 00 00 00 00 00 88 16 36 D1 DB F2 3C 8C 92 3D 5C 58 C9 3E 70; +0210: 1D 59 69 19 35 A8 14 AA 75 95 99 34 6F 15 FF 97 4B 90 17 10 39 F7 F6 A2; +0228: 49 4E C3 68 E0 8B F0 CE 0C 29 E8 B7 86 9A 52 01 9D 71 9C BD 5D 6D 67 3F; +0240: 6B B3 46 28 A5 C6 D3 27 61 18 66 6A BF 0D F4 B3 46 28 A5 C6 D3 27 61 18; +0258: 66 6A BF 0D F4 B3 00 04 05 23 22 03 1F 0F 0A 05 13 24 87 25 1E 2C 15 20; +0270: 1F 14 05 21 0D 0E 05 1D 05 12 09 03 02 1A 19 19 29 2A 1A 2D 2A 2D 24 26; +0288: 1A 2A 1E 29 22 22 05 2A 06 05 21 19 2A 2A 28 02 10 19 2A 2A 05 00 27 24; +02A0: 16 19 06 20 0C 24 0B 27 12 27 18 1F 32 11 2E 06 1B 00 2F 29 29 00 00 13; +02B8: 22 17 12 1D 42 45 46 41 41 52 42 45 4B 45 4B 20 52 2D 55 52 41 52 20 49; +02D0: 4E 41 49 4C 49 43 45 20 52 20 20 E8 90 90 90 A0 A0 A0 C0 C0 C0 48 48 48; +02E8: 00 00 00 D8 D8 D8 28 28 28 60 60 60 D0 D0 D0 80 40 40 20 E0 E0 20 10 10; +0300: 18 20 20 20 E8 E8 E0 20 E0 10 88 10 80 80 40 20 20 38 20 20 90 20 20 A0; +0318: 98 98 48 1E 1E 58 88 88 10 20 20 10 20 20 18 E0 E0 00 18 18 00 00 00 08; +0330: 90 B0 90 A0 B0 A0 C0 B0 C0 80 B0 40 88 20 68 DE 00 70 DE 20 78 98 B0 48; +0348: 80 E0 50 20 B8 E0 88 B0 10 20 00 10 20 E0 18 E0 18 00 18 E0 20 A8 E0 20; +0360: 18 E0 00 C8 18 E0 00 E0 40 20 18 E0 E0 18 30 20 E0 E8 F0 F0 F0 F8 F8 F8; +0378: E0 20 08 00 00 10 FF 7F BF 32 D0 00 00 00 9F 63 79 42 B0 15 CB 04 FF 7F; +0390: 31 6E 4A 45 00 00 FF 7F EF 1B 00 02 00 00 FF 7F 1F 42 F2 1C 00 00 FF 7F; +03A8: 94 52 4A 29 00 00 FF 7F FF 03 2F 01 00 00 FF 7F EF 03 D6 01 00 00 FF 7F; +03C0: B5 42 C8 3D 00 00 74 7E FF 03 80 01 00 00 FF 67 AC 77 13 1A 6B 2D D6 7E; +03D8: FF 4B 75 21 00 00 FF 53 5F 4A 52 7E 00 00 FF 4F D2 7E 4C 3A E0 1C ED 03; +03F0: FF 7F 5F 25 00 00 6A 03 1F 02 FF 03 FF 7F FF 7F DF 01 12 01 00 00 1F 23; +0408: 5F 03 F2 00 09 00 FF 7F EA 03 1F 01 00 00 9F 29 1A 00 0C 00 00 00 FF 7F; +0420: 7F 02 1F 00 00 00 FF 7F E0 03 06 02 20 01 FF 7F EB 7E 1F 00 00 7C FF 7F; +0438: FF 3F 00 7E 1F 00 FF 7F FF 03 1F 00 00 00 FF 03 1F 00 0C 00 00 00 FF 7F; +0450: 3F 03 93 01 00 00 00 00 00 42 7F 03 FF 7F FF 7F 8C 7E 00 7C 00 00 FF 7F; +0468: EF 1B 80 61 00 00 FF 7F EA 7F 5F 7D 00 00 78 47 90 32 87 1D 61 08 01 30; +0480: 05 08 00 28 2B 03 06 07 1C 31 33 34 35 36 3C 42 B9 A5 B9 A5 42 3C 30 00; +0498: 58 01 03 07 0F 1F 3E 3C 7C 78 46 7C 7F 3F 1F 0F 03 00 31 3F FF 51 C0 00; +04B0: 20 0F 20 1F 32 03 07 FF 41 F8 00 32 80 E0 F0 31 30 00 4B 01 E1 E3 E7 C7; +04C8: CF DF DE FE 7C 78 00 21 07 0F 21 1F 3F 45 7B FB F3 F1 E1 FF 90 00 20 80; +04E0: 20 81 30 C1 41 C3 E3 50 F7 21 FF 7F 40 00 21 F0 F8 20 FC 20 FE 21 FF CF; +04F8: 20 C7 51 C2 80 60 00 26 03 07 0F 1F 3F 7F FF 45 EF CF 8F 0F 1F 1E 21 3E; +0510: 3C 40 00 20 CF 32 DF 9F 9E 41 BE 3F 31 7C 78 31 7F FF 40 00 30 FF 41 FE; +0528: 00 40 F8 40 00 20 F8 20 F0 60 00 50 01 50 03 50 07 30 0F 40 00 40 FF 20; +0540: E3 20 E7 40 FF 21 C3 83 21 87 FF 52 FE FC 00 23 80 C0 C3 C7 35 CF 8F 9F; +0558: 1E 9E DE 20 DF 53 8F 87 03 00 22 1F 7F FF 83 F0 C0 80 00 31 C3 FF 41 FE; +0570: 00 56 E1 F9 FC FE 7E 1F 0F 58 1F 1E 3E 7C F8 F0 E0 C0 00 21 E0 F0 24 F8; +0588: 78 3D 3F 1F 30 0F 41 1F 1E 21 3E 3C 40 00 D9 0F 1E 3C 78 F8 F0 E0 C0 80; +05A0: 00 00 FF 7F 4F 77 C7 22 9F 03 7D 01 1D 24 38 6D 02 71 FF 7F BF 32 D0 00; +05B8: 00 00 CD BD 05 3E 04 0E 00 CB 20 F5 CB 11 F1 CB 11 3D 20 F5 79 22 23 22; +05D0: 23 C9 E5 21 0F FF CB 86 CB 46 28 FC E1 C9 CD E3 07 CD D2 05 05 20 F7 C9; +05E8: E0 13 3E 87 E0 14 C9 22 CB 6C 28 FB C9 CD F8 05 1A A1 47 1C 1C 1A 1D 1D; +0600: A1 CB 37 B0 CB 41 28 02 CB 37 23 22 CB 31 C9 CD 12 06 CD F5 05 1C 7B C9; +0618: 11 96 04 21 80 80 1A 13 47 E6 0F 28 08 4F 1A 22 23 13 0D 20 F9 CB 30 78; +0630: E6 0F 28 0A 4F 1A 13 22 23 0D 20 FB 18 E0 11 04 01 0E F0 CD 0F 06 C6 16; +0648: 5F CD 0F 06 D6 16 5F FE 1C 20 EE 23 11 8E 04 0E 08 1A 13 22 23 0D 20 F9; +0660: C9 0E 6A 18 06 16 40 1E 00 0E 68 3E 80 B3 E2 0C 2A E2 15 20 FB C9 3E 01; +0678: E0 4F 16 1A 06 02 CD DE 05 21 C0 98 0E 03 7E FE 0F 28 05 34 E6 07 28 03; +0690: 23 18 F3 7D F6 1F 6F 23 0D 20 EB 15 20 DE C9 06 20 0E 20 21 81 FF C5 CD; +06A8: 9D 07 C1 0D 20 F8 CD D2 05 CD D2 05 21 81 FF CD 65 06 05 20 E4 CD D2 07; +06C0: AF E0 4F 2F E0 00 CD D2 07 11 56 FF 2E 0D FA 43 01 CB 7F CC F2 06 CB 7F; +06D8: E0 4C F0 80 47 28 05 F0 C1 A7 20 06 AF 4F 3E 11 61 C9 CD F2 06 E0 4C 3E; +06F0: 01 C9 3E 01 E0 6C CD 20 07 CB 7F C4 74 08 E6 7F; +0700: 47 F0 C1 A7 28 0A 21 7D 04 4F 06 00 09 7E 18 01 78 CD D2 05 CD 6D 07 3E; +0718: 04 16 00 1E 08 2E 7C C9 21 4B 01 7E FE 33 28 05 3D 20 40 18 0C 2E 44 2A; +0730: FE 30 20 37 7E FE 31 20 32 2E 34 0E 10 AF 86 2C 0D 20 FB 47 21 00 02 7D; +0748: D6 5E C8 2A B8 20 F8 7D D6 41 38 0E E5 7D C6 7A 6F 7E E1 4F FA 37 01 B9; +0760: 20 E5 7D C6 5D 6F 78 E0 80 7E C9 AF C9 47 80 80 21 D9 02 06 00 4F 09 1E; +0778: 00 2A E5 21 7E 03 06 00 4F 09 16 08 CD 61 06 E1 CB 5B 20 04 1E 08 18 E9; +0790: 2A 21 7E 03 06 00 4F 09 16 08 C3 67 06 2A 5F 3A 57 01 21 04 7B E6 1F FE; +07A8: 1F 20 02 CB 81 7B E6 E0 FE E0 20 09 7A E6 03 FE 03 20 02 CB A9 7A E6 7C; +07C0: FE 7C 20 02 CB 90 E5 62 6B 09 54 5D E1 7B 22 7A 22 C9 21 51 FF 3E 88 22; +07D8: AF 22 3E 98 22 3E A0 22 36 12 C9 3E 20 E0 00 F0 00 2F E6 0F C8 C5 0E 00; +07F0: 0C 1F 30 FC 3E 10 E0 00 F0 00 2F 17 17 E6 0C 81 47 F0 C1 4F 78 E0 C1 B9; +0808: C1 C8 F5 E5 C5 D5 21 7D 04 4F 06 00 09 7E 47 80 80 21 DB 02 06 00 4F 09; +0820: 7E 21 7F 03 06 00 4F 09 3A FE 7F 20 02 23 23 F5 2A E5 21 81 FF CD 69 08; +0838: E1 E0 83 2A E5 21 82 FF CD 69 08 E1 E0 84 F1 28 02 23 23 2A E0 BB 2A E0; +0850: BC 2A E0 85 7E E0 86 CD D2 05 21 81 FF CD 65 06 3E 1E E0 C2 D1 C1 E1 F1; +0868: C9 11 08 00 0E 08 77 19 0D 20 FB C9 F5 CD D2 05 3E 19 EA 10 99 21 2F 99; +0880: 0E 0C 3D 28 08 32 0D 20 F9 2E 0F 18 F5 F1 C9 21 30 FF AF 0E 10 22 2F 0D; +0898: 20 FB C9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +08B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +08C8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +08E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00; +08F8: 00 00 00 00 00 00 00 00; +END; diff --git a/BootROMs/cgb_boot_fast.asm b/BootROMs/cgb_boot_fast.asm new file mode 100644 index 0000000..cddb475 --- /dev/null +++ b/BootROMs/cgb_boot_fast.asm @@ -0,0 +1,2 @@ +FAST EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm new file mode 100644 index 0000000..6fb74fb --- /dev/null +++ b/BootROMs/dmg_boot.asm @@ -0,0 +1,153 @@ +; SameBoy DMG bootstrap ROM +; Todo: use friendly names for HW registers instead of magic numbers +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + ld hl, $8000 + +.clearVRAMLoop + ldi [hl], a + bit 5, h + jr z, .clearVRAMLoop + +; Init Audio + ld a, $80 + ldh [$26], a + ldh [$11], a + ld a, $f3 + ldh [$12], a + ldh [$25], a + ld a, $77 + ldh [$24], a + +; Init BG palette + ld a, $fc + ldh [$47], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $34 ; End of logo + jr nz, .loadLogoLoop + +; Load trademark symbol + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + +; Set up tilemap + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + + ; Turn on LCD + ld a, $91 + ldh [$40], a + +; Wait ~0.75 seconds + ld b, 45 + call WaitBFrames + + ; Play first sound + ld a, $83 + call PlaySound + ld b, 5 + call WaitBFrames + ; Play second sound + ld a, $c1 + call PlaySound + +; Wait ~1.15 seconds + ld b, 70 + call WaitBFrames + +; Set registers to match the original DMG boot + ld hl, $01B0 + push hl + pop af + ld hl, $014D + ld bc, $0013 + ld de, $00D8 + +; Boot the game + jp BootGame + + +DoubleBitsAndWriteRow: +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +WaitBFrames: + call WaitFrame + dec b + jr nz, WaitBFrames + ret + +PlaySound: + ldh [$13], a + ld a, $87 + ldh [$14], a + ret + + +TrademarkSymbol: +db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [$50], a \ No newline at end of file diff --git a/BootROMs/dmg_boot.bin b/BootROMs/dmg_boot.bin new file mode 100644 index 0000000..5f42834 Binary files /dev/null and b/BootROMs/dmg_boot.bin differ diff --git a/BootROMs/dmg_boot.mem b/BootROMs/dmg_boot.mem new file mode 100644 index 0000000..f25ee30 --- /dev/null +++ b/BootROMs/dmg_boot.mem @@ -0,0 +1,256 @@ +31 +FE +FF +21 +00 +80 +22 +CB +6C +28 +FB +3E +80 +E0 +26 +E0 +11 +3E +F3 +E0 +12 +E0 +25 +3E +77 +E0 +24 +3E +FC +E0 +47 +11 +04 +01 +21 +10 +80 +1A +47 +CD +82 +00 +CD +82 +00 +13 +7B +EE +34 +20 +F2 +11 +B1 +00 +0E +08 +1A +13 +22 +23 +0D +20 +F9 +3E +19 +EA +10 +99 +21 +2F +99 +0E +0C +3D +28 +08 +32 +0D +20 +F9 +2E +0F +18 +F5 +3E +91 +E0 +40 +06 +2D +CD +A3 +00 +3E +83 +CD +AA +00 +06 +05 +CD +A3 +00 +3E +C1 +CD +AA +00 +06 +46 +CD +A3 +00 +21 +B0 +01 +E5 +F1 +21 +4D +01 +01 +13 +00 +11 +D8 +00 +C3 +FE +00 +3E +04 +0E +00 +CB +20 +F5 +CB +11 +F1 +CB +11 +3D +20 +F5 +79 +22 +23 +22 +23 +C9 +E5 +21 +0F +FF +CB +86 +CB +46 +28 +FC +E1 +C9 +CD +97 +00 +05 +20 +FA +C9 +E0 +13 +3E +87 +E0 +14 +C9 +3C +42 +B9 +A5 +B9 +A5 +42 +3C +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +E0 +50 \ No newline at end of file diff --git a/BootROMs/logo-compress.c b/BootROMs/logo-compress.c new file mode 100644 index 0000000..2274eb2 --- /dev/null +++ b/BootROMs/logo-compress.c @@ -0,0 +1,62 @@ +#include +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +void pair(size_t count, uint8_t byte) +{ + static size_t unique_count = 0; + static uint8_t unique_data[15]; + if (count == 1) { + unique_data[unique_count++] = byte; + assert(unique_count <= 15); + } + else { + assert(count <= 15); + uint8_t control = (count << 4) | unique_count; + putchar(control); + + for (size_t i = 0; i < unique_count; i++) { + putchar(unique_data[i]); + } + + if (count != 0) { + putchar(byte); + } + else { + assert(control == 0); + } + + unique_count = 0; + } +} + +int main(int argc, char *argv[]) +{ + size_t count = 1; + uint8_t byte = getchar(); + int new; + size_t position = 0; + +#ifdef _WIN32 + _setmode(0,_O_BINARY); + _setmode(1,_O_BINARY); +#endif + + while ((new = getchar()) != EOF) { + if (byte == new) { + count++; + } + else { + pair(count, byte); + byte = new; + count = 1; + } + } + + pair(count, byte); + pair(0, 0); +} diff --git a/BootROMs/sgb2_boot.asm b/BootROMs/sgb2_boot.asm new file mode 100644 index 0000000..1c3d858 --- /dev/null +++ b/BootROMs/sgb2_boot.asm @@ -0,0 +1,2 @@ +SGB2 EQU 1 +include "sgb_boot.asm" \ No newline at end of file diff --git a/BootROMs/sgb2_boot.bin b/BootROMs/sgb2_boot.bin new file mode 100644 index 0000000..7b2cf45 Binary files /dev/null and b/BootROMs/sgb2_boot.bin differ diff --git a/BootROMs/sgb_boot.asm b/BootROMs/sgb_boot.asm new file mode 100644 index 0000000..cdb9d77 --- /dev/null +++ b/BootROMs/sgb_boot.asm @@ -0,0 +1,213 @@ +; SameBoy SGB bootstrap ROM +; Todo: use friendly names for HW registers instead of magic numbers +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + ld hl, $8000 + +.clearVRAMLoop + ldi [hl], a + bit 5, h + jr z, .clearVRAMLoop + +; Init Audio + ld a, $80 + ldh [$26], a + ldh [$11], a + ld a, $f3 + ldh [$12], a + ldh [$25], a + ld a, $77 + ldh [$24], a + +; Init BG palette to white + ld a, $0 + ldh [$47], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $34 ; End of logo + jr nz, .loadLogoLoop + +; Load trademark symbol + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + +; Set up tilemap + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + + ; Turn on LCD + ld a, $91 + ldh [$40], a + + ld a, $f1 ; Packet magic, increases by 2 for every packet + ldh [$80], a + ld hl, $104 ; Header start + + xor a + ld c, a ; JOYP + +.sendCommand + xor a + ld [c], a + ld a, $30 + ld [c], a + + ldh a, [$80] + call SendByte + push hl + ld b, $e + ld d, 0 + +.checksumLoop + call ReadHeaderByte + add d + ld d, a + dec b + jr nz, .checksumLoop + + ; Send checksum + call SendByte + pop hl + + ld b, $e +.sendLoop + call ReadHeaderByte + call SendByte + dec b + jr nz, .sendLoop + + ; Done bit + ld a, $20 + ld [c], a + ld a, $30 + ld [c], a + + ; Update command + ldh a, [$80] + add 2 + ldh [$80], a + + ld a, $58 + cp l + jr nz, .sendCommand + + ; Write to sound registers for DMG compatibility + ld c, $13 + ld a, $c1 + ld [c], a + inc c + ld a, 7 + ld [c], a + + ; Init BG palette + ld a, $fc + ldh [$47], a + +; Set registers to match the original SGB boot +IF DEF(SGB2) + ld a, $FF +ELSE + ld a, 1 +ENDC + ld hl, $c060 + +; Boot the game + jp BootGame + +ReadHeaderByte: + ld a, $4F + cp l + jr c, .zero + ld a, [hli] + ret +.zero: + inc hl + xor a + ret + +SendByte: + ld e, a + ld d, 8 +.loop + ld a, $10 + rr e + jr c, .zeroBit + add a ; 10 -> 20 +.zeroBit + ld [c], a + ld a, $30 + ld [c], a + dec d + ret z + jr .loop + +DoubleBitsAndWriteRow: +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +TrademarkSymbol: +db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [$50], a \ No newline at end of file diff --git a/BootROMs/sgb_boot.bin b/BootROMs/sgb_boot.bin new file mode 100644 index 0000000..553a7ff Binary files /dev/null and b/BootROMs/sgb_boot.bin differ diff --git a/Gameboy.qsf b/Gameboy.qsf index 72dde82..cba1d06 100644 --- a/Gameboy.qsf +++ b/Gameboy.qsf @@ -351,6 +351,7 @@ set_location_assignment PIN_W20 -to SW[3] set_global_assignment -name PRE_FLOW_SCRIPT_FILE "quartus_sh:sys/build_id.tcl" +set_global_assignment -name VERILOG_FILE fast_boot_rom.v set_global_assignment -name CDF_FILE jtag.cdf set_global_assignment -name QIP_FILE sys/sys.qip set_global_assignment -name QIP_FILE t80/T80.qip diff --git a/Gameboy.sv b/Gameboy.sv index 970fa13..5daec91 100644 --- a/Gameboy.sv +++ b/Gameboy.sv @@ -172,6 +172,8 @@ localparam CONF_STR5 = { "O34,Aspect ratio,4:3,10:9,16:9;", "O78,Stereo mix,none,25%,50%,100%;", "-;", + "O2,Boot,Normal,Fast;", + "-;", "R0,Reset;", "J1,A,B,Select,Start;", "V,v",`BUILD_DATE @@ -536,7 +538,7 @@ gb gb ( .ce ( ce_cpu ), // the whole gameboy runs on 4mhnz .ce_2x ( ce_cpu2x ), // ~8MHz in dualspeed mode (GBC) - .fast_boot ( 0 ), + .fast_boot ( status[2] ), .joystick ( joystick ), .isGBC ( isGBC ), .isGBC_game ( isGBC_game ), @@ -635,7 +637,7 @@ end wire [7:0] bios_do; wire [11:0] bios_addr; -dpram_dif #(12,8,11,16) boot_rom_gbc ( +dpram_dif #(12,8,11,16,"BootROMs/cgb_boot.mif") boot_rom_gbc ( .clock (clk_sys), .address_a (bios_addr), diff --git a/ReadMe.md b/ReadMe.md index 75742bb..78f077c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -5,8 +5,8 @@ This is port of [Gameboy for MiST](https://github.com/mist-devel/mist-board/tree * Place RBF file into root of SD card. * Place *.gb files into Gameboy folder. -## Gameboy Color Support -Place the Gameboy color bios/bootrom into the Gameboy folder and rename it to boot1.rom +## Open Source Bootstrap roms +This now includes the open source boot ROMs from [https://github.com/LIJI32/SameBoy/](https://github.com/LIJI32/SameBoy/) (for maximum GBC compatibility/authenticity you can still place the Gameboy color bios/bootrom into the Gameboy folder and rename it to boot1.rom) ## Palettes Core supports custom palettes (*.gbp) which should be places into Gameboy folder. Some examples are available in palettes folder. diff --git a/boot_rom.vhd b/boot_rom.vhd index b30f54d..68c7a73 100644 --- a/boot_rom.vhd +++ b/boot_rom.vhd @@ -23,7 +23,7 @@ architecture prom of boot_rom is X"06",X"1E",X"C1",X"FE",X"64",X"20",X"06",X"7B",X"E2",X"0C",X"3E",X"87",X"E2",X"F0",X"42",X"90", X"E0",X"42",X"15",X"20",X"D2",X"05",X"20",X"64",X"16",X"20",X"18",X"CB",X"E0",X"40",X"18",X"5C", X"06",X"04",X"C5",X"CB",X"11",X"17",X"C1",X"CB",X"11",X"17",X"05",X"20",X"F5",X"22",X"22",X"22", - X"22",X"C9",X"3C",X"42",X"A5",X"81",X"A5",X"99",X"42",X"3C",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF", + X"22",X"C9",X"3C",X"42",X"B9",X"A5",X"B9",X"A5",X"42",X"3C",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF", X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF", X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF", X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF",X"FF", diff --git a/fast_boot_rom.v b/fast_boot_rom.v new file mode 100644 index 0000000..8be16ad --- /dev/null +++ b/fast_boot_rom.v @@ -0,0 +1,19 @@ + +module fast_boot_rom( + input clk, + input [7:0] addr, + output reg [7:0] data +); + +reg [7:0] rom_data [255:0]; + +initial begin + $readmemh("BootROMs/dmg_boot.mem", rom_data); +end + +always @(posedge clk) +begin + data <= rom_data[addr]; +end + +endmodule diff --git a/gb.v b/gb.v index 5911f75..e00f5ce 100644 --- a/gb.v +++ b/gb.v @@ -590,7 +590,7 @@ wire [7:0] rom_do = isGBC? //GameBoy Color? ((cpu_addr[14:9] == 6'h00) || (hdma_rd&& hdma_source_addr[14:9] == 6'h00))? cart_do: //100-1FF Cart Header (((cpu_addr[14:12] == 3'h0) || (hdma_rd&& hdma_source_addr[14:12] == 3'h0)) && boot_rom_enabled)?gbc_bios_do: //200-8FF bootrom 2nd part cart_do: //rest of card - ((cpu_addr[14:8] == 7'h00) && boot_rom_enabled)?boot_rom_do:cart_do; //GB + ((cpu_addr[14:8] == 7'h00) && boot_rom_enabled)?fast_boot?fast_boot_rom_do:boot_rom_do:cart_do; //GB wire is_dma_cart_addr = (dma_sel_rom || dma_sel_cram); //rom or external ram @@ -610,4 +610,11 @@ boot_rom boot_rom ( .data ( boot_rom_do ) ); +wire [7:0] fast_boot_rom_do; +fast_boot_rom fast_boot_rom ( + .addr ( cpu_addr[7:0] ), + .clk ( clk ), + .data ( fast_boot_rom_do ) +); + endmodule diff --git a/t80/T80.vhd b/t80/T80.vhd index 4fb4f20..0ed5250 100644 --- a/t80/T80.vhd +++ b/t80/T80.vhd @@ -375,7 +375,7 @@ begin MCycles <= "000"; DO <= "00000000"; - ACC <= (others => '1'); + ACC <= (others => '0'); if Mode = 3 then F <= "11110000"; else