commit f1e962337103d66716346d8f0f20d0c3e508db91 Author: Lior Halphon Date: Wed Mar 30 23:07:55 2016 +0300 Initial public commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/BootROMs/SameboyLogo.1bpp b/BootROMs/SameboyLogo.1bpp new file mode 100644 index 0000000..b219f7d Binary files /dev/null and b/BootROMs/SameboyLogo.1bpp differ diff --git a/BootROMs/SameboyLogo.png b/BootROMs/SameboyLogo.png new file mode 100644 index 0000000..4bc9706 Binary files /dev/null and b/BootROMs/SameboyLogo.png differ diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm new file mode 100644 index 0000000..17fe37c --- /dev/null +++ b/BootROMs/cgb_boot.asm @@ -0,0 +1,1087 @@ +; Sameboy DMG bootstrap ROM +; Todo: use friendly names for HW registers instead of magic numbers +; Todo: add support for games that assume DMG boot logo (Such as X), like the +; original boot ROM. +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Select RAM bank + ld a, 2 + ldh [$70], a + xor a +; Clear chosen input palette + ld [InputPalette], a +; Clear memory VRAM + ld hl, $8000 + call ClearMemoryPage + ld h, $d0 + call ClearMemoryPage + +; Clear OAM + ld hl, $fe00 + ld c, $a0 + xor a +.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 + + ld hl, $30 +; Init waveform + xor a + ld c, $10 +.waveformLoop + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop + +; 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 DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $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 + +; Copy Sameboy Logo + ld de, SameboyLogo + ld hl, $8080 + ld c, (SameboyLogoEnd - SameboyLogo) / 2 +.sameboyLogoLoop + ld a, [de] + ldi [hl], a + inc hl + inc de + ld a, [de] + ldi [hl], a + inc hl + inc de + dec c + jr nz, .sameboyLogoLoop + +; Copy (unresized) ROM logo + ld de, $104 + ld c, 6 +.CGBROMLogoLoop + push bc + call ReadCGBLogoTile + pop bc + dec c + jr nz, .CGBROMLogoLoop + inc hl + call ReadTrademarkSymbol + +; Load Tilemap + ld hl, $98C2 + ld b, 3 + ld a, 8 + +.tilemapLoop + ld c, $10 + +.tilemapRowLoop + + ld [hl], a + push af + ; Switch to second VRAM Bank + ld a, 1 + ldh [$4F], a + ld a, 8 + ld [hl], a + ; Switch to back first VRAM Bank + xor a + ldh [$4F], a + pop af + ldi [hl], a + inc a + dec c + jr nz, .tilemapRowLoop + ld de, $10 + add hl, de + dec b + jr nz, .tilemapLoop + + cp $38 + jr nz, .doneTilemap + + ld hl, $99a7 + ld b, 1 + ld c, 7 + jr .tilemapRowLoop +.doneTilemap + + ; Expand Palettes + ld de, AnimationColors + ld c, 8 + ld hl, BgPalettes + xor a +.expandPalettesLoop: + cpl + ; One white + ldi [hl], a + ldi [hl], a + + ; 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 + + dec c + jr nz, .expandPalettesLoop + + ld hl, BgPalettes + ld d, 64 ; Length of write + ld e, 0 ; Index of write + call LoadBGPalettes + + ; Turn on LCD + ld a, $91 + ldh [$40], a + + 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 b, 30 + call WaitBFrames + 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 ; + 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: + 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 ; 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 + 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 + palette_comb 3, 3, 11 + 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 + palette_comb 27, 0, 14 + palette_comb 27, 4, 15 + 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 $1B77, $0AD2, $25E9, $1545 ; 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.1bpp" +SameboyLogoEnd: + +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 +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 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 + +; 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 + ret + + +ReadCGBLogoHalfTile: + ld c, $f0 + call ReadTileLine + ld c, $f + call ReadTileLine + inc e + ld c, $f0 + call ReadTileLine + ld c, $f + call ReadTileLine + inc e + ret + +ReadCGBLogoTile: + call ReadCGBLogoHalfTile + ld a, e + add a, 22 + ld e, a + call ReadCGBLogoHalfTile + ld a, e + sub a, 22 + ld e, a + ret + + +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 + +LoadBGPalettes: + 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 + + +AdvanceIntroAnimation: + ld hl, $98C0 + ld c, 3 ; Row count +.loop + ld a, [hl] + cp $F ; Already blue + jr z, .nextTile + inc a + ld [hl], a + and $7 + cp $1 ; Changed a white tile, go to next line + jr z, .nextLine +.nextTile + inc hl + jr .loop +.nextLine + ld a, l + or $1F + ld l, a + inc hl + dec c + ret z + jr .loop + +DoIntroAnimation: + ; Animate the intro + ld a, 1 + ldh [$4F], a + ld d, 26 +.animationLoop + ld b, 2 + call WaitBFrames + call AdvanceIntroAnimation + dec d + jr nz, .animationLoop + ret + +Preboot: + call FadeOut + call ClearVRAMViaHDMA + ; Select the first bank + xor a + ldh [$4F], a + call ClearVRAMViaHDMA + + ld a, [$143] + bit 7, a + jr nz, .cgbGame + + call EmulateDMG + +.cgbGame + ldh [$4C], a ; One day, I will know what this switch does and how it differs from FF6C + ld a, [InputPalette] + and a + jr nz, .emulateDMGForCGBGame + ld a, $11 + ret + +.emulateDMGForCGBGame + call EmulateDMG + ldh [$4C], a + ld a, $1; + ret + +EmulateDMG: + ld a, 1 + ldh [$6C], a ; DMG Emulation + ld 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 + call GetPaletteIndex +.paletteFromKeys + call WaitFrame + call LoadPalettesFromIndex + ld a, 4 + ret + +GetPaletteIndex: + ld a, [$14B] ; Old Licensee + cp $33 + jr z, .newLicensee + cp 1 ; Nintendo + jr nz, .notNintendo + jr .doChecksum +.newLicensee + ld a, [$144] + cp "0" + jr nz, .notNintendo + ld a, [$145] + cp "1" + jr nz, .notNintendo + +.doChecksum + ld hl, $134 + ld c, $10 + ld b, 0 + +.checksumLoop + ld a, [hli] + add b + ld b, a + dec c + jr nz, .checksumLoop + + ; c = 0 + ld hl, TitleChecksums + +.searchLoop + ld a, l + cp ChecksumsEnd & $FF + jr z, .notNintendo + 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, [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 + ld e, 0 + call LoadBGPalettes + ret + +BrithenColor: + 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 + +FadeOut: + ld b, 32 ; 32 times to fade +.fadeLoop + ld c, 32 ; 32 colors to fade + ld hl, BgPalettes +.frameLoop + push bc + call BrithenColor + pop bc + dec c + jr nz, .frameLoop + + call WaitFrame + call WaitFrame + ld hl, BgPalettes + ld d, 64 ; Length of write + ld e, 0 ; Index of write + call LoadBGPalettes + dec b + ret z + jr .fadeLoop + +ClearVRAMViaHDMA: + ld hl, $FF51 + + ; Src + ld a, $D0 + ld [hli], a + xor a + ld [hli], a + + ; Dest + ld a, $98 + ld [hli], a + ld a, $A0 + ld [hli], a + + ; Do it + ld a, $12 + ld [hli], a + 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 + ld a, [InputPalette] + ld c, a + ld a, b + ld [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 + ld [BgPalettes + 2], a ; Second color, first palette + + ld a, [hli] + push hl + ld hl, BgPalettes + 1 ; First color, all palette + call ReplaceColorInAllPalettes + pop hl + ld [BgPalettes + 3], a ; Second color, first palette + pop af + jr z, .isNotWhite + inc hl + inc hl +.isNotWhite + ld a, [hli] + ld [BgPalettes + 7 * 8 + 2], a ; Second color, 7th palette + ld a, [hli] + ld [BgPalettes + 7 * 8 + 3], a ; Second color, 7th palette + ld a, [hli] + ld [BgPalettes + 4], a ; Third color, first palette + ld a, [hl] + ld [BgPalettes + 5], a ; Third color, first palette + + call WaitFrame + ld hl, BgPalettes + ld d, 64 ; Length of write + ld e, 0 ; Index of write + call LoadBGPalettes + + 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 + +SECTION "ROMMax", ROM0[$900] + ; Prevent us from overflowing + ds 1 + +SECTION "RAM", WRAM0[$C000] +BgPalettes: + ds 8 * 4 * 2 +InputPalette: + ds 1 \ No newline at end of file diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm new file mode 100644 index 0000000..bff23ed --- /dev/null +++ b/BootROMs/dmg_boot.asm @@ -0,0 +1,141 @@ +; 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 + +.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, 15 + call WaitBFrames + ; Play second sound + ld a, $c1 + call PlaySound + +; Wait ~2.5 seconds + ld b, 150 + call WaitBFrames +; 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: + ldh a, [$44] + cp $90 + jr nz, WaitFrame + 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[$fc] +BootGame: + ld a, 1 + ldh [$50], a \ No newline at end of file diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h new file mode 100644 index 0000000..69b6e0f --- /dev/null +++ b/Cocoa/AppDelegate.h @@ -0,0 +1,7 @@ +#import + +@interface AppDelegate : NSObject + + +@end + diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m new file mode 100644 index 0000000..d443346 --- /dev/null +++ b/Cocoa/AppDelegate.m @@ -0,0 +1,17 @@ +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Insert code here to initialize your application +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +@end diff --git a/Cocoa/AppIcon.icns b/Cocoa/AppIcon.icns new file mode 100644 index 0000000..05a241c Binary files /dev/null and b/Cocoa/AppIcon.icns differ diff --git a/Cocoa/AudioClient.h b/Cocoa/AudioClient.h new file mode 100644 index 0000000..9c61896 --- /dev/null +++ b/Cocoa/AudioClient.h @@ -0,0 +1,11 @@ +#import + +@interface AudioClient : NSObject +@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer); +@property (readonly) UInt32 rate; +@property (readonly, getter=isPlaying) bool playing; +-(void) start; +-(void) stop; +-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block + andSampleRate:(UInt32) rate; +@end diff --git a/Cocoa/AudioClient.m b/Cocoa/AudioClient.m new file mode 100644 index 0000000..e143693 --- /dev/null +++ b/Cocoa/AudioClient.m @@ -0,0 +1,115 @@ +#import +#import +#import "AudioClient.h" + +static OSStatus render( + AudioClient *self, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) + +{ + // This is a mono tone generator so we only need the first buffer + const int channel = 0; + SInt16 *buffer = (SInt16 *)ioData->mBuffers[channel].mData; + + + self.renderBlock(self.rate, inNumberFrames, buffer); + + + return noErr; +} + +@implementation AudioClient +{ + AudioComponentInstance audioUnit; +} + +-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block + andSampleRate:(UInt32) rate +{ + if(!(self = [super init])) + { + return nil; + } + + // Configure the search parameters to find the default playback output unit + // (called the kAudioUnitSubType_RemoteIO on iOS but + // kAudioUnitSubType_DefaultOutput on Mac OS X) + AudioComponentDescription defaultOutputDescription; + defaultOutputDescription.componentType = kAudioUnitType_Output; + defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput; + defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + defaultOutputDescription.componentFlags = 0; + defaultOutputDescription.componentFlagsMask = 0; + + // Get the default playback output unit + AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); + NSAssert(defaultOutput, @"Can't find default output"); + + // Create a new unit based on this that we'll use for output + OSErr err = AudioComponentInstanceNew(defaultOutput, &audioUnit); + NSAssert1(audioUnit, @"Error creating unit: %hd", err); + + // Set our tone rendering function on the unit + AURenderCallbackStruct input; + input.inputProc = (void*)render; + input.inputProcRefCon = (__bridge void * _Nullable)(self); + err = AudioUnitSetProperty(audioUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + NSAssert1(err == noErr, @"Error setting callback: %hd", err); + + AudioStreamBasicDescription streamFormat; + streamFormat.mSampleRate = rate; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = + kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; + streamFormat.mBytesPerPacket = 2; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = 2; + streamFormat.mChannelsPerFrame = 1; + streamFormat.mBitsPerChannel = 2 * 8; + err = AudioUnitSetProperty (audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription)); + NSAssert1(err == noErr, @"Error setting stream format: %hd", err); + err = AudioUnitInitialize(audioUnit); + NSAssert1(err == noErr, @"Error initializing unit: %hd", err); + + self.renderBlock = block; + _rate = rate; + + return self; +} + +-(void) start +{ + OSErr err = AudioOutputUnitStart(audioUnit); + NSAssert1(err == noErr, @"Error starting unit: %hd", err); + _playing = YES; + +} + + +-(void) stop +{ + AudioOutputUnitStop(audioUnit); + _playing = NO; +} + +-(void) dealloc { + [self stop]; + AudioUnitUninitialize(audioUnit); + AudioComponentInstanceDispose(audioUnit); +} + +@end \ No newline at end of file diff --git a/Cocoa/Cartridge.icns b/Cocoa/Cartridge.icns new file mode 100644 index 0000000..36a9adb Binary files /dev/null and b/Cocoa/Cartridge.icns differ diff --git a/Cocoa/ColorCartridge.icns b/Cocoa/ColorCartridge.icns new file mode 100644 index 0000000..95dada1 Binary files /dev/null and b/Cocoa/ColorCartridge.icns differ diff --git a/Cocoa/Document.h b/Cocoa/Document.h new file mode 100644 index 0000000..869abe6 --- /dev/null +++ b/Cocoa/Document.h @@ -0,0 +1,12 @@ +#import +#include "GBView.h" + +@interface Document : NSDocument +@property (strong) IBOutlet GBView *view; +@property (strong) IBOutlet NSTextView *consoleOutput; +@property (strong) IBOutlet NSPanel *consoleWindow; +@property (strong) IBOutlet NSTextField *consoleInput; + + +@end + diff --git a/Cocoa/Document.m b/Cocoa/Document.m new file mode 100644 index 0000000..7ab022f --- /dev/null +++ b/Cocoa/Document.m @@ -0,0 +1,366 @@ +#include +#include "AudioClient.h" +#import "Document.h" +#include "gb.h" + +@interface Document () +{ + /* NSTextViews freeze the entire app if they're modified too often and too quickly. + We use this bool to tune down the write speed. Let me know if there's a more + reasonable alternative to this. */ + unsigned long pendingLogLines; + bool tooMuchLogs; +} + +@property AudioClient *audioClient; +- (void) vblank; +- (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes; +- (const char *) getDebuggerInput; +@end + +static void vblank(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)(gb->user_data); + [self vblank]; +} + +static void consoleLog(GB_gameboy_t *gb, const char *string, gb_log_attributes attributes) +{ + Document *self = (__bridge Document *)(gb->user_data); + [self log:string withAttributes: attributes]; +} + +static char *consoleInput(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)(gb->user_data); + return strdup([self getDebuggerInput]); +} + +static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b) +{ + return (r << 24) | (g << 16) | (b << 8); +} + +@implementation Document +{ + GB_gameboy_t gb; + volatile bool running; + volatile bool stopping; + NSConditionLock *has_debugger_input; + NSMutableArray *debugger_input_queue; + bool is_inited; +} + +- (instancetype)init { + self = [super init]; + if (self) { + has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; + debugger_input_queue = [[NSMutableArray alloc] init]; + [self initCGB]; + } + return self; +} + +- (void) initDMG +{ + gb_init(&gb); + gb_load_bios(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]); + gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + gb_set_log_callback(&gb, (GB_log_callback_t) consoleLog); + gb_set_input_callback(&gb, (GB_input_callback_t) consoleInput); + gb_set_rgb_encode_callback(&gb, rgbEncode); + gb.user_data = (__bridge void *)(self); +} + +- (void) initCGB +{ + gb_init_cgb(&gb); + gb_load_bios(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]); + gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + gb_set_log_callback(&gb, (GB_log_callback_t) consoleLog); + gb_set_input_callback(&gb, (GB_input_callback_t) consoleInput); + gb_set_rgb_encode_callback(&gb, rgbEncode); + gb.user_data = (__bridge void *)(self); +} + +- (void) vblank +{ + [self.view flip]; + gb_set_pixels_output(&gb, self.view.pixels); +} + +- (void) run +{ + running = true; + gb_set_pixels_output(&gb, self.view.pixels); + self.view.gb = &gb; + gb_set_sample_rate(&gb, 96000); + self.audioClient = [[AudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer) { + //apu_render(&gb, sampleRate, nFrames, buffer); + apu_copy_buffer(&gb, buffer, nFrames); + } andSampleRate:96000]; + [self.audioClient start]; + while (running) { + gb_run(&gb); + } + [self.audioClient stop]; + gb_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + stopping = false; +} + +- (void) start +{ + if (running) return; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self run]; + }); +} + +- (void) stop +{ + if (!running) return; + if (gb.debug_stopped) { + gb.debug_stopped = false; + [self consoleInput:nil]; + } + stopping = true; + running = false; + while (stopping); +} + +- (IBAction)reset:(id)sender +{ + bool was_cgb = gb.is_cgb; + [self stop]; + gb_free(&gb); + is_inited = false; + if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) { + [self initCGB]; + } + else { + [self initDMG]; + } + [self readFromFile:self.fileName ofType:@"gb"]; + [self start]; +} + +- (IBAction)togglePause:(id)sender +{ + if (running) { + [self stop]; + } + else { + [self start]; + } +} + +- (void)dealloc +{ + gb_free(&gb); +} + +- (void)windowControllerDidLoadNib:(NSWindowController *)aController { + [super windowControllerDidLoadNib:aController]; + self.consoleOutput.textContainerInset = NSMakeSize(4, 4); + [self.view becomeFirstResponder]; + [self start]; + +} + ++ (BOOL)autosavesInPlace { + return YES; +} + +- (NSString *)windowNibName { + // Override returning the nib file name of the document + // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. + return @"Document"; +} + +- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type +{ + if (is_inited++) { + return YES; + } + gb_load_rom(&gb, [fileName UTF8String]); + gb_load_battery(&gb, [[[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + return YES; +} + +- (void)close +{ + [self stop]; + [self.consoleWindow close]; + [super close]; +} + +- (IBAction) interrupt:(id)sender +{ + [self log:"^C\n"]; + gb.debug_stopped = true; + [self.consoleInput becomeFirstResponder]; +} + +- (IBAction)mute:(id)sender +{ + if (self.audioClient.isPlaying) { + [self.audioClient stop]; + } + else { + [self.audioClient start]; + } +} + +- (IBAction)toggleBlend:(id)sender +{ + self.view.shouldBlendFrameWithPrevious ^= YES; +} + +- (BOOL)validateUserInterfaceItem:(id)anItem +{ + if([anItem action] == @selector(mute:)) { + [(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; + } + if ([anItem action] == @selector(togglePause:)) { + [(NSMenuItem*)anItem setState:!running]; + } + if ([anItem action] == @selector(reset:) && anItem.tag != 0) { + [(NSMenuItem*)anItem setState:(anItem.tag == 1 && !gb.is_cgb) || (anItem.tag == 2 && gb.is_cgb)]; + } + if([anItem action] == @selector(toggleBlend:)) { + [(NSMenuItem*)anItem setState:self.view.shouldBlendFrameWithPrevious]; + } + return [super validateUserInterfaceItem:anItem]; +} + + +- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame +{ + NSRect rect = window.contentView.frame; + + int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; + int step = 160 / [[window screen] backingScaleFactor]; + + rect.size.width = floor(rect.size.width / step) * step + step; + rect.size.height = rect.size.width / 10 * 9 + titlebarSize; + + if (rect.size.width > newFrame.size.width) { + rect.size.width = 160; + rect.size.height = 144 + titlebarSize; + } + else if (rect.size.height > newFrame.size.height) { + rect.size.width = 160; + rect.size.height = 144 + titlebarSize; + } + + rect.origin = window.frame.origin; + rect.origin.y -= rect.size.height - window.frame.size.height; + + return rect; +} + +- (void) log: (const char *) string withAttributes: (gb_log_attributes) attributes +{ + if (pendingLogLines > 128) { + /* The ROM causes so many errors in such a short time, and we can't handle it. */ + tooMuchLogs = true; + return; + } + pendingLogLines++; + NSString *nsstring = @(string); // For ref-counting + dispatch_async(dispatch_get_main_queue(), ^{ + NSFont *font = [NSFont userFixedPitchFontOfSize:12]; + NSUnderlineStyle underline = NSUnderlineStyleNone; + if (attributes & GB_LOG_BOLD) { + font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask]; + } + + if (attributes & GB_LOG_UNDERLINE_MASK) { + underline = (attributes & GB_LOG_UNDERLINE_MASK) == GB_LOG_DASHED_UNDERLINE? NSUnderlinePatternDot | NSUnderlineStyleSingle : NSUnderlineStyleSingle; + } + + NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init]; + [paragraph_style setLineSpacing:2]; + NSAttributedString *attributed = + [[NSAttributedString alloc] initWithString:nsstring + attributes:@{NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor whiteColor], + NSUnderlineStyleAttributeName: @(underline), + NSParagraphStyleAttributeName: paragraph_style}]; + [self.consoleOutput.textStorage appendAttributedString:attributed]; + if (pendingLogLines == 1) { + if (tooMuchLogs) { + tooMuchLogs = false; + [self log:"[...]\n"]; + } + [self.consoleOutput scrollToEndOfDocument:nil]; + [self.consoleWindow orderBack:nil]; + } + pendingLogLines--; + }); +} + +- (IBAction)showConsoleWindow:(id)sender +{ + [self.consoleWindow orderBack:nil]; +} + +- (IBAction)consoleInput:(NSTextField *)sender { + NSString *line = [sender stringValue]; + if (!line) { + line = @""; + } + [self log:[line UTF8String]]; + [self log:"\n"]; + [has_debugger_input lock]; + [debugger_input_queue addObject:line]; + [has_debugger_input unlockWithCondition:1]; + + [sender setStringValue:@""]; +} + +- (const char *) getDebuggerInput +{ + [self log:">"]; + [has_debugger_input lockWhenCondition:1]; + NSString *input = [debugger_input_queue firstObject]; + [debugger_input_queue removeObjectAtIndex:0]; + [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; + return [input UTF8String]; +} + +- (IBAction)saveState:(id)sender +{ + bool was_running = running; + if (!gb.debug_stopped) { + [self stop]; + } + gb_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); + if (was_running) { + [self start]; + } +} + +- (IBAction)loadState:(id)sender +{ + bool was_running = running; + if (!gb.debug_stopped) { + [self stop]; + } + gb_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); + if (was_running) { + [self start]; + } +} + +- (IBAction)clearConsole:(id)sender +{ + [self.consoleOutput setString:@""]; +} + +- (void)log:(const char *)log +{ + [self log:log withAttributes:0]; +} + +@end diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib new file mode 100644 index 0000000..d2cd3f9 --- /dev/null +++ b/Cocoa/Document.xib @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h new file mode 100644 index 0000000..a687949 --- /dev/null +++ b/Cocoa/GBView.h @@ -0,0 +1,9 @@ +#import +#include "gb.h" + +@interface GBView : NSOpenGLView +- (void) flip; +- (uint32_t *) pixels; +@property GB_gameboy_t *gb; +@property BOOL shouldBlendFrameWithPrevious; +@end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m new file mode 100644 index 0000000..ada01e9 --- /dev/null +++ b/Cocoa/GBView.m @@ -0,0 +1,163 @@ +#import +#import +#import "GBView.h" + +@implementation GBView +{ + uint32_t *image_buffers[3]; + unsigned char current_buffer; +} + +- (void) _init +{ + image_buffers[0] = malloc(160 * 144 * 4); + image_buffers[1] = malloc(160 * 144 * 4); + image_buffers[2] = malloc(160 * 144 * 4); + _shouldBlendFrameWithPrevious = 1; +} + +- (unsigned char) numberOfBuffers +{ + return _shouldBlendFrameWithPrevious? 3 : 2; +} + +- (void)dealloc +{ + free(image_buffers[0]); + free(image_buffers[1]); + free(image_buffers[2]); +} +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if (!(self = [super initWithCoder:coder])) + { + return self; + } + [self _init]; + return self; +} + +- (instancetype)initWithFrame:(NSRect)frameRect +{ + if (!(self = [super initWithFrame:frameRect])) + { + return self; + } + [self _init]; + return self; +} + +- (void)drawRect:(NSRect)dirtyRect { + double scale = self.window.backingScaleFactor; + glRasterPos2d(-1, 1); + glPixelZoom(self.bounds.size.width / 160 * scale, self.bounds.size.height / -144 * scale); + glDrawPixels(160, 144, GL_ABGR_EXT, GL_UNSIGNED_BYTE, image_buffers[current_buffer]); + if (_shouldBlendFrameWithPrevious) { + glEnable(GL_BLEND); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + glBlendColor(1, 1, 1, 0.5); + glDrawPixels(160, 144, GL_ABGR_EXT, GL_UNSIGNED_BYTE, image_buffers[(current_buffer + 2) % self.numberOfBuffers]); + glDisable(GL_BLEND); + } + glFlush(); +} + + +- (void) flip +{ + current_buffer = (current_buffer + 1) % self.numberOfBuffers; + [self setNeedsDisplay:YES]; +} + +- (uint32_t *) pixels +{ + return image_buffers[(current_buffer + 1) % self.numberOfBuffers]; +} + +-(void)keyDown:(NSEvent *)theEvent +{ + unsigned short key = theEvent.keyCode; + switch (key) { + case kVK_RightArrow: + _gb->keys[0] = true; + break; + case kVK_LeftArrow: + _gb->keys[1] = true; + break; + case kVK_UpArrow: + _gb->keys[2] = true; + break; + case kVK_DownArrow: + _gb->keys[3] = true; + break; + case kVK_ANSI_X: + _gb->keys[4] = true; + break; + case kVK_ANSI_Z: + _gb->keys[5] = true; + break; + case kVK_Delete: + _gb->keys[6] = true; + break; + case kVK_Return: + _gb->keys[7] = true; + break; + case kVK_Space: + _gb->turbo = true; + break; + + default: + [super keyDown:theEvent]; + break; + } +} + +-(void)keyUp:(NSEvent *)theEvent +{ + unsigned short key = theEvent.keyCode; + switch (key) { + case kVK_RightArrow: + _gb->keys[0] = false; + break; + case kVK_LeftArrow: + _gb->keys[1] = false; + break; + case kVK_UpArrow: + _gb->keys[2] = false; + break; + case kVK_DownArrow: + _gb->keys[3] = false; + break; + case kVK_ANSI_X: + _gb->keys[4] = false; + break; + case kVK_ANSI_Z: + _gb->keys[5] = false; + break; + case kVK_Delete: + _gb->keys[6] = false; + break; + case kVK_Return: + _gb->keys[7] = false; + break; + case kVK_Space: + _gb->turbo = false; + break; + + default: + [super keyUp:theEvent]; + break; + } +} + +-(void)reshape +{ + double scale = self.window.backingScaleFactor; + glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} +@end diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist new file mode 100644 index 0000000..c8ac696 --- /dev/null +++ b/Cocoa/Info.plist @@ -0,0 +1,71 @@ + + + + + BuildMachineOSBuild + 14F1509 + CFBundleDevelopmentRegion + en + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + gb + + CFBundleTypeIconFile + Cartridge + CFBundleTypeName + Gameboy Game + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + NSDocumentClass + Document + + + CFBundleTypeExtensions + + gbc + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Gameboy Color Game + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + NSDocumentClass + Document + + + CFBundleExecutable + SameBoy + CFBundleIconFile + AppIcon.icns + CFBundleIdentifier + com.github.LIJI32.SameBoy + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SameBoy + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + LSMinimumSystemVersion + 10.9 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib new file mode 100644 index 0000000..79d13ab --- /dev/null +++ b/Cocoa/MainMenu.xib @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/PkgInfo b/Cocoa/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/Cocoa/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/Cocoa/main.m b/Cocoa/main.m new file mode 100644 index 0000000..8a6799b --- /dev/null +++ b/Cocoa/main.m @@ -0,0 +1,5 @@ +#import + +int main(int argc, const char * argv[]) { + return NSApplicationMain(argc, argv); +} diff --git a/Core/apu.c b/Core/apu.c new file mode 100644 index 0000000..49930b4 --- /dev/null +++ b/Core/apu.c @@ -0,0 +1,415 @@ +#include +#include +#include +#include "apu.h" +#include "gb.h" + +#define max(a,b) \ +({ __typeof__ (a) _a = (a); \ +__typeof__ (b) _b = (b); \ +_a > _b ? _a : _b; }) + +#define min(a,b) \ +({ __typeof__ (a) _a = (a); \ +__typeof__ (b) _b = (b); \ +_a < _b ? _a : _b; }) + +static __attribute__((unused)) int16_t generate_sin(double phase, int16_t amplitude) +{ + return (int16_t)(sin(phase) * amplitude); +} + +static int16_t generate_square(double phase, int16_t amplitude, double duty) +{ + if (fmod(phase, 2 * M_PI) > duty * 2 * M_PI) { + return amplitude; + } + return 0; +} + +static int16_t generate_wave(double phase, int16_t amplitude, signed char *wave, unsigned char shift) +{ + phase = fmod(phase, 2 * M_PI); + return ((wave[(int)(phase / (2 * M_PI) * 32)]) >> shift) * (int)amplitude / 0xF; +} + +static int16_t generate_noise(double phase, int16_t amplitude, uint16_t lfsr) +{ + if (lfsr & 1) { + return amplitude; + } + return 0; +} + +static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) +{ + bool xor = (lfsr & 1) ^ ((lfsr & 2) >> 1); + lfsr >>= 1; + if (xor) { + lfsr |= 0x4000; + } + if (uses_7_bit) { + lfsr &= ~0x40; + if (xor) { + lfsr |= 0x40; + } + } + return lfsr; +} + +/* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with + these tests in mind. */ + +void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples) +{ + for (; n_samples--; samples++) { + *samples = 0; + if (!gb->apu.global_enable) { + continue; + } + + gb->io_registers[GB_IO_PCM_12] = 0; + gb->io_registers[GB_IO_PCM_34] = 0; + + // Todo: Stereo support + + if (gb->apu.left_on[0] || gb->apu.right_on[0]) { + int16_t sample = generate_square(gb->apu.wave_channels[0].phase, + gb->apu.wave_channels[0].amplitude, + gb->apu.wave_channels[0].duty); + *samples += sample; + gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; + } + if (gb->apu.left_on[1] || gb->apu.right_on[1]) { + int16_t sample = generate_square(gb->apu.wave_channels[1].phase, + gb->apu.wave_channels[1].amplitude, + gb->apu.wave_channels[1].duty); + *samples += sample; + gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + if (gb->apu.wave_enable && (gb->apu.left_on[2] || gb->apu.right_on[2])) { + int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, + MAX_CH_AMP, + gb->apu.wave_form, + gb->apu.wave_shift); + *samples += sample; + gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; + } + if (gb->apu.left_on[3] || gb->apu.right_on[3]) { + int16_t sample = generate_noise(gb->apu.wave_channels[3].phase, + gb->apu.wave_channels[3].amplitude, + gb->apu.lfsr); + *samples += sample; + gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + *samples *= gb->apu.left_volume; + + for (unsigned char i = 0; i < 4; i++) { + /* Phase */ + gb->apu.wave_channels[i].phase += 2 * M_PI * gb->apu.wave_channels[i].frequency / sample_rate; + while (gb->apu.wave_channels[i].phase >= 2 * M_PI) { + if (i == 3) { + gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit); + } + gb->apu.wave_channels[i].phase -= 2 * M_PI; + } + /* Stop on Length */ + if (gb->apu.wave_channels[i].stop_on_length) { + if (gb->apu.wave_channels[i].sound_length > 0) { + gb->apu.wave_channels[i].sound_length -= 1.0 / sample_rate; + } + if (gb->apu.wave_channels[i].sound_length <= 0) { + gb->apu.wave_channels[i].amplitude = 0; + gb->apu.wave_channels[i].is_playing = false; + gb->apu.wave_channels[i].sound_length = i == 2? 1 : 0.25; + } + } + } + + gb->apu.envelope_step_timer += 1.0 / sample_rate; + if (gb->apu.envelope_step_timer >= 1.0 / 64) { + gb->apu.envelope_step_timer -= 1.0 / 64; + for (unsigned char i = 0; i < 4; i++) { + if (gb->apu.wave_channels[i].envelope_steps && !--gb->apu.wave_channels[i].cur_envelope_steps) { + gb->apu.wave_channels[i].amplitude = min(max(gb->apu.wave_channels[i].amplitude + gb->apu.wave_channels[i].envelope_direction * CH_STEP, 0), MAX_CH_AMP); + gb->apu.wave_channels[i].cur_envelope_steps = gb->apu.wave_channels[i].envelope_steps; + } + } + } + + gb->apu.sweep_step_timer += 1.0 / sample_rate; + if (gb->apu.sweep_step_timer >= 1.0 / 128) { + gb->apu.sweep_step_timer -= 1.0 / 128; + if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) { + + // Convert back to GB format + unsigned short temp = (unsigned short) (2048 - 131072 / gb->apu.wave_channels[0].frequency); + + // Apply sweep + temp = temp + gb->apu.wave_channels[0].sweep_direction * + (temp / (1 << gb->apu.wave_channels[0].sweep_shift)); + if (temp > 2047) { + temp = 0; + } + + // Back to frequency + gb->apu.wave_channels[0].frequency = 131072.0 / (2048 - temp); + + gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps; + } + } + } +} + +void apu_run(GB_gameboy_t *gb) +{ + static bool should_log_overflow = true; + while (gb->audio_copy_in_progress); + double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate; + while (gb->apu_cycles > ticks_per_sample) { + int16_t sample = 0; + apu_render(gb, gb->sample_rate, 1, &sample); + gb->apu_cycles -= ticks_per_sample; + if (gb->audio_position == gb->buffer_size) { + /* + if (should_log_overflow && !gb->turbo) { + gb_log(gb, "Audio overflow\n"); + should_log_overflow = false; + } + */ + } + else { + gb->audio_buffer[gb->audio_position++] = sample; + should_log_overflow = true; + } + } +} + +void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count) +{ + gb->audio_copy_in_progress = true; + + if (!gb->audio_stream_started) { + // Intentionally fail the first copy to sync the stream with the Gameboy. + gb->audio_stream_started = true; + gb->audio_position = 0; + } + + if (count > gb->audio_position) { + // gb_log(gb, "Audio underflow: %d\n", count - gb->audio_position); + memset(dest + gb->audio_position, 0, (count - gb->audio_position) * 2); + count = gb->audio_position; + } + memcpy(dest, gb->audio_buffer, count * 2); + memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * 2); + gb->audio_position -= count; + + gb->audio_copy_in_progress = false; +} + +void apu_init(GB_gameboy_t *gb) +{ + memset(&gb->apu, 0, sizeof(gb->apu)); + gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 0.5; + gb->apu.lfsr = 0x7FFF; + gb->apu.left_volume = 1.0; + gb->apu.right_volume = 1.0; + for (int i = 0; i < 4; i++) { + gb->apu.left_on[i] = gb->apu.right_on[i] = 1; + } +} + +unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg) +{ + /* Todo: what happens when reading from the wave from while it's playing? */ + + if (reg == GB_IO_NR52) { + unsigned char value = 0; + for (int i = 0; i < 4; i++) { + value >>= 1; + if (gb->apu.wave_channels[i].is_playing) { + value |= 0x8; + } + } + if (gb->apu.global_enable) { + value |= 0x80; + } + value |= 0x70; + return value; + } + + static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = { + /* NRX0 NRX1 NRX2 NRX3 NRX4 */ + 0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X + 0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X + 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X + 0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X + 0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused + // Wave RAM + 0, /* ... */ + }; + + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.wave_channels[2].is_playing) { + return (unsigned char)((gb->display_cycles * 22695477 * reg) >> 8); // Semi-random but deterministic + } + + return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; +} + +void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value) +{ + static const double duties[] = {0.125, 0.25, 0.5, 0.75}; + static uint16_t NRX3_X4_temp[3] = {0}; + unsigned char channel = 0; + + if (!gb->apu.global_enable && reg != GB_IO_NR52) { + return; + } + + gb->io_registers[reg] = value; + + switch (reg) { + case GB_IO_NR10: + case GB_IO_NR11: + case GB_IO_NR12: + case GB_IO_NR13: + case GB_IO_NR14: + channel = 0; + break; + case GB_IO_NR21: + case GB_IO_NR22: + case GB_IO_NR23: + case GB_IO_NR24: + channel = 1; + break; + case GB_IO_NR33: + case GB_IO_NR34: + channel = 2; + break; + case GB_IO_NR41: + case GB_IO_NR42: + channel = 3; + default: + break; + } + + switch (reg) { + case GB_IO_NR10: + gb->apu.wave_channels[channel].sweep_direction = value & 8? -1 : 1; + gb->apu.wave_channels[channel].cur_sweep_steps = + gb->apu.wave_channels[channel].sweep_steps = (value & 0x70) >> 4; + gb->apu.wave_channels[channel].sweep_shift = value & 7; + break; + case GB_IO_NR11: + case GB_IO_NR21: + case GB_IO_NR41: + gb->apu.wave_channels[channel].duty = duties[value >> 6]; + gb->apu.wave_channels[channel].sound_length = (64 - (value & 0x3F)) / 256.0; + if (gb->apu.wave_channels[channel].sound_length == 0) { + gb->apu.wave_channels[channel].is_playing = false; + } + break; + case GB_IO_NR12: + case GB_IO_NR22: + case GB_IO_NR42: + gb->apu.wave_channels[channel].start_amplitude = + gb->apu.wave_channels[channel].amplitude = CH_STEP * (value >> 4); + if (value >> 4 == 0) { + gb->apu.wave_channels[channel].is_playing = false; + } + gb->apu.wave_channels[channel].envelope_direction = value & 8? 1 : -1; + gb->apu.wave_channels[channel].cur_envelope_steps = + gb->apu.wave_channels[channel].envelope_steps = value & 7; + break; + case GB_IO_NR13: + case GB_IO_NR23: + case GB_IO_NR33: + NRX3_X4_temp[channel] = (NRX3_X4_temp[channel] & 0xFF00) | value; + gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - NRX3_X4_temp[channel]); + if (channel == 2) { + gb->apu.wave_channels[channel].frequency /= 2; + } + break; + case GB_IO_NR14: + case GB_IO_NR24: + case GB_IO_NR34: + gb->apu.wave_channels[channel].stop_on_length = value & 0x40; + if (value & 0x80) { + gb->apu.wave_channels[channel].is_playing = true; + gb->apu.wave_channels[channel].phase = 0; + gb->apu.wave_channels[channel].amplitude = gb->apu.wave_channels[channel].start_amplitude; + gb->apu.wave_channels[channel].cur_envelope_steps = gb->apu.wave_channels[channel].envelope_steps; + } + + NRX3_X4_temp[channel] = (NRX3_X4_temp[channel] & 0xFF) | ((value & 0x7) << 8); + gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - NRX3_X4_temp[channel]); + if (channel == 2) { + gb->apu.wave_channels[channel].frequency /= 2; + } + break; + case GB_IO_NR30: + gb->apu.wave_enable = value & 0x80; + break; + case GB_IO_NR31: + gb->apu.wave_channels[2].sound_length = (256 - value) / 256.0; + if (gb->apu.wave_channels[2].sound_length == 0) { + gb->apu.wave_channels[2].is_playing = false; + } + break; + case GB_IO_NR32: + gb->apu.wave_shift = ((value >> 5) + 3) & 3; + break; + case GB_IO_NR43: + { + double r = value & 0x7; + if (r == 0) r = 0.5; + unsigned char s = value >> 4; + gb->apu.wave_channels[3].frequency = 524288.0 / r / (1 << (s + 1)); + gb->apu.lfsr_7_bit = value & 0x8; + break; + } + case GB_IO_NR44: + gb->apu.wave_channels[3].stop_on_length = value & 0x40; + if (value & 0x80) { + gb->apu.wave_channels[3].is_playing = true; + gb->apu.lfsr = 0x7FFF; + gb->apu.wave_channels[3].amplitude = gb->apu.wave_channels[3].start_amplitude; + gb->apu.wave_channels[3].cur_envelope_steps = gb->apu.wave_channels[3].envelope_steps; + } + break; + + case GB_IO_NR50: + gb->apu.left_volume = (value & 7) / 7.0; + gb->apu.right_volume = ((value >> 4) & 7) / 7.0; + break; + + case GB_IO_NR51: + for (int i = 0; i < 4; i++) { + gb->apu.left_on[i] = value & 1; + gb->apu.right_on[i] = value & 0x10; + value >>= 1; + } + break; + case GB_IO_NR52: + + if ((value & 0x80) && !gb->apu.global_enable) { + apu_init(gb); + gb->apu.global_enable = true; + } + else if (!(value & 0x80) && gb->apu.global_enable) { + memset(&gb->apu, 0, sizeof(gb->apu)); + memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); + } + break; + + default: + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { + gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; + gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; + } + break; + } +} \ No newline at end of file diff --git a/Core/apu.h b/Core/apu.h new file mode 100644 index 0000000..003f1ca --- /dev/null +++ b/Core/apu.h @@ -0,0 +1,59 @@ +#ifndef apu_h +#define apu_h +#include +#include + +/* Divides nicely and never overflows with 4 channels */ +#define MAX_CH_AMP 0x1E00 +#define CH_STEP (0x1E00/0xF) + + +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; + +/* Not all used on all channels */ +typedef struct +{ + double phase; + double frequency; + int16_t amplitude; + int16_t start_amplitude; + double duty; + double sound_length; /* In seconds */ + bool stop_on_length; + unsigned char envelope_steps; + unsigned char cur_envelope_steps; + signed int envelope_direction; + unsigned char sweep_steps; + unsigned char cur_sweep_steps; + signed int sweep_direction; + unsigned char sweep_shift; + bool is_playing; +} GB_apu_channel_t; + +typedef struct +{ + GB_apu_channel_t wave_channels[4]; + double envelope_step_timer; /* In seconds */ + double sweep_step_timer; /* In seconds */ + signed char wave_form[32]; + unsigned char wave_shift; + bool wave_enable; + uint16_t lfsr; + bool lfsr_7_bit; + double left_volume; + double right_volume; + bool left_on[4]; + bool right_on[4]; + bool global_enable; +} GB_apu_t; + +void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples); +void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count); +void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value); +unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg); +void apu_init(GB_gameboy_t *gb); +void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count); +void apu_run(GB_gameboy_t *gb); + +#endif /* apu_h */ diff --git a/Core/debugger.c b/Core/debugger.c new file mode 100644 index 0000000..f850b8d --- /dev/null +++ b/Core/debugger.c @@ -0,0 +1,410 @@ +#include +#include +#include +#include "debugger.h" +#include "memory.h" +#include "z80_cpu.h" +#include "gb.h" + + +typedef struct { + enum { + LVALUE_MEMORY, + LVALUE_REG16, + LVALUE_REG_H, + LVALUE_REG_L, + } kind; + union { + unsigned short *register_address; + unsigned short memory_address; + }; +} lvalue_t; + +static unsigned short read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) +{ + /* Not used until we add support for operators like += */ + switch (lvalue.kind) { + case LVALUE_MEMORY: + return read_memory(gb, lvalue.memory_address); + + case LVALUE_REG16: + return *lvalue.register_address; + + case LVALUE_REG_L: + return *lvalue.register_address & 0x00FF; + + case LVALUE_REG_H: + return *lvalue.register_address >> 8; + } +} + +static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, unsigned short value) +{ + switch (lvalue.kind) { + case LVALUE_MEMORY: + write_memory(gb, lvalue.memory_address, value); + return; + + case LVALUE_REG16: + *lvalue.register_address = value; + return; + + case LVALUE_REG_L: + *lvalue.register_address &= 0xFF00; + *lvalue.register_address |= value & 0xFF; + return; + + case LVALUE_REG_H: + *lvalue.register_address &= 0x00FF; + *lvalue.register_address |= value << 8; + return; + } +} + +static unsigned short add(unsigned short a, unsigned short b) {return a + b;}; +static unsigned short sub(unsigned short a, unsigned short b) {return a - b;}; +static unsigned short mul(unsigned short a, unsigned short b) {return a * b;}; +static unsigned short _div(unsigned short a, unsigned short b) { + if (b == 0) { + return 0; + } + return a / b; +}; +static unsigned short mod(unsigned short a, unsigned short b) { + if (b == 0) { + return 0; + } + return a % b; +}; +static unsigned short and(unsigned short a, unsigned short b) {return a & b;}; +static unsigned short or(unsigned short a, unsigned short b) {return a | b;}; +static unsigned short xor(unsigned short a, unsigned short b) {return a ^ b;}; +static unsigned short shleft(unsigned short a, unsigned short b) {return a << b;}; +static unsigned short shright(unsigned short a, unsigned short b) {return a >> b;}; +static unsigned short assign(GB_gameboy_t *gb, lvalue_t a, unsigned short b) +{ + write_lvalue(gb, a, b); + return read_lvalue(gb, a); +} + +static struct { + const char *string; + char priority; + unsigned short (*operator)(unsigned short, unsigned short); + unsigned short (*lvalue_operator)(GB_gameboy_t *, lvalue_t, unsigned short); +} operators[] = +{ + // Yes. This is not C-like. But it makes much more sense. + // Deal with it. + {"+", 0, add}, + {"-", 0, sub}, + {"|", 0, or}, + {"*", 1, mul}, + {"/", 1, _div}, + {"%", 1, mod}, + {"&", 1, and}, + {"^", 1, xor}, + {"<<", 2, shleft}, + {">>", 2, shright}, + {"=", 2, NULL, assign}, +}; + +unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error); + +static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error) +{ + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) + { + gb_log(gb, "Expected expression.\n"); + *error = true; + return (lvalue_t){0,}; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error); + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + if (depth == 0) { + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error)}; + } + } + + // Registers + if (string[0] == '$') { + if (length == 2) { + switch (string[1]) { + case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; + } + } + else if (length == 3) { + switch (string[1]) { + case 'a': if (string[2] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': if (string[2] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': if (string[2] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': if (string[2] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 's': if (string[2] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; + case 'p': if (string[2] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; + } + } + gb_log(gb, "Unknown register: %.*s\n", length, string); + *error = true; + return (lvalue_t){0,}; + } + + gb_log(gb, "Expression is not an lvalue: %.*s\n", length, string); + *error = true; + return (lvalue_t){0,}; +} + +unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error) +{ + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) + { + gb_log(gb, "Expected expression.\n"); + *error = true; + return -1; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) return debugger_evaluate(gb, string + 1, length - 2, error); + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + if (depth == 0) return read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error)); + } + // Search for lowest priority operator + signed int depth = 0; + unsigned int operator_index = -1; + unsigned int operator_pos = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '(') depth++; + else if (string[i] == ')') depth--; + else if (string[i] == '[') depth++; + else if (string[i] == ']') depth--; + else if (depth == 0) { + for (int j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) { + if (strlen(operators[j].string) > length - i) continue; // Operator too big. + // Priority higher than what we already have. + if (operator_index != -1 && operators[operator_index].priority > operators[j].priority) continue; + if (memcmp(string + i, operators[j].string, strlen(operators[j].string)) == 0) { + // Found an operator! + operator_pos = i; + operator_index = j; + } + } + } + } + if (operator_index != -1) { + unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); + unsigned short right = debugger_evaluate(gb, string + right_start, length - right_start, error); + if (*error) return -1; + if (operators[operator_index].lvalue_operator) { + lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error); + if (*error) return -1; + return operators[operator_index].lvalue_operator(gb, left, right); + } + unsigned short left = debugger_evaluate(gb, string, operator_pos, error); + if (*error) return -1; + return operators[operator_index].operator(left, right); + } + + // Not an expression - must be a register or a literal + + // Registers + if (string[0] == '$') { + if (length == 2) { + switch (string[1]) { + case 'a': return gb->registers[GB_REGISTER_AF] >> 8; + case 'f': return gb->registers[GB_REGISTER_AF] & 0xFF; + case 'b': return gb->registers[GB_REGISTER_BC] >> 8; + case 'c': return gb->registers[GB_REGISTER_BC] & 0xFF; + case 'd': return gb->registers[GB_REGISTER_DE] >> 8; + case 'e': return gb->registers[GB_REGISTER_DE] & 0xFF; + case 'h': return gb->registers[GB_REGISTER_HL] >> 8; + case 'l': return gb->registers[GB_REGISTER_HL] & 0xFF; + } + } + else if (length == 3) { + switch (string[1]) { + case 'a': if (string[2] == 'f') return gb->registers[GB_REGISTER_AF]; + case 'b': if (string[2] == 'c') return gb->registers[GB_REGISTER_BC]; + case 'd': if (string[2] == 'e') return gb->registers[GB_REGISTER_DE]; + case 'h': if (string[2] == 'l') return gb->registers[GB_REGISTER_HL]; + case 's': if (string[2] == 'p') return gb->registers[GB_REGISTER_SP]; + case 'p': if (string[2] == 'c') return gb->pc; + } + } + gb_log(gb, "Unknown register: %.*s\n", length, string); + *error = true; + return -1; + } + + char *end; + unsigned short literal = (unsigned short) (strtol(string, &end, 16)); + if (end != string + length) { + gb_log(gb, "Failed to parse: %.*s\n", length, string); + *error = true; + return -1; + } + return literal; +} + + +/* The debugger interface is quite primitive. One letter commands with a single parameter maximum. + Only one breakpoint is allowed at a time. More features will be added later. */ +void debugger_run(GB_gameboy_t *gb) +{ + char *input = NULL; + if (gb->debug_next_command && gb->debug_call_depth == 0) { + gb->debug_stopped = true; + } + if (gb->debug_fin_command && gb->debug_call_depth == -1) { + gb->debug_stopped = true; + } + if (gb->debug_stopped) { + cpu_disassemble(gb, gb->pc, 5); + } +next_command: + if (input) { + free(input); + } + if (gb->pc == gb->breakpoint && !gb->debug_stopped) { + gb->debug_stopped = true; + gb_log(gb, "Breakpoint: PC = %04x\n", gb->pc); + cpu_disassemble(gb, gb->pc, 5); + } + if (gb->debug_stopped) { + gb->debug_next_command = false; + gb->debug_fin_command = false; + input = gb->input_callback(gb); + switch (*input) { + case 'c': + gb->debug_stopped = false; + break; + case 'n': + gb->debug_stopped = false; + gb->debug_next_command = true; + gb->debug_call_depth = 0; + break; + case 'f': + gb->debug_stopped = false; + gb->debug_fin_command = true; + gb->debug_call_depth = 0; + break; + case 's': + break; + case 'r': + gb_log(gb, "AF = %04x\n", gb->registers[GB_REGISTER_AF]); + gb_log(gb, "BC = %04x\n", gb->registers[GB_REGISTER_BC]); + gb_log(gb, "DE = %04x\n", gb->registers[GB_REGISTER_DE]); + gb_log(gb, "HL = %04x\n", gb->registers[GB_REGISTER_HL]); + gb_log(gb, "SP = %04x\n", gb->registers[GB_REGISTER_SP]); + gb_log(gb, "PC = %04x\n", gb->pc); + gb_log(gb, "TIMA = %d/%lu\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles); + gb_log(gb, "Display Controller: LY = %d/%lu\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456); + goto next_command; + case 'x': + { + bool error; + unsigned short addr = debugger_evaluate(gb, input + 1, (unsigned int)strlen(input + 1), &error); + if (!error) { + gb_log(gb, "%4x: ", addr); + for (int i = 0; i < 16; i++) { + gb_log(gb, "%02x ", read_memory(gb, addr + i)); + } + gb_log(gb, "\n"); + } + goto next_command; + } + case 'b': + { + bool error; + unsigned short result = debugger_evaluate(gb, input + 1, (unsigned int)strlen(input + 1), &error); + if (!error) { + gb_log(gb, "Breakpoint moved to %04x\n", gb->breakpoint = result); + } + goto next_command; + } + + case 'p': + { + bool error; + unsigned short result = debugger_evaluate(gb, input + 1, (unsigned int)strlen(input + 1), &error); + if (!error) { + gb_log(gb, "=%04x\n", result); + } + goto next_command; + } + + default: + goto next_command; + } + free(input); + } +} \ No newline at end of file diff --git a/Core/debugger.h b/Core/debugger.h new file mode 100644 index 0000000..949523a --- /dev/null +++ b/Core/debugger.h @@ -0,0 +1,7 @@ +#ifndef debugger_h +#define debugger_h +#include "gb.h" + +void debugger_run(GB_gameboy_t *gb); + +#endif /* debugger_h */ diff --git a/Core/display.c b/Core/display.c new file mode 100644 index 0000000..0888393 --- /dev/null +++ b/Core/display.c @@ -0,0 +1,384 @@ +#include +#include +#include +#include +#include +#include "gb.h" +#include "display.h" + +#pragma pack(push, 1) +typedef struct { + unsigned char y; + unsigned char x; + unsigned char tile; + unsigned char flags; +} GB_sprite_t; +#pragma pack(pop) + +static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y) +{ + /* + Bit 7 - LCD Display Enable (0=Off, 1=On) + Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF) + Bit 5 - Window Display Enable (0=Off, 1=On) + Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF) + Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF) + Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16) + Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On) + Bit 0 - BG Display (for CGB see below) (0=Off, 1=On) + */ + unsigned short map = 0x1800; + unsigned char tile = 0; + unsigned char attributes = 0; + unsigned char sprite_palette = 0; + unsigned short tile_address = 0; + unsigned char background_pixel = 0, sprite_pixel = 0; + GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + unsigned char sprites_in_line = 0; + bool lcd_8_16_mode = (gb->io_registers[GB_IO_LCDC] & 4) != 0; + bool sprites_enabled = (gb->io_registers[GB_IO_LCDC] & 2) != 0; + unsigned char lowest_sprite_x = 0xFF; + bool use_obp1 = false, priority = false; + bool in_window = false; + if (gb->effective_window_enabled && (gb->io_registers[GB_IO_LCDC] & 0x20)) { /* Window Enabled */ + if (y >= gb->effective_window_y && x + 7 >= gb->io_registers[GB_IO_WX]) { + in_window = true; + } + } + + if (sprites_enabled) { + // Loop all sprites + for (unsigned char i = 40; i--; sprite++) { + int sprite_y = sprite->y - 16; + int sprite_x = sprite->x - 8; + // Is sprite in our line? + if (sprite_y <= y && sprite_y + (lcd_8_16_mode? 16:8) > y) { + unsigned char tile_x, tile_y, current_sprite_pixel; + unsigned short line_address; + // Limit to 10 sprites in one scan line. + if (++sprites_in_line == 11) break; + // Does not overlap our pixel. + if (sprite_x > x || sprite_x + 8 <= x) continue; + tile_x = x - sprite_x; + tile_y = y - sprite_y; + if (sprite->flags & 0x20) tile_x = 7 - tile_x; + if (sprite->flags & 0x40) tile_y = (lcd_8_16_mode? 15:7) - tile_y; + line_address = (lcd_8_16_mode? sprite->tile & 0xFE : sprite->tile) * 0x10 + tile_y * 2; + if (gb->cgb_mode && (sprite->flags & 0x8)) { + line_address += 0x2000; + } + current_sprite_pixel = (((gb->vram[line_address ] >> ((~tile_x)&7)) & 1 ) | + ((gb->vram[line_address + 1] >> ((~tile_x)&7)) & 1) << 1 ); + /* From Pandocs: + When sprites with different x coordinate values overlap, the one with the smaller x coordinate + (closer to the left) will have priority and appear above any others. This applies in Non CGB Mode + only. When sprites with the same x coordinate values overlap, they have priority according to table + ordering. (i.e. $FE00 - highest, $FE04 - next highest, etc.) In CGB Mode priorities are always + assigned like this. + */ + if (current_sprite_pixel != 0) { + if (!gb->cgb_mode && sprite->x >= lowest_sprite_x) { + break; + } + sprite_pixel = current_sprite_pixel; + lowest_sprite_x = sprite->x; + use_obp1 = (sprite->flags & 0x10) != 0; + sprite_palette = sprite->flags & 7; + priority = (sprite->flags & 0x80) != 0; + if (gb->cgb_mode) { + break; + } + } + } + } + } + + if (in_window) { + x -= gb->io_registers[GB_IO_WX] - 7; + y -= gb->effective_window_y; + } + else { + x += gb->io_registers[GB_IO_SCX]; + y += gb->io_registers[GB_IO_SCY]; + } + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !in_window) { + map = 0x1C00; + } + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && in_window) { + map = 0x1C00; + } + tile = gb->vram[map + x/8 + y/8 * 32]; + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x80) { + priority = true; + } + + if (!priority && sprite_pixel) { + if (!gb->cgb_mode) { + sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3; + sprite_palette = use_obp1; + } + return gb->sprite_palletes_rgb[sprite_palette * 4 + sprite_pixel]; + } + + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = tile * 0x10; + } + else { + tile_address = (signed char) tile * 0x10 + 0x1000; + } + if (attributes & 0x8) { + tile_address += 0x2000; + } + + if (attributes & 0x20) { + x = ~x; + } + + if (attributes & 0x40) { + y = ~y; + } + + background_pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1 ); + + if (priority && sprite_pixel && !background_pixel) { + if (!gb->cgb_mode) { + sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3; + sprite_palette = use_obp1; + } + return gb->sprite_palletes_rgb[sprite_palette * 4 + sprite_pixel]; + } + + if (!gb->cgb_mode) { + background_pixel = ((gb->io_registers[GB_IO_BGP] >> (background_pixel << 1)) & 3); + } + + return gb->background_palletes_rgb[(attributes & 7) * 4 + background_pixel]; +} + +// Todo: FPS capping should not be related to vblank, as the display is not always on, and this causes "jumps" +// when switching the display on and off. +void display_vblank(GB_gameboy_t *gb) +{ + _Static_assert(CLOCKS_PER_SEC == 1000000, "CLOCKS_PER_SEC != 1000000"); + + /* Called every Gameboy vblank. Does FPS-capping and calls user's vblank callback if Turbo Mode allows. */ + if (gb->turbo) { + struct timeval now; + gettimeofday(&now, NULL); + signed long nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; + if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) { + return; + } + gb->last_vblank = nanoseconds; + } + + /* + static long start = 0; + static long last = 0; + static long frames = 0; + + if (last == 0) { + last = time(NULL); + } + + if (last != time(NULL)) { + last = time(NULL); + if (start == 0) { + start = last; + frames = 0; + } + printf("Average FPS: %f\n", frames / (double)(last - start)); + } + frames++; + */ + + gb->vblank_callback(gb); + if (!gb->turbo) { + struct timeval now; + struct timespec sleep = {0,}; + gettimeofday(&now, NULL); + signed long nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; + if (labs(nanoseconds - gb->last_vblank) < FRAME_LENGTH ) { + sleep.tv_nsec = (FRAME_LENGTH + gb->last_vblank - nanoseconds); + nanosleep(&sleep, NULL); + + gb->last_vblank += FRAME_LENGTH; + } + else { + gb->last_vblank = nanoseconds; + } + } +} + +static inline unsigned char scale_channel(unsigned char x) +{ + x &= 0x1f; + return (x << 3) | (x >> 2); +} + +void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char index) +{ + unsigned char *palette_data = background_palette? gb->background_palletes_data : gb->sprite_palletes_data; + unsigned short color = palette_data[index & ~1] | (palette_data[index | 1] << 8); + + // No need to &, scale channel does that. + unsigned char r = scale_channel(color); + unsigned char g = scale_channel(color >> 5); + unsigned char b = scale_channel(color >> 10); + assert (gb->rgb_encode_callback); + (background_palette? gb->background_palletes_rgb : gb->sprite_palletes_rgb)[index / 2] = gb->rgb_encode_callback(gb, r, g, b); +} + +void display_run(GB_gameboy_t *gb) +{ + /* + + Display controller bug: For some reason, the OAM STAT interrupt is called, as expected, for LY = 0..143. + However, it is also called from LY = 151! (The last LY is 153! Wonder why is it 151...). + + Todo: This discussion in NESDev proves this theory incorrect: + http://forums.nesdev.com/viewtopic.php?f=20&t=13727 + Seems like there was a bug in one of my test ROMs. + This behavior needs to be corrected. + */ + unsigned char last_mode = gb->io_registers[GB_IO_STAT] & 3; + + if (gb->display_cycles >= LCDC_PERIOD) { + /* VBlank! */ + gb->display_cycles -= LCDC_PERIOD; + gb->ly151_bug_oam = false; + gb->ly151_bug_hblank = false; + display_vblank(gb); + } + + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + /* LCD is disabled, do nothing */ + gb->io_registers[GB_IO_STAT] &= ~3; + return; + } + + gb->io_registers[GB_IO_STAT] &= ~3; + + /* + Each line is 456 cycles, approximately: + Mode 2 - 80 cycles + Mode 3 - 172 cycles + Mode 0 - 204 cycles + + Todo: Mode lengths are not constants??? + */ + + gb->io_registers[GB_IO_LY] = gb->display_cycles / 456; + + bool previous_coincidence_flag = gb->io_registers[GB_IO_STAT] & 4; + + gb->io_registers[GB_IO_STAT] &= ~4; + if (gb->io_registers[GB_IO_LY] == gb->io_registers[GB_IO_LYC]) { + gb->io_registers[GB_IO_STAT] |= 4; + if ((gb->io_registers[GB_IO_STAT] & 0x40) && !previous_coincidence_flag) { /* User requests an interrupt on coincidence*/ + gb->io_registers[GB_IO_IF] |= 2; + } + } + + /* Todo: This behavior is seen in BGB and it fixes some ROMs with delicate timing, such as Hitman's 8bit. + This should be verified to be correct on a real gameboy. */ + if (gb->io_registers[GB_IO_LY] == 153 && gb->display_cycles % 456 > 8) { + gb->io_registers[GB_IO_LY] = 0; + } + + if (gb->display_cycles >= 456 * 144) { /* VBlank */ + gb->io_registers[GB_IO_STAT] |= 1; /* Set mode to 1 */ + gb->effective_window_enabled = false; + gb->effective_window_y = 0xFF; + + if (last_mode != 1) { + if (gb->io_registers[GB_IO_STAT] & 16) { /* User requests an interrupt on VBlank*/ + gb->io_registers[GB_IO_IF] |= 2; + } + gb->io_registers[GB_IO_IF] |= 1; + } + + // LY = 151 interrupt bug + if (gb->io_registers[GB_IO_LY] == 151) { + if (gb->display_cycles % 456 < 80) { // Mode 2 + if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->ly151_bug_oam) { /* User requests an interrupt on Mode 2 */ + gb->io_registers[GB_IO_IF] |= 2; + } + gb->ly151_bug_oam = true; + } + if (gb->display_cycles % 456 < 80 + 172) { /* Mode 3 */ + // Nothing to do + } + else { /* Mode 0 */ + if (gb->io_registers[GB_IO_STAT] & 8 && !gb->ly151_bug_hblank) { /* User requests an interrupt on Mode 0 */ + gb->io_registers[GB_IO_IF] |= 2; + } + gb->ly151_bug_hblank = true; + } + } + + return; + } + + // Todo: verify this window behavior. It is assumed from the expected behavior of 007 - The World Is Not Enough. + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_LY] == gb->io_registers[GB_IO_WY]) { + gb->effective_window_enabled = true; + } + + if (gb->display_cycles % 456 < 80) { /* Mode 2 */ + gb->io_registers[GB_IO_STAT] |= 2; /* Set mode to 2 */ + if (last_mode != 2) { + if (gb->io_registers[GB_IO_STAT] & 0x20) { /* User requests an interrupt on Mode 2 */ + gb->io_registers[GB_IO_IF] |= 2; + } + + /* User requests an interrupt on LY=LYC*/ + if (gb->io_registers[GB_IO_STAT] & 64 && gb->io_registers[GB_IO_STAT] & 4) { + gb->io_registers[GB_IO_IF] |= 2; + } + } + /* See above comment about window behavior. */ + if (gb->effective_window_enabled && gb->effective_window_y == 0xFF) { + gb->effective_window_y = gb->io_registers[GB_IO_LY]; + } + /* Todo: Figure out how the Gameboy handles in-line changes to SCX */ + gb->line_x_bias = - (gb->io_registers[GB_IO_SCX] & 0x7); + gb->previous_lcdc_x = gb->line_x_bias; + return; + } + + signed short current_lcdc_x = ((gb->display_cycles % 456 - 80) & ~7) + gb->line_x_bias; + for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { + if (gb->previous_lcdc_x >= 160) { + continue; + } + if (gb->previous_lcdc_x < 0) { + continue; + } + gb->screen[gb->io_registers[GB_IO_LY] * 160 + gb->previous_lcdc_x] = + get_pixel(gb, gb->previous_lcdc_x, gb->io_registers[GB_IO_LY]); + } + + if (gb->display_cycles % 456 < 80 + 172) { /* Mode 3 */ + gb->io_registers[GB_IO_STAT] |= 3; /* Set mode to 3 */ + return; + } + + /* if (gb->display_cycles % 456 < 80 + 172 + 204) */ { /* Mode 0*/ + if (last_mode != 0) { + if (gb->io_registers[GB_IO_STAT] & 8) { /* User requests an interrupt on Mode 0 */ + gb->io_registers[GB_IO_IF] |= 2; + } + if (gb->hdma_on_hblank) { + gb->hdma_on = true; + gb->hdma_cycles = 0; + } + } + return; + } +} diff --git a/Core/display.h b/Core/display.h new file mode 100644 index 0000000..3d9ebd2 --- /dev/null +++ b/Core/display.h @@ -0,0 +1,7 @@ +#ifndef display_h +#define display_h + +#include "gb.h" +void display_run(GB_gameboy_t *gb); +void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char index); +#endif /* display_h */ diff --git a/Core/gb.c b/Core/gb.c new file mode 100644 index 0000000..9fbfffd --- /dev/null +++ b/Core/gb.c @@ -0,0 +1,444 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gb.h" +#include "memory.h" +#include "timing.h" +#include "z80_cpu.h" +#include "joypad.h" +#include "display.h" +#include "debugger.h" + +static const GB_cartridge_t cart_defs[256] = { + // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type + /* MBC RAM BAT. RTC RUMB. */ + { NO_MBC, false, false, false, false}, // 00h ROM ONLY + { MBC1 , false, false, false, false}, // 01h MBC1 + { MBC1 , true , false, false, false}, // 02h MBC1+RAM + { MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY + [5] = + { MBC2 , true , false, false, false}, // 05h MBC2 + { MBC2 , true , true , false, false}, // 06h MBC2+BATTERY + [8] = + { NO_MBC, true , false, false, false}, // 08h ROM+RAM + { NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + [0xB] = + // Todo: What are these? + { NO_MBC, false, false, false, false}, // 0Bh MMM01 + { NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + [0xF] = + { MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { MBC3 , false, false, false, false}, // 11h MBC3 + { MBC3 , true , false, false, false}, // 12h MBC3+RAM + { MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY + [0x15] = + // Todo: Do these exist? + { MBC4 , false, false, false, false}, // 15h MBC4 + { MBC4 , true , false, false, false}, // 16h MBC4+RAM + { MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY + [0x19] = + { MBC5 , false, false, false, false}, // 19h MBC5 + { MBC5 , true , false, false, false}, // 1Ah MBC5+RAM + { MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE + { MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0xFC] = + // Todo: What are these? + { NO_MBC, false, false, false, false}, // FCh POCKET CAMERA + { NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 + { NO_MBC, false, false, false, false}, // FEh HuC3 + { NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY +}; + +void gb_attributed_logv(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + if (string) { + if (gb->log_callback) { + gb->log_callback(gb, string, attributes); + } + else { + /* Todo: Add ANSI escape sequences for attributed text */ + printf("%s", string); + } + } + free(string); +} + +void gb_attributed_log(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + gb_attributed_logv(gb, attributes, fmt, args); + va_end(args); +} + +void gb_log(GB_gameboy_t *gb,const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + gb_attributed_logv(gb, 0, fmt, args); + va_end(args); +} + +static char *default_input_callback(GB_gameboy_t *gb) +{ + char *expression = NULL; + size_t size = 0; + printf(">"); + getline(&expression, &size, stdin); + if (!expression) { + return strdup(""); + } + return expression; +} + +void gb_init(GB_gameboy_t *gb) +{ + memset(gb, 0, sizeof(*gb)); + gb->magic = (uintptr_t)'SAME'; + gb->version = GB_STRUCT_VERSION; + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + + struct timeval now; + gettimeofday(&now, NULL); + gb->last_vblank = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;; + + gb->mbc_rom_bank = 1; + gb->last_rtc_second = time(NULL); + gb->last_vblank = clock(); + gb->breakpoint = 0xFFFF; + gb->cgb_ram_bank = 1; + + /* Todo: this bypasses the rgb encoder because it is not set yet. */ + gb->sprite_palletes_rgb[4] = gb->sprite_palletes_rgb[0] = gb->background_palletes_rgb[0] = 0xFFFFFFFF; + gb->sprite_palletes_rgb[5] = gb->sprite_palletes_rgb[1] = gb->background_palletes_rgb[1] = 0xAAAAAAAA; + gb->sprite_palletes_rgb[6] = gb->sprite_palletes_rgb[2] = gb->background_palletes_rgb[2] = 0x55555555; + gb->input_callback = default_input_callback; + gb->cartridge_type = &cart_defs[0]; // Default cartridge type +} + +void gb_init_cgb(GB_gameboy_t *gb) +{ + memset(gb, 0, sizeof(*gb)); + gb->magic = (uintptr_t)'SAME'; + gb->version = GB_STRUCT_VERSION; + gb->ram = malloc(gb->ram_size = 0x2000 * 8); + gb->vram = malloc(gb->vram_size = 0x2000 * 2); + gb->is_cgb = true; + gb->cgb_mode = true; + + struct timeval now; + gettimeofday(&now, NULL); + gb->last_vblank = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; + + gb->mbc_rom_bank = 1; + gb->last_rtc_second = time(NULL); + gb->last_vblank = clock(); + gb->breakpoint = 0xFFFF; + gb->cgb_ram_bank = 1; + gb->input_callback = default_input_callback; + gb->cartridge_type = &cart_defs[0]; // Default cartridge type +} + +void gb_free(GB_gameboy_t *gb) +{ + if (gb->ram) { + free(gb->ram); + } + if (gb->vram) { + free(gb->vram); + } + if (gb->mbc_ram) { + free(gb->mbc_ram); + } + if (gb->rom) { + free(gb->rom); + } + if (gb->audio_buffer) { + free(gb->audio_buffer); + } +} + +int gb_load_bios(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return errno; + fread(gb->bios, sizeof(gb->bios), 1, f); + fclose(f); + return 0; +} + +int gb_load_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return errno; + fseek(f, 0, SEEK_END); + gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */ + fseek(f, 0, SEEK_SET); + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ + fread(gb->rom, gb->rom_size, 1, f); + fclose(f); + gb->cartridge_type = &cart_defs[gb->rom[0x147]]; + if (gb->cartridge_type->has_ram) { + static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; + gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; + gb->mbc_ram = malloc(gb->mbc_ram_size); + } + + return 0; +} + +/* Todo: we need a sane and protable save state format. */ +int gb_save_state(GB_gameboy_t *gb, const char *path) +{ + GB_gameboy_t save; + memcpy(&save, gb, offsetof(GB_gameboy_t, first_unsaved_data)); + save.cartridge_type = NULL; // Kept from load_rom + save.rom = NULL; // Kept from load_rom + save.rom_size = 0; // Kept from load_rom + save.mbc_ram = NULL; + save.ram = NULL; + save.vram = NULL; + save.screen = NULL; // Kept from user + save.audio_buffer = NULL; // Kept from user + save.buffer_size = 0; // Kept from user + save.sample_rate = 0; // Kept from user + save.audio_position = 0; // Kept from previous state + save.vblank_callback = NULL; + save.user_data = NULL; + memset(save.keys, 0, sizeof(save.keys)); // Kept from user + + FILE *f = fopen(path, "w"); + if (!f) { + return errno; + } + + if (fwrite(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) { + fclose(f); + return EIO; + } + + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + fclose(f); + return EIO; + } + + if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { + fclose(f); + return EIO; + } + + if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { + fclose(f); + return EIO; + } + + errno = 0; + fclose(f); + return errno; +} + +int gb_load_state(GB_gameboy_t *gb, const char *path) +{ + GB_gameboy_t save; + + FILE *f = fopen(path, "r"); + if (!f) { + return errno; + } + + if (fread(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) { + fclose(f); + return EIO; + } + + save.cartridge_type = gb->cartridge_type; + save.rom = gb->rom; + save.rom_size = gb->rom_size; + save.mbc_ram = gb->mbc_ram; + save.ram = gb->ram; + save.vram = gb->vram; + save.screen = gb->screen; + save.audio_buffer = gb->audio_buffer; + save.buffer_size = gb->buffer_size; + save.sample_rate = gb->sample_rate; + save.audio_position = gb->audio_position; + save.vblank_callback = gb->vblank_callback; + save.user_data = gb->user_data; + memcpy(save.keys, gb->keys, sizeof(save.keys)); + + if (gb->magic != save.magic) { + gb_log(gb, "File is not a save state, or is from an incompatible operating system.\n"); + fclose(f); + return -1; + } + + if (gb->version != save.version) { + gb_log(gb, "Save state is for a different version of SameBoy.\n"); + fclose(f); + return -1; + } + + if (gb->mbc_ram_size != save.mbc_ram_size) { + gb_log(gb, "Save state has non-matching MBC RAM size.\n"); + fclose(f); + return -1; + } + + if (gb->ram_size != save.ram_size) { + gb_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n"); + fclose(f); + return -1; + } + + if (gb->vram_size != save.vram_size) { + gb_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n"); + fclose(f); + return -1; + } + + if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + fclose(f); + return EIO; + } + + if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { + fclose(f); + return EIO; + } + + if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { + fclose(f); + return EIO; + } + + memcpy(gb, &save, offsetof(GB_gameboy_t, first_unsaved_data)); + errno = 0; + fclose(f); + return errno; +} + +int gb_save_battery(GB_gameboy_t *gb, const char *path) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + FILE *f = fopen(path, "w"); + if (!f) { + return errno; + } + + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + fclose(f); + return EIO; + } + if (gb->cartridge_type->has_rtc) { + if (fwrite(gb->rtc_data, 1, sizeof(gb->rtc_data), f) != sizeof(gb->rtc_data)) { + fclose(f); + return EIO; + } + + if (fwrite(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) { + fclose(f); + return EIO; + } + } + + errno = 0; + fclose(f); + return errno; +} + +/* Loading will silently stop if the format is incomplete */ +void gb_load_battery(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) { + return; + } + + if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + goto reset_rtc; + } + + if (fread(gb->rtc_data, 1, sizeof(gb->rtc_data), f) != sizeof(gb->rtc_data)) { + goto reset_rtc; + } + + if (fread(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) { + goto reset_rtc; + } + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +reset_rtc: + gb->last_rtc_second = time(NULL); + gb->rtc_high |= 0x80; /* This gives the game a hint that the clock should be reset. */ +exit: + fclose(f); + return; +} + +void gb_run(GB_gameboy_t *gb) +{ + update_joyp(gb); + debugger_run(gb); + cpu_run(gb); + display_run(gb); +} + +void gb_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) +{ + gb->screen = output; +} + +void gb_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback) +{ + gb->vblank_callback = callback; +} + +void gb_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback) +{ + gb->log_callback = callback; +} + +void gb_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ + gb->input_callback = callback; +} + +void gb_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) +{ + gb->rgb_encode_callback = callback; +} + +void gb_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) +{ + if (gb->audio_buffer) { + free(gb->audio_buffer); + } + gb->buffer_size = sample_rate / 25; // 40ms delay + gb->audio_buffer = malloc(gb->buffer_size * 2); + gb->sample_rate = sample_rate; + gb->audio_position = 0; +} diff --git a/Core/gb.h b/Core/gb.h new file mode 100644 index 0000000..280e139 --- /dev/null +++ b/Core/gb.h @@ -0,0 +1,304 @@ +#ifndef gb_h +#define gb_h +#include +#include +#include +#include +#include "apu.h" + +#define GB_STRUCT_VERSION 6 + +enum { + GB_REGISTER_AF, + GB_REGISTER_BC, + GB_REGISTER_DE, + GB_REGISTER_HL, + GB_REGISTER_SP, + GB_REGISTERS_16_BIT /* Count */ +}; + +/* Todo: Actually use these! */ +enum { + GB_CARRY_FLAG = 16, + GB_HALF_CARRY_FLAG = 32, + GB_SUBSTRACT_FLAG = 64, + GB_ZERO_FLAG = 128, +}; + +enum { + /* Joypad and Serial */ + GB_IO_JOYP = 0x00, // Joypad (R/W) + GB_IO_SB = 0x01, // Serial transfer data (R/W) + GB_IO_SC = 0x02, // Serial Transfer Control (R/W) + + /* Missing */ + + /* Timers */ + GB_IO_DIV = 0x04, // Divider Register (R/W) + GB_IO_TIMA = 0x05, // Timer counter (R/W) + GB_IO_TMA = 0x06, // Timer Modulo (R/W) + GB_IO_TAC = 0x07, // Timer Control (R/W) + + /* Missing */ + + GB_IO_IF = 0x0f, // Interrupt Flag (R/W) + + /* Sound */ + GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W) + GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W) + GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W) + GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only) + GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W) + GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W) + GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W) + GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W) + GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W) + GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W) + GB_IO_NR31 = 0x1b, // Channel 3 Sound Length + GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W) + GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W) + + GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W) + + /* Missing */ + + GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W) + GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W) + GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W) + GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W) + GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W) + GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W) + GB_IO_NR52 = 0x26, // Sound on/off + + /* Missing */ + + GB_IO_WAV_START = 0x30, // Wave pattern start + GB_IO_WAV_END = 0x3f, // Wave pattern end + + /* Graphics */ + GB_IO_LCDC = 0x40, // LCD Control (R/W) + GB_IO_STAT = 0x41, // LCDC Status (R/W) + GB_IO_SCY = 0x42, // Scroll Y (R/W) + GB_IO_SCX = 0x43, // Scroll X (R/W) + GB_IO_LY = 0x44, // LCDC Y-Coordinate (R) + GB_IO_LYC = 0x45, // LY Compare (R/W) + GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W) + GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only + GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only + GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only + GB_IO_WY = 0x4a, // Window Y Position (R/W) + GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W) + + /* Missing */ + + /* General CGB features */ + GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch + + /* Missing */ + + GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank + GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping + + /* CGB DMA */ + GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High + GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low + GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High + GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low + GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start + + /* IR */ + GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port + + /* Missing */ + + /* CGB Paletts */ + GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index + GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data + GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index + GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data + + GB_IO_DMG_EMULATION = 0x6c, // (FEh) Bit 0 (Read/Write) - CGB Mode Only + + /* Missing */ + + GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank + GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write) + GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write) + GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only + GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write) + GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes + GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes + GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only +}; + +#define LCDC_PERIOD 70224 +#define CPU_FREQUENCY 0x400000 +#define DIV_CYCLES (0x100) +#define FRAME_LENGTH 16742706 // in nanoseconds + +typedef enum { + GB_LOG_BOLD = 1, + GB_LOG_DASHED_UNDERLINE = 2, + GB_LOG_UNDERLINE = 4, + GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE +} gb_log_attributes; + +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; +typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, gb_log_attributes attributes); +typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); +typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b); + +typedef struct { + enum { + NO_MBC, + MBC1, + MBC2, + MBC3, + MBC4, // Does this exist??? + MBC5, + } mbc_type; + bool has_ram; + bool has_battery; + bool has_rtc; + bool has_rumble; +} GB_cartridge_t; + +typedef struct GB_gameboy_s{ + uintptr_t magic; // States are currently platform dependent + int version; // and version dependent + /* Registers */ + unsigned short pc; + unsigned short registers[GB_REGISTERS_16_BIT]; + bool ime; + unsigned char interrupt_enable; + + /* CPU and General Hardware Flags*/ + bool cgb_mode; + bool is_cgb; + bool cgb_double_speed; + bool halted; + bool stopped; + + /* HDMA */ + bool hdma_on; + bool hdma_on_hblank; + unsigned char hdma_steps_left; + unsigned short hdma_cycles; + unsigned short hdma_current_src, hdma_current_dest; + + /* Memory */ + unsigned char *rom; + size_t rom_size; + unsigned short mbc_rom_bank; + + const GB_cartridge_t *cartridge_type; + unsigned char *mbc_ram; + unsigned char mbc_ram_bank; + size_t mbc_ram_size; + bool mbc_ram_enable; + bool mbc_ram_banking; + + unsigned char *ram; + unsigned long ram_size; // Different between CGB and DMG + unsigned char cgb_ram_bank; + + unsigned char hram[0xFFFF - 0xFF80]; + unsigned char io_registers[0x80]; + + /* Video Display */ + unsigned char *vram; + unsigned long vram_size; // Different between CGB and DMG + unsigned char cgb_vram_bank; + unsigned char oam[0xA0]; + unsigned char background_palletes_data[0x40]; + unsigned char sprite_palletes_data[0x40]; + uint32_t background_palletes_rgb[0x20]; + uint32_t sprite_palletes_rgb[0x20]; + bool ly151_bug_oam; + bool ly151_bug_hblank; + signed short previous_lcdc_x; + signed short line_x_bias; + bool effective_window_enabled; + unsigned char effective_window_y; + + unsigned char bios[0x900]; + bool bios_finished; + + /* Timing */ + signed long last_vblank; + unsigned long display_cycles; + unsigned long div_cycles; + unsigned long tima_cycles; + unsigned long dma_cycles; + double apu_cycles; + + /* APU */ + GB_apu_t apu; + int16_t *audio_buffer; + unsigned int buffer_size; + unsigned int sample_rate; + unsigned int audio_position; + volatile bool audio_copy_in_progress; + bool audio_stream_started; // detects first copy request to minimize lag + + /* I/O */ + uint32_t *screen; + GB_vblank_callback_t vblank_callback; + + bool keys[8]; + + /* RTC */ + union { + struct { + unsigned char rtc_seconds; + unsigned char rtc_minutes; + unsigned char rtc_hours; + unsigned char rtc_days; + unsigned char rtc_high; + }; + unsigned char rtc_data[5]; + }; + time_t last_rtc_second; + + /* Unsaved User */ + struct {} first_unsaved_data; + bool turbo; + bool debug_stopped; + unsigned short breakpoint; + GB_log_callback_t log_callback; + GB_input_callback_t input_callback; + GB_rgb_encode_callback_t rgb_encode_callback; + void *user_data; + int debug_call_depth; + bool debug_fin_command, debug_next_command; + +} GB_gameboy_t; + +#ifndef __printflike +/* Missing from Linux headers. */ +#define __printflike(fmtarg, firstvararg) \ +__attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#endif + +void gb_init(GB_gameboy_t *gb); +void gb_init_cgb(GB_gameboy_t *gb); +void gb_free(GB_gameboy_t *gb); +int gb_load_bios(GB_gameboy_t *gb, const char *path); +int gb_load_rom(GB_gameboy_t *gb, const char *path); +int gb_save_battery(GB_gameboy_t *gb, const char *path); +void gb_load_battery(GB_gameboy_t *gb, const char *path); +int gb_save_state(GB_gameboy_t *gb, const char *path); +int gb_load_state(GB_gameboy_t *gb, const char *path); +void gb_run(GB_gameboy_t *gb); +void gb_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); +void gb_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); +void gb_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback); +void gb_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); +void gb_attributed_log(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, ...) __printflike(3, 4); +void gb_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void gb_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate); +void gb_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); + +#endif /* gb_h */ diff --git a/Core/joypad.c b/Core/joypad.c new file mode 100644 index 0000000..aee016e --- /dev/null +++ b/Core/joypad.c @@ -0,0 +1,50 @@ +#include +#include "gb.h" +#include "joypad.h" + +void update_joyp(GB_gameboy_t *gb) +{ + unsigned char key_selection = 0; + unsigned char previous_state = 0; + + /* Todo: add delay to key selection */ + previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + switch (key_selection) { + case 3: + /* Nothing is wired, all up */ + gb->io_registers[GB_IO_JOYP] |= 0x0F; + break; + + case 2: + /* Direction keys */ + for (unsigned char i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i; + } + break; + + case 1: + /* Other keys */ + for (unsigned char i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i; + } + break; + + case 0: + /* Todo: verifiy this is correct */ + for (unsigned char i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i; + } + break; + + default: + break; + } + if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { + /* Todo: disable when emulating CGB */ + gb->io_registers[GB_IO_IF] |= 0x10; + } + gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support +} \ No newline at end of file diff --git a/Core/joypad.h b/Core/joypad.h new file mode 100644 index 0000000..c148e3c --- /dev/null +++ b/Core/joypad.h @@ -0,0 +1,8 @@ +#ifndef joypad_h +#define joypad_h +#include "gb.h" + +void update_joyp(GB_gameboy_t *gb); +void update_keys_status(GB_gameboy_t *gb); + +#endif /* joypad_h */ diff --git a/Core/memory.c b/Core/memory.c new file mode 100644 index 0000000..30a366e --- /dev/null +++ b/Core/memory.c @@ -0,0 +1,531 @@ +#include +#include +#include "gb.h" +#include "joypad.h" +#include "display.h" +#include "memory.h" + +typedef unsigned char GB_read_function_t(GB_gameboy_t *gb, unsigned short addr); +typedef void GB_write_function_t(GB_gameboy_t *gb, unsigned short addr, unsigned char value); + +static unsigned char read_rom(GB_gameboy_t *gb, unsigned short addr) +{ + if (addr < 0x100 && !gb->bios_finished) { + return gb->bios[addr]; + } + + if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->bios_finished) { + return gb->bios[addr]; + } + + if (!gb->rom_size) { + return 0xFF; + } + return gb->rom[addr]; +} + +static unsigned char read_mbc_rom(GB_gameboy_t *gb, unsigned short addr) +{ + if (gb->mbc_rom_bank >= gb->rom_size / 0x4000) { + return 0xFF; + } + return gb->rom[(addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000]; +} + +static unsigned char read_vram(GB_gameboy_t *gb, unsigned short addr) +{ + if ((gb->io_registers[GB_IO_STAT] & 0x3) == 3) { + return 0xFF; + } + return gb->vram[(addr & 0x1FFF) + (unsigned short) gb->cgb_vram_bank * 0x2000]; +} + +static unsigned char read_mbc_ram(GB_gameboy_t *gb, unsigned short addr) +{ + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + /* RTC read */ + gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */ + return gb->rtc_data[gb->mbc_ram_bank - 8]; + } + unsigned int ram_index = (addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000; + if (!gb->mbc_ram_enable) + { + gb_log(gb, "Read from %02x:%04x (%06x) (Disabled MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); + return 0xFF; + } + if (ram_index >= gb->mbc_ram_size) { + gb_log(gb, "Read from %02x:%04x (%06x) (Unmapped MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); + return 0xFF; + } + return gb->mbc_ram[(addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000]; +} + +static unsigned char read_ram(GB_gameboy_t *gb, unsigned short addr) +{ + return gb->ram[addr & 0x0FFF]; +} + +static unsigned char read_banked_ram(GB_gameboy_t *gb, unsigned short addr) +{ + return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000]; +} + +static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) +{ + + if (addr < 0xFE00) { + return gb->ram[addr & 0x0FFF]; + } + + if (addr < 0xFEA0) { + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { + return 0xFF; + } + return gb->oam[addr & 0xFF]; + } + + if (addr < 0xFF00) { + /* Unusable, simulate Gameboy Color */ + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */ + return 0xFF; + } + return (addr & 0xF0) | ((addr >> 4) & 0xF); + } + + if (addr < 0xFF80) { + switch (addr & 0xFF) { + case GB_IO_JOYP: + case GB_IO_IF: + case GB_IO_DIV: + case GB_IO_TIMA: + case GB_IO_TMA: + case GB_IO_TAC: + case GB_IO_LCDC: + case GB_IO_STAT: + case GB_IO_SCY: + case GB_IO_SCX: + case GB_IO_LY: + case GB_IO_LYC: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_HDMA1: + case GB_IO_HDMA2: + case GB_IO_HDMA3: + case GB_IO_HDMA4: + case GB_IO_PCM_12: + case GB_IO_PCM_34: + case GB_IO_SB: + return gb->io_registers[addr & 0xFF]; + case GB_IO_HDMA5: + return gb->io_registers[GB_IO_HDMA5] | 0x7F; + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return 0xFF; + } + return gb->cgb_ram_bank | ~0x7; + case GB_IO_VBK: + if (!gb->cgb_mode) { + return 0xFF; + } + return gb->cgb_vram_bank | ~0x1; + + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!gb->is_cgb) { + return 0xFF; + } + return gb->io_registers[addr & 0xFF] | 0x40; + + case GB_IO_BGPD: + case GB_IO_OBPD: + { + if (!gb->is_cgb) { + return 0xFF; + } + unsigned char index_reg = (addr & 0xFF) - 1; + return ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palletes_data : + gb->sprite_palletes_data)[gb->io_registers[index_reg] & 0x3F]; + } + + case GB_IO_KEY1: + if (!gb->is_cgb) { + return 0xFF; + } + return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E); + + + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + return apu_read(gb, addr & 0xFF); + } + return 0xFF; + } + /* Hardware registers */ + return 0; + } + + if (addr == 0xFFFF) { + /* Interrupt Mask */ + return gb->interrupt_enable; + } + + /* HRAM */ + return gb->hram[addr - 0xFF80]; +} + +static GB_read_function_t * const read_map[] = +{ + read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */ + read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */ + read_vram, read_vram, /* 8XXX, 9XXX */ + read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */ + read_ram, read_banked_ram, /* CXXX, DXXX */ + read_high_memory, read_high_memory, /* EXXX FXXX */ +}; + +unsigned char read_memory(GB_gameboy_t *gb, unsigned short addr) +{ + if (addr < 0xFF00 && gb->dma_cycles) { + /* Todo: can we access IO registers during DMA? */ + return 0xFF; + } + return read_map[addr >> 12](gb, addr); +} + +static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + if (gb->cartridge_type->mbc_type == NO_MBC) return; + switch (addr >> 12) { + case 0: + case 1: + gb->mbc_ram_enable = value == 0x0a; + break; + case 2: + bank_low: + /* Bank number, lower bits */ + if (gb->cartridge_type->mbc_type == MBC1) { + value &= 0x1F; + } + if (gb->cartridge_type->mbc_type != MBC5 && !value) { + value++; + } + gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x100) | value; + break; + case 3: + if (gb->cartridge_type->mbc_type != MBC5) goto bank_low; + if (value > 1) { + gb_log(gb, "Bank overflow: [%x] <- %d\n", addr, value); + } + gb->mbc_rom_bank = (gb->mbc_rom_bank & 0xFF) | value << 8; + break; + case 4: + case 5: + if (gb->cartridge_type->mbc_type == MBC1) { + if (gb->mbc_ram_banking) { + gb->mbc_ram_bank = value & 0x3; + } + else { + gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x1F) | ((value & 0x3) << 5); + } + } + else { + gb->mbc_ram_bank = value; + } + break; + case 6: + case 7: + if (gb->cartridge_type->mbc_type == MBC1) { + value &= 1; + + if (value & !gb->mbc_ram_banking) { + gb->mbc_ram_bank = gb->mbc_rom_bank >> 5; + gb->mbc_rom_bank &= 0x1F; + } + else if (value & !gb->mbc_ram_banking) { + gb->mbc_rom_bank = gb->mbc_rom_bank | (gb->mbc_ram_bank << 5); + gb->mbc_ram_bank = 0; + } + + gb->mbc_ram_banking = value; + } + break; + } + + if (gb->cartridge_type->mbc_type != MBC5 && !gb->mbc_rom_bank) { + gb->mbc_rom_bank = 1; + } +} + +static void write_vram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + if ((gb->io_registers[GB_IO_STAT] & 0x3) == 3) { + //gb_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); + return; + } + gb->vram[(addr & 0x1FFF) + (unsigned short) gb->cgb_vram_bank * 0x2000] = value; +} + +static void write_mbc_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + if (gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + /* RTC write*/ + gb->rtc_data[gb->mbc_ram_bank - 8] = value; + gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */ + return; + } + unsigned int ram_index = (addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000; + if (!gb->mbc_ram_enable) + { + gb_log(gb, "Write to %02x:%04x (%06x) (Disabled MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); + return; + } + if (ram_index >= gb->mbc_ram_size) { + gb_log(gb, "Write to %02x:%04x (%06x) (Unmapped MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); + return; + } + gb->mbc_ram[(addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000] = value; +} + +static void write_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + gb->ram[addr & 0x0FFF] = value; +} + +static void write_banked_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value; +} + +static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + if (addr < 0xFE00) { + gb_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); + gb->ram[addr & 0x0FFF] = value; + return; + } + + if (addr < 0xFEA0) { + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { + return; + } + gb->oam[addr & 0xFF] = value; + return; + } + + if (addr < 0xFF00) { + gb_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr); + return; + } + + if (addr < 0xFF80) { + /* Hardware registers */ + switch (addr & 0xFF) { + + case GB_IO_TAC: + case GB_IO_SCX: + case GB_IO_IF: + case GB_IO_TIMA: + case GB_IO_TMA: + case GB_IO_SCY: + case GB_IO_LYC: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_HDMA1: + case GB_IO_HDMA2: + case GB_IO_HDMA3: + case GB_IO_HDMA4: + case GB_IO_SB: + gb->io_registers[addr & 0xFF] = value; + return; + + case GB_IO_LCDC: + if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { + gb->display_cycles = 0; + } + gb->io_registers[GB_IO_LCDC] = value; + return; + + case GB_IO_STAT: + /* Delete previous R/W bits */ + gb->io_registers[GB_IO_STAT] &= 7; + /* Set them by value */ + gb->io_registers[GB_IO_STAT] |= value & ~7; + /* Set unused bit to 1 */ + gb->io_registers[GB_IO_STAT] |= 0x80; + return; + + case GB_IO_DIV: + gb->io_registers[GB_IO_DIV] = 0; + return; + + case GB_IO_JOYP: + gb->io_registers[GB_IO_JOYP] &= 0x0F; + gb->io_registers[GB_IO_JOYP] |= value & 0xF0; + return; + + case GB_IO_BIOS: + gb->bios_finished = true; + return; + + case GB_IO_DMG_EMULATION: + // Todo: Can it be disabled? What about values other than 1? + gb->cgb_mode = false; + return; + + case GB_IO_DMA: + if (value <= 0xD0) { + for (unsigned char i = 0xA0; i--;) { + gb->oam[i] = read_memory(gb, (value << 8) + i); + } + } + /* Todo: measure this value */ + gb->dma_cycles = 640; + return; + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_ram_bank = value & 0x7; + if (!gb->cgb_ram_bank) { + gb->cgb_ram_bank++; + } + return; + case GB_IO_VBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_vram_bank = value & 0x1; + return; + + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!gb->is_cgb) { + return; + } + gb->io_registers[addr & 0xFF] = value; + return; + case GB_IO_BGPD: + case GB_IO_OBPD: + if (!gb->is_cgb) { + return; + } + unsigned char index_reg = (addr & 0xFF) - 1; + ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palletes_data : + gb->sprite_palletes_data)[gb->io_registers[index_reg] & 0x3F] = value; + palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F); + if (gb->io_registers[index_reg] & 0x80) { + gb->io_registers[index_reg]++; + gb->io_registers[index_reg] |= 0x80; + } + return; + case GB_IO_KEY1: + if (!gb->is_cgb) { + return; + } + gb->io_registers[GB_IO_KEY1] = value; + return; + + case GB_IO_HDMA5: + if ((value & 0x80) == 0 && gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + return; + } + gb->hdma_on = (value & 0x80) == 0; + gb->hdma_on_hblank = (value & 0x80) != 0; + gb->io_registers[GB_IO_HDMA5] = value; + gb->hdma_current_src = (gb->io_registers[GB_IO_HDMA1] << 8) | (gb->io_registers[GB_IO_HDMA2] & 0xF0); + gb->hdma_current_dest = (gb->io_registers[GB_IO_HDMA3] << 8) | (gb->io_registers[GB_IO_HDMA4] & 0xF0); + gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; + gb->hdma_cycles = 0; + return; + + case GB_IO_SC: + if ((value & 0x80) && (value & 0x1) ) { + gb->io_registers[GB_IO_SB] = 0xFF; + gb->io_registers[GB_IO_IF] |= 0x8; + } + return; + + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + apu_write(gb, addr & 0xFF, value); + return; + } + if (gb->io_registers[addr & 0xFF] != 0x37) { + gb_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr); + } + gb->io_registers[addr & 0xFF] = 0x37; + return; + } + } + + if (addr == 0xFFFF) { + /* Interrupt mask */ + gb->interrupt_enable = value; + return; + } + + /* HRAM */ + gb->hram[addr - 0xFF80] = value; +} + + + +static GB_write_function_t * const write_map[] = +{ + write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */ + write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */ + write_vram, write_vram, /* 8XXX, 9XXX */ + write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */ + write_ram, write_banked_ram, /* CXXX, DXXX */ + write_high_memory, write_high_memory, /* EXXX FXXX */ +}; + +void write_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +{ + if (addr < 0xFF00 && gb->dma_cycles) { + /* Todo: can we access IO registers during DMA? */ + return; + } + write_map[addr >> 12](gb, addr, value); +} + +void hdma_run(GB_gameboy_t *gb) +{ + if (!gb->hdma_on) return; + while (gb->hdma_cycles >= 8) { + gb->hdma_cycles -= 8; + // The CGB bios uses the dest in "absolute" space, while some games use it relative to VRAM. + // This "normalizes" the dest to the CGB address space. + gb->hdma_current_dest &= 0x1fff; + gb->hdma_current_dest |= 0x8000; + if ((gb->hdma_current_src < 0x8000 || (gb->hdma_current_src >= 0xa000 && gb->hdma_current_src < 0xe000))) { + for (unsigned char i = 0; i < 0x10; i++) { + write_memory(gb, gb->hdma_current_dest + i, read_memory(gb, gb->hdma_current_src + i)); + } + } + else { + gb->halted = false; + } + gb->hdma_current_src += 0x10; + gb->hdma_current_dest += 0x10; + if(--gb->hdma_steps_left == 0){ + gb->hdma_on = false; + gb->hdma_on_hblank = false; + gb->io_registers[GB_IO_HDMA5] &= 0x7F; + break; + } + if (gb->hdma_on_hblank) { + gb->hdma_on = false; + break; + } + } +} \ No newline at end of file diff --git a/Core/memory.h b/Core/memory.h new file mode 100644 index 0000000..bd46026 --- /dev/null +++ b/Core/memory.h @@ -0,0 +1,9 @@ +#ifndef memory_h +#define memory_h +#include "gb.h" + +unsigned char read_memory(GB_gameboy_t *gb, unsigned short addr); +void write_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value); +void hdma_run(GB_gameboy_t *gb); + +#endif /* memory_h */ diff --git a/Core/timing.c b/Core/timing.c new file mode 100644 index 0000000..3e797bf --- /dev/null +++ b/Core/timing.c @@ -0,0 +1,81 @@ +#include "gb.h" +#include "timing.h" +#include "memory.h" + +void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) +{ + // Affected by speed boost + if (gb->dma_cycles > cycles){ + gb->dma_cycles -= cycles; + } + else { + gb->dma_cycles = 0; + } + + if (gb->cgb_double_speed) { + cycles >>=1; + } + + // Not affected by speed boost + gb->hdma_cycles += cycles; + gb->display_cycles += cycles; + gb->div_cycles += cycles; + gb->tima_cycles += cycles; + gb->apu_cycles += cycles; + hdma_run(gb); + timers_run(gb); + apu_run(gb); +} + +void timers_run(GB_gameboy_t *gb) +{ + /* Standard Timers */ + static const unsigned long GB_TAC_RATIOS[] = {1024, 16, 64, 256}; + + if (gb->div_cycles >= DIV_CYCLES) { + gb->div_cycles -= DIV_CYCLES; + gb->io_registers[GB_IO_DIV]++; + } + + while (gb->tima_cycles >= GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3]) { + gb->tima_cycles -= GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3]; + if (gb->io_registers[GB_IO_TAC] & 4) { + gb->io_registers[GB_IO_TIMA]++; + if (gb->io_registers[GB_IO_TIMA] == 0) { + gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; + gb->io_registers[GB_IO_IF] |= 4; + } + } + } + + /* RTC */ + if (gb->display_cycles >= LCDC_PERIOD) { /* Time is a syscall and therefore is slow, so we update the RTC + only during vblanks. */ + if ((gb->rtc_high & 0x40) == 0) { /* is timer running? */ + time_t current_time = time(NULL); + while (gb->last_rtc_second < current_time) { + gb->last_rtc_second++; + if (++gb->rtc_seconds == 60) + { + gb->rtc_seconds = 0; + if (++gb->rtc_minutes == 60) + { + gb->rtc_minutes = 0; + if (++gb->rtc_hours == 24) + { + gb->rtc_hours = 0; + if (++gb->rtc_days == 0) + { + if (gb->rtc_high & 1) /* Bit 8 of days*/ + { + gb->rtc_high |= 0x80; /* Overflow bit */ + } + gb->rtc_high ^= 1; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Core/timing.h b/Core/timing.h new file mode 100644 index 0000000..0364d0d --- /dev/null +++ b/Core/timing.h @@ -0,0 +1,7 @@ +#ifndef timing_h +#define timing_h +#include "gb.h" + +void advance_cycles(GB_gameboy_t *gb, unsigned char cycles); +void timers_run(GB_gameboy_t *gb); +#endif /* timing_h */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c new file mode 100644 index 0000000..6af8afd --- /dev/null +++ b/Core/z80_cpu.c @@ -0,0 +1,1342 @@ +#include +#include +#include "z80_cpu.h" +#include "timing.h" +#include "memory.h" +#include "gb.h" + + +typedef void GB_opcode_t(GB_gameboy_t *gb, unsigned char opcode); + +static void ill(GB_gameboy_t *gb, unsigned char opcode) +{ + gb_log(gb, "Illegal Opcode. Halting."); + gb->interrupt_enable = 0; + gb->halted = true; +} + +static void nop(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc++; +} + +static void stop(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + if (gb->io_registers[GB_IO_KEY1] & 0x1) { + /* Todo: the switch is not instant. We should emulate this. */ + gb->cgb_double_speed ^= true; + gb->io_registers[GB_IO_KEY1] = 0; + } + else { + gb->stopped = true; + } + gb->pc++; +} + +/* Operand naming conventions for functions: + r = 8-bit register + lr = low 8-bit register + hr = high 8-bit register + rr = 16-bit register + d8 = 8-bit imm + d16 = 16-bit imm + d.. = [..] + cc = condition code (z, nz, c, nc) + */ + +static void ld_rr_d16(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + unsigned short value; + advance_cycles(gb, 12); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + value = read_memory(gb, gb->pc++); + value |= read_memory(gb, gb->pc++) << 8; + gb->registers[register_id] = value; +} + +static void ld_drr_a(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 8); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); +} + +static void inc_rr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 8); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + gb->registers[register_id]++; +} + +static void inc_hr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 4); + gb->pc++; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] += 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_hr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 4); + gb->pc++; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] -= 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F00) == 0xF00) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_hr_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 8); + gb->pc++; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] &= 0xFF; + gb->registers[register_id] |= read_memory(gb, gb->pc++) << 8; +} + +static void rlca(GB_gameboy_t *gb, unsigned char opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100; + } +} + +static void rla(GB_gameboy_t *gb, unsigned char opcode) +{ + bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x0100; + } + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_da16_sp(GB_gameboy_t *gb, unsigned char opcode){ + unsigned short addr; + advance_cycles(gb, 20); + gb->pc++; + addr = read_memory(gb, gb->pc++); + addr |= read_memory(gb, gb->pc++) << 8; + write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); +} + +static void add_hl_rr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned short hl = gb->registers[GB_REGISTER_HL]; + unsigned short rr; + unsigned char register_id; + advance_cycles(gb, 8); + gb->pc++; + register_id = (opcode >> 4) + 1; + rr = gb->registers[register_id]; + gb->registers[GB_REGISTER_HL] = hl + rr; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + + /* The meaning of the Half Carry flag is really hard to track -_- */ + if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ( ((unsigned long) hl) + ((unsigned long) rr) & 0x10000) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_a_drr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 8); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= read_memory(gb, gb->registers[register_id]) << 8; +} + +static void dec_rr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 8); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + gb->registers[register_id]--; +} + +static void inc_lr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + unsigned char value; + advance_cycles(gb, 4); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) + 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_lr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + unsigned char value; + advance_cycles(gb, 4); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) - 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F) == 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_lr_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 8); + register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + gb->registers[register_id] &= 0xFF00; + gb->registers[register_id] |= read_memory(gb, gb->pc++); +} + +static void rrca(GB_gameboy_t *gb, unsigned char opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; + + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000; + } +} + +static void rra(GB_gameboy_t *gb, unsigned char opcode) +{ + bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x8000; + } + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jr_r8(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 12); + gb->pc++; + gb->pc += (signed char) read_memory(gb, gb->pc++); +} + +static bool condition_code(GB_gameboy_t *gb, unsigned char opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 1: + return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 2: + return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + case 3: + return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + } + + return false; +} + +static void jr_cc_r8(GB_gameboy_t *gb, unsigned char opcode) +{ + if (condition_code(gb, read_memory(gb, gb->pc++))) { + advance_cycles(gb, 12); + gb->pc += (signed char)read_memory(gb, gb->pc++); + } + else { + advance_cycles(gb, 8); + gb->pc += 1; + } +} + +static void daa(GB_gameboy_t *gb, unsigned char opcode) +{ + /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ + advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= ~GB_ZERO_FLAG; + gb->pc++; + if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] += 0x9A00; + } + else { + gb->registers[GB_REGISTER_AF] += 0xFA00; + } + } + else if(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] += 0xA000; + } + } + else { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + unsigned short number = gb->registers[GB_REGISTER_AF] >> 8; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + number += 0x100; + } + gb->registers[GB_REGISTER_AF] = 0; + number += 0x06; + if (number >= 0xa0) { + number -= 0xa0; + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + gb->registers[GB_REGISTER_AF] |= number << 8; + } + else { + unsigned short number = gb->registers[GB_REGISTER_AF] >> 8; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + number += 0x100; + } + if (number > 0x99) { + number += 0x60; + } + number = (number & 0x0F) + ((number & 0x0F) > 9 ? 6 : 0) + (number & 0xFF0); + gb->registers[GB_REGISTER_AF] = number << 8; + if (number & 0xFF00) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + } + } + if ((gb->registers[GB_REGISTER_AF] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cpl(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] ^= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG; +} + +static void scf(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); +} + +static void ccf(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc++; + gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); +} + +static void ld_dhli_a(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_dhld_a(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_dhli(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; +} + +static void ld_a_dhld(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; +} + +static void inc_dhl(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; + advance_cycles(gb, 4); + write_memory(gb, gb->registers[GB_REGISTER_HL], value); + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + if ((value & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void dec_dhl(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; + advance_cycles(gb, 4); + write_memory(gb, gb->registers[GB_REGISTER_HL], value); + + gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if ((value & 0x0F) == 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_dhl_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 12); + gb->pc++; + write_memory(gb, gb->registers[GB_REGISTER_HL], read_memory(gb, gb->pc++)); +} + +unsigned char get_src_value(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char src_register_id; + unsigned char src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + return gb->registers[GB_REGISTER_AF] >> 8; + } + advance_cycles(gb, 4); + return read_memory(gb, gb->registers[GB_REGISTER_HL]); + } + if (src_low) { + return gb->registers[src_register_id] & 0xFF; + } + return gb->registers[src_register_id] >> 8; +} + +static void set_src_value(GB_gameboy_t *gb, unsigned char opcode, unsigned char value) +{ + unsigned char src_register_id; + unsigned char src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= value << 8; + } + else { + advance_cycles(gb, 4); + write_memory(gb, gb->registers[GB_REGISTER_HL], value); + } + } + else { + if (src_low) { + gb->registers[src_register_id] &= 0xFF00; + gb->registers[src_register_id] |= value; + } + else { + gb->registers[src_register_id] &= 0xFF; + gb->registers[src_register_id] |= value << 8; + } + } +} + +static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char dst_register_id; + unsigned char dst_low; + unsigned char value; + advance_cycles(gb, 4); + gb->pc++; + + dst_register_id = ((opcode >> 4) + 1) & 3; + dst_low = opcode & 8; + value = get_src_value(gb, opcode); + + + + if (dst_register_id == GB_REGISTER_AF) { + if (dst_low) { + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= value << 8; + } + else { + advance_cycles(gb, 4); + write_memory(gb, gb->registers[GB_REGISTER_HL], value); + } + } + else { + if (dst_low) { + gb->registers[dst_register_id] &= 0xFF00; + gb->registers[dst_register_id] |= value; + } + else { + gb->registers[dst_register_id] &= 0xFF; + gb->registers[dst_register_id] |= value << 8; + } + } + +} + +static void add_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((unsigned char)(a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a, carry; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if ((unsigned char)(a + value + carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a, carry; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + + if ((unsigned char) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 4); + gb->pc++; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void halt(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->halted = true; + gb->pc++; +} + +static void ret_cc(GB_gameboy_t *gb, unsigned char opcode) +{ + if (condition_code(gb, read_memory(gb, gb->pc++))) { + advance_cycles(gb, 20); + gb->pc = read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + gb->registers[GB_REGISTER_SP] += 2; + gb->debug_call_depth--; + } + else { + advance_cycles(gb, 8); + } +} + +static void pop_rr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 12); + register_id = ((read_memory(gb, gb->pc++) >> 4) + 1) & 3; + gb->registers[register_id] = read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. + gb->registers[GB_REGISTER_SP] += 2; +} + +static void jp_cc_a16(GB_gameboy_t *gb, unsigned char opcode) +{ + gb->pc++; + if (condition_code(gb, opcode)) { + advance_cycles(gb, 16); + gb->pc = read_memory(gb, gb->pc) | (read_memory(gb, gb->pc + 1) << 8); + } + else { + advance_cycles(gb, 12); + gb->pc += 2; + } +} + +static void jp_a16(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 16); + gb->pc++; + gb->pc = read_memory(gb, gb->pc) | (read_memory(gb, gb->pc + 1) << 8); +} + +static void call_cc_a16(GB_gameboy_t *gb, unsigned char opcode) +{ + gb->pc++; + if (condition_code(gb, opcode)) { + advance_cycles(gb, 24); + gb->registers[GB_REGISTER_SP] -= 2; + write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); + gb->pc = read_memory(gb, gb->pc) | (read_memory(gb, gb->pc + 1) << 8); + gb->debug_call_depth++; + } + else { + advance_cycles(gb, 12); + gb->pc += 2; + } +} + +static void push_rr(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char register_id; + advance_cycles(gb, 16); + gb->pc++; + register_id = ((opcode >> 4) + 1) & 3; + gb->registers[GB_REGISTER_SP] -= 2; + write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); +} + +static void add_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((unsigned char) (a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a, carry; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if (gb->registers[GB_REGISTER_AF] == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a, carry; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + + if ((unsigned char) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_d8(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value, a; + advance_cycles(gb, 8); + gb->pc++; + value = read_memory(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void rst(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 16); + gb->registers[GB_REGISTER_SP] -= 2; + write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 1) & 0xFF); + write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 1) >> 8); + gb->pc = opcode ^ 0xC7; +} + +static void ret(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 16); + gb->pc = read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + gb->registers[GB_REGISTER_SP] += 2; + gb->debug_call_depth--; +} + +static void reti(GB_gameboy_t *gb, unsigned char opcode) +{ + ret(gb, opcode); + gb->ime = true; + gb->debug_call_depth--; +} + +static void call_a16(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 24); + gb->pc++; + gb->registers[GB_REGISTER_SP] -= 2; + write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); + gb->pc = read_memory(gb, gb->pc) | (read_memory(gb, gb->pc + 1) << 8); + gb->debug_call_depth++; +} + +static void ld_da8_a(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + unsigned char temp = read_memory(gb, gb->pc++); + advance_cycles(gb, 4); + write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_da8(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->pc++; + unsigned char temp = read_memory(gb, gb->pc++); + advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= read_memory(gb, 0xFF00 + temp) << 8; +} + +static void ld_dc_a(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_dc(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->pc++; + gb->registers[GB_REGISTER_AF] |= read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; +} + +static void add_sp_r8(GB_gameboy_t *gb, unsigned char opcode) +{ + signed short offset; + unsigned short sp = gb->registers[GB_REGISTER_SP]; + advance_cycles(gb, 16); + gb->pc++; + offset = (signed char) read_memory(gb, gb->pc++); + gb->registers[GB_REGISTER_SP] += offset; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + + /* A new instruction, a new meaning for Half Carry! */ + if ((sp & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jp_hl(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc = gb->registers[GB_REGISTER_HL]; +} + +static void ld_da16_a(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned short addr; + advance_cycles(gb, 16); + gb->pc++; + addr = read_memory(gb, gb->pc++); + addr |= read_memory(gb, gb->pc++) << 8; + write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_da16(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned short addr; + advance_cycles(gb, 16); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->pc++; + addr = read_memory(gb, gb->pc++); + addr |= read_memory(gb, gb->pc++) << 8 ; + gb->registers[GB_REGISTER_AF] |= read_memory(gb, addr) << 8; +} + +static void di(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc++; + gb->ime = false; +} + +static void ei(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 4); + gb->pc++; + gb->ime = true; +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, unsigned char opcode) +{ + signed short offset; + advance_cycles(gb, 12); + gb->pc++; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + offset = (signed char) read_memory(gb, gb->pc++); + gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; + + if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[GB_REGISTER_SP] & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_sp_hl(GB_gameboy_t *gb, unsigned char opcode) +{ + advance_cycles(gb, 8); + gb->pc++; + gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; +} + +static void rlc_r(GB_gameboy_t *gb, unsigned char opcode) +{ + bool carry; + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1) | carry); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value << 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rrc_r(GB_gameboy_t *gb, unsigned char opcode) +{ + bool carry; + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + carry = (value & 0x01) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rl_r(GB_gameboy_t *gb, unsigned char opcode) +{ + bool carry; + unsigned char value; + bool bit7; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit7 = (value & 0x80) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value << 1) | carry; + set_src_value(gb, opcode, value); + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rr_r(GB_gameboy_t *gb, unsigned char opcode) +{ + bool carry; + unsigned char value; + bool bit1; + + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit1 = (value & 0x1) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sla_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value; + bool carry; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1)); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if ((value & 0x7F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sra_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char bit7; + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + bit7 = value & 0x80; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + value = (value >> 1) | bit7; + set_src_value(gb, opcode, value); + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void srl_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 1)); + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value >> 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void swap_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 4) | (value << 4)); + if (!value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void bit_r(GB_gameboy_t *gb, unsigned char opcode) +{ + unsigned char value; + unsigned char bit; + advance_cycles(gb, 8); + gb->pc++; + value = get_src_value(gb, opcode); + bit = 1 << ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + if (!(bit & value)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + set_src_value(gb, opcode, value & ~bit) ; + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + set_src_value(gb, opcode, value | bit) ; + } +} + +static void cb_prefix(GB_gameboy_t *gb, unsigned char opcode) +{ + opcode = read_memory(gb, ++gb->pc); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode); + break; + case 1: + rrc_r(gb, opcode); + break; + case 2: + rl_r(gb, opcode); + break; + case 3: + rr_r(gb, opcode); + break; + case 4: + sla_r(gb, opcode); + break; + case 5: + sra_r(gb, opcode); + break; + case 6: + swap_r(gb, opcode); + break; + case 7: + srl_r(gb, opcode); + break; + default: + bit_r(gb, opcode); + break; + } +} + +static GB_opcode_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ + ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca, + stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; + +void cpu_run(GB_gameboy_t *gb) +{ + bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; + if (interrupt) { + gb->halted = false; + } + + if (gb->hdma_on) { + advance_cycles(gb, 4); + return; + } + + if (gb->ime && interrupt) { + unsigned char interrupt_bit = 0; + unsigned char interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; + while (!(interrupt_queue & 1)) { + interrupt_queue >>= 1; + interrupt_bit++; + } + gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); + gb->ime = false; + nop(gb, 0); + gb->pc -= 2; + /* Run pseudo instructions rst 40-60*/ + rst(gb, 0x87 + interrupt_bit * 8); + } + else if(!gb->halted) { + unsigned char opcode = read_memory(gb, gb->pc); + opcodes[opcode](gb, opcode); + } + else { + advance_cycles(gb, 4); + } +} diff --git a/Core/z80_cpu.h b/Core/z80_cpu.h new file mode 100644 index 0000000..0e36945 --- /dev/null +++ b/Core/z80_cpu.h @@ -0,0 +1,7 @@ +#ifndef z80_cpu_h +#define z80_cpu_h +#include "gb.h" +void cpu_disassemble(GB_gameboy_t *gb, unsigned short pc, unsigned short count); +void cpu_run(GB_gameboy_t *gb); + +#endif /* z80_cpu_h */ diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c new file mode 100644 index 0000000..d997fc6 --- /dev/null +++ b/Core/z80_disassembler.c @@ -0,0 +1,680 @@ +#include +#include +#include "z80_cpu.h" +#include "memory.h" +#include "gb.h" + + +typedef void GB_opcode_t(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc); + +static void ill(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, ".BYTE %02x\n", opcode); + (*pc)++; +} + +static void nop(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "NOP\n"); + (*pc)++; +} + +static void stop(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "STOP\n"); + (*pc)++; +} + +static char *register_names[] = {"af", "bc", "de", "hl", "sp"}; + +static void ld_rr_d16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + unsigned short value; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + value = read_memory(gb, (*pc)++); + value |= read_memory(gb, (*pc)++) << 8; + gb_log(gb, "LD %s, %04x\n", register_names[register_id], value); +} + +static void ld_drr_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + gb_log(gb, "LD [%s], a\n", register_names[register_id]); +} + +static void inc_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + gb_log(gb, "INC %s\n", register_names[register_id]); +} + +static void inc_hr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + gb_log(gb, "INC %c\n", register_names[register_id][0]); + +} +static void dec_hr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + gb_log(gb, "DEC %c\n", register_names[register_id][0]); +} + +static void ld_hr_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + gb_log(gb, "LD %c, %02x\n", register_names[register_id][0], read_memory(gb, (*pc)++)); +} + +static void rlca(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RLCA\n"); +} + +static void rla(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RLA\n"); +} + +static void ld_da16_sp(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc){ + unsigned short addr; + (*pc)++; + addr = read_memory(gb, (*pc)++); + addr |= read_memory(gb, (*pc)++) << 8; + gb_log(gb, "LD [%04x], sp\n", addr); +} + +static void add_hl_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + (*pc)++; + register_id = (opcode >> 4) + 1; + gb_log(gb, "ADD hl, %s\n", register_names[register_id]); +} + +static void ld_a_drr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + gb_log(gb, "LD a, [%s]\n", register_names[register_id]); +} + +static void dec_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + gb_log(gb, "DEC %s\n", register_names[register_id]); +} + +static void inc_lr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + + gb_log(gb, "INC %c\n", register_names[register_id][1]); +} +static void dec_lr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + + gb_log(gb, "DEC %c\n", register_names[register_id][1]); +} + +static void ld_lr_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + + gb_log(gb, "LD %c, %02x\n", register_names[register_id][1], read_memory(gb, (*pc)++)); +} + +static void rrca(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "RRCA\n"); + (*pc)++; +} + +static void rra(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "RRA\n"); + (*pc)++; +} + +static void jr_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_attributed_log(gb, GB_LOG_UNDERLINE, "JR %04x\n", *pc + (signed char) read_memory(gb, (*pc)) + 1); + (*pc)++; +} + +static const char *condition_code(unsigned char opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return "nz"; + case 1: + return "z"; + case 2: + return "nc"; + case 3: + return "c"; + } + + return NULL; +} + +static void jr_cc_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %04x\n", condition_code(opcode), *pc + (signed char)read_memory(gb, (*pc)) + 1); + (*pc)++; +} + +static void daa(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "DAA\n"); + (*pc)++; +} + +static void cpl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "CPL\n"); + (*pc)++; +} + +static void scf(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "SCF\n"); + (*pc)++; +} + +static void ccf(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "CCF\n"); + (*pc)++; +} + +static void ld_dhli_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "LD [hli], a\n"); + (*pc)++; +} + +static void ld_dhld_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "LD [hld], a\n"); + (*pc)++; +} + +static void ld_a_dhli(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "LD a, [hli]\n"); + (*pc)++; +} + +static void ld_a_dhld(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "LD a, [hld]\n"); + (*pc)++; +} + +static void inc_dhl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "INC [hl]\n"); + (*pc)++; +} + +static void dec_dhl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + gb_log(gb, "DEC [hl]\n"); + (*pc)++; +} + +static void ld_dhl_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LD [hl], %02x\n", read_memory(gb, (*pc)++)); +} + +static const char *get_src_name(unsigned char opcode) +{ + unsigned char src_register_id; + unsigned char src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = !(opcode & 1); + if (src_register_id == GB_REGISTER_AF && src_low) { + + return "[hl]"; + } + if (src_low) { + return register_names[src_register_id] + 1; + } + static const char *high_register_names[] = {"a", "b", "d", "h"}; + return high_register_names[src_register_id]; +} + +static const char *get_dst_name(unsigned char opcode) +{ + unsigned char dst_register_id; + unsigned char dst_low; + dst_register_id = ((opcode >> 4) + 1) & 3; + dst_low = opcode & 8; + if (dst_register_id == GB_REGISTER_AF && dst_low) { + + return "[hl]"; + } + if (dst_low) { + return register_names[dst_register_id] + 1; + } + static const char *high_register_names[] = {"a", "b", "d", "h"}; + return high_register_names[dst_register_id]; +} + +static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode)); +} + +static void add_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "ADD %s\n", get_src_name(opcode)); +} + +static void adc_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "ADC %s\n", get_src_name(opcode)); +} + +static void sub_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "SUB %s\n", get_src_name(opcode)); +} + +static void sbc_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "SBC %s\n", get_src_name(opcode)); +} + +static void and_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "AND %s\n", get_src_name(opcode)); +} + +static void xor_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "XOR %s\n", get_src_name(opcode)); +} + +static void or_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "OR %s\n", get_src_name(opcode)); +} + +static void cp_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "CP %s\n", get_src_name(opcode)); +} + +static void halt(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "HALT\n"); +} + +static void ret_cc(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode)); +} + +static void pop_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = ((read_memory(gb, (*pc)++) >> 4) + 1) & 3; + gb_log(gb, "POP %s\n", register_names[register_id]); +} + +static void jp_cc_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %04x\n", condition_code(opcode), read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + (*pc) += 2; +} + +static void jp_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "JP %04x\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + (*pc) += 2; +} + +static void call_cc_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "CALL %s, %04x\n", condition_code(opcode), read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + (*pc) += 2; +} + +static void push_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char register_id; + register_id = ((read_memory(gb, (*pc)++) >> 4) + 1) & 3; + gb_log(gb, "PUSH %s\n", register_names[register_id]); +} + +static void add_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "ADD %02x\n", read_memory(gb, (*pc)++)); +} + +static void adc_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "ADC %02x\n", read_memory(gb, (*pc)++)); +} + +static void sub_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "SUB %02x\n", read_memory(gb, (*pc)++)); +} + +static void sbc_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LBC %02x\n", read_memory(gb, (*pc)++)); +} + +static void and_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "AND %02x\n", read_memory(gb, (*pc)++)); +} + +static void xor_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "XOR %02x\n", read_memory(gb, (*pc)++)); +} + +static void or_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "OR %02x\n", read_memory(gb, (*pc)++)); +} + +static void cp_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "CP %02x\n", read_memory(gb, (*pc)++)); +} + +static void rst(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RST %02x\n", opcode ^ 0xC7); + +} + +static void ret(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n"); +} + +static void reti(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n"); +} + +static void call_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "CALL %04x\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + (*pc) += 2; +} + +static void ld_da8_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + unsigned char temp = read_memory(gb, (*pc)++); + gb_log(gb, "LDH [%02x], a\n", temp); +} + +static void ld_a_da8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + unsigned char temp = read_memory(gb, (*pc)++); + gb_log(gb, "LDH a, [%02x]\n", temp); +} + +static void ld_dc_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LDH [c], a\n"); +} + +static void ld_a_dc(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LDH a, [c]\n"); +} + +static void add_sp_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + signed char temp = read_memory(gb, (*pc)++); + gb_log(gb, "ADD SP, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); +} + +static void jp_hl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "JP hl\n"); +} + +static void ld_da16_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LD [%04x], a\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + (*pc) += 2; +} + +static void ld_a_da16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LD a, [%04x]\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + (*pc) += 2; +} + +static void di(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "DI\n"); +} + +static void ei(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "EI\n"); +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + signed char temp = read_memory(gb, (*pc)++); + gb_log(gb, "LD hl, sp, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); +} + +static void ld_sp_hl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "LD sp, hl\n"); +} + +static void rlc_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RLC %s\n", get_src_name(opcode)); +} + +static void rrc_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RRC %s\n", get_src_name(opcode)); +} + +static void rl_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RL %s\n", get_src_name(opcode)); +} + +static void rr_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RR %s\n", get_src_name(opcode)); +} + +static void sla_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "SLA %s\n", get_src_name(opcode)); +} + +static void sra_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "SRA %s\n", get_src_name(opcode)); +} + +static void srl_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "SRL %s\n", get_src_name(opcode)); +} + +static void swap_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + (*pc)++; + gb_log(gb, "RLC %s\n", get_src_name(opcode)); +} + +static void bit_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + unsigned char bit; + (*pc)++; + bit = ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + gb_log(gb, "BIT %s, %d\n", get_src_name(opcode), bit); + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + gb_log(gb, "RES %s, %d\n", get_src_name(opcode), bit); + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + gb_log(gb, "SET %s, %d\n", get_src_name(opcode), bit); + } +} + +static void cb_prefix(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +{ + opcode = read_memory(gb, ++*pc); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode, pc); + break; + case 1: + rrc_r(gb, opcode, pc); + break; + case 2: + rl_r(gb, opcode, pc); + break; + case 3: + rr_r(gb, opcode, pc); + break; + case 4: + sla_r(gb, opcode, pc); + break; + case 5: + sra_r(gb, opcode, pc); + break; + case 6: + swap_r(gb, opcode, pc); + break; + case 7: + srl_r(gb, opcode, pc); + break; + default: + bit_r(gb, opcode, pc); + break; + } +} + +static GB_opcode_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ + ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca, + stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; + +void cpu_disassemble(GB_gameboy_t *gb, unsigned short pc, unsigned short count) +{ + while (count--) { + gb_log(gb, "%s%04x: ", pc == gb->pc? "-> ": " ", pc); + unsigned char opcode = read_memory(gb, pc); + opcodes[opcode](gb, opcode, &pc); + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b58aa4 --- /dev/null +++ b/Makefile @@ -0,0 +1,94 @@ +ifeq ($(shell uname -s),Darwin) +default: cocoa +else +default: sdl +endif + +BIN := build/bin +OBJ := build/obj + +CC := clang + +CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE +SDL_LDFLAGS := -lSDL +LDFLAGS += -lc -lm + +ifeq ($(shell uname -s),Darwin) +CFLAGS += -F/Library/Frameworks +OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(shell xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -mmacosx-version-min=10.9 +LDFLAGS += -framework AppKit +SDL_LDFLAGS := -framework SDL +endif + +cocoa: $(BIN)/Sameboy.app +sdl: $(BIN)/sdl/sameboy $(BIN)/sdl/dmg_boot.bin $(BIN)/sdl/cgb_boot.bin +bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin + +CORE_SOURCES := $(shell echo Core/*.c) +SDL_SOURCES := $(shell echo SDL/*.c) + +ifeq ($(shell uname -s),Darwin) +COCOA_SOURCES := $(shell echo Cocoa/*.m) +SDL_SOURCES += $(shell echo SDL/*.m) +endif + +CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES)) +COCOA_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(COCOA_SOURCES)) +SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES)) + +ALL_OBJECTS := $(CORE_OBJECTS) $(COCOA_OBJECTS) $(SDL_OBJECTS) + +# Automatic dependency generation + +-include $(ALL_OBJECTS:.o=.dep) + +$(OBJ)/%.dep: % + -@mkdir -p $(dir $@) + $(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ + +$(OBJ)/%.c.o: %.c + -@mkdir -p $(dir $@) + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ)/%.m.o: %.m + -@mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ + +# Cocoa Port + +$(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ + $(shell echo Cocoa/*.icns) \ + $(shell echo Cocoa/info.plist) \ + $(BIN)/BootROMs/dmg_boot.bin \ + $(BIN)/BootROMs/cgb_boot.bin \ + $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Document.nib \ + $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/MainMenu.nib + mkdir -p $(BIN)/Sameboy.app/Contents/Resources + cp Cocoa/*.icns $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/Sameboy.app/Contents/Resources/ + cp Cocoa/info.plist $(BIN)/Sameboy.app/Contents/ + +$(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) + -@mkdir -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit + +$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib + ibtool --compile $@ $^ + +$(BIN)/sdl/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) + -@mkdir -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) + +$(BIN)/BootROMs/%.bin: BootROMs/%.asm + -@mkdir -p $(dir $@) + cd BootROMs && rgbasm -o ../$@.tmp ../$< + rgblink -o $@.tmp2 $@.tmp + head -c $(if $(filter dmg,$(CC)), 256, 2309) $@.tmp2 > $@ + @rm $@.tmp $@.tmp2 + +$(BIN)/sdl/%.bin: $(BIN)/BootROMs/%.bin + -@mkdir -p $(dir $@) + cp -f $^ $@ + +clean: + rm -rf build + \ No newline at end of file diff --git a/SDL/SDLMain.h b/SDL/SDLMain.h new file mode 100644 index 0000000..9bddc9c --- /dev/null +++ b/SDL/SDLMain.h @@ -0,0 +1,16 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#ifndef _SDLMain_h_ +#define _SDLMain_h_ + +#import + +@interface SDLMain : NSObject +@end + +#endif /* _SDLMain_h_ */ diff --git a/SDL/SDLMain.m b/SDL/SDLMain.m new file mode 100644 index 0000000..535e4c7 --- /dev/null +++ b/SDL/SDLMain.m @@ -0,0 +1,382 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#include +#include "SDLMain.h" +#include /* for MAXPATHLEN */ +#include +#import + +/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + const NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (__bridge const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface NSApplication (SDLApplication) +@end + +@implementation NSApplication (SDLApplication) +/* Invoked from the Quit menu item */ +- (void)_terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); + /* Call "super" */ + [self _terminate:sender]; +} + +/* Use swizzling to avoid warning and undocumented Obj C runtime behavior. Didn't feel like rewriting SDLMain for this. */ ++ (void) load +{ + method_exchangeImplementations(class_getInstanceMethod(self, @selector(terminate:)), class_getInstanceMethod(self, @selector(_terminate:))); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { + chdir(parentdir); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [NSApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [NSApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + +void cocoa_disable_filtering(void) { + CGContextSetInterpolationQuality([[NSGraphicsContext currentContext] CGContext], kCGInterpolationNone); +} + +#ifdef main +# undef main +#endif + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } else { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} + diff --git a/SDL/main.c b/SDL/main.c new file mode 100644 index 0000000..00dabd8 --- /dev/null +++ b/SDL/main.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include + +#include "gb.h" + +void update_keys_status(GB_gameboy_t *gb) +{ + static bool ctrl = false; + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch( event.type ){ + case SDL_QUIT: + exit(0); + case SDL_KEYDOWN: + case SDL_KEYUP: + gb->stopped = false; + switch (event.key.keysym.sym) { + case SDLK_RIGHT: + gb->keys[0] = event.type == SDL_KEYDOWN; + break; + case SDLK_LEFT: + gb->keys[1] = event.type == SDL_KEYDOWN; + break; + case SDLK_UP: + gb->keys[2] = event.type == SDL_KEYDOWN; + break; + case SDLK_DOWN: + gb->keys[3] = event.type == SDL_KEYDOWN; + break; + case SDLK_x: + gb->keys[4] = event.type == SDL_KEYDOWN; + break; + case SDLK_z: + gb->keys[5] = event.type == SDL_KEYDOWN; + break; + case SDLK_BACKSPACE: + gb->keys[6] = event.type == SDL_KEYDOWN; + break; + case SDLK_RETURN: + gb->keys[7] = event.type == SDL_KEYDOWN; + break; + case SDLK_SPACE: + gb->turbo = event.type == SDL_KEYDOWN; + break; + case SDLK_LCTRL: + ctrl = event.type == SDL_KEYDOWN; + break; + case SDLK_c: + if (ctrl && event.type == SDL_KEYDOWN) { + ctrl = false; + gb->debug_stopped = true; + + } + break; + + default: + break; + } + break; + default: + break; + } + } +} + +void vblank(GB_gameboy_t *gb) +{ + SDL_Surface *screen = gb->user_data; + SDL_Flip(screen); + update_keys_status(gb); + + gb_set_pixels_output(gb, screen->pixels); +} + +#ifdef __APPLE__ +#include +#endif + +static const char *executable_folder(void) +{ + static char path[1024] = {0,}; + if (path[0]) { + return path; + } + /* Ugly unportable code! :( */ +#ifdef __APPLE__ + unsigned int length = sizeof(path) - 1; + _NSGetExecutablePath(&path[0], &length); +#else +#ifdef __linux__ + ssize_t length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1); + assert (length != -1); +#else +#warning Unsupported OS: sameboy will only run from CWD + /* No OS-specific way, assume running from CWD */ + getcwd(&path[0], sizeof(path) - 1); + return path; +#endif +#endif + size_t pos = strlen(path); + while (pos) { + pos--; + if (path[pos] == '/') { + path[pos] = 0; + break; + } + } + return path; +} + +static char *executable_relative_path(const char *filename) +{ + static char path[1024]; + snprintf(path, sizeof(path), "%s/%s", executable_folder(), filename); + return path; +} + +static SDL_Surface *screen = NULL; +static uint32_t rgb_encode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b) +{ + return SDL_MapRGB(screen->format, r, g, b); +} + +#ifdef __APPLE__ +extern void cocoa_disable_filtering(void); +#endif +int main(int argc, char **argv) +{ + GB_gameboy_t gb; + bool dmg = false; + + if (argc == 1 || argc > 3) { +usage: + fprintf(stderr, "Usage: %s [--dmg] rom\n", argv[0]); + exit(1); + } + + if (argc == 3) { + if (strcmp(argv[1], "--dmg") == 0) { + dmg = true; + } + else { + goto usage; + } + } + + + if (dmg) { + gb_init(&gb); + if (gb_load_bios(&gb, executable_relative_path("dmg_boot.bin"))) { + perror("Failed to load boot ROM"); + exit(1); + } + } + else { + gb_init_cgb(&gb); + if (gb_load_bios(&gb, executable_relative_path("cgb_boot.bin"))) { + perror("Failed to load boot ROM"); + exit(1); + } + } + + if (gb_load_rom(&gb, argv[argc - 1])) { + perror("Failed to load ROM"); + exit(1); + } + + SDL_Init( SDL_INIT_EVERYTHING ); + screen = SDL_SetVideoMode(160, 144, 32, SDL_SWSURFACE ); +#ifdef __APPLE__ + cocoa_disable_filtering(); +#endif + SDL_LockSurface(screen); + gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + gb.user_data = screen; + gb_set_pixels_output(&gb, screen->pixels); + gb_set_rgb_encode_callback(&gb, rgb_encode); + + while (true) { + gb_run(&gb); + } + + return 0; +} +