From 8cd82ebb682e7bcdeaab9f3860946008eb359ee9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Jan 2017 18:35:20 +0200 Subject: [PATCH] Quick Look icon generation for GameBoy ROMs, using screenshots from the actual ROMs --- BootROMs/cgb_boot_fast.asm | 785 +++++++++++++++++++++++ Cocoa/Info.plist | 59 +- Makefile | 73 ++- QuickLook/CartridgeTemplate.png | Bin 0 -> 129246 bytes QuickLook/ColorCartridgeTemplate.png | Bin 0 -> 1138571 bytes QuickLook/GenerateThumbnailForURL.m | 96 +++ QuickLook/Info.plist | 62 ++ QuickLook/UniversalCartridgeTemplate.png | Bin 0 -> 116941 bytes QuickLook/exports.sym | 3 + QuickLook/get_image_for_rom.c | 95 +++ QuickLook/get_image_for_rom.h | 11 + QuickLook/main.c | 198 ++++++ 12 files changed, 1356 insertions(+), 26 deletions(-) create mode 100644 BootROMs/cgb_boot_fast.asm create mode 100644 QuickLook/CartridgeTemplate.png create mode 100644 QuickLook/ColorCartridgeTemplate.png create mode 100644 QuickLook/GenerateThumbnailForURL.m create mode 100644 QuickLook/Info.plist create mode 100644 QuickLook/UniversalCartridgeTemplate.png create mode 100644 QuickLook/exports.sym create mode 100755 QuickLook/get_image_for_rom.c create mode 100644 QuickLook/get_image_for_rom.h create mode 100644 QuickLook/main.c diff --git a/BootROMs/cgb_boot_fast.asm b/BootROMs/cgb_boot_fast.asm new file mode 100644 index 0000000..60d82c1 --- /dev/null +++ b/BootROMs/cgb_boot_fast.asm @@ -0,0 +1,785 @@ +; Sameboy CGB 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 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, $FF30 +; 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 (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 + + ; Clear Palettes + ld c, 64 + ld hl, BgPalettes + ld a, $FF +.clearPalettesLoop: + ldi [hl], a + dec c + jr nz, .clearPalettesLoop + + 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 Preboot + +; Will be filled with NOPs + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [$50], a + +SECTION "MoreStuff", ROM0[$200] + +; Game Palettes Data +TitleChecksums: + db $00 ; Default + db $88 ; ALLEY WAY + db $16 ; YAKUMAN + db $36 ; BASEBALL, (Game and Watch 2) + db $D1 ; TENNIS + db $DB ; TETRIS + db $F2 ; QIX + db $3C ; DR.MARIO + db $8C ; RADARMISSION + db $92 ; F1RACE + db $3D ; YOSSY NO TAMAGO + db $5C ; + db $58 ; X + db $C9 ; MARIOLAND2 + db $3E ; YOSSY NO COOKIE + db $70 ; ZELDA + db $1D ; + db $59 ; + db $69 ; TETRIS FLASH + db $19 ; DONKEY KONG + db $35 ; MARIO'S PICROSS + db $A8 ; + db $14 ; POKEMON RED, (GAMEBOYCAMERA G) + db $AA ; POKEMON GREEN + db $75 ; PICROSS 2 + db $95 ; YOSSY NO PANEPON + db $99 ; KIRAKIRA KIDS + db $34 ; GAMEBOY GALLERY + db $6F ; POCKETCAMERA + db $15 ; + db $FF ; BALLOON KID + db $97 ; KINGOFTHEZOO + db $4B ; DMG FOOTBALL + db $90 ; WORLD CUP + db $17 ; OTHELLO + db $10 ; SUPER RC PRO-AM + db $39 ; DYNABLASTER + db $F7 ; BOY AND BLOB GB2 + db $F6 ; MEGAMAN + db $A2 ; STAR WARS-NOA + db $49 ; + db $4E ; WAVERACE + db $43 | $80 ; + db $68 ; LOLO2 + db $E0 ; YOSHI'S COOKIE + db $8B ; MYSTIC QUEST + db $F0 ; + db $CE ; TOPRANKINGTENNIS + db $0C ; MANSELL + db $29 ; MEGAMAN3 + db $E8 ; SPACE INVADERS + db $B7 ; GAME&WATCH + db $86 ; DONKEYKONGLAND95 + db $9A ; ASTEROIDS/MISCMD + db $52 ; STREET FIGHTER 2 + db $01 ; DEFENDER/JOUST + db $9D ; KILLERINSTINCT95 + db $71 ; TETRIS BLAST + db $9C ; PINOCCHIO + db $BD ; + db $5D ; BA.TOSHINDEN + db $6D ; NETTOU KOF 95 + db $67 ; + db $3F ; TETRIS PLUS + db $6B ; DONKEYKONGLAND 3 +; For these games, the 4th letter is taken into account +FirstChecksumWithDuplicate: + ; Let's play hangman! + db $B3 ; ???[B]???????? + db $46 ; SUP[E]R MARIOLAND + db $28 ; GOL[F] + db $A5 ; SOL[A]RSTRIKER + db $C6 ; GBW[A]RS + db $D3 ; KAE[R]UNOTAMENI + db $27 ; ???[B]???????? + db $61 ; POK[E]MON BLUE + db $18 ; DON[K]EYKONGLAND + db $66 ; GAM[E]BOY GALLERY2 + db $6A ; DON[K]EYKONGLAND 2 + db $BF ; KID[ ]ICARUS + db $0D ; TET[R]IS2 + db $F4 ; ???[-]???????? + db $B3 ; MOG[U]RANYA + db $46 ; ???[R]???????? + db $28 ; GAL[A]GA&GALAXIAN + db $A5 ; BT2[R]AGNAROKWORLD + db $C6 ; KEN[ ]GRIFFEY JR + db $D3 ; ???[I]???????? + db $27 ; MAG[N]ETIC SOCCER + db $61 ; VEG[A]S STAKES + db $18 ; ???[I]???????? + db $66 ; MIL[L]I/CENTI/PEDE + db $6A ; MAR[I]O & YOSHI + db $BF ; SOC[C]ER + db $0D ; POK[E]BOM + db $F4 ; G&W[ ]GALLERY + db $B3 ; TET[R]IS ATTACK +ChecksumsEnd: + +PalettePerChecksum: +; | $80 means game requires DMG boot tilemap + db 0 ; Default Palette + db 4 ; ALLEY WAY + db 5 ; YAKUMAN + db 35 ; BASEBALL, (Game and Watch 2) + db 34 ; TENNIS + db 3 ; TETRIS + db 31 ; QIX + db 15 ; DR.MARIO + db 10 ; RADARMISSION + db 5 ; F1RACE + db 19 ; YOSSY NO TAMAGO + db 36 ; + db 7 | $80 ; X + db 37 ; MARIOLAND2 + db 30 ; YOSSY NO COOKIE + db 44 ; ZELDA + db 21 ; + db 32 ; + db 31 ; TETRIS FLASH + db 20 ; DONKEY KONG + db 5 ; MARIO'S PICROSS + db 33 ; + db 13 ; POKEMON RED, (GAMEBOYCAMERA G) + db 14 ; POKEMON GREEN + db 5 ; PICROSS 2 + db 29 ; YOSSY NO PANEPON + db 5 ; KIRAKIRA KIDS + db 18 ; GAMEBOY GALLERY + db 9 ; POCKETCAMERA + db 3 ; + db 2 ; BALLOON KID + db 26 ; KINGOFTHEZOO + db 25 ; DMG FOOTBALL + db 25 ; WORLD CUP + db 41 ; OTHELLO + db 42 ; SUPER RC PRO-AM + db 26 ; DYNABLASTER + db 45 ; BOY AND BLOB GB2 + db 42 ; MEGAMAN + db 45 ; STAR WARS-NOA + db 36 ; + db 38 ; WAVERACE + db 26 ; + db 42 ; LOLO2 + db 30 ; YOSHI'S COOKIE + db 41 ; MYSTIC QUEST + db 34 ; + db 34 ; TOPRANKINGTENNIS + db 5 ; MANSELL + db 42 ; MEGAMAN3 + db 6 ; SPACE INVADERS + db 5 ; GAME&WATCH + db 33 ; DONKEYKONGLAND95 + db 25 ; ASTEROIDS/MISCMD + db 42 ; STREET FIGHTER 2 + db 42 ; DEFENDER/JOUST + db 40 ; KILLERINSTINCT95 + db 2 ; TETRIS BLAST + db 16 ; PINOCCHIO + db 25 ; + db 42 ; BA.TOSHINDEN + db 42 ; NETTOU KOF 95 + db 5 ; + db 0 ; TETRIS PLUS + db 39 ; DONKEYKONGLAND 3 + db 36 ; + db 22 ; SUPER MARIOLAND + db 25 ; GOLF + db 6 ; SOLARSTRIKER + db 32 ; GBWARS + db 12 ; KAERUNOTAMENI + db 36 ; + db 11 ; POKEMON BLUE + db 39 ; DONKEYKONGLAND + db 18 ; GAMEBOY GALLERY2 + db 39 ; DONKEYKONGLAND 2 + db 24 ; KID ICARUS + db 31 ; TETRIS2 + db 50 ; + db 17 ; MOGURANYA + db 46 ; + db 6 ; GALAGA&GALAXIAN + db 27 ; BT2RAGNAROKWORLD + db 0 ; KEN GRIFFEY JR + db 47 ; + db 41 ; MAGNETIC SOCCER + db 41 ; VEGAS STAKES + db 0 ; + db 0 ; MILLI/CENTI/PEDE + db 19 ; MARIO & YOSHI + db 34 ; SOCCER + db 23 ; POKEBOM + db 18 ; G&W GALLERY + db 29 ; TETRIS ATTACK + +Dups4thLetterArray: + db "BEFAARBEKEK R-URAR INAILICE R" + +; We assume the last three arrays fit in the same $100 byte page! + +PaletteCombinations: +palette_comb: MACRO ; Obj0, Obj1, Bg + db \1 * 8, \2 * 8, \3 *8 + ENDM + 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 + +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 + +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 + + +Preboot: + 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, $11 + ret + +EmulateDMG: + ld a, 1 + ldh [$6C], a ; DMG Emulation + call GetPaletteIndex + bit 7, a + call nz, LoadDMGTilemap + and $7F + 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 + +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 + + +LoadDMGTilemap: + push af + call WaitFrame + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + pop af + ret + +SECTION "ROMMax", ROM0[$900] + ; Prevent us from overflowing + ds 1 + +SECTION "RAM", WRAM0[$C000] +BgPalettes: + ds 8 * 4 * 2 \ No newline at end of file diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 149f678..72375fa 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -14,9 +14,13 @@ CFBundleTypeIconFile Cartridge CFBundleTypeName - Gameboy Game + GameBoy Game CFBundleTypeRole Viewer + LSItemContentTypes + + com.github.liji32.sameboy.gb + LSTypeIsPackage 0 NSDocumentClass @@ -30,9 +34,13 @@ CFBundleTypeIconFile ColorCartridge CFBundleTypeName - Gameboy Color Game + GameBoy Color Game CFBundleTypeRole Viewer + LSItemContentTypes + + com.github.liji32.sameboy.gbc + LSTypeIsPackage 0 NSDocumentClass @@ -44,7 +52,7 @@ CFBundleIconFile AppIcon.icns CFBundleIdentifier - com.github.LIJI32.SameBoy + com.github.liji32.sameboy CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -61,11 +69,52 @@ LSMinimumSystemVersion 10.9 + NSHumanReadableCopyright + Copyright © 2015-2017 Lior Halphon NSMainNibFile MainMenu - NSHumanReadableCopyright - Copyright © 2015-2016 Lior Halphon NSPrincipalClass NSApplication + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.data + + UTTypeDescription + GameBoy Game + UTTypeIconFile + Cartridge + UTTypeIdentifier + com.github.liji32.sameboy.gb + UTTypeTagSpecification + + public.filename-extension + + gb + + + + + UTTypeConformsTo + + public.data + + UTTypeDescription + GameBoy Color Game + UTTypeIconFile + ColorCartridge + UTTypeIdentifier + com.github.liji32.sameboy.gbc + UTTypeTagSpecification + + public.filename-extension + + gbc + + + + diff --git a/Makefile b/Makefile index f91fc8d..ffe52cc 100755 --- a/Makefile +++ b/Makefile @@ -82,11 +82,12 @@ SDL_TARGET := $(BIN)/sdl/sameboy TESTER_TARGET := $(BIN)/tester/sameboy_tester endif -cocoa: $(BIN)/Sameboy.app +cocoa: $(BIN)/SameBoy.app +quicklook: $(BIN)/SameBoy.qlgenerator sdl: $(SDL_TARGET) $(BIN)/sdl/dmg_boot.bin $(BIN)/sdl/cgb_boot.bin $(BIN)/sdl/LICENSE bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin - +all: cocoa sdl tester # Get a list of our source files and their respective object file targets @@ -96,11 +97,13 @@ TESTER_SOURCES := $(shell ls Tester/*.c) ifeq ($(PLATFORM),Darwin) COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) +QUICKLOOK_SOURCES := $(shell ls QuickLook/*.m) $(shell ls QuickLook/*.c) SDL_SOURCES += $(shell ls SDL/*.m) endif CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES)) COCOA_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(COCOA_SOURCES)) +QUICKLOOK_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(QUICKLOOK_SOURCES)) SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES)) TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES)) @@ -142,33 +145,61 @@ $(OBJ)/%.m.o: %.m Shaders:$(shell ls Shaders/*.fsh) -$(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ - $(shell ls Cocoa/*.icns) \ - Cocoa/License.html \ - Cocoa/info.plist \ - $(BIN)/Sameboy.app/Contents/Resources/dmg_boot.bin \ - $(BIN)/Sameboy.app/Contents/Resources/cgb_boot.bin \ - $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Document.nib \ - $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/MainMenu.nib \ - $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Preferences.nib \ - Shaders - $(MKDIR) -p $(BIN)/Sameboy.app/Contents/Resources - cp Cocoa/*.icns $(BIN)/Sameboy.app/Contents/Resources/ - sed s/@VERSION/$(VERSION)/ < Cocoa/info.plist > $(BIN)/Sameboy.app/Contents/info.plist - cp Cocoa/License.html $(BIN)/Sameboy.app/Contents/Resources/Credits.html - $(MKDIR) -p $(BIN)/Sameboy.app/Contents/Resources/Shaders - cp Shaders/*.fsh $(BIN)/Sameboy.app/Contents/Resources/Shaders +$(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ + $(shell ls Cocoa/*.icns) \ + Cocoa/License.html \ + Cocoa/Info.plist \ + $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/Document.nib \ + $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/MainMenu.nib \ + $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/Preferences.nib \ + $(BIN)/SameBoy.qlgenerator \ + Shaders + $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources + cp Cocoa/*.icns $(BIN)/SameBoy.app/Contents/Resources/ + sed s/@VERSION/$(VERSION)/ < Cocoa/Info.plist > $(BIN)/SameBoy.app/Contents/Info.plist + cp Cocoa/License.html $(BIN)/SameBoy.app/Contents/Resources/Credits.html + $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources/Shaders + cp Shaders/*.fsh $(BIN)/SameBoy.app/Contents/Resources/Shaders + $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Library/QuickLook/ + cp -rf $(BIN)/SameBoy.qlgenerator $(BIN)/SameBoy.app/Contents/Library/QuickLook/ -$(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) +$(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia ifeq ($(CONF), release) strip $@ endif -$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib +$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib ibtool --compile $@ $^ +# Quick Look generator + +$(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL \ + $(shell ls QuickLook/*.png) \ + QuickLook/Info.plist \ + $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin + $(MKDIR) -p $(BIN)/SameBoy.qlgenerator/Contents/Resources + cp QuickLook/*.png $(BIN)/SameBoy.qlgenerator/Contents/Resources/ + sed s/@VERSION/$(VERSION)/ < QuickLook/Info.plist > $(BIN)/SameBoy.qlgenerator/Contents/Info.plist + +# Currently, SameBoy.app includes two "copies" of each Core .o file once in the app itself and +# once in the QL Generator. It should probably become a dylib instead. +$(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) -bundle -framework Cocoa -framework Quicklook +ifeq ($(CONF), release) + strip -u -r -s QuickLook/exports.sym $@ +endif + +# cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided +# boot ROM directory. +$(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs/cgb_boot_fast.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + # SDL Port # Unix versions build only one binary @@ -220,7 +251,7 @@ $(BIN)/sdl/%.bin $(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ -$(BIN)/Sameboy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin +$(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ diff --git a/QuickLook/CartridgeTemplate.png b/QuickLook/CartridgeTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..bd65b5d6c235915cd1888394a9c7cd19821a9ff8 GIT binary patch literal 129246 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