From f1e962337103d66716346d8f0f20d0c3e508db91 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 30 Mar 2016 23:07:55 +0300 Subject: [PATCH 0001/1216] Initial public commit --- .gitignore | 1 + BootROMs/SameboyLogo.1bpp | Bin 0 -> 384 bytes BootROMs/SameboyLogo.png | Bin 0 -> 14763 bytes BootROMs/cgb_boot.asm | 1087 ++++++++++++++++++++++++++++++ BootROMs/dmg_boot.asm | 141 ++++ Cocoa/AppDelegate.h | 7 + Cocoa/AppDelegate.m | 17 + Cocoa/AppIcon.icns | Bin 0 -> 261383 bytes Cocoa/AudioClient.h | 11 + Cocoa/AudioClient.m | 115 ++++ Cocoa/Cartridge.icns | Bin 0 -> 278314 bytes Cocoa/ColorCartridge.icns | Bin 0 -> 366311 bytes Cocoa/Document.h | 12 + Cocoa/Document.m | 366 ++++++++++ Cocoa/Document.xib | 109 +++ Cocoa/GBView.h | 9 + Cocoa/GBView.m | 163 +++++ Cocoa/Info.plist | 71 ++ Cocoa/MainMenu.xib | 405 +++++++++++ Cocoa/PkgInfo | 1 + Cocoa/main.m | 5 + Core/apu.c | 415 ++++++++++++ Core/apu.h | 59 ++ Core/debugger.c | 410 +++++++++++ Core/debugger.h | 7 + Core/display.c | 384 +++++++++++ Core/display.h | 7 + Core/gb.c | 444 ++++++++++++ Core/gb.h | 304 +++++++++ Core/joypad.c | 50 ++ Core/joypad.h | 8 + Core/memory.c | 531 +++++++++++++++ Core/memory.h | 9 + Core/timing.c | 81 +++ Core/timing.h | 7 + Core/z80_cpu.c | 1342 +++++++++++++++++++++++++++++++++++++ Core/z80_cpu.h | 7 + Core/z80_disassembler.c | 680 +++++++++++++++++++ Makefile | 94 +++ SDL/SDLMain.h | 16 + SDL/SDLMain.m | 382 +++++++++++ SDL/main.c | 190 ++++++ 42 files changed, 7947 insertions(+) create mode 100644 .gitignore create mode 100644 BootROMs/SameboyLogo.1bpp create mode 100644 BootROMs/SameboyLogo.png create mode 100644 BootROMs/cgb_boot.asm create mode 100644 BootROMs/dmg_boot.asm create mode 100644 Cocoa/AppDelegate.h create mode 100644 Cocoa/AppDelegate.m create mode 100644 Cocoa/AppIcon.icns create mode 100644 Cocoa/AudioClient.h create mode 100644 Cocoa/AudioClient.m create mode 100644 Cocoa/Cartridge.icns create mode 100644 Cocoa/ColorCartridge.icns create mode 100644 Cocoa/Document.h create mode 100644 Cocoa/Document.m create mode 100644 Cocoa/Document.xib create mode 100644 Cocoa/GBView.h create mode 100644 Cocoa/GBView.m create mode 100644 Cocoa/Info.plist create mode 100644 Cocoa/MainMenu.xib create mode 100644 Cocoa/PkgInfo create mode 100644 Cocoa/main.m create mode 100644 Core/apu.c create mode 100644 Core/apu.h create mode 100644 Core/debugger.c create mode 100644 Core/debugger.h create mode 100644 Core/display.c create mode 100644 Core/display.h create mode 100644 Core/gb.c create mode 100644 Core/gb.h create mode 100644 Core/joypad.c create mode 100644 Core/joypad.h create mode 100644 Core/memory.c create mode 100644 Core/memory.h create mode 100644 Core/timing.c create mode 100644 Core/timing.h create mode 100644 Core/z80_cpu.c create mode 100644 Core/z80_cpu.h create mode 100644 Core/z80_disassembler.c create mode 100644 Makefile create mode 100644 SDL/SDLMain.h create mode 100644 SDL/SDLMain.m create mode 100644 SDL/main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c795b054 --- /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 0000000000000000000000000000000000000000..b219f7d2a198da14e871fc1559abfda928c026ed GIT binary patch literal 384 zcmXYtJ#GRq5QS$&;nEjC_7&1+WM7Fci{!{e6qI{|y5T9yQwX0cF;Mj>ZX4>YBeBtshI-bXqK=bkE%wyuL>49$o&`iZ`_ zyRIXPI1L-xU2fCgr+fPROypf?^O&5sS~X%YjCl?$CXPeQZD3|sv|z1St=e|Ie)k{u z`}O*LI`#b?^ar8>h>XEjo>}RFOSIaH4Kws7&Hxzy#^}8>1}kk|(p#+2GKXopU3Rh7 zF{NQBrCJ${E@Sv`fVua^Eagl|!kc}JQ9NV=gaKhxcxNs1cs%ZL*aB*^#Ruk)aV*~O K*b2q3+4K*8j+-0+ literal 0 HcmV?d00001 diff --git a/BootROMs/SameboyLogo.png b/BootROMs/SameboyLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc9706080a1d5e161d506f2d2be6d9c8e1446d4 GIT binary patch literal 14763 zcmeI3Z)_7~9LEp;L>R#cCPQJ-vp}L?dw1P+TaUJ6w1ZCQoTJDl5V+kv-7ahIj=S61 zO_L2wTtG2om>^;#z5o=>e1nl_l!QP+{1Y%lvWyUvCyV*4SD>(o!B#*4lw5lhSP$6yrGkYelFckP%>l6tF}(lS-*eTLzlZbrYHPhO(_WV28J1(XYB%Q>cwS&xI`?sf zDqs{a#dBt|E+X}i|_CWbh?!HobejwG%v4a$ujHol5hh@r{WGU~_yN#qX zHCbXn5~P4?nb60T^+BYuSl(K8xKygF)3h3T;2q?Mly#c%UL7zkz|^`82{iP;nJg+d zdA(D!G_zBi=DzdY)84(MBnpdDG!?sNS{ukH)7b2utj#Hf>xFfR zB*`jEa>QWu%c3RTPnIHWX&y~h62148rJzJ|$dZuIjFf1Fl$6*37+viMFoi}$>6D$& zsG63jN}w?kf(;%;kp(Fs*Cu!%x@&ljck@1$b&C?uyA$m-{yMP+NIt2~!4Zq0_9>|& zn$&GqelE5AfT>p3Ou#MB%-lr|DZ#ZNi$`rsA9re>qTl1R-$t*tpnSNTPHh` zigUi`qG1Ai`vqN-`!9bW<TSb#(f(3z!Ma{mtdKW1TuC$Hvd`Zvj&<-+0fcINzG`29DQzxwG%Fd9Y>g;z=bLst zb6$8@IHuB`vd*@G)61hqq$6j{t}*go-6?@=D|}{vk1I_6d8N2I@_XnZ2ZcL&$cfQ( zWDt}FHYT{Rh)@B+h0?&r1Q!+&Dj>K}8rYcN!XiQi1Q$vJ8xveuM5ut^LTO-Qf(wfX z6%bq~4QxzsVG*GMf(xaAjR`I+B2++dp){~D!G%SH3J5Ng1~w+Ru!v9r!G+Sm#sn7@ z5h@_KP#V~n;KCw81q2sL0~-@uSVX9R;6iC&V}c8d2o(@qC=F~(aA6Ul0)h*rfsF|+ zEFx4uaG^A?F~NmJgbD~Qlm<2?xUh&&0l|gRz{Ug@77;2SxKJ9{nBc-9LIngDN&_3; z6IVsy4M3p6*Z$J*{k}Iw7w&=Y2GUZrB}P$w@Lj@zw<&7kUwFMnQQa&>UEfMk!iN+! zUmMzTdKJ{(X^hmzGozc%p4k0#ebwokXYXF{E~GCF9sKjqqe};_ZU10k-ppA~y?4qN zd1Bepi)fZq`s#8JI9X?ogR5z*!0Mf_cwl)ur4faY3mu=I6C;s&nthpddy#4 zIeYNp`MYzMpW6M$@R9Ycx0jE6KKtg~TjJwu+?Q8h*{7WJ4d1%W&;9Gn#NR)yT<}X| q|J65BRq~ZvM=n-=HTda`G&Q(G{(bH7&EG-_RAWO^WO(J)9sdD_A1D9- literal 0 HcmV?d00001 diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm new file mode 100644 index 00000000..17fe37cc --- /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 00000000..bff23ed2 --- /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 00000000..69b6e0f1 --- /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 00000000..d443346e --- /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 0000000000000000000000000000000000000000..05a241c99cf657fc92e594c6ad2fcf48f23e464b GIT binary patch literal 261383 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�$H?BAfMwe=apQ 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

k%MuEiIP*A}5UBQ22H;i*enXrajP2o1I$ zrN9g*E(CcwNB?QcFz4_B!e60<{!8)squ{-S!w}-HZVCV4=NHIB%R=;@q^v>&Ggv(H z!ud}xzWRk2d?BwBU&W*nME{=bR$|L-I~ zs0|#F!_jv50TG*MPR3t8t#vOufR*i*R_oQPLdwP7)w2|^4-=P3`eryeXvUB&%+sJu z!VF$T3^to7EZxs`xCm`eOOen!NlgUMjKs&*yXaO(tNY1-dFl7(J4q9WtRaQsq_Hsc zK)3wk=6r`e^a}BAVt>G<3c4dy&Q06MB#Ha9qq_+yYNymrys9E~@0T_3W>74b5@N+h z?8qS%oIZey$ZlivoR5-(EaROeM4z^opVfV6|-cx9rV{|73ZF+>D|` zBDtS}_S?SSKBtfVWF_T0yA7|`tERJ8N*8_M6)eBhU+h)c>&gJ+go>rU{8EupIbcv7 zHlC%@cw&f9w|}=QgfY)G7t-%MB5#n%>@Zp^wRmAdS?uUOeURMEI7W|;*EzZn-=gwHPa!-npz zc-Q)1ETu^Vc|8h5@7;EcAm|WYj(>5~MhJ{09bHvYghiaS z_R0i@6sP}27ZLnS>gT&soEGC)Jk?hf7>OMKd=X^CgNDoO&;v?<&f8c_D(_W5WdP<+ zp|2y{aVmaA<5MkoC@O6?DDVogvlnx03a-6dsqLTf8?Tg5Ep*IiIXm!zFEWOMhj(%C zYlAhuL19RCe-U}2VK_d< zue>?p#wqj%;C(2OQem;iRx8v=io~~9Kwca>|1DUo*6LKps_D}|x)T6uA&DOP<>P}T zRxYZsa_BhkW|H%!TofJXzQ)NL9Gh7HZ56g`f5_{mc=y~TWwi^qYuk{e+cO~q*!K8z zafDNXSZMlDa2WW3OK=Rg2u3#-IA`k4CE&`^$Wja8U;^fIAJi|XjG*t7uGz8RSmn`> zhug}k@B2MBcjE*J`U+@QKDuHCu#*p&T`@!E_&y4xw!dNwV@Q>|z}vP|jJjBN(O9?P zr;Qf){I}s4)LZ4X-mgiqEh(?YDGs{U8zXLK1`8-Dpb05EHj?R0DJzI==jZevnT^K}MK6y#B&^08%vywUb z(d{O08-2!+!MWe5StgR;Juq|Eo%tFAYl>otaz-FNF7xwIpAV>#pQuqZ=hQcObdJhH z7g@TeMSb^Zm7skX?!$-rlOn-SBfNQ{`}b@Hv*GB4iy`jQzN%{k~y>3O`ap0fQyrZaH8q;10YM-OQz;*Oh={3<*u6_r6w zUA6%wPC>xv(*a4V-tS*ay&>9&Cvu~{Xa2_?HKg^M^>kIbSB|YLwwFZ{IH!W9hnM*j4|!C^FRo>!aD7Kb{eB`o$VJ~krmGwGKaMAsL#MNsrFObPzc@~Qm;a&O?n z@p+O^B%lTLn=p*4y_+`Nh^U{hMv~Mpwz$ym>~hchc6@tF01xr0e*@AQNo>AI%EM?g z+3XoIdz`)sour3+BDkBwImZ_|x$}E$)W^rKOdhJk?K9a#+OL#&ojepeJDFue{^uVX zH=Mx3fxAvcDZzzfm~5He?meGKzB3|g_~BOdO)~W^uGKcSwLY`-l-ck`f)JGtG&Y)* zBI{M6w<%bdoY!iQmsYwf^UAV|I@9-oikGCyr>6xOb=#sQnKeEyhGhOJ5GxY*1z=j! zbTFIQMr!@?M-dM<$B*rfRZjznkq$CE{7#=O+ObH~YN5d{U#mVARAkW9jMkRUD8-$W zc|1BsDM-DlzN$hc->x?MLPI@-9GP80y=D7GcrIik$=94_UqlRiPk8f0vKckFr2D<&@KrExBh!ojwYD2P|j+~9$>Y9%C6 zPWf=!RPXXYZ4EGe**JfsX=#uzvpX(4-Z#|;i2~S-HmpW;{|aT^%ceGrszBz-WJ$rV z;DNX1p1`czPhjEXcY;jo8JfvfQs6xpP-nPDH+mkz_6K$a`IZ%eYaDLtstUaGz;BUD z(GQv=JzGLUp~A?5mA``C1ikrtg4A;*f$Nyy7$#Vj8>gX2ZEh75Rx-Zfvi@)!Z6nfi zVF34c#`SM3W}5{2V&*34Y*aTGZkuxQ`$aJ^xuZZREO={0!L*c;Kf#QMGJxWlqu<01 z;@akkVhOK7$N;wHs~CES$+m6CulQBov$vY2C*EEy-(_^%B7_VcX~ddoDP~6}WbVF> zXik!r!tNw}4CqM_)p+pScTD9+Upb#_1$onqTsYXEQ?s6wCub|eY*rNJqx$&BwO%US9i{)lGagtKfahP$Ks-MG4>UqehxnpNlUA(qV6QsRscO7I@v_B+JG z?}lfFCy*Y$j#z0HBeR^Hh@s=2?UcXK_!XfKIEykwy#$#K1==rhX`x9@IWPT`sT=*b zXngt~CbdM#Oiq;s#-s*h+5NwmRCJ7kY>=T}`{m{32TjdnO97!V;^2MWXaF910#gEW z0y!SK8wz&*q9Cw?fT-`bX}!|*Ah$vrL65zouk!yvj;gA1y%VA3K6qZB7k%=tJ8z)a ztzFtOUo>g`Lj_YZ{;Z8?=pw)|&@koRV-TP-l`R~Gu`}WZd_{2}R>dIS`QN2>sxW|4 zv)@sr`)@i1t5kdmXC#7u|IvA|VTyJBOI9}-U{?Phs8f@K0j_s0DKN+IZ$X9Pi!z%J zKV)BUr16#m3Vz?MBR_QV`zv18e_j98m6c_tc;44^BBuzzC(vR}kRoFEgCQ2XvZb07R|85Ao#4xe znMX+q-*!36#{TO`=d+wUC6RDUA+mK!T zyEeh@i4F>QZ&d{6-wWaZ=$*T}xrEjz z<@bOq2_BUcI~~ot{EN995!&_fa8Y}K? zLLF|t8CQAS?^h}HAT0^Ji6#Ty(!T(lewZat!K37mN>ib*PJItaEx)y`%_0Jrqlo89 zR5UuMt{xx(33vw*bHjq)jW85Xu;Kp<2TX-tDu?8l6{}3Su;!g(?yH#?ndPbaM)idP z2H)0*%P^OY^H+z%!tjJ=Q7@Ea+@Ixe{d`WA;_1h8LW8pG-!JJy1A4C8Sy$alA=DiL zKLh*4iD4@-xMpN(f+Fr8LL%WGqAr@y#`s`%1uKOcmK>Mb>pcdSy(-dD>0NJdsC7`I z&$H^{K{2Q?wnt*IBC7|)BEthh#S2bQEYmV;LL9uss}tEB6ybC*+;-GqUBR$Nd@%at z{Itula+@p3x>O!NRO@KWvi)>H?Go>eL=Rx^(){$@yuHv90I2rw9bf?Cpn zqGCcd2iNZmm!@pb-BPKATFM0)%sy&iEP0{ zr_#(I*ABa#eGzAbuKrnR;f{I(TM0lpxbOXXX~Lkw9(4|W?5M>-_ctQ*o}-NHEt%Rl;EN8iTAFX8OV_J?=n z+)f<)2@we{Ym7v2)HuGMOaLsKDg)sY#-xc|+(tdl?+^ydF*KiOL+oc|zpxa1H>Cfi zPn!}Ojz+CMHSTlOGBx|Y!55ztja*(w7DG$VVf`Mx?HI{YS}*9_l_*TL2^|j==5|9! zPD)(}x?rcu*MFdJq138XYQm4y(UwW0% zNr?A6G4TV&^$Z*xmDIvDRXQNmrW8=I#Dz}d+=i+sxdEqMqr@Qg$oPNi2#-cd7-h`} z0xb^J-Ao?7CCcA_1nE&;Q$q1%bmwU_xHKlI^39{W^%QO4d`ZneQabdqX{5}8W4;gA z!Y$%!)3kqNDUxMDv?M$INHXRjyu-v`B;V)=+5a>xf9-qZ}Hc|Ao2s-5; z#e3925@SAqPw!_LQ*s=lH!4pJ8Gtmf{xDb^G~h|ui%x>uo{5oNg7^r>thU)5K)dyw zglRa7!yTK?g#TQ%dyew^v`aC(;q&K$c(gwUi9^X|USH+%3kuBaKuQFz6hTO2W#M-3 zQXcN_O&uImLXpBuPtQd64^Nc`r~pkINGcfiO~DHvu#^T0(MA!)?-qrXA$#~(u+{D* zy}-E9+D>ewT2t$h`rfX0mmD9R*t(mLR|If^Lxw{!JFW+|KxKT!x-bEj)HXHLTbG49 za)H7u-xc(JiEh1pTd090(ZY(|Mhj4wq0%k=xKqmk+`f=550k1iX)_Kvb+(_rVF!nH zw<|GbJm{Q4^5nUAMd@%iHz$O+Xdc*EhvKZ_P7s%dG-Dk)4z;RGDlL@gko&eLo|FXq z)>@91YIU#os@Wc{PnCBCey??EDCi55?a2W4Izkq8Qrdm!sHi?Kabx&zL_aopopiCG z)P1<4N1dtT$;OzLpnkK5>TXN99R%ndB>zr|S&!6GMq9FpAgFa0gk&k8e}K2G!q)g$_FiXmAkeB72YYXO{ELqF8>5T&%}8RR3U~CgQuib!?LagJj;zskVa^Q#_NcTbd^P8%a;>l%W}#a`q#W|{ zcyA^eYbKEbMFpQ@xx5&tsca z9HsSDJkbI(B4hNA&0$+f^=+T;-7yCH>ob#7{jR_)Z0rY8hDnm6e+SO?%slT$5Sv3W z$%So3VHZ7>{`Ggys2a?!`UU+`cK9w5%c$|Z0@pBOX!1s_o_E%70u zb>alevhWL|`;~HN>|D87c6&ST8Tc_#cf|nBPJ<~y22vtNVv}M1ll<}G++KR z;bPUN3q2rnU%K(UlhbpDtu*I343n~Mx#qR(huL{FxSYhjEw%FN8+Afw{upffcZsvQCLpWEwfxvZ*#fk?axLWk{B& z_s6ow1NCK*2~8vQb4t&3#j2Y>IZoHbF#}T}%*MUt%?J&70Iy12gRvA6L^M@yuHt$C z-Kb$wCHJoB=((iR(D`X>pXdW^sv&Cq7;LB%;evWhwtdOkuxXUNG4jxf#8A<79ZamX zFVWt9okwnZKOEbM?Ea^McCz-Q3(!VhcQDHVjZ^_%l)W{fT(?;oN-CIbf55hMOkY9H zHS4B`X&lVgCeymj62ZfcMBMV!<-`cpg@>a&oa4(Y-%MM1l0ygRVb%TkAioVeI{)-1XcCsbyKcsB?D3)fEq?B8|;a=ksb!oDz|v0!7BJFQO^ zJFKuroK10|^lY9w!-MX=x7QrhbE!P=9vL4g&gb+ER8fY~zlpzh5zQPEyWPg|MKSu= z)a+%u9pn9AQZPEW)Kjy(TVD!N(~g-89l!o@Lhv_AIWA~lO^~z!udl>aLCLPhfPSG^pN-5 zH>`O@4<`Mb)CB2v74B|+!%jIZ(r9K0+ACk1yt9ut=XNm0JSYx|xUX2-YW&n(?splD z3ptuqT3C`wf23z0NC5F$hLHfyYY=uDmwo^ zHJ=zs=ZeOZjYj|O+lF#BC;GdcnZqrOncOz5)hH+`1$)})g$=IjV9hpXs!Afvjp4(; zfCZx6cizNhcT@;2`q;r4s04-?4OP*;nvs>K%FE5Co@{vf=k9B>*J=(kiy_zXd~i%e zz>(sI5$B^I<=t6dBNI8e@6`BUl3&o0n+m(MHf9imFcy1}Q++pnyIt1eVIj$lK0x=; zB=Wf8ov2a~4)$mw_DKSwqWrJC1Pz*E)qJN6o@|C>mNj;{in7cS6?3sz(S z@T+w0-R;8kq!_svx?oLW`qWc5iMV3yV43xr-Hvk5uQU-00-y1D*#C22>M~ZxZ|q?( zIl^8VT!4p-{&_baV!7^v3b^d3o0MHJpGfjyHeR4m&DE<)~HHy(+nNk>h@vmC|xKWr)Wf33v?cx zr0{==|ETf;yK&_EJKz1NaYAF$#SdC$vWnxS}gXhxbN04L_+6!YKRC&zQta z^Y4@px4-P3mry3p+Ys@LC#Te1u~P5dee)v5^(Kw=;uKL+P1W|QC((~ukcHI<$bx+o zUiX%7Vz;djo}4G61Gd6u?>W{vgF8b!N%EO@ghL4mrj3UxU6|t%UKu}xhKeR2e@!v* zShknO=h%_fC%-RN1dBKTX~b1cwO3!GIKTFlRuZKwj1Zmt&woG-{&b0<)_a_XS`Ma%F#n3x&CzI$>5%A-tLT!-u5WH57wYDEI? zwxHMwzt$1*22P4ysa*->wxKH~=0_Jh3@yNGrmWpMTl`+kXW9|`97H)$z22ZeNA?xf z4&KFt)ZqDSJX3s9w>T??(W0K@7u^`hW1IQxJD0;=zU%at4=lYb;RSUvumGcrWq&n- zNDSVU!cODh8NhbZo9`SA6=A&^SNl9N6q@@(JbW^vx<&Ieb{8m89z8ppG?avRTq2p8 z;zq{n<)u@O#G13zDRp~ILda8uEa>M9a3h{(3(3unA||w*OsBT_n~M~l@A{1 zXY_V`vOf=(2cx6_KgkOIB?;}Mvdcq@MZBj}B48-k0b&%P^vP-uFpN%CyNZZhGml3c z{@JcxZ*lUpaunHW_Wv9DiEZr@kVc-p+zwpsYJ?yTkWBi@lT@9JK3Xf@(FdqOKb6`~ z#1lmawDgsmWQY1E1~6d;rB|$>>h--cC)wd}vSE|8>W4tzj(+A^SD9v*!&0H9xCp;> z6Fe?$U0xG*S&$4!fn9gL^VFLPda~v5j zKnVT@E)lY)Ps~YL=e%jM27&aOVBnvrJ0Nmg$whd4EX9R1<|}yKMVz zoAoROEPVn$o_GjTB|1*xDKogTxq~2h) z_`%tQ8%K$-elNI%AA(+oI9)+MM@5C{%}d6;*Ah?+=G)xx0&hdRumPj|bq5NCbfJT= z^ZS2CMo45DTi|t3v|PffTqegJU_J;-^)`G%ISNjHU&qrZOX)!B_zMdMc2SJLhSj{o zH(j=!*A2=$YA8B#f)<-KxrLX9%4gRUq)GFf%Obi46^RYQL*f{{2M1%%A$cK}#QvE% z-R24Y$VR5l1Sx@NfJ#N5>DtM+ZN{*zZbMY%R%n#$`{s@0ocH!1x?_7erWrEg0aAyC z<|LcH0#({sCwnSg*CO#Ju^n1DHng;kn9tbAto#M1E=w`r?HN#-IP@xkkc0r$`+K$Dm>>00~6GJ3acsgNQ!L%7Bq1s7tT zw2#8F`_lSin9o^)*wX|fS*jkYJJBh4OmSIapPJmn*cfd)xRY`)3Brn)7#Dv_P=rm{ za9(kFz*>gri|y*>sNvOo*$BuXwDefkdFc3RXT{cf1)xB(6MVfea~)?7h3B!LJ&vKD zN@Jd|{+}KkQ|Se2T1k8e*|Dg|!w~+73P{LCP&d2}oj6O~6Yk#adLS7pzAW}0wOGDZ zDfw7n#E^fq&@QD_88nU{`KHSH&JvB;&j`TMLa8*g2)SkQGD@s+#b@z+#aoube81a1 zv3t?G6CQ1G*XILF1eCYkaq=<4ZgNN}Dq?I6d1u@F?$(jQ2N@7FR&=^^b3k@-NEw@* zP3=qM8g{hUZkQ!YJk#xH18X?}K5E*eYrM>}pZiKC(_w_Q zP`D~p;rY9s%$#uQ!?f-j2O<0Rfy~-YB?PjxdU^VFnjx$WUL=jQ0=JhyDHL;(=BpY& zOytoV*=m$rmqY&XUSH7 zCb8@Sh(Bjo*b`o2vlXD(s82ws{pDhJ@{tu>M`)ej8tHclgK9@#iD&#;$s3pF%D-8n zr~8vlBOHeY1F0qJc%}0 zcaPj_=NoNtd*wbfwgy&E*r~_NAQc7zWKS!%k)iU#j5j$x2na<%OL&mqw~Vq`rjA?%MWgFhbd)YTtm4Y zKuw!N=9D7Hto@!Y5!M~8?o-kFZw^ECJTXB*rp@H?{s8*??X;nF;9|Vh49*s9-^V^Bb18`WK2p5 zN&}hetxjjPH~sUYpz#FAR#)!P(b%k^5MOLMUT;dH>P#eunYk+tfOZ?4-c9Gjjwe&n2N-X)10O)>6LxzCpo&Ebgw? zGj*z+yj3W-S=F#qF*1Q=II1UrSmTL1g4-N$JM$bZiM+LUX!u1~im*X{R;TypMdlUX zgy>Km8%+MvaX;c6_8hxngG-1!3IBZ`Ot;80e5K(c5wF>%V_qr==WuxDcq>L3G}=R>cqff~Pos^>9^6RZgoLgJ(FG+df zy|TH`dpQ@5XA0i5Lz<3>5Diw~qm@YD4w(A&9n-SL)Xtd_@2} z-7~_G!kpuKf24y4gurJJ5c$!?d3I`Fx!%W0h-T6h>s zF;sHps#|<7{8Yj5E{F0UHFn@4+3e`Az`QdwcF74Uv{g9@8pvGe<9>_W?w}+yZPWnn zi_7UddN;@Q#ahZoN!b15r`}D=;nqq_tKrkWNxQWW90w2G4*gMLYrqMdH(SSw1}C?; z5NW=>Wv2^h4;fGFV51axL}!HDL9X${{Z;7U#crjNAVZ;puUh~O_7t=oF?k8$(e)43`@3(}Po$SfW#i-ob=y0k~mFqfK zPdke1pa~^BcR{h*88hEjd#A1MWRC?y*MW!$&#pIZDovNo4=PX2n)GHL6TLGxMtQz& zB@6EKDWBb11Ki~evv~; zH{T~>V(-aV-wpTKlV$v(KId^X&xI3J!TuZhz1!3psUkXy+Wwj>_RuT@%17j>%04=>7)%3p_u%wRzI`9Eex;u z?R0Ia+%>&a32QSrb+;I#3gyMGq=%6P98yL5`;7Zy0!-xb&iT-DvsSi8^*_bG&(2K6 zG%n;UontLU81|%nwHzQz3@7jU$Ay&os||&$WI6_KBV9zCl;{k2-wL@O>(rW^T(H>* zD=~*?nr=-Lf^sc&>>i&^lNK*MZrz{WEBT&8aG9L$=ZuE`GR$FyzlY1gE%$C*x_dgU zwI}`QwV`moNWRv+qvr%18IdH5zB6m^czL5MDaZqBUDAlbuXI{o8Fl?a_=XnwS?>6~ zC+b2JU{vyfV#Hct2g^8SbEQ;B`NE_0k;S(sI|zMinoj3Orn2~+jk<@&8kT5> zDx#$lCt8JTe?r+)0eyklZ6$NvJG-05Q&DQothNT%?bBPU#osq(j^;}FGJi}hZbZE+ zu-ktlZ-Q^e(YUU%&{BPYK`~6tipDW$_ zLu4~!};TRc_5_++qbDC8#~7E?Q01^9+}OirK{4N;od!+-6)u-$A( zYJn#inMa!Qrfc&!v6c>2UYN`b0@0hQ3Pqr;D^40LV^y z0b-l0QV`ot-2+KQ%CP&meiZTDJzqPm$g)V4#VFgIlCKwK``f(>Mu|TM9wef&k4MhC zTiLR0R`yc-An}oLN$1Lorhoe9!KyKK#j9FTjdNeqC|imfrHuWi5oJqQ>WjgUNLl}A z&#zgClB1BxE&6UWGgCvXY;!xKt21IVk?<# z1%cm!0WwZUg$f0nGQ@Yjr@?MZ)V4(VK6gh24$KXZdM}*YIQE;6sSI3wOhs<>RNbwz zS2k5`)=4@cygJl{OkNPe<*0ApY~agWi*G{W;OcWY?N&UY=o)85Ysq+tKwtQW19b5l zD#RW+#N|V7!tf83d)^dP1gVZhhNm|-by#Jy#{J1!1Thp4GM52toRd|b*9+|K-!d8R zNy-+C%(he%bk2%FN%hAtI~|k)nD~G!#06DQh=)KP*KbEObV6*{0ex6^;cETR4;@&! z325IxFNKsm^v{1E$_e5j;{Z^7s6@kFL1t&XY)~X@Ia6e`6iJ9VeeOH~A_3rAYpB3rR%_^M%M+4kd_cZ`!fq__7;B6Ao+%P+$sJD_9 zx*rroFsN({UeA2^kzf>{#b6GX`Az(#Tc8?TL1{IWAC)AP;}ljP{pDPO^@JybI)fHJ zgC?%t#}%2@1~yBtC4698Iwm~0xmoBP;0sO1BKMP;f7cB;{20Jb2~8BTE`(8jY9`!< zvwA3~KlSeDN1Yu1*FwDc54gPy#hXYUE6nIz%LrK1&TYjtbHp zx;gtw*}Gn1gVN-M#fdmDp$?-1c?y8Tnnl@5+Xpx?W%6N7xA#iRij6wucbCrIlj#3YJE0&^wr`j97} z{j?38QZ-qwBe+t6PwRp8O*Z}80x}&`7&pK08l+f5rL-p;PyX<>Jgs*N3!L@kekGW( zGQmRlgOEHmcWF&~maP|HNa`jO!Y?DHB#FRw^~0YwO|Bl>Md)YV+Jw~mTTM#NN6A!% z$VOY*Nf%R^*Lq~vJoi%mLICMZcx)v)n)e-%aJ}6ScP-!7KRh>kBp=r4 z!@9{n{2_7fn8Wg|I9K+N)sG2t*pF065kM(Y{V>rG#@$whP*FnG#hc8U8O%x=BDF)9 zw5@CLXFKW8UCtgW>yB5&Lk?y`QF6ze;xs=}{%T2VkZtXt_$4#JKqNbv>S z9TSD$Mvc#{Bqh&6+0X0}lT83mT+(g}CR9%DDZc%YvGFdS^y*z%&)OVtG#bw%;4CXM zGwIW#W7?!eCKro_uWIMoJc5j8hV2lK5$<&c`l6`~(1sTmW47#Mt5Zz9W1g~Oo=wXp z+Uns1c5&r#ZQJkflp*o!%3kqm%G2Inca0mea9GJciq(fQs^C3*mI#_iNRlQbO6D`H z*Uw3{^623HGo+;Dv<=<~+8>*DF7=XxfiMe$orV$N$gCQQU`AfbaG zp$Y;5f(l5l0!mS&NN);=^j;IX6s0#oil|8My%*_CQHq4#1wyC+639Kg-}~MB$NhJI z$vJa&XJ?*yW_A`Q$sYBo9eXcGCpIWtkK{WdUy;%E$r1CwW01{zHvVczgXnrUTc_dS zqho!U*7SIbo}MT~^=hh4qj!<04B9ae9dO+5~F6DN{Pee3(t|AWp+pc4VXR!tbY|TYV*(+ z7}b3S<=^XJ{Mx^3rwXB+bv#!Dm4J>Ez8Pe*N3|JcGe#xG1J#U!WFk2i(AScIs<6dP zOj!`-S>JFvMOR98-SS@Q^I>f2I`#TWtz`CN?g;)ZL?daBBS*sbVcj7_FH+(D2u(Pe z3Rm8RmLaDE@ptWcW|*AGf?6-4>RWs$)jJO9H&WeV=p-L@tfl%#`QMq=T^fYZI^8hg zc)mO61r6rr9Pz-`^=?VfDTw4?)cBlK)|M6)=)b`NCZC1SfG|52ja=}HhRxk`L@kK$ zzh$DbbK5Gesl4gohA-Xe2L<{#pie@j$KrdmxkN^fLPhoNW!@u2ilRL+ZRS(sj@;Tu zqJi?(H*;WxWjE|9tL<+)dGUS&5%k>3Dbi6%v_D9g=L{--8}Q*q>;fnuVx7n&`td*d zYXW(y`<*)R>ip9;i(PZ0!wDqPs~pZH@Y@bjaEHFw36bmT0>~w|{LF4B z39hZ(k=5~7bnr!9wt9|xEDm>$KPgFLpFTmW+55gcbDMGa;S|um>4yx`iQh3|exlG^ zObY6u{V=#IzGtC{2`sHAK{YO@}L{jDPdH*zlWu z1YGXe^1xAPlcWts-x064%Pr$NNwfV2w%8?mHv40?@}>`wNq@Pr)%e}fOd8PFc?nVe zmK?a#At>)5P6p=7)#L@pfQN$XXcC$|8R=r!M~B_mgD2oCV&C>#{-4pq6fZ{z00gfMhh#f#*Z~hAT(?J%_tht8F8bB@o(M>ZU{L`jR-@+2#=#IaDgn$Y*6$I zk=T zD3J zj(csSDMlk>diV_VAE*8HXlzAYc=U^;=^LND30;eADa4}h&NHdjj$^t$-&+Y4kn;Cd z(&<2%h&TJZ*6C>K>o+t!p4JgBqifSca%FY-vMi#t%YJ=|YOR+Q*>EWHZhLDyR`yNE z$bo=ZfNROvoI<6GCmZZ|5Iv|d+u~w$qy3bfI|_T4Op$EmDEB;tVeYR2F}*nZ!-&IN zli&MSJ1}GwiMeninS_h)bQMQ3n6Fm8=H0SaBs@P z75$q2Wat-kw=!=+g<0a>++bGtD#rOiM47z_&s)7ZL7?~LDE3H9O5S1axb8v$+Q(FZ z)e_OpB3SbDxh2Ij-M0PeG(rQ`<~d?kMM4FmZ;Be7PSQQpJ&XP{3G&m$D`?n{$TzVh~Tf()h3-F)Q6AVrGW4N;n{O+r|xetu1s z`nNQteIe4&=J|(N|84rV>~R9jE$aCGt6gOF?n0fOGj`BtrpH&e(B2kwPR6hdJrP`J;Zf|@JM z8s)j~Lp^V_+RGQp`gr&uVJ>F$MguhOQd{FwH(y;QJ}Ici&nPzS6ztap96CcxKefDx zVJ-3Dch{Sk9g=d>TYr=>bJ$-JOy@!OY6xb53)rq`)j4&@DoC$jHtSF+b*=;{HUH{r zB_(fNOEbo_2H#}e*@Z)m$9g%fhv?Ph-Is8NKzQ;0*v!Z53?@59Z$ z2i#)XeYtqFvu-zcYej(wk zB^z0WmjRhpZm$0kpmOq+MoBx7E|p1^v^}6TacIB$&-$t8jjvT9E2gq`2`=6rb1IGD zr%fL2xJbvfD>%O9FdlZ9V(S0zWkHw;*l5tl$MZ>iJ13%`q@ZM1i=dWd6{JQeClGFU zi8JpAYsOzVt}}F8C~n`}s;Rf+&*+{84_SR!q29LAjF6kb{eobXQxh^T?1v{?%`Q|d zr2$x5`N_UP(?s`8@d=#{H`Gc1aW};U+Q=bDb~rKnmaQm}L_Xjl zGl}lfh22;W`kqCCeS0KLGyu?(#12|xdeoBnxPXqTKoc3z@oN`au~NdM5uTbMafX4! zrwjC0;epj5pr#}EZujjooA*i3`C`xwC3)VB;(SISrUc>?&(AU*{`*J08%XxfQd6cY5e zq3;NdCb`m;$B``B-zJweICf*+i#R8OJ!MY)@|EXq^iOW^`|HCn+&^7b>Ny*_{WT(L z2IXX}7+$fgZ-Bx-Q8?^BH~+ZVlv}pCU3WY^sRo@|FDuucl;aCU9#Yc%j*bYBqUw7X z3W`JM%1QeqIIm{VS6XYlEPEAmVZx^~t9Ri8-@1Ev7~iTVEOXcxh@H$$r&tVB`X+3V z>K7CS%AJbX+1jnOU}n3ya5u`_H5qxhVK|gO=fc?f3Z1f`nUOs;zH*UWu`w5jP|`_h zKDUxP!@0rs`mB1l-zgMaGoD*C_A96QIhnc!BDgbMx3nr@<+SE*$<%VD33xNb2L=SM|HAlQ}$@b zVcfQrR;eOvONUF1t;q9433NoPCoC6Av~>4x`0=|7E9VvIOW#*6$A?DN_f%A&wN^d$ zYF0XWi|*F)&kny^9#QbhWRFOdq<#qzLi%qQ@(+sB*k8AtxfvEPTuZJfjaIi34YU+M0Ba&?}0m|7z{l5FMkr&rFWXu?mS{mriz7*!2b0jt}9J%TXlztUh zy4!;kB`uW=VZfm?#5v;5=HtJn8BxsdEpJ=P;*In z_Uv#lMkza=&oX!k91qt>u$jc8-w*d%-H$|NSo&CSZ^Ojy%94fik3UectfSic3tUPG z=MM_+*`;`RhhDt?yennntD56?o6WVg<=>bpHE?%u9s>pUY>&b4ianXIbh?tjxJsVX zwYFrh{_-?ctR&(FDe3T*Xc$xVJ!2+YlDQLWxods1eAmpf8J51NIkPQIRk+_?*b;xt zG4|j`cXUG3Qh)C26-0>&_u65rJ_O+ba02MmHxmbB=hf=!HDb(ncgZYAM0F10;}kyA zD>Nn8bFzA*D9V265s^o$%0Fq?O(3Cl&88zD$uXe-=A!I@In_F&Quzp}s56H%r(~FT z6`RI>qqe~v%#ItP#d*OO-*gW?o7ae>Nj}w zF9s%;a~J(kf$n(TZ)Ps;5*>DTOy`A2@xJHQGzmdm)HqA+(&~DdReP!$-H}KJq2NrW zJ9pK!DCoNwJ=g9buw`oEc zuMe5TQW+J@QiY1Q&{-BeAB4Ip-dK*Q#7?tG3jmE?I}$_M<>-2OHQN4|zmdL>D5&s# z|K9A6PE!t*lKJ3gC!f^tpgV0Z!eZEq zx~6MJV?|NS9un$3XYzrEF}O_SBRt+z=fIOUH&OZi<6Q%CXxTBCZcHJ=Hm?dxrp z*?P-0f2;fxF0qZ|7$dh&-AMReq#I@3w@e(0xx7 zpIV9Gw2w|1Gu{*LI3?{pqVyQ*6LC=cQ_JEndb&G~Ixh(+Y60B>3VCz%qG5(r#SiuO z!V~KcRHd0wOHs9axmhXro!Im>3hpC=y^x)Y_2aEj7VvcxauYNtIpJsqIG9@0%q4#B zlTU)#bkHI-qfzbr560ckv><{ckRmjdu_nu;#h-QnsrrI!(D*8Ommn|#`@vsu#V z$9-39vo}U~*nfDJ&Qv{pnP&aO^JyYi^g@73Z1cqC#oHbmhPMn zSS;HN91p-4j$`5iMtel;1bT)u(~lwd#xu5k6c}xTeSgwP@~x3A$R)JA}>3?N0)K zuEBM2YG~QjSOLQwCIf7)PGDTnW66oX3!A~00S%W_NEq%|rvG=d&(5QHiFS{5=B#G6|>XlX3PDLSau=Jc)wIi-A z%paY!IU9X)aKh$^CGG}scAgu!6B6KM(U-eeM*qb;>ahJ6^10Z|4GE(fSq|~@?QZX_ zjGTh${`UClvPGKA`Ud;IDj1yxgI-p6#XU6G6GI#5>O452wZ8kWD~C?1Shu>NJ3W3wKN&|bRf z7qG)F(ny$}|Jl@H*Zdt%IpHgA=vf!l1| z69v{axhj`f{NF8EoFCHVLz6TE4E?$=bkIyJVr4$^;y1f&SiJR6Hp zmn?~Ve-3Rx4ji@gsB%MJcR>5H-*^mzZDg8j4{x}XGudPWEBcTGeajCq!dIE&{Y zHk7f_I6rr*5bSQGI|k_ARYdD*9**Bk)Ard(ADx&BK6+f&jGs7X)B}kTIQ4tb66k^4 zt4%(e+`)sXIx-xnu!Q;X_SE#Cg(-6aOAGX3MUz15=w>f%>(*?P{j`21FRZ(pJ$n2o zEJ)Gp*po-C^voYSUjKm@yd2DW@oBi8XgPqg?e!n=uc*3VCpSm@A1eKFUw`yp(+3?t zK6OkiJ3lzR(KaH-3Qkn$bO&+$)cRTSg&diIIGIy)R)9vu_gh2{zA-gesU_Pn+I<)- z^KQrelW_H7zt4#|+4Sj-Y}jg*Z{-GW9_bmUc?cTg70cdju$oVL*3O3V-wKKh4)(gV z6z-HJd2=FqfOm;S7w!hOznDJlhrYXcts%zos-^9%ad$$z`WTfYH;PlKst!(+SB!o7 zK2&KfQ@T_|)xR>Vl$e=tYI5oRC}?xCq@9wV8`gF@4fTZ*RO35Zt9$PqQ|B*Ec?qIu zEalC?f{_aJG@?WtF>Q=pkD`d2XY^z9auA(UOJ#-IG(!Id{uM6t{NJjRkV4gyH87W&c5=6^?2*nknPg%E?F|GXRcuO$z#uS98o z@81015ssDXopx}e_8;rDG}l4_Qqv<6^IiiV_umnk$H2%tpFI6V;1m8M0y7!-8z>I) zpr!ABN8U+-AF^6rO9XR*Twt+gqw~_`Rry_Ywe`&l3+xgOzjzEE75pA8E%nr|dEF9C z$F~v4ef0H~6m_ls1k^Vi5CcuxRcpcmp5^LH>950}nwp!9X|7JnYYIlCW*=|gWOm&v z8!a!WnLLRO74<>2X6u6wzuuZX&H7|Vx<&`cv!>@_Y{C_ypl4IiVi=%Pc2%Et+XtSv zD0_K4>Ac$a-gzkH4hrbq?zuUh>*MvHVLoLr!ug_hyWDzXB-_!Xy_S0uL8bq_sc7)e zkk8kzUw<&r(>GREiyctvHzg${O}qqjCl07V0jWi1nY|*VWDO}V7xux7IT>15?0?L< ziBzzt5EKw_H)(JN%H?HIz0q{e3Z?j%81lNaSwrqkLxa(qr!{Ivp@9zl_SVPzC@rI} zb#*^(&2&uPnrWD3(9YR44_>!%UN3%d(Wb~*X-z6a34E3erN4I>X95wpbzpy?i957o z?7_o_jSJ;B>Ne_~=Dvkq9j*_TE4qWArH#LVhMcQDPL7W6UE(on?X@7Xgexrn zc6K}heBfVVILVi>^8V9zjVg5+5T?iEujYU*U=mPZQ%YQPy-AEB{Sp&jVO%$)udm-U z*WeNx&@ffk(D3BYqN05*B!o#e2V{P=n=9it)c84BWRKYp`WTJYNG4aEVB+HsH#7T$ zLg$B-+xAf!zA!E2ETBRD6}|_d2c~Q{7SK>IxVJxg-g*`*iSLe}n6G{{%yUpN_DL?t z7}-)Luv=9cq&QQ39sJxwwoZQ2!9?{3egaORHx_D)@=t;AbCE?-? zv;qByoaP@Q0JC?+S7IWZPTk@>4nQ&|BhL+ujf>~5TC0pUpGfzCOMdzjo97K>GN4v- zeMjdTgGarFzC$D5)z{TE1qJw&xQjYn+}9w@zGtjoiQSO8);~WIlcPT35ZAy~jTM|M z?Pjj1Rp{-rgJ135dmnG(4`?4(QZwLMcik}D0J$ymL=q%-(pPAfyX)f2l4Xhjv|{G? zVoN?ExT3V!bIyC$6V{67zSKUrYzp2h7Uoa?@q@P+e(Y$YGavz2h=B(7y3@EjVA4a| z)pNsg(>vh6v=;6`IL5XWKOrffx@OF1zGaT)Mz~!%&(-$?;rrq3LD4_=ER*@ru6?V(o0^TQgw~7#aFy+SokCrq}lHHz+ZTXYcS*}*C;O^ zQGIy$WqM9qnd27lyVjLYT)X_x9NIMM*?W?v%Te7!Vbvwy)?qaxq+ zeYOwvr*D&m7MKCswc!0^&q7B{P0s@fi6+d|@;>he_^;7~iLvr&bHj!!HqHP|eKa^_ zjNi?qp<}HbGY#j}$WPWMvw!o(lP?}N38m8rd1e{+^pDz{yxg@hlWUyQnF z%gL3LXGBhwN!NTh;zrZpwz`94APepIKu0N4hBh32Q zvYQ~YasdY@6mPt!w5*i5ow79Z#X^vPVy z@$+u-ClGjL8_l3>DS0gfTkS5g|0b~T$oNgZv5r^6j9#0s={Ec4%+I6UY~W@^>;u%)#GY=yZLkUrQo%YPS`KX!C)h^4e>3D|R7-XT|q0-Q> z<~$=Am05?sezIy4j9cRdYHsy)pmjPWh0t?Vt~ZTFLH*Rg=IWyk2yhRFeosEQK%Y(z zulQkM563!`BYiHvkivzU#O=mgga@x+dr81%a>`a)z0xbxb{pGMR|2t*>jDt=Vk+<2c>aLxL&cBJess_pqCfVGw*kkKpTy;Y_#-EWd4R z$|sBw7u3k%0ZLdI`^FKe0ME^mhqniynl@Kf&sowURCT%;H}#BaD&uXU-L77tu6vT+ zYWF($^{PMi(n?cjACYC6k;C|qe9xDe^0TXxk57CFzlaAwf!QG&u8y-V<~vvT6zSb_ zZ%M~aCSjLkKi=s9TOcb3%_QLuQA8ZswRYoC<(l2hKtk)ZZm{)qUN)<6^ zbknxJJ;29V136XH0<#*jmCfusvWq#gXm*z^7mcG|qGQUMpCh?BFCXkxpQR^tciqXo z?baQ^96J*9p-h!!h>76K%R+7T>8r<;HEp;ihJQrrk5Q8o6I(;PtMJutWhmIy6^WZj z=Pj#L8TeIpk{XRB$Ng77paa*KAQM`>8vg)ngnGb*+h2sa3L2I_|7V_olX*rxgb-O+ zfyGMVPo(!ym#bPR&7cbG?~g1F5%MR)KfKQ#mIf8il=-jO2z^%sDZ4m^L~**W_esEUiNVQ0J*bq`ufG zEiKZui!d>(30e*MaMrxfT$tDK#~pMgn>VgambQhFmOGRH0?!&5P%Ewb_DyZRPR?V| zFg;z2A~ZKcI2JbE;kEM+lQ|x2u!3ug=NVg^`nkCvrMuG}l-~n{Ia_HXLY}M!FV2d; zm~K9o?shsm9HMQo4SqJ{zj;WYB!9AU>teyDW7YYEVT+We$ZfKh6kymd#%~8yOd@CEedi(;iEsDn(O-KO`u^ar zie9%l>o`?l!*X*rQ4dZV7lSIs%2I7Ig|RBTS~C58r$bwY6j*!|bp&#gjEd+BH)ea6 zE2VNmwk0qE3i%W#NWd4)gb4B_uuKBLx zh;^oWo#{$Z+_b^4miZ}<(K2e2cenrOsQYR#_hN(_HXgr(TTL!QS+qSGzH4^8G#t43 zPYe^bzvzY4e%N?xM?mj%*0-*w|9ntCFWY6`CzokC{xDSOC@SjX{6{-Yu?^8LZ)YUg z`IFtwv%(2iCc1)XZ@>AoPqLib79i*Du`x==xsd&kTV5N*tqeNg$x}yvdc@_7D@>Oy zF-LgjLsCeU$abb&Tm$4TIM_8g8uE;9Q(M<7T+Gvg`wyNZt;TE1d-L}Ty8ID`sIi93 z))ad_e0clVs?i`hj@;5AFrW^UYT_4uwh9{m?)eITwX6}XpN(JvJI&i7kUwJ&PK;rzq> zlIID07e z+uOA_S?yE(AnNLM)}%bjol2lV-KwBS1xfE(X|yKnb`k(*DL7>lxSkE8w}1$P9!$0Mnn^{z#Vm?aZjWzo1tmkpA#bm zN5zplLL{4bXA`hzrYXF%YDULvv_5nC z^Eo#;A-;oCfBx)<8UHaC(@_spA;`Ob-$xp`Vl)QD;rfPCKkn&paiqx=fSBddg*S3b za|-_4fXH{_#uwWPy!whU1+-`|?%&08eQW#2hB_VCIh(u936$#QJ?Y+~ItKIi7N_rU zewwrP5-+9xyUD8~yJwF5&HAf;@Z)T5vC=2APtFUi=Spiwt*=kGJEPadw{;Q{ExsT@ zMdeY*LM62uZ~wMx4FK~3YTnP+y!LY$(hw7)n_8P>%Tp=1#a&IbLdq>)5QEKj+X zjz4qABo^PO#~-KwYZ`CYVN%l1LLjv6st8>Xek>WhD&1gq@EiO-i(7%_8zr`9#iGW( z8QUhYv8J*iovYfbLP6O+x#gMaFv0Oh0J-1vBQQp*|9cNfimDC@|a9MOs3!@*h5wm zGc@QiVKI12J87V0W^p zd)wU7S8qS2Iolm;`uEq49@I^*3X08*=MT3{ZK-Yb22GA!8eB#4B0qGDe%R0*W_4g9QuC-A zyk&E4SYc}5o@<~%sxnb1z(b!#-bBqKXSz@18%63p5TyQSFV&$?FLhx-&QO59lU2gy z*%d(r(3}4(TmfbE14??$7cf)JJ%p&y5G0*mO*6b+Y#%~R-nXrYSc z%{WP{y1G?wJ|qRaBu|QhxssmE#SW_#bILO^Kz1!%F6V{s-svJ{?q#1rU-XoI{TcnR z&UX!1>^h7~+(`+N&ac*@8t%KfO#ZU2+hys92fe}tHv*qR2{)Q%Z1ljr2 zjU=SpvGHA@j=$gcnE&Y~wl%&4^ybcolH3Fka~{O@W1VQ+4mTqh z(}fhgdErm9?>|%HEq%_|>Bx!|(yBSH2dhfjP8YOru+n!O~b z@8Qf(BYsYfS3|^f(tI7)yj=%&+E_xHlw++bBXcV<-(_$9^35H(@yDDB_F}=ghYLUb zy^^_Ym$7i`soIoYd7F8*Lk6@O$8VZ7xt3i2vtCu9z!WAWR(ctNWifLDd136%ba|7_ z<9Je&#b6&4^5wLstz49=?5s0XLYn2=q<;S$g)V)_#}=RY^BTL~Frm$&WEsu)V2i)pap9gn0nUzwGg4a*@ zR_>kLEAx$ss;ecL-T(R{8F}j&F`Af7avnOafsT%Ky_huU1~1Shtfbl0w#m z%43sVn(^n4@$;8wZ9WfrV4NR|zkA7cIj{v^(NWNf9kLk8%ed#(2_R_Nc}_mj5v`1g z4g@`Pnal6hljDSb09Rj#Ct^J{gcY0kIZf95%TMwl-P9z^sd?v=Ozk-nx5Bq zYq5jC?`QVy=LNREDVOBwnYnglq+g8t>LtQ1b zn9SXgPRV6Ygvm)It9r=N+=w|0At3>UMqJJ(}7!1IhzJV8i zjUN${CJ^Zm%S1pyJ?rK3&eJSt^$Th5d=1Pp6`GeR2^VM{>zOOzhD?lkP|zn$(AenVEgFy&%rg~>22>HglJ?;DjE1}Et>A1s=c#zZeqon z90P+UYL!Uzq|pPL*21sEoP+nQ!Xc1_F9w3lHPKw1a(-K#!TL(A*5b9}XK{5FruWSl z5G>-Ft2vZve7lKvqR3QjyE$fqoGgMl0m~YEQdsvon`Y)bu&DVMeu76%KtYp>Vkz;5 zf4hrN^1IG2%Hc+6=AB$eo8{!Ls8{h^OUfV~;iUOo$Ay@DuYPiTZ@Eq}!wpvUA<$Gn z0%dwM^0C;VSG~O0Vd0Oc6S9W*xrVGb9OJFO633ZW;@$Jf*pVD@31P%D@aeFM>@1)! zM<`x8={v;Yr&`T6{8sGZWUnwU;@c09p`w3O%{MMbppmOuXweFIS3F%`om^HZ(66h&d0p+nf zV5QIX3)rXvP2-VT8UsN*oX}UdZ#BhFX*EW5E=3bj&;cNx+ZXiKXlcS_boFGG9?@UY zQC4Sz08NQO}FIB3Hv6T74Uc*u>DwWh1qMg1Ja7dO#(w{&3JOBPo#o^NB zPm8s8!_am{i-^X4uVyo?$R|1|@drdr)cN7{LW4oj_Te=7^gM`yRQBB_ortF=*p2ykDW@2!Hb4cbPi%eSmn9en`FdkfI@Ln9exM)(s zD4x=&yQPEj6!4-FK!sGAs$i=Y{F; zwp3uTPPGzR11y{u(~A9$9$rRU#E^mK9(UKq0ERw z7T$Z}o?Bcz^kq#>BK#YZj#9I((vvu3|2W-JDikfr$<6w1UHsCe7%T!hqI?UDS#p^C z)_>2k`g2f~u2OS;Vi-nCHoa>}s-A%tn5lQqU0#}Thaq&bW1mKF>;8-;qD!S^ip^Sk z5cxZiGTmAI*61P2c~*ndw5_DPaE_x0sT9;1?K$ZB>66(%BETb&ude=kCVXRWvBd3c zWKC}JK6giHsZ0)3UG_AvSB4-dG1uw^fl}b|XTJH#C+R=Re6Ini_6FHN%1_vuIAtVl zV_LY1J4?A)Y9R4JIe;K=)5^*s+Vlk7qQQk_+_3;bleqV&q>&Tt%c*PnoGpHRRn0D2 z-q{u6`-sAT$RrMFw}9hY0W%Z$>Oy?$*W>_>nb9!N-948P!0CeFv>9*h?bU*T8(O42 zE>cLBrfQEMzV0w38sLV~8^rv|wFH!Z=k$`Xc}q_d9SthXHeZwfarLd{68$|GBgW6U z{nxJlb2K2t&s~X4W9=(Ge;_AEPPFs9i$Tw}Z4XaQxYgQy+Uti5*Ze=qIoBp~dpkk_Km;(M!@ zq6M?>T?&zkDiO>==e}@Zq`VG&y@CBq$FU~ID#5GW5U{gB#f#~RF>q0iVLw=tBeisg zwz9St7Mi4jTA8;GSt?JTs2sfHJEsJwhEo6qJ~c?WL*Hqu8k;V&o$tz=>9IK)0L+_c=lt74 z7M!hWl^?j3_E~_=K+stBl&<>UKw^!I>^o};u5_+t{h*vsT2AfHU-Ya4mn@v%dJy^w ze*8Tmj-fU?FJNxOA^uqZSIzT3sEsP4s9_^9NRpdN!2MVa!9BOV3IYiH^#_7T`79#} z=@4a3dzxdbgF(2Hr!ttWuOro5{#jZP3PU$;;+b1Yx z{-xPh!^rVKl-+~u=eITg7%p5CXG!>I|8hz0=SG9o`3bvRy5;W}JezYMSNQEe^3Rb} zrjHZ=qmBMzhkQCYv<;RuyqN8M7<307!_(8ofx=}T?9k)^DW43$%r@?FV#stFVxjWh zyWb;eKlWK>C27`F=U9nFpdX>q1?{i?Jve#`@ot&Id zf6Q6{Iu?`L;)4G(hdPIcCeTD!UIi% zb|&8R%ddXc(E#Q|v6Vj4`ur2uE*Y;7Ozh>)qGUd{WyI6zu;rhteDhHOr#tSrH3t^e z37QHUU+zkrO+LX-LNsWroj{))JjxLm;`Epph}pO%^$nf@!hi@?aJDtdsMX}MI7A;f0NGo(@Z{&d$|PexvYQwlmll6m92~YBO2RP#ynOw?JE_KuR%fSJ@Oc67F#UmD7C0iqob4qiv<&T8- zoI|`dgrDl`x_%q*Dw!f}8Xp-74~(P}n}w=q#D88QB~3#NBQ~qmt8MIduR0pC2y(4|C2&?82M-wG-o}z=T872muD;tqC)PuD@l#CAy42gLTR~oo2c)% zExVoAt|_CE?=X%}^l4gcm|(jBdJIir`?2(0U`S@SR^{q?t~xrLKIU=3moIDi?YH?~ zE^v1m6T#nVk>)iNlI*EigN;Y%poCnSf8k?BVTA!0HK4B7h!!}RR6;9GG=j)#9B<=n0C`bvgeNoiJn(DXRb6J$!WKxhM zudMfT<~32t+GP1Rfy+|=lVhIdlmBWJ=aT7L>*U+E>%0HyfGX6{h3&5_RP&mino?ABO z(+3!eJVN)})t3oa2xXWCU>&*TCma1st#wd(FCPf9lXqryHeNfQjlp-3n~{K-ct*Xq zm9o}XCA4s1H!nW{6YB0-Is%njw7IMP3(M*XAn2?1KP`QX0BmGDNTnqLsJDod2Nw+n z-Jpy`+U-$@+~$FpH~V(!Q)syanD|@Y(m~PP;O)Gdw4wEUBj%BDuZXs~G1di(1OI-X4eR4b7U$o)K#wg z{@`8yq#{Irv9GGa>Lu54#EDKO86mGF=v%MjbEb?)H0Vr4VYZg`{{q2l19%s_cWW;x zQC>nDwL0iG_DPnT<=jUs2@4&596iI9Cy(b;~gqSyP#s-gC!3dI0O~^H- z3H|h1{~?4jWR3Lrl=ukpcBXNbfq{W93A=kk@=#o>Vb~XAmnJ;`@=qYLv87i_6bh)$Wx(IM&O?aHeRf$QXWr(sAODwGs z{srg8={_B`!qc;45dR!zJV(mQ=pUFa4+AnHwG?`^Nh9Awk9bV^eSQqkuRbOM9aN3m z@Km@_FyMI=Z4Rx5ee9j2ZB6C%Zn&h!1wtCBc_C|ZBq>&+Kys$LS_KI(<(itWdx0*t zb#@q%mx?>UG(pXC|OA235gWsF;v-7L2q(@QxhTB zK=dFFJO$~qYkN^OT{;HXiv*gGoE-Ch9@!_kW;vEWVDjEi#SV9XT_Y~31FFKY{+UKr z4g`5qicz}DcH@WEpAO?3YRzNkkp|qgAW#2+7ePMfHWdopPK|!80RcAfLOs9 zWN0=yXTU=He9#}gw$L9yxU=3vPE1$OzRh`W1Lg@oh=RR&@dZ(ckz|wZWBL~c`ae=h zQG`v@gW;KxMBd~&_>~_|Jj+8|`9mP8_F?_a;Sun&*{&J;SA|1rJ1z zXNjhdA98gnY7IaY1Zh;Si>|!0_2+=Mig8brPVO1JQDV;r$(K?Cu3w_qEQ_CDn|$&l zO>7{WgUNOoWVKiUu!i#D7Fp2pgOyjweJ2Y_s zRQA*n2`~^k=ZcEr&~M^nxjfn&b?Z1c>Dc=g!x#FD_>Y83Q_BOMU-4!K-8p=b5%8r8 z%oIG~B))T>hMIg&g+^hpDRBGOV|U?QI6sI7^ld6|EiV;$U6O{}=Pt{EE z)&))OU7=k%m<&Px-t0?h*y|YvMvoAyhgwws`oj=TTd&E#eDSB`5t~jMsH_Z?H$ta- z5pAZOc@YznWLAFf1R$QJ(+&izmSN>#h7esa@e-Cvagmko>Zx2fZRh5P)6@Eb$2Z#e zro{wlvj=!U#yAUwckUWj@8jKLOHu!+l?#XJF7pjKJ^5j5*Vo}HbcI&TbZ#iRV{;82 z^lNAbfe^tI8V>^0{DJdUbW@$wi?e`ySgEqjRRo&Eg~XtP>B8&r+Gy`>d3I?RL!%zKrAY${vJIS5r}_>B#<`|qjrxWZw}hpUAb-MM@uw(<)nFk37)P9rCL>$?dZsAbT{ zUNR8f+2XG8Nr(rua5R^|tdObe)Xf1^5ds1NwmAqD0DdsrryxT^L&;uFzY@}Z%7+VU#4jbtyPW$(b4||A z7L~(OxU2E;FzE}z!LJ-vA!m5YQ@Ha3A0dv>7CV5L@-cfuB?g#oTYzdF;^BtHSVk@} zFfu+W^|i)hd9ShI_>jIMl8%D0?@llo5j{hyx^vfW+xT1QL75TwpwU+x8e$ySZH5MT zx-l@HX4O>YpONYdt1sWd-@I~2irB@sTU`9DK#F8ot|(W&^Q>Zz^ThBHFNea~{MNY^ zz8YLUQ$d7xW-^G+`)3LbP*bRoH3sj!nF4%ZvA}#*$5|{2xbImN&D5j=-zc2_rp2mC zkRcQEU>r5oO4!}K2tUOJ_Q{7X@?9n##G~zs*Mu3Xa1ZDwW@p=c#!>qN$@V<8U+L$(T0?GvG>V;S1S($&eiA2BR$)6(`jEmBG|2}=q9f{|`7oFgzri0Jrx;qqn8A?@Hfw1ZG zJp2Gw|MojAhoIoKbtNVk2+@2s16z|@48n>$;Dm~R3J3taCj+o2pZ&mt&OIqTOd=LQ zKf!~l1&J5vqd*ZfNdwCU6A#Ynyg#DD`07?MC}H5;4(wE(W!BZeom(zpXMafP5Z1$H zdBvI`%2|2Er+uHKdWmM+T9$bml{{3kG@XhgyGeI-fZgB?&^WWHe+XfhfMhH?|4fdZ z*Bcjl&j+Es#)4J(oZLTL%9kD-`|QE7!Em%x9eN*{4lj&Lu;c`s;o{$4f6tal_TeUf zN!>zV7lpm@E2&~oF0dfCQ zY(S;VVF!Jej2(CzlFX+Q%?G)m{uY<#N@z*7Dlqu zy*T}|h6W1pe16a`^YD(PUnf7KeFcKZP^J;PZ9#{u3oHftWC)lAk!7ja=epr+(*)Exm~BwC4n1 zDDBQ75S@$>A;1d4pdFU4Uq7-*8jrJ&1+`6Jz&VbW?fIdA&UJiD)3W5S<#$slgm z+|YjUAhX-&fV&B=?z{po2)@-G1*{L7%^XEgN8i841f)rq&0$aB5cuO3$Mk0uM)*%k zapCAq)4?;i;s0sx%A=aNzW)#=B(5M*v_%D~Rw@)IR%(hdpimZ3M4=LF0RdT5mZFGk z$s{2_K*Rz9f+T<<0Rjmit4NUAiu$!w{aR~PHrIke*q0>a_hy0!p5HnB_xC%ea|mbd z=iYbUyUTks^JdshDv7K+pF`W^sM32-?C1;{&URlT<>_#3$J^; z!MOOwNzD#FT^=h|K%fvtni-EhH;nz{YrFcs33E+6r3dY;?V7_`!-Uhi7Go8@GGIfp z0_3Oe zkG6Ig;#ek2R#3DDUmh4;!btcwzq+0f`MVt*DE(ZPv{7djO!3%Pp_4z;`Q#1 z>NXL)#+L`vJO-$g1!$GgVhe5_7ks@^}&$?^RC>mTTINN3TVF4ISWi?#uDGcI<_L=eFAuQ7+i z7BC3(5OsH{`t?bhE^rv1T0gKsW19TQz=q1yl9+G@&&UlUNoFmkmJXw%NBqb7Ctt4~ zguDjVPK4ZIO8o0}UBp+#6GcM!XSu1t^*i7)K_I_Lq-hVrj%lG#fr{bXZ(E{F8|61l z8imajV^232#xamD6mS#43jla9yS;BZ?(`ChQ z`hl>EX7c9ItGb5IR`%%l$`TSLM!SYq=sQ*E`fejCT6$J=n@n12Icr3nUQL!gJLccM z>P)JsOkYM#m%O?$HBReJCMvSK#s{afyLe1ZJuc+u4DJ`HGtLH}&oF!?bz+NMuD*KG zuqk?lvHM7T)8`s}R!2@!+n8)qc749vg8HHS0hjewq$XAjWK7IpbnTTM0q3qhEU5Kq zmQ|6SpZo^x=jn*8dN*mk{TsR;UXiZet)qAz?(ewTOz-Tc2LuK- zd?sDlb3^mx#D;Dmi0mHQ7M~YBZ=KE_N(x>3`qS#wg;3W=h&mF%dDlMmOS-h~yolh= zX5&pcc_o5U_kw4N&4z9sAc{>!>qwztynwf|(6enmrW8F1oOn;;z7Fzfg|!4cSPRZ& zHDPN33MZ<``G=k zq3f08v={X6x1~LGO5@c{>G(e0CmT~=NbmTlAX`kNyW;*krEZY!tck<7Y=xKyFM^Z0 zyKlB>L1s~jk7`zZbF%A-uJn3E(NMpEiy`ArFC*hER1b}H{aUwn;_j>&UsHV&-wh4n zo-ggSAYO(R;fZU$Tg-5_^!okQ{#P^!TrEDvHa@{ok zO`_X$uf3z4S*C>>#|Y~xeNDLEjUR*v%CAODj+CU+>kg0RI1LYZ(L#*q9@_OVXHnq; z!jBeUQtcX{0f!GzDcEA!rmwN5o9io;r{j zL~Gw8?HN+1by|7zIvl5ozl1ynAI5v`8roy$=^0Y~#{1fk?E)_c&nlVxa&!H{e)LR6 zZgr~jiu|lLRZAsBdgKi+!nTm$+<$O=E4Znu@$|Q~FhYR#d)(nXv+Z0FbT#Ht_3gUb zuNHhWY3o^hYOz7+Q})x+hUm9>dytnnQ+EWfjzphKJWgSW9>xl_>-<)@?p7 z)=I8wcHt@erg}&&S(FD4;*Ga9L2<#OOrfas`4S${b_%ASqhb>pJ ze|;qAC|`J%H&)(fc8KTrlI7||)KEp(?X>Pq19({MH*wqDM_#2JlCL528ofcJiH2B4 z3e&r(y8ZsGy8jufK|{&4QL#+B@MxGd?h`ik33xrTqO()c6&xA)ODmWc& zpshEZ45%v^PHktrTRji9U7;*bw4iddarWLJ#N*o`;-oj#;ZVt$BBRqe3-Uy9csSRqb5rZMFaC>c-x!RLTu=BY0_QhNfyvZjSv zHzwBDYe9#RJ+}30pD!D`f9TqfKY5q-SOOWk1QYeZ81tjGo{_*AYIT5ctFQH+RjHM? zZqxp4kgNL0#fCXruLagV;Kvf8_@6`S_|A#%&u>@BHzS4%iCxPqUssD#B;$XG7tmF} z<9laRiR<>^;y2>98MXYiNG1s<{l56_w*TawUoJO2?|?1g2z&nItA&WLV1?wmRIUjR ztDS}E73E;=(ko^eanZ3_XDGg+L|+vyK~PpA+Xuo?6$7h?!^o| zy2%WJ2n41g9ZFE(ASf-R9+w7zeN_m`Doy6AmS&+83@E-O#~ilgW3(Ddo0~hbcdI#? zo6o~s+3sqtbFq?r`@Uy;skzU^YI4DA0Ow#I0evXu>a@%LTbtc>v%)9?Rp7kS-rm;Q zanCGcrUmYEwfrMSR<;zijh|!=A61f=adrvjYL<~TN(49}D}|54Ohxw@e8e3(1b*?1 zIp{kH0;;a4;6gE*k1sAN#!3+Zc9BbWVidFZY8TQ{xS6OMQS<_G(BUY;;1e%#&nF~a zn8o>~q^~x83Gjln)74cCOVCpg1RM^}b1Bnp28ct{huH}npHq=6 zP_4*8Wq@FgU6P@sSK(OF@PK_YRYLW6=1`-PpHWz4i-o|b>aER`)qZ4)Gs;7}!r8MiZmq_!iv zUWovY<)jF3n5pPKLx99UEbz-_%t7BtfDq>hN{TL(u?6_Dk}|9mNQa!_0)`W-j3rPj z-fUKI4s|1jGQAywSPTKNw2=4Zm&LPqnKG#a#EY9Z)zy^GYAH_)_~H-)$d{R!)e57q z;9xb@ByMCd1;mP4+Bre-`=|)Y%h>{`7@5;hrT~tK5yZqG0(@p<{;X9@!IFf8yp)7I zUNW7I%z>cEFU_IO07!BIjO@pw)V!oR zmm`=A9 zdDxmY^DvM*ToJ&0esFi)>wLgvRv2TD$$_5xU0wG%Q9WiEGcA~>-r)LUl&(7o!H&H}rJa?rc@4yVe zOiqGX)Y+s8Jcb?yafOf~2}01JEkCXvmk)t`5P&Qa(_EDVMna6rwqopt9nCRvF>q3@ ztNjie%UL?xYPW-(-A*M9!j)z2j(CADN53ncwUuEGlcL`h&*Gm#TwDEX4Y+t`p zfq<{{4qBa$Z<(NMOLg=NKc~7USu9`SGrIfCGj2L+>c2s^7E!H-2}(96nS$HeZ4C z*|m@7^4*H(ckh~D-5K^VY#dMl + +@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 00000000..e1436930 --- /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 0000000000000000000000000000000000000000..36a9adbb2230c73752325a41a116cf9fae8f62db GIT binary patch literal 278314 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

uJFEs+J|4x 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

blf`%}%kJ-h%k83MB4MtnVuI0kZYe0!IK9kIO9 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

{}7blzVkBiEIv-`=)kqBH=cmb$~2=4 zJ7Ann^UVXAUS-oHeLK_BB|v-r7)gOTye*k&o>ZjfVsva?8|{J6Cp2_JSfLU|l}Y)U zx;i6p9%6x3&$TKcu@cW+QW$Gdi7%5TZ{VBTbrstfjB@D*bvjL3FwvJ!OiyR$3H5?m zzyiBo{JV^9h1Lk6T9tiYiLM8?QALHX_@~GkyMS5krtO zo^qN6hy)#jNgyw+P=d|Fp#HnOY}B8{V*WO!r!U!CrEY)GKp$|D>(o_~F>33tKQnSW zt+&|hh%*QHXS{ia{{l*0(MB%=?LM>UA#r3uwQkq-!+bbJZJ+x*ow*ZX0T2OBl6~VH zhWy0+>S`jrttYz`ul)URLWVM2kBOB{H(d+J7u2$b$@u+heH&>6xTf)3?*v(xGV=YS z9nKp_ISc76z;I=QeJjkL4)WU}zZG(DTH7jw-#ourXFzad)a%*J%mdt?FElS}C1Vg0 z5tAz#8sGfD6HN$`^XAn0JW=y3$XE3A_=$e8lc=l`IAg`r;0SBmt%HWY1locY@8g;y za-)nvKa*5w+H1ytE?2k8hIW{9-A)G`r%KGqe<@`JYmJ#jy=i{_$>+YYTKaUOf!wzZ zj0(JZ#%_7sF;*Rn;d({oq;3*yj)oqqH8R&P9&$Y(+_=7tdHb;h8(8m*_M#k9M*Z)U zasiT-h$qf;HIVo(YG^rsdDaD4*tDf)K%IJJZc$y_2z#{M12a51`>6DD~ z)uPBh9~xg6Oh0@GHX;xz7(Z-KE96plL${>-JL5{>rC$qCnjPx;;7~kp4O-EVirm=L zWaN1M>YEy|=gtmuwlx+AOL%02LwQ=G*w+CYr0@nq8Dm_^NOHc}0yH$JTR(~R&W#`7 zrWN&8r-s_7QQ&hI+otI=?@A>mB+JATJvk8IEg{cGXlZ5)h#Xp3ne2SVF;v*$VbYI z4aVYRKCWjyG6m#GKG6$Px-ZGBAWoqt&>4J4fzn9KvM8dDRkXPfla;#$4Wd7@w__CM4p zPKY5u?#alHu^TYcp<4HzAN5@w!U`&XjC6=1R%0 zBRLXiBSMu1sxzQuLF`!2zY?7mYiHi(DX59~ z#m3L0Qa*fqWzj1tdviY}e!t}6u#o=JL`c=meJh3+sqI)dT|2Gc>|kS4W~l}aLQf?@!FS785tn>U0~hSkb;NBjGKCQrMwK?5+a z!D(HA5@fUg`7Ut==uAOorRH^`DrJNJrNJYYj^AT7%8iUcc6naa4lX6=4^lB#e67Au zD8eI3S{Hf#P2%2w#qj$5cHvbWKHx3)ochbh+>wU?hjzEE2bX}q%Mv#RmzUQnG7k2m z-`%~-YQ!$T)J~G{;UlXw|Mi0q19T^;D(P)+3u#}4u?<1ia$nqj#n900(9k79^nRx+ z;Cgr-v5MBxdewj!LA0H+A@%}N>0oyaLH!Q4&ivIMF1oNhp`y9CO}V(K_|vK51w@d$ zJ^D=QLbEN2v$Jjgb(ihRmb-xKOWd#DgYf|0_!7zclv(0bZrESL<3T!hHp>6)52QDb zpfhvWm*OO?7BBq?{ZN1;65FENzn}NT%=UQ%+{IkC-BY#y34U(yK#VvcU^3^K&f5*l<3H09!!M_{`_xHk~c!ARpp$2kjIxW?OTUhLVG_sHva={g zH&~9nI-vBH?xE-&6}8nTe@If1h8kxI#YWy(RV{H0h%Oe^J;O2{D=N=Ss z{XxZdl@%Ln&-q{HynBJ3T=Y0JgySg=>X>u>{*%w3oL!uS$byQ{rYuVJ!!80f2wugs z4%h}u8*EyCS!QkLhXClopT!S?i9ZA2xF?XMs?+uW7>yxRf2Fq*K(2w=`bY9gJ! zXy|(^4wNVY)IvH|WP6Ju`(37^PD`-35xY=SR8-lW9n&L>z^eSuLvQ{a%M+NOm!`=r zO0`chmnp_}ws><{+S1Ob@3@(FTJuW`ATM=qyQ z$MW&BiBHX&V~hl?4FoLy9TJY6&>hL*$&13xF;}Z*^_H=_lpfOG-qokyTu&gLV0ne_ zPsV=oAK#1dt^FlI^O@de&l3JIki78wga~nMm3x znul9PF}LWpJl;aA*q#n+ilY8E#>7J9Ff#&zjit}OZ=u<#qyh@76&W2@?HB-Fs8XNAfx@Vz*aG74jw0SkB|x>idueN5?$?^{DwiwVr?2TTwyIph zV*_{P_Z@D{_kX^!`q!7u1w>(aYy6W-OBeIslrQ`HTeO6{?42V&Z=wD|9j7wT0#4~ z$}-(CHxlpgsLX%ytKk1c?+$tCoVR5a3VG8HpZd2711q0>E20N)G(GJRjP&!6v_HT? zCE!8fCPL3bAN{0z0Kh;_F+^0)?Vl?A7jn~qVru;`sO(r2!+Ar|stZ&K{s&9NqqGVD zRnv?hiS!k|HR^S0-RYR)_6(XW);-UzK>my9n(MW!`u%@b8Q(qX_*&cBfU!!!k0>ke z9h5@@j&=I3*^*n8Pjd?o%T8_Zwk!xcYOMzg*L87YUYDm6zK->{dTWNaOMNU)^{!Lj zAggry@;Q>9o7_eS5pKC{ptVj`)6xPsJLbd#RGuGX3<&(YP-*;7@Xj^?+g4Vg*y4|W zI%S1~(9R5nG^O6&R}lg>W-xJR$m65DD)BW9osRrZVip6l&Tv0U*933MJfT0=B9y9z z-D0D-UPoIaYTH1t$qibP(Wi!b$F-Fw)NniyATWS-sb)<7;^SW9oOEl6;lH84RT7ZG zwbK?04+Rg416ro?j=$0yFF$E)kxK{ySU>xRfNWK8_q7_iU8-hQ(m8q$vFo!7*B*4X zC?bm#sceFP+v#d2o^$GUI`MS~Zk(oQIvo%U^C$i1Fl@TdA_my2E&m&p9WiOB&l~18 z`2b86GM0aF2MoSk>)?te9EIVh4U3mA7t_- zi*rn$LbD_eOX@+e!s9@mSegggbZP3Kc`;$C0>chOv`}%Y;Fr69M-BkW?Whv(F>H1c zfoF4Sw%`5?-Hgp~>Z!{BRwE{%uEvxAWhVZrV?IQ8Y!|B`R#Iu%wu>*xe373Ov z@~(#uB;VOd-ma81zWIc~K=p~AP)#F0^UReM0#|>wUUc{Go2seMAq^u78WjdxxawgE zVOO~4%X|2M>i+XA7X&7nZ-*ONMxT>o*)oIAR@bfn-uHQtJH~g4J<>)E4K|PEcmV4~ zF{RCaQz1A^yO#sJZ4sniX7^`U(C9>Gu=T}=0a}Z-^H9JOQG!u(C{+eHJv#|#`Xzrr zgLihiiJ#{qPn?;Um{?&6L1J+&o>JYvvwek78uq~;bl`i>;A(?T`U4L7#0Vi=bUH^| zM-M3g3x|>oi1HzP-eN9)H=NRaEo2ybJ4gI1d7wiA&v?c*OE@2#;+@PT2p=w=ebjj-5rM!30$LZ2mkjt!3*ystw_+l)3gniF5PXC+s@e-I!E}gm_&ry+3sy zkWXoiHhEiUH}sO}+++2iWe(<$@)nmMs5a~e==_@nU-jQ?|5-#pw!e~X`v1d!Gspp( z1>G;T_Y+?Rgh7CMKf<;JiC&Zb<;kzrHIMmuCDkO#DE_4&8GGuKufoYGPKR!l&)(Sw zVTohH@`5!B@91(+bj5V_fB5qq2)+b~$^hsOmw|j3D1dHvIm)ga!7PkuB^(K=9drJk ziG+ww$Ub!cPL|dV>nDgTN&MRFtUp)^kkcrPHpIfSFpsw+ZN#r&MQr=GuA}2Cfy2sW zs?>%veg*Bv|6uP>py)C;8m|D%LKF5n#r|zT?$jcQg`-Cu0TEHy#(WSiwT&nij5t_@ zM1{T!X0j6O|Jl&eqwTRAdIf#$G}aT*>8-3_mdfzUd-Y`dfV3P^KtsWQGL#^DPUAQ^ z>rAHbDx^g)%n%l?zWcN<^c(-}U`d)B((1kr!4SzoU)6W&yD}MU#YVrvqQg{2pI{fgMw*FFfOaB9W^L+T|$|3Zl zkbqDmvdWRu$iCpQ;wjdT({bXh`ssI+-Uc8O0uMHIrtV;o=&AMEd_Km?^~Ha3(p6j1 z`uTEg_~iNs*iG{OWLUMsGK{A5ZN7Bl2;Sqx{E^hutTWYgc$;@T!|&XnUzU-J@)j>z|+aV>HA>tGH)9-gVni=i*uttsWMS3;d^#ukp zD_IGtK9PzafB0>PcJk_`x1xcRU|?u`P8qdnwQE=`EsTqEF?RV5FwBx~UVm;Y ztxk>#`|dk)2)kjL3*vd^^Tr-K{MDHKf~7jorA-OD-xvX&tW3gU@`nJGPYqdooUJ^j~0W^sPmPZZ%Q*<%p5bEJ*y zxAJnX1dGZy;5%l`o)(iA-)#H-UrjtO_6D_)c40Tq_yR~9McnW|98LEGEgt<{;irX_ zz&6xY&*Nu-8=K6Be^AD9V*ow+Vu7a9{9NR0TyE6<@2{l6I z!&}+eI+vCMbCVxRRsF4}xgIuRv+)8Vjz;8<7 z+1#g4z^4^5)R{G!9{2WYP%i5?KDM8+oE|C2aq^csg zRFKU&_6-}_ld#e+Bto-e%}H&}oE?9aGHMViLUEIF)q7bhNb?1&Oui4ZkwD18P_}a79T?SCQ#_Ad_9Z&x_Oe0`raImEsu`Zn{OduvS4i z$cju`6o~hk#S)2jX^-+~u`HaQIWar1U1A+}+}TlV3rgnEeARlAEJIQielqyhzH*nA z-ISXBtp=3?c5uh6fgDM<>(?X;4vlfXs{L z$Y}zAVn$iqKS~5^AQ=$HH8wGPqRckSC7Nym8}KX!kXuUI%}UA#P}I7kWR8Bz{kxj5 z<$xE|ZD>{(aph3VpCXcXX=Wt)8J4cm_$P9Nj+ zx>Yeh(<12&I9&XfJ{xY~o#R-=FH_eAp)3T2dPx6BNg`iQIIp){5-TQ1M(jAzu=-i< z7eLKU)a|5=(j|Zg9BCwDcv@`{I5SYVoQePQx8z5s`hOP}fVa6%Mc1W8*npiCzyyR^ zOV1+z3;RllX>)#6Zkz?w%2Vk z(KPVtuA8v&nh+b=pC;J9`*iTL#DzDgLy|J=lJ1!1Dc7E|s_S({9I)&cnRfGY;!nlt z_yA*Av>Txaw~EbyKVvj5tgm6Ta!q8&i zSdL_62L}L1!(2kJWrJp5H<9^{o+nLbTC+zxKfFx8zHLvfxf8xGkzuHFPxG~W@#u4Q zjffrRkn6C97+RtZg8`s)+FCyIePtgrK~Tf zP^GsE88^+C3AkB8bA;j& zpB>ptKP9L|JYS~N;BdpBC$qIVAOEP#5U55dvn%?S-dQqK-2vl|NHO`ggd|sYSAQ(urMN$gKkCMfbCyU<4@3SssE8RUGI@Va zt`#lHc_(tdbxig@w0ajJs$aWa8p`>~*&NKm>VmjNfyrFU1Q%G&Vi~bqKLeA#&YR0i z(#z=2p2NBA?d{R;0=c}8{aG;N)xX443CA_QcM8>8iaJP$bbp?pNDS}*A)>qW z-Yrg)%nHpG3x@lF4oG43gml&Y*!Rlsj8K*6IE4Din_<)wgYx(`Xh!OtRq84N!HnNt z#2GX9=E%Ik_g!fRh+zRzTz1wvn8CKyFIg@ZH9GtAFNQV!{>gy4vuLryX^Mb8Fh~=A zp~%hufRS-T-U0uhvSa*bz>TSvS$=Yhv~~Q&HFZZ3`t>(qH%9#e`ZADE!q)wl2}J@Q z^zVsjzG^qhqfjfvG_!mjQxty)Jp30O3BFb7QDcAcR-Qb=_jvJP!(Di_`w!ZogJl#--4*44K<7A18&9-ajNBaZXKig{V*$~T z(g#i7BDF4$7g%;^G+Gjvp!4L6H%9~!HhrKTEcAGo?F9C&;B&bu05kts$h)cdn7ph> z7pd%aLz>$5P!2O7Ovs{Kf+`*X6M5Iq2wv_O#&Eb93H0ap7|Z5cgJu_lDpj$c;+8+9 zx5I=l`!U1TeVu1y%pmZ4w1CTA_LHT1GWUV=$6_3^QM-eqdGT^B*IIR1AGZdm%odMIdV7lUP(Nu0FMj? zh&Dedyr)nk;KJy#REck9L@sO~o43*3nx-d58vLq|t$rRs_Kd6vI%0}a$u|6l$lSeK zZCY3Fv!3$w`^)PCMkl9TQOJ|*0f)_7GRZ*!09=Y@`5O;VMEXJN!sGl4LDGh+xz*0fU0g?CUN$ssEJIJ3JlwYhd zI(BrqO_CB6;~=q9Y5ANPeoi5S%g^3dLu1r=X1Q+j{u6KRQi9X1T8UQv`3yuGmLKi? zLKuJ>HPBmO2&_Uy{qy$Af!vjr+W{w*oA1h|eIz2SunAZcH5x=nuAktOjoyZEM#X+LAU zPwA+MKL%CuGCYz|54a}vOqj4UAlcI{qMFkL&;t=wKEtMzOTqxfzp;=H)cV}nK8dZN zQyd9UdSCcmu8CH}70cWCRiU~u>|CgVCx<4wLrqtU{;>rmUnNNYK1$IX&!y`Sp;0aB zYZ0p`QU0%3(SY)U%3b3)(0i?;Px%<&U}_Sk;Bn(e9qnfYi(q+jd9^_e-^5diCbu+= z40}%y$9n~Q0`Dx**!wQu&)Ly*`+9Jxw@j5^^)o9vXR+#7DyXd&4c%%#qYO?ZN!Q{wR(JA=|M%zL{ArOkjie+Ea` z;v&SVmt$>qS0B6Fribo_9@}I^NZJ+C$5xk=abJd}SQ!5(gM6iBKZKow?+7gq6ri;z z-K`%YN4Gh*zS=WwGoqUbZ~?#I&XQ<9f>l z^l}V=v}LE9&SvjeL>Y$9Te>c&EZMC_NmAgSHX2m{ zpQztK2b6d}gn%-n-{UEYem|J*XOq6p-Xj>dDt+|z{jpi|qNWLNT*Wy^5N?-vP=*}q zQKYxT*qgG1GWGqh?_$SF#{czH!J=4?E8i&SWnVbU$~mtxmU{qEyb0^fhh`YL|L(TlWDACW2$V6; z)0R!GqWzn2LqzDfVC#Vnk8Jl4yD88w+?9|Ia-zqvUV8!eAK-X$AD3sBN)5;f$bJ*W zVH(3I>tD!Zeb5+!$Nstyo5lb)vWWJZ-)-wNm_}SOkk4uyIz9m?uPYZ{ zjLmARgPgOptjEk2Ex>M^lx8Y|e`Cu2xetQjdh7ev1z&edTW7|$4K$A(ArD89gFiCe z3raD~Gzl1%U(f@ozkT67=I8Xpn^`!B#N~-$uTiH4>xPNs*}>gR6hd<2jl*w*SpZW? z3lDi$7A}56kDuzaTj_Qj+S7*ZA^(qDdoN=|KvE{QHM=>SK+!_3NmkiPRai`=j69po zUUCL9>bt&_(8uztAY+8?U(a@wBr-1*pP(VmO5{nQR*F`;ndO~q$p_?A3VpxDc3cd( zxMWnbzZ76UCN%k_g8@>ApD+m!iR^#RC+{D;dm+O_LLA-_hu8LbCzSQov3$m@Q;+EZ z%&#08hj-MMyz^9uWXA>XZe_=ZsyBkW@JAcb7LB zc-$;>asUz%#c0b@2f7J-Sg2Tv=n`ikA6?KapN|cYxfc5md@<7u+cix-m4%{w8-)R~BdLaCA3Z!U0X|&Vmm2P%PvHD%Qy8;h#yWxw8+5jBGk2DM5@({0; z>pyq;r@~=S<}A{Jnj}@U`FyVSa-vJ+Z!m9mhfzAC0&m|Oy~#+3g4RvY`tccGk(EgH z8vT*AG zAfF@F3x?>zLbZ9*Eau5=(B0f;La3gv{rU$C`4f2l5}Uty0I>QWEW=t7i}lBP=+>V6 zgdZL#l{pj)^G?C(%m5<31m?4_dXnUPR|!Y_UCq(gbJ}hkvG^7m=BlrN4QVcQz!{Ng zuC@wB?8V1eWTzq_tT!f@*q~BJJ9vF79DDtkQ^2#gm6GO{cSkM_@HS9YI6@wh=JSlU7<3Q*51$lQuYt zSo&4^d&Q8!k#Z4kW1p^$-Y!|K9tC7co^5-zlL#h2JPhs*V85- ze{%Sng(C+U19)H9)W#3b_RcmrtTLKA783|>G7;+ay%O|cHP!hnWa43=2HcphqeS@ zs4*Q|4UsXV4J-a(J7LRQGdB0asq(iwq1I933 zGdk54zv^8(9ej0qVgA?oS_wU)YGSzA&7;IbM~@vKiE|MzAM_~LClBKpIkn!t(p#RO z0FFY%m|te(HnCDxF5VPnI(%^V9R!WlV3+-jA#!z|=bdFPy|1yAH|f9r$9htL?wLUB zrNm0p$iQ<7#M&}MpbjEX8?B=A^>Y?i5@3vkP*62WS&vx-B8PGTEd731`5rCMO!jmH z82njj3e@buFpPJJQ9zmO+OCAYVoa#Q6~Q?A&=>rXsGO;_&}?J?!1;giow-T%(M86J zzcNhbR4|eCB31I|93Vml2!xeUEe*eB7N+`mGsX2k`6@zxKnLJONK8slINoOd5XRyz zH5fs<^8TmH%;LjnQzn4p-e4LO5kQK)bX`=cfM6*TRWVD7Tc_yk0k_Az_1oIi*P`(? z)E~{m!wiIF#Mv~xm$DMMEC70>!I`x?6_ik~W_m=+CB#Vh9-!5`M=S2H^6v+mr1vj6 zctHd#(Ad!iMs+oDtS2z7FzdDaD#1hvxV=k~0Vcf5YK2$NX?;mp2V`r=ks+8s6CDFk z0HUQSvFN!k-JmY#4q!;pGS#mfURE{jKVj~BU>BJ4f3m}QQj|fZZ|I{mK z27!H@{acWfFtd68M-8 zoBub{O%54A^-6p4t)!?VQvIiA9TaB>*@6I;fD#0{c?fswItsuU$uWW`!|v?={_hhU z0|LsLyNLPK7$Uzhax(H5tw>G{t)yyL8ELxahD{GYNt1)h%Opy(s?o36J=28B8%o~A z)t}Lp<2m;JiAQ z;~tc9lHUn=LH`*VsyK>W?J{ZakC9WVm|29v7qDtImgm~wFe#<{ZM13Fhe*`-)hVy< zEFhBCA830oAHO-BU)OK{JE_FonX&joiPcw*7TP4~0(LOSFf+dpp%;E066}A)-u}SM z()Ts(Q^~r%==l$O{-8cJ0>W%`V@{rrxWOw6^IAo%LL%e=>FRhEpic}CkD3b{vq}3A zcIu-+kBdE7WICRELblPYv+8-#Zfs=_|2Q#o@$6mj`kghF z*HU@Iko+l|isby;rmF9TQypl$MOTT$81toc0&E~8&rG$9ju0le z#EbE`i6#FGf#*Q6F(V=oH1qU-iD3)oMMnn4g9zN54+*!}0^!6^)|jfu3p;6|`;L^; z8Ol=j$w9V4A_cN!=6hTq>!m0GWB`o)-hcyi=>qfa$R=S%qf@CXqXdUt8jXmS@s9vv zNQK|_=*|@#!qUfuN=_NYaVr*Zy$Y>m*N1xO<^4l=ANlXs#N^y>Zt(C!^L1%{tAFRn zLtQMNsKBKt3l4Y-A~%Go$8sDmn?_F1APfp`_&$|Djgz=k^d5Rl9NK@82OB?7dIWS= zAw$YPU&wvIqdlb9t!c9}A2nGpjinO@Q-mKz`fm#&hqfuIDH%M5;`|;np()ShljOnr zzR2SEf_`@BQ}+iyi5=SX?;q}~&x8u2wz8ok2Er1abx1u2(+dasetGY7z*r2ATknwe z>uc+Va{P>@fErjZZ{=(C#(!=9rXZv6STC}KZlXPe^w9-=BX1(;nk6*bK?cy3}*J|KV-Sm7dUybME^u58u zF%mJGsqcq@{IYM4ozw7-q{nEA$Lj+T1m9R33<88kzh`s~(S7mF)6mcL!u$2y4ug*! z+y5-q>Bse2iG~BphwS_g95z_h!GL*8ynLz`MNB&kk}zAmO6YRRQi3TAJ0vzh&O_pWJs<32R3k08h`H7x-XY3d`Kd#%wLpE@WV=5!~ShJznw3 zcYoXfOnR>~JNa{D zWcx1R@0I@EIeF>*@R4KI$Y;1!p{ZPZw(-@s@$1(FFJAqp=?g3jB1(X2yaMk2Fg;RX z+TmC&Wtc4q@K97N10Yg-N}=ixyi=2}CjrU`t7erF;dKf6ZlU2tKDG3x4zUr#8Z@TzegMUJpAe zfqRha@zyKtJx!Ptox8{RC|EKMIoT+U(tlvqKGxNCZ~ zpVFy2y4q>5`aFd(rEipjoVmFymY`4*%OM~XN^>%NS7pi)Bw~Ft60iVvT}}yEK1?`F z>6`Bz#!Y4oObXbbGBN4jZFcD=Hh{=Eogj!97PSi6LiW^Y?ojs|ZTSj%iKNb*_fCXI9Pxz$>RJ!WPh`)T_rVpt-Z)t#f8p>ZB_Hzi&b4nyGr_0TueYx;8n z2>qk2*edtK20d&0rT5ig9!6xp*4P+)y9`W)E64lH(GK%6JuLM)9T60(r^owhmrRff z{)<^xlY5{ihVmyjC_`LY=s@9;`t8@N$8$aS4@Oe*Vtp1y5nsT$XEZJ+KjX=+9>9^Fq1&R}jC;&A4bq2cbV zY<>_0m>TiDmkHHEhJc{bt?Dm<{TXaoemCXY2hi6~ z&pRAMwCp1$e`}FHRR%8r3Vq%GcpcqjV|X{e)IM>NzyNiRrx8q>_Qqqg8Qh~g<+SLNHw%UVSCD~dJ1M(?|EvT>U zL%v)=EG(?jVGNN)wA@i_MGtc?oJ^%hmjPdXE1te?)?V#+IB(oiyZAzci@Hwpj?T`6 z96ddztsJ5R)`^e}0`~QXwd{_6dp>rru=(CEU;>ov*#HQW`}5iyqG6R7g_E&W*91B8 zsD=p&=7bR)E%%)?1UTwN<6GGi z#LsJxg)!~M;qiW+y8M&<D_XNe);Sdk{LJKsWAsNKiJ?I-4uTW zzrbBD5UZ>FE7IC0y}nN`7D3tK>2BXPGg};iXs=ufNUsExo0N)2!Lg_|IR*z>!TvO( zuePMZpcux>RNKmx@Yon-{}t?6y8_~tEB(9JH^aU!E8ii&p@^ODkFrQQ|-L#6;(eBcTm=2FhQ@+G~rffHT zi0Z%>Y?M&$gTsXP1!y@5m96+)fVZ>R4}n7)PB-dLE;@$qsM$8QdxV%SH7oR*%*-vM z3}=R5I7`r4in0xV?Za2|{8|lAyd@eQnEsyv5nr1hU&*f`#O5~kprKQfI&ValpjrY+ zd6OGt2@&&ujr{-}Gd2L>XFM2qQB%yej}Hduj_eI;6Ak;WgLallj+Y6Bm)?aDx12;{ z39S#}*mhQLs4xfZ+%Bx+YkNxyno{|R);L%x)e$ISQKwu;aWTenmnW$Y!D|#Qt!jKE z`N=)Hd>A`bW@%V!`cX^HZy{D>#;rzW=diTGMW{vGp%1S$+F5-bK52(Z!J>-P4wVyT zf3e5q>Y0L*jX(s4`CJ1%snMjbQH%9p}_0Ut$z4 zs`a-ICs7g#&w1(*X6&kov#QoPybUeRXgb?=iISLDuDYQza}l_|ZhPd_eyQqUz!cU!E; z5>Amcq~JGi?K>TdcW{`8pn z@08GGzwyFlj`2kF_e`sH7aJMy~*6w2LMZnp` zMQ^D-(+c{~b%r>BQOn^!n|Go`#JnCo>)vSW*Qq~tfl6u8a7mll<`h=h@I6gujPSGN zUxLgQ0GeSh(WSm$3h0G8f6&{kNA-YEhr^Bx~)qFS{=L*T4?Bd(*&m{74$o|6$+~ZBF@}~CLVBvTHt<>in;L ziQZaFN@F zCVqTsPgs&ph=pZEdlb5C@Oh2mJ#{P)s@El4`-O^{P?4{M;g^>BqVbzVfi+BS-y#2f zh;~|6?o33XmL{J0bkm0xUrmyDQEwDa9hrQq=;rb+m`XigRf{$TA- zlb+zJxT%3_De3l`Id-$Ha&-|e=z-B!ci7(?%M`B(8IyQ0cv(V5r7OggU^X^kBor9J zr24+UAnb$~J|!HXy5s_3YK-BUn4OuKeol4tBAp-nu*+e{(H5+_NU2T7pSz_NH4Wg} z_L0)JpK?w@e5}E6JAbnkVR+=z8O{>31VZymsPZgK zMkBcx38&WwF}0b`*E{bf`mdh`ZZ8OzKaX_Ei|*`Uz*mH{>w9 z&bUSfv4;OqA)Qf!aMS1*KVBY$&ilDooT8wdNwvHlYMJM5DnXV0)0|CZRbQX>gCWqJ4OQhD({*c(CrD zG2&N19TavQ=`f3q)_x9sJI%q@GB$UN?u-i?hAhrIOO=tSf?ma#n&-uLPu912hd`T+ zkEZwPtwBWMqo2sp=n$?%4oEk3!Z9`2EGP5H{aejIh!n{xaB2W|uP&*doJO%iL~% zIq*MMxDmv;Yc+lK9E^LHm2YTuguJkClRR=&ncDU6f5@@31nzZ}n09o`(pelCD8Gv` zGZW9wT;iagPe?o;hpLi(!W0?wo3=g|3AT~_{r$&fVbw_y-N zeti!G`^Qm?7P58p5iE2LEF#?_*rE`T)ZtUB8_tOTemQis(HUdm6N|(Lp(hFhY)+UK zD%)@S{rvW~<^Ad|FXws5*PpI@I=&&R4l@wzjFpldxZwcGW*%)ipLH4u0;+}8T+}Sn zDJqGEs~-WUQzGe2bLk|L4AmWY#(N~W;-aH(E~w?zBvoSgiQYH(G`W2AQ)3(Ca~$Ca zX{DJ*v(&w(3Rc#V+h@;4NZQ1`grN;74hVOChN2G=0H5xX-xR{QF!N~_`WkXusRA5G zCsySP^*4?2%`ga~hHhrUh)wAwZiBMs__WS&NdyT`L+8>+=kW0xQWAhY9WHB{lbH^s zu|H?)J1KPpBQ~8g@1bse-oGSvo@^E-8L@+E^yXgs6V7t-e8B^j*j4wL*2|@kt&E`s z36K6T|Ft`xkCmC@3AiVld)gXlc^*^ohPy#Jy@+w?cLeYv$|31=FOUw<>}7fr{5SQoRNd> zHpKGkHmTcOqAtuY8J~;01MCT)VZO``%1quGUC%g0Jz(#Bl{+{Y zCm@K!QDw3Ri{fnc&zll#u#3&o%YA_`;er7K*PEX<>ks(#Y}1)>ktBfJUM9> z2c30YsI}vDpns(Edcg1Pz|SKq8=w~nlK?t^k)n?Ii%VlCvgRv z4N&N#S*J!AyTy@x$}!_f_am|Frl*2Qm9cF{bt%cvNr$SzG9|9i)hlTW&Qeet8kv{R ztBi+bg=~pRy78Tqv)DUCL}~rRorgVf(2tiB?{b0h3(gL*s}4$+QS7BKJEY*6djxSl&-pyd4Fo*>wX3{gpzobpP<`&bHDYKF9ZR!^^a!>YjY z$k5Rk+p!vdP_5`XRZ`HSaYK^?RT@Xcd~x_I;x zuUh2oU8X$CwmI3aCV}!a2}7sqdbxxhzk6ksAfC{&Qc_Fa209#4&{dFr;&;BSKN2comIc|YMq3wuZe52S%a6XyG&lyE zwZYrwD5nqLDufc0Rxo;MXaA-@jf2X-uBZcu(=BP%NyG#Ta>e9%b&+aOnZFX4hfu>3 z(AZ2;YFICvlFW2;kvmO0Aw}VKEFihFlF@`snU-w!TlrcmS%z~&$%?7bnUt>-Q>NvGAVQAr3IVj1<0@^>mJ^BxgVJUUcd zJ)6oRJ>D1&bP$(0xwCsI2+BUwuCU{wpFwv!XSDM${X45qYEj^TTV>F@jD2&gBt~!Q zPLsJmTbOC0VQ8yd`e=-0-;b+NM9+?nR`BtP!@l+U*hdSwH;%ceadzDdW?S<-vg=bC z-wwlhYpj1Vnx&=^*~$%IWQ+h&-5TARswf zh-~_A0Ui7B3I|ze#t&7^c(P1OERiEG^!MpkcdU82fdbqJ`=7+xfcz59MM@YvJ0!t! zZ(;_#fh!(3fv!~4+7(ns{qG~jIIa`cLvTICsu+-6NiNAJX!oP6)92v|E zuq)f1YJ#{*06W}o3=nHt7AoveLz0p=puxK41<6e*mxY5a@NjL2-pqnk52c8haB_-H zE&!1z;&S?iEf_YXPbeHoQlDvHbv+cY?*dp$XJd9dL z3|i8x6HCg9uf>COPGUr1xNn) zt2?c3oGj6#6r0bBP+fmanRjfLjO8vo`Y}2f_hv_dJb03TNi$U+b?*Hxtt=J%t%)o=|1~yl!DG#Q-C9)%(UJ zI0C}Feqka0VnFv=ckz;$9QgF^iyuXeRZ`_u#|y49ll>^KmQG)eO8IWOpA$3=m(e-V zhGup%yfZyRGuFS+f1XLNfkVVT@329~{E>PA3V{;r7RA0tfl-_8z`b9><^9Xjywf zTpMO}+PThj{RqbgQ{y*tRJW~?!0zVPI2x^q@>HQ~R50ZSUnQsj08zkyF}gxe1E&gs zw@ayp5nC?IEAo`KQ&p4sW;L}Q^=uPHM*-bo{7pZ^ zvd<$!!xi>MZb=JVr+K)mA;c}v3w;q(HMJD z=+=l%k3;BIvT|@6GC6?2fQMiD`ySZA-wG_`>?Q zLv$5q3v^Fmv1nJh&s6qBp^e@Ye>ak2%l#xxgCfeQzL|wdUDzn`;QUoG6*w%AwCO96 z(^v8v#6bw7PT#Y6vU|EjzthNx=g{@L<2s#k%(C=Y_ z=(r6US9w92vT?*6NfG12Uo!ttQ`QHPc=`9YDwJQ#?kfx{`lZop6kP6i^pRZhD+)H% z5izuE_;LmmfnuQY3B5_3o~$4ZBC6I9iRq%9xwurDO<4%|mt4lklh=Ev;(Lt6;X`L` zqI2-u!57a+nawFAkMtRJyra6|%j=upvvS$iz_B=Cdm-(2Cf;MXUT5W4gLlsgvGIQG zno}88b^QA#I5zQUCd&OqtLv>W@{p<$mHZrhbg$IGFehL67&|2c*I1Tk=^MZ1x-XP+ zn)O&k+V;9YjzD*BHsGJTb7AGWj>U;DO3Ig7bOe5{9=R);68tf(q_D78dXMb>3k?g9 zge!y_1ZiBUOe{=?Tj_9mPeiHl(goouG7VTjKu+4C91PxT#7^>XlE+fMMA(1YxpYaY zicV34{-;gBxx>qe za6rY>a`t~Xhm-TpL+|G`I7^jOnQAM+AOn! z=G~^01mjH}_TKhsE~FHB55#|XJ>D(SWv46FUz-*+b%G7AFt+QeX#>pXl;4+E zAuR79ji6<#iNl=a?6*~~Fxm8sxjh7;zZ(E2D<5P2Hb9Oo z0mJgCaqPadx)M`->G74Ni5>t(9k`H==qS1nU(xAo^88cT|4|nPI@^!|{ zj_&w*$@g?JxKoMA-&lpoP4VRxQeCe>rJ+%s)?n+JH1uS6W^-d%7 z^Z{}Gs($FG)WCv}LI)@~@NZN6uVOgm6l@D*$*AjTw|IJN0b=%>K%V=|JbuAX|3$DK zNxUy(pJHVCJppc1t1+)IzcQOhrqj||G#DoUBBCWF<0*OTVqdbm0ehs}} z9<>~b=*k>8YOrH2zE+6miF{g^y7ns2#UHs@wMz5_o%*_0?!dwcZPn`CC^v_E2AxiE zCLXWOxudGXeP6gN68df}UAcVb%7*kXz9Yy})@XLHTcXZj?_7F1@>=DLy5$wtCHhpK zQ7L`H9bCKI>G6G)_;pu+^mvM6JJv5I&!2FC|LJsI86w{y^pEOM=HYWCCpt`H4lJv( zlG4nG3v=r$N-~^Zq}*1>>M5M#@L$DVv=Wb?ZXM=>gZ z+*~e>Lejr0lE;~(u4`?bcf8r$mQXe|@#r=xDxEwtc2t%@ZHzOy1~mP%Nf2{Cuf(BZ z#E68{bX>W0+ykvUvO^Tsq;-ZX#p#s`(oCdD+jD+jq$Vn6_^}Wns_8pCc|dgu!@VF> zjNL{`uFK-$IUb+=0{wxw(C@kL&>Y29boUP2THu!M7*O6PzAcmOT4Xp_)~r;+|5id# zEAa=f?n`IjHR(0Cr{kn_#`pc&1kbEy^D-=>{c`%9ZR>mRfzhtnimMQ@hzsk!W38cy zPYOO6nAb3*AsfwZGH!JIX}CCWGto`v4$Nj06&0yJ>R9wFO+{hCz(mw5YpwO8X2xK{ zxxbeP^XRdddU9oBy?oVBKUo87au#!cD>d%&M#ID9S1LUZ2~Tu-hez$ZznNHpqq&vL ztDM6BRPz8!>fVjDGB5d_e6R7Uapy=?KcDBQEd59oX_HeV=Z}G$R%0eg?wXll&Zz}v zz%P)3bj`*uic@Eigc-tt+HPg(&soB! z=0VB!b5gC~@E|~4Hhb(whwl+w11v4Qip-g-aF9;m)<$0XfgA{5JSg!j>%gkqLi-?1 zu@;9sc(|qRWM@q-Fo&Ux+UE0~Z@F>+3#^<}rL5AKc)3^1m=4||E{9#PGeg^8p0e?! zQAH4Y<@Jbqz|akd(er6;`(984m9h(%ZdmWK6XGRezD07bp?ts%)khaije}W2G;*OL z7-;}v)KUIn`KQM)tdAV1&52OyMOV)>dMoKDA0-2${1oqOpXyQoL^sL|Bx|>RIxKCbll2kGx^@PUuMlD z51bz;JCQbZ1Ke4lo8hX$tSDm)NC;cpLwac|_6!K{+9q$U?7ynr>6~O236GQmochpw z;qSHRlf)4L14*taUPltd#LO``z2x6<=x7x5(Jaqx{%59Nl@~)36HrA z4cI1`=CD830b~yHKbQ8Od0qGt>)(-GtZ&m@{0bWyAp!y@PI)7MFp9Vm>-PVDt=RFO zIPtH=P<*tg63JMPt12kF?a@i!C_JGp`k?QAMv&$I$JJX$#nA-+-pjH$1P>5AxVy_D z2`&jFXmEFTSzLm%xD$fA2iF7$?ry=|9hSS#@7{avd*1){bkCXDndzyn?yCBJ{cBMt(Q=_;qY|MD_ zA)-L3IOMJQA_?(O{oa;^NFy`kr#KGU{(zKIYK-C6tNH*Mc!!>|?3Jk%PDYg+ohGCO zK7E>bYF!!+?*3@>=VsN};nk2}hAG{7(H8v|mTF3@70w4M;|y4tFtrYD zSD@DwFdm-9f2ILR4^IhRAk85s*NiO4yD{z>3Ka6)#H<;WZg|-_-T8sU({;w(Xa+pj z3ePIRQ~vwXhxZY9CAjmmxNB(ChrpA5T%{iN67w&Z(l=MU8zG5ep$Z_EMKf^U^rX7* z28;e}Lkqq~>-y?h2KL>xb$FhUF{b1pQ0Qg6zLDM)lpZvp3-X@+{eA9X~fWkkW}qwtmNoB{yO8VRs`h zEa1p#dz6{N+`s4a;*Q62(nQ>7QvGpT|YtuEq&TxL{sEe{p={;z$-G_T8uQQ zPPd7Xseb>Ci?f?NujXKBecG_}2GN#iBT7KAhVBi5cw?s&A5?ytU zmERdtXs9aBO!ru1}Etji&hO5pM{G6thD)l=Ac|`+Z#y-Oy+_z}Dikq!un74xn%Awdd*M`tMYl zf7uyxmqDF~$7qnSm^_@ieuhw|(Jpc;XlDcrPR>L;fnh14-+uq@c!=5z;7@A!O7FK;n!tv6wV6=m6oWaU?!TP^kJlUM2*Xlg%;R9 zc2o~X*%fsUHSy6k!ipopUD3?7SEBubi5xfrh+; z#iDpUzmy1?3FmIQ&KC{d1X|wqAV#3v_bUEdoC?xa?n|>Yu#DVhOt|~@KKFt(dDkFa zK}VMIe%&9(qHT8FVHF^|o14!2Niw<=ci~OV~)5gbi+4Sl?*j(-;mg zAm3)y#?LEq;eO0++TveqE#6ymi| zdSo3|7pR5{meo8y5<#8{&t7(Np9yykiHm@`e=5YUGsvvN-0%TSe6eCF#Va;mFs@7; zoNMXQYO%d6-U!~jCj0`sbn=a`>`K$-4-K^gcm8j)+0~(*Es~KD@q_Zh9u#GLKTG-5 z^UJL?^7ICjK*k#~`Y0tUk)7ymMux34+7*Km=OinJR6ruMvSp93b&2?;8U0W|<9YuA z_i-yJLO=CS_5+@kY8LeO^!`!tMRzfWyHk9rWbYXN_@N~pOtUAX7cGD=Eax$_DB4y0 zk~^E{d%3XRZ$1$y2)JAQXvZH(J#V#A;q!yj=YZXvDX2RRg*IVZF{?KfJW4~RBx}TS<4(EdXmd-S>{HZVxb1ZOi9Lwzh18d#LT(9OGqX#Xv=U+H=GcIc;Aq7I z?Z-Bk7eyfbRlraN#Q8^s|I78BNUCBb3BHOOW)KVBX?AfC&eV)Qi9@G>GPi~wFd74n zZV5X<2_x?XnVTB11X8dH7n_VI9!0s_;0if~!>UtSp1gwg7W#STvX26rtEvWv?A5z< zdz`qH<8Iz&T|QNs%wPn5qEFodr)j76LZtCsQ3aPT1iCcK3taoqouX@rlDFD-baJg( za=t(X-`yK@p0%7bz5d3%N|ed`Q&e`%U{5}kJ2P(n_D#h}2rIsW|3LcwqKl_=2eC=HXE)J^RY@Xk@8k7v#0?%z*Oy8?ZhlAW)_GDjWeTF*5D% zPedG!Geu8w==+c&E9LKA-@6?=6e0}=K2d?46MfPqnG~t>!{1`oPiJ9}85eh)7ESOM zBPJ>Z&=bkzG<|WXr&~aOQ=q;VWVDNF@im?n%Mq?Ph=?mj&#cp}cK;EWGO0J@AiaC$ zea>Gi@9+=<)IIHbc1xER23^gfRr>1O#)WT-?unY_g#CEPC+v%_0rY>5XNypGEzc0< z!Z4rYpEVwm7zJCsMhX#+OKkkI(dGWEixgOb9kitM-g(xYQE(rW>Z|I_89;tWDKxEG z^h1 zPO2**#22mX{m{$v0$w zZ+McLxImOH2gM2u_b#VUz-PX|uestQD&r7}Vf{6NVaxT64Ts;wdTz`86eXKz`^{*$ zhyQV&hW9K%67%|43BSevC#ss~FV}64RH4b~A_jV$0LIgHDfu2;?q3xg-;cqjxHl-4 z7jcz7%po>2QWOydZ@BtHtBq@l(2I4`kfl%uIkI|9P=RR@ZUM}K?jteNImG+jjL0mQ z!0&UD3P_CVAx|C7B-5{36Dw_KA@g|RjZzisK0mWBdL~QE&z{TP9o=q|4k3K{lfN-p z6{Qh&iw?-mY&ah*nbW|B_Wc=My{>%RpzlPL-@tjZTb#ZS6K&p01{Muc*TZl}Gq=|< z~)2LGZ#Z*H4UhKEd zX+}E5%#J#;R|dvb#YRmSlb6}B}I6Kg~IP~PxvCLwLc zB9KO3-y;Dczh6}wN&>z~;S4uTeN___9U!s7*!dNDwT2=8%a0+w>u+GKUEWjA1p}Q! z>W?8)QvyAI8F#X zw^P(U>;Le2;QRkYd{6DJX;SEOgf)<)z+2clnpa%$oL z3B4(OIpE1Y`C65Zf_VIS?=(pIX-mwHK<50&kx)VwIF?~R99c|#$w|xGeu~x+bOW#*Cp1V`GjmjNH%U?@9LdK!G z#bUR5$T~%xDu9a6*UHOMYYp-J{<)K-91v`Y3Fa^_Q1mfSU3MxEnsg8GrhkdM*g*Uf zdOnl;K7{6sTru&DHV#CvR(wi!Ab*)K%3=zsS~08tnqbOm@O41#cQNBnc#RdoSA>m~ z)*>NLK#b(>1*cmW(Mh}thjrQ_LQQLDXN;CALb%1_2h$0BO?L}6;<`ZE?DW48A0TTb zIIg&2#JYRK4DQM4T|O}m0UL^G#j_=9!|qinC!d1JWdOwFlmlLg9e}cG9?O}*5&WeU z1%;{G(xZRmT3*FqgZKz@^=3qtG>Wq`r0R(##t-?d5hY9hkrYhhrW$W#C9*t`*%-QT zcAC;1=9bh$*|awUTJ9Xr_xoG+^-!DoyY*Lv5-?7*^gV-aYf#&Zf`z^uQL*;Fp3;3v zU>HxtF^>F7O`EJmNFjX0amZzUy&PY1sxy!cPkDf@4lCy@*M4b`%@(=(X&&Hyd7S+3 zuObH_M%*ezGrDT1ynE=kNm)-HwWs-UL-O3Dh_{i*LY$Tcg)lWL9_Jl&!N+IaT)I!Q zaX&aPISIF1d{UA{R0L`Ys8b?`>Qld$b996h9CQqp+ZE=+GGG@+oQBCS#78qiryP^w zw;3Kpa!;z?ESuN$c^=JmLKFY&!konpnt@HpPH&ZRj%FI0%9WY~bkYS&-U9r@lzOSB zgDy`@geT6vOfWHp#($7iKmeF={*JgPNyL{r5MplXS2dnE7<5+RTT|tN8%?ceoo*}@ z)w({ERgGj?M67MpSX7z=P6W<069BDLk`Ahrmu|a)fXZ=ouzkE$%I7)5*C>Z)4@*Am z?;fyQ2Tmh#=B8_{YRt7Ug-bfSv;ac`cq#b#TD3S4g=CFW$oHQ7A|*TxbmdvcuCe7O ze~IA0U9yvvt+?JT5t}vtHqEIZE*X)cvDN5VnomaJ@{R(K(HpMj5?a+Z5mfLve!&N| z1MB)}VmvBxSSy8-GCPhJ8-P35)Vu1|t*mi}g_ih66&bvd*vh^u!9-2apQy08$&uL6 zKTRGIf30x#s0%JYSHVe@{9`EwMa+9hT|C>`DDOEA)C;rnZ9-gO)hv%^;eDI4a_~$q zt^^55n&w6iu}|>5iV?WDlaM>i(Zoj1X|G0f*CWUEEOVii3NT8B287=ES$Uz}2U|qh zcF)A|94Y3$ii+s?qtYmH-a1GFD*r~O%VK2*|a!JX|6!o(1MZu3j?G^6T7>B#zITq8K;aBSr(m=g~hlIaJ zEI8kCBL$<%B%R6qHG0>1$flc%BJIDU6X1{GnQ3`24{}bEDG|m&p`@sTfQ+{v2k)W&<=}w z+z4Y@aiRL9 z!d^Zf;1_c~fBeKuEEV+afzFTRNh*Thtj)IIVi~K1SJ*wa{U| z3Av(Ah36L&k@a?+s^bhr<8LiE4A_gJy%bfMqEP3Yw2lpSKhp%?3=g4V8sudAOYR<$L^7dVTh2?wi1*1Rj$F|_0poyX{)S#dZxSTRmy?QzxKjL_}+fh2)-x9+}+ zH-9lW-%p|BGi+6GyO&6u9wHejoIi#ShcB7Srt2m@Aln(EOCyU5Za;g8fX98oKe~|u z=Mobhn_p7MkH17vLnx&-w5JpT^6ZwtXTJ)ocAZHiOuv{L=d__y8&AKv5x_{7x9tty zwYFT{bmG#<68wo^qFyYgJm<Y_SICVI~8tA?0IjD;)D;6R{|S$1jZmDuN5w?UjfT>!}vY~mABY0 z35OYRJ02~@!yC+zb|0m_tf$C*?KvfJjTL0DtgKqVkpdKaQa)Gz__340>6 zA2K#{BqO=N$;fgk=|FvnHFQ4G8;LA8iVaWKFd*oBww3O803ahFxGAF>d23>~DSfK< zl{K*R_CNwG$z2<@S!|hX%H)V^D!SELh82#FD##9+Ju~~--B?s=a+Z4NLc zZS)8FAde@ce+OI7cuzCzGV|cL4_#>Ng2(jv4C{vJTs z6V-jPkV3>V9eAFC3>wWlo;nQzPj$P`l~6?()>2e0k>I&t3r|bl)UAYMo%-C?M zrk!rNeaeD-KAc2C2~ILu<|A~YTn5%-?M=thuO}US<3eL;fJxM!Y3`t0JGYUWKT04 zqSTN}H)d0`>6$=G)&e3#eB`1CsfAU03j6n>Asbr%K6{J2tTw{h@$4M9<_}Y!go` z3}n3PQZn^Ipx)>~p^jI{>6q-js7Tfc$AZjoL6#>am!9jBTr5LweF~s^o%}+6H%F|9Ml6P-`3l=7 zgn)l4+BwS(zts~1@G3)yM)*_DguZ#E zvY^N@)DBTn-g2c^WV5*Wy*&3}li1e(rY%wv*!nCoHnMi&PX{ZJpR1{mTMQFC4{xFm z44CLvRHa(*Fs7K%y25o%5g(B^lE4U3M@Q3O;uv=HU*xO~(^!p^-}QADK@R+>BJ5s@ z#_YRITN@`1m_zvoG47V*Htalg!;kMP^d)^{ z-Xm~JCrEaNp|36*>66j|0`50i7Ufc`+~b$ED1>8;S9dg34|7suIuuORtkSWLv*MMR zpfxj;U1;RPASMChbs;KTsWy>N0Q<*yKh0gc!_K~SFrpK1ke9YfBPdnJ+a92H5DQ%l-OV;yS(Yz zK!4?W{jENa)R%ZmJe`qY>|4+0!(h`j=kxC~&p;=^n81Wc<&CglDy;Y>&UpLZ&9YNJ z+>5N8H*NoT8+R0GroVAqM?|cNR?&+VdL&?f>*6UCfIl)sld((5hR{IyCl9bW)sq}b z!pqxqZMH>9{_-9>t8s>+upIB!jhBQ6_k$B{+*ke)qbG~M?~y_DYyAxcP``r!rk>eS z&}Nl}2HjtDQWjKZ>l^NdYfO6CT;>ql4~`TJ*kt=k3^m8LBqx4pUOU;=rW88zSsI^B z$jx=+c8x4XUah>_Bmr9PzY^a=>4KnSTY~J%-7D6cP zB&3%U$f1DHdRpH-rJSa3Ml8~^+g*4eU+x_^Q%{9qd{4;jWD~MTTK#Y0sneD?I+k*e zpmC@OTJhITXb>bv=NNI{_VX>{wDS%VXzD(;v1YtQowez3*e_`ccrO3E*`SJg1!#*% z*7eiZ!B2y3Eg3#SAq6(q@LUF1+;@L;@$w_B=}hX0<7GtuJt6on7F&KiC)`Ldm$3kfS~67GT|=Ly`6XBb-SuE` zr7t(QpDW2kTxb-%Z*9^Vc%s`cIl`enQAdl(30g`t_xCp)_(^y3QOOhaO+Oq$)E+kE z_Hf#USq0k;1dF0MnTR!JU?Gle`!?;mDZdDVEAjKBi^}Hfms<{d%CGb=q%hOXohH>4QDmVF=qYb9GhUc1mC-UZQGr~Di+a* z!0mZke}aU1omNjS^K`SrRUEX$x?4jL`+5xWxCT1f+ZT*asRG(S zc`cAO2M*-3X{Oz~|2#-AnYJ@2k@1OEKnaB95oVHGSqKyn9oBvBmNy1{L>s2J_1^e< zX~WYmLtf}NSteE#h16l4>EV>^FaIncXTKOS#&DwyB$i%#dl_eu_47@8ft-b(ct5S47r$6#$pgbn;!=KQACiG^h?#6{ z2V%32*1yiE>)A8mEI}$a)9bw+oe@*mkg2J$xtJYg!`o4x2|c$3AxpJ6j^DD#&2?>% zrrnlsfqqOgM=f2v7#~Pn{&}kec1>2t(;nv65MQ7hif6n)yW`J&eIVQ4ZXvuMoHk5L zPc*s+G;~-RLd3;b%Y-OSy^8GAYD*{`6@w&nyN9nOw2+iptxStjxt9{{ zZEmtBjY~f=gaD8Lj&<{{TE3noke`#Z2SU%?8y^twqZ!HGSGJH(QRXzM>k zwpf7SKGDKYoNlKw7u)RbszN|#=C`=J1VBk>P)&axz=~wlX_N)IOKucr=z}Gl68gD$ zme$f_k)giawB)wgzmbo^fN-i0x@k2~SN*2IGElzzxf{MM@kYQq=fkwRDLg}^TFd3& z5r@+h?jFdTc=CD4$2T#Pnr+=RdN~ar#xI5@c)jNm+)wz}YE3Bmn~0F}>2629rJWXx z%^{?D^a_~$GFcQ!^4M6OL|#tVxsqm1^@U0p#BPbo0K?xn*rHU`H8@216HBF$hOAg> z#EQ|2a?eTc-^xu-r4bC6nJS%~XGhCZPdNVV?XejDUfx2<+J_o|9?6U)rjCK)BQDmG zNk;~xmdDV(Kzj(tl9xKM$n*s-4gTmo?Qy4Bj2qBMBaxu@0 zy-ULi$6YqnSxoE_QIlYzGL@Gy{}>@fnWSsDo8kfrkI4RTBeO||D>qtl52ZO&jQ_uA zY?`9G%9lu8sZ<gQ2b+`G1Dg3JMj#G4=>1Qn z295~1sWc}u1gAu4MdFGN8ef%k4@*7&d6i`2<$kzs$HcNCLN+IV!-JF%-9ua77dh&V z8F(Ey{G~2>el!dD_tY33?4a!frr%Vyul+i?!4LBdG~GhOkoHq9Os@c61%xhD_IJn)&B%N3WYYGJwIF*eXX|O^(P0t z4ZmbRA9R(_$x4Sb$m$RWsR=8bz+pX}NF#2KNxSmLf|peqIE3u$)BhUV_oqH9!K`1k z>`h@r4(;;MiK#fi1q(^V?$1Thq;T~zjNPO&;r#y9%#9lk*zJEpdf*3^vW-Nf^e)9X zcgn+d$0@7Vq>B*izZApjr;Vbo_bQ&w;b>pM0J;l9HLFj^YUH2EKPvHU67IU0WP|0( zkl&7A{@Gvg0FQk@4Ym3Xu<0QfKG?Oe4`x+piZ-tR7`i)KfA+=#0{1+4uq~Byv&2DK zvFN}AG6K=9aVHFf(r4ay=^#~ssr6}@tTH!;F;Y}lNeM*-t-#h(59ByYu&WY(DKHTI zYCYkC#j(+n|C)gQouYMqAtB=CXwnS7Ml^P|!4 zFje%ovMG350#7yi635xE^G098&AkZP4>MIyJAKL~f)7&Q!g|}aC}zRIL{**sR{wfO z*R#@EubJlmsi^XJR=#2^x@VP-n}3+^~(G zR8;k;r+*-?`!xA*3Fc>$R(@ZG-hL9;m#};H(-ke0$O{%g1tGkUwGUwqYml4GP1w-( z5Ga~=SmCo0qk&tm6!-iE){7#60iZ5-Lm#j(%GP^lWq9#FVIQx>(g40Sheg-Xe3eo- z?9Di$LsV>>o3T(d{bvy+2}E$fG5hvzcQykR!0osP>nlk9JKV3=$?XP{vDLwQCuWBx zOH_=&=@7ZH`3nybITidz^&JH{4gKOHvuT3|rNY4PJ1b0z3lpO${Ca5gs1<@FS*Y);PW;1*>Ux zh1mE(&(p^oMS@mkQYNf~SSJ+}#E3)ue*VOAbxZ@qutug1@T6+{j5Sq_XV)Kuy<5Hh z>Q0@Po0+MAtdRSj!hugpLQlB0R2D8wM-3_)0gt@ka5lc`XFxb#8+4@P?CR>|mWUJ* z%&#Nd_gBCplsYYqV`7%Y4P&$gSgJnlC#w9VzC)7_1>E#(MQq%@M?}Mg!MpWNpYZ@= zpAG&64yZ{K1Fh5RrnFQ4G68^{L$)`c=OB3FsN&l9Ar&Z=?GEbgq#Y9{zX z(^=%(4}6oA*@276IXqhyq0+*t1FWF_1j$4cy|=(b$`2kxqi+9Q5)1T(2$&zxAJX+* zPEE#i_?je?rwjqZlWYySa@Jt1U0fpABjm4`}jmX+Z7D#s;{vJ_jFj z_^P4M_pQ3c?}Xh5io4@ITZ&1P&;;42ow~eD^jJWH*H@}663fi-$l3>neQo4HfO&M# z{~_vi=S%0A-cF_QZ*f?~iz|J_pyw@T1JUZxSEB1!{&L(MD)f^?TLoir zglIZOc_+%{Gi=kU!JNqSo^x7=%mEH6|3RZX%~4&gU+6}e()~GMiR^Sm>zV%AXjWMb zDj7r#a4&Hl-%B2TX)lHAbe2`q8VA%F<>|RAX@g&?{j~8Nc@5=O!!b%@27Z-rk+Sf9 zL}w4dADX0z1P>Fa!NItD5(nv2vV>UcSH_6Dv79ZXDdQYUmcEt^-3`68fumiWrR7xE zteyXdGA1Ypnz?;Qh53A0+=!J$3J`Sner(=->ib^0MY-8MxsP>E6XSBU^2`Kp#_M2x~pO0)H4Dx5lbQ56J0 zbbuP5BKXNoRxo}-MfJ~x(Iz1Z271L%eL6_kZTL?VRgW4cdR^crA2-YuGi==$X0-+u zVQy}KkU+H9pm;P$Iu=N&4a)EKzIE}cj;`O+Pf4YEw5b=v5K?M6pPVlC$3)sR;KEQv z$$-cz1((l_e;3&Pa2wj(w>+eaKzi8o)_=qb^9;h%Og8}JyP)`hOpB=B%TYWYUG<`` z+iNkJo!4!)fIys$G1ZJ-zK^B&QF*%*aL+TrRBPC|TsIQGQgMFl^EgQ|)N&h9Hm*e9>fg z*V$IR+I)9c;~YvAslqPufDZ+Rz{v@O=`CWnyHuK&eAV9{Mw?5yuG=&ifW+yqz1OqF;FsaIgy(1`u_pm% zizwQ2b1GOT<_G{*`u;^l5}YTj3H_*&JCp(J?Ant6sFzd74>1yVFJ;BXg0vx@x#$DXN1vIWkaI6N;AMcqb)Lh2u1dGUhIICBvCp73LA16q z{m!ENb5rYaGf{;xHaM5i;bmjt?-`%n;_ieelV>fn7O>*k)W0(9{l-^-GFHE@%m-c6 z20EbrIZym~N9hY1&Fg5;I>tt$_}~*wCEDvoL%R`7puP)Dw*3JY#^X_?wVrY>x_M*3 zwK*i;kUnu@D|dR$uxQ-Ubh3q zL_fC?3bOi-#>a4h^J7YL>pWUeh{QYC*DGI-DrG2PV5fuAX-;zp{odosOr=G;bAVai zx&|nuq}uQL#=pkGw?We2%;%}G0|4K;`>t(X*{R;l?)!J*!~K_i6fnEV&f-Z`);cTQI@h6rSBHVX(1#tm=F4V7~ z+h4!RpY&nb;`gDw6y$d&F08{G%W#Q$Q5t|mHCZ=37vN?FkgFwF(8SYO$lD56jeJ*s z;d9c^84`fA{|(e$6aLaD&i(wxk750JOEhRMx7o#A3Bcpvzqh~wn8WtUryPOVpab@f zg*68ImtSMg`iM#DSH>X~20>GjM_0r*(KPh^sEK(}qw?aoBjcB|x>={iTVPGk0oF1$pa(K5-V|4xYIKRfN%c;?J8Fp5 zQd!;I_b%aeN90t*cNfXtaDf7+X^M{}0K)mwXN%qBH=rW=OvrO^Ww_pP(U5)*hC5=r zPul93j|&Tf1jI#X3xbwEEpkO1ACXS~gpSh(Wri<*p^!3DS@#|TpZAG1$;{n$@0v+G z$sn8uK~6}2BW{ihaXfoXKJQaXb}ixo;zWDaqRj^MudkL_keNPGkyXtb4oBXw2;%^y z^7L>PRu3rQQzCa1+`JxfDs z;79hZN$lQrnOFZL2bc@6G>-l{9?uv!@^C_5@qWGi_kiuDQx9-%GiPX)x zoJAieD(G}SgJID3Ezu66z4IG8Bj6~ba@Bkss;aNJ{7q>X;>Bf+jkLr@ijaR&=WOIQ zVSKmTH_Yhg|JX{JDQX{0PYS%K!vSqgq;q)8=YB-1AwpK^z~i*}-d|5&aWc{`DPYNg zbY<&{bDNC5ew^2MD^dtJ?|s0Q^4BAgIIm^`*Z|jAfDx~Ng{5Xq)TxNjqXQS=xA5oI zfrCoE@lKO|3|i25Wdu6uI?3u9Yrc`qx?0yqkho_kcy%iVb6k@=RHH%aWqzQ>@5k-m z?G`!wD$OLi2ve1L(ZADc{X6mxFe3~-gkm$>qYzNWL z1xxIHBqb5~RrT(6dC=d}Hdnx9_18TTI5)|2K4Webng+Wg6FiU^CE({3-88^Uq5!ux z*J&zY+@K>UNQ%)J>fvr|e*7&*VVrY3s#Td?9jPuWHqU312bD6sdtXC;T+U1!(g`Mi zl+N0-81pCW4V1dHO$X*8v*M+z1b~pP7QfcX)R$Sx&RGG@{rO04 z${7c9NAa}Ym%e|E8Uacac&`#Jzvfg~I%Emr(0w6GvIt_&59)B}8`Y|1jV}ICOzLPIv z#Ayrh@*8=sxX5jBnf1+`YkyJRXE5?lwfj^*Kwd+rsuYzt9`{_CD|uk3Pf1xU>GFt? z65`&=-!mi#nG56V{mCdH30i%yOJh z!B?b=09%xH5A&sz{@UZX9Y=lvI5sQ@4_^=+z=r>hNpwcO5U;0k(r{?e{UItq#^gHa zZ4rD1pjLQq_LUXBp|%#dyOyoEd)<7vcNNLwcs6GC8egeJhM-Rt6=nDOC=!qYF8o_wT#>lOGE=>=7Z|8J6oxe##T! zM717aR@4eS}YmH|0n4uY8-T2ANv#hf+c0y8H;TEB?sbcO%o3 zkfQ!xD@2?#kA{DL?8;nx*PSdRo!WujSzW1Fe~A4k{iyB=$Qn%0>*yu{8#q0=DQwrp zLv;?14!$IKn-0TWY&n629k-I_#ePeI>l*)!A6gOg?#`VD?dGYj?Z>n|{=HThEY$RR zJs4=XRLTS>-!#I;zIoRmYPe}+!_siXg`WiQNg)2c+z?L69ThOj;DxYeqj-NWD}mKS zoKr(&<2p{Ai=zDQ<4_RVg3@yQS5g*05Ji;4`{zE7AC1U#R6UG_gcSA^*V~ZG8V946 zFsT~4I2BX`>Q8Tg`Pdadc1QR0rR8QqDtjTy2&hG`%Dt5M-~tFiFx7h{%S=O)LVU$% z^Wv(fh@!O4iwFVIU$699<3B_1l;1z=-6_5hU)w5lb}2sIDDN#F$c8C#)eAveZ4j9c zjWht&Ae<+&$sqFdIuq^@ahF>oCPPAidy%r5vmVMunRsKsDMIYHeAzlD~zyQWB_1ff6U3 z<71B)L$j5=ls%odk_dU>9}%?h?>FB)Pse>>roVTxH-B_~EHXH3kC0!#W1?Rksgrxg z_f4nA&b>-eNRf8Iei^fuqat|}=kU4j8Tvj%3sugugp!KK{dT8`Sq^6lJZN3!Z_ES& z;Lvu*l*G^ez+ZaY(0hB;c205oCMLv@xLWnM1+cca@1b;j9brA+h4Kusw9?v+WZ2rz zBTl$DjnVt^gwSrW(>@y75%u&(hkot;(4JebgAdaxx<3JjG0i(!_e^b2>-Pyg)WS9# z+JS8ygkQka6Y{9Ecnl9VzC2o&k)J!swNMVTi%wAdfWS2stut%N&R{~r6Y+HeEpW(= zo0rv`%mw{@Wo~+XO-KE1;~5Hmr)tk%ty=NY`4ccqRJWA(6orKm-9N*n@>R>k+{2$5 zs`QVh058h5du^E0bn!9L9h65-2tbDAaB$p9a*B3GvgIMi+Qm3w`PZvn0J(GfT><({ z(gi*%2|OHi4^1rgk7F+U3|x@+>37RxA8-5$NZAjg05 z-oX7O#G}`%0rA_u%-54N$n)La{rd;eq0OSM4{yJ~$-z|c(d$xC)JfkDrI>_0zCO!` zK%9GHp&0HSiO@UIkR~&d^*W&jqnK|sRzocZx}9+spI-(oq;gB`lwR?n47OFTB}`~d zw&N+*_3ZvpLtPmIvCmj4nNRK8nCMCQR&8yGYNJ7LSHBWrWcF6^qzF(!lO+1%HWg(l z{8_K7!Q5W^spGcW;oGX1Y`dK0y5)4*blB)aTrvQ(l$PFV{wAo%>qkd5=;KB!+G|IO zL+%|mF|TY7ZQWc=`dwK2dmK^U7rDH#`UAp1YhnTL)2!x=F5IN2q0Zp&=nu|sFnzwp zmyE5BkF)5KqBIFw2YJ4~KdTZtUtJ}>*cvzQr7YgA6$@YOhvBa&aGInbWC35EJvtDi zvBZT>3F^!%{pXInvAb@B^FUeqG4s3Usra32o9rzubovg6xpTqLxn9)iF-q8cCDQ;zg2 zMNZ@+(?dX>P&2aU{VDhJS3T;9&!`pJ=NpS6dtT)^Xg)Hh{_i-oN&4Hx>HS%wG1(nY zO=pzc-f`E^TOpY&~%eAoEwdHJSh z;6|Xb;m57A^ohZ_%h~o$MHTu^geLCZcBRO~kEY$9T>x1NxvY(nBwDo(!eDGON#Q}T zjRvp?1?LT&4dRx^@e2eNY&hLHq#wW!?Z{O#4kIb`% zOzs0=%yJ&$@DJ?gEu`1zWD^8OTf-?nG*O$M%5wp4pdU{v0Tsb|viH?`drk)UU)_t` zX)1|kH&NOhebb8jgm8>vxg{Fd#>SVImb9;CZvFz^lr%|GEwGNzO{1ZDQfLt%Q7#S} zv*ZCn?$JrrK`FzRKfc*%fdCGbY;~DG`#!&YlFA#!)jHdq!+y3T56l+4TiRdEJUYpQ z^`@x%Aa#d;Up{F4O+ocNRN$~SdqDMg+cO>_j)+-Ae!vjn5oZBbKtu;AAT_vq{P35T z-K!m{u(fcHApZd{eSb#z0lA1b5JKzm5oWVg#y7x{1CBk-H?9V@mpJ}Zpg;m-rTkXmL z_b3gqR07#^@8~0*_$EIFpES)eeWKJK-1(k_HirA)Uu%^rejne>59hFvH2*DO(_T9N z@Wc%G>iNS-o22lax{#{KLqG`RV5CAJ87*P~rXabx=ZI_bD{Ow!k%)v{V2yzI?F$|h?ztkLOMX#5-LP7j7 zdAn}e1VH#7L=9vG9V-5K)U5j(1yRn?zgU(agZmCtAh>(QNRGe+D3oRo`tP{^+Wyf{ zAyN60Mj-z0ejH#SydlqZ@FRQ=@xT5M3184pK)(Z`6a04o&VLI@S9n9xn-)~`|LKQ+ zg>eA{0rz%PZ~k{be7=r_H%w6zN&eq+Uf}LWNJv~5S-(LkZyLA_I%(;sGoE}P@G2L| zD%3L>kAA@%OaM|X-n+q&*UQVSHrR1)n?C+MO##&2&~l_{ldMVa9+}^+clui|v+{or zZbA~Z!cOH&DeGcN|5cOpX0&qh+Lhm4f(u6z0@O~El`fACiy0}8u12_P_Vi{r&MOei z+d6>t=B(DQ8NOnE*5K$&jeIQ}d1bQ(5VFAE!ZOW9F@k7n(`Rk7iAqe&#Q>{8T{$B2 z;uW?d>9{Pf+UC3_KQrNYNhScBC9l#GhD-B{v9#}AdNiWK|2()V zSwj>|?69$imUFZ7l zbNE2V`PosU7lamk1#9xVC`$@>OI7_IZie?ifV`C!G>@^@#pX0ysrQg=X9?cSM7emZ z4BjK0(M&|T*;wQh0vht%yq}P5DUNcEc%YCjCIrd*yW4B89)~zDjFK|B{|1YM#ry7n z2>sfDo0jjo7k>19dw>6B={8~e9saFj%hj-E2BL#zz6e5ly`PjOG?{nXxwjurdd#*d zLm-!F%>gCf|K|g6Lp$r|eSsPycoR0$K!s6RiMIi}Cf3;1BoCvL)dGUg zi)a1Yx!}vQW>)On4n`)UQ&k92{+=0`|Wa7qvYOXP7*$c9olJ zYBo}bj+dLqAujfYhTY_%o)da)?xRkyxk}yBl@^!NwGJP9Q`3I2hi$0%IDGrp*9!`T zo=z1ip59+1uNZ-A?Y(F0rYi}qhD6qG- z{!5uF?zi++3HN_7b(H~8Zd-I1YUmV{4iS)45Tub*kd{eV!U0zwaR$ z@)VDff4T1nM`Ric&nKkIk3(5@-zW_I;^7L3eU~W6~cQLK;@vWrY6X~FXcLq zjCXT$gZmoX7ssLN+CA`kahE*AKFIVP{SC(2WKJB4lL zp~X}1n=2vjd9gYlfwRMHDgmc%1e$c`kG;k#520^F^~&g}x-N@Un1h% zWb4c$Tb8(=P8iQDv;+vv>ETK(XA9b_X|7D_d^$aRVy*b@7mELHlz1qg}2v&u;D7s(Im)U*@}`+ENEXsq#A_Oh~L(<)wKGlGJ32 z*~7iX-AHi4MF7Mtx}}>+|oOX zXZFXl>JFFNX8OmG>~$s#wjYsMp8CNyM6+Wv&yZh<(LA?)4y$U+N z4!;+_icJ>V`tU-8$G)^q_`*(sqlaD(pUpD)yKh*AK1d$G*;!=_ihTSWk8Kluc3mDc%&BcJ=e)l_`-4l<%yG==yV-6B1FlxT}wG5o#(kX?;^NKNNXI zoYdo-hTD{Jou2a?x09p~9sEGpcM+ruA@0nu%#dBA82^bkZku|V)muSSuV}p+fD6kI z7Z^OPDaR72ARiZv zTl1N#7avCTFj3e9lX#>D%`cOkWEMqZA%+9^Kd14m34sM%vM5sT3GcUAA1aGXCK;z# zm(WEa2L`U>7awbbOFucVtnUA zA~z~$jA@>oHL{!wc)NEQTzq9G;{EAU0no95;*a-0 z=ErQBugP?->&3l_ym`keXSN=p(&%F?ytW&pK-hGOiT{2e1C;)dXiJg^^xTLTn~TrO zxMl~e|NE$_n)-C=Kx>zv3yJgx=`0|n;)B_wp)nZeC=>$v`5vt65z2zMFs#MP^3;5! zSlq}p{>v?T(W|C!Die);iF^FkhWf}yIXD2Sh6H?S27x*x((C>2#KJ+Il)ZiLKQ>tv zTMLXSkU)7%QuGMTk=0s#?;y|$1V1lIgHgs7m?%D}w?fLgoz~eL8V)VVEn-APqQn05 zEi@aSI5H9P66qU;tSckNt6;v+7|=hzZzT%-2KA><3l0Hb-;eVv#e~*5BQnvuDfi<* zO6Gho>(ANP@`zwK9{4mmkblgAm!_$LksO=(m!v4B=)3EiaNXxE_`Glbe2ep>!|27I z5y31U9lf`R%D-Fjbu^Sf)FyeKKYyeq2x;i9#1mcqgzpL%oQybVftm;MIP4q5@`Wg7 z6Uoa?k%Lwuh36Nb=!iNd0JP$x`v|$8nrzrptn(_xfJ-pKpNsr%YolaAioQdzGf>dY z%ttKJ+Klg(+S1IR$RuMMy{5j8GqsK^PZ{YrZnvEY9f>s++ar3B4uH?>aH|Qy_g7a} zm6$Yk0)h3wqOQ>P4-KhMJyH68yC}8&(e@e2dFRNG}5!Qbo_kMpo+rx;R$s@jGV#|LRhX@Pmt(Tmd1po)Y{ zL!@*tMVEO*&zC?<0rBlPw1)u5i~a;cruIepySlQRe0&@1x{VYGM3JQ6+Yk7GR8o*)*s|0bZ$)@C{z}Jez={O@wG~1-_CJUOr;l9@V#x|!0h}!A#ETcoA_itebg+5)iUpp4?D^y?!5zotw+%9@1nP<0d zNECZ0UxvMZyfGAvtVp%|^|ovJ0ma5`ba1UbOm8_GBw>X-+RV~TDVV?^8V}y5>`_fIq!6~Y+NTT)9rvDKJAd#3JP_3d3o*c0_%yP?R_9x zXxs1K(+5j!^!9YO3k&hF!*9B{nE&awf=?7vd+QKba;~IV7;yoWOu(@jpBgDvNVneQ zi7>r(uMaVl#|0#x{CvS`u5kt}IS}9luFn@t0qi^>s}_2G{SB!<3r(ekz(hy~%| z;_`#R%)l^cP->BoazGUi6t81r!T5mZiDF-XDqe`+4MFAwpfaL%XbY!g`4}5GEG-(v zc~Z)}f_a39M@&$N|M5Y5w0|tr%m?u58Bh=^CoeDQe+C4wg918fA3jb17NOlgH#r0V zlc&L=e8Uowwh^Yl8d5Z}FNolQ z?4KX$?(Y6UFCFe6t~ym4t_-5%#gheHFcIqg4JuH${7`E4PwlwRckb-$LnDeAtVA_? zXMyif0^P+R4u5=Qjv(ZMlMOj^8viy}h?+u5E?+J5$@lO47r3jah4jf<=o3fHltMxv2Z>22#oI5r+0 zg8xM$pQ}BXu3xHMdT$mTmgpnFLf8)r6;K?zww<`Rcp>8>%sbQN5uLADOSR{P-;C3}+&NZ|abKzr;Fo+@ur|Z%WZSM|6WsQDm~*ia-merCnP+O1cy%J-WiT7WFGWfQ1Bhq3#IQt z&`0mhnw587b-%X=&-h~%<5&KWZ?`{wn}kv?0f!_8F1nk1!TL`Ke(h?;m2Owk`;`ur z1}lZI=slYIW?n8uox%Twh~+b4SoU4qR0C1)*-3DS|B3M8qNNI*E&L&0h@q?w4tHe@ z8kEHFCg6lS1DNhmm#R+~Ee&e=lX|T$Z#l$d^;9!}RjXhIvwAmrUO6S%jR{%2LNf8I zS~a}Q1mre!w|D{{Tqq5qhDu`j<`9zt#VFf@GxPTZa7Ov$!2dIi29O54;LqQOCm=7~ zk^T!_dca8n(@PC=a6%#r^kGS*QgG?_l3-2_P*Mbu=3CT z#XyaU&+r$K;9rNDK>uusLP`y11NMJ;FFrBx&^2G!~ zsi;^U$JG9ldZiI4t??--VgJov&%+z}PYM{o;q{6+a7OqhCn z&)1Y9!Jh*ZLyC#e&i5Buc7fwCTTa&EUC!I_0cAjelmC=o0{f~MFwEb^R2HtT6K&wY z?%;o1Q04_xcn>&tXZI6EUfyQ12svi;Ku{o0eOZ|@rVs(($3T{rg2pIJg4mo`fN*$B zvc9w7Xc20=JzlzAA9%YQ{-O^c^T;$J(5Ih!a|Y5pHPzpSi7Hq}%s~R<-rLxej4Z!t zyhWKb{NQ+m;YsK$_9PaK6Hy6OY~RjQwLv-Eal9&}vAW_m(3gCeMbtxfzT?cL+V1+LIqj;fmbhQ(93qu@ zRz)$UU4`?X#heM85neH2_*h9motLBPsjI4cqSVb{jP)#T3OuRdw8_v?yPq5byVM$3%XqN2kc9AK=2q*Y+rTNd+l|rHd1(A4x45{k<1FvFPGKiAYPxDW z32XJ34hk{o`W8kh?H7$2ikHCib=la@kr~T*E-#v-zw{{x_SJY_RFiGti}6V|ZbZWu z6xPtO*+qn*gS5lZ4 zu|eedZ$z>+3HMMH5_&p zHl{wKvqr_M;PXA{Hh*0*38~V=FkFd2wASohl%!4GNX3 zQd=G)^-aB^zAi^;p*#C()6h42B1I(M7wkF6WY*Kiftr31&+|r~6VWz}1?>$WIFU#7 zcKo$5z0U3Y*neo{>o-o{$B9)_7@rP95=F(~`wcT!%{RmLbvp-(=;F*LFy~I)n=iaY zf*)ej3x>_8?*BZr*n4=e{Z!89v(<8qdGw>q%Q$$0M@CO{TP6pG+GR2PeW}OByWz7i zkvL%$MEpi5-u3z^D_eekez?Zp6seE`yCo-)CpG;X;@{*Zb0% zH?8ZcawzgVZJfK=Y0zm8t0Ml>gjMqdE5}YWtp2dZi`_V2>#589#B32E)c3H{yS{-r zo$mb2+KYv&)#_CiB+a468&BtsbcbNUl(19EU-$avyxf~NdtwkWk0Jk!-LIrickJ{XSQff9NjwhU4XtHCS zS}T+2Z`z%Zro4Lyt_951P{PYVT)- zIFv|)u_TF(;F5(Dh*isp-V|4EAoy&puC5-IZMcO1XP$n2=uz_{(;lFD*cR%=rJxQ2I#Uzt~tHFSwi!pAC=6tPL%2bw1q9 z(iy2J^D<@z86M!DUj?DDK8ASv?>pt{fqQnmj<)Mh+sgOgv+9Prc*_B>es}PdYP3g*8%LR&x#L$s}O2p zc9pe2q^_{c(av6<7hLI$?wLXA%CtyC<8%!*BB^G=j;n;ApBiyMy|$|IY4kRgSDuqR zrnLOA{l>woBo>4EeXz}%IQqw@1hFdv7*BcQc`+@s8m+w)yrkn)XI;1Et6KV;r1njP z_w8~s*%R@FMQ&Im?ZB{xq}=C-FO1+QRBwpo371^B_^x zM4m7HD!6_>{<4feT=bKCVlNff7Ka9U+Y}u(u;aZR6StzCH>!#1vp>3 zl{&qeQ}IkTbi#8D8OBO)pEospgOr zFT5i_O@G-?mC1=p73@K}u;8WgI3$a@EYe0t3~DHcoMA$O6DnXzQZ@Lrz1j!hOd;>00nqRpkr zSWfQ4TuSJA$hny!ZlB`ll~#0w^Axuzzmm~_tM|2Q;tVPNS=LN*TJFI{tN}B~a&Qm} z3oF~UIY-zPqns{tP$LL8IM7NHgVx9NND|Lz#g1KJe7KhU(ltj_tap{+8t;&>;!sMT ziQn~_m|$9RQ?=`Wy%;y_9Y?cxYLom>{Z|&k4^+wcqIY)@1k_ ziHX0~;0+neJ&d!fB3_2+m63q(qQK8D$0}cXt#%wqtw=V{=x?;kF!4)L90o&n=}tNc zV>LZU8b&RsgpU&}Ix7a=Ha2*re$7MxPa$&To!^rhey&CG%2h=}5{!I+>mLv5z9Kyk zhr-+%ktNT2JH_YA#~1p_&$nXDZ;yb-`}-v=6rTtc)gC`pz~%^*NqV5rU~;3O`gtw% z*Ehz{*N4QQ^{1N_4@(Qpb-ofA8&?-ZkGZy}r$*7w&Ptwh(ESJ*&R;)@RNX5u|LDSL z5%SPqi^@wwVY3I}XFf21i11V=2oGLk{yMBgMt=2yA+?3h$4BD{l8)=>BNL_By195s zN8>U1RKbmS_-#)5X~d)0uW2XiFe5Jtb7& z6-}Kt;(pmlRO_1JJiDG&qrd7Jvac&)vo;dKfA43M$CO;pyw4iSN_3BF8R5BMn0|Tp z)`lH(?t>hJLxSX73!RD+utXVe5Ho3V_KH{W#_j^-*Td|EK+`MLHRnyfu_e2MS{G<) z{y`IY)$0fiDe?McS6n9~vcu9|>Yv?-#{9!-7#RE~^);{RzYQISJokCF35j3(Nqvob z82jB2HT^~4N|?@sTEZKD1oxv3?YD^4kr09L4%@n*Nc!0W%{&MmVm&T7iF?S?Q7q#z z8DxDRZmsw&qCYpEc^FPPnQ=O@lak#KpW5y|n`f;f#>Y%}pDBXm+QQi{O^|A6>8LpJpz%SsL25Tw+t(j& z`qoG`UY4WSvdtbvXVO@-I2S&Bj&20G)Ves#QEz%a|JENtN8efRHj*zra3l#PxVJ!S zjmZ>|riD~sHghB*qPn%cY99(07qDn7JJLP3BusOD`zfKQBHhFq$b(F=usW zx1Y2Z5Oiu0N(2W^cVew#w^%_9(SN0jn|~0pd-mF)p9!Di;_BCGZvI{|yysXf(gxPu zGMd=eV-Lmh{gG%@I47Ar+}sWe>=*L3WVvUu%q)Ny+>`+))kl&CE3v)%LihzHxf#<7 zOIfNn4OBaXrW<*}xnPNwr+6p6af0tUuHs;H(*xIgbeUHTs7wCuQT0$@Gbm1l$`0RM z^^X;}MgNqSa(lnTtK`bZ^kN*Xhdy=wa=)Q|8=Z*SCnZ|t;WS76V8@7zq*SWpM}^3X z(_)9WASHs5O#7b< zb6&dr3JG4G)JfgQyS@q*oQf7}2th&nE*be#bRZsFxodq)F`&?e?KR5eJ@Z8uI9bLT zQA5jm5F3xVbVF(!7=x6Eg~7!aK|3+N(5@t`+z-t~qq3$x?>H0)_Uw1r=%#B^^VQhdcx z{iL}n7soWm*&6}4YMct-lyonddHWV{?cMoT28x{Ax~39iPsTQ$hU};C9)6mucgxl?0QA8=hit@ja<%uHYV$O{qc%S3FKmhzb;8Mnr~#euM+*} z;_~Wh#=cKztevfM3Je?|D?r0i!5^r$Zx86e0XJ-V^te9Mq!i8La*m`_)=1xhMON%=^-cn$&2^y zb4f>@(ppMq0|QEPdzD`;Vl*j`3c*a_sW-9IILzm%|9%+_0_ZamPz$Bfr5?|iHuTS!v1u>o~+lwziv z6S+J)v3s~?j#|jP$dKA59_dDlAMDRtr0ypu`3-`1?mGNds{P2WYZ6by*V{1M%kqK`p)m)%W(xu(bMOpo(Amvw(I*#882_4T9*U5c_ z9?yV{jQjH^=k@Fc@6m=Prf6huc`T=>i&qmX_6gw%{7j_K`5B*X*Pv3Xxv;DWS}+h% zczy8EJ?*it%$vO^U*We6(sK&#c7|f&K708DY~w8Yj(lcEj*mQF+D2u+9X^5qR01CV zHjJHrOt@QpKjydnifhK2HgJPg4B5SRBZm0%-_$yn0A$MqlI+aO9v@ybty>G;?+;co z6=LlCiitObEqR9B-hw6)m!v6ym!V%bKbU^H@-|- z9>GHR39?98;)bO4ECF{Y57My+nC&ow#)>Uyn)=8s6D+s}&F7R@FKH^Owjz{JqUdA{ zxHjJwV|#$sf%)Dxd)VP#RM^xYRwP3PDcUK`Vm$B4=E>2;x>!yhtH_ylvzW1N`;*m| zrCRg$pJU8MLavkXa6>fD#%k59GZX0UP+ljWYdwi^kAS*=0Nnug5XC^aB*n&N!sQbm z(lCFFl==qp!TJ$?uN9FFJkY(}sonK7=K{zh$wF$MG$ZfwtGr}TQkdD&3IK#D%5!E* zEqoCGHz`{Vso#|F;m4E9sg3^NBJCmuCe1G~jCx6!Rw>P!7mULy*n_`5BwSY0OC02V z_Y>AnS7bp)5!C2n6q8{OH+y3zT?eSe z6PO&QC~wj@+Nb1M)qMI=JkkVR%Z_gpoSOHn8q{y|h;~&WbOK^xV)AVzS- zpF39h-cJ9IH22^vvlkr{W`IFTcFvlny4hz*Cg+ylO;DRgjwvf6 z$~RT7!hiSf5>JVS8Q470?&^VP_-W!xsQG) zLUKRvw{jJx7&+5l>{GUiN7K7taMvW3dvE&qY`z+*3*nA8gDT9Dt}YyLMec#a5M^Y`Y?ATm!$Omd&bKL4mlDu(;bHoS0j&1M(hi^ z>{ESV1#c`*+B@I2b|pv`TY9jRqr9CD8tzo4aye{uDB=GpL zhAc8U{zf!Wo-5oFvg}=Y^ktK|dgDuoM%duRTie&uM3!j7m>0y4aW zv@~nCiEVwgKIGbs)&fx!P*O-15v_m7L;j|ecwyu))(o=4eeIoSwHtvHUW&Dtu){CL z4C$;Qm%OslYL6GYQ40k>=DlnvJ2zR@5*o5g5Jd4x&@pX|46pLsn`)j}@e;W_{DAPQ zuj*dH%qCdj;drK>*U4gDHx`84ziHaHb2ut@_6k7MlNP~Go;_Rn-DU|c?FhnM@w8@c z95!WKCNOhIH>C}l6CL22(mjW#D9-(;Hd#~KVO70x&uk6Lq%&O8#9%5M{Uk#E=1V3J zU^IEuvy#mwr*5?SE|nkQ?f16BX3<179SxrSE{0{klLgts{kg5A9)C9hMq-AhTiMyP z_o@qIPOGxZ#|&CoZ1_0Y-0aJXFhDht6vNPSekLVRC=)7ySU2Y3Fm7qp;hyH8bkedN z$4HYDsNSp@)KUSDco08fTuV^@`MtfpB5>%PkWbQT-OGl-3un7`#W-DyNGyW^-bMEt zPc{Mdbopz|!q+}yDZLlDuB^WH1o=v}-D;G&Fs~T`?3aIfDr<-YtQLZjm>>dz6ay{+ z9;us9Jz=Za{*e^#0U&~)>ML0&x-fBZsP@IEv7l7$78e;53c6x~yl^Fz8)7gw+(R@T z0w`H~RX%)cq#*_XqhF_=+DQ+vea%`KiFC>VZa#H+Aeow7e>gbC91%mg^-gIVgsr7OOrDwC7|J@G|YQmfm|F)_L=4!qq?YmegK4 z=+B)x29@iwWsJZ4G3VTnS9_d-U@s$tx^Wqkt!n4r^EG3)v10vF)_kb^8#utfWbR!# z5NEC|DQ0hY$v1Llapo+vP^UtLfB4=IQGCUtXSPz1U!b8kb|&SE)%1@ABO`3Z>~BE%D-hFLEV-rxxKlx50p)#f zZhO50F`DEXNCgI1BV@4CKB;6R(juAPu@j)=7Sg-U$-VAEPshoYgjwjppywsxjH1Rx8{P zP|3gSXh0LD|^74+MKC|3= zd`oH&ZV+lHQqHURzyWPnCHkicD2KwXUAvr_K3e}RD*Lt(%l*xqhz|erD#F|#vmBV} zwmeEa_y>SDcMIO3kH}z(CUKLHmHy$E=utxf8q^=g!(_TEKHMLy_NTAC?K}CMQn3yR zW;6oyILkjzzu>EfAR{9SFukb0BmVPa`Q)a$eJ{U1`*Heqgx~1t>Alno%6P!R@3jBn z7(LGc$)^dkJ*1~k_#x1a|wsw0Zde|MPa>x=cO z37CxUA>ez?dauBJc;4qN0!(t%XedQG4V!Ff3)PKF0Jn+;cX#WJ63D5XcRLqWu|0c~ zgthDK zavCr5VUrDu_lj*eJU3*?nQNC0_2u0A+-H~Ab?Vo*t}N$HEh*Iltn}jZ`qDKV#XYAg z!#8c|tGfXr!5pc(g+(_G9-j1Sj_dM+6~vLZ(3;nZ=fpsxBBs~M#KXBUkDc@@*qq>Q zFBM1MoDag)$W5WKwL2i~u$Zs^$=onf3ijII8mSA{rl6WNV1 zC#KFm6@#wlHZ-{P8B_;r(b!?p91nbQj@n0L&m}r@+>8p;G{eX)Bj5{PuYSW^+ulD@ z?34n%hi|IG_|?zV<9ro8X0~*y*&pT?J0DsTf2Fw=7ZN_s!22lfSxo?DMfO+>Vw?=j z-2EU)0&U+3N1X>wz+Mtn$yw%w`b4pr)@jxo?d&MHA`R4JRo0~D>WVI^RVr> zS&)vKZV-q$QJX*KMYMf&A2>QTDe_ZxgzkG^P0i^z@+4$>oi7AXhMR@%A5=)u&>au0 zs)ZATmQqa4GxvzlACJM{CvP{0)-iGhOKcS9K69hue>N$${+dPzzIDt^!==T&yfh*A z(V2SJAxa@Loej$5)W$cq)`*D-wr4KKs^Yu3vx2LX2p(OEQ(T|--$4uujI%tuIyZ4> zyv9StjwqaK2B*dHkb&yPlfq{=uLf06A+HX9@r2|`Z}uF^y0i(*F2p2pcpZdjFB~b} zP&jm2cOz0}=NvIdH9cgI+Qo9S%oT($vfMjlb(9G!MFUd!&c7%!ck!_%5P!}MVO2H?dnfI!0CVrH(|Mo39 zg|3NbeWdR|zH9gAdYBc6n zInU)XX2#g?nDGd8c6VFGCW-G2!L0v!QTxDPq=5ou98Mwqw*d7P5G2f&bakcdoS#z9 z!6v5CRy;+{VPn)6)}*HsZKN~Xak10gOITJ5gTY&A>$c);mow|)epZVt0on+cs2iE?D zULH8-ibnG>OAfw9lbRg;4;2RNkQmggGV}|7@fHt6}63;KJ$Of z3wLw;{)>KXJ`8V&Y{My|UcPE|`%&APo8{Q<+AU9pR>wCt&1g(WuY>S9_|{uWZ1Ftt+{>8`n9#=Dt7UJEzT@vT`gNje zSUi}b>7zteTPTYHdWw+O=V&^@Cc820;bb6x&cy;8iY0?- z2p{CQ8FceBg{9ZO8Tv{w$uOPPNY(p(nZ#8pNu8AZ!J7_QM$%)JmJpuyA|kVu%1Qhg*=mrD3X4*fEk z6vgx8WM80vr{cmP8I%xqg)r0Ny?kvAhW7NZ>~dxeMoGa zld)drZ+h>RnuB6ntBImHH<+hzgVLyKIpp>AH+y4v$L|_4X~e$0NTb(j{;BYyd|0v# zqf~ftS{J|GD_zI$yky0_aVJRwjG0C#FYkw;mGecS5{SO|7Kxsr^Wj6X&T7C{aB;Nc z#Cuo$`4IDsTD?!jab__|jn%WB3xbZX;(ogBGAh%_e|aZ;{&jy?xmJvCV^7Nvjg>j- z(wAXn6~mzu)wqDjG^6)rT9vQ*I_*_svGRe4cdf5kS7c(ZUb7 zuM;Tm2=@5Bx;PZH;?Wa)5HXP!>G?9VkJ=9JMmK<`=Y)RU`ua4~{gRSboX7cK&ALuV zmv32$+QQ})tq}r8yP0H6f&c95QPSEa!Es;1Pz?vXiC^ff6;GfV5Tz37R@xT*9?%uu zS3WD&Ij6Vk6E*0>Nb%sO)jQwjrcEZGfDknf8PXNZ9p~^~3_NWPkq~xargQLnAf)}s z5XBta_7H{KAvnivg`|f1XQKQtB4y8GEyvBCvg@T1mXu>c<+(vET84I^N0uu_8|28r z9k!*tr*%e6gEubjSH=|8dRFbVRqYh20J@=k-ygF`m(C1O*xfg`QN}wa_m@*s<(P7i zuPkc&=kcV@M(p8dh;qLqe{ZJ4dEST`@wrA$Eq2_ExO$TzDIxoogCW*Odhk`}%BD7=hEzpN!>44Hl9R7$GURk)0dDZIceCh$PY@=k~t^<4sRR zoAq^S9XcpQbS#9QKka(ko+i|I@}&96{`LV{&z>qnpJ1aVlxr<5MiF-(4+005r|GtL zHlC1m-3$itAqxEMpx&Z-_WUcPic5_FXM)AQC-ig8dP0U4j4_h*85r$*A=!Gf9B#U5 z!cv=sF9n#ZwKTXy(qfu;*!0X-3Q1~R4V9@@)Q$){rD|jtbLar$G9+n$b$hgee%LNa zB|MbOM+3hNuzL1%}aM>uDg0Ow84vT_iN_$JeoY{JT{v16@Gh^7I%4S zvF}6I#4WZ^QNJKQzt^F$1k*9nI(tE&N0uQ!IW=GJU3*_@dL(nCe!%n+?$#FVQ_Y7d z%7yy!S@Fp_ubUU+irQH5OU)4#j*;t61J2KNv$_ieM+86Fg?Q{O&(f{%3Mk~Z37hgz zJ?WHn)_t-bl0$2=KmUT43wXnAXKJj}j$=C<9+A9rTzsx!9^6%f6FSH-aE5>pPS_x`ErS;pBmTLs-becHQIa^!4Y zFqx-Gc?V*bk#ss}uMlsjf(`w3ZmBd63;u@rvxu`dd~avUXn4*L4m}0%Wy`nlIC*y} zIfU8e6M0?8&G9!PykobYR^K0pezv#9S7b%VGOp&DyDq6GWe=S>4BDP>V`>=jt=I~2 zs4Bd&QDDOs=Ns7}?G4!5JXC~~bz+@4PV%y(5kr`~@-4?@myQNwD2{(nwRJ(&Ef^VN zZ%U(d`3WfZ?-j$g@OL$X*!&TfbB#;0=Xkl(bC+#HIs`P{@k4lLyolEWfE$arYjK?UY79R!-!UAE8*%r@0shm4%q!L_2LsF?jQ`2 zK4gk6q)Zp7YY#<~@2KvT)!up{#oM2Y*PSad>fNXsO!&f9Tfm=laAw@Zs62wZVe|k8 zVUX%%mqppA5*5V2)}=36XQXXSoLP2gpsjxr^+!&is>WhvVPT=Z9oDm+Qs_N|!2nta z*OTrKh!mc)pgFb_dA>4xNl(7unZPe<`hzQ_dnWB7qvxks;IZ>cTrO|V4#ztk+lZUA zF-lCam#tWOrg|+K3y6vf61p29gBM=I!UM1d+4y4Xx=`pmGBb)O z{w~Cyb8s!G#o-o{S*jLOGd_dw1l6}g9-uWu-q+O z=?Uts?gd6)K1h5hatQ|PJT_3b{J!IB; zh7SlN(3lKUF=as$xGua8bHMN$SiH^NWrxsPHMHfCh!~V<^@eBrL3=6Fgv$YC_YVRX zyfZ8)5UFQe0bEO{{`HQqy{m-67hdh%fiSIeuCD#Xxt?%R#kl@ccA99&bkx22T%C>u zo$eb*ky*KS2^kF(|LL+q#LKOisr8G^$me3%K5e{8i&_Z~#W%7)A;-v;`-M&v*kj*e z9d9iY@_JkLHcdh*4{gE^zo!V8;!QHLU3bW8YY{jEmA#G8xM<~B|Ce};aNJC($F^x8 z%tOvW7KW2Y?F#4!e94EDIPRZ$_I>M1a{^=kTIlcwUNCp|UYMgk$%NYo-}GD}42r8G zS2(Cnrog$y&pbjI5l6FI)3R*rpiH<{&*3YWQdT=ZF>9H7Q3c!LTMk zVi1eFHhu9?RIEw8Pe_Ht_v3&Izc)#kRFRSqYlft@ZL-8g-beDWg~CWT^k0u5yACxJ z^xeok6_ObwyYu&5((4NZhkbay$K^)yFbnKiJvfdId&{+P!U?vF`><+L-yS}>=CcOd zc7zS_axPz@ebUwyne!{Ec5XU3jqlY@)3Hx|tF24Y=+#f6`hY6QK^>s9kyDAxqXqod z*0qKm)W)=Rc9yw!(7Vr&!+*2Pc%IFS4U__pb~19}Q}f`~n&Y~=%Y9Qw?A9M^l5)mv zU9dhlEK9l>{6fzI%114uLs>nDbx*nQUcJE}_P1*?^C^J%)Sy;9LZ;Uc4oxR`3ZYt^ zB3Q1Jsvmi0V}1WbU7%Za&2rV+1&3fC|AQeG*XHY)SCGw${n=MSEiZ+%sW&fi=Js*6 zetrEwvwWo71Zk7=;O(?X6P&HG_k92Ur{^M3nvKYD6wLhQO3hfC=(SaL*)|F3Zq0=| z@63QbhwTxUkxxh>q2`2v?mc7gZf`8=A{oPuLRPlgm=)Bk)apLpt7+ z)L-WplioW%IUnD+IqB>Xpq6TPz0h)OP?eF^+DD%q@&YI4$1pC-d83DZ&qW<{<==>B zf(GDHZa$24W+=Ypc@$`HLr}8fXz!T$<49o16u1WP`jR9o*T!|-N_>h!n%CvVjB-kN zVE_P5m+edh*QQo8Aj;vDlwhyj`nlj$7=S>g($+_`MW( zt<`oKYm!)|a~z`pp-v0uAvHDinQV0yii8z5@Wm+!XpM*PYYa}Drgae%Zp?gae7Ix- zyt+fGZMy1hbDA^uyyN{Cup)YVnS43h>f++p2xLVPxq({^&@+f00i;vZoM}5;IS8fw zkqO}XfVB7Bwa|%7_M!`I^c|sp1`171oyr92tP)lMv{PsV#fM83;IMfsMI%xiyaJW{Cor9-&e(x2RadZr0BICJA9ru*MB@CbwP=E(dt>~e z&R9NFI^YnqE>5JgQ@K-GGSD_br2%agSimndOCIgH;X8d)=vVz1p9&K|Nz%^_!M|GW zU`(?hb*hcUyOn}cS&y@*7cmjr{8h=!19v-wZz%lxFA)4Esi~&uHqaR z2BM9^@G1IN4C4%_+jKFS%>IfAntF5X?d@k2N2-c40lA?Gm9M_ZgIhEWn3-5QG0Hf` zty=Q`Bp#;fpFVw($z%|_(_M^1O0F*sSGr0k*4-{|6uIt9w2%G56$< zqJv#5^_4+pOUj!QfFL7IMxpg?@179cuNYC#Lc3p%N;r9Ir%2(PfV#|vltU-$)twK>01Y>l%BT2IYdM0Op&<>Y;?s0DOe`;7( zcYtro5fJ}v_*U<%ZPHl-oOHhEe6rgoxd#}^B7IYnF#>*b@LWVVf^lk4hp!1a&}g2V ze{x&J2QDsg`#%L3p#1}`MJZ%_i21L2^n~>4>T;&MPk8I{@={CTHSwK&Swfpq1e~YW z*GmyA#XS;EZF<1B8g+;CuhBy&b_1$1SxQ6iG`g zU`AD<%DX|ovKjVJG}u-DG2vEmnr&aQ2xKKRfGiuChOCAJbfILFO8?U_w+at*<{s`B zNx#Ar75X5Ve8Mzx@DAHZTH&3k^oO52L(Ye7}ne*Ri3YrSrcH89)_a zuON6J!TYANaEl43&Lpmlml~7-nqGlBO>gH5J$?P=fxSPHZ=sW#8opa$8}6N;+YVn3 z{S5^YP~o_vprrD@cyj#oKS^DBb@kYj{lAK{&G;7JqLxW0Q7Its0;HHi;PYE?VAhGh z1T7IT#XAXY7bq?+Zsz>lWh*G?pNj-=@NdPT2LDxxd2epB7I5a{{ZFcj^cD2@+Lcek z_*T63KkD8ys>yD7AHEYpN4kLYB2^Fs1(6oI(m{%d(xpfjks>6ai6BxG1f)xs-VqQ& zlOhNLN*9pcLvJDZ-#q8{Jm;+A`SN~zSz!fM%5Jk~X3xxZ?NA-k5wKux5@dW~)4BGy zBm;#@)lMV-fxmB&?5IPW)i;0#@c&p+XJX;brE_@WV7iz7$o*(EW*|le1cxoQ-KVM+ zIM?5NB(jH-o>6Rnjhh^_JX4J>1lV4Q{w^NLe~|icYR;m6!%qUxHLx!OZlJd}{UF8EhQkpXrN4*v@-rm{0%2oVVDmTdS08w+zWd zXbR4WwoXRlp5-(*TigY5GDVa?_1UflOy|jXZ?@ZIt*wBiwOA5rNEvw22KU3?ctuJ0 zkPr%_U;qs~3pE7+2kP_l!~X!si^<7tfE%WVi%-bw9AwY2bL!tPtl)ntTQU)sw1Q9Q} z|AY5yAOw@SO#flwLPH5bZis&@-1wLxdARS&Aa7`g@CA=^rN$`H}%DHzuFrfE;yOZ4d|Gab#m9Hjv%yRu4IQSa<4Bd zJ0t*v6<4wShflOQ)er%@$3E#L_Ms0RtG6m3c;ByVnfDum59ZM=2bvtv|3@ ze=sxj>nF7m!^I=jd(vd znrK280M?SRE@5(VmCGP8$pVb2k`25!fh2H{+VTt!R+0Emn=tFsfS7P7E*>XsII50J+nJ+lARAA6J96bvA6{`Lep6qa(A5~jA0 zdzG$T#sm($@b)j%j-VfMKjK`<0vhQgq0E;qc_B|a3QIcotK)LX`p-?iq`b)I`o}$P zZd<6+vuF$Ve%a^Wr|LY~h6)XZL~;sS|3V%=;zl>(S64r7{gvQGoCc@6OKn}PXP=;a zD98?~f%{Pb@4^c^=~0ME=*`1Q{DHTN2|DlW8>-fI>Isb4;9n&O;!qVV&-Zkios{qR zF!>#iLo*IsP%)yX($8)`H8oP>6myjWWn3L9*PzO7(TfW>Q{F7ltlXSFTUDQ0vlv>U z+)T#k1X?|UBazKAAamNZ{9DGCDy%M>VDiVFO~2ax@;u>OsLg8;MafZHlFA7%Z*|Tc zHZ?z={d_UWezM%A6YJa4$weGWkjL6X$rUab=HFNsr&|PlCsYw8g)IwNI#=t21Wn{A zc@(+gB@Y`G6C|(RPqg_)bC^u^NS{x(jr`H?UGy^Qw7ZRuYkt2gOmD zVO)K=e}v7>fH7;E)JFg2ZfnM1pAn+mA zpQuY$!C{1B6DrQ3E~Os+v#o>{Kf&~#|AZ2iRu{pdY*rQjQA|FSSKd`w@}+O7yFQrY znn2=JN+R%H$rkdFF5Rh{xxWJK$0NV-jle{zg_7*0pA~7M`w5rJe;M0nJ`xO z7oy7^T4&N!R2bdUp{U`FKw@@`ET(b5ZeU>w#lC<$AXeCv-z&F&;MP1lr;Z={oZb9B zwL?MSq*4y%=dkze&H*Z#MO1xb7rkq7P(QuG!t^A7pv@e9aDn1}*v%Qg=S6hYx~1VJ zTFZCtE0FMiKu|?%*I&^^4!(|Lb0<)yD;Y80boR3RVN?`+&-}e-VLajC0u}oc(bJDh zpRP@a>=O1P+r6tAs~Y_qZ#O<@3~5|%tZSTUEb?DgSr%B<>k6aJ=VUjrJrIjgp^MW1n8gYU{u0X^6mtz za28M<-sR|7lJX};tT;>SE~$>CfOWa8l!1_+aWrC!>>(7RPhL-F+Nd9;f|V3D8unj_ z1L2(%7MNF@VaUzUpGgw-SP8V$BLm6fnZRjFMV3Iw;ghsy*%U`S3Xhb7DVbX?Ji`$7 z4>Kw%BX;!pj`S>gNQ08FmbXu1!eB1r7nrMV$YN3kY9>1+h-zl~rIzZxdETBX;PHvc zC+I(-)0LgwY}k@dkFLo(OY>V5>^F1pTvcs=WvWOIaJDSPUopW-VNs&}6pz|OM?R%X zlK1n~>6?_i^?8<0m59-bp<<4s8RwOKHJ@WXOw2Bj?l~~e8bPv@FLm@iH%0lTa>6Y_ z2zwe=4=EYJG=^MMR=tX*pU1Z)-19Ofn7?Nsc`4ax0QS5r#CtQCeH2aXizb)PP-OR6 zN~Een2YK2T7dzbDr0NG2J~#}j#kGWqXZo%kut_!7t;!!1h}J70*XtXebv}czvX;xo zen-B8+*+YaV91@CJf~<|*PO&^t#<{67p<3Z?Jo%<4 za_^U01)p!Lyve$>rkcxjgJfP);QmieCynm$1kkIiNF21L>iubZ7jiIoVJ~tNOz7d( zv22G)qGy3-1=Os|$eZjp|5cQh$5p1$+=(hKohEt`>^eZC5{X?SMSd&wn|+*WzAPg} zd8_!urN5LOWqc!6?ug|{@+aI4d&al;{Wui+F7a}0JmlUjA%cridb7OwIbu~2j`1LG zDR_1ehaxaGO4H!(D1 zQ$H5`LXyA(74(-|?-IGoqDlP~v9-~vK$ey$bl~-(+I>TLgJ?Kr%cRFP!5l!TpfgmetRmeFHX@&?5Nh?QibJ9@~ z_r@9rhH{!!ce-i*R3~MfuShg!%kZ&0Xbj&+7re$KOm?*^jwoEWX3x}g-TWmY<$ILE z!e(X4!k)D#`1mK#c)J{W`5lTu?87yD`Lsa$&FP{Q4-Tomp!?2j^pMGch z4#%zQ6(fDx{ECC)RYGiE-_fHOuCwLhazZb8&n&lXe(C5>QYxD%vA{OU><@gMJo;sG zfUa!sC<1EeGT4%mG~4hD*MtA+eJaiJqlffYU`G_AsIK39y^}iuya9Z73y7oTr$xpM zfq;(&yXWY1f336NhDL{$Yg$=yKC53RQ#GSMb&Lrf?k&l$N2NV4pYWWSzweJi*rD&E z1OLU(TBQKh3gjcmb(Hej{m`&8oSqo^^gC4qWtv}8;XPKulfm&TdE?E9jc=J?_p7P- zaG_%ju0HZzq z`C)xlA=4m!S;z+>r!hq1g283UHxUmm!v&k{>e0W3J zXI%F}#*g3R)I@kKg0AjrqYIf&z&UqNhuz!lXGJ!v64`Dxk8o>IQoQyh2lVj_OS#2V zH=IznI^W$$r$@b0{OajcUaj5M5$xs2l|+l-L3sLHx~zjDIulW`P(80Mr6EZqH|(&^ zE*f3U@Ay>s)BgS2DpEw>DOf}Xg2mWv@SMA1qN3w!95wA7>J4wYn_=^+N}!nPz|qNg zQ-+PXEP)e8Qx`tM4B_n8lu^Qki^sZR|0*m!myK&QdNB)$%dcwA-M@43zI|Re3v2B9 zt7;S6kT1i*Q{?~0H#l4b84*-#p3`uy&i(!^=M7%-VRk4RU*yd<8Xh3bdZOmDT%Ggb zGaIW|(9~7`53aMLI!6MS8d9xhmv}kb@8bLitkv~SvV))9LLajefUfd2J;uHg)r`)N z$=1n-QUPnG9Gnr;0X4+8#M5j}=lnKgNH4fpKan$6dCB}^lSQ3S-tl*c$xol>3t!%~ z80*2-lc$G$JCxAhrW#h>F)mIv@f@BGJv?>K|E-i^3!Z-BDC)Yrq@Z#&1mo+&M$LHN z`*oGX_0x=>JDX@ua$$SpheBkE#LA1I&D;G=o4393+GUJEP43^WkQ(>@cr-z}S;KNk zA^-EVOx@=WKl}6A+RAaw$!B6v71ZTPL1UCGI0Cf%E@WB z@8p>YZ6sA{N}@UpVh?nCuAD*bp0;$i}dR&+byLlJ-v+eW!$-^{_X4MWA zQcK3Vh)yV;xLLIEEp%X;szC@8tlN;~mv*#o8wdi~nhR+%HF8_yTWSJYiv0zM(xXinLIN8 zTleOouOSV6oJJp>qF57il+T#U}dWGkTNBaPxlin-q&vmiFb`x4dWh zefkkTA1MVS3v=6#R-%pQQzH(&@<%@rgmGXBx$ZHX2U)zxND*n}*qm8Uwim?hI8_&BvD4Mt8P(FMouz)~!do^)ve$ zjRc+?c@B4bG#a~|R^z*rL6y{O!CoLYBlqEi2d1G2XFwTPGSG0vaH3FViJ#X9PiOCn zK9x#8eNeI9r`EzpN0&J*WeFAk-j^gvqPf`WFd@7mZzXw1rfBOP+y~`vx_rEUA^dKZ z=}p<_Tx^lB==h^k7abIr_V_q{_WKq3idSx*E_O^1-()3m7$(ojpAvUhjexXtQ7nFdOlZQ#H(KA*w0Hhu zXfNx&DABC>6?N((M_26abjJ%t3<3A9y{RM0WWRF7-DVqDuRNTQ-VGa_3&jt%7L{6v z+`7dN&ZyIQEjIH5qmR`2(LmO06|Fng7rd06Ukl)#r}K!QMOgsNS85DPya{>Haj;T~ z#G`gmAQW9ftk^hfSetZR?QkRaq`pHfsa=XQA`@@9y42P~v^8^EmOu?p9-c$c)JNl9 zDviBBseAj#t=8&kI<*7z+*$%3BB-Gkc=U2$>k0=M4W0|6wbmNwier*JD$3*q{{daX zy^`_w$G=n{mSmZoW&~vvFjcgQa0KeCGWdn5;~r77|95h1yE+#MqA7T0iNy&huBl0r zPBdQF4ufo;i^$Wk9D=;gM_Sn2I<0jXt8!{JUM#u7i|L$eFgRmA5{c3 zUpDi_H6&uxA)E?4$i;)Rs4zZk=ONo+E7J8OS79sf+KdrsECU5dLYgjT&e9miW^-L(7(b89};Lz&6MY6>qk4s$0zO>Kc!rSlC&`}P8GKIyA zmif@8skvsBi#WRuvDs$J2<;9mWc^(MyMIh%a1UykQw9xQ4pAIt`7B5BdH<= zg=Rd9tv!@_Gz3o13vWf!r~^R;+uP3O0^-hj(&%kT+U~JrIYL=5Ds+O>i z+*lXMQ!1wd3r6|3AD`HtM_el>bSJ$pL1he}LXkTjWMys#)M}URX|9pMUkW*pp{9RO z5ptRwfgeHZPb+1-J@4I}VqWu56xYB@+L-`gM9-$N`SEjNelyZ!j5;Ysuov$Za?yNx z&|73(EGm$z2EQ2F9D-+lc|r?iJD2PzC-G~-u!t)VzQOXXi3@y3&z;Gwdhu2)v)5yO z?WP|idvS^QKIlwnvkRDmMAA+mm5-dhDO^a83Wf8Z^Z3x2Y0HvBlru_r0nO&hn~>m| zc(I9{64Jwn4r0qftLE2bQg~u=KwKWYUCxHf4QIcZen$K{YWj!h=WiCR(*#)xN*QhE z(>8lS;Gls86fl$v=hOYh>Ck?adPeLSfdHf?nA7t#-uy0bROTg?t^&c(0Rp7IR@_|n zI6|6T^x8`hyAs_ci023C?g6>?911MF6}&&5ZNfpm;Y|niA0J-P9N{NqYJc&S2#`f@ zB*Fv)CV36H#>1M7&OV=AJmU{=4K<{ux=0oWgxf@KT=EV$Jv#cJ#7iHSQa%z+DbZvZ z-MsBn{JB3(Vhkp1IDy={BMHeenok#pwB;|VAMwy-BNW*@R_U65PiS1mX2H(cmkt%C62}|MCjkmot?1?C%mN2$taGGL$#t=Ptp?> z)F1o!G%)A$p;!Uc!0fZN?L5P&=p%)gBaVAm(N8Sc>BBd0@2Wyef|zD$&BQ6I_fnIZ zbalqi3BH-m)izSTnTIN;Qm#?TGu>oGc1L$k)K*R3ogKRHKRyJuC=1{wi zz@IdR9n8R_Av*pjjK+Z)TeDgyVbvW(wut(y?e~bRX6rzNZqx~CxF;G%N|K2@5?-Xp zPny!rJH8O@Y9h5IACZZQNw1ES^Q3aWWv~PK zR`saw7eT1|V1|}vs<<<1g273h>vv0n zf=(>3lnaG%B6*LgbnH^Gfeu`z09qc4LmW&@?w1n*j6X<(OvId$eQ%hwOj{;xZXHr` zLxy-*;AFtBubn~^fytzasPo5zcW{y>bK4|l8wc>3V|3iuONH_Fg$mOr9Pbw>(2p{S z-9jG1Sw1}XiokIXjGP4!0r`5yY=F$|B%l{AD>{jm8U>m|{`xXBf-Yu~dYk7YQtEC> z)Ib=7Q-Qy(S$s;W}HUu`3Zg<~cO&jbJ8rw)M7!F~RwpDfRXmcwplBw$>sWa zw+2b)j~%G966c8@j*CI0SEIS%att5hFXz);%}^nrt|+yR2>)=B1qhYBp1jD1dP+Vb z8GUt@e`iYq2A@oifK`=|3Y?*L4wAshhj+t_ZH^;P-(u}a0D`jX5Pk9tuXbdmW1`>3 ziAO&fHbGUb}gjCEk{R{^aaNERcpF(AyM0x-*wasQUK`dF$O=jrW=nox+^UZgG~6@ zrR5GvJvq4RTD5<40-q)H{NWx78|SOi+`3mNG4>@Pwe_Ow&xBtZTu=2c)T*4=UYD0S zp6e@|Miz_eSO{%J3%W~yb8|jDJ0!QW286DYZwwMe4S%orAOZBa#@Xmyf2OPUTV`K< zwSPFhP{F9V6p{T6i~P1h9`r9UL|4-}`Ho zk6i$bG3{TJ|FGI$ zthVGBtINPLaUcWN5LFPVW(-9BxZ{O0dNA0sD+X(c_Aj%*+QNyP2jB!1R;Ub$i5RYz zxs%Q{3w&eF-jz!-x<+13J;y0?cr&NcmjSuGveLtGPWE5MWDe+QkRVYou6pEG`E};k z@1KUlxnoE!L;rZf*zycKuMib2y-IT%MjNQY`NtES^CDRB{6Al!4U|B^g@A8p?Qfb| z|NGMhkQkRgzw@teyGcNj4V|HwoNsLZAI+Y>1#Fn;|91_)qYH9`fu$(9Je@X+kMif% zFg`1jnt#krCtIMt7sRVB!gaA@#8=#31*Vo2;;p}I04Za#zEQw0>y-7SixFq7-aYx} zq6zR&_f{T|>bkZNb5CdaYx}y|1A&GB>n2}ku%>ISb{Pi(X|0fJq>_Y*7kT){%F8a2 zz>=tt`Dux4kCPg%luu*R(f+D~Y#Y4!??K#Vow@)8X>F#&!vrO%buwFA+aLODNc6 z7?JSwRX_z2SBgJY2&fRn#FbvX2%HkI>e9;Vno=X&ZOo4M7!kX>38ewQ-S$oT&sa1+ zGS|OKjriIOOdmvhMQRk1*r3gUjJiEd$zFAAG9RyicEL>v` zt@34~%2E*CYHcma!64`Yarz}1xkZdTA)YqaE2;Thkx7kCqMgyjt!!NlSdqffv6=F! z;#6mi5)j3|^d(eiGS1k4hzdR#m5@W}BQ*qF!h2OoEpII8a=YJ#89>~{^lltW4VH2j zt}&*6!i-s9S@7ko<72F zIJ^%s=$xbVXb1n?u)#C}u&LYr^EKt(bl<}|QaEJ%v;DsEkw5I`3bd&3r}_%;QY=ge z$VE2Ck?*(}LnG)+ju!g;w`+0VrSKRI{MpU-{uLc+k3Q-&P%zV8B?wz)jl6a5maO%i z&f#rM)vTProkOE{PkMx#S{$=4!b6`DEpf+?p~ePaLtmCm3HTQe(x0#JBR0stIx*td z*p0uoN2kdkxqV3%IM;6+hhbR4y&s{LX1TsRP}c~;iKnUznf?4}W<1t}ZHkD)#_1;f zq5eCSz@S_E^HGX00-EDM>}R{W+6fAxhCcT)esPgEZjra*AaBM3l$zLB1m)uZwMh|U zib-K2%ilWdCjAeOTs*=`8_%W`PF_?9wA>@;4J;t>MgNo0FJ~rIiB2O-`(Y>xycGG01~6=`swG>zvK0QB8yMBf@$MUD~2;a zwT+g!?OkF$>F@3P)tzteOG*sskM3k--mM$4a4|L>NyhcuAF?p7E)JuG_W(S20bno2 zOmOwi{}`h(@Z?)MN%dQ#AfR#*#*GjD%r^b3HhC&lyJO?!Tb`A+5#DI!Z#Pahbqwkg zTOCj-l(nMG!&G->n0`~Z6AJ=OStMi?0-Oi|$W5AsY}Ye}B#t#`q1h!fC!it&ww#5~ zyQr*BFGzNimK<8a0Jma^0DIA^4%)8W@>xea@?B&luv}V%f3Gm0F|RnUFEPZi4|F(( zSzDb@9oL3^;>HaUN#4JlEdYGGNC?a~8vw$eps4!$xKbVdKQ*`TOH}vTmrA2D0YEJh zH$sN`02`eWCq$>k0WxNX+BATo)GzrrE1%;4J3>pXhda`(w|EY$*WSH8c}0u*7EfL=qV{kG`KA9Lb>TW=k8%(LqJT-)qfU9mv+9{7%19L z@Dtt1ux7UGXK4=2?95tPghcLLNs!A3Nl-cJU)zNwypOLYAO9xx^$IuMh5>lAhrBZc zM6{xN%o1kU+OR`?aetWk#8BW#gG}hUI|6N$VM=satSkFdShv$Qefc(ss?9JtIeY)JgL|%0Mt}v!YqR=^bEaLWY=}8 zF>a^a2O1KOfOll7r07}ct;}A$G~EzIzEG4lA`TJu~H0O8|~fEuCD zfy>;k0F%Ga_WN&?S7{ns1)%oM8YJjdaWS1TpHhmnZ9}KMiuj?(|3~rdKI1A_08JPu4e09gK+!j;X}td*KViQ z&mJDh$bRd<2ASWp%#Dm=BlO5_ob`fjWi?PwR~d^HuP?pVrSJf%qv%?C4`K)_5@niC z-~EkRrgc`aXmAghW4}yO&_UEkhAO4axT{C+JNbG;#r7l|{t8@-`vPB}vP$Klx!>Xd z2lBPB7=x+Z7Y91eYc%plIwR7)il139tXP=J@&E4sfONjXd`$Hg|3M7HePYCj_Q1kj zod0wdXA(&`?O2=+{IuRB^ir*)m5zbU5}u(Ke;s6rEN$XS{V!Nxx%{&u2A=&9if zIzQGtP91E}ntp?yxoBIh9Ra;dH#(C^c9(qv0963}Q`d|T-lppVwT9f`dLK=~#9Lfw z1Y)n!B)$gwZ#PFsaQKFI~P2&0W?FDBDoNCHg$qPCfLS^ zAz$yaL{Ho5I~I%@As$VKvJ( z;}!8;Vu}cy$byx94+t$78m15+(gR^lv{m}bYY99m8!P-!Pijy{uv%(7mCZju-G3l! zEAdq@xtQo<}C`^MY?nIjDUNwC6SIhFE#%(<9T85 zV(R=ZzeJGQ4n?u`6OiZ4kV(=&Y;pHJX#TJgD=txa*PEWQ$5;?j)QDg@WOQ=R2^ma9 z3bY7AK0NmCa75`(Un}B~NFmYU0!3f&_QC6cV6*G)Z^xX)2W+#SNO(JRb>ZRtRq`yY zZ3R)Voh|0}LB`&7T8#jqH-8>}qQcRcwyxc$0t8jp2mwN%LRMU^;!H~_m>kWlbx^8K z%ul|?lna|_>@o)(r_+m_hA2H~OrSzK5!~FYWI$e(i3kuNu}kTh*Z0IzoNq=|H+iZ> zS*a}L%(qM*q*;6REIjcpd(9`zz**oGc6<6X@03X0KOJi|pj9{iDyEjeSM{BGZh8SQ zsv9{SEi*dyR?!pZWqHb(xM7f4NX5BV0t__0CAy-TA}zW(i`9lv2A&`FAcM9HLq#3R zQb{+<-4>M`E~uXLW!?~eg@0`d#N3k`PLoE`{kM6T8K!Yxw4e;;hWFk#lS#B60usr@ ztRUzVYt=|k? ztqnBJ@S=HVs`H)c>IGT;)Y~q&>8qEmANdw7L@dc1C;zELgWxYWL>xo&{IXvkTyfr! zCGfL@h;8jc7S>hTJGC%WZ`L$#UBN3uBNvqy$M_CiVm++&3HIJ2EOyVHe;p*=+w*Px zu~^_FDz%jEmuqU;U`ZA{)KW-DG5#y|Bq-@E*3p_OHE2ITZSFTB|DhJLkeMjzfxA?J)zfXiV8;*iG9Lb!AY&skdzm z&WC@xqi%tm4ve+wO_?l<~# zq3@7`_g#hjT<}JumZ_@}f88HO6*MozMc)8qxq9*XFtMKKm1a6*zs;%8u!Z^C6?})? z!eob?>HKsa!0JDFvu*BVu1-IBZaE^Zg2N}MB2*uUp;{ob61+#GN}5r3v=j6eS=$64 z%}ibk4Y6GUjfgN=Dzi|8$WVuAmQh=WFrkixH*-%`t~1q+d&Up^K6T+Ekahxa*gTq3 zJm8-?_&n|7PPO(SA?^6THvvB#q)Q$jV<0{M>=i~3u!4x$iuG6eHLZ^1_~45f*9$AO zh-XQ@QACQuw!KR-t`F)H2}EBbp?Q(h)EOHVi^J`F%ms>=YV{)18whi$yoZ@&ex|hA zI6ixzQ1@^=QatJkDOpP%s6E~FiLO%z2w&yIMO3aZX2J$qS69GftPl_dO{c+^a89Y5 zMv8>`q34!49ncpvfOxYC?PxdI^^V#lsmDo&RZqiu_5KJhq`;u@&I;N(sfq86M8yN< zFPf%4iyVJ0oOMEAdqObpx1;~%m10h+PJ=3HOA&i%qxOM*-fQ`H1{6Z8{!DBf6gA0q zQgtuVzph|cC$SiWIjaf{!wtXyHtUtwN}Nu@~Z&y0Z&l zcJ9&dAd7{Yv2*n&m3EM-amD!YnY%zS5L$3?*5g0DdL13CHwv_PJb ztAb(tpXyiVBBXo!r1R5LT@=Yix109xagn#Jp+kwp;M$IVCb4HFGY1ODr^v;-9&EvW zbxC-*6XCQ-fdxH8le``)&r;rPW5_r6_L!L`Ri$n;(dStd)Be((UdMn8T>K~U$Q;O@%s>*HqW*g4v# zJIz1VU8id4b758CX~(t*FWKgLgNL&s48%k=M!_keYr^|n``Mn~9?+bY>lfr*8C(+` z>G*Zq(SLP-9_p_##h@UY^i{gB8XU>2Oet}b>~4&2N>b6NuHly<-LF54ivT}A%YX&V z#c}~TcG?{QE%V)?2g7d>e~6XrO&HVul0z3(fMAc`^QpJil1l+Ryik_L$c}*VxfRRz zT1UX>T{q`tMq07YnDoxhWuy#Y#scNX@LfYmdZ2;%Y->N^QJn1X62yd$%Ja#o&b=ae zmZ=fasx0inWY9WGcXTNOe21r~lp&E8=m2_vfNy1Y$=s(RSmxAObvgsFoy9jlUAg6X zBL_NjyLM6rCc4T-V3iBapjp50!~KW+R|&^4;U@65_tuXY&I`i>#dP@}=*x9Q@7nje z|1p~NS3rf=#+MKe+6aGhqLccfyO$cWX~oOghP*{C0@a~~I^73vb7Jsj1}@j@Fa%U@ zQ~f4}TnTcxdnpYO-lt7JBnIkj1fZ0%u7Zfmr75LsS#tO%Y@v;{w#cBPLoHIy)@V^x zIMpMxLA4thc$sEEDzh)#fN{$Fu;p;}_Q=y0WZ)wcx8N!&>U8E+Gv+PWN0@Q0m?x`Pw#=XQ3`i~=) z+&?p9TJRjl__R{{zm&C2zuAT0!qJ394?>#_h(>XVN&t`xzxi4-;KQ00Av&lwfw(t$ zilRd2A&&LxJtw?584Igdr9#(NGh5V*2RSI8y-(da_IRW~X!7HuXZhzRO=<@le$x7>j|yRy zl*l|7=dXj;*emAk&A)0fwv+H42tC209<<M}~#{o(|!{(W{jcH+Qn*PUjgpI_~FG%i2xgd!PmP8j%m2++OnuiVJAYO6;4A%!a zf}>SQ;V_K|;#p|yO55?aSq?yk5Lz;N;e#A%)Si=~au*VL$s&0t7j#_0XSOk42mk1jyD<%Q-i1nJ z@39RtDj%d9_ngZc-DWb7*G6e@cB+yBvk5mK*Q{`EJZGWsk&9x2N&W&5?y;U9NjkTL zJH9A>dlK>?BNTm+Agd)!knqS4Qo@TDAhMq4C`8dNR=#NZL_3pVV}F7>*Q`i?@f&~S zqi7*qmWB>#`t+z9%iq-Ru~a7%E=c>f#E4?_j*b9KxU$5w?!fvn>NPG5iazVU<%zro zM)k{>%#&pPX#X1&O1)E1&?GiV8c{3&_Jfgto@?Z#Y!x;`5K!9z>y(5_#ouP(*Y8qA zgx;}FXAd)Iphe9~3k7$->6}cj!`0Ua73A~ER;M)i##>uH;DQ^M4dwj^BM{UrqaJ7t ztDsmeO&NLtwd`B_I?Z!_HsDcCApGy`tuhm?lo7N2&NU&RB(^m0lTEC=g|pi|Gsx(& zg6+efK#!B>-h_D0vX<^;ll{bt&1}(Gwf17h*3SAKMZk;R&Jo}4-#};LN!H8*8XBLm z@G(bUtxq)=p->=6OVIMu*|=J;ex*#~$oj@XGHAM)#5<4g5TZQ>zE=Dp5g1!e&Yh|6 zo0fS~IZ2U)!-6^E5MfPv?ff;H}r*`V8L+e%7|(pZKNQqggPdm-I7xk{lzx^ zQ!^*%RNtd6>rMN^+1F))iX2lNbnhFHLAq+BUrMD<58Z1Fs^$f3Ml`eJ)z*7Hv_l9} ze))~9SlkuTlbWAqz|GxcPy1HPWF*yof|9j*sCNZw$8am>_Eqg6_S}bFeRLW@z6@j! zyw)_fOBf9HZ$Q3^t@*fgQ?NqM6K>G#-Zy{r!X5^($qL1?fb3DLilJ7yYU4RDrpGov zxIUHts2>rf@qMemo%-URVYec#neY25HkX9cu_+tPTpya31~;mk^%FQUz(XJjDR z{kSy#B@U)SZz4!sKWYj~_k!5+T8%!XgME0v$9V3IznYEz^rvv#QG#cWIeN!zdF*VZ z+;`+nFP#)Me{~T>g3(K_=82B}Dsd6Lv|CA!#(+X=D|9t#^BoXz8Dt_$Tip%?RO4S+ zTWXP7e(D<4UwP^0&_uvLxfpYBVra&z$TpUGoUhI%pm{&8zMVoO1XN)RL02UB{|Fh- zy+R%3=xgzm8o-XC{Au+l>iYMcQl?3yc8|i^FW-&U{C>^91{DXR8v)Vmmp)U0@;6GH zqNVZ2d7PgK(&c~CN8aky+DnCU`JJtK1>;TcW_orJr`hHOySkB)rPUf?e2fuLEii*{ zEr`Adk>0a$B4_x?GG8k+sZEg2&2fFUluFxYUG|DF|ITZg)9@-+?;`P6QBDUt#r4@& z-eiWJ556_#(SM)IIca=$sFz8cmkA?JCSD@daedAf9s+=~XSL$cP!B!}7lB;>z22d` zCa3!-){B+bO8Q?IqiYm!XLzL+iM7XHEW*s-h;1qSuiH8iL^CXoRvnw{)k)%I)aToc zq@*|H4?)*ufL<&R&cc5gd405|@p`?%?I&b>%MsCR3Usxn3qlx(=wpAdYoHb`-i2n6 z#00OGYB}G(j-G_Poz^Vj?732^yZINYJ@yhKkq zNX7W7EXe-LkL>hUFMiT7$|nw=ww0chs?uOaaISGi@1Clk$L-V=MZW$~Ac3}tMqgf8 z3Todt%RAa=ii4EE+*MJ6kVGOhH%b6dBF%uFJraZ%|9t<*7_`EmdcJYF-+|}KPHKp}wgi*&jo>LZ5v|JN zi;umXpIi@GI+6 z3FQ%z>#(ZkR);<5*kSrJD%VS2PWUUrF^oG~r1OFCJ&c=nIF2<)qBzPCX8>LH79dAW zpHShukxuDlZ_iF{@BFZI8<{U{`Bh^pF&*;*s|@_E>G(Ev&-IY!CrhKr1E1w3wC|BM z0fQe#p|`&@Mdas*nlCC8d*1}Pk>TH}S0K~6(AV z-upya_~`?Gqk@A$Hs-)}Ldpw9V2pr00OsSnzB%beG{MqWgPkfq<;Et5jw+lirQ0X| zTh_vqGmM9`x)dU9Jn9$X_rVxIsGZBc(vXGBbWxXX2mG};M#DoZ=u2hhuW=fXjvLh~ z7j6~=u#g@+(fOJ~FXjZqiR;wr4a~@%qHQHuUXD$~DrNlABUu-iM#~3C317Yhx@yD< z5%d(xOg-jfT)zYBbZ9c$jvoomXpqVjv&%lzVSJ@z+0C$do_ZHv?36m=CO>|WWyG~V zzO#Dbq}z-+*?Lfa&|}hsf30vteCXQbt6m^8zDmTd0chmPK*8;E6Y5}kna0PE8nwRV zJmvUIa!b;TrFI`WhA`9->F4=-S#QJGufaG|WP%&sux&~n7J9CUmfl$UX5nf)B6_26 zDNQ^F9*YJYR0dbIAY78NPNWy9dGwOMe zIB_9BFP%w&Dchp6v$^4K2uQv(XPf@^8&VQw|Bh;-S{=v5%9S+*R3lue?{xk+kY>w4M`*o zm+rN@tL0jGwQWG_nGDD17yU$Y_f2fP-3AzXn>;Z}{)+HZN7mioPc6h zZR5r-md50*Fu-h?xqCSmnLt+mn+I&eXD)g1>c{tBKG?ZFf$Lz+mV~pFau>DIz z-;M2Ym!UQ!`KiusQ?50eQ_hmr@w(lSkEDB|V!fS$bGR5Pudi0E)Rx76#U-~+k zq}Px_YrcEx-zLo+rV?tW)Pmjqd2y_A$^@MsAHh8PDnv4!H}Tl(vdC`)<)C865|OuA?_3Zp8Ek z@i?tZWy0LTj(3ug6Xs)vdE4HbOd;khhv)!$AS8HC{e2u!Pgckit<6vA%HER)>$<|0 zWT-kFdd6qwDOo?`2Mvk*>3Jl*GL4A3TwB!eY=#BU`@8_dg-HULB_-V4En~1mU?TzX zd!%XSz#G5JlGbutp!z91HPE5teY?!8CtK_oA2w_fhK zO`heCHST7L&{kSrcsF$i;rqNm2iMbnWA_e6bx2wQZHET#W=6b|H@8^)1~uu!cNG7T zuy}={JCkO$6CU?oH-R}#Dq>%5`|wzj9rC36XPN%M$#%5)d~WIl+NYA;Ccbb7gSDCy zDb0J4x#SrXiLwc%H)KmZGG4$sZq!HGW~XzGh)jDfo1AK@eTi@2U3gPN&nJq!nNUk_ z5eJb}Nb}v!+`je*_~aGFiCa|OxtROQT7=rl1kkHZ4`yrse^h<-U(;>-{x&+4knR+a z4(Sr48>9pQ326{%i474%y1Nk&>Fy9gMoV{>w2WqK`_AY4dOr94{IGxE+VwuK<2;Wz zW9Y|kZkV!YTyyHT{2B6;W=Dn%c6DpAsE*z7 zXu9D9seh2Xz~>&qjJimC*nH}TKne~T?CYd+aoU0zhP6VM=kiy$r6ow7a4_#`0?2m7<&R3hOG$?z-A1AxHOx+m`l z%AO|15fvHoL2GxrXrJ-zQ}CT>Ae=b$_DwM6(=fg09(8^pTqll^Wp}uale`@05qk_< z=HfsedA?`Q`;e*8b2=zh)hy2XyC6Cq3r*?gfUK6b=R0$Fc{MvHj@+C*oVY;uAH>}o zHMxKz_>iQ%4+||1CCam$XPy>85B`cT3dbFz`T<+RfM5g8WH;u&%^k71Enm!9u6k~B z-T05M_PMDoEk&_KDdzVkrkiMYdUKG*`K{PV2ZwNb*6afP?*02KAKllt$o-7XrKqC5 zsgmT?9p3F+q12xVoTX@=1Y<$ORB+abqif0hgYGA!dT7GyzvtV-Ov1^s<8|`+W4ufX z$rA^Pvef=_tc_3dXP@{pO`ee|=a~+wr?5Q>&-AS#txo1L93*la8e%Qrxn%YGk#@tUEm{z~>fjflkknq-(3d=XA zg#PEZvIleM`D1OM;ih-iQ$Q;4-t;A}xrez#tU=)MyyJRv?70N{-qXW0@T+on*{962 zypW?QK3%{?^Cg}-uRv?P|3ETu_2qX}^j!HAZ@31Bm}$lIkOnlqs(-4)o)4Ix3@3#= z2>4v})n}5P;qMpCP%c{P3p}m@A(@&+Q*q+R`OVC7`S|H;WM-wsT|@X}6zws9Bi~O} z5c#bEhsTQJhsHP#{85>Ir2OYJ3bhQ1Hp3J+o~$(vVh#s?HjYN5F$%Q+^dv18AM@vN zsmS28{n(bMNgVk8(uvQajn8z>x-f#NL&wHihB3>?d zXuA6S_a7o`qnYXzPN6sT!0#afoH}jPf5#u#oN(pFN`nyCuSbm^MSp3&+n(b2VOr?( zTjcr}lhuYy6Q%YXGIq8~KeD@OsA3A9^ty8zQ0*{jw8VbVL8ZP-Ho>8V zu(8L|y;F}WrQ%PoLfL9TP7GxjX=XgS$Boi8&gYD!;c*3hLkn7 z5Dz3C6rR?hXJ;8RDi?;aL9UI~`G)wMvb{Ic{XI%jY4jOJ9_M2Tmms%JluSK-i{E9~ z?xq31?9~*8xw>!1cglr9fvQU2$It0C21b*^fTwlj?2#6LG3P3r*owsN@`SgQk=<-s z00JC7E_@64A4$EoBzDfnGK)hVU^0IZZo6A66g_0>%Zv70VS&v7YU=X1ePuB ztDn`jdPQ044?o0C_BfOUPG-mZ-!hr>DIPH2(`8A2{1iVeX(2?|mvU3{utwTuZ&!-Y zYx(wf%(c(RPyq?{sg zo7aFA!w<`sczse_*8}4d`H~Y!r!@=H*n9oD5=i6797tE0rE!-&o{@Op#z4C0wNEIW z@qU2in?7|Q9f=ikZe@uD`TlmH^yRMk$GFX@!FGxn zXDSzJQ7On{tirAGom>0nVZJ)Tq7>xkYC0TS|6F(+gX;QFiva7n|dzdR&IGRbBYOfUl);WVmOGJdq zaiU&G@Adm%d%F&cvgdbq6eX%lJSlu~Q00?OV3sn6WhbVB zsl2=8qu?Qht7Ai}E`{xlb=c_VWwWpB7wi_FQ{$%jhKBzv?Vc72CI5rPiD*KMZnFJe zX{_>`x_3~S%@BXnn>LaqZ!N?&e&~f^$_aGovn<=f2dZ|flO=YGXi(>i57k|2_@x>G zFqnPape*u{kkZ`Sk0sdB?)G>2B+|eA)-iytz$RRkhW;60u2QU{#ijS373@D8*}ndH zl`tSeT7_HQzWyM$bG1iRC>6wvlgZzzjrXKXF3?tDTdLgHk?oLQdzu{c*fjgSd~>@v zU!ukiI_)C=VxvCn?o8Te)jU)q%RFrt)9>ZwVPz8TB5S~n4- zVWRn#IKf~d)6sPJAnB(oQ9TWpYxjGK0q0Vn$`5`nM=XJd6I)#>dma9$H|J+KYk6=k zxwdzgSF3oF%oM`azmEb62p!H_gdo~^SD_##7|n)#p5)H3)d&_KB3NUQ*YH zeLQ#WoJa4%N)5;Taz)QWaevPXLaa*{@l|{V9|v>UqOu=Guq4hH0W3Z`Luxh;+ptnr z_=3r>KeYsc7 zS&BZe&fQs4* zr+5)C$R7}@nchyJNIWuQsebkt1VQ6m1Br(_bC+(mO%t=3hXmXzry;T8Ge zbCO$@J#9Kv_n3i2BSk9ozko}gMSuWkqgPLQ&;otY8Awf;vPFB=BHG^S`&VQeUm!MwGBcOdjcL`Y ze>|Ui_d}K*2WY>2TE}m$NkN!|Qn=STnW|No-D+{2e+by$lb0NUh=4ZKTVf->6y5mn zCSjGbzS=MFgX1tRJ`1Gok$dFe(GX_(@YmPblkR4jgNj+edZcv}jrPHlqAuaBQjpbm zGp#)(6=+`E-Fd!Ql%e8Y2p>7A*p)orJEntS(N0`zB4PfVKd*RCeD{hN_YtBiwKJ^9 zkj@@X=na&23M+X* zb_KiEu;s|+WSs6Q0orYf1szyk!_C9Ax!&UjiK1KNCef}cM_`@{->cw3+avM*PX}2% zr`gkLO}bBXB_lX7LE|3^JV0q!)QLcxM_Un3dfF4Q#`3i&9-%itQO^rz{KD-P;vS1L zQal<9-uYL78>)w3squ zQpN)JD+a^Svb$Ty^~xLj!;|UOaSkL;xI$@F0+=LN((kIWDDCw8P9{g%>*DFYyA+D) zz61A7LeQp0d!1*7q@q&%Ud`v$`1g4ih?F1 zd@^Ssb`y_jx3UMxi`s|r(WFBFC^-Ett5pt$|8X2QC(eZyI4$e>-E`SaP6>d)$q4=v z`etkF(`e%!xNyJPLN_Rw0>>Oy#^$;G@TuI#!MnsPmeH$}ZF_2%>@_aaXYz+ic#y+}PoLe#9376Dsdjtc z;7`f=g_xP%NwVI-_Al@hua2?gjlw*R(yZXH>ts>)qfAEIRrmrQ-Y8Pqc+n><`?%!? zi{gLlLXR4&fZEM)RPgNUoBInMu>ujaf7#k~p>rfzAL2k_xL3QaBnnwq2R+uzv?W(Y zCVUcOoXYt|%?Zz6#w`F&uAz>FdRwa=DHm$xYssdW8O11R_N=|(&(_FWo-sD?xK!K>tD1xK!j<+OL6;kNNO6zMPDN^w<;&b?*fdC_=Ubi0lBb)@$ezn ze1aGG&PtyP5_o_?2*pn%c*BVi?@wK2^}@0QSVg5fp8}-WQNk`jaw$Q~FfMG#t#;3# zywPL^Iv#`-ffd!9t`2%T_7*p4K^nnYmth_Bt=jX(h%REQ);EUk%(3MLwtU@#*9p;a z+}4l81s2!XL!(I5`00Y$XB-6s1Y;D z6kLw{=@Q2kWa6()StUduU5ESY)@>PH)5@NNQj76!@5xKJ6`HITzz3p`j>N&&ON{{h zvr}cb|AX?g5jI35#~_VyNLdG!OI#Xn46#aRHp{KZ&=|YvX$|!MWLkd&NW^1&VadD) z>52Kc<&FCJMz~)3l{&Nnw+eDf)K*iNRILn>xArGsXYwAV2wXvD&_%Lr6)saZwA+>f zTlUD)gNF>d{5GYm@`*2c(0q#cjdHAVQt!Eq8mD-73ppX3lX@)r%!zsXt^TX#+uZ*A z+Np22XvY+Qs9#=@DXd9kP_|F{C|N)sOhy+rHdja{CMCPJEmF%(O%KL_km0SBDI^5^2VGko2^9 zHRGG8^0k>h|K^$s2~%bBK&wLjtM&0@Oyp)38?wZsRArC-5i4TRT@Ac)i?b1zfRLt!e1JrBY%*UF%ZrB5k_0y6#uH5_$&zlwH6#qp(*Ps0vMG8X!!f?L-u{=`o;wRV zKS&uN1k`kQTx6}QEH)CibGJ`oIW;z;zr8hHRO_B0#RrzFNVr&X6+ z;Asvd?Xs!Q)ku0K^&7(EHbDPxV=J!uEn(`-OiIj5Z>~(ZH1W=N`r&w~I3iFR*ntQA zu~%{>ft3-0XJSdgte5e@9Pkn{U2#=8cw-q@lRUq8p3(Exk((GC==#QxSe4{c=*5=H z$L>CV=lb{39^Gr4!W`qkK~Hr%um7UicwFhXnBUT_S~w-QG=B;+1!%T87DHf~PcSs0 zmvxG?5Y9eVeG;a9aZNb$wd~WL6rkW;96JCuMqCWx!X!)OcN|i+mEnZ|(^o13MjI(* z(HU*riJyK}+rwVQHW2yG+%`bx8x@qu?S*6-v2TgGrN}v3ci7`*6toucZyBFr%oGI~ zMCw*S1Go7HT**Pn+;x`agf&qtxnRU})pli(Hji}3Q{zE7Ih*aZO_{M|IeozG5Q+!X zvPA*3_%-x$=?RsJ2tE*LP9o1TP#5aavI)^9)N6IF`Ot#+UB~T?^jD0;)8`g; z8sbBVqo2F?&WgNVPfEdbqy=-FPmO8Uu-h~_NH<5eTQGn0|2SqakAS;Vx4c;ai_ZFR zS^}*|R&i41b7YwrfBE-V@x`b6SC8xWmc>iHTLVrJ4VR-s<+9CI2>hB9b z+&+wb9)tm;^ir4M7F4$`iz`1Xb`tb!`Te!jEQ!*re-Y9R#z+A`yZH38&=SH%QMLP5 z!2G5HFe~!eAIirFL-@B39;?EIEL@0x9veRe;`!^K>gU_j9%9rBI}Ugy_(!FLe?kZn zL=7-?rRg*t9F+j-Rigu~Lsk>E)hIxYAKO;;{rPxSIe(z` zA;?^;aF9f2#3MVtpOAN8pes0^`+x`N6_#Kq`%cd9)>N5_PF}IB09PQdj{SjV@(+}T ze?Qh$?&$f?r!O-L?@hKG6ZX6U+jXmcmQw;Ks)BmM?zaXw!niK@|Cc)$!WpzB|H zM~ps8Ag4M2V-}{LHaVFi{vV3)OL}R~@rwVC=LUApfWE*ZRkxtb_x{KL7A zT{SPo*Pua~H{=W27{5rER*9`$w`Kt*r1_YC4xdx^v;jixMYA;KWSKYpZVu(hG{3E> z><*4zQfe}lL1E9-(H3xz0|vfZuDQhEZ%m=0h%El`D#Harvqatm%tUAeckm%OdkBY5 zw=0tGpijXgp}03TnSf}r;mp44M6VpMp6gi8&Dtu%tgs>zRkux5zNGHg={iA}jr;ek z@S_`+-cPxCVW%VW`+Fx=*hj@U8S)st^P5-U+McIZ@MDQ3f(Ib?FJRNq8{+q3m(67W zna$-Ru*#eiu0v33%8p(xePgt$+%Aq*MY- z0oY6$X}4)J9KVhlCGf*^h(Ix>{t{BM6ugZkN#g zxRzjlFOI&qO{D~r(?4IyXPn1ULZgwq{=nhz3^r|(^70jfy`4twg*NuzF$h}K{8g?_ z`cXqL9W&8Vv%=>(Lm8<6VBw_92i#wS-|Aa$(XFko{T5-VCXyO4H(RYLw=G8osQsOn z0Y9{R!-_I$Zgaa&-(t+U3+d~=__Jk^y&9NDxi6T_#SyC~8}+Z3cAvF|SR#|1wr9+~ zg~i7&(kIf37TB*6y|-*k@+(b~g___)1WOOJ1~-4yJxv;OqB$%sA!ewn`aw@JVA6yY za5C;tchwj!kCq|zg!gAwci#B#Y&=JFyfL5L)SE7r>{KqjF#7BWi0m8RNmGp7<4G9O zh%l&+ia3h=bEZJ5PEK(Vglc5dX~u6u@QGy9Ye=-}nn%H}oWa(_g8^MimdDhAtF*h1vjFvBpMXF%+b;w@g zCze5@m_0?@DLf6c+wV(Y*``Tw#dJ4YotzzPEsyRpPP#3N7)5nCP&#E1ag(Mjd@_pj zW|kmG`$TCmvvX~CI%(p+J&8QapSL6sDf+8US>@h?sa$r&5ncR-%?c17UNdk8TjGii z&J7|?4%(_oR?vN8n^$9o!TGS2t4Y6ocD&iyE<|3t`V+gn#CQx`ENfzE&84dI!r;l? zQ1~5b9J$+|ORL*hxgygCAKZa3tB=yvh$g)we~P##a0cgYI+nW7@uDrJ+w7QNvOi27 zN$PBJa6wqLG=ql!PHA8UYWxoWBQFk4O`C?>Re55vwr}8nq7K3*V7@F)Z9p#Te(WNekrfLIZA+lnC5ZQ*`?66~JEsMNJ^1;jsQ^wK zzlHf^04x0cS^BxJPDOfr)D4Ma4c>fyF$$$whrat&x}-Y<2Q^#u3!OsED0 z#TtAXj`(jCLu;M#ahY`0znH9Fy6m2Nj@6dDNww1-OK0-)n7_Jtg(ipJJOODwoQ;rs zM+}RL0cnt0e|H&FdIPH_!?v2Y2ViCVRKC)XGycn~Ht5rvr~hW?=zT^u$GBI4 z*7(x)tu}N19OqR*Hnu?cC~JLD8lKZO(dxZH0xH1grE;##NwDC#ZxV_LftHiu17&1~ z4)bn{zK8}m-f0jdx?e7MTnH7auvKK=duwmhnGN{tlWlm5EiDMXVQW0yqIP6>dhOt; zVXN>j^yY&35RN>toiY`AM%<_-QONG*k;-rg5551g$n|AOxs>f?{So0J1Xwh?3Hw-H+iz$(4cvSrA zzCI%lR-=u2J3udY>Y$VCx}@8XChAzP}WsoPQYTT%T--bklm-RtY?Qi zxkYojHHQDM;X>nCf83Gx*R17jS;@2?dS--_HMR?Nd9yHE4PMd>9!}@dzv*Jhw-2>p z33~G&e`MSEHFt=TypA#|G3O4dv#1L~zbHs~y-CXW6&?VmlL6=jO~%88lLh18pF<0( z@3I5#h%iISM1}odY-tpKPS!zTeG<9^s6!G3qk0E~iwI(izn~Hs5 z3#2jU1o+t7bCgnk{-ELXtEAN$A?IydMw}kC9IC^~pAdKPVLr8q%06>PVfV*KH=++M zhXR{Mp>CZ}XgDwnDhW=le($fg5S3Ju0UvHQrU({?`M1QzO#2rhE(s)H+Fc#2!Fh`V z_1hNz+>C5yOs7GNvPREF&DwaObe zU5Sc%#D^ep))x=#JmhWLY2AbB{dafr1lgZG0TR}!Y~g6L&5fZrjs&lye5zlvL)gE6 zchaUvcJ3ev;Jpn1fb?6~+>)q1Lnjz39n7Yhs*Verh8J^FTd)5{T*x;2Y{16z%)0N3 zS7D1!g)vGA3CPzlb3HpX~fzi(GfjU=opiFn`5U<{XahG+zZ|BuG3I;O5%;w_hOrb5Ecw@eqVl z)c0evKIZe)CL@A|09n(M5fXnF2=|eC8NLzg~S)d$l12{oO2g|E#{OAG0!6-`HI7?vql_se40O z%aSP5w4VX!CYtq?oJuLVUp|K?lV+CPewY20 zoqwhYy7{UGOu%h8A0Y`>3bV``<<69uXs}%6Kz3=JJ~d%*vI_@GC?mB(Y`%2Gql7ly z%TZA`HY8Sg*W3e1qWPSD&+nr^J*w|DkOkLO4sXRJXTc_$a%y*F_OfSrrsa&1|6_&{ zCk}a;ihHmdrhvq?HXi1*8G=wjzuuTkJ9$4>5)F=P?Z%d+f`6?94sl?lID8;tMVzJZ zV!r#rUH#Jqn=FJKQs-3kq>FRTKsgyecyS?n<92@Gna6vG&-vqH=soM{tDud`H-Abf zKCX6E-+6~EY7XkY8h$>pEb{=WHoT^;DGXoe_B}tYR>$y18$2yh&C?;!MDv_*5+z9@ z^pCK7uQLs7I#4XvYE)~%vAkx~qBpFmRYvp2cowL1pb$XcVbe>pI zFS_=>Mx7S2JYT?5p)GqgCwefC8@CrT|39uEaY+!fW%RIqqoz<9p?ETj{LIT3BfUmh z$Y({ix*DOvW?MOJ2LE^wpY8VO?Y%cq81EZ`S46Yhj)wu85e1m?yJn`Q&U~^6Jl2a; z|09*U>!es;FBxsuI3e{i+kT?A(2_ou_Bx zs{DjG^1`CHh%@IC3s2QmhkBGmk=%!61{?222Xp3o*gl;u=y|pcc5Vmf`_c0}WdH7R z7JuUQuRQ#8~L_# zoXB!$Ix7@XXUKtW511CJ8?k@ifhqGoYq+Ihr_xEXw7a(Vr)BD?K#7F-vsb$W9fc?LyJ-Py zq9LOBw7c&W$tM&{{w;Z8nTljdbWxf?LNs@mrZHa;BWp18OB-1a7O2@Usb+O9zi{b ze40*0n^zH4#UmUj*||JQoNF{zBge_R74DDigibH$B!EwHHaoAI1-_R_Iqh(0Yqy_G zvawryooX#{${I@){0BqFqu1Gi8VQBQS{2;N$bmNMfHC%MMic4wD>GY$@_Zu@XRRTK zxR1=zj!o*i;X>|pxzeOxIi_yH%hb#=OuuXXteu(j(Srl_B=TXd^oO9Iu^BrrDN9tv32((V8B>8z zZ}*UnZke67EtYnNC)TZ>*tAwh{B8};E{C63!GBn|nJ)NbZ#M^mFbj3lAC*VH__uty+FCqb)40O@N=m9Z7<(8w( zWLJpEGfu3~O^N^`D-AjA1468xFGa)FMKNeee&F8EthF}e0cF>S?^f2N-mj<6Xns70 zb`e!uUy#hcdOqcv0{XUJ(FSvjFB$(Tm-A8fPvMM3TIyBB`Kv_I9=8ZG3?|g1b9cqV z;DJHZNowx_k@|oQF~tXDg3-N7IqmkE(U}TYC`GeN1#B($UJ?0uZ*@=%>tWR7SK0or zPAhH(-t_~rV{Q)Zdmhhqy0JQ9MOEOFN7gK4-UGqt0GxkNn6wL~f-D|$lN4i;yM`B3 z$|e#L9~H^c<1`sZE(m&MiZMS2jQ`0-$?}PL>luPfgdOlM_cZfV9U#|25~##<{y;t0 z_L*((VRhhlShG;kfl@e~YU=VrD*0DiKid(pF2wk8-w}ajLkMXo}6a>OVm$g$)niPRaxp5kG&thCTi1za`d>{31cC- zpDO5Ii?|kAcN~7hz&C5Wu^$1F(1$V|mlsjROmqFVvzl16)E{ulR4lk^FuJ$@p30kn zJUw<1#Yo23u_OH&0v})utX^1calHnxoYI(y?K7JF(YdaC3a=gnbGkF3mIpHOZl+#c zpu`*C+j(!#gxD5WJs<6NEj2}Elu>8=qjve+^FxJcJB2s~5v|ZA2rK2ZMMGD>f@aA< zpNrEwq&suUFDq)&e8C`F$Bfv+xMz*|L8-QdsXJecqE*efXkG#BXXjp9R|0H%mly-P zTi+pshcq7gLgS%{8i`!KY>(=iriJ;DFN(OjzUdl60^7-{f@ZM(k0 z2d~@Vzpcbi!lOvQCwRUeXbaHNU2cfXaqsir*bj@DSP?Fqpm}Rx4{ulod>q1q#YKTb z?;fbG>if5i6sIa`3z>k!8aus%Jg1ct8-+T3Nxf+7$V_uWAA!l6 zOBIUAIPo=oBAp+xZhnSUmNz_nMAs55=?`>%Y(g`!)=`dpHr^6{s^QGpSuGOLJ7)B0 z<{N5{zicbGa$&V&>gdE}{&%qR5IOF*`qh?eJ&b}Y0x4KL!4RJoLa*6odbl(E0z&1LRw15z6#gQXPhL@p?q@FzwNTSa{qm4_*$@@kSSwj-#7{QJg*){pM?mtT;Y%f3sz9_)mVMeoMt^R|P`*3IfTvtCH31lQHdj^*a6?-vhujvw)?uai+2I<>|^~>D@2l zL}iPBDCpeq=3Tkm($TS-X!{Zis(ooT;nzgCVuAcCu{C&X0{9ZzX7k>I>ehI`wTl}h zuwgxfJw#KMk|MXMH~Xhf&s0k8;7`C*B6xG>zL_rP{!!IqRQ+c;te;L9UzvUX?G^N! zCXr9w-xeGb-OhbUB2a?K^r&v?3n`#gwT@mW%?!kuM}gy|r}=m%xjY3>Oj8(S&Uo(W zqP|dyospdc4fG0&C$YSl=FfC7A!1puO12$dSq&dNpR>GO*j+hAtUV?O=W%++Tvs** zM{C4KURv3CuOwp!^TvjlmM8>DoIE;hD?fVvu7BZ@GOq?ISAv4>H+7{|@L|7%oMlkT z`QC<#p^hGWpN4%S&854I!w5t5u?EmkrsG3G^(tx8ueny6iGPx@kLY1ncOa|5C!0Hm zT3|cF#b>4fRa&aNv>%!dYMrUn4O5Q4$ER_>K@yH<$rBe&NLc(IRF_dS#8ao zT$U^6AA{rc(K#P4>O0}#gG>8KXpA8_oKN%)Hol; zj5?soMUwz03-mbmeD$OB?r_sQK7}3HSJR!cLtV7 zHFV}-fxZD+*F~}xc7&>bL6`} zkwRt^Ry1+-_xmQ>d0ErHLDlTa^kVLKTACDfOP$ZJXGo@)kB(~qQ6=~Ct23_~vgA!I zEYc)qOEc*!ha&wc)I&|rk*CXn^$W@5Au%_!yJ5>w>%wBDiNr*x+JdxAX%?2q3ZVnRPvM2y@_&!m;kVo?2j0T?_> z%R{hvPHUaG?C2k)R78%nwRsNC7EjhBJ^)Z25)C?8>y)z=gKmT+UlOtk>Jm0WY3k3|id3+J~lIsV&PUMUVt6{c)V zRa$A0(caWvf;YsFW9}D5vliATx_{axV7ge?EQzw?bTL-ZJZ8m7^gpN_e;rFRc>^4f zCVfQu-QFmldXMWLZw^^7PZbVqVk;g-h>F_hc1Tj(kw|aB730i3I43` z-dEeQnyAp50a?fRpnE?lw%h82(f915AgB30_qO9o717397EQB9=MeIgrh zI!}k&AmjGs*vJR~yj*s0POR{VGkuCbj*_-N&nfc~T2YxK)8gX=+v6rgkXr;apRw~+ zi?*M^>GC_nAr{Yr%+V!As)ChA2{OJLb*30R&XR!Co~AJ}*ba5V>37NON2o%4-HrsX zi!9ZjANca2ekGVaAbBAfI()v%M!B06l%99Pz52$w`3sr*HDo)Ok#UZGacs5WF@v$}{iYG%HDI;BXhk)A>uH z=1uWz+!{PrG8o$ai`RgE&cGHSQB;;e1d3NY#3 z*Tk0dzii&$q;m|C^mt&391oCFlAV>5%U>XSIErFli1oXXTk;5}P~5UZsM;H+ligib z)(n~#y%**iz)-E!G0M_p%Yhcyy?thsPG|m#$D#iAPjV7?<9V7^cpyq8vxHA@YDXE7 zDgFCh(O`5Ut5!o^{G&r)VM{K@c=ZWCiwsD*>N&jgwaRs|Cc;h&P>=ts)1$xVWOc>t z^j=I383n$QgX*t037vEG1j%8fn;YTsqBFq-Fa)>&A5wJ^Q8NU_79H=;VCQlLI-dgl zy%DgrU!^EnyCNvdoour-w3xxWi)cu-K@!-+@P+i4y)l!*0Qnq8aLX%q`Kt*!XSP6K zsW1(4vqbw0@l(}3JSxr7f$vq&gfhyv=_IpE7f}<+OMlfacW*-;UzWyYZQa*h0b~4v zzHhyxJyLIB6tMBi<|Q3tEnzG4rw14C7>Am+w={Y$KYy^L*7`3Qg4V4rl#dy!i2)_AodgQXwTKdw2iHX9p)JSwj1w*y@ zFQJ?sf(}LdBXYhi^f7J_^{uJV648vR_uVcJB|iSq*B$+&h}pJF2aYH2O;L-B8wtdH zOfV{u@nS|%C|O8lOk7sZs1yxiP3XbvsLz0HhGjo^*2|Hd zl0Rf)l^{sKrV(|U;7X@{u;qo$wbC1#lI)OaPvN6*&M{RP_|+PNc8c%oFetT~=ov@j z@FV}Jf1BXfU04LNs?l#+5jQZ~Di&J_nBNL~c7OIX(iVxL!* zjd@;?99KT8GB=f(t8jX9(0Xp%u)gMBkNq~Ib~H7F>VT5;VV=MXh#Y*bv*xlBf0qX| zW*v3KE>MEyECWDE=dTr!?CTS+BX>|RBFp3f4v-#b+5lr6wKA%2-4;p$eTp8+VLB6F z0mKUcCTlCL)qC`t4{Mi67(be*lQ4ad_ z!^Pjdke=c?a!Qa-22@b%I@=9m_^+*Y%($$!zwYbqUrYvA#hRv?*$a?EYT~YvcC#|v z7dp?(&?CMfj+39g+j6JHD+BlcP8-ixV%je9Sp6xl&Iyc99Y_X1t?XW8Op5+4+=ROD z!0p$7FFKASf=o;@j)nn=>`%k1l>ic{+v-=1rZfg#pH&tq$DgBzYUVP&rm#0+=quLx1=?dGj1F{BNOfip#_8J)hl<3u-YL3B>+4gJP7Y+fxvZd z5N)xFhJr91aZ$Ojdzrt=w{#+Mou{u9;=vR}CTpuLTaRV!p0BA~D+$D0MBVV8?RO7O zyfIV^M&h3t>yAmqo*`MK9>wE*@~5CtkbR_3Dz8F1Mkd&MX*2?-j8{gRh7R(V0&LLV zB#10dAHWXTAi8l$chu`zend2o098khd5HZOWw2XF-Ovl4$!)N z@WB#&LJ^{!FV2`BMe@}xqKHj(2M)O6z+CeFVwUP3Csyxd8mstvW<^3W645^ z&u*Q_hX8@t*^@K4Ue^gr>kp&xp55zVI+Rwl>YCXRUO&H6SwRPTJ;h)rG{EdoK*#CX zDFBIOaa5cC#lk% z6rsYb6vRhc*G`N)*cIPUZ56xAR(pvQxPB)sWY~CrSV2Z00F}wpFQuFUQao$-vv+-vGqaNK^}i z{5doxn;38p{L)d8@MG?cFQD0o&o(b5w!$^&pATO#TS+WrJiX&^SrH>n0zN!F@0j-#fAQm~=vI z;}s;hXAlmSHus7BT+uJYqWhb*u0q#62?xW&UbHDeH1EiDOkM($@Z7;Stms%tvD*n; zPMjpxkwwt{8|Ggt-RVD>8>CrVNSw%lRe%~L8rO?8N~i5Z!b$81Wv=c7lr?gV3t)l) zd0>Gyk+#dN_>pC}fiXW^9_1anluYLLe2NLDFaqG01kB^gU!1PKUlIvdzF|fzzMKL( zzWZ_4%kwmHp*twHcH02xP3w`>%5v4o;aD9C55?Vzhtm-CKdg|sUKiWBy7c!h<#={7PDS5WEa7tJ;mY#_GvhV@$^H^L>a zB0bx`)h@MDPFaLU03QqRfPY0%8BiF9H_=J!PP5PGxW)tc!#xF-bQ|e>NdP#6dP{}g z*c=uwnH>pL&{7|&y|6qkv5@z+zItZku>C(PE25aurimExoW$=>s7KXP3i+ngf)#%f z_!F5DQR~tQ6%2;sFHv?rS(wI%rY}u2sHT-D4s9HWd(1oJx#UoLWq(No5n0 zu{4RwULtNPLoVI(GWUzK%L0Kk)$$%gzep_$($}raF7wqD^Ht@o(SFB5oc0Pi=D`iQ z{h7l@R|XS0Ww=M_=WiCAN3;CdcS_F}{hq}QuKlA4`3vicSab>OXzI&+oHIt8s_jIF zjqTrPgLzpC3FY0qs3&X(A}`u`_F*hLnzKBiS}L81JigYrJ>M08^2IYTvb8qZ8IfqT zg)*9|dEDIRENel;hNX3ckUDirh!?u6ekf^#vLEYCLjLjBaxmC?7*i_T>^gFhC*D0# z&zB!OG0mTE>M#7iftBmqBwy`nUW%5-ef)LOpj%gf#ReZu$6rqcsMk+`)2LlnIJvOR zOL)`)gPGTCo+ae+F)&2}))CJrj^;8X zcBF}?0uelMatD>mX_bWem!R6(m)BAOHU6T@T1=@}j$I@c>Jq!Kr?F zfuByNJj(utjbuGkur|lG)71E1lWqoF&Ec2KNnz2m76Ts%_sna*u)x)@MGAGWpi$6R z^lsRQ?tT(WQCqDpowy^lTy*L|(g#5KQt1+7h!yk3X`YmU4i+$LuR8W_wNgxDVB~8s zHNqH2%f1TN`y_CWoUxO?8b|VG(vebFU62V^MkKetPpKIGfluF0dI>+Oxw_x7uE?-sy1 zEGh7Fl6-PzYE7v{J$U8qlLhr;M*d14v~!rTMbm-h(;{rLhMGk<_HI=EwE-a~+8;XI zZhPErU$+-FJf7k{H7=WW@P}HO9uv^Ds=dc)|78|U40TU!bL+Oaao|DGP6^TV`78Eql2I`=yH z9(=tg?l{oBixbefwKtq1cVvGe{`=I}N4a~&tm)cS2jOdpxK6xk4L)oy_uY4Ro>$pr zmIb%PC5%f)ttc3s##C&iacaIM9%}?o>XOc#`+GYlR~bH zw+XoQBAIpOVLDGD8pLd0LN-℘1m-Xd-H%paLt^rAO6Si`n_1%Hsf*(5meJ2a7;- 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� z5`;{1YSL@O9U*SK`6l%`>fQeNk9)uJpZzxHTg-GnzaZ~PS;RCYFn0pBl_mg<8l+kRL1IAM*nGo%|E zxLPA*KI@bg(pE-=1!%FW)l%(k-)e9~i9`S|W4wm>`Pv($)cz998OP)$@~yQKL?;@g z7sVFawgp!h-SDB!Wj?pKN z^{y>^m`PZR6m*Gqr77XEX}k_-Zy)0w04+MR|2?0W>Y492tNs`RfLky>&GrAS=+M3w z;6&DMhX>N{9IgNCRBcI&6TV3u^U;DlV0J-Y>^II6;zaxr?$dBm|aC4$gA5B(8JLrDJ)1@Zv13VthX(*3aq5 zP9_rH``TamtN(su`=3g~(~=$%(WR!nw0brZ0xJW+Ob|KDCwY`ol^ohn`1#xTOTYVj zzq`J*^PYl6z7Xk&kdO0k@66f%Sn-+xK+-2EH1@NvYj3{{Ng|Hxj4 zF$xYfffmwEcLoq>4$v`#cs}%2u6VXdAe?8Gx&fhesO4lY zyFL*2LDzT$Kiza6vQAf{*)D@+qE#z+D~S)&%DL)B@KgAbrtD9S&ax>eJBu$R^5ooC z<1ZuhGe9{wZ?oQa|JwBo+((BG8K_Euq|)O*K6f|u;u~lGcG-LW`s-irz4I$yr+S?N zl6IZ@s9p9g7H4=z9HYRphWWEknYlG!o4Wz@MbY-f^rOlba27VY*%s@X{>k~LRyV^r zCn~<7jAY~o-ZtE&zaJkHKjHK`owxg>vCSzHngmr=ZY&oc7NC z`9J!*9!;abu6wJ$b3#DiuF`l23{htkf3HPCVW!!hQM7(v{q|q~%XMgQ)LcmQ)ijK3lwmjU1~>;IiGdwg_118@dF z2FQUm+_ppfFg~q*Zi8mqhiO-y;dic4UO3Ie92?xnWo{=}fmM1=5MVF_(}& zI2)R>{o+66C`@Gbb0L^iHT@gt_Gi9fg~^wU&2LuXoml-D`1O*K6P8MK?!)vGeaA6N z01%c*9XE8-mPJUatE`ItyM4H@lx%D z8&rF{V2caF`XV$sI@0K~Ma|Bbxx#(bu`slD@q^JjAe%I8TmVy(2{bIqu2jqYJ2qvh zATctCjghf+%B$nPPn-;lJsTL=#aoSl%(sUQ$G!hN?+Lqjgt3g!+0U!DFyL{o@mn5F zsgB>$99pA7ae_g{qi)njR3FP@rsOvB5%gn$&gRCuv*Ur)hH3T8XM8Fj)BvCd9C(D6 z+f%RO%tpN8FG%O4VM=P}&uY>~*|l)sJZvply2Z#B<=@_JvWSSQK@ej%UMqJ#{&OZk zK1o~=XSp?mwQ=Ztt}pKOfCh?eZ6h>lg*`$)`E@jqB>>QZN0(};OMUIP{>Be~@ZI14 zmwchJ;F>{&00^(2%+Bk6xAvc`PUePy%Iqplhk%so@4WKAyS}!0<^6Ac=Wp#_ee>_# z`QZC_a&`lPv;EBaXMo_`PZKQW|7Y(_er#K^^S+%Y`{w5D-@C7GSXHm8SY(N!B-)e= z5(NPwAeit#fB^#*;DO+Y4FU%E2M8K^V8HO)1HuLbNPqys16!sC5=0cJl2xKnC6ZP3 zUbU~@ea-E&&y(rz_pOM%_c?j`n|W_G5t;k!*wM#|h!tzCSP_v2ed@BY3f9g%_vN3xO$2)MJ^@i^Fp942uPEnnrOMY?t`|_EMXGV4Cs~`mm8C>JEHrI`WZ}> z+Md3cr`t)=Fk_cg@UBap93qsHm5O#=4HArz?Vdm>h^1N}Sqf8(e*9RvvTS-ba!ANl3+j&_7_ofzMo^2j(IVL_yu zQBr-}HjKUjFrX(KK@=k}gfNiMkh&(C?nGAj)v(s>47+pNVj^_@gXpcTN3r^k0B4x` zzaksHtQ_v@RCZv!qT3|co|4Pin`zWVKomX4nFEvduGBpRk6-{2Q`$ZQrZ=O(Uo*8q zPKvUJ@Rl|-P^S<+3g9$4B>=RsWCG z$iPuBsmC02oeMlH8A(eK7=^F_dbk61_+%o&>F*bgtUm8|N&@ChmtTD1FG1hGlDnK|*|m{$E| z)oc?*PtMesdc)I0TQLq0{;}oKbNx|wVg1^jfAZcp{@%TxeD6>GNP-jgJhc%Xz=%}= zk}qav2owRpV9@2!24%+s2Lh~gNayP}H?H6N^6JLzZ!N7}{|CCN<307_iks`0OhN7) zo(@ENoVE)Q$>aUZ%toZBw|j0H;BvW`CLi>nI$J23wBl4?6(jeK^g4B2o4C- z1zi!j4cRs#0k6XnB>ry^ETVx8tO4uD75d6BzJ-3SV{g_A0)Ze-i-~h^ZtH z1d|%lpau{NDLto;UC*S3{H=8%#{H0kHmz1q{3*(!&_L4~PF~H3+(~6@S=0`(w@;y6 z7UG`F8x#r7Kmc&41JZ)w5%J_!91=%xV09lM3vJ}L@_?w|;5MJzaH9QypMtAKtUhok z9tTk_N%-THedFzuC&Pu!Z#`_ z^Mbdy(UFRIfe>JJ0e{b8VvG{g^m{9b1qp+pVNV*hFXL8t*NcA8!uqo@eo|WE7u2zw zD89{hg_u_crFt@-bLf)c0b-*PdLN07u?7Hs8Urx@%-z-wWL|_v=yn%Yuig2BFa3@G z;`eoJ^gr8v^69^P^w~#$xxf45*??L)Wx3DRqyrMXDo)IZ#SDf3Bb0{`2%OMOU;gIr z{-Yo2?TK`b{d*!RLamnTJUD%v1BUcyKOjV;SIUt3G3Z0bF9yGnem}vGkOCQ8GIK0~ zg;SW3ZtiaB?&CgVLQTk!!+%cGnCZ&Mwhi7IRiw#)_E7%CygUe)@zYr0j%81#CIC^7 z6Or0(yRk)7JNiE+8V_MNEkDB&3PM!7z_kjs>BIl7-@Ri(lofpM9cQwhqK{-kzpKgZ zr5eht{>@l`0RTTS5%V{U;TT+A>MtKwzI~ONMRS9L_PZ)xz5xJM9wKL?p}bxp3FchX zvVtQD8e(W0!8r?DGDB{>V&VcqgYr{>mT^2ZJlI|lc&Q@>2~|xMKmkvd6K0fiB`{e_ zpbpiazC-07@{TgptGK{Xf0Hv{#8)!2v;oDS9nh)})3u&`?)ntV>p%v#@QXWBbp zt^R{%-c+z2QwjP9V@lVA`bw`M{bg7xw4(`O8pYJH`nv3tagf0s+}Q1M`-ir0XiVS| zW^kOG4msrh&uu0LVv-lN+782fq|O)HrADWb(>lF(8Vjn&!osSV_M7)!bMnTy2<|j? zPmJ0A0tk?d{X-6t)mB8#RPeY2*9k(=6Z#{~_HBGgin3TW+jL!mn+N8YbQRTjN&-hr zvjyix!+uOTFS0jths)oBY0DZNf<7N(;@KsQ!B^fVCUhY8e7@2?gFe?jHQ1@W3AeoIm{+ z|M>M5kjRLK*5zHyln^KafZ+h6Q!%^NtN*gEqLy5*q;;f5ivU2XqD%(e`F$Te69R!o zI1s962mq9%hy)Tg_K^*zUUR~s*ZYEgL9F=RV_)e5G2cI4^{p_z25>+fjXdNYhr!8m zBsgW-XFzy68Mq0aezjm5Gx@NX#r>r~AjX4N)HJTxla0yK`E*5!{v_&lB;T0RG^3#Y zH?Mts>_OeUeOKL)1HM|S|C+Qev@!k>%K-6#9gPCiiUT8!tr|myajRHL1NC@TK3ijI zWD1JMq_2tHYV|J5umKz;f;gsV-R_>59ixmP*_laD`mCJTF6JQ{e?x!-HIkg;+jvPt zhgKG1^GBFeJvKEHnFRc-lNu6CYj z4?jXz#<0pZ+>i#nT5T+W2nWa>?z8 zrYsm=&w;&xF3DGK-2=38lf^!QL&if{Gs`Rg2mwSJAlPh}fADr_1RR4SBe7Khilo4p zJlOLiYcbP6pa=klLyJ+*Yz~aP`mgsYCw2OSYD<2RG$Z|w{jg^IwZFFG1HO`GfF?H& zcEa2u?=f3mM3nP6Ri6Bf+hnh01YFoGJ8ZgFZuAVaAQ&9?vZ+G!V!6Z(N}2WZrkC(n<0 zg1+R{BlVV!Rys!+tNtenoNGL}hG$uOz&UVlGD37z>gU7{pa54r!MrnqAh1ybA2gay zmYZ=`uY6ko5N#!31Mrg(l8e2GE5c{o4FGi_X8tK}90Ul_#7=1ywhq#7F@d1WhL@y? z;o?$kr3+EEt38+O8|HvgpWh<8aYPfPvC&tQfbl&{Kq?aXCL*N?+ixRJ#fK@SF3csv zYt$b(aNjI#P^U!R8~~{D2Lpo7G$p80dLfos`Ftg{T8UF0S#TUW{cr{3N%eMA_(j-D*`=?7?Us>(23=tp}QK zVMB2hg34~P``n0$gZAKMYGUn-O%m8a^u{dgSo9VW?$4eYZu~0*)v4iU;rlMpT<2O z26r0eEEyKKV$u{D|M4%LmSL8su1d_?Y z$~!^8OtJuFh8#u?=(!&chyJX!2mtdK>zR|`A zoyTjPOj|F~j?TEG`3mNLQP)qRF~sPkhmA5sr6(cSLMUeK1oifjD(e>11%n&fbi3X% z$0%W3k291Pp{Nft)!4uL)|=I*Kl!Q0<^IkSlho7q=W)6A`v=^h0T7_U{9n6u*EpA( zQnW=*w*m7F_yh8LOq)B? z@-s06z^B>RYKl9%_?<&|pS=g_fYAz_+7nA3?v9LFOs_9AYtXeP;~7tAoO8wOoCE>J z`A*|9{xom_$Aw82IT(7pdBA;oD$xXNo$xt5rrFY}fA-fK-hi1YxXGN0$*zj<|3Ca;BX_X^ul z>^*cmC6o!iv>Bk*Fv2Uc4cnFa%}7BLf{?({RlC59FRfm)G`kDIh|tSmZNYY2a%25H zMYxUfow%H<5tFFKRqh8v)1eLZs83`y>w4S`h~tS!X+n&_{BPX6?NA3&<%1CAu%~f^ zC=sGqK!AgClC>o}jC@;TNn0Jdha3XN7@@2aq>(0Jm?>7jVvH%a z5*gkuq>aDOqHrg$G&4r7BeD!e2tmc7+Hu!$Q(;HeZ4ytnm3~--hiTYTU*KD?8&g_w`dcJ-gpD-fA^pH}#n+FS*XEh%Dr z(p9LV@ZfM56lx_48~^}707*naRG;Ax$O8dv*D}q7>Hdx%uqRZ^Uv_ClNLHU-EqkUt z)rQ%c14FacYU*^(K4Uz@A+(p?G<7$bqWab%5sg>-3O*i58b_WBVeo~MScKuMu&%x( z4UnJ0oB2lopqK$y1OR*KfiFV{K*J4bJoywe6aoOSn86UBLqQ#7Awbpw7#zB|K0gFN z5E}FxPBhGbK!RZrKG0tuif={BIDrV*TPA;JM4eEFU-)JBR+Ou06@&!~5V3B%sFt-_ z6RXF%?^cMJ6Vp#y2;fOWd6Lh9_Tn$Dtcs|c_iB7d?3)Q@Xq&jqtgOXBSy|9_GCcJY z5h;zb)s>sdZ&i2Du4@%)*BStxfJXKO;wx4C2uksG@vxuF?YUjtja3`rh(3dbqaL!|Y zR9sRXZror>i?^563KNi6OJwCIyL6niU=c?7Yy(9qWN&WnN%wFH93-bf3wG7W`VLcccjQME81qlq< zyMF8S*VR8mP&oe7`jo0F#{Sl>_VRN*<-88TqlBaSp8Y&_By>_4z`xzq-ROt&c`W7k zkkV6jmGw}o=HR)t&CTldFMp-__=i6bQ@&X3KGQ*%!tGX{S6-)Mf?L(>xG6Y>U~&{32d6usD`jg5 z|0ax82MeKv<|VMV#$wPWrv>Lao2zMIY~-5P7C8*+$GEv8<$#0x8|@UPvE?kYok$OjvWN&X@I4+R+{W_W@QbAd`){*_jh$ic4ROnG0Mxl4w1Vrp~0@1 z+re>D{X`5eF0U%D_3D;Y|GBL~t8z%BkwBB+KojxFFeKHn-5^V}T@cme)C_%9tF>Oe z7ej-91bbdhADa*La2jTbOehlvSnKFRjev1&XTVw`BCfxhV(L&-+Le_~#u^Ooctd>^ ziXX+dHU}#MsLf6ieMkmwPTGu>L!E%mR-;8-Jqq(<*edbB2ZIv;7lDT;f*H#M)4`$J zu8OC)(Kn;79Q)qr(M(@E1p`85Z>}Jlq79UgH8#{|7Il4@*hbZ1I^jcaSzg4KVD=pK zb${1b;b70BIhfCBVBKFowYi%(>Rhjq3HUe1+0h+fN=>J5QhK zNobPN;4_*Ff?@4I6Dcefu&3j;y%gc>}(g`D`SSNI1G1|OR;aX%fKkh^mT zGX;{1vjt~t#m-{@dY6nR5nE0cQUTig_)i8H-~tUQ!#t4x_+}drw9;^Tfl$Sq0tDc} z+5&)Ck<%#@<>DtX9R+dm>pN);=v4V(lP6d{p9L^!8YSGc5b(s#761eQfoPi~K(}fX zAz<5>jcFk}Hlb>pTQ4`Ef?}0{_zIIFr)2!eq@mOa+yu-KuFzXB5Wxlrme#Ima@(lh z7J+z6S2T6BB76I_*F?-MRUiF_|6ujYeKAoGcxF%y=mhMkAf`NNe?7~qH}q^)x3#i{ zN~kAQ3d1wbC=6xilk%a|fF3nUEe!{jsxdKS!F?kY1=6TwHwp!5Y==|bh z^-TSf^70p}vXiw%l;U)L5kJH#?eMxkPkn(YZQ~mqI22A}G{8JOzmqj<7+Bhkz6-O> zSrHpIZdI?o^N#V;*5k(-`x^6T0RY!=eSt8>QQ(XnZ$Al5lJV^jaMLxZDhDUtb9E)S zfGrLk?n)ikf2pMMr>rpCd)wQt_WJeB>dmiy)eQMBfAk~aGYJ>K>k^caWM~8sbQ1U_ z7{Cw_R|jh@NqY=I@*7|Izyb&I-Fos!8VB0!^pH=$@BIZTdN2eMYduGplt#-kL zEU(?rRbHfp;U;=V|6r`6iEyy{R7}n@5pHWypSsVEPYhT9 z1Me$0L|r5$ZKN_h0g5hIQy1zm?IC=#wgK(!RbvSNR@T?G%|#k7i}PmwGCWypM0{cE_P5AwwJlLK8n3I1R#Yd{W<*76BqpfH*k z{w4KYm<|rHTtLXNn8u62IT)rSmHR1s?JJ*K5N=1>qr7(gM)kpO{Z{ql^UtjjaInXA zC3T70L5KoSNRxx_f=$20LwpCT;6$jRCfDJ*RnbP{hd2BKBL zH}u~;wx%v}i7+VyG9Q+urP08X#xCJ{AW#GVJ>cm1pkwtvxzqIoEFY%>0ijkQP$o$2 z$<_fuTaWH5VMq#I!6+KzhKqh7I&u@$j&>RL74Aa$36VG9WFsk02r9><6GBe{7x@F& z2UCk$DEs}<#DMw7{iz5-pU|wffv8^6HF+yi&%gI8@0%dL|ItU)vqulaSZOb(R;FOS zZ4!|Pp_5Gz_WBY*MeDM%#(sY^4L0w*Qr&s;O%aAL*3U$ICbdvz-El@?yt2lXP!dM? zfHqcI)RxVvP^za6oMDRrME#9dUKJ54tpO3kH8DCgzlh(gLOs%|*(Y9wT9xL4I}r@| zSs558{T-@)`)C13?G=JFI`8C}`YZ?Sc|Ypv9&klR^JQ!rhKYz@F~7jn&rmqE%Azn+ z-vFTK^qC{Ua$bZz`=rghO;F5M&b?NbL(G?$u01hL2ozS-kI{y(WeKK#4C6aXebqO?QD{7{$9;MAtJTwoQWe)J^VByQ zk-}%3(u~(RucULB94ovX3rud@y<1(ob6fTeMLl*pTMr*u0KiqD2V#!BcYP!Skou0| zGZy0NNgbr>va$t7y`3>N)-digU@50=Njcu%vI_jd;(E2L{y48|h&59xt^&-G&71>df2UNcU-{l=RTF6>l~KmDwF@bSk9|7-s`!aUUhSi}%$YmYMwe{$elv5y8`TE<^SJ^v9Y7WprGSECVqsOF>TdTGxV2!+kNJ0V( z8X-*xr;OBdnpfkU2rLnETxW%f>-D?$#3Z~WB5O~ononZ2Moa?j1EB|@=xg%CJg~C! z?yrBthqXQa>@ySk2RkD2BnV)&!^EQEnfMk!>;3Bx=~A785x@QVYt;?57|3nsDTJaJ zT;%05thgIBttsw6_``j>D5X?GChy2deC~vOJInWs45HK_*I&6tkz1rP;R2gVL zYYwn|Kz9=pxo7MQikujyc@e&>Ugf+Pk;92!`x>0|1NWWO7WFtClvi?#p{aHgOpm3q zpAQHNVFiJrO-Ce=Q*s&9YHBwQKcjCw)3uLlp(3s_O~QPcrxL74r=z{n2n~ScB!+%t z20#oCeP^Z>z?s@0N*BxrLaqZdu#7Vp^Of~A9mKX#t?QsolhKocbdpLFhAtp;a97vk zs_*>#ufFfG2@L2cMRp%nu@m|vui zt8G<4`W*-6vD&{UmG#|iuB+Rfiixf9hQ=*w|Lk4oB>$C+quh@s1HQ{k+0X*eSp=xU z?=F5}p#_2^G=e4-0vNZdaK}f&wdxNrG6xa}>}_r9P`wRli)>V{{mT2*n_v6D@a(Gp zSm3c2r}fjowCjQdCJVX-aba1ExV{SOWuBiLG&hOxi2gc#p?K`708skejR9l39+R_ksezNw8LBK!&zMOWhdDeYv z()eSFR=isfFcwRbA!8e_7M%0Mg|ixzPt$z~PZ=NKmyKvlFHX{nVJ1`QLfdo8c9 zybvjxA z_FRvLI{Av>IyOrH#dz?;Dsl)8RAUo4J>#S&F+zxZ73?6mrz3t95#n8pw;RQIlQ@7H zXB39;5cejYscY1KT+9>xv@7kVgTuHp9K#AKOjNYj`0_vqa%iICvI?knu44h)F@}CM z`wHOfZ9u*jaEf`MpH~`Nz=AQhrIkn+HVZQ9%8aSVwB7;@xZ>#-Us#yO>VIy=;ew#q z>r3$h)?#KQLe_4HaO=C1?uK9%1`+>=k-e!Oi2ntqcMjVPECLLC- z@w4Dle&9230XSh4&T!Xo+cRdxT<<&-GyKTo&zdCCglKToB*2xvfVK=AOLJvDF{5ll zT1Z+c2vr%QFg6-KV~y*b>OecW-V*HYNr<~6;qTJQine^|5!@D+mn1k711$Uv9w7tA zSKs1bK_BR+xZrMZK8O1OvwW>;h8b~&!;B&WQ(nBmA0cps`QK&Vzv{Yx1{5pls;kQ~ zwx+WOfaIV|F!4SEg|R;gaC4T5YB*`0BN?AUOfSWU?J34n5j`QO_L{qcD8}!Yo8jwq(7tvF0IaIX{?8V3?7^V85HzS}Loh<@p|ZcMvjLV?xivs< z5ss+f`f4UIahWgX8YS{Q6rso}KPyBC8(1yM`$9<*Vstd2s4}=Pv9e;d+O02??zZwj zc_oyY0`;ebc@eS_c5L3cD*~Psua@dR1pPo0c`@~S6t?TA%Mm9=w>dUg|Lw5`={I{a>b@3H#ymKd20@1xF#4318t<(uH=6@1F&ve5}g z8Fx|zh<-Zr3gE15pqzstR^Mh^sm~+0Lkj@LpTjSw0O+WS4)7EFVXDOt?rG(m12Ykx zafsd6JZG&h@$`VR4(23y($OS`FsBkQva%0z9Cc2`Rr3%&9xR30ftTVR zrZ;nh=a0nnlgDV&z>O_|2#3+w*?xLo0_D%EIqmnS&M#!&e=2AQ|DxRB&Cm`>jW)JE zBto4uUmocIz>GKYgg6=)#dlH&2(E4s_en1E|70E7UHePd#I(=s$o-$6<-(c;%a`~(*DH#NeqJ>aGY=!bu=G?260B|;Id~|+A+5QP2s(}2mz=ZX$&Dq8D@;PV_E9e zuPg*GU~&$F-oPxFshVgWlFy*nE|crqF(Ux{DUAo~zz72zRmNyHoeiOr|B1`W>6}(j zbN?6+>!cGfQd${qsXMzgW0@L*n6Wb33gGj#0mT*!VDK0NUEQissT{yHSK?%CPsN3N zEy(j5VIkTFXabQpS@`EfUe8lsw}tY{+C$_IjzQRg(8H!(Z-vDeM%XE<;N)-K^xyQ2 zpVN1YQ9mhMN~dze#Xy&*_>{FA+1JeBsa>UA3(;BVFavFKg%%7a3uJ61KbqK zc%AWr#>5k={qJr)5Z;z>S=w7@jZHm%$7en|1cFlan=CkW{ax5G(NrJR%JT)93_kyZ zRg7Yu4+7v2r=Z`kxPADjhquEdPrurB&4>DH#xosH#&|xgbVdZ2l017{(2_Ai!XFwH zTGjK?1brc;T3$e4Hh4ko7(?{ENwJtIAy5PWqXCvMf*F4(PgobNK*?5Pm>eX(2PH_K zJ>$K>gR;GVZlGC^Da_}ZPWMFsfE#E#z|L0ERV1)3USVI1` zIK^*U`}ugErOGYnBaj3lBtPb+HzzYg?Bxk)y`rel*tTu zP;n?L#B~(!lWT0zE1DS;X%BWA47a@Hf43hxlPzYp#I}?kCb6V+O z(oGMv>)Zo-lO+Y34&GPPj&nugH2hP==XK~6vU`FVX2_5coj-NN-fvUNuTYHnSWrbgWROSLH(lF zPhJMMi`pJW_E1|gs`(N9+v$r5U+_V6Y;l{Qtk6q3_c#+IM)z*Ws96W?3#N_ZJCUvAguw6@J zB-%&%n_T#)B#P;UK#QKBD@T{+H)sw5*F8Lk?Zelm^HQn<`*hy{fb>ruYZGGCA40do zy&d;k`=|8EYdQ!Fgujz8pxcSTgSB}wqUNU^uGlAlKoJ0p1`-_$>Y6*v=y(S|__Kfh z4;NO}KM<4jo03TXO{v-4_G%m|xvW%)sEO51Ry=t?^oi&-A_}y`Z!-bZs7kI^$FLH6 z@15T?B5fnjSRod}Am(IAdv#g0i!mO*f~a7v2ZTK6AVAzPfYnI955D?rga8qQ37Vie zYD6X-Pt})D2mlabMHWVOak=Z0m02n6c6YT(CBjhz7c0aF2{OxNa5hsbP(EQY(Vbf1 zg#T;~iBJ?{;Qk?Eihi*R^DoucBdyG@Ub~YBMDB!+L$K&E7653@SYElQdanB9^F5v7 z%^uaPraH1r>J)_YN}dkz;=L;P_-q5lbO2>!5~uE|JRw9;DTlFy7~y|xQRv{Q%;2Yv z_VuFL&!2}>-%!crM*4284zGTNEA3k|2038;wy(!itdQs|5Bi(>PJ{RAz+)`HSbY$t z1{HdvWYQl}Sf`frkJW$fO6Sl-?;U2}wO}_*ISd=1BnvQeT{5s6q%pi6D$et+08ZZq z!;AAGeE?4|K%v=X?THCiK+ku5>0i{#zqWW3p(-qa6K5SDEJSUc1N!K@tJhu;PSvUP zzOz1PgVBB1S&aE%Y-k$Pxa2Yr2JxJ5b^9aAD#pPo%e-7^qKX(qn zF|@fTudn&r`dl|IJTTtvgDZrZY63y3t1B zuc>pmIuV$^T-%KQ^E-i6elaJ@J%@FFX~H$pqixeb?uDTb0F=LxKchPvY{xoBNQY2r z-0f?;(|>!6^PQ_c`N_ZYnKM0pSLP!i7ISA*8Yo(TlHQC5(`SO8=XcFApcEAz@67Mh|9NGB?`7kj=^mr4f_LCjIOKO-TExgiR z)M}1XojX2ZdF$X8UV-6s`=w>BS`tI8l@Bvv367pmYnYxDfCBjL8nl&q0jhTNi-U9p zA16YyM{iBU^}JL=eW(@!3$-DQ=LtqCtP!}LyNktQp zh&Qw(_NBth#2-xHlzYf~(w_Z8tb@uV4TMUo;>W1P3q9pNJZZ8tAnh*!kBKEFT&Z;j3RDRRfpaEAJlZ;g*ms;iXkE;dj*cbWkWN`NFf_2R^C;=L{!$ z#~3j#R~}L`s8(T2ICJ3P$04v({!4O?6?9gyrxWT zXu???XltzvfE!rqMH-VAsl4^|p9vp%f*4mAx`*)~yo>M$cyL^t;y)P#1W3cu?ST!4 z=H2#-CbNse(f3lgl#^*k``DL8^VrU30PqdX$nrC0ee)I5BC-D;HlZ=hso(>~e=$=+ z0Nknpqw`Y!)w2gb|2MKflnalpbE$M-m4yIV08oSg0?4eU2ml5{&RtMmnfa%)(eZZl zupGUtM@whN^Uogq?As!6pWM0kwcppIt~prhVVYb~QDcP_L;&N&yV+r?Kg1dISnnA(yZW_XjPlJ`qLvd|xbfZ`Cb zU6t#RPJ{l!s`wq9z`ME`Cql=;Zdt8XbyGYhVNqQ5Y5l&fK(`HW@L%q$g9}OC{Hxze z;IOYf|J>b8KSmYx#UA|ETf)E_>XE`TN4l4r)kf6Z=hYBF91m{?*Ex?h!N<(MlNX$h z1822!V_X;tz)^V2FwuDK9@?WFv}I4QBOGebI%7atndxo?n%t^<%{1xVJ=}4c-imNU zc_~M`zNuVp`{*y+8P4{QWod0#kOlM8blSUGCv2&DI9P(|d6~P5r$puKF_`7pyHeyzVcrzNznI0M(zV9)S=N-UyuJRV$6X zBUY}(_%ol?fk)U`WGFEBE4<%s)0xm6PdI$(_#>fGVM2|1qk~KTWn_tB_f$wp5XH0#Q6lB2j zcBZYfQTv0Gm{a6wno)K%4&z)JGyo#490Tn<4hkM*5I(~rFh=PM!5>6_I2+tBs1z^4 zEnouZ0-E_7e83}U0ypRculnm-^MjtF&p-K#Ki+=)$@f%BPVLn_E)|j#crSP`crhm5 zECe9oNL-d;CWk-~01Sr}9g2R7zz@-F768y0;W7iW=)L{;{`a@mZ{NJ}>X&}U6R!rp z87mEN-X7H06^Q{KM(=EGEXnVpz8K6Zdm7j=z++{Efo*l`q%LK~OhI?Ff-4OQfu$FT z5*OxSbwkXj2q^3r5C(@Z!UUPY<6C>R(#ed0pbbw7lE7WV+)gCoHUak#)VSS&_2X#z z0Wm8;FjOhvrNl9+r}}6|4E@}qP6yY-wA#6?!}8We$UfHH-9M}L)^q@q4z5~S+4O!= zR#BPY@&x9_$bi(lAKV6V0)6kxz_ydy0{Geqxa+T8S<5Q5TFDg?&gvSfqKi6&Z&_vP z=tO8AsFlZBYJap#jyc@W=NX9UQy=8YLDJ?w{qzg= z?RkWz(iVxWLNMGJXAG{lGkC6_7A{v03pjUlHo%g%Aw1ptd9}a##5gQ)IfBf<W)0LajsPwzq>m0_W-%JG6i_b6|)MG$vq__O;>oBjVZApMo7I7Ndx2sG)EvOvvestq!N-5` z?VGQD>DQLD%oKYZ5dvgQ01ar_HSAj}PoV`s3RAB})!CjeON{d@A!UFE^KWL~16)KC z%m%BKsQ8PRMuj^VNr4wBU^0$N7`6f2c->k6-U1*{f~b2m0kl^OM%_r?@l}!^(Dm?BQm0 zs4F50!Wy9w44_q7PY{Wu0l@~5sXND2zP$R{5CL@|T%?{K`UrpOr-%ET;H`bUTAgCm zf2Ql>dJ;dK4>y+@jh-Ls?Tgis1R~x7Q4n31^5x_y@D}538Xp^84y|lEU_9vI=4R}**qnW7#_En>aA;U^$EnrQ_ zk~hS~q0WTZ-+Nr`NK*t2j%BVzGeZFy@2g*mJN!j|65i2@zxQ_Ui6&7JV_|JLa|*o5 z!VLss5-p%%u+c`-m?_&}nxxMxY^p!P65qg})#zGiIIjLLvc4)OhA|%&-n4l<^e5&p zwlL&<_23iwHB23I6wE3@xFwxbKQE@AzGlW>cE*X%ByqQ!w(mueCjV`4Ik;{?BbzJy z)`YwEC8*d}A4IU5g{Mp0WP+f=LfsIKS+EGTLQ(ZY-*4ttN`gm%6`!>UH^b4^3_so> zG>L-|5t>L~B7DB5@r*!VQCrOx#ZUo9GyX3eZgnM2T2yv`z}P?7f2y{P17Kn5Mxm0x zgZZqQhn~9@*M&RPCZ+{X=2TopK|p=69zC+y9-Y)edFj43bD$e#EE;im`iQi_JUKaBsQ^v;?9SpWb} z*46;fXED=4pa=klLyR6v--Rp9LI5yE!Ww?(Z!Jn6b+(>-_Fbv0e~ZD*;UK(2>K_P! zsBwR{Qo+s(Vk}knTu;r*8-YR&?3# z*A)`TCQqIg0B{b#=N}ny!1x<6D-TO>`36Y~nBbcX%3uWKkWKbbxPUu^4F{8D0!QVw zAKwT8BE5)C4AP>_2-oA8bGYDdCQIa0I#|wdT#$;nq}2-xT0vU%Di zPs$;Vi;HVM-5uuGlUWi57#K>ho`4h78z3nY|MMf5sx30pZz7S^p+vBP&*^mDbzw)a-gs~@z> zVMarE#SEJI&4C0!Yuc-s%R34#Cm|Isz=X~k`;@oL2ynogwN6IB+Hhc{n7*v?(p-IY zRFv=5_A^5bT}n!)(t?2G5Yj2qNDG5>i1Z8%0s_(?ok~iIGzfyUlyrA@&wTSc@B6Or zoU`^?v!4HE-L>y~?|og@&eofN+ckU{AaLZ@y|I1p{fo4$MCN7QO(pD&M7@1X?-|xq zidz_n_}O<(mromYk^9 zvUg)qnj#>0`3+s9H$Y%a7JjY5hZd7 zmg{r1t+@Cw(c;4Udfn@4nGr1m=8NlvWAlx1ND;X)v*A6hfvPbeuM}q0?$=3*o6N% z*(vep#~A?(`0cdqM9jn`7DHfTXsX2uuIzM0&voXtT<1eVl9$@< zOL(32%H6*c>T0wkjDol6oJYutITUAwy4wY70qe*Jx0l>DnI@CH>tuIDt$ixx%VlCu zwFgyWOoITTwqUQ2oN3>Ss+q}$>Ln>|y-AfYvf!Z@a9P}yOCu`*doW>O{T0F<%YNjfrklSaPoZgR&{Y-V0%Uqd`Yl;B=_BlNCIcdl-zKmBlzrbOq$xP z+JkRI1!RwTqaPkAmLP1xHAGA02x?qBu?Ey1c|yJxF=#lqlb@+ZzzM<=2QB~NOQ8wK zr}Z7&ZzQ8c_jbdUVg!UF!c%n3h+zHxtWrz=N9ehUK`lx$x&7Tz_3uSpCj+d7h42GR^SroeqegG2{ni;`=%V5BAaXAVMPD zeGeb3CW`v!^F^jv*ypQhf76Z2qM3yv{SWz(9hiq7#z{j+)}9vcI)W;GlY_;mb`bBK zg8S@!!IC6 zdK5HRi=(YhFB=tMBlP?eOkwoXK(Ob4^GyRiOlC|xUB^=%L#7S-YdlOMlsLz3zW9w9 z)SVx0vbHAVOy21(>9gx6{=4mxO5i%_N zB>^@Sxr5D};OX#Mzuq1<&?}E*1Es#b2vSD}7uce7cn@T7c10R%mWf~Bo(|iLukUkY z2h1s5rj9dWGm=4-FejQqBb3^U>W^N0$A8QD1N#{xNU3CxL2$_sYxe^Q>7QL$ergK! z!pj%E!}C}&__zc!*o>C&(;nK1{9!2}^QX)g76?YOtjTwXv`B%x_tDXJESej5c_W<) zcZ?i!FxpQnL;*g}mS0hdFL_U^2c;lZYCz+>{91}+m;?6C8}XPRrn-k?74u~y_DCLt`@*-rEmnv>$({>A0Dz^BR;!hc*aTos7NUpGEMGD2(=YpA zNvzIpLbKe7V1oF|$9e;Y1#d<5ERCgXs$>;`bDYYLKU@SmHP+>+>OA*#FO%BI@7Wxu zq)vvObT1I!KibE3(5u{bAaD%E&2ttr2%e>q!#`t6vNDqC-E$lX>+7!(IiGrbPCkz6 zo@$uj)0Jwys)MF7R1Ot-KZ4)X(eL~jKEp{g-foeoQsm*x&$1GrLO16PDLEbJ>$DS- zuj^TdiEcelT=JmpQRwLuUtZ06L5TZMwl{vKmo>7=lsG+rDpV7MPWqYZTpcG36f(GHdp(qKpo#e>CH2EKn6)PkRh^g z);Z324nKOdjwq^|jJc7j4Q_aHGWpjYGi>KFG4j}O(|`01-3DpOmKl(Pv zb8Lpbhb5%s^eTLMv|c2bN9N(zixa^=nDMK<&!ugZI=j_y|G_~JS9#*(C#)m)bm9IP z*HgalEL^W$Uc5i37BlZ*D>uM8mUAII3;Av-{}b{p!LMt)gi-u6>b>kog_QI&!(-d^ ztX~OiRRyxUW>RC=h$SWN;| znt`OVlWrc|8@2y^yhV$f-^szr@plvZ=QGQ$3zB5d9QjoG4yY$9n{OP>HEHfebVSOI z$PJgjFnR2+PZ|tk$(j4dCz6Ps{1zoHR*#bjgN1g4DBTJX}<_DpYeF*sMTIS=eJRNRdKB% zfA@)9JEp1VQbPwi5i16IKhxOeQ=6bQC0oY~iYtn;v-l#!RurMD!zQ!~3xx774@mC3 zd{_XF5i~Un)YdU`X=93^j8IbRw5iBv!bpN!QY^DOoYX@!&?jL3gC!0ynDE zO)AfE;^p~IuoRZwmdC$ZEirk)#8@?g{&;*6V3V$d>sdn1+pLK$kFj#Eay?vd%%Nx7ju*d>$$D*FD$Kz8a^ApF}4 z&UyoxUHpJ1yboUq38aZVsZbZc;q2_c$Uz?r23-Ep9C7)(mNft?$S>61hIH~C#0_A3 zs7W^*_`g}5DHr)!8!|o&0W6@FHYb>SAg8^D$~N*FiN4jiDN)C?!H+QC<^@S6SOVGt z){CM~evZ?gc|u+$*#HC0`Z4SE`Au*U=eF|iJOxis+-m0V)AWmBr(N?TKdPK(Ks&eP z?-xIWW`Q%TT!rUk8+Jyq%7Mubw`8>1vxZ%&UYP#YKS*p)u=LZRXBG?duSAeAnkU@y z&e+XSE{-O-QeXr4gV&?uD(0ZltV}(wUKrw2by1pvZ{+) z=NN4MUZU^%Di5=QTcLCK^m%#z47L`HIXw#zKmRN7p67MJBCk9p z_!Aw=G}k>#1M@d;tOY?N&UVv5sCfJ0iIWn!|A&J}HGe}7)i{nk<0BnZSaL0k z`-Bag?R{{1VDx8p zQ#pq%|9F{3flV=JdW?I0&5SUujp1sa%# zLXG7kaYnHJ{x;H@#T-(8Aq+6VSRk1NF<%9JAI63xGC>F8tZ>GT&YUrFxY6PQ+nAQ? z`bQ7*iXK7JVFJ=QpF@_QPHJO63#BkMmmP4tNdqI?TvX0QNO^0t9SMm8)P6$zyYMP@@8UHB%fU?SvmlA$+L}bzeDOI!KXZso@4}>k%j!F|hJ7xA`y7F4ajg=SQ z-s18Lc`rS4R9#!cF3$Al@V>lkQC^WxO-Z|4D}40u#AC+%713t@;oaor_3a<;Ko69c zB|VmAl&QsmpyN{@TgPwMA7dx}_?*O#NVvWVaf#U4ocgjITeH$;(h#M4XtI-Xq{o0~ zz!9NSh)4WLuMt0u&P*$eM;v!gf6`fx_74n=BVlaE!FnsM2IKKsdXZ_QY(`ZY z8mc{GWgaP0-x(p|&zZ_1e^%F7cK6GZsaBEnYx8~S;!Qdxu&7%`ey8guNPJO_+JU24 zz+aGS@LLQwP8)@Pc0n{^H5q&%% zhHy_iMmmd|j$})W$izJw52e6VO2(-D-a`n2>}c-YE+d$U=&QAvN>wJSBa3@c|QJ63dwy_xCBfy+;K0q#!b`je|HxGun%MVmX)}w@v6x zZ|vVI2AoU#z+hZzvfjrpfJYT^pdm29{me+BE%lj)WV*4eMw~k$PSN%UIBm;A>oUTo!wUXLAPD>6A4?mJ~GQ!566fZG1xU zVbKu`NBo!!JJW?DfJLU}{?omU0_c}x*bZ8$IvV_*1@Z)W$pNVD_DIHjiAKjl%SwU%_g=7QjezzTkJXb_=tKsl}DOz3iP`m;`Y z_78Yz+^PN;)6%6S6e9D{(p3TjkMl*cN} zY&$5YB@_4={nWywd@Y>xQM_ zkJX4&pXiT4IG=*Ds%8WGT);X@BA_^xIB?)UMhOlifyC)%31&KWZ1Q2==u0M~2O%b( zc7&>q*tYi!F#Mzu@(3X3mHa+o!CV9r(x%1?tW^{Gx-B}EenP&#b^1ET;<~0dui$XE zVG&pNU1rR(JAa~@@LDJU&`g#hqsb|dB!GEwwD1r0o~h0Q169$n9FzEYTHazm(4vf5 z@eE!?Shkcm{^TTbf`5;G5xm?MAUMI24vvxZQJMU%eL!Fxc$4;B-BQqt@K3d)K_U+z~G$Y z0J-=2UPMe4k}CsKQ7y`R^WEg989rc8Cz3Z?ukyecgqB~rlEJj;DYWfvU0`AQ<^5%6 zm6+L_Z~WT@qYe3Xu69*v!o1lR`Z-h}7IxK^9*Qy#+YqN;gT6h!3;T@~eqa_+1F1QI zck>27$u6yR}KL+naESRky&2T&!KVyGjIf6Kj@@#Y{+Lr$nncNR|x7^5; z9JQ>wX$!?x@5?dFeZ9V&fQ&tlDJmUqK{T`SeECW4h{pWTJ-8Z}!7Q_O=P4S-m(y~2 ztR{zn^Zc_#+7f~Ufy0An#$s$~48=6v(MQg}k;}2Ix4%%amhHCRP}x0psbHEXt=pKX z>|~|x#->CbGIG4r}7z2sla$_B7g<{PwQd=9Bs^C zPq?>d&(im^Qrw~pV_$Z_x-jRdDARAd>c~z%#BL313*`%{TYSs=#dJcyymzp;oP9r9tM28G zgPY&_>sBw#nUB`^mog$dmm{p<%p6To5p+?1j{4g!9!vk{)MJ4$7=bPa+xwYMt?D1T z)K)(l*URZ!Q3_-(1h-~VZOdduhFP7{>$*D?TVm1t7{Y(q+NTsr9ldpaVT# zygG5#$7wR2w0TqMli?h+v9?iJ92-z^_KKZhZU0kPW_81y|32@#Zn`lkObHX-VLp#% z*gg!U3yKf}HO+p|)eRI{hXE#&4ifJKI#5S0hxzyWzfcaheZ{DgQCo)lb>u;Utx6~c zxLOPGDl>AXfMg!rBUA)K*Jmm66L37~M0vIi|5x9Qr&R}{6udCdI&oi@@_NeuYDjag zaaQMWAnxV^u|Qz~^qY?kMxolhV)&SBh}}Y zd70Xw8`9lO-DQ*6+e$FmCb_P(3UZxvJ*0(7E<0{N!=^^=Ltvje9QB-x)Unss^bA>g zco#-|Wo%&dF3qI%(UwXJ?G>hK#UZBZ*s$fEzpf}3%4g*f;=5sB2Jlyf)EI-C#Ov_8 z?@>6}?>i>OyD|82teJD*r)aOI$MYvoCYAqH*~^tL-Z8wNy4>k#J*W}+V^NoVzf*Ga z%M^8R*GdYy_4dPJ45((o>{U`>2;57=zMrhjdb$*V6+6{C_35~l2>v@i1+(~CXH{0`%w1&u8`i#?kjILT?O z9x%$dm~i~HrlkI-OSOV^x#p1K?kBmne{PnT=NGzTOm|@jrwk~{h^Y7>oEERuLx;J2DBtlKy5#kqDb&CPY(q_Q2CHy z$l%||Xbk1El@FyKM1?*G$^xV8xI9|Fu^V{NtUknQVba^v179)xB{>!cDi=MZ{hFir z6H|OKYpnq>JNz^cKNi5EW0ZPQjmk~#Ar~v&bJZ5tURi|KYGe_x{#X{s(f+TA3Lzo6 zPvS)5%G%UZ*uu=Dh(c+RBY(CD_aAEZ4|+WRB77X|N>DVGUdW$#Cv)P;o5XE;FG3iR z@842VbN&1FX8cJVOanG-;qkZ=R1c|=1Ok?JXgc_)muPzs$q!R;97d~wQICPe)W z1DyyMIK_y(=J0c1svrSMM&E*<6qxdQ>+hfZD@Jz-d=X;UjFQg2X`7i}toT7q+HqwI zLM-wleRYdV_W?5C)iz`M2%rQZNG!gxSa}Vf5x6a6)ZfN(JHl`)`KCyW$=&hrljPsU z@``!Cd>?KefQ6iivY=jXj1ub4#%M?NB_E5Qaw@vOkrwwP3(8Dzy{#P;o|r-DsS}6{_;wwwPFkMfRvyxe2%>UH8Ho#H!)!+0HJ{?u z)IDoyXO*7AT~+jW{pZ0^!_aw9&)it#{lX=mjYk}lV+h7)x}_IvQx;mirAye&UBDmf zQaW=~AP;{(K|uFAMA;b&Fw$gtewmkP?!<(E9VwB3fCjJ&HsBm_HXdosW%I9jGItrx z_1ZFNj|ku8)RSRW;ANyLcuALeFdZMFMSyDFQ#{=%QEH&*Y!OQqI#MzMv)UFEynOedoaB+#DKq#^G#z=+zN#f#tzd<-5)SK9bsF6t`5z2=6`=d}=vnw`4b zZ^{`mA5tUtzi5eIEs4?+UkkA}zHiL5x-A&H1iqQDM}_cuf=&Oz5`M0^kF;srrjU06 zsR1D%Jm(GRFQ_rtO2^q=Ax^^Fv>fP zq>2J~qAxo@D{`p&=!YdPA0({BnEybQqRx7%S0TD-&N7x163VZQug)He^X9v>maK&#Rz{f2ZWr<=xvP?qjs9UsHYu<;9=a%9GIh|L4S$6}=EWHV$m30@nV*#F~ufTQ7B! zAWp6EOGbGam>?w|QhH|iz*PGc@01a^oi)F&YgW`tBtcz`|2pG+$crczb|&`fwIZ_5 zBw+r2YDs~|Y^!~|@Wl83=1-x4?QEgYJntavo&3l>9Ks|&-&A<~B99T5AhH?#2@{rf zT@n^Dq#E_JT(DYJ#2Jj-2&RzucEd-hhZ3ao-d(^c;>i`rrEW3zP|ny0(#5AQ;{kdJ z{X4$8N_{X!Bk^YgHIf)K&H#;Ov_d9SlsL@)W?pUx{P#=plyGTXOjGh4O~)k+6e5f$ z0xe&ZVUbKVhLAA6C6D&!KT+fTyuWlxCloIP4h-AA#0<&iC{AsrX&8TZ^#0_gF7$9s zPg83XlwL^ipX*ah5Gap>wLVr9ni#vyczwh^2le@C==BY5bMde>cWkECP(`!PxcgC^ z+vo>$q8lc2Z-k$i#H1aQM=dAn;$Y z0OlS#qu=-q0q8HTjYm+%TA)^1m24)sWq)UKmmyVKu{qWELmo+u<=) z!j74Z6BpmakrT)KtK*|UE_#e}a-E+!(!Dm7xeCVBT)ml9MI)8fkMJ%DzPqqE{r9;e z0!Jco>DB9kC02&d^zx!Vwd^l&7u9Z+rymnH2sU>^ZlDq1?n%hT#{{n@@_IR9!J>wpm7H3}9jK9@@nbnR z<#=;O@$RR&x*afvd;9xhW%}JWEmb9QF9jxwwCjFHcS#GHRe@v!;v1BbbqlUZLFNjMC{pMKQlsu zQK5MKoP)&0`K95>?dJ!(mK6CAfY+ue=_LmQ@$>o8x;aBZi_X0Fy?)HNJ!|aGsauq{ zDi${#1Y)V!9d-M<IZXkdF%?;6I;oVER`O>R7ZyhXG(a#aPB3wXev{|!TldQM zG3cQzx&C_`P{vUanNjGW)|s$Bi!To*Y*fi^xxfZV{(3^w_6-d;teTaeWV;N|S)j*r z3@<9yN4-E^FG*MIoLXxBfeeB{*RtZWawn_?A%M^O=fZKYyehc<0J4 z1W*&p@=A4)kP!eq{#-D|5rSEuEo}&mV{em~77+J&>QNL!lH=)a_gnKnecN>iAcTMR zQI)docwX^z%_R++nMl*-ihK}D{msVWTjaO6PurC_bwL|J3M{jmFfutwwEXL86|SpT8>%d#j#f&+BzpC8i=$G$D&%}R%> z#*ZL*GAW5|LlX@QFw^lw-9HpQ%2NA=g_8Pgf75+O>8JUJeSx0PyncD6TUQ(bXhV7e zlu8}(aYX?Qh)UYL`; z6h%Ic;^6yT?3bzJ#^Ul{zG9OU%zwnIf7hevCg_ME`2tv|7L{rp4&6P-SVZ?P?vl0R(zw}-pAT=VB1A}g3xF8U&piS4N3Ap@VW#YQM{IVJYl(%! zSzZ#Pp*GOrYhWMu>$BW$`QqHCruDWFwd|Vgz34RMzZ=>~KRrD-VHLyr z#9l%@CX96ZZoYME$0&G}rg!o-Ag^@CJ=w@3d80i2ZQF3d#K*ig zDBsj<$8j-4xXx&dGkI17kDf!KY+%RJ#zdNO+{B{m~+06b>B)s0e7qbOX%qwjG*@&q6x%xf+9`Td=(!G}HtG5Lx}!s?DgRJMZ$XMeqAv zv^wZ5N=Q3(UO(z7Xe8CBTk+|@TzvYGxpV(MF@~Q;`pSnKO`QIGx|73cP zo-#3}vu-8G_g+mHeUIa_R^K@c$KE*FT@ARE?*`YA{ zUMzZK?wG|*QzQLKGx0=#;GeLF;OVl)W>ERqYgZd2alnx+>W+c8tPEa-7Aq3b*Q-Pg z4t1V57Q`E|Q@o6tScz`04k5$VDCv(jk&Rc#>e!2q7hprfutVB6_8Y#UlbeDshqa+c zCC+qv`>+#(GQCM>U3BgIYnH@aqf>ykj>`UGZ>9^_do$HDid|}Q&4LMP+mR4`nLIf5 z7IPd!ZiqFnKIB?4V(A`3Md=HtVPk)J_;CXix;z*9QtAxT83c2B!pM$IIs2G>)P=*W z?8dd^>2pxbe{3>zj{)=6OtU+i6;{}TfOQ6jYBD{B=6$L|1pwe-6`}G=y}esIG|K;! z1i#L>b`HT|k3F;UQS(^L9S$TrL>ET*BvWVEU_Y}*d@wl0YwMAS*-__CJP<7Rn*vPnD^ zwZAaAdidzT@jeL%1`s$|1bE^cJZP!u|AicWe1Wst2S&8yuXYvRipQcUdPm!HbMJ-G z-HvB>GcE&)-J^&89R2(-ye+%b7o4qMI(6_$Rdp&_!fi)^vGpON-kI~l?aR8h+uE5~ z??*h!P<9q7J30W6{rYipqPKdNAn+q5kSyVQA~W-ugFTNVS%G3q(?kH(steur-Ua9Y zIK3)WmHS`DAP{@OmFGf_u}3*SFVVu8Sc}X@gNRRbeSy0rxR?KmGL|}TGds_IdT=ORp7SgU-KYR@ zrn~AS*@CLflDxWZi@(n`@ygnn9veeL{&RkE+F<{4cbVrj;{T2s!a5z6`!zdSQ*LWG zoF}$%hbGM2UTD8Z^=0~+-TEtY77{Q{xH6S_GnGYOfu}nL*nU1P*wS6S!~N8J(o^Qe z_?GH5$#;@^>P>EHhb|?g+1-k?US-Q=gG6KM^IzFsVi0UQmtt~RM3Xbh=lqzsl_!#U zFqIwL0yDvN8t47~h48_9_+?rHc;w?btGvBH+OcT!%9#KvJvmXH43Cq*4ri6`y{3d) z$#LRIzoqs%^})5esL2*N6C0beoZ_~E=BWOG>c>OQ_2U10ym&CiZAIsZuTV0x>M)P6 zBdhgCl#bPN&Iol5mHcop7HzI@^~a3tk{D3*4Xlh2cZqBsSzAvkT!c?6CGSSiQUtQk zWyZRnxD~Ou22bt|@aT<%e7?8N4b;4+bo`rFSk1T^6^qLIC5iE@HsFhD0GKFoI7g}N z`Q3z{+}KyzEi6g3Qga`Dq#XxOn=&lerN`Jl_J?9N{DczTe1Da2`1p!ka`?<)JMc0H zCz!I|k6Jmwg_T!TAVKaQWNp_6q8FQJi4U@3ZXidn2iuiDGi(3XWm{Wa!0kqie%qNi zBPI(kl~PN1i?se60s?@X{C zSO(=4BY|tnLnzlxJN(5k*CDcQCxyXZk>~5#ZaIJ_@_rm*^%<_ae<%^-shaR{HW33W zZ4Y`x0rq&HzbI=&*AkmF6dwY14|uPA_WpW3m*1y0hLMEfV0@Gr{LtXt_+p78s2!74 z1TXFL5Z zz9`fZ(Y^!~vMfPf{?QzXA(pyUUBrWCJ>%iKOm9tTQ3T?42&oY20=PvoIoP5(yWi64 zi`DZoH@xK*`|n5Z8IZdar?CKZr>TyS5g%CUJ23=WVmM+g*@OQ@nIX@_<0Tw&Gb4_= z;76uir_>3%6H6hZoEQDhRom)on*;DMbKh9A;Hl!t5GVnFq``&%2dq-G6 zvP#8DGVoOG9V28LHoY*hBQ+pJe6g>X{fdiLq;%q3T$Wu(pK4EW2G6B;EvMeVv~3x~ zto5qKl76b^=TzOFsDex<30hwr3+HE?B23yqd0f3zOssqse* zAAkG+Mi7T17`OXNgglA;MI5uI;8_TWh*9MXs5)I&(}aMJ+^N?0<32{&|JUn^To&qq znW&IzJyz-&n!Mg`>a~=xP{)8wJ9eJ6*zsP|8`HTNiWU-jdU4|$Hv=D0QEWYM9wzva z1|M}8(Jjg}SR;MrQ6}D`)+tB16DYf4z7<+H@mBK|nM;^UI_tr5UgRLIDJ|%z?7i`v zQUyo#)Lg`$PrSATt@bI@yt{{Kz3Jag=)n`Fmm|sl{oC3L-x0lqO}=!)msDdzGZnXh zsfvvDW%%*b>jexf;K=&OhLs_*|K)(kzqq4Ec{-zw=A<BdcJ1gkN(L&kJqxWZ08|{>m z6nI3R%6=W zLs7Sou=Ab$=9p za^9kv;D>-}_Ae!@uTai&{%t|nbMpZv`|dk!tE8<0iSayG#0RLVbYp{YG5TTIx|~ZB zbW)r@Mzh7Bhdc(B%)q1hDJZ!gyBhVCW~>q%yUS3^X+)J5H=k^}rQ_?$l0rEoKP!EA z)^+${!*-&r=a7L@}ocNx$91&V< zc6WCBlJAD1cMw9<$JE;a(fZRF-TR=wBxu||5V5B}ox`3z7zB0N&LIgU!Bjk>8;tT! z9;YlqRW+Fn((x*-y%gY)wQVglx!G|hRv{yS1aa|`=Fn=M6>-y+7U1*{ajlQA`i*6o!0<=i9v^m6 zU28}#Uv?w-g_NiRP>Od>y@kdd1Ylr+@KRjpg;BJAf5%uuSk`;_b<_W&GGgKif&T3M z>D+G$q!BmI<2bp^T zhn!Y+W#*3F5Z?!1hZmi&%*0>z#E8h>w&cs9=zSNZg_vq{B?xg)-<^IHv8_&m1dtS9 zZe6+-%8itu;|2=Ax^1WUCbfct_smRql6{at|0+fpJ7+YTvV6nvocLyPpXG-BCbM4Z z4+b$C?Ass>2>LHfFExOc4+aCho-|bx5jRh4EdTXV&>GEcm++_xND!w*yFar;fA8JC zVg?qTGvyN{fjZ|oB@H@bNfTUVj`k0dX)~Sg@dK?MtG>{ov@do{2b`{s>1k%soBLUihYzOm%NW1r?p^CCJlK zI3p3#_Fu|QMM(;vq|P*jC|P4TAKkNVJhh8k>G^QF(?d7r=kO4Aaj%+CtB(Iz`uFJ; zl2}dElOnl!N4oOzbC$ONY7(2|RC?H3D{uV^F~1TjQAmyVJ+js9Q0-Ug%7I)YyZRAD zUDk}s{_mhP5S2d;y~;O(?M)ey_5@;JE=h6`h|@>8+k{p@M1Irb6hug=SMGOZI@A4(!;vb#+y ztpvBh?-hd(EUEB<3d_3~7?uc#v?xQ_V)wXsVrGL&_FyTTU%>m3fsq4(>yFDAp8o^2 zDxD&Kl}kHbO<5-tLug9LSHZyjgBl?RdbqhpPEI!qhJ?3qJhTUeU<2uZnsp*m2)J2? z4t*(A3q$dU9r7487Cq;F-WhAfg@SmWxr~H_i#+=y{W_X5EJt`;l^rf7E1IKAU$f9AK{bV1xi?cpasi}@NebIecVA|V=XI!}C)dexzJ zo8k8|J&|=Rq~q?;=R6Co(sWDt&58{7=GWV`ywaR6XO8Ns)B2N6A0}^~2_i?rVFW*O z;b~DSIY8WQgiWQ_c- za+t$!m(5>2VyczpBr;91uyy?0%%~v$HD=fA(B6jfbsdz&-7FW1g8Iurb_ z%A0`)Kf&L)k3HhF7UJ)fQsH7n*~XWx)!)2t)t4ipp|#FRkUYhc>0mW?x8~WthWW}M z$-o(i?D+(30pPZE!`PPj)_F5>qQU&kicf0i3PkH=&Hk7@4~@cqh2PILf$m?Cib-~r zO7bIImj8z`E6a}l!u9zMQnR(RPi98p1YPM{X(HY)rYg|v@6WuooL8`89uFsJJ1HZT zrc&-Td(*&(W)|kN2%L~%$C)|cjAZz{0YL*29>23a`PzWV$xUuZCc73n5)GHl`x#i% zfE_f?nK$>T$?SWRGxhmPhYV(t-Z;*G!>XtPpfL`CRYgZ5kHT^-m8Xv%F?BuBH{6R# z(z&OZ68-aaTdRWU8+HO?b**SDN}?X1MAhgq}|e1B6t|pF`oCi^v7?v5%+9!RSom zyQ0KU&?JCJp7I8a28{K7^p|Exsf`S@&AG%@b16?SsRa}dbDgUQwd@ekeKj-AkwtS| zE@bZ+_FrR4JnHq@JSVPz-_%H>zA%sb=~iD>-fa?`WT<^eBl~AT>CN|vl2ekrPhEt8 z*tSLF&0#>5>uMGjz!(N)e?XsPi+Q+G!Dj7MIpJGoi|Hq$|bVFH9lJ7vw-SP#MMcN%FbUA7mQ|3@G2i zN})ntVU8ZUb`*z*f(!iL$I$1;T!HaF2`LATmtR}zTPLD z3{%!h-&6nnP@aMUf5M&yhub-1O`hvCi8}bD<#K?}@R{}q2!lf#B)i%?g9DfxKFWPh zHm(lOnA6T5aGFFsd}l2KExQQUV#LMx1lOQVXQT3~_y2$#%R>2&+Io|6-pR{#?A4qQ zpJr>^4NoC?c%5I3cskt68v{4--K=9qulT5c_gf%}s822FN>P#5B6N#}!oh9dtF>P@ zUH@>-G%_(2@t=;@GUpwI7b!E*xX*z8-g%!d?4eQuE8gkIXn#nK4&|ANo#dOwD3!NGQdxNlyTels&p;P_xwF-7iAbZsEfK}xWDwH5M;qL;vSEFoCphp+6H_% z?MZ>(9&#%`g%t!+0K=bOh3)8C#xaWuRbiHlv-3LzS_eUGjAYIf707NUI#$w`f3qH6 zjWOUy?%5Id1P$$4RU@V+KmtLV@l;+G-ne)Hdh>B42<3h?H4ICexqy9(4bv4}m9o(Z}V_*}jx>~O7zaV5lHkWA%SAwKyqW&~8m&W*}EmJy+> zip(1Lq-9PdLQ=3e&N4e;<`8>mev)9U@bzSPK)R}(inlTD=X-CMh70qn+`vCzsxxge@J}Y+zSqmj`NG_B5sxJv z$a#I|z&6E~k{pme)0RFZ_P3(hEJ2h0ADp6SEuXN7nlDNZw4sX(0q@wYaUH2@^CCP_ zvdat6P>x8{%Ueax94rY;@RU=FcDWfGE#1~=iiQ_O&dH65tY`-yKiJ6tGuk~K9L^my zp9IDlOwXl1Cc!ZQiIrUBMcZu~G6n8a5ZtPm)?N7S*-{Bfw=>zlQv96jPCdq{zkN}4 zQYP6TfM63fxrky2aJFzgClqr%NVRG7fX$Bz;9xxY;PD$nratZk?eXK>`aV;;M|CZ{ zEiE=7bz%V?KMjH0AKk1qRkfCVN8jSy!~39zEVy9Nm^wKxmh{z~=~c6>v#EwW$)_ae z6!v`6%u{!up66dY9QDxh^Mtq{WXmOj33_C`Dat5NzC@nLa1P*u-b&vpJvc_yT-r#c zbMIQ}jhjqNOq|ZypYJ@|b8n8FwmpjX11z|!ctEm{ent`#;DiSr$mMi}0f}733yg~n z{fPzPhMMDZS9NSHxi^PDJ3sI)JJ_l4Uh-cUxY{Y*u{gghH}ciBl^V@VN{c6rKb9gj zQ}{`%bG3BeC~9u~5ZNGk?W~7Ma;OE==7Zckfa+RB6N0RDoWxs+ua<~QVb5Uqp2r6# z%_wf^AIHT{uwN!lIFdFCbrAM0KseIN7usMe@H_9C0kZz&U#l@Wxi1O*?e=jb7g zi0jB^=IERAD@I?r?U*^0W^8;uAL?hF2GTj@PHsV-RJ5y%rfrDYs=H>Hf70wB;QUMYF6twne1J_~ z2!AO)YOwo!ZWDgQcRw0gSc)1tRb1@3%roRJIAAc6_6v@cN4~u>2pbj(j@ocS$=o(S ze-`rtLoc)GOX#blq*$?-V-tW~ifGWKVk7^ffBI6W@bd(=ip!OJ$!9kA(9OAi9jxu(?H})Uii)C?|fsp$)u^|;N?@u7iui%)xdl#d1cIOOx>n> zZbGQ0U+~7oQ>!B!Hej2s(9}A;K84}^gP`Mu7D(bK@cdzp#{1rQ@G*Cn{hCt|rckIc zg`3~*%w}K2Zs6-%x<~c)*-YlE6^*~!mMa883@g)dLEa)>3u3gNzqUpHZlA>eXETt+ zAuvBCw|6{Q?zmZ5QkVM(OD*oxaeuw5XBb0R>Mvqq@eSVN{DYyIcFs1l=-u!$rf2C(ny7mbo^BXr$MRh4)jRBYgxWgCGcG1fFzP>1G{kv3Lp=>0wow*$bMqHx= zCRnDx5og6b=LynxrDpdhhbR%0dvlO5#VLw~wB~}YSO`K2&0%8tG5rdojCY)D_|BV!UarI7E7Gn7%ISN1-)*S!J~5NC96 z#8__7aW3vIdS^6SmDUFf$CReac1eC8OgKQjSQO{?J8E3U=zNHU9V%4x+Ui(DQd8^%Bu_gGL=)0WW z9O+9I^5L$arVWX+GH;93ium)TcRqHH3KxIpPpdo=bw^z!R%`#7C+lcFuu)_ySF!38rY0o*?w!=biIF)r6ZCjESp(uH^AU@mo4F6 z__+;fn-_@uN_}DGi(DMPioGiTMKMywqxVC_B?(mR+{u|-fl{8Fhyu+6cAuz6asN#)$3=?BZ=FPtEgk? z#ravA1s-Eit2-@pwmRQd27UU?DHuZ28?Fo8$9qtFeS%ZNyjhJG3EhSGCk8Kge%ss(vG|BDzw2PTJSL~{y&k=O>0K4vI*IMnCs2hHlBXsw%^2X z3X@z+#(SC~0zNHH1E#!W=7g7~8KWXxbYdk7*&jMu+|Uh3Z4*iW9i|Mv>n840`Wo-6nMFCd#>j zrjB=3it{#Y7O@%MHQAtJ2@7)~PBaZaLs-;|f8}nbJilo~JNv7&2F+DChtCQ1!#LfH z`eUds_O=p-JI&Yg`^7cu+1GYpS?@hg@iZLIX%=<5uW7nZ7h#TipAR-xFi;}|~@jQ6Z zdgWvF?z%1De5GU2=VD1;I+m`XkrI(l4GqyUP%X; zgxQ()y;z{X)0wr{I1Dyf@J#uZz-q%kVF&{)cFI7t7h0@NrDaRT z6GAr`VFCH;A9ux=5uL6s^WWFIra7^hk%nGwvE2LKS3hP*eeb%ZkqvMo_`bi??{EVl z8yE5KO1Hu9Es_o$?!7)C{^uYs!5~6O-7*H1`ozW=a>RK>euV)?0FNU^Jq zIr_I>U&mubm49eGtUJg_e0;T0-un(yF-I_L-kh$oMv|}b zsMUUkd}YhkbChDzbtW}R{xCcrS`3gH18fEY211-O`oeZR+6=LSiLi+B{+AP-TE5@T=cr0Gx~n-Z8QExwt(eF^JXA^c-k4l9E?8fPs%|SR?@) zArT~yPtE&FRVC*=);Qv$39eskb1!Q3xLZkr&4fiC(LY>HpQDzx@rK;2+iBTqoH4=YsoJ@@krdB-zsd!N)!H z^g1L~{J<}T?D6Wh*1tM}nYIYV&(`FTu+t!ja^lr6iBx{1gL0YY56;FJ$+Kq1siaHq zoXW$>fCclqq0Q%B3%Cbp60aSsWYvFjGdi>t83n5)iTT*ue$P1j3oQMnu+^xT`0VAk z&Os238sUS8|7fm}@dfw? zTDtSS(7th2tx&b7Zkd9+jcjhWd!ku$Du0~fjr1)Iy+2>ObIv6U;C*-)Sja#spdtp~ zbSQ@2V-qEC3cTVgvX#zAp>6}0OD(G7y>FH*wZ*C8*AGa%fv8JYchs+0r+vx#*Z^$f zW36qHI6;Z0)ONEFR_sYnvJ+uCgjxA+^D$m;p|$~s*-oMI2L!j1+kUJyiN0dF595&P zJNOc?B`ev2De^P2X^fIY!Uf2@J zukLpBR_|ds=iSq-+>Mg&AE{={_9hiRKMyJ%yr}yD2@Q}LpDL-jxkG%q3~gA}%gJ|p zTUV07gA?7rYz9;G8uOP{i8wCX**WVN}<=4ge=fQsA2R#YE`OW87a5> zH%eTKC+Q_2pU(AvUBZt)!Ag^ivtkeZ+5y9<)zY%k_`o`dV zg@M^!xVu0w2PH06pO``#n?sG<@6O1W2x?V5`JNx3?2^r8I2OZJcXrW2iXtI%wP9ae z4x=8?Jzkr96$1+Q0g8p3lB`a*Sfr9WSn`}nB67@pG0aZ! zd`4`HJ%M;CzQsn(1mOr!E~Gc~jHu6*l^#d}9_%u&MIDwhMwC%32|P|}7;GNS|J)=v zc<+5IWS3;pY&FDO>hz%A^9>Wm$(`EZ#Z~Rf$EHaf`{B`d0@uGiOBdw+`5M(Pc9N4H ztjvk0S6j{RlSa&JL&ASPTXY#bV36JTBERl4^J@XOW)?KWb~eC{3w+p3TEsTyE``ZC zDSxlro~F5Z%OLqh-H>zOl}Q_GT1P79t3QeoS1rH$3h_M_DY?%Ut7 z6{fn)lpyyASj|M)v|XD>Jj2gb(vLU@vzGId0pkxAHn&*)C7vik5?G!EgOOAp9c_r2 z&Uro+Y+M{K$uER=g>j9mGA(d$0F|Yh4kTvC7Y0a#mG3GD{hYpOk#^oyv)bDN835gS zt^D=w2e38^S&kDMG?T8H?P$i=Tq-E%wy3MGva*}kJHi+uf|3%X&0A-6gjT8%;k^Y? zb%3B@wiECK$?U0eG%a-)y)(NddfXG8suqQj3iwznG#9MEUe>AmFWk$Qb%S;N(J+=1KGMs_P}y1^Yo zIrq&s0H03sAygeEhkeH0U9X*H2hzBWx*Me*Od{TwIp}l6&gh?RYV0X~-W}VoGR3S~ zL!Bkwzl}4hXwqNodAG1k-(*B6#lg!|iIZBpED$$!7WW#5+b3{Z@&gmkDUg)ksEqCA z1vh2O`3hMI02_I&XGaUm@UM*WW>qd*sg0)=#S$k^Uu1j;85MW>Mq ztghc3^Z;$4iW4J+3%QW;UG}4RhrU{I?7<;=5=v(X%I^}2CWa6{rmj!T7KT2XH5-s2 zzmW!Dm()>@32+{3`eLZEt(X!ASR+6Pzcyl|y+5b7|1n{Yg=mShh~m&@M&{y151RR< zTRMT~uQ@}?{?cFz%8sU>eqjsZNo{-t43)d_O)3ye{Bn8!mo(OjV+;$*>zG-@^zN9%^$CD zbFme@&63;W&0fV47@Cv(Vj%WmsEcxWmHgxi0Z&S#Ap^Mo`s~MdiFDlG2FkB_oUiSr zGwHcytZG`w5KXxSSM!!^S6!nCfQeh?GQZcbimbbnywf%3YH9ELS?41!cQMgD#+-yQ zzOu*bd{b`H;ckFP6%i1c-=3h&2_;K;6*wi+^?Dc<%pJ1gW+~C2 z#YD3AOwawBB2A&}pY!*h2*oZqY{Wr5)#~GZ&#tSmH+G-aD z&mhBFdMCV_Vy=2P%O0pNEbZm#DAVfrdGc4PI$BB`MKlj?B+elyRRPAS_|rKb=8tZM*iSkjXDNt9X#+Wg}( z#)%OElyZ0divqU7e@G{jKoVq$3s>FaR9<{g`^o-noBcYC+M7pY)iuU%2hDE^S!>N8 z6>!4RGbU*DcCBeG<(_0?tdn;Z+m{&bKA3zlNg+kdj$^UBwJU66>XpZfz!C$(dopUY zDs^naZ+k+*f5`Wd-qvDh=+7xy`Y17Uqz|yQGOIc=o3G0vSGaAQD?w6e+7vvj+;yWe zY9Op>5<}5+{Oqqtl1i%I?J|JfZD8nxuqCTs)jA9{8|Nw*(&X+ z9+tj7U0hP|15&>ej~w^r)aSA)#f5ejP$h4lB&VNmr9kU8X6Iwo-RjG?|+51l6l=Z!=dj8`HI(TU}rT)@+d>vrhOb|tqDLDH>-S}HU?>F2acL6ew}t6hVP|o^Tzx|DYfG7L$n6JBo!?;i|87~n5Mcmr3)7mCX!Pdv!TK%w z8y}3=+*46d=U$^h`M#=Ug4Av#fK*gffbj~0b44GMoppSMY6#8(=!c@))+%z=43Nr7 zEjvRgm-X@^=6^~2t;z(^;zJw#A;fgb=F5S?}kD-GBdRMQW zJwPhYa~>9ai9b}rNr>FuWcB%i%yXQhj=FUJ=5;mu7|Zgtz=vv6umXqIK$OK!Z%NW! z#os58fYU(l*X0=}zgCMUAssgNRBO2Bg2q*EzsH{A{ToO5IttTu1z*WC;91{EIk84V zQLWyy3!JB#Y~>;V{|Pze?Y_%1-a^pQPG*mBH1(lvb~};o3SaD@$j+nq>$=7aU!{~e!q(a4POx9YcR4zciN_bYUPj7oe5Y!!Ir}xHpDP@`{bTqNd0oK(rfb2aj)2OejujITwmyu>pE2hT5%=p2Y1cjtAWxxtv?)tQQwfC&@ zGzSbH3OcbK2fLdN1b+JTOkKtPW3^t(J?H4#+D-i@S1%!k;RnViR92h!omc4QR=uXd!=>9nEBIF zTO=f$gK$A=&Z&7UX6Qn1771}g?};DcqjK50l+Ww^H@^stNCwPsF**sXg`9NPQ%a$D z_{u`e>~bf?K_n3K^kpAl_Hva$$M^AzEt$^Ippbsjl;;a2ceJ`0&trbC<16u(JeDhx^ZOp>5a^R?H0Svsu6 zC@D()ZJj|waXz*t^ekh9Q^wWJ}sg()Id3-02kKXikv!wq32@}pWJ8MF@~yIvx?o1pD=e+esS45 zXmkIXTfC5>IriKA)V8Fx(@3lC*yefBiOUDcPpMIm#8Ae~94)B9Q(mpg#`obof$U#5 zqMy&{4T+{5O@=;NvyT2~p8<2kXv&#MzK79fl--+3&VJe*3->5yiDR0`I#u({N~AWW zQi5a>1c2H7&5s<=a!a#0Bc!DO=ESF4-+Q6KiW}3eGsd2sIG6q9a*Z{yfpiHuRC5-MM)H_V?%F>V)7_69RP=#;J##Gi0uE zt{WA(>!&X*$iVpUAq_hLuJzfvaL7rs6oHCuuJvVdrt|^EFA3}bk%|v)J1G9u3t%}) z-s6RrdHz@CL6AITNa7K9-!k~u!^>t7qD{%UWtS-_&NJ7S?!*Cl^berr)|l%qZPX+0codGY#uhezo=+<(_Jfrr4Fh7{CRS4!wr%)neXh`Om>gQM3Vp|6DH5fz1AN2 zBsU+^HI~uWE1BU{4@(!Sp8?UT10r35w-2`_gyZh!rEm@4QK2%xWf5@Z1bCNrSePJ_ z75s(|BMol9+StB{ zmBQH6e=w#m%i40U!iC-mDSeu{-s8JxDBl0wqajhosFK9^=4XGX;l!1GDtnFpX8^Ob zYSeAM0jxd1m+aFvagtJOg$G*!yVC2o}J%$*Ipoq(7K9f`f0JKFBfSmCZfOz5b#Gm^@BWi_A`_U_5A;Z=#4 z7eem~L6*6;?0qrQQJ-&>7(JGzK2lRcx#6UvZ=G{<)y?|t{35vvdqrIlz+mnZ#!mii z!-#MgW)btM>ITMKvC;5J{v1`kY1V0H9L#=H`8@0{xHbHXGu%9)=F0`|B#|Q{**!A- z>p&gq7!61tK!XVNKI|PuW@ggi+aeKy6-9mGZ$;Gl^0o_!XSF8#O(M*TCl~_lTSYhi zhd`wJ%@9_6z`<*$P;iHg$yxnL{ch@Tx4{)<2gIe>09p_db@r zsbvufQSy498>gH6rbK+b^p!>`{9Bj?aQGPQu3jC%7Dw<oeRAk>;}m_?VI_$`t|TwH`w1GRfVGnNVtK8;(BAm-P@8sb*B?R z!Muz=+j&bs)ygWO44LEr#%nGm#8O3|8Fd0fvf*vsVI3w*Z$O_&$NC+yBfZPkV3X#o z;OD=-l(CoIpigD7UEpa7l=luDm>!lC|1X#p!n1$+Y2lqv!0eQC#?FzkW~q~;7uTeT zgCpzc)EGs#s;ik~c^k{xV*bWzGAC|(XXqW)$FnW7wI>DVh#6BEs&+z1iz`h1Ugy}J ze%nOU#Wwq6p<^X-8)a;)_-5Jthxd=%J2XmI`8-%Pi4vAJYOPan*uJ;(ZgbZ~b4Otm zRtYyfyC?$Oe=yD)Q7?6YtW|5d`aqB0)5JzLYPiz7jPd=DH8|+j0(DILZ-I(h)%Kvi zucr1%rb*m;LLRnZW<^3!TxN+5q_rkjP(|EDAOs`=SV$gg{d{nA9LgMBiSI$gz}ecJ zyfM7hd__x2aT6r?wc74l9y4uq@9@>{76;EZIq@GcFhdCoH?N(?CLssa;D?qD4txwn zsSh5p=xY*t3>F74jw>8=mHWi)49tIhZvA+nZbTQ^YQkpemM)GpuDQFz* zI6t|yMuAw?fSS^v%383ra+ml215d!CHMz;XMZ%r3|+lUTmEd9xPIQbYB}C zyBz8Wy;Q;t=;N-IvFOiMQdOwdGJC0m7{6bn9B#*&h~nb4m;Sg|!;HS|4r{3kzHLXe zyIY&f(<29d)r8G0|Go;jyK06u&QydgznTYBB4xUIHOIMqx9(V42dtu1fG|u5t{2wesenN7k>5VXYkf<+@yu%{o(H+*MNA+ z6?mj(d<1mUxcesOj7{fUOc@of@%ZReHJ=OFeXNF91Scn{P)%MV0uZ6@iGXBkXqBf2 z7YpKc@Ek0(>6b+j1zL1%y=(Y;hThL-!UPjf3pAqQ^aSk!X0I$JDdh@jxMv!W1P|)s z6CQh6tbLj9CEzkwM*M14ehI~y_#DkNijWagFLVyUE13QhnZqjSnaB6D?azXK9i8GE zB_v%S??mr-89Y4Qmz5>32#{z9@(Sk?mlhi#1Gi~~MnY_+{wedO1tfbpQP`d^&g1=j$cOO!=F4F+6u6c75tGQNa8Z39!uPnp zyZS2j6GcoUM$ii%6C@vT@0C0dA++Y?R684KcJKPsWV%(=XU}=Faea4TcLLZ2C?}S$ ze@xB~Nh8cu+_`y;^`{?>LzrPu8kO`f$ub%W%oc#W&IXUB({B?vc}(`k82=V}=Dkh30C*Mgb6t##BZ znvC5uu&t7rd0gK#CVEey|5TPgn;gbPCv|>OF<-J5j8^;6SDma7hOS;?2>abv0?Ttu z2hNTlhwu&32SX+A7FO;P-)tgz;FK+Pa^Q6YB(EMntfLs3w@p9InXg2c^Rk%!RW^YzQQecOXH%;{x zwGVKUxO@~MO|L-JC2{>4m7az}JC-qQf2M@u4> zPj_;V_$(=VS%K4sv0H*phX`&QOUq_NVP__`I5P-gP{x`UJPqkdt*0v`j7kF37cG&j8 zk^SOP&f$YUg%NO1K^3(E^gF=Pd-*ZLf;d(AUv;BfsYn^nAwD;ApJxQ-8Sn(D7TGUK|m%5#IHBOltJGd1`g@P+XSfQJta zQT5l(-w~El22?r?*HdCX@iVt_NnO+|7x-bwY>+suu|D{Jj~n<4m0342?resxRnVF7 z{L9lhiskMDEEsFw$KzXpnyPSe!Jug_pqO7t&t`uitv2?0D@mnoPq z@>l7@f2&;nVCe#Xl%hRW{r9i`yA`T{y3_DhWRnfn_k3q0)nf4tBTpXnqy zbui!h|IWjJP9Ny;|1a#XIxM()3kArdEJCX`cB2k*ZP|0 zzMbZL|7f2(?7rno>#sUG4>LBh&TmV$8Lu(wW}MTn_uZRv<{y^E3;z4t_aSc`tlw|D zRA!{F&f1-Hb$)GXIx?49d9&EythcwSt@+m`fHxT~s<0NpPfZ_NnBxL`XRn`bRMyc?D6ie^wzJgj?Gq` z93BA+mQJS-20B`7a+jB^yQ$NdU@d zEC~U9u$Heilfr=FuLo_9>-_5aP^V9X3F`WE*}bKd8cwMDJ|;sMor!X_pqK9g1;VJK z@qR}wRFSYj17yW192it4LIcl)O6-4*AG-vnA=xMC-w|deqyiEEg_CVEXFqMtMy`@s z%x6E&Nb6y4{$nr0*FoOrPsb|BS~5BT)r;{WJZd1{9CI8q5%~H^f8NHxZ+7Lof8&=miLt>dRr*hIJ~1$#$TIA7+7E^&j60GjNq9qyEX zwr|II=X~eS(>AmV3c9_OC(3|E??OZ~Am`yxhLkl;<7wqK_yFTAs(Q&yWm>BUy>Oj2 zhh|aYYhfk7;M*SKvNq0(*9(?$QYS?R1(czz=%9Yw`ZdV$uRQ{=UE1FfjIJlCu#;KP zMgaDe?F2zK0g7oVy}7%CSQ+`ap=B+#!N{kL>^XV=UbnI3;A^vg;OAWBGi_1jtI#H0 zj?k(zI`kJj5{&-RbGh(^v^oywM)5Q79OXN>SCxn>(+7Ci4najR-MLT(qJfT3{12n`4rX<3Fh0PFiIB7~*xw^_MaFPl7-IM(x2)n9MD_8Ivo zIobKJT;jT$G#y(1y3`uy!}@}sW$knTt|rK}@NiOm$$6{9CXRLD`><<+_t9)NQ- zT@-9%(f((juxxNB{RGOEem`{sPfjIpejFKMH5j{;?)e_Fq&qDtUNA3KA{NYONX$!E z0?I5?run1gl!)8e!LN`OQri|?q2<%V4ykR-r;x5NUT&rW;e@76oy^Ba2pOWBaB%Cv|5R!4YG<9_lK)$aG>&(oW}=|uyj~9QI!>0GfQX_wZt!MyLo5gA^gYr^q>e1X zMw}5aXB=X%wl?%b3b1?ToO_rK_Y_#l{Ef`M{bP$8h##|Oal!wW=spuUpbnSQPlUUh ze4P*Q@)tCi;{`?*_>NKQmx|Yx#lLq>{;;b+Uh=T^o{>76*gN(U(kz~Vg;X=+56g;n zsUg?P2$N*-5xH9-B;ol3*e~34v`>(;(wVhg500=1U~L4qP55bD2c^1%IwLL8{uTS_ z@X&T~&2MP|6=TE64};qCHD%!%4n}5%vLBP~KYz>@MjZx|KT*>XKr(YZR{s8F;C_F- z^(|QJ@V7PjN|p;qxsN)>FvU8^?1+cy-M?+3H3!a?2JnAh5*t}u7ESd_y)38r5qHr0 zRt4|pT1(x6ORNBhFFF32Pky*&{W*7$RUV3fy_=tp;Lw6F{EB)8o!n)yIVZ*#O@`E| zLM(CpEsT9Of#Gp-WV-8+LY}&(*F8_}+|siyr`y==M~nU6Exffn^L+F--@96DrnX+j z+ygU{HAmtjX+3P@B9k_T+BtOU9Dex4HTo)g)s*)_=CWQ6`q^h&o~8mbZ;Z0La|*lK zzIb%LGbHxbtHi#q!{vsT@k~x*`p9x>DMoyO*mh@pT!2t&=>L%vq_#UVhZRWf4O;6<@ zL8s-p${}|OI+6WZlIO}8^Oitq9oV@`O&(akB{{H?dF}qJcW(7{#Fnrjxq5LafgKmxn!3U6{f32$;u+8rlu@{ zp|SMK)|Q@!sNTcF)t?Eto!NS?c8wK;WQRSlJDy40KGTezNiJW`lee)WEZw(JsNB8v zTyO|s<>n@a497@HO0ND$WV^vd5kqpYJiia8+~W~lpmg@PUK(g}UuZUI#T@?Bkz9#$ zX~UjWy}9NY$$^dGa?&+(zG#mMS~)-77^BF540yEn>gcV+Y@h8d4k_Nv=gzHa!+y_I z3X&Pi_gK<1i)Gqq`SK-od7&jp){g&H3IPrg3r@{aggt+Fb1GE^b;EY;c{7P1vu#Jx z*}iheT~6aAV(9u{9vdRW)AI|QD2U>4iE#o!8 ztIemqT<7QW+*|>PQdg(5?ve}D+~%mN$jC_B2g++9Q_J;h5Zg}iHjZsA+|8$zwln2b zRZO<+mYU6$DvB|XHj}{!Rd!32?BIhgrbsE+jW*BBmnqcFoOg>aT7PDUJFurl?pr4F zR4nP3mpQ@Mr@WFCF=CBE&r`TedY)FJu1=Ibuq?k8LrFIrvNJ}6mu>WUHPEi>DWCIq zc2fDG2&-dM_wK*}3bw2L3R;G9^u!yg7=MkDLS_=9d8IMA_%MWIk-Q^Zc30cbf4TBUo)m8-f{3&E%{Lg^ z?aqR4OY^8(sBXw!v6+fJs-o>WhMQ}7>1{B~bTkI6&6jzgdq}=ZmgC|7<8nXR?4&-@ zq63lStwA-nL?W+zbv%~#W=|-9DeHXDvvQFJgG}~6q&j3g5A%aZNx=p@%zAMRUHj&v zFps65?o0{`?e$@M;KcI2~llOHdJ|{sg5O+A3c=C#Wnvzof3{*{z`M*Vpo5 z|3NMo&4U|X1fC};-pf8@LpF7s5y)#h=eNH)YbRoCrp_XZ_kXjC$iZ}Z`7b8C+{_Us zS*Ex-$0JK}pys>Kt%N<$M58$H;#rh$_K#xglkX68YtE5tzYgh%;i+0m^5bUPimq&*|~+_W7!)Wt?q{pcO?k#%olQ4$Lej1h_69TrEkAx6IMt+2M| zj|0%&6>}lq%tHg>gPSw*?hR-S@0l_oaBSZLUs2+CkupAyc9AiV-aZJO+%BOYy4sP7 zE{4k!f7@m70s2!x0@9m(eWd}dfJ3yM%Nk?&qAw5uFJ1~N14if*=LwHSbL2PV@5Nw$ zU)*w(jqa)#?oOXOuN|l?LOVRnA&3g%P;4iBDcd z;Tp%hR;h9Q=Z7_mt;VfrOij6>{;na-`FW6wet>87!?(wJ}uY-q0^I(qDSyVJL^p@bK3NOHv@W5Mf~WUP}j8qLk=( zU%1L)WE*ocU84gz&+rLdm2Sp5V1x*uPQTf~Pt}~`j4Grl?uZV4Y`&l_Kp@uq=58uD zhU-*uecnjRpXuwNn20UIzER4H$ZKclPD;;0kZSM&wQNV_OOJstot_Tq^!un)B?Obg zD3z?V=yt4-XW>f%=$xTy(TM75xJL~AsZkOb5x#G!k=gv_7=G)z$o9d5=2*zK+d^~J zp(iz0gU7=QdPWj_L_4HC_@H|4dnbp6kzi^p0gFG$c~xt&lxng2`&U}%2$QRW!Dlyw z22ygqbt@gfWF18OBN9awg*Y?&h^gApUE-rk0by~HZrY}!DZy%X8NtNj#_bW{)a3he zx92H1J;E$17!Ji2VsodER3@>`Em73Vbb1Z=OSL$R7BEUEm;s zM>V5AFi@W8EIZX+}=*%@%Et0LMD_R*G zT|1Mm{J`>cIq6%6xM$fR51ZSUCKes5*#2fFS(Hsj@vDao4a-51K`VrMqtQ{6l=3SH zJh%2Ad~g3Gqko{8&pYoKpNlf=;H6-;(t<2JXg(P78f8?#9OdvSqo_lN8oD|2r+6gy z1gV-yvMfoUD>|Vt5-Sfuim71wfC9-=Op*sR*NVXrk&BiZs!Qph#IxOxLXDF<9}(_H z&$N)t6B^tWLtypVp$CVE2FHqHJ=6jZLl-pX&O>GM=yj2^#2_7KWO%O}7J>975@(z~`KA+&u0+ z=}_ey%~Dk3Y)=AP-opwmG}E^s5-}<>DGOkA;<=RM{diUL%7bJI<_>;rIuVvM+*`%B zLy-OOw&$As=~R21UwiZ-NgEHE`FFs%g@98 zmJ7U*&k-EcvDD{L9{yZ&U^E|P%r(zwQ%ZW-i05Ok&sK;lBf6C4zzArNLlq_*>RpAI z$DM@+Tm&Oy7=RBIZYYG~=_DpLBKv3MBuyt@>EjJ+h~b)TS4>_dpYkda6Oy5)vl;2n zZ)_Iy>9tSkyo#88jTxBQ-)iaivnGoG8CCs(+m(4jntua3ts;a>Rjjp_3QaFQso zID5KiVBNpXdlm)!548oqp=^d{xZtbZ-)5cyFz$ANzC20vo)I9+>?re^{l3mJDvd^!1Zq zp!P>#5Vrk0k7-d}&1UU|s!>CIyOES?xv9xP3-Ljy z;+1v<%b!_N@KzC{eF8&~TtrDRhg%BA52MV|{(apBIx0`zQ1r*Ik8bK2Wu`Kj#NOP9 z6KyW|b}w|Hv)!wMTW2)Wxr0aSgHuJ?L>!xw+LWT2`DnOQhs8iLBAP%gKa5}_A~%9S zEmL2jxd zBZ$u_syFvw$9!&5FiVgu+CU;2at=BtBRLO9nyX%s7uB*^=~GCJ4zlgM<&qrk3Z5t{sg9-B(psC^Ry&zI|@Z9`OHD+8!( zM&J5z-(Hq^>hE*Sx?K|Pe>j2%Hu zh31(@4<2!%IyrUsksPS1Whr<*8qVq46YYY?@LrSsDY@-WjrG(ECe2v~BeRbMcAE?Q z_e1gWqm)3~Xd3t23stp0ls=C<>*Q?fIe93|+4BYy6(CL$8o;zDzS}6M!{92-1&`hn z+YZ1d)@Q2Ej!f6(t={BE0I9cMu=1GRQdsJj?9ia<;I2dqwY4*Hq8^57_6#YI({9v%c-q4mrw>iNsFzy}S#7O!u#p8++d zQcJenBRmc?6=QDhm7kDoN=@-OUkTDcSG3HWv)kj3yC4Aos_JEY2G{U1(PANa8@!b5 zN3Z+%_CZrQep?9#Tg3&fbb9%}p$-sQ?syffrmW$+9V6N27z$-xdBy8)Z(oPe!(EF1 zT*O}b{q&_EQdxT>_e0vXME^11o|=z?g2t?CW%n1>?q|XPtkZP>$^IvR91NK`UkdP7 zdxeO;O9W?Yz+^so4D?uMf;Q=>58=EM0GatzL@EgsmSPj_=eWf^zBZyyOAA%C%+DPFtdTB;qOTemUM*YC&cEOnUBemhK@qP9H1p zN}wy!Ykm};o3b33cL<1K@+}8L@J$@3Q3CDB>M+?!6o)OJQp~mGNjWPL zXaA)F^rK}2<+63M&VJ(&|G5~JJ0cd)K2fs1G}$(aTJRW07Eg;+hNBih#H7x>suxl8Qaf z(#a{62rdYUx-wSCZ21AY$NY$G<9==m*)GapG(yg!(vI_BIe%ZGx}6f+BO77biMWM* z5y~@n;Wqbck-+KN=VgrM)ZFCpU0u{#%ZFQgD3-nTC3z=+;b=znXCBUmWu5Qii+Ul0 zEVzcS>sW~sS(WckJN;nb9viUGZiFW0OV!smj2^G#iJ?7a@`tS#&s8}?Nu;@GNl<(R z^kV(~gQ~3pZ8V(cq=LVw&cHmn%}s~M=gbQbOi)=HTgNg>sK2wc9T z{ZI_s#vRO+5NAU-jo>+j0)>^O2#FjW1OH89BkOBih0%R$q0WiZ=v=xH54L@NG@JK#;cMUmA z_QU<)rN@Bgu%F3JJ}N*OK7hS7A$&abCgA;3MOIvfcz;G4b`lFt102bVroF0tLKVek z&+y@IUKIa9)1o3UjaFD#f4DGM#R~2i4>^AW(f&9#1jQa5zVNBR2tkp1>6$~<_gALw z!(?v)eD4CQ@yMF5H6b1W>fERTErC;!-izLg93{*wOk~Y(REk29fzJw{w?T9L8D3N#cV-F0rt40Oi7bomzrkF z;TqgMztJ7DK$vu)HrpSaJmR=&fm?@`V75yN)>3p>uPFR%NS{kPX30za3$*JFSINV6 z8%5lyjksVN6VE(7sJilUUWHuc;4CN>9S*ctf{lE7i^$jgJ){_~xR*#_p&z{bKDe~i zJf?4|3gNK0_e5wuzR?wj+A|j^Fmr$M(7}Lbt;%XxWKup6*`{9budyKfz+%_0oB-mF z0B4YgSzB;1CUP;E!{~jhX3O4X=CiYMpAo(>f#3qIji;bL<>_FMXdWjuXopo%lOby= zg5Q_g%5ka2Forr%S^{uXi zy;M4tYl=!OPqDwxAOkx##t+}Rh6;;LeJXZ4_xd0%4gS|VxzYf9ccy()sW>Kltnb11 zaL4y2F0%~)kl$OBW}xo3uy_FH><0;Jh$4^?cXPGIS?Fi_PbMz`lc-BWm8pH)^2R0c z->=(OqbszDmImq*Rx1Fu6b$w*yDENVejkzoFdba-oU@%C@Z+_J%o6*tDXU^B{tWj8 zq|)f=bAu_a@#X6MU7`5m%nIDUO@vDfsNwbXIrl8+52{!~sJ~Z))l|~w3sc5ZYs1|} zPuD^~5=2WY(~*aGz;SLjxyw3VG2nF(@RTD!;2+*%{~c}VEWy`m$vyxhDdrd%|K)0I zFj_F~ARV4c>p>1reJ52&odjQSV*muL*0(hn6c-o2qy#V~s2Z04RZWFwnbG@XqT=G~ z2&X%*TK4(7_g8B;X5wlvr1#qmbh zlON2Ahx3aLMtw-|45EWork?dJKpODh9! z_V)uoGVf?WC)R!bd-{DC9!vGs1Pi3Xng7(&0w0Rwh4}}GUTP1YBeHz);*)8|jep3b z-|}I2ME3BYn=4~}e*SA+-Gmow+@c?7EQF*GLs#0_AtNjMoB+Q4-*3qOkh~hOTY%}E z4ZP+WX)L=DhU{bs_u#YuzU5I*CNfbmvA5slbeL8R1^==k{$U4@Fu<4q#!;Y8==kS9 zy!LZ1`tk`mm4l6q0V$H%R{u;WMa7R)M^|^vThBN6yYJ4k|69&CZ{Cy_N(iuW7|P#y z>v8`dSqJLE(s8mfGUP?d(saKS@Y)O+0`^%{;?aM%#n$J;R1hfEjQ4LFPyuo9dTfrD zb!_6iPj&Q~2%km2eq!+7%8|eg#<11Z)eih+0>n#w)${jFKmOxfc!;0ODl}BNo**rT z@B+`RsT3GmOuqqib{NwTs{^hf{!NL0SF^OXwg!(L;Q{@5S{-eEFfo2+HY#He>Pf-J zxXoXmS34rFnaFuRxbv?et_0z8I1*}36a%cRtSq!s|6<{f0bGi51EO#>G~5pVme(0+ zH5DAsftYbB1&%8%r)mAYEBL;Fcf~VZBQfm+bEy0yDSo_F;zZ)d59NQNiq8c@tFNDMF#4@r1S0V?O(P#6bHZfqsZOxk@fMX zr~H46^0ut((@c8b-8uQPe;Z~DyvAYZWnPJpUVa+h;=Q$QtEa+$6SDtjtQ0^ZZf9qQ z>VC1--cTJJvZ0CjMFPn-jSvFxTl1d89v;A8Ul{&Uxc$@ahP+ju>Um*AQ4&@ zv@kz^3$GoH*E$CL;5+zdiX_K0yGg4F0FgjGfNp{Eyu=2B=HH3LaSp%`5PJBg0F|a! z2c7s6HZVm02^(N8ip2d|=gGd#XzGu=|AgmZ9?;WDvm5p9i(>Kd@pe{L@6=x3h$GYx z!h5y1{l#zQu<7aPG{=w-rFw$xO#irlgdV5|5P;9jJoBW4EjUCj5d2r2fmlCZ-@YtK zhg>Fm-+zjV@Ly}850G;^y&MI1G8^Kg=)3Q{R0(@cAZ?Dap zL3EbxQ+RUaLp3vZ6Yzg~0KSH<8*hhRGY)?Ie}d+puXKO+ACE|Y@Jv@E5TVPPhuD>j+q;yNOG)M?agVG%aor(x3BHbY& zDBVaZ4HD8IB_K!$(%l`>UHi@6`(8!A|9RM%XU?2ooHO&}_doar3MVSl?a}OldnxJ= ztK;#*@zk?&hfdU5<_0(JOq8>eq53X$NG%#YECb8j-M}|O^QsnAI!wPu|9e0$pd5QOrKM3= z;!`!wB~#o{b%#jo6vIOI`e`(L(R~H-9qV_Dxx(UR+n#mu{ZSD07UB( zsmK2g;1?Jl^Yb&-4Ly!C`b1q5u!w5@Z8zKCL|J_Gi5uGwwePR}&kbtvfF5haVK$V> zK;4;Ap{dqsvAfBNp=q+wvn$lG@(vN z3M`56k`E^rS1e%K|0Iv!Nu0~Du2Tq<*ALqp8(&Gle^`RGcS$JZT~3%;swp>~`gSJ?5An2JLNYldk4E+kR1rcf zLH51Gu~>*!%ekN7r(Iq~9F##P6K1b&vD1%wwJv4CuUG?NmzWReP#_sZkzf$v;pyR= ziTWLBdx;I><9bN;y8%JAP5hB%7mzIw19_TNw>jQi?p6YPj=M4Pk%a|Yg~vw7V-TD@ z05iyv;>Cnyru$UIIgt$bxv@&3K+oUkj3w6H)wTGcI|%mUGN%WWJ&T(%$lX|*@!@ZP zh5z7$azHCfOXojyPbfUhAol@@i6?a>Ll?HZpOT#1^%D{s8~d3tvG>;-_f94h08plK z_?Q9-&E4iK9#cp>bH!1+fH(Z$KJYhniu*l%xl~=))1#6FV69kX%D{2=EY5I~Z>iQ6H@+9bwvgQZW|0Qxj z&HTY}~_x%Th$JLBUy>do;$gDJ%v4%q+ zas3RmtmvVFw2|jSz!veV!)TccMF#R?Tr{7@ysaZ3^(%N&Z_&02v#2AU3`yBd*`Jib}Zs!r?D9C-J_42;zTnQ}!>R z3HX5@e#*Uj_ZC3T`BQ5gqJsn+P0D2u1<1IAssQ6}>=b2cXh+!Xxa|*UTA>clxuq^& z16*^_capHuZbd^|Tf0rr7>PupxG2A8bOYb$x`dD`*~R7!h`xfhDLJxr_GR`;);$<9 zYpX#Mg&nPm?mo+c-T!4Jz<)eK?oZHigoWY(I;upO-Y*ldNMG@)N%cANdm>P>WZ1YRmIQwH2tL++-93Ga(=yVAn*e~>n5`C z&3Gvb(69f|RPb0&{7Tv*m=%!zl8p}mUta0U5@t%8{q-nl;1mG4#Q9{;yb2JJDwlnW zdSe#%O=O;PC&6m~JJv>v>@CO|za@UP^fd4t1l#*ok|z{2Gsr_5_M9g77(rkf@oO#m ze00%bqCA)y1nhKuNF2joJ3T2W$>w-x0qJaTegRmXY*gV$1z3AyR_Yd*^)-bhVsh&UPgr*rUs zFG2Qy#?_DA?$x5F{zF4Uwr|P)eAUP`)4(G^LDFe|H3d3v9t;8GDRY-K92CAU_IJpz z{5I0_B7VPV1)$rrm#Wt8G0s*MA{ie4LbR)dk*R6MzvdVgd-I>m4={^CRQpi=GQ+h# z*<0yHFOdksYKmk16)XV!04N9w#tVy1)VfyuLoj8Khu)=(gf^*>CdxPgxUOr2N#-(A zQ$dl06tq*4LLSpeTuzFRE6}%%+_#@uS}L;r+Av73rl_`7pM-=2Gf51H?~Sm>Z~ovB zC;Mxrj*hpN5Z&Lu+y6zTxp{cP?L{kGHyp*(PW-(vA+~onF~&bT78Z$O--A#BTVP;O zWzT&gYyi!zHq2hsX=-*DM8uFMuOgwyhm8J78tagT`>GXg z8)}Vg?OI^*gZz|L_30ZLJwYqIn{f&-yKr?gB(n|nCUKRo7I?pa$(C$)%R&5K|AD2FfUJy(mEa(? z+27*6^W5wu873Mjbp7Gzr7SxPtR3;=HdV}}fq@6!a;ZbGOjpj@MnzimBHGTD78wga zZ9KI&pK8gc>3qw#z7kdDT^ER|^BjuLkPU`YZTjzSE2w;7HRQKNQLDX+;<3(qTAFK{4?1=qjuxkp0YkwMA(q8x}z?uAR4w;BhGQV6oC_18HZ@h8i#-bI3iby^D*RMzvV*D7f1rVI& zdLTr~B7!8&xNtle3y~I8Ob+BP97)Oo4wgvV)zm+zZ%RtHpfT{#DgVl(d>11tfn|`@ z((VnpEI0ufvb5IYv{LN@8>V=IGz_TzaEEkZWHa*m;DdHlC_nN`AH2IH#u8rCv07SM zBKz)(X#aRf7)FMI^mu`53p63s7ho6v-sKRi#v(jg_9mNO$N519TE;$j&iJTk&c<9YT{5O`}9uzQzhKfYq_;7~qZ?>VIp>BOyN1~s@zGZYkVqV=&C z_5aj;9+>=T$ZUbgEv~5092^=#9tQY-QDVEOM1t9gn^&G`OOkX#TTrC*inp_Riy z(RHssXktD%s>vaX9eU9kOP(&Ut_(!??N5UlPF+kA(Na`x$X&@{A zpltK=kBzm4lY|}H0ZY5`dt3fvFYY8JB(Q%x+yg4^Z<08S;^k+Y2>tQN_qV4Hf>7%y z6opy?i1zN_o-K-)VtXz-`P_zG5 z%~J872n+)iZ4n^M@_}CdKNRwwBmm{bHX&+VaZlhT*Dj<^6Luh1z;?h#bRTI7B@V7{X%tg@um)D^CEJ>{6nkVz6H6 z&7ed9daD9uU2B#t~wHprHPH;OI+rC z5@{&|#v@mM-2I|N_p1<6E$gKZLCOsvdOkBJ6383DkQRbhMxPZyTtY$zE9B|x5rRJe znG+dO(1w7^ej6I>2j)%8X>b|`RrtT#(~$EQa|5GShlc%S0u~t5>HrQlyqlHcG5U879D@`T6vTjl6o97c%IE!7 zqqG>~z*q-oTVlcS8y$LrWq$7!1I5BE0(5A1vZ!M47T7WcIP6515Pt)&(s9$EV3!Y= zpkj!#UA%R~YKS#3&hU{u|6gJqDt3~sq^cU**48$TQ{+m(7XDvo5&)+r6N0S6f z#Gi8Q zx}Jb7@wQL&b7X-6FExn4xtD#KuhA0)Z8P=i+^RaeyO$n%Q4uJs|3>;vjG}KhEyX^q z5hDK`P_LJrIXFf%sPUv-#)>(lf(D}c%C8QEd;@=JjBTKV?Ivh|I{eEt2EN&q{Sp8*<10TC9JQu-2^58P4VR?p;eK~dFpk%rXvO@I0fqOG(Zo|I~1W1%XR%yGsLM~G}Avpl7 zIcuDpoc3x$(h&`(gx0Wz(~@3OSqxA}T8PpC%Tvvjyt)5ijrZ0+N7pxPX15GD`f9IRIV#w8n1Y4%BgDoD~ZTOPgCw;?Ke{ zxOa!81?o=&v;rMz z9d&kgrpu6B`}8NsF0a2oOeql-vuD%^-lbV#V8SsvHVZogT^4 zNcOWiXI(mYw>$DxAV%T%Ov85ZziT3#2)%VY)MAV(3;lQIcG;DyG=7RGPV2*-o;|7q zhaZ8<{g^r!=7MY%2_-b_xek4XWed_~4XC6?Y01z^_^rP<_tChxh|M^gs^?e;^;B

n_u+%X$+ZLJf#<6a2l7dBFz399hxBY&_XnrmIbKbC5+8Z-8S$j0qn1LQNcs^&MnYcN z)WMJn*Px{61x*b@q=${-&)6U0d36!JBZbstj?>}k^WP!GhiAN)xla)t@=h&PE&N9* zM?=o7Jaq_V%)~CInF`nf>(YM4i%zxhg@RA>&~a?`sOolmSqP?{ z-?1_NnzgZE<@Ym$O8Aw^0uMn^GK2~;=*UBi%}``DA>!KHpk0w>Or_4q)w=>>4ejS@!CbLhN)%PiR`wcQ> zzm%VHTg`BhEcaH3!JP2cCmNRh3RM}JiO{Lf%m&_w?_4trFVSn(u64a=_qU@8bQD5l z9dSl-98Gl#f0&uY&DzDb{?d{7v@uk^H`hSZ)3aU$;~697kw=$2;Z$8!$w6++;-i%n4hQ#749N6?#nka#8yO(#vP(Ht{bk1>IiSnNQ~H3 zB-X@Psi#K_2B`5<;nVB+hI4;&KsXljUl_yRQ9!g09+X`~j>p>ZRRmyf=^s|^?vRj4 z$5EGU6ORPzxsJs;g>KfJCIx&#qgr|y6T&M#Lf7=uP;Iqy(uDL78(w1%nUp!&bEFy>_6{%I)#>~4t>vk!Z*K{x4Zb- zl*YqRC?uYqxO3urZUgHBUue@J1MU-c44D9B#Ta)}Hp}6CE@6iCpw(7OKNOiTF+bbVqSXuW6E>xy~l_$5LI;6Fe9RJImGF}39*XKnSX=1E?-mqyP6xi z0#Nu!-B`Q@t@hSwtn40UV!>@U8I8y}dr=!phaOI+E=hU`ktLAw)++JbWTA|32{Z`| z;Uu;cIfyMH9EN9L>=e+Uw`G0&?l6OJk-3eS;ZcA}w%=2)!*Pb3)ri_b9V=5keVb-_V~x9^L26E zx_4cuFNfU|?V87QU&OdCJl8xCeTXHmW08bXckj(S#7`1CZ7D#_>C9E$HG^!Z>RE<| zW|)~ovB;TYM48`^pNI0?ZS25jC0%A-&7S)D`WD~c@h5JOPwtB}*+A_eT9aSyb0tC= z8xJ<83Lk#ddVlbfyU`1Lml&~M(D}gV?G$k!D~cR1 zi#Dz9!+OW3%)uTL*2ZFa!=+_q;8n<-xvj_TN_X9bGBSZh3=~eu?IvspMJYyfcXnn+ zvunm+Vg-?tA$mKAO|RkGD!;u&H9hLum!THLy(l6xm+~?wq(;FY4Uu=H?%mFEUg-&I z&dp|~t460Sx;t+JzBt|(E!+>ltat5CBJi4OOTW^0zeq3RahnMRi;M#iP$mfQ=H) z#&0%}fAi_v$Dh!{xfH)_!ZT6foZlv1e(8uX&_Ay}r75g4G~tTLk4`=eWmPh?qm+>+ zbW~XO6IxweE#00P9c6pKxJKvw4!IkZ9j^Puz(M0pUL=H`6fzjVDfvb23AJ`-#>k|N zy_hYA&t7E<#(B5J{g3Er@xr#&cjxiXy%6zyi%nN26el0a57itFhRcF{5$h5f-kk35 z%ZbgV{p6F3NMb@+3`cpyz25GlsoO6hlXlL%F?oiE(VLo6FRDpH()sF^#B6;^_|hBL zn3ztkUAvak)+RqNIoU-CVWM@;Cx$IUiauP=@x|~4hW$_CjrH~Wrtzfy$1B?8IZW<^ zc0WlI_kC{HGcE_MhtipA#IzCZs=&~FXbX?)wPnSP`fqldoNOhU8OqxVzi=<6cy6L5 z4`C=bXM(|mIl^NX4s|m^oIPbUhSD*PxTUY{PGu-ioClvW2QTvrtlqBEP}rT{r){)t zi(quAW-jM|t33f*@n5j;IyTa}PLhTIMfbsbZ6+SoSop@CWcf{sxv5=+R;(?kewxf8 zv{NTxj;be{yEtYlebeY^v8H>dd!2n-Uqn+to>+)8Gpyp3dmbqxIozB|(SfFcjnyjR zv{JNXAw=X(Lomnq{m#zD?Qa&$BU4A`E}#2adr?v4ytIg9MPndR%}dSAzN2;SpP!i( z-6F{5fd=z4aeZY(c?m_Ln=JyLWU#*6({L)1fj{ zsGj9%pc@CBN(7$JAmWZ~o)*+Z=yoP_y70NFcI7=}a>t9AE=Z#>U{=59MCItlf0oGC z&8@vV8O`2ZXJZ;Rnq1DIt$d3oTZWeNHSJ>kSQu9(MM>qU5iJ7+i~J<{N7v9PLso{< z7b$kUt1sT<-Pzyeu~$Ah#B#<>pU=BtUsJ2KLEZ=1>Kp=4wP}*942{7D`=uBsLuebr zXx?+8+Ww?b@-@y1qsi=$#&u0vH}%z-raT^xWu8_Z4nlECD7tA2RvOfaR#xh6ROc#s zN~f?-k*3+EYY;~!&x_tMnXq3EBXh7@$d$dkP<4bz3M1+S= zpILYb&6MNxO5Me_;rz8B1!p#K28!jZSjL1tkGV}&zZ@9Bl|#Z4vpM$f7ELdT?0g9m z_d|u`5pV7z6E_KmR;t6#(FgEPELa%xcfD}D^omM)=<$~~HU=3GK0ZFHoj1EVBA(6` zr+@O;Vz#=E$#7jB6MIPb;)guPf=ENn5;YJ<1A6!<68|*9|inGGq-S-tUlA_R6ix^H4K&Pmb@%KKFSrJYZ#}qGUCUd zwl-eh3AmMB$Gkle(W0|u5MR4_TW|UCn5TOH!$`cMq4qS<(MJ7u*=SSZ9Is8kO`)Er zFFVSv&r^c$r0-7MvW29EW@y@Zp`wMmXdD{rwRkKsKvO?`QU`TRRwXneI&WUWRMgbY zTGa`D?~@akGTSAUfxf;+{P-ed3a-b>L7rUCf|k$s?(J&Fu*=FCS;O6@56Z&_T_5|a z%o&U2*2H1G5P}kw<+tzllD+)+tZCDhqz9Lqt(cePY_Itu-Ez`(EozyaaBhfQ(O{F2 z46ZDJo@9*oMf(kD(BL&sdU2PDO#u`*HrJU!IuCPA@h1L=U)A;zjw0J_%DY3e`-%nV zJ+T-0c~RG=u#P;?-@4@0;0Vp$R%SE7wt?H9RJ!Z2rmZZ=T7BEi~L~9~KAuadIQ`l`)L_&=Y{R1zSm&nL2{W(M96oP7oOTZn z_7rK>6ygX`O!+PxsQw)uardxP@?F#Xr~}J7cFCNh*b|4^{sE9q9mirC;8yiubhN^GKqJH|Kon~JeOB#aA z{x*L|`iVmOQGSc?Pj?M$_y-<6{hmT=XaiXco2W`s<`s2i^N`fJ!#UX$mCf6*lh5j= z<|^8E=1J?AXGy+Yy{d5q!+tJ??}V&=%v_YTd7yCg7+m->1pf72d}8892h78833K!x zC~EHtGCz30!j5Td8^b8CisO}&w56NN9mPKJo-qX9`CCq2uz7RmCuWH4fUGrn05AnfTQjjg6^OWqNIzUB5bs>h#yWhP5At7M7i6R7PA3yEaCj>=( zjU(gXiWe!3Y>@JpJIuDl_gO6hQldPJ_l-8vn{D32A=r3MVjWoA_^Hh)rUpjy%2j)F z7V>HOOm{lhrlX}B)go5&(nk(!Pv-aQzF`|U_+t~h#m33JHF_)%3>~SncNGiKSK?Ic zz}PzMVuQ(JdPr`cb3LFA5V%GQ2+5B*@3nM3`3mZJ6f$yhb*=NjKNymM)65n7)ts!{ z2YC;3K$XQZM=pQ+ZPb0;m2K z70gN=GlE7Ty55~vNBMfiH>TufM048h1mWZl@-G}nWG+G}1wVuxIrn^!eiFTHL6H3t zU~In=nueP#R14<`xOU3I&aS_v_|;(89!kbLA$_d8pPb!dK{$$?pWk zmc^6zpM@OoEi|un+yS}UX76ShHtmnm(UaL??!LZ06tlq%x}44*v|jkQzW6bpBsO(v zc!wx)RUGvM4MUr=bDNi+Q0BvfHUOkVkR3G(D?UG9x5Lj9Nl?VI@=0L@f} z%owp~T}Afvn%_2)KG(qQFS|Miq-`U!_({PWU1S#L%`C5;?;J_I1Xnv0hdM+=#26|m zPzZ$Ig%B}xl64%&E8%-bHkX2NKMs4BWtFQMvG$cr0AJXgC=ljnO%gBlHl61r)cH`F z=VHPmGTAlMr5+Ka>w5OH?E0+XoZ}Ul!@P;LqvykR6;lt|4$@xSuHzjkbJpkQ<4gB{ z{d(Z&sPbJ>(sxDdaxiRKFl@SN@4NHzkT#<*bi6{)TD!=qVDVg$&tl0)-z*_pO#_Dwpk@BGZtFE+w77a^u~fgoVgr+DBrm3 za802P9w6?jBEuEr^)BS%5!EvR{&X8&If2*k6HH&#!kM#dkZe!VcC5bEJnuWjvA3%G zSiHEL^5P^8ox<>JZ#Y@2~Vz0(?{`QyNH)UF3N4N|i!QO#G zurQ+Hf&Mk!a9=IJsJsUWqih*`rGB+9d{E&QO*Om7irNBy*$FwWIsy3-KD7ZS{Nb$e z{r2UcURhznC$$HIKPjRz)5gi%Z#p0OD^*?;aJgo@MNlIQ3-Fl+NDCt2b$qRbVax2x zG5*`GdhK>o^4xL^81###Q(NS=6EjW<)n_yt?t$r<2Ap`*Hx`EX2HB4<2K+Gy*-d#~ zHoPLnwhV-tMO+W272Tw%zZYs16ULh&#Qn{u7?`Z0%aNf(&!S;(kh{`k4>jXkc*FdB zx`mb1!{^VR3o3+r^bza^vS-ws+{(C7Uvg5@)NX6Jf0KG09>M%2;I5NXS@9q_|1D09 zv_xZ@1|x2&PkR(m1pXPP4cD1IPi>zj@s}zutuw^OTKfJR-W<}XInR`dk}|wA%U35n z8!qQjPK3Gw58&=9u&xtV-V_#KI;+$CpoY~!Nt1!IDacJ=NhhL5kCLOg>Cv?9lW^m3 z@X5gi%F-$Jq38xz&a2J!R&j?jVU^s;k858dx&t6&l$4#-=H;?bKY9@I@2hm`4*Mq2&fT2VeM1)IK9&Nc9-GhGhL&_(1V?8-37O9^8{$ZKx z20j`lNv@Ijj^ibnX$WqYnzkDF2^4p{K$1?O{=Ts6heEYN^C%hqk>XM2uM+eWO#|dI z6~5b{t}x4@gs4KdDWu|ckDVSpU>>?rv1H;yOqeTgyb1|ZQaP$n6~Z_vj}wlqaWMfO zFrVVube8rAT|jIFRhWFmEVDLl8@hSJMpnIC37zjrFXnYRZzv%+$@A$GkKc1prT1p! zC%mR8<$j>}#2%&}T)jPN{eBL+^5YR*>YeJ!*03cBlZp8vbsjQO0)5m+t#(I~ZY%FICK>X_5l0@j3*M5AYv{Izq%Yr|Vor5vxPy5?(j0}mB5?&f z*58pxMavzmFto^O%8EJw{#8(tQ-a3vyOnDS0IVV^P-IaXgyB#Lr5o%- z#A5QXqs?D-5T($%#A*J>8g7R zAFR!!n7})oQhjaJ=Bjs+6)`J8pU0NytEt3N&$Ky~pfiHW%?g1bl5M?@2fn;KJnB;0 zEQA=3Wx$5&R4aM^fna%gSq*4(%n+~YYN4`c@iS_w>csZ*ji@@{EWW|OcEj5qY&e$G z{RaNR+nz@IgDD56mK)K|c#<~E0(V}$Nxcm8l&;F=i0Y)|A6@rLkFJ zw4__tb?=ZM4H2#QCNNlFNX;N%xp^){+qtkg|IVz3bb_;>1lzvfHX?t_l+s;QL|6Os z$db-YBISC=no>ADM2#l%4e^3O(`LE62L@LPeGLr-g@zV}5hyBR-V@iBOYMZk{ZE9T zbRuN6$UN|#-MVpa zY`qQcZypzUPfN$&_5PNLMt|n;l_7a^1avE7 z%BO(3)4l@aSfOc9p4Jcq*L+7xKB-qjXJ^m>W<^B>sl+=w>4qBFAny4G+*1VjC2znS*PKP^RCCr9au3>Upb0* z&k8}ij?-qPVdG~-N+v8KrJvUtWsTh@Pgu;Zq(nZ3i&VA{hPLn*tJb(&L^@u9999N~ zFAd_GZHNY?aJhHX@{W+CeTueGB!5ups6u zyNk$(9eA6k8nK(vco11iW%r%``m2yk*Ulspjg@reih*Yqtwy-XhdMesdr{rbbWGXd zYD&m$Tw=)bz#leEEOVFa*(VVt@^(jrn;5FiWObvFv{b`aPXcjY;cFwt+;J5C1$q_w z#N7drF4GEq%XjGc`ZPGJsDkv#gqg0iIQG9{WLH)!tNJvC6F(^AS?haK?nB|@Qh9?8 zojw+H?^FJ&y|w~h6n}i9prfWORZD}~`!-TfG2+>ijN3}P4{}kK4rO!ggt5~xwM-PG z!S`1b`%MhN7k<5yGc%c}u6Vhr{_(PlKw}TTsAzr`3ZX4KXi{(#Ob-8jat|?h0^3-sI;TtnZawS;TNuqL082^m4j=|QVwIsTwyo_ zeP=k_KXO;-zMvI9%p1#-j5^dPmNIA0x6KuNJ6rm|5__w5J59B_(4}ZQ`XyhmXTjq- zyg)|wIZGoCzsE4IejDbtGD1&d?3$n|vG7Z~`-aZoQQui)TLZ)%kwvtcDK z97uv|Y_&AK7tIx4-)p$I>;kCm8q*81w^lv~pfpg8gmTs2OyO^xaSPKStR5ZTDmqnY zT$a49A-|m4c75PmfkXc1?iY3SIiFtFy^Xjx8&X&u0Uw)1EQGM<8M2##zG_I+a^9-W zlZv4z$$TDbs`uZ*ZN9s2@9H$K#3%94KWSXeA2>$%bP)t=NROg;F;bT#=AvaWWulGh zg)L$Di)TWk=$0bCyUR~>V*Efc>I&o6k}zrwp|Y^HRs#)X+NsKR#M7{bfO|2x6vn;A zb_cV#vNsnSSKqi$&dg~ja?RXflSj|!*YJwKJ%~PoQ^Gx5-ch)Q+3N`Lk>a~bZP7Q)Wb7*;UN47s?-8zBQ>OUY zwxn?_o*U&38lyuLH_I!sD~gV1*;0m7g`D+{9)}7b4nCH&*cr6FX4@1byB7eH`#g@I%Ge6yR2Mrb za0*1QjeU%vtKUgvN$KPEfK$i$>_u98!ThEf)~d|K(e?|8c4=kQPpFsV_8#6M zv9(>jYEYhX9zm$RheEz&kW$j0^O#5f(_M|2YJUg*lNBFGTAwzNJ!uN1)ac8H8Y)S^MsQ2RE6%9eH8f+)*ZkOl4FPmfjUdDO@ z=hcvwX>io@) z08iAR&rJ%Fc{QENpRAeE)|lqp_y>7gDBVXAMSO**Zv7VqZ|{?tQ{X@X=U*A0#8bG7 zyB4_67?t9-M9P0D4!I-!#+fXQmVEpXf&0Q>^w40G&o%7aPmW2Dfslo=?$W}YFkLo7 zw8yHp#4ssM+QJ~aW8a%!aq4$X*_|fW*4H`o>#~aXZ|+}1%25G^WMe^ZAn4f6SZHx2 zbafLJz~ytexDIg?;J)JBHH{^H(doh0vu#uh&jg5-Vts7K?v(mAQ#$0JnYX>x)szKe z^k9cnKKG?^?A{$~jK9aZH)gj-(?ez4QG7+3=BQ2gc|9v?KWmTXz~c^^6jrqBWJlY# zf`e8`ZCp;;H22PrLc+cc&8(WQR@$v?j~OsfUV*&6tHu|ZLRnVl`SlGkCVq+)Tq1u> z@>ssX7ed;|_rBFKpSH+;?csiJY~6ijRn@!`my_l*A>yle22M`ZQ}MSETT^Yp$){y2 z8?8+gF)NWendo#y8Y)+$WJRo#*mFi6(i4ioAzaHW{I^N%2>8dj^)9+a4~zPn_OZ8KVJz~k3~EwR@d7K=g3xF{Vbz#qrN(Q34!zx6nRg#>%Caz2&TSdXJL~g^1{VI z*{bV8Tu?A)8v9SX7L@dI0qBU{{`mRx=h8q(+9ZzEeF#0Ok>12Hu`MdQ!kD9=I=Tjq zR{~9pbKI+OxnB7GmTNWPOS%U|@Z>=Jg=k6oFZxl=#RMl!(>wm9^Oo(?zQ(XGq*X542{=w-zn3WDq$XSa|;E!WwcAzv!<)KR=4R*P-eVo>m>|4wXvDZ=s!k=}&iBnq|=Vy7%RHPSa13 z0U%B%W6U!9Qx1MAM)1*Gav;d*HBzZIEZtO%*arNu7WSkcD@JTet(czTZL6_gsZpvY ztf@9}Q5)^T&DkXV_FSl}CC@d#mWGT>4YaT2W@WuTPPWM3WhoQ@$8Q4|O#RAaAiIdQ z)z$v;=VSfaW`gyL9G@_ZV6Z$iT6zdWK?JUrfid;zouj8856>(%Zwq!|=J1N})$_&P zch8o!@Kw9s*d^3U@YV7@b3WIqh<1>gXx(jdb_M69*@TciI+#fN^5lU}+rqv9d35;U zyY@o@cR!(wpecpp;v){A4Cym843$W$o$B@a?}K@m`;^7lV2W)0Y^UZqJsX_j{Vdi# z5YaW>7b9)n(`EZEA>o0n>}!{es-=E#%?A~ur%Bd_{x>jsP!pwv!jnyIqxd31gM*PK zJK3-k9^zVf@xzk zKq;uF38!OsWe1(P>azyc-90xatG{ETNF8b9B}Zf~NiOuEOZLUXRpQOqmj{f;Dp4C_ z%oqG3@|lkjO!-*hbWBs61EUj1Z!BC*59OsxC30DCl(kph-Wvl|wO51?jFj!p*1J$s zCeTFpuN^`Qo!;wH)da2%oL&lI`}$L7n4R}_@}4pJP{vGD*r0lDo#<)0g@(e1g6#KM z>fKnqICMzG9fwLYKpU%Lf5)U>|3hIfuZPcx(oP)Ar|i|S02?Se?PqE}*ib$YY;*ij z`iLQrkUqZri6&k6$H#Y$Jo`7%BOMqib)JeVP?Hb}zV+aYK{R>cQlbbutg}>CMuoyO z7(wRDb<%d`#1nAY%N{MWB-MoAZE$ehgfS>o?=f~7)o$aM%fHd5`9K&Uq*(LrJx-uG zhPrHW<3r4?_Qk`C;EFhDm+3psyQUsxDV2t7b0QY}RPoWPd3?cBgez^H5hk=eR19*$ z7_zato)Lnx8dWm1k_61Y?5+=Wxg*-oXWJ8{kHkkKbd2F(jGDKvSCH+u+Nok6aA-~Y z&K8A91OoP?Hrti8x(lnWt|k&`0+PXnRl+8m!AlMj#H0`dSJa}T9KUugHY#J;o{Ir3YULd_^JCiqsg(q?E2%ij}Nb(4DOwF=2yfZ3iYX!%0VmZebDE4A5?9F zj`&d8K>C(I^CF&*V^l(K*m{i$QEaxR@WN_uF1#THpf(~1G;C+Vksi+&qWIQB`lW}#5KL~Xamv-f%TG-y2 zVvTOb$5+-VDM`1jmv|?ja+h!=gHk8#4)1ESR8~y^6$M3a9>nN}nTv}M7tinw45C-a z9+c&)nBlj*K{@=~$BWTK>oJC&UGg*cmoyIBTg?2g==0=H%H%t#qUEeBtfWQscPVpD zrWdv!M^F2ZFv=vpg)54@u`yHR3FG^bXUa2AxgKvxWJGN}wC}Eb?C#O+MkobVr6FHdh{(czs!L{MKUNj{ zED;7X)@UTCzAUqUBj7hg?c&r4H$nzikm98OgftgMB=^Pk=Q_QQED-J(oO873k1~j! zm?%js`+*$<|NL!Exr^6Zo>GwY3Cp6vk2$n8$uLX_Fes=URQg*iB;pG;4$f+4(UG?i z>n-o>7P?0D7e`8;?B#WZg4*LLC3WPaX^eeKJ$K1ghKy6xB^=z5_S7_^%$KRcuM zp)lRnp_$pj!SA0Efw7L!ibzgGbK9F!vpUigx&)uelYv+dtedUs%n{QI_yLhnWc#Eib^234dx@l7d{WJD~(Bq$95s}S% z1Z2c7li<<3^7{`+T3%$VYouh5#T`h;i5;n&jWIWMz3~vqsVO8$z6uFUagkwF`rKZU zZh9t}#UYF6NsjVS7g|DnoLBbB8Nq!fD^zEB1Zh*q-Vhe8S393JwN@)b{>$ohAVQL*S+G)M3G?Xtm?LW;Kdh= z_$Z5b|JK(B-#x<2fVkAqw|96r8wncV_KAUR7ilA9@;9pB4bC3oGb+VHG9lBl#$)Br zg+w?O7Hx7oI)v|K72{Ep%-dmtoj6K*H30lW?=b~b>~0yM=rWjp9@e; z&&R}FogMh!_@7T^F=ZhpS*Z`r z_bopJMP+`Pzh`f5@u*Nzn>!l{dIw#ZIx9WN27%2U_lfGc*pXGR;2Q24Pkxawp zv<**(9`c5x*e*pA0#?*kl*~HXXX3%HrpV7iMXdFUSyHok-v?VJO;wfdc~MR$9&DOs z)M+T{e3;a^s4tk0*t(r(t0(w=V-#f3?;5T!c@PJi z6HE0`6Z3UM!J7(CV-F#(DEzs>H7g#MmtUW6FvmY1mSY`lmSpaD2_?Ix`0llFmMw04 zOeLVCf0spKBmN08XPbQZ1=a5wVCk%43_*}iJuk?j0P+HFNe8iN%1y@#eh z%g#LYa};}#*HQs;9=Fp5ARDPAMm8abK&EiZVh zw+kao!uU)wG)%tvY!19lQrcS*ylPD$C~u9hax*pU*cO|c9J4mAv7Al5mW4*ywKgT% z4SS}9=`j`7@#Oo$?!JQX12$9F`9U|zM;bUS$Yof^itGmWKn=DbiZH=(^nZMvM5x;0 zWY)$z$Rd&+tFRp*Nh_+ViScfBBRCuT>Q6sCwUOfqxE}0{$QNr({Yv7`S?=^e<@1|N>6((s zJ+@s}G)@ytC6b+4Px5+xrz!1Unfx4a1K5!fzkBG^2wV}6g#bPG@c6hzrF8i zlul8o!xD?g42lZq{c=Apy`D-w-K%89p+@oO>l9lbve1GZ;%&#pce z-JE)^*Z$t`Btpixs(83KDIuY67XdD?$OSj=JbJ6s?*k(MJx#Ruw+tQ=dM77S@1N~< z=k0cj8Hwn}VJh>lgs6QNt`!pxN%TXd z_0KfGd{$>=%I35FkFM{Ir?UV5zs_-N*?Z5Bb&zbw$}A$Y#1SGA+1s&YQzTg>yHFv@ zIH=GtLP=(ly$Q!SzxV0BKX-Tc_wjo?JpAFD%XMAv@p`|;^Z9)FzK{@@k|hbz(a~Z2 z&n$tYT@tQ!t(~$02(tbtLqAlPu^FX=XdFy+%ASumtVU^GlAcjl3fXSd)TS-L~*e3HUjuaajh?+yvoJrr6-+eUAGH557q}h`fChahVXA zk%;HJ3d53cJ3Bj-KwE)}-9qd_PD<6M$b1d9>?e%3eLkL0*)7@gw zv<~HNoy62DtLBf@KOOf`M?H?81FJK>MIFicp3?9BHdIZv+UxCw)|RLTdyr?sIIRC% z=4?*Ln^Av-w{Oh~R|k3)5cCt)C8M-ojx!Ajr#rkdD|ldoQcx(n;_U284Lv$|MTo?J zS^sNJyI`1N2#~cZ78c!R?cn5vHD~f36Vto|PbfboQQf%Jbm2VtG^>WaJD;_~AP{C& zGCi?nwgF?_xvl-JR-vC!vEcXSsC7l@7;?l65_@ zQd`#Dq*f0(Bz=aD0#98lnmaMyCzITs!0+)m7sP-?7cO2j_xFEpi)*y_UXm-AMiMdd z#P4HYzHbIb3-WHK#kF4hB2(wm{~+gbr1lk7q34g$Cy#^HvrmrUYaMy0X*|su8TPbc9~G9l-*Zh&4rTcuTnP6zNk_@Dpk6$vxn9PT zd{UYF{+>1S=smKyJ4u>vO^?dl+n#I;pM5QT#3x@;tCv|zUvAKS$4Qm-Z~_Rk0H09z z&&od9n)MNMuJTqdsIe~ z)vM+q**s4HRUu5N)(WMV#t9FmDSxxY%bMjXM``snCyWTYb|CR-&EhZ4b41avrYzfX{#<_zb@n7a3*LR4R+it52>sbluh_trCUay1YM}Fgekk^HW1*4Jjfm zrBassuv3sq+vBUrk1AbX`hdm9Kh;uTmdio@JXEv;q+i6s>o2*By%w%YzgHqY zw9w;im-{N!^f0}&WxQT`w5AwSW-cR_2XJ0EY3%JfH@Y&e3yk(;jRegAa9yQVqrRcGivbx_nm`GphxZo?(aW&$q7wXXL zMqcxE)KplORV354{%LUX4dH$Q{E(}Qiymrq=t4{B1!v>EL|uaSzD+LlHXHi{6z_LY z86Dy_4an(F16QeOTmy#N4*DOY3^&j?neYTuH(Rc>brl2nEK{%`nU}$bV%^HwR zv)BmBI_i8gJE&%0|?;og(!^(**$gzw4JSTi-Ex&in-oi#s;oII# ze{hdEFjl?wP!8|Lt7|nFfgk$9)uuH%J#p(Qjd!thoN5%4*^{N93*1-`TW-t05x9* zk9qm_G{imi^QYesh^#E(*wiR@ry^Ng1dEOo&tbZ0I9_Ul#Fti9<)CHW+~CsDy>%_5 zea-mV-NKPuTw<=@S`FO4iN{$4y4Urx+EcJ!O`;b%_tC1d9~XJ&Tijt#ukQ-|A%u$Q)w3=Kq8)wg16 zVx@P7X>Q|mJNbB3gQ6M%pv36%)>q>K_6&$T4bzO6ymqwGD!TpZnbDiW*_m)V^d$d- zZ*{FoJSKFVR~@B%U75AfKwLhE0t+Ngx3At)+sJlxx#pYyr6?(;XWp&$!%6wd0ygI> z#|O6t8*=hFeQh0Fhj0^S3=UeZS6un^lIXeaIg`XJ=02yc3moGL6Y=ipRCKP+xz``a z_Tx(K@@Q>N-i?eKy**`jzlhGJZGH(%4aM(Gx8As{J7hmQG}}ay_c?x(-P`t-QR{n! zi&?CQ2cz5471QN{=YkQF3P4?UFc4Bw$}LUEU`!fC3l4J~dqczl;P)C!{w)^kf#`rE z)u;59$B%pIwv3ucCE+gDQo7ySw0|ThPd6k8qvwq{fk5IyBy6kip{(}yBPUp!Mklg0 zW)Ii;4pyncE}o;co;nQG$cv9}U!~N2KFhZ5;R9!#Y@_TZ$ojJ%YUb<1nL&LJ+ip~B zuuitMy&wEGYfcPB!cFLH8n%Q=nMxVSzTCgr%W#G;b% zIddl{^x=Z)zkb#X;jDXhKr=OL{F%g*tnyvNH}FinlG6DeT(G1Ahd|c%hn-n52tCaI zez4qdy{#k_$SHZFwp6(M=D!ZSKzugKDQW5Zo6umb3}gQPt^k)#BH%z5!S#*)$>*T1 zlt(%l9d)1%7ZL#8Pq$L;LjD;j7DaKZJ@dT`9m=-=Pg!%D*Af9B@p4T-@3w|uymvyLw!8)zl{H4xaTjyPWh z)*rFHK0d>xymI{i41Gif1BM8a5=1x>h!JSS-##sHM$Q9NL68t;ef@^%A2SsXl>{3Z z8M%@|^zf(ujv}yx4p!qQ5dQCe;5cVZ%jia(ofH;82L~O|rDo zf!^WeJz6w=pjOeSt5ebihfJk)W-z=Sa`u?Nl8f}f}wBbP@Ae>2C4@I*^wF3@o&C8cxC0~~vBY#Dp)15)9 zjH$svZ-XEv43n4zlDsf<%rN<%-D+S2(dBn-4~~vLDkZwjNjfB@2s~kfFrssz#j{b# zsj1$Vtj4K+ZyivQ1+oMJO(sWje=tVJ>Z`w(*qU@EJCG;0U#qg)DpVCWRjGVrVa<_C_&5{ z!zF%9ojp)Od=$=_#v2=haLrf8$LAbw(1Z+?z1&qX_s4lnOso8|mL_B>>#rDqLs+&@ z4uEY74-zFSf|8W=dEMaI3yP;sl~`2UK0Yoi{Oo%Ea=Q9`0x-I4r4SiNOW;o+57vu3 z!3K?qV`io=bAo%L#wy;7?~B?oji~LzI$>);zi%@dx-&*0P%-ot>;vHSki_nL@nN)k zkq30k-EH|cFEGi>@nA_3FHj|9&_sZxm`4N^u>yYW9$A~kYceXT-U^s-CQ-fe&Q$ZJ z7fIQtstU_7t^mbV%br#ix7$qV>J*5zyyzyu;r1>sR7Xu@(bJ+Lt;W^!F8Suaj(0f( z2`OovWHvrE8uJG(s}ESn6hLj5#e**7`DU(35Y>TmxO8Ry!Tdv9tMS?L0{pcT86;xH z?&n&!7e6eROL>^G99<~#qt|gw1;~wQldBP`a>}K%XHZAGw0M4GcA;CZOfGxsgcal&;wOE7(}WLiZvP!AFv9h z(ST*PG1X&1sa*(?(h?VH_4w$aB6n7E2T`?%)kB{Pp+C}1wvAC|PTldCsAAimx#Y0$ zE;Z$6p<}Gp$6g|3LGxiyT{~W{k48N-WBQV`WVCkIqzqN4G29`3X)=YhQMfq z0l}=(`^cD69q$_(dz=Eodn46tGgT(u-D}(*?qSNOR)tZV$vo-LDB|v3Afd%};jvhZEd{OHtEY^&pdXv9GlyCWI}@^L z0fVT7D}XB0qf9bLx}w57qNs{-?PWz2$JdGq|Shp_@linXA!aXy{|Ey@j2FRd#y19Asqb#DeC z1S>ig5@1-MR2yTw9O;o5dnfi!!N6#+%7&|UWzl@Abd^N7$u zB+Z7s1igc3UmFt)y>a^D@TF=a#bOO#-%sULDN1q*Dy4qQ`6hZtETw4k2xI(*23%M0 z$h6HHZ++nD=>1@3{utU$oKHh`>2()Vl4etMK z3=8H9zCc#lttE4Rb2sV8Z(pe_^wAOF+@m#u8uN`5XBZEg1J@?o)+hr=$gKzf!$jpf z6{iLI1_vKDw}CYKl`F$8T{4P`Rju5q?gs)96CSNi)|YTOSL*znwjtr@EeC_PTd2FH z;X8TcOw87v%R|d2k7Va>1an06bA8^`g`->yYe`LD#{7p6G?oHODB|TQ}Z{8M+-r z@wCrG;b0-7f$c*9&W$u)SUy;1x?Mz5*iP((+^8|!NoX=}r`H^M8^m(X;|{P$lH}J7 z4JUy=G$N2x41taFhS1>!)yCX;&2kGR};dtu6_h|&^sNqd$aelWDIU<(XF|T(LHp*(Q!i?XW zo;S@%T%g}_EA}R>RK~)74O=pl6!twt^P{skY1bb)knQd5ku}u*(zC{sXJ9Mjj{6f^ z_g%W*o=NsmAX4)kfcwq}Nw`k?YDwu3JyNOD2%X87oDhkGqaTpTsY84?AY7gzSYDpK zVvGMF-?|swc*M&0#`FfKXv*NR7-e!JNFgANk(Q1M$fjECKGqC>22N~kVIVJgAMj#} z@4z@Yz$WJF6@*ceo291LoinM>hKC_Q_o2b3PVcs8i1;C^4WYIE{Ra}Qq|^BRVCB;~ zbG^!yCZA)JB;}) zd8@xbfbu^hz(-%aI0U+9yhwiHM=zoPQ#;Ml6eVJdL##PqRG?iA7a-Hzs!@L~6DyBb zyD*1O4)43LjpJX<9Yz)D6x=&E+(V_jGt%|Q)yArtU5VZ7T>{Ba*pC^UXh4?30lB z%KvupEh20({^)c8v(P+RZZD1UKG{yeJ}UXKrO{3}u}q|})yJvX;>_h>!4RES!XTBk19rP^d+gG%BjQM zUOh?d9D22~)*qX7o}Ej%(R};B@8v>(^)Us-*u1#X&;pZ%IXmUp@aTP(OV4*{ZZG)S zCZ5u6z~UKMH)yW=k32Sgk)s5-X&e0vDIq(PJA=XnFOA)Olfpf$zvbSGC4zL+twf(j zP;|^J+TmXioPt^nh7!t@j;_YZNXpvtM60(LJqgEhg)N1H$Ga>EVNUNmR?%$>5(+H) zlZuaUb5VqRRGU#z*Hf@~*2G#j#5fP{yk@UFC5Y!>%Rbgb z!iK93I=_Bwddm6D(2Xfpc377L?KB85HmsS9!#cS;T{=^OM`f+)%SsUPExs-MZ~*IF6wf`ITc(3~ z`6f8`#efi%-eDF0y*ZSzqew-0rvZ^g|3?5~b;RO&NQNQ(8YyC3D+ipK5P% z7h}&=*8pTBST;0_7zbxcg;VY_c9AMh4@L!l_GMMqF(Q(gj|lk851%Ap1OQ!JY4&_m zW#B3D;nNsJXlcRN^%h1Q)D{2??L4F)B@R=MFw8%=8n6;G5qc?6-EiJwy}cASC^a4~ zQon8|%9Kl5M!IePLf!tYRMokK0z00G-D)$ahiEVgSAsi{nh+AZ9{xd=r)zLGZjx1w zMhrQcV1e$k@#+@3nS?jEG>K^VmLPVSO()ZLH#8RJJLda&kKH%y^Gsc<%SQXIHfo>k zwS6wyrO4w@XQ-o!MtS9JoO=e`fpepcF3m!t;QC0dzViKVtoY0gjL?F9qLWjw6F$i# zTmc3;!j*HuoMtr+-<<{Co!1Vk&Yui@eZ0Ihzry2*?7QK_%u11l^(j{>)5OrtvzkJ;lKAIl7x^3MmmC-ck(B*K! zvv)LXfBA{dTv4s45YI>ghX(CwwcKeX)(oX{6?H^e0Z$$pLT^wRst)tct0&WaE;=p$ zK@uT&r?;RIwvXBOmE^t*R*lKt_hOOTMCr^G?1xiV^yA5lFSAxSW-^?UTIiLahXl6p zA+fiI9~mOw~Fj ze+(P-P7svRxogj8$gIHdV06`FyYV&2I*j?g(Kybv!MEIWuZMq)=3R|^=tR; z-MeM^N=zRMhjXsf7BZAavcV2m(YCBA?Q2vH=1|r(a;jq=h)>1u;m5dMZ&_Z)&-Lr+ z9~JV_AJ#p(Zko*H>lzebbav@(f{(-o5ikAZdwio=H8(ApA#Sp@_0)w7RBfVmxO>mR zQHfKEMtEOi5BYX#1_f*ud{sr5zObBBjxCc0-^r!(`JRo9dsV}8wUA!Mt6SvWY0?k4 z9j#uHAC10WLq@N)``VK*FU0VPOE2I5q@$n6FNG6L5ufvD@+^1}nrwovr5I_L%@4k8 zzWR3Vt>yCNF7c~o9aD5~U|v(Uoy^_`>!BDMIxBsmfvCtMyFT59Hy`ZIofTd&G=eHs zB2e_G)vwQ`0#7$(AjAm>KjJ(+ruSq%zP>=ksEZaLcZ^6eY(3hRaYE42(j!ANj}eyT z!vpn)dq;*+7t69*ZrpdZ3*RV2fu9u@L^$dC^bEz(Xt9_czx#pYN_Bcq2N6-eL}5$EX?;)I5VHBV`qHIP)USkChC z6o~cu=h1~7Oi`^b76=;Md;7D$s-P)MMt>=l{b%a;S&>+| zg~*VG)E>3{C5Bv=Nh%_aH)<=LCn=IPdB<*qV=mPVagN6GblnV!bVN*p&`9bSlYyOZ z7T(RnVSW+D7mD2=^751BXhhri#S zz^`@3UJ-g(caNn~$iwDbxCOxv(YC|%iKgAn8$tzt@5D|TdKL4TSOa;Tj z#+BgI3&v^d#Fqq}w%*=0v-RN8AJ>|w*_Sa0FG}SJ{7&bNHAtz@Qfu9RGbDdTjWaGJ z;JzB`m!Vv#>!CNEPqI9e#W7#w)jijCWAvG4{NCA4OcAk4WbSnh_qA+PM0vTW9H{Rj z=Q=`+k8%SZav~CT%q~%{3_evyq_HvQPXR0-!RO=c7D+HHtOPz^Fp4Ln`lOOk;xn}| z4wr+MIQy4#=F1DCtu|Gjm(G#9$NZ zuzESJq&42Li%+zB3G_iw+P7I~_LGsV0h&f0HbhK1Yw|7Y$$D>E#VXu}5p3%AI{6nZ zZ%a7FbUd&~dZ;oJ^MuW;&v|D&*yH?0;xkY0jP)vMYgsfU?^@_ zk9Z-VtxXgAovh2^axufkcBrsIwC5hvrHW~%^$mSqcJy{|-nD%g@iIS-cPKJCgp`No z`sELJ&n9M$$lh|51Q*PJ)(_l+OA+QU^a1MUB29(4yO>z9-m*92w3@iCx@-ICGFE{y z*85a4-w!Jy+L#=DVv;rYhb}hqxa)BQWV-8x^_;i$KdGo!uvp!yRxdzIBHq4!mNHk< zK83G~#mCP47Hrl~QIuAn_9U(#n#YkkgXZuJzcJ9Evje&qF?s|o4`tW|Wv5o6vepYV z=!hP&mjO>0Dw-Y1Z#tggHSlU}Yg@Ytw@y%9Yo2`Xr}y*ufjgZFhk5iHAD9h~S!@9t zjncKSr9PIZMfInxa^dtLDHA4VSg_j~Gi40cXsfDAJERrQM6G!Q-IWri#MyFk&3bC9 zqKMk6^%Nm=h|biKu3H;wS<^Zf1X6ejM3_|g)L601XBPSF zDy6E|GBIY+6lFeh1yNj(*mysar|<6lq1Ab6mGaf@$+;+OViD&Oexl9!F*)07nwAUY zvPYI{Y;i5LAu8YJ=!Lr{PQSC|^0H8h>(CKP4{ovBl(-8w zs(A!w(=^#F7va3 z`U;zF?77ME9Qwb0treOky#z#Il8j!smo&ySb=9oIx;ZQ=NqdUBl3%8;qh`fx=$s!6 zda5J^-bg3?Y@TR5H}Yb{BxP0mb~Xv}N$`skjI}6L8MjfRp|?+DT@nPr$J@KM?DJ4n zTY7I69nBdgAA9Zlne-RW zF1`TBY+3P3mxLU(xIDX}>t^b$8;`x9-ZoGO=5;uyoGD`-M*RZ6j>`C?*L?GRrqbG` z3T)K)rQfot)%wkM&_)&5C(lX;pAHlqSW;EUy=e{WJ$wXcNU@=qU{EZhT z>|tI8-C4EyNFw>G&6&6yg^_m0N7oEDyshy1_2wlnXqRu2Vdm!-dbVYr6jsd;z#fhJ z1tZFcG+Gi={ct-QHbXVS&~A)#WR3=Z)!-NvDaXY9OtM_sD*?}|D0-Y}&7-q2+qT~) z?Hb>WAJG}wY^PeSr>wAL4tCxTr)&{?PexHG;okx+GKwiKhq~@U`nL?9-HX}We+kOFG8SNmD+K=t4b?0$FZ|b zXf>4!+#rj0yR^EzoF)PQvFbdhG>1NY0_z`_`({j(9%}&29_&n}dw+)o%-y>sYC!jR zAvWa&mZ5X!E$a`4{m8|uR;)|BWQ;O%XstDwXjgHCX94GiggH45cNo<2xA^RL@%r12 zO+-3&*(T9(`_x`lvuLS%(EREUv(?watB!tba0omQyn3-X> z)=-UGw@7&v-s4;1bcW3DoU9ZvBrPd67(O`aR&7(#&UE8{n2*%e9`rL(l6t?!&7m-# zp{@Kzlm0M9k)K{J=fml25-=OQf$UGqW)%&n1QtPdITb-{bgT*ZIM=(bBpc!lVcD(=6@O{gWZ$7;$ z!IADP5g;XZdXD&Qnpgx-z{;bm2Zcpl*!L4>ZgRA4oPE4-B!!Ujh!er2hWVWdT__=L z%+P#*YSqTjljG8yu~INz#yff^S;=VISi{1omgM#NO259|4kL$dz(;7jwdWEN_Jj@m ze6(SNv^2kArcg2DpejDd+CwGY$;>Jmm}S4dU+jc{O^&VJ2QQGhsRp9=G+Su7zXYg^V1Lzn2~{0bG_N?-V#m|( zTIl7sLB7jFQ>eFgVLoSoAm+e53RCQ8)qrv&{8g)>cORLQC05-eV@4&;$6VVJ;6ltNyvt_UK=sDl>g&#!e?hxg z!uskbHhy{ju0O2bNntfgC|*zOSICqJ@8TW4TNhjE`J96qr!@5w3|%KN8j@>cHQ)+WD@Xjrb&8SlP~U+bl2Eezm!6c9-k) zmbw+@)wj?4moCZf+aEZjgj+?>-2icWOv;QaDxk;4xiD&63^JKGra8YTEr> zD9tqL5opBRRBuns_LY;B#l};E*LCYoL6pdv<}7c9Rn7**Et(zil5Rm!PowO&aef_+ zCw(LJy0W8<*{U^$Q7uFOjyd$;G++1icVzKOcL++k#S7O@TucVs8(#8BkT|X2`q7d^ z&P&02?KBksoqd7laI~!YKKE%KZThC4c~KPSDVvB3q+WQ{xITo@!+thNDo~(%o%e_4 zHrGo|oLMR`u`j~swf2`>uy|Mt$529>t9RTzQRWlZy&pia;iiY#6XWad1~JJ;I|>6d z7mRl*@0a`(pa6@VgQ77u=(ggX2H;Wg6`@L)11QxTT0g5-`;Z0n8gJNMu9ee&d83Nl z8&cW6-ABQ4BidlyM&U-J=5YT>oJ18}LZAQn4iaT{{_;(B4iSK5A72SEeqPqHXU_Sc58yn}Z{e=MQ{i%$>i3P-f9*#bsxL!=n zt)j=T$e_-Mje72@okIbcq$|+c@B1Ha2Z-xpdqRygH97;=lvi2TE`_2&tu2&u);!C6 zm6}^aT?RXQUFBi>kIk3c-B?Zk746Dv_719UE3D4T zw(H`11F+2xemqBV)?fQV@1h=%8Qt$SUwp?kiZ>;Knuz+1c5(XZV~*L$2DeAiY_Z6V z2h6M*`nnlK^?WyMvwLhwZ)yjhE+ZlrjYdmg61cEXz)%@{WCL^*c8;`$U~_w_zaIguT^Mc0zuV>Bvd zTBS9UcyzJPb{}_T$Kl1rr1t8gsL4~4h+Su48+=Uahia;G%hwzaI&b98K7Wb{<{DWH zXx#5kkVxtYUOjfBB;aJs6g+BDt~xOJR4rN%+Yoz$!r_=yQ>PkdlnIYBBi-jo9B4ih zd_i?jqK8tJ;*zy})1W5##S6!9xa3oY?6<9-COjP!^ocS_RofY0*x|w{R`79~>uPsJ zpN17mZc(oIUTc>5ri%M;Xt%c5TDD=POhb)9&ZaM7DEAB6@Z46GO7GX$&PQPH3V{ZR zcO-{E_xR>!3NiR!X!dFAzmU(MK`l-^?tPXThn=2nj*9B zF2v!FX}9c-C0;+Q;+zwhn;B1CSwH#w`FK#}Q8<|^(Z*Cb30|xryyi6})p~fO=y46a zR0FpWwF1=*)kJZaHnas`>{V5gF-)*u#Y>~G2NJT!7HkdR+3=chd{{FAZJa8*bGI0Y zkPP*Gc41$BY+(V#Z9&%ATZG+hUHe=SZr+}TI_%Aw>pQIJ5ANa~8m=CE_N3A_Zx1gq zkP`LQp*&1S5`#Y)*!%SbGPnb(qHUF_se5nj<70#Ss)m$Q^Sa!D+8mcPbkuVeI(Bzb z>M#4SlGvSV>vEiNaq8PUjUo|Hd(Wza|30L9bPj27;^YyZb4Kye z!IlNu&dHK5KFxq^5!;d@ZOiEaFo5Y z6n@4?52bk^M7f~Vob_R!Ml(7tSOJF4bzCn@T|4v$ndT4%6!MwMAFONKsW@(uE1y!5 z%buF&lm5Ix+1}vRjJ~ZZE{ZnevfmY+Vl_kMLZ&20M@rdGw+`_4Z9UI;xm&y-tvmn2 zVNMp{|7y9_7BlyZ3RghV5n7G<$e;kQOx%Y&931C?zfe4D;t0tfX;hwiKj4KRlulD| z;lP=sFp5(k9)R^6yfUrx+q^vO0!1HGw}hov(QGq6;8np};!l@9%kynKTLh!s2!Dgm zuI~L-gD(t=x&iOtB#Eh&j_{M-AfM)r4YWE$x-^|9iu_)s@}pirj^4&PoV|cgtl{<( zb&vBzpX4G3CP{_qkEGg@{9EhC033YVUZ3x91bav%oc~HS6@PZ?rq5PX!IIMi%`{J? z8Xv_ktrOn7>>(M3;uSev$le}^Tq>EQ-V>_-aa$xEi+n#t*07LqWxG#acnE)Z)A}PS z8i!w5yf7$KeRVx=k%x3OLtgS+0N36pY2Uf<)ac`6Tc46Wfku*P|+yd2~wc8P>jVJl%`~`AK7vS?8e=XY}_R@%k|BuDf6yw-fmbm;Yp%z z`o_A|KutoHk)}cYevgJSuv0FLV`Dn>F;h~soo5^I0w{Gc7}r|)Fs~d&9MH@yitJJM zbvBfu4-8|6EFEI%`>1hUA2cF@Wy{o0&6%@J(LTJ_9ZE^!`A}Wb^A+BG7@uSrrl7>@ z{rCtU;9H)#W3^u1*l10FB=hb4Qrl7I%2IPsOjieRqNr!2Zthbzn94Putetv#UP5(8 zetg|3RIBKgT#Lq#;@8+9XOVY-)jLCSO8dDxJ-J@9$ravk@r8xl%5w`pbk}oLU3kIyfM9+6f&Y5WjaSNW#I!ufm4Yx^b0cXz>^s^riZ#cAdwkUx$8 zhG)oCvcF0S^Wex_75>>O)zqk+U48Cur!~BZ?YyXd_>6O9oC><=HWzr>e#3hOJ3z5J1^xpGF6f zMBaqFKukn3HJkZ{IEjUokM;GdjIb&b=Ml9ke<#mdcQvRPYoktGnm)Z+JDF-}*F@^! z>C2`yQJXxjBHbSE3z}8(98!;s1yfeSCy!*Lv(IjOub#G48;nznIFnYLbFG2I&=JR^ zKq)Aov4&c_g7aS$xW;!+OspP#b@T9h=S#AE&?{>QbkQ104L@-3owYmCmOELm)VpNz zMswC($X)ishZEQSKj`X&e&Ws0(9xDh-s4JxVU58@jZ{BobDvp$IP?;|F5ayw|M-e? zN!~5z!S_qIy}S07nmSwX2kR3yD`uXk+<$6h@|KT}n0 zJ|-ehuFYTL*iC=vlp=IFOUjOWo;}}893D@BO@IZMXXWkx2*38JD%Zc=oZEO`HVMb9 zOt%8{`Hj0vxf7jr>Hlp62#~p}#)_C^J_Nz^qbPan{;Qj z;#zsQly|1BX`Ik#?A_L0(P_V*GIa?C;H}CY2ISWvXqkaiECbq_ovyB_Yd-V(FRWvN z0W?K`saV{x4Ju!+pz`F8PCl}H_r%2P)NR99XoXUIJ4pnRBogVkT{kF6z*Rh76=|e} zvEuiCE)Sq6rl+Sh>z=az4;dYSgd^Xh4~ixaOU~XK9P^*Z6Jdu^qQ$BjG^Ki32CvU} zMQF@!WOE^q!%-r};;?|XqNT(48S8leg)oKO+Wq|f#SRp%JiAxe4V|I-*G9j7TZ`T{k%e$>juUiPi91-e@xZDdk zhRGAq(Y1hwf(Tmbdcf~Dx3nz9;c#;Z5&hQlXaB;;hmd1kIztX!Umu%^lZCv0N;OQv zn_q8)X1AtJcXV`&XsiL*6Ws(>tnX^@#D0ZbXJ{4!D+kBGj-iK#)TXNS_-8_65-w;e zDIj(Rfh0ydI%>P!`xtwZm9%ZK4UJ# zdg$CfJFa~&x1tLm&jJ7H-XSbTY- z^^prpbsm7go={n}KY@)Trc4m#72=Pe0%2Ji-?dZbXW$@h`H>om#&;;c{|6?WCJAGm zo;dg+KeRVnP;gz#Xc^dvx$uQ-$}y-U=yLZg=sa-ab*bhKW6V2(9*6b0D;zONCD)!n zkUFHVtzmYYAiSqaT-e%bCnqn@a>0T^_wNU+r^SvQfufpmF;h+|?)kMZ50JQC)9Y|O zD1Ihinh7JOmbq_R_|rfdxCoIS88Gqz00ZC(^re7rf8V~iRfy~_3IVSc+$xJkN^q0X zfQr>zP;md{5pK~p&!l=P(2v0dRO&$c93rUuKX`vSD4Qjg3azRqx zNied|P5795wgNyyrL-_t5<5yi<~_sBEyWxGM8gQhEYcuAT|O^9C31}15n$*rNfFN) zztOP*Q6fF+sN$G3B250&w{1GmNSd&GL}*}1lLERJ`Ktx@PSP6U+cYrVqe8GT_dqg} zfd)j)psYm@I~SKZK%(D}#G8zs2v8%p(gM&tw-*@+f{){sE>qp>)7S>)eetiiZ4|#U9uXX&1uxE(?^b@Z2MxS}p{{r861*ynm2>9k2 zmH)CC(pKkhpEokers(_p**Wzx(7mIlMbl&dVl$+v?hc+eHU}2wJQDi?b@C@;UgC(FTpgsnXMgX4{6%|n!0{X%~ z+zyC*Sc?FEyl@w&0Rz#L>wmu$I0s-SZ7;J#fZAzHAMz0bw54&o<24xj8>S8b^o{uh zI3SyI?tSF)pDlopJdZ%{3Hu`yfwBGC{h^Hf-yo^@I{>u4-c;<(mn=R`0unArOsC;8Zy!)r0DHKo z6o%GU|Igl-w5CbV*MrYVlDnk(f#vs&Zj$lWG!-LgVSRv+U`bSCcDmy9Z!$@-AjZX5 z5N^}pd4J@EubU{_xvTV^pnWq|sLlN1&KlEVnRPb z$-QgF&rNSd13QQ0Q?R{40}F_vV~-8APfk9#Q&Z|(PX2qG-!lx?x!uwH<}69nrTkzC zkdJ<)A>H5v-VdL>6)3&@-ZTVmhl7JdMRW5dVMzNwT`R1fU(d6J)9Tw#vR(O);VFR{ zj6m(|YA|EbalteUI0?xM*Xdu9p(z0K1*jDh^m_h})d}7I36c>cb>M`|+im==P!`rr z7=#TD%z_vfF10^AAa0)N2bae0W#&hR&FD>JmM{(8Suv+%7Oyc>1eC6SG0QUAfB+C+ zN!k!&TULsHGs}d8Yd~o%?|_C#HFw2&<6gvqm=YAga62E$Xy@Gr;d(s$PsUChfcf$O zdawPw^LOykzYW17%HXZJewI==?SED?dW(jNYR;+P$=@ogc%r|$eW=r`zdxo$j|FGv zf5Udo_# zIpalF%?G9F2o4&`STZm$R5Ui; zRo5#V1a?!zcjtT{EoLelcn61?+LRYTn7!w1ZzKQ4ePyiw#(Dc&P1yJvYwtL-@ z%LE!4fi9|ItMXM7p3QIyi7YIIfu5HCHiBpxLY;GHWTe6}^xLz1f-jUo=W@o6t(qUW zP`pdwLlx#hM#Esrep&B-t+-|c?^C?M?0J8G`I`g_3QnoPG20) z_s~F)HV+I~zxDbVLvl_fA3X`X7Mtp0ZsiGP|K31np)Y{W1Q8Bl(7&Vfe;w+05CKt^ zkE}HKf3JRZ&|?iN)0@3;v&q4EoCKLex7sZhO^2lp*Xlc>b6%eUP zp;w4E1Iq%KZS+14X9m0i0=#Nre*THEzN_}{b3jN4Q=ZQ{H^02>M32oFCowygyUl@?nOrQw^ zkG2}#H{4ao8XTOHlm0&!;&vt$%(L&S+4t|ySks;Jpzj0LU^4k!5a9?lA&|0mH6}-D zid(tJ{xP6f893`UK{A)Hd&8_txQF;6tiWaQF^R%o^(KK1289s@90(jkMMl2|9{kH^ z&z=PplKvB}?c%=%KO0xdaMzIfdA!_u@KJ_=dBqe}4Ts-XMEv`CyGi-| za@bc4tYbO$(2@1JP0`o6;QgPkxyDL8CR%0(ADU*Cd{n>5c-m|AY_*5en;*=Hw`)bs zZ@xd!H2A?t$F`U_eD{gPnb4k4Cu8^-_%6s!%&hw20ryR3%5Q6evP-+$HA2ntU+wzq@U}I6Vne3SxA)e-7^X5ZbpNgZ}#8An7CF z+>_f)L&SU;NZ|4QeTBL|8Nqk#o9s9O-R_rD&<6YwM6`X7C*(ph&m578I#LjhwYIbC z4gJ*{{C_>Y`#j)VYY(5yzPiaU1LFU~*!x?}!iBKHozvcZ+oT zIcivJVA$pdc_#(VEa%>QUtOe7Iu=Ie0x3WzqkIyv^8=;c7FOaNMpOfjo6On&_eO%A z+Y3XF77thaTNz>zLB{Gt>Z^$u$ciK_3xDzX^6P6 zDN7ioEK#Ur&5|uq*0P(CEoIFf8D&edM1`_U_BBGJWFPxBV;#oK_ZgLUeg1>*Z;I!6 z?mf%vyw17j-aGb4_eT2-+oxeDYLxe_nX))o@%~aFQJFeLDIMWbqvwBwJ*^N3ZgUfY z%=#(Pt)^gZqC*Vt)2X7g0Apwj&I<0g9H{r_Pr(T(p0~VnN6gUoMbi|TOh9{TN?(Y@ z5JBy?ceXS03SwAMe$2@nb|AkK6%)IsbFSgPE8TaFsXFVl8t3a}jP)G&fu{Up3=+7gPe9@(aO^UNnD{(?YyTV72++mQEe16T;$&L~Q#qo%hqhw&Uvm*EE-AT^uHtVJ zKxnf_9MxOP9nL;GvjDN=1V{0@)+jiIhuRu-8M-&4j5Dxl5)!gg zzazyeE*QC|9nfpK+ExfFQz6d*(e2l9s%W}zg9B58hU-UNzybkLgmS0-H!93Oj)_W0 zzdKm2PueOXe;5R>p*5{N9toQS6GfCl;7pY-Y_?lchp|rajpCl=^9L?6XZ#?8+siKl z(|)=?TMUmWa6o(ko`}@viswhbqcX7I#^a7`VCG29Kb{si*IzUxse9d@D^mTB1FZk{ zGr%2?lzBZ@d;g?Gg8H7+u0avRWwj`BS(*K@vFaiU$efx=@V#g$oe~ea%7c5!i&lRR z>@G`2wMJ&=6dE}s_9dxvyi%G+*4k<$^D3fhf%+Mni(E{AtQwwNl{KX$WJdIS2 zAbT>R?oQ-g;YmX&1B{shxhLL0i%}g^|EF@!S86cuP=>_A zR*6(*#>O)-HjnssN@ffQi-vGu zHCq?ZwZWP}%-fXNCWCw`>puuT+9d_m*;L|-ZtL#8!nkib+3S&DU>L@F8qw z@9$Bt4{9SI-c^jTwLzqLRD}aoD1+KH;^ozu9n4d3$n=>$v*B zw{O!K!HnEWwd$H(fN>ND7u4hZ*XcZ-2IfUU0U*nFErjZRy!(cX7< zCjk!D0oWvMFST`~%6$|}exf-7fCkJ1JU#p20-!CK4=_+uO##!_`%Xme>9CnEeQs_p zB%N|n-IIqfpG)9}ib^d;1yEy1uqzwiB8)zG9onU(y5}MuVVK$3+X}8@=hs)WKIc-L z{uVIpzIf7?)l%8W2O`OU;=K8fy$iU2`>$mI`B8UI{537c%M>FV;4Be@B_CSv&AW;P zPk;!?{?|p0EQ6ch;+YV{;=vF1@=YJ?LPW8AF0x8H!yPt#czq@pofs#qnw@JnAkv9&$x8P^cmPZq`0m&Go2L!VD5w3PovMs({TB&;ZJie^^BXUEPUxyyR=ze|VujqdUCPErUlKrhjFyU;%S${cs zmjD=q{I*tS=J$cX%V_)h1rK<+6u?KD8u&qd1U&lFlMYj8`wIVu=pcHB}UF z!f)cNQ#3RBLE(1I%^P~rcJa>V7ge7qa~>9Fp!wHUfS)AenQ$vW2ln!BOW}xAci<#} zb;f5@80r6f9t^eFWIZA~J3IZF%HL*BF}eF>^5Vy(5;@erb`New6u$mLm0o-W@7$gWJfc+>)^)9!QvcC)oUY1oFcusKiOpH>)#=!ODJ?(+ zxU0Nj1*(1-6cX}_rczKo;2;P0I(WRm#S431(9BSSO=FFdpv39SXvL!`FElL!pzFU` z3dO3h{WYX}6(DT<2FyK3@wqfISmBN(5>^zZddh|aemEZ9F9Di*V&D*)<(+TCAg{!N zjJblxaw(AZFK2%U+*9)LDLFWT$Ge3cy?T(Y9b}#uVmreZDJW<#X|e-$52rY!67Ap+9Ys-nVkW9VNKfg5C2va$i5a@Y&zwfhuRe_Jdc6_`!C6P&{-cs zfv5c2NT7D7`o}agHekui1hDiH(t^JCciQ5|%m61x<Vb@rCxv!NCIR(F+HFvObHYVtBO{|`Ex=*^6BrNv4vz}S zAcy3k25E?K-rl?+bRG{eEpfv12UIA@DFr6}*U`Z3vI=|=Z>&UFGXJ4>fuKq9OnR?O zcDR9bdyzKhD62dGIyDW-F6J(ayyv2sm3J~&vpkd-_T`@l4p5_FAHyG09-u4hgLPpDXnX)usaoUhNoW{;x$IFjjA zh4%o5lIMV@Fp0LWc#PGD*ZKjJWDj)cU+F~SPEkPtwqCNG=6@n4*aI;?qqRW>shj8j zX2#&^dFwmJ{{3-4_XVAQzeFM1U(-*YKHUm(hA!Su@#mp5{27qnN!2*6+z;2lt{}zm zze&GjQ%_H>Zrigno+V#$&pZKBq)9M;w+yfgo#ucvK9FLtl;uho?Lwb#D)}qzQwaR% zxiooO*Am0LtKgRlze*8)%b#elwITE&Ujgc%z<+@rC6Km@{5tPDdH7xj# z{ec`1>yueu<||7vU=E>kjFhI2g9fDB;ngrCfN_&-XZ%=Qo$d~~1^Z?-9=ivm^0TmX zCGRIy%2F_53!2!X&)&4EJyJj`4;v|1<}9a&8F3%ZCL7jlCBy;u1=JGG?nSV6W+O8* zQLyeI2Hs8Iywfhmbgb(P#mSSf`zuo^G^O?XcN4;Feo>Bg{rviP#ojk#Fkg;h%ydP! z+bQ{R=JDXXM9i5ZppJP1WuInhqr$BMaE~qGDMb&^F_)+J-)uehr@VuLtsey|1*=GOh;9{vaCC<8BF-;ZV=?_WByCookgub^rhVns=cE`u5sWm`Db4rVWy z!Dfc8Q&3U*6)*z0Ee%x_hUfexB3pn( zK$qA5fGi}Q44yVuYtyMi+O2Sit{Z#VCIK)bQuI43-E^gtP~aah4+E5y9d;~CP1`Df z4}vLAC=~5a;G-zN57`%X@6hge|HJ^WA{DViretn)VN44P1KZvFKp0rs^U+0VS@9|R zTuTtzg5l+_Or#hl!vwSCp`SGWs*3=RJungmf5>;%vMb@~~rt##CenKG4+z4c#=L_5QKhp9CvOg9cvTBise zhw~UL0sN-4U1y=a3!nugS1BhYCAHNFI|^X=Ni#>gbSaLVvUKq$D@UWo=xD2YA)PPi z25#Vqmz6;wMt?;c7Y{ng8F-X;dh&_`n8W!pq1k7V;9L_xK(C&xbPL1DJwo~c)s<@^ z!l70VlEE63FsBOh-=Q>9dj#Y-DVrM6dOdVAF${^jV{GbcuD6K>AwUgUeE4s4h&l`r+O}2^_JL5q$qzg+ z?Mv6nTjJxIE%7=3j)UN_FfMQ1zV=q11l2NhNNZf-2`p3!W+`bTs+%6!iY6k<>S&7- z;dFhEc1D7j+$uS)+x%sRm{B3soda@Qk#3!=DCCM9GEP~E+(O}?40}pXh3^Il23tsihE|~;zcJpP6kua6J#d1 zO8fI?Y0h`Qs71wVUi`bH#QCZ>cPb0sA{1dkxWkz9SFQ&?T{ zsxEyS^aT-q=DyR#$I3b<+(W6!uRa)m+~NikuT%9SU5SZCftW_oRsvv zkL*4Bcx6*rNl7Ohwk`-FK0?|YaRoM(5HPZ8CxJ3rcP@7kjVHSj(4-B-NhUs(mOi8p z6@>&jC{IGbnw*H}v7r2weG}pTRdZ3WfWy#t;*Ii1F8k>%Bv>3sTSL2&MadcxpCzg* z4M?NpEs3_CcY5T^bh(FHLuXCN!#Rb7QdOJ)tCt!@QOxl}A9PSHzzm(FD805emK4{4 zM-aym63F!=vQQWl#Mm<8dRdKF4S8p&r=*=GbkT;Jo12oA(uFcH#nD{XNGb(M*xmgVYS5OY(atz#X$4$p?Qp9bvjlF$ zWg|lQ`KCfwlw5*eZuq=vEg~fM(z5T zwV+aGp%qT|yZr`_Ae)kwOn_^%hTpuEL0hwvIV)1^WNCZ;>jWv++Ui!d@3Cc*GSXi? z3@Vn}N@Iey6XYBOZR*y}^u@!?ykqg+a_1bIt&9L?isPJQ+% zC^_OJmUN2G25Vg3Aak3*|LhdWCsMMxR>9#D68p*BQzpNLJKcEvoC;CjZh&prNq$VQ z%HVE#OKJJ(xf6nT@FtFq$$e9&BC|59qwR{^D}NXmYq2{FcH~cT;|!|nKi#W!scMaU zU0e{M$&?n8P0G2=bIN?|MVc#I+-bAOyPmJcV6rvJV})nTUQp~SOO9gsSP>peP>Qx^ z4s~BwLa)}vN6K57-|)YC$-2YPCKzIRFYC_wv$KQsT7K`$dN*Gox@A+*^))|Y<${Cu zx#D!ja&%VO2bP1eCjQ9cB0_DIG3k_UdDM7iVo+;ItNb=YB4M21B>QbyDIRH)#kVm` zszZouq&wN{MERJ@tdKu(M?UQ(7(z8tF}2PCE2)`cpLAokkjqRY@YZcR8mm~nEu;)B z(Z(sa$dBkrc6=R1Wq#x>mrS^(uUQecEyR(Szm&X=a+Hv}Zpg&)@va55E~KHP{9xap zMp^9x?w@6DkG0G~9}}d*{Oi^-l(+>|CkI^g`Ya;aEb6;a`MQR!_&h6dc%8=NT=Ip) z0UN?H?#-xmV_05579shWlD9PCgCECn`gy`j*7Ru6DJQ>-#M+T%%F3+NA&81u@aTAJ zSoEUfW&!*X1EXI|nEtxa=TNVTXk_7=-UI#NkH=3M7EVZm*O-o--H@-PEJ6~V*Gs7J zlRjEjoiz%Uuu=}&ea9NZkwn)VPVr_4jg5ecO(08v z7B}2^U5T4hi<_h%Bx+`XlAl zXV1m^@$Xub-qyxR77IL6qOYhX;@fo2n8h+BZidm7^i~ERaya%gSTI_IDeKwWSoF2U zn1o1s7N#b%Fs7LbumLSmE?vV+DKpVv@oEm?lD>+S=}N3uOtkD1;bxQ|BT>)0fvL*x zVVLv*D&lja75p7fTplpY@dlx3vvP_<%>T=@IH;lyl>P;W(EsK-+mes=)PXxRBCxa z$tAy)ku-XatA#SG&9Hql1=vDq416ixLit)^%5iiEDzzKEtYCi$UtfXb9dhM;{CTB=M^TWYrb;WSD8lqh} zjk6JsG^HG_q{Nn<6^n0@3z}kqI|R3Y*+@cX7~ElGAnUPxjQS>CwDxMX7xkznx5H6} z&QZk;!ktf#4PUE9w+O)XIz*rOEhkN|G z(o>5G=NehAB}g!AlW{mw(0tAaA6?Sv|5;1IyGf=zOlNQ^ZEBo`ufF5d(AnZ~g)ABk zo-A$!N0Q#8t4j!t_gPN$oUKr=~>8iqDv9SjKc&Oy%2ejN5i{ zLmSXsV=3Q0H{6$ce-b%`*mfqdw2Ejq9k^K|8Nu$zGj+;b{7&`}#&hrnJ?7^wA}>cJ z=_?1E|HtamnS@1F4DsJR8FGBnX#0 z7-F$AYezZa!v_0hb2az-ne}s zj7bdl^$}#WLj6Lx^@$sPF?1SR-ggTC{j(FC_1Yi255sL$UR{%)Rj$<={e;-x1FpaI zNH|PY_<7|S>2dNij%9W?YH(KE_BwcLtO+qA{PUXEvNjeaU(I-V^ZARjiY`htq1PB= z9^11D6BVYs<7v`0E+j5zC6(vFSmYA+m9s>7T#*WtXr-s*t@{!$5{^?Zo zQF)bk1|t@Pg8=ennxT2~r&HZ}J@qm%f#op=YMipTg_;{y9>(F%@m%wuGaTm^wGG28jk|M#QRgN0`-zL+D6ZA(by8-c0dzj&3yg#(QNQF&sgUZM=%QaPcZYsM+h)&Q)d|NSVWep%G zU%M~Do0Tf*!e7<=7;cm0gfqiaRm2e3Yc?;aus5JqN7&Qr1C@yCv~4T$LXi_}{a$<| zd607jfvirbHts(D*#>;HnXT1sX8}LBMH(e!p(V58R^#|4UN14U(woIZ+#z-rscdP$y!z#{E?GQ6EveDO`B>eUa{0;!UyUboP+S%TeM+Fc@~gtN@n|wJfvz! zUgEbsa5*>+rs2bH83`ZNBm75^c_71SM4@cOy?H_!|Z7i~#m0w*3mLjeMY=7LwMw9VB@Lu;0vXQ=62IJvNxR?1g zk@)BrinOQPSsx*!BQvAqzdte$NX0#iUZ}I!VYjv3ao}`bSpBRcS+!G|87EvelEm1c zV#co%Qj;ZcY}SByw@m6~@MZYgGVbVXldID6%Y554BzE!&^3bR0>uO2bA9A{W?z6_& zO@v)f0;HmQfMS{guQ#`VZ$WL_m8&b9Kn$y+U?^~~>J~1XY3```d2KMrJWgeA6PEj& zRI@S{-A2G(we?>x770a9(x90R!Ie*Z@JpN3U}}hMrGgut*zcE?r-Re4V}dKI5Ytw0 zQ$g+iV1k$Es(O2)MRrwtMYl^_SQpmD`e2}f)U5a?S>%Pt1M@)f4icEVsQ{cmV@ko(YpJ* z*(s)FD#sI|gFz=5yoIK&y~3&a)vFnGYw|7YoRH;VODC!y?p9BCYxl2>Ih`ECSAJc= z>MWL1K}$nS&?T$(`XK`jHb%q*H^Cs1eN(U|?>vic0p^Xy;dE`IF(J_erNU2i7f^1E zD=~3%S?p0}bxZ_RMTIF9Tdu_fNasYFOY`USw-_2kR)V#coHk1}E5D<1W_IaVTBu{nuVedQ|<9*)b5XkoQm?O-JhbxP0;muSV}sq zb%tbx5={_utqq@RRP!Hz*;B;lD$wCq>EBMdVQ zxHH%RNpSb~$8%;A^wJ}=Of(!%z)YMWoT;zA^WIQWR=8umcJkJlajwqum%Cffgm1=# zd&CLHXFt849H-K{pXALH!+4rlg7XcgpC9l#-C5uuyi{`rVQps21xWV4f2A;lfBkNVZzNpi?DW2_fV;O9?G}AR{wm;k(Ag+U%=!@XsZnV= z5+~x@4;aiPX*N&XpZTt6um`!vV|MFQH*?U0>swaol6b{Th2QGo=x8zml zmipuP&IE`b0P^bH#YoS;F|ptDpKq{JcshBE_aYhf&cwQt>%|*U9UUyY={ilb3m0_D z{AIkwp9TLRR?P;)Y#0HJs58rPVa`|0X31Fm(U?8IkC_^nO@GI?|IBPouX2Im`un52 z3ZG5^JQbpIjt^4V=fY*)G|sm^LopW(&yT_TGy4(R>tf=(o$Twq0BQ^_vu+J=Ol}RV z!fuVFR9CXun>q59{o60btVoP-)Xkwr<>VtXS2dVWFRzw7!Spi6P@}X`BFdi1Pm|DW zK)uRrWbY+z?C`}21{)?OO5A=dmMUNNNEWZiNB^~irKoPHnBw00H44|n9Ao-p z2TrBx)YFnZK4jB-f~=n;8uG!m=Vfp#>cdRqO0Is@V4mT9hSV_+%F1q(?JdIwhl5-C z)h?Z8<@$Z;=Os{Hgv%U1;1YvhCVk$MB*dTH@`Zq%MFkMc?^GEJ`v4IJh>SX_u;i-v zUq;0XeU6s{SIn}L63FjqRJnks&w<{vWsT#n9qob+ssmilUocki-0PO5)_D>2v@!6R zz1Pvex{II=950EINNalz zFigKHAm1A{a?FQhEYL~%Kf3<&_|}_3jSoA-~vr*SHcrSmpOqx(`Flq$39k_Fo848WPnGx{4(x zbhfrK4cD(p?vK~!)H&R`RT_q^&s&k=o=Zt%W_B_l6m8pysOA%OEqWK|e{8%zc4^Q4 zJ>pa|x5JzYSO6;3`#Vjv#YuShvmK38TO!f8PfDMZ=YC})OBh3FLu0c7Q}yLKd?w$T z4_xxQ_?4I9cyUS3l7iXACC2oPOp@xxwr;NJ4@4Y;&_v+i=b2C)Q&>U|jqS{4ONp20 z`;c+M&CYb_Xs#6Wd{Tke*<1ns`LY>5E6?hAk&L}HX-((=9({+Lx0%brZ$eIWC4X5! z4`gTFmFLoELo!HZ-__!E_f6#kdWD6to8hIl1bmaiI{L%$d)J=9qV|IZt8pw>$G#Fg z|K?u4Q$w4VfE}^MZDlF(qXo&Bf$CwBtI=KU_HQ57R|CZ9M+dZR9dOsugPgx_ zb#?_Ne@eJ}1MFL=E5FGR_}8*cMjh^OYV>}R*Xj$WYOli=$wBzl(XxRpL^vJE2+B-_ zahWk9j7%b9lwmI)UlGo}d(rD}k+EhRLWcw1Gu;Mrn|bcoEqi3qV9i=lt4QIgh0Qp; zAJ&|K#fvAdbWl?eASEVVP6)AX5*)m8pK!C%@D!~j;p^Wkqe-egSKzIRkr(xI+kLP$ zCx!5j8?&9(EVQuXfx4x0GtqB$eu$8zdS%~dkLR|1O(h425aUYTR6n_=KbuwmJPxBz zX+sdfCOTY#derT|gJ>kn?I+^m+OS&^By;brx&r*_CpnMh=pnPpZ)I(*J4JX!YJiBt z+ihZM$F6Wd54*qbS0^~z1s-~{XIe2}@9;G&+FvFS8UJ&m$pbrVM4wG^@YuF!@tvW; zqWg9@PWvp*=1NVH?XDsEdxq^cbJq}aSwGdX&+}6Dpp0`4YT>@K%f&&)ytLra?5p^O z=(YiRcxWy8j`w>v;k!2c8zDM zBE*NxYYn_*H>h2oMylVT4>+;2X%muHdAN=EtR^;Y=fKVMyn?l_CAgcXBKP&4UD&Eo z>&eW#03y+sR*9=WSVxZWT0`9K_nF0=VgO!ue(+FAq$Q55xRJg~v^{>OVQamhWpvxn z`au60PmRIa4qY5(GjSk-l9$>r3Bb zy)CqD5_!OtY|OsEB7I?j5%jR1Ep0;*a=6A)h*@AM@X%N=2s7}4_h`hDn;avMZl0uX&iPv6uk*+`f&A{S4e zrba!}A`gk&RsP!m(WU$H|@2n9jm$Q^Dd1+9_t z6j+RT&e{;SMy>`O&rQm3T_=;@D$r%EC5D?%S09r37^T1E*iYt|v+XA<1kT>rL*3P2 zfExAkLf>a=L_$O7_AN*&@djkDQvCD1m z*TH|8*6$yE!FGrLeT^#ApC4?tQ<*4{zW85D>Tc5nNK3({bAJ}*y?6h4_t;x(<9d63Z-zEa4?zH0u=8s(1Zx_G9!<%gN-P&9EN< z1UK`Q&Q3MUF1xFf!}EeIZF%G69a&Gq-b@XTb>m7rh&l=ZTYjL8-?Vvi=g3tjzF(rV zr-;s}??m#hhOR+_^tMi$zmESJR@Qdw;6FnxG@))Y5ta?4;wQ}`UA(3rY zzE}S9*7Oa<`inTei^8o|i{M?o`I~aSm9md!qR^*#J|+!bJLNy~N_d6!TPJfD{usUI zaYEd+b#AIo=*BCwu^`ZrJh{f-+Hl|4pLl~)_kHUO*3pWsRwV0pdyhvnXeo=>Jl0)T zRMiHsNXL^VUWSXgwQ{0$IR@nhq%Ui(??MoV{14XA_M1g3-wz}PO>yxr<-p06y3oi6 z!&#{Dh33)gGo9(YX8Ubb3hkXy-+ERuKl~(+#s=z2-g=eroVj;dP&H)2eAk^6K|EB= zExBs7Yw8)SsukP}VYX{6&#r&c$Ya{)dYw=twzazdL{0MthXW}MpJ z8&-jofZf`0UL{=5)A$sPytF~qypm5Gci?EsBbEhxN2T0xD(mL^yj-p-? z&@H`RfPgszHV=;QQl0wMh9b`c;~1`aN2rXS{`~Xpbl4O5e4>VQMS3e-jASKh>7r`( zP%O$6ixeo7VpnD4d1&hDuAxfgt3PEjclO+UiFQ-T!ROK4aARe#jkoQl5J~)XRVOGD*A)a$np_-^$<3#CuyzC^@D_9I*eVOL?-)PVTwr z+j0gZHJ0;V)NoFRcGB4S8Gwo@lR=Fc;dElsb}`Gunq}W*a!n@cbBh%8{hdB{>UnT$ z^2(2v2hFAG1dW#5XqrbV)?|k&kU(;%azLpUTu_owhMTGDP<|flb1`;qZpSx@nC#+1 z5i)#t|8%3=;0KM6zTzdrt`ROvL*h3>8l=B_@re-@%E_y-uaGBvJG{ut?8^I|rUOzw zN(m&r6?b@m{%Tm{&2J+4cQ!*N=dod*J=fQz+BYf-3F-LMuF@d#z-Rc)?nC@hVVCj| z;O&F}13dmeS$kg99&geSI@Ua*E2Yo9=`K-4f1OLtJaNn&-|&zbEO@s(sOYCwTyJ{) zy_9Ahpz&ejf+d($rtYL3%$U%iRu5=o6a=cMUi?51ezecSsnI;F(hoVw!U__qpV_+h zcZqIuV;M? zqlZdSJHbQ-wa@27)_)ApqgEJ_l@SlEtY(ZRe1jd^fd1cmGXurJZMa<_X4j~9EqRj# z5>FVAqN&2*x>d#^HWP(pa$>l@0Onwle$PV&eUI$` ztWn6GMe$bmQxhL1VJs5`XoO@V``wM2ETB?o2M@gZ`1vv_r_)d%kFkY?8UtD7UJXa)KEZ5IF z^mVy5jgcqpEdN1ro`B^;JltFBzIqWbe@2Ryf5Z6ncbKT?Gg0AA^Ai$a?&|V--|zM{ za03MxJT9A`2;U7JZ((|aLT^IPw3Xi=j`U;0mdN+=i9O$ca6zuTiyr5oxtv7K!$-&R z3nK+fx>!xC+@04}Y$xigit@K+s`)B$C;W=|e?0i1Kkj}+gFyxC>Ro51jnkbbKkteT zKYkr}7>T@86vMlogdlpB&f5_3#?|lN_5Hp3L%y4mUe3(R;C^|Ea9|%G`Q3-53J_0n zPemsrz^gA8^&BHjFLrK{^`NPCBX*suE*AAO@acL6yX z>_J=0B>wb%qOm&!{=iHv3g2y)yDGL@b1LG;FazMuS)lkGuF|Z0i(a;thcV`p9#n0n zt;UL0l{fAF_LNU1Q_c$`cwE5uxHQYCapm}~S!9{3rH_CAS^wU`w)Ar&27adJB(QA} zWLfH%f(j374rdNV<`8~ENV6;1H7+8DMzj8&Ks*_%4Y%(nf0=xoLldqHn6xhb3Nj}_ zF?k#lPEgzRUh^ZW8)Vmro#8>Fwo^YP%*h5P{5o%52I8xGx2`BS1vQl8c$T71Kcu&n z{t^CTWgu}&-IhV3lwGj7SF$VAzAQIi?5pq4{6GU|_Kv}m&J$ak`Nndo`$`GY0_O5@ zUq`*qOab54g3B*Q$E+A4jX&S0Ha?wR6#VIySX<;c+4BwlS2auVEEoudOY{YH$7m@R z2$q$8mTRQW*;3g`9<0*t4@lTQTpBi_<^#t3>rbT?q-o#wyKRJCTyl!3=3UBNmgA95;)+l4JVU5UMv|nM_|*X)sI0!p?Qq?(ZNUkS9L1~{^*l52s#q{sR#UFXFqxkk67 zsYq71M#PGGo`t{1M<4nS$+xw=wD~3vqc|>tMI}j|#x$UGzwhQ924H?h+r0B?QG`7A zy|`viZdm#CuliB<4g~&eRWY=9pFKZ8FSb6RvDore$@$>cQMk8s_z--;e{xQAqR-{( z0mxL*g);JE_#v?Gcr91K%!Wr@(3EWd+*@?lrfz__W`=e}p6D-Z7KHXZ2V^|O1C}56 zZWyBd5bmcAHdcObr$b4H;}IOBp5VsPk`I;y8C z{>Hi0Pm;yx`DSuSZD+#Ii-=a0Dp{`VnijSQNC6AV?A8j*Vq6kfR&smi+VSPSDCFT8 zT}ByNdz6{a^TpUi)5W{>{afA%Zc5zR>`@zIUCr+@ zS95O;pV*Fk0uQz`A@^{^v?6_#k7x?&OcTbN=?Asd6Vh-9i6nQ!bA+1Z(ZWPz0y#7a zxr^NyZ4}d2@J#qsLCS%x%RIY*=jwKbvoI`yy&a~fr*2wm{*L9PLP=@$|oAC{)#CXhTynXPkcOd z9h9AF4MkM)NNg!eTBH+PnP9}SG{~$!_v&+gu0a@p$i2wgm{U?|l8QdT*|lPqw+E6~K|U~CY3N27a@Db7 zO6e_3?W?Gx?xGafh)0K2Hd!&gV9CJ-xho;b$^Ynf&lW?a;vymp(wh!^WsYejf35`b zCaleprKp)T)>$=qlYKeo#4~|@yBnF3o#qN}n+yh;iepHg&bw1@EN*?w&8>Z;0>430JN`3g z4)H_sbRqVzGSp4>Y|j>Yzr)K{M0z;xXq3rMdB)pGrupqnF+y;~M(aH~S;` z-JLB%j}W1#%D9Wl`w>=LRNB_JM!iD)JE$07qfb8T@;Ocb~z=2QA>Kr=Zgp_T*8DT(TzubO(Z`(^xIuFDDF~C zOG~K%fbP=H1-b3Jjn9r|yJ!u&wHa}`|EkudclU5c@3(UC=toZZI~!kVQRn#XZj~uC zpM00E;>qpoDwFcgH>yIa2oF~ieJW0VP7DjQyOw|qwLD+;82cEWPqZ%tug?C(B-Fql z&Pe}jB_Jag_KL;wIx#5%Scoi$oa)fOUGkjNLx;*CU%Tad8>V^K1@pXF?xFmqRs#7J z0e!xWEN?=R`rMnqVV6Jw@PRGe>PTx5jlx;IxT#-R;L$Ehx3o|4%%fkBju2XIR7dHZ zzS^d&&eODY2j&&sioCxaeVtrjj79kDvn*Gr@_YA^NG^}?{k?Z01A>wMy}9WYU1cl_ zzt9@@9hKZKHLgLD&@Q?RXJv?aHc?l*SGQ{c$s@BPGYQ8cOxAP7$S25GXMYs}z)exV zJI9kKRg^$;Fps8Xe&uwT;hU(O;T1v}^pOI@+yQ2Jt_F1P8ypN#tM#a&DTNXh2O3O{+9g>%ftkERrDd7$AnsiB0witu;%O@V;JyVid=vP-Qo^G zca!vcThgDcKEJ-`&%dlF2t3-hk$&1qD`}8;@>B8~l|(M5d$VSQB|UD6EDU3B&FLzP z>+&p1Z1A{`-!UOs*CGzNYg zR2FOe+sM%$chF$FU7AVtm=k$tlk-IOE|0Dh1Px+{MKg1Ec7cnmUQ_C*s!7JpAQObetGAg4RgQDhen{dh=u>)#5-ZntFCC@%c!4hRD7oeQLW^&j~-d= zVvi~`ED8fZf?f`iwm+V~!+Cm0Ver{`SV!%4+WPI^t0_>3!?1 z=mFBgy2T2nYIls9BbJ~aQqEl2eI@7^>!+ag907^6N?o%+Sb;DmKc4JR4KCd1d;PWp z`6HAkVk4wecE|1FMLX{RD__+=Dj7=knrCzrxRXTY?S4;ODqjA*(b#1b%r)h=ykEk0 zpIGLjoxcOS6-EVeARyvJQZ0C>B964Vm6AaIV#j%>O2m}A)52u7^DcRNGFk5TRR2*V z3dRL>z6MLvF3)WZXoERKywgDX6BAlNOl_wnY$WhU`%u{> zUs4ncYVIS?1~chxI{rgnWjOQ%ul2gTzm|44dM z0nE!b6-2ffevMKoa3=zouFTh+tw(+Iff7a4QK|^q1cWa%=D;OqRFVvg>TLO-lxX1i zx=GrpFjqbr?<8R$f=hTm;^^|a z|J1IR4}ROJ&^Oa565NM*v5o4trkni2%IK4{cA%k}peS`xN03L5*s= zLyS>PqNI{n?w)x3%LP24VNJBa{!k$JibW(~_9_&J$}M~YObjm%%bO4s9wF-WLC392 zBqzq5`Sk|O7fND0J&|xcVONrZ=SG^;Q49E$4VdL?WiCHQ80tnLAUV?7n;q3@oVife z?-@_Nwr)VmtyJ1gr6F`YwxBMe+!j;r@4iz0EiAwZCQ`s+$?Ogtq)Si~s(oy=@Y=U# ze5j)yH9a@IHNBd*Vwg~4fX}^1{v7lpleYm)e}asFPVDCr%cY|*RYWc0{+w< zG=f0e%Z$~{xv~o{Q6hopep-~6s7Y{u7y&Q%_%T1*^gg&vu!Y8Aezno88((OF6 z>pNgJ`l}bxx z#b|j9eytEjXpUr3IBKg6o;wpDCA(Pqo|)i?ZuNR=tZpjd z7DZL~bzu;z+nQIJ)Yb@Aj4Mc@OVLfKw%nt8Z2`nAa0tXKuSl~$F&a3=-r&{O!3EJ9 znv#JAck`;PTt(~dRGe79Glv`;8J@2sM>53+M13nm>W}oiYehWTc@5z|gj^JprAFlm z@Yhy7$0JGjTgXeFdp;JMTqf^T}f3Zz-Hu)IP8uFL%uJ@&i3^W2L` zZB8Q^?ZRXF_U)8H!Op-Kd^?RcW5E5mU&!O2^_Pp>JBY>Vtj)~j{uc80L~`EP@LN(o zQCN?>8(L7XC>Uks_eiQv)~@Aygku6DfX7y_j7!yTV8KI?weV}V2%YADs{{jNUCn7WEZFuF#r57)QI$VV5<%nr|wf3mWEcu)u3sm9&!u?n_FLL?%UWc`;$#H64e?=PM33 zt-@&EDSxh`?OF#^bDL;UNsU!bw3^vyZUbDC_5U&U7f?~PUEDZ)hVJf0xzuQ%efIwC7Iz_r z&73@_LM;qF@?pmD^@5{rmP}wH$uRkwH0rbF#19B<|8I|_>-TV>e*OOaoxtB`B)(-Y z3JBi$2JGRZB-D>Al9D9m_P)Mgy|`Nr|fI^>)nKxhy+vPA%N}dq$bZGiLM= z5dLd-`!mArq2;!)@JtF&W|@Cay{HoCfkd+R8HzDtU-a?nCb9X8W5oKHxPjq~-!# zLn`H{GTjH@;pdjnH-_5k8)w^n_LhZu=FOo`4%DMUm7$rCCKil?Lsh%-NL-=oXsXtSB8a_aLZzxJ-?pwvD z3)NiQi-o)}0C`0LB|)?iU{UPlABtlpQtK0D>@dE?blr!Y1gTmWzVdwm^r&Yz^6#?k zkG-`lhUmxsT^4Q~!%@v!Q7V8zs=tf0^0=G8uWG(_E`4yZxrH&n6On%Qe$PbIgs%Rg z*6JZERdV4W<$!hrOeG(7P_vQ72V)sDDW8MwmHYfn3v*M1>um47sE@mvDoy7?(M`#& zT5P_lwq%c<47gxp52PgrtyX@Phg`oE^Bsq``tGN+mYh8)0zt&b5ObaBnsn^3reVHu zXGrPS-8#WDnC1l9IJu`wRnST#dU2q}13otD_{f13mZ$p=@Av&=ml6A|ZcjHIuk+1P7c1n$5*Z3w9o#1U{h&^E z6tgNB%?%rw09TQ>W44vI$m4>rkj5VUQ?HNywYZa+ATDDRNwz$`es+&^Ju)28>00!C zs`7|t;Ml|m!POFJ+OOhyLhu7HA=o}aQjQw3=A*5o+$mvn$>J1sQ5JXaT%1%gP%Xgj z#4kYi0ZE`2cJnji)U4w7zwjdoEg#w(vWE8qj;cdaEWiAP$anbDOK5oQAR2H|E1bou zX2+_*^_-mC2?wD$PpQ2<@#m5|U(J)R|6F_nbzmdBsC->vXC@2Yros7Fo+sG%O#>FGd|Iwr#1l~Pp!ym*O{RCt5vfvGkC)6yTG!J+=Nwx!UdGICB z0ze~f-T}3|0k3U?@D^b0mU!axncY$ynWvJrAGZI^t=MENuRRPY&AGFO-(7O5qp@OT zwY4ag-oXtcyXHvNQ_9O+fdiWN$caqB~fnhJ!m>RU@+UB z@VvZ%V317h7(PR%m4W+}K2;~pVGxh;OwqQv#kd0<|7lUwF9UIhzMb2`)UDO-OXA0f z0t)7>0_1zr;NG67l4s4@unoN%xR&8#X%YE=XYTZ${&8?)BBx2mpXI)K zDRKObbcMP<+gql2p$N(6ICVS35k2Ir^}FjOR@xr57rFIz;l(Z28}8k=Y;0 zyG|JgQ>=egAaz6s8FLszf!QATXl{#;q*;DCoWH;_@CGf4#k!$!#?NE+`S>ILbSu}; zqX~#}pEw#U2K_d5q!H>*gsrg*#(%1|wYCzEhYiTz*xE_m#}6d&`v0Q3b4Y3N0^46fAR5Ga|{)|ht(1X&$ww%BLUtP4Bg z!E`l3oRyx(!JLkseAq@CuDg{bnBmhD$_EmV)bwsa8hV80j#QQk0rEl^uF=4FzaiP3e z>taAM#@AgEc)IbcvBIfRTKNNVw_!%|zX^xD-wEj?6#PE(s_@7~J|x6EG;WW=vWv?k zxU)*^CSt5%$#}=pC;S^7Gs+v&JsZgk=Hk%e_yN+QM_CB&i~m<#B(}IT=3)=~g^3jS z5Hx1ki+%EVo9ESchmZOaj3X4;c$f#r0Oo3?M3cxEPEOBCC2bzswLiOW&?bWy|0Zxo=dWuM(rvxVzQA zyC#CNjg9<@zFGsdy(AG}PRc){fmtP_BXM+H&~68K+p2$1drJe-aLDLJ>pAC^Qr42k z?#mPGRoa1^ZG&x{fr~D@*rojO0*BX!OUQ7M;h{axmS4TB)!NI=Q9--84%RG)xMySr zQQ>5s3WQGpa66y<{)Tdz4=FZ;mKOS5_-{ z0nV=Ss%so~SAr|c8_D zaHF9mDhyt9%oe;mk}HiXw)u*_wIrNh`Ty8MaPNg+pgZeHuwIHS39KY}*tHIFO4~Ql ze-2D{p|x)tvv@VeFwNB@iSZ zywgtvaH#V~B2O9Odi}Rm$dHD=ONUW(z#}YHP@N5Mw*jx2!}+}f+$mr>u+{ypfI259 z0)c{E%<47;71EX(nutIn4zjzmEeBtxb$o&Rl9qb=zF;bI4xXIeB12&XC|q>6Rs4N0 zj$P1>Zz2Nk1FRRZ)vW&Y?7c$riVtwn=}e2k_0fdN_2lh=A0i$1wZVlbkmHavuRf`p zlP3}j_3+4nx!RZNNDSw@$~e%|cCa=iYYt2_ON9=pj_i7{yXi)gde=96XSD=H$Zn!an&|7&~vtEEBc&~YTwoJxTq9B~$7s7&n(`b<6|ah6MXu^hg+LQfey zA<>@_1!j--(PKdr-aBfUPDFFw))yp8KcoqJd!sG`5W?g-sw1tV5zwrpu2Zp6n}C~X zLm4Sm1tYXvmiNcEv_=<4e;wO@{eH16>Z*Wso+m4GBICR^LNe(qI;@T7Hz7PRBM$}m z@T0m5Ql}S&l+BkeNHK1=(ERAn+qXH)B zpH^?X4V9LR2JFkm6yC7fFuj&7j&GsL9Q^~{8 zYDuc@?XUO<@Z4?;cRV7dcSR4;4y((2eyR^-u`d`>$F6_ zVx2lA)-$Xl+yRO1yDbdT6DzXr>Mk@qmU;C)vSxxV#AV?ccc+n=(YY84ZtUTMpIp&AJjNeK7=R8ekK@Ff}T z8WuQOASuc@-eTb{KGwawC)aY*&Qkd7it)Dcx0&58{{~^>4-kk?*L?fB0yhhEXEEp( zuD9fJj?zLVH7(|HRq@{)rXJ-a+<~TXK4N7_tKi;mM)a;!xck{1nDvzP>{m4xj1^{2 zrD!FU1r7r`8T zSq$SLBpj;oFlN^Opw0uS6MAtnL<1^WrR@S9_yTRL@aS-`0geBlmA?W!ELO@9kXljy zPU}hCi0aQKtfwf$;!uA%#r04J0vF3CP8_iPb+8HTI6Xpzy27<7^A`>gf6wk0x5 zieS*x*Yb{((uGbw1yS39S1_8#R_6LLMaa&%Cy?@iKjRb^U;LQ(%OinRDX(YQ$W_A4R` zB^iHv!6H{M9hO$7pYX6*w}W4vw7O*C*tT3tLHf}p#(qSxMditHPU_?Ef_wV`l47G_ z)}Ac1ryGgL@ols)&C|RO5RG+@(`MGTnV)6};P`vJlPw=Vk;lgP@#;(SkkBJu96`sj^ zQQNI|@9LuPTZ{ zG7@BaS3*wr31Gcuw2~hg8q*sA%PGSG!f_Y&mO2{8+bv_%go?5bNc;E7=-}Fj*lksh z%W0p@Eh`V#^}doLj|ZCMKHoo2idyDTZ7X$=6n7UonU+H;BL(d%Kq}0Pku&dlM!t-N zh5LV^TvbwTmZFdYB4($CMc~UeRZ3+5=K8ghB6}}FFZya3+I5w z){NAtPi=|4;QiW3y+gn12CuW2OwyL6CDHA@GBsbcpZzQ~)_TsFr*# zvfd0f1ADWU1iPv3T+0g}-;xYZeMsk=ltedU_`ek#4ol$3<{8iMuP2^7mNKYC@{q!` z0w!wSy~R4pJJ6bBZ!4>gQ#Gm-JIuYQNt|5#{_qNXzEn~s4vm{s-~aEK7ympi@F-4= zupaIG&Q&vP4My{wW7ROGgaTA(s&`RU>#YI~?h1*bQ1`M0_xBIc`9(3D$BNL*^-QaE z*HF#fOmkP=d9Vzb+-#iYq{4PiwENO{IH^QQ&8 z8tpaHHRg-pR|(YV5#Jhaqy>e8AT$7PrxXMq(7zY9{a+9>Y=)rwzO2`8qM8>--_J50 zAxuBqqXMo|;h{t^cf+e(__NvYl?WLN`A3_Qe%vmr(**_cmqgg?eKWik7NMUVR@v>Y z{9n!M9+f1#y1<6mD|)2bA$U951J{k(6U77;L7MOI7=h5>PWYx5R_hu z#ajfOI)sIkyaX>C!32)}s+qs4j$m6T&;iMO=tF6!rTSigAVRv;fQx58+&|yp90mX@ zbnuPaE0_o}^q)MNk&YO}?JI;1dBNa5`t|>=27@o^&kW%XExIcl*Jl8Hi5?2ivpn2O zZi`k{zZ}y2bC(p4cj(Xu{v+-GL4kXZDcj-<{{K1pKfoOB zqZNa1vw{RB@#ArS_0-=#k9{8Po*_lr|NVk4km0fG`{yBP___^0T41=+H@IA_ zv+WB2{6|SnO}13pJjmXPj}HKlC&M+=6tFQUFyLQdD=Erq!Qbf~UuY=s-yS|b=kPa> zhn9jgP&!Dp1OI^TrfBE^03=M0FOZTJ<1u_0kH-o|1N3TrV<#P5qE}ZFDT8l6c$JSo zRV>Fg;El*;M8P(r5DeH$`f_ar6=JsRkR@a|&7voejA|V|w)iMdko5ubMJg6>7^Ta| z&@j!Y8Jmh9%<@QEo4pzgB^ON!ix&NL_09J%pt$l%VrxH3r$`r#En>7aH%$Fyld+1> z#XygYFS9d!RnZjr?)%fugDiQm90wocg=6rZ&kxL@YR%+EpY8pVvc=33DnXKKgUnwv zQoRD04qgk|vRgfp)0^^ujAABMTkzp+H@>HupjEix$1tv@@d% zbIms?jtt$hLt%c22(gk8nWKpjr+zrE#2qcFs@p$Sw^wlGf0gen9b#~gZz^BpE?w34GQ5#9E<@_>;Vey3(dYu=tye0YUHP^GR`~B%!oWqn# zcE53^JndNV5}F{1yh__t-O`jG(zEv>NU_`AK#uevY7rkJ#l@61A|Pq;`a_`_B8pyd z09EL(W(^|)ytK@Ip+C4OeBq|~7b3zb7R%`M(i-O~x$meqJ(AEfQ)%4Le~4AxXrdN4&{kZ^b}AcH8)qJXk)n`NW1r z{(9)en;DD_(Zo9%Pc}1kY`|sE&cA?e@jhQ@u<6eX`^3?LDye1>$^;2@D zvC$JnAV;ep=R;<^9762(Qr*{N^4W#CO04rgb1?kf$*$j%dEeMCKOL%&pqU=sjBGKN zJl>#Rz90>{T$_Tp23&MPVg9%%{@q^F=;z1`wM(A?DsHZft1E;eJp|Fqn=}KN9B?#( z^)rpV?Q>eB-{H&N9VRJ&=`6;+1OP236oDycXW`$6^`5B&>rjw2;u-XQLe#gH>LTDY z_W9Xb2VmG=TTUa^;2@;#FwK0(VjV?1IPrSxPVaZGMmIZ@5;Nlc!RnR3k37^(Gz}?? zv{*732|D1pN4Xp*{CXg=hgvYT3rQJ){G=3PSDq+x6iwIS4>-udyd?$Thxl5 z%Ewj(+1lBmE$);1!4B|Z&fHZ@X-_==sFBNk9&U5KxVER9(0hg89Xh#lJzA0S zL9OCvo-8wqah|K8PaUxI`*u>0CGLB9Z3b_eD_5)<4cpY6cm>+OXGJbsr=M%JE=cRJ z2{Ozp0TxSc<*$yGz?f2K>+lxP@R{JppxiP0*j0znS17VZr=IarJx zq)DSSount`QAXro+LrdRy7c*!&H{<;2gP?HY#4d`Lq!DT6yFXQr{)Z zAJ@}W_nqiF|NO$;nFfwO;uk^_h^(C4D(^RL(3x%C_PazP{Mu{h98;x;89em~K&Mjn zX$5o8G~o1x1~J2y_I|l$T@tK4o3)k$L9w|l=vC6ho||eoh!w4$B%i>njv9_d znTA5db{rNtHa3KPbrHJdNX@{?`bXH!HP1{)QWp#_el@`oU&;}k8mcIuT+;fi?aawd zTfE`Ku4i4F0H%MY==gFAJMT<;V@HI}XOi!o1a~^fNTf=?{aGe=eKy%=IEndadI7{TDWOe=A%gE@InCH=ER}iUYmtV0x3L0~>qsS~M=Yo;A!G`e7<* z?Kz5RYA=(3wE?@sU1@fb&b%gXR0J;i;u$|S>7vff&?G-0zV>>#-tzF}5=ExAW0A8{ znzRkG*GlrYi(eVG`_tqE(izrIk(hdleYW19Thvs{f3>lAn=tcQhs*`|j+5FoF&tvT zj8Z#gYZq8mT*gL+Rol8W{~;re<#Y1awwZPXJ}xef{m86dh9uQUI<*ax5--s~nj5LA zQZwC)%OV~n$1Og==P0(pxF>%gB2t%L%t~xRq{UkL>{~>4u7Z+M_EK&ob}e%oBK*9# zfCHq~-E(@yZ48A7Sqx`=dpA4irN;G$1F!*`n)=G8Qoi!DdGV85 zuye6b2VJiN2qg=)y%ew_K!| z5x9l~K@Jeh_yO<1mb`Ud#NhO#O51NoY-Qj{Wai~+Y4)6l#d`mX3q>B60MiO#ka=TQ zgWPXRjAo#Ko_|ewUyoN*?3KTF#m(oAbv$KBuG)9rT3T8nNTjKUF1XZ2vF!hO{q$p}<@WZr6c{W?wlq9U=Yd6T^~W3R z=7vWpU}1L!S(|K1-Q9Nc9+zq}lbEQ+#oOcixssXaV&Nq9ZM&aFDA z<#|(Nt)`)L;%#!mil!r<#$TM%ClK7z!%OuOMI&Z>bTm^yJ4~1hFeo3|lH=A-a|{(w zcr}UwDN#=?QJAfB&k{181zuMgrmVPU>w*Qu#EJ*vg#er|?e}jEzUt8-`eHmDT_k+* zvikBq0qTg@(tojwG*peK!}iwNfD*uQ*sZ2C!N)e&ZyzxdCs0WHom~Q=^7+YNsl|6u&Xjmy(bEmN7o1C+IGCs4@DfV6Us<~F1)4}DIa*nIsF|6%Z~ z=vDg%Dp6ArV^`Cv)uG>>(v6-)V=^|C8!;1u|eZc!(5qx_H1fNIdP)8IH{Y6%hL1B8?Y zvBrOtH2}u8{5=k0MiIPnaUdmT17$Hs3PT_$N2EnaBW`tVFgbPQ8|7bOc!x+7+qQK1 zYPp&vu;{f}=!O3VI-)^9vn}c{i#5T=bf*u^l25<_N^?k0KBI~3JrQ>!bi2cDc@={o z2`kd%H{N^T0UYIy5fNJ0R>Bd^B**>?CS;aSLSzt#T75Q$q-0mo@xoyR#JRKD*A70{ z$}KB|9}+dZ3G${M>1kS~KX74)Z+yMuN}+rB{h6BEs>rdYJ=16czl=wXR}64mDc;g2 z8l^YSF8v870vJ--3qQ-nLk&Qke-{b(R;q-{Np&$HlWE5VoM(dSG=E#jb53Qye&fY9 zA0@hEObO|2MKMlgmb!iLZ&@3nKp3U+`p8=~pon1Zh?*l;Hsg^0H1w%`AbNO z)BJm-F8O2VplkZOwcArah=cJAMgo?9<+Bbr* zz56!0xp9m6vwoCIVwFF{$P-_|?h70A2==*i&@895Rw

    }jyaqVLsV=ix#*zt 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 +#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 00000000..7ab022f1 --- /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 00000000..d2cd3f95 --- /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 00000000..a6879493 --- /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 00000000..ada01e9a --- /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 00000000..c8ac696f --- /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 00000000..79d13ab6 --- /dev/null +++ b/Cocoa/MainMenu.xib @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/PkgInfo b/Cocoa/PkgInfo new file mode 100644 index 00000000..bd04210f --- /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 00000000..8a6799b4 --- /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 00000000..49930b47 --- /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 00000000..003f1ca8 --- /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 00000000..f850b8d9 --- /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 00000000..949523a9 --- /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 00000000..08883931 --- /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 00000000..3d9ebd2e --- /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 00000000..9fbfffd5 --- /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 00000000..280e1395 --- /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 00000000..aee016e8 --- /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 00000000..c148e3c4 --- /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 00000000..30a366ed --- /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 00000000..bd46026c --- /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 00000000..3e797bf6 --- /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 00000000..0364d0dc --- /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 00000000..6af8afd1 --- /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 00000000..0e369458 --- /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 00000000..d997fc63 --- /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 00000000..0b58aa4e --- /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 00000000..9bddc9c4 --- /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 00000000..535e4c7e --- /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 00000000..00dabd81 --- /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; +} + From ec8823e6207f8f68f904968b8c2374a9bad7a776 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 30 Mar 2016 23:31:34 +0300 Subject: [PATCH 0002/1216] Cocoa port can now enter an empty line in the debugger to repeat the previous command --- Cocoa/Document.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 7ab022f1..d1e20af7 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -10,6 +10,8 @@ reasonable alternative to this. */ unsigned long pendingLogLines; bool tooMuchLogs; + + NSString *lastConsoleInput; } @property AudioClient *audioClient; @@ -307,7 +309,13 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (IBAction)consoleInput:(NSTextField *)sender { NSString *line = [sender stringValue]; - if (!line) { + if ([line isEqualToString:@""]) { + line = lastConsoleInput; + } + else if (line) { + lastConsoleInput = line; + } + else { line = @""; } [self log:[line UTF8String]]; From e822f17bb72c36ee7efed439b414e43deebd18dc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 30 Mar 2016 23:33:32 +0300 Subject: [PATCH 0003/1216] =?UTF-8?q?Wrapping=20external=20RAM=20banking?= =?UTF-8?q?=20support.=20Required=20for=20Pok=C3=A9mon=20Pinball's=20saves?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/memory.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index 30a366ed..459bf769 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -234,6 +234,10 @@ static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value } else { gb->mbc_ram_bank = value; + /* Some games assume banks wrap around. We can do this if RAM size is a power of two */ + if (gb->mbc_ram_bank >= gb->mbc_ram_size / 0x2000 && (gb->mbc_ram_size & (gb->mbc_ram_size - 1)) == 0 && gb->mbc_ram_size != 0) { + gb->mbc_ram_bank %= gb->mbc_ram_size / 0x2000; + } } break; case 6: From 3344480de764faf6cf663dbe6bb40be66043facd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 30 Mar 2016 23:36:24 +0300 Subject: [PATCH 0004/1216] Vblank now returns a white screen if LCD is off, instead of keeping the buffer unmodified. --- Core/display.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/display.c b/Core/display.c index 08883931..b5f08bcf 100644 --- a/Core/display.c +++ b/Core/display.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "gb.h" #include "display.h" @@ -197,6 +198,11 @@ void display_vblank(GB_gameboy_t *gb) frames++; */ + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + /* LCD is off, memset screen to white */ + memset(gb->screen, 0xFF, 160 * 144 * 4); + } + gb->vblank_callback(gb); if (!gb->turbo) { struct timeval now; From a3dd58c92ce6b1413c3dd3a102d4139c3302a930 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 30 Mar 2016 23:37:08 +0300 Subject: [PATCH 0005/1216] =?UTF-8?q?Fixed=20inaccurate=20LCD=20controller?= =?UTF-8?q?=20behavior=20that=20caused=20Pok=C3=A9mon=20Pinball=20to=20fre?= =?UTF-8?q?eze.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/display.c b/Core/display.c index b5f08bcf..75d69ecd 100644 --- a/Core/display.c +++ b/Core/display.c @@ -265,6 +265,7 @@ void display_run(GB_gameboy_t *gb) if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { /* LCD is disabled, do nothing */ gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_LY] = 0; return; } From e11faaf3fb07949e9aa8fe609c3c027005bbe318 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Apr 2016 22:13:05 +0300 Subject: [PATCH 0006/1216] Added license and copyright --- Cocoa/Info.plist | 2 ++ Cocoa/License.html | 45 +++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 +++++++++++++++++++++ Makefile | 4 +++- 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 Cocoa/License.html create mode 100644 LICENSE diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index c8ac696f..f2ae5d0a 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -65,6 +65,8 @@ 10.9 NSMainNibFile MainMenu + NSHumanReadableCopyright + Copyright © 2015-2016 Lior Halphon NSPrincipalClass NSApplication diff --git a/Cocoa/License.html b/Cocoa/License.html new file mode 100644 index 00000000..2b61afc7 --- /dev/null +++ b/Cocoa/License.html @@ -0,0 +1,45 @@ + + + + + + + + + +

    MIT License

    +

    Copyright © 2015-2016 Lior Halphon

    + +

    Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

    + +

    The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

    + +

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.

    + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..c2acb408 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-2016 Lior Halphon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile index 0b58aa4e..8357c63f 100644 --- a/Makefile +++ b/Makefile @@ -62,10 +62,12 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(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 + $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/MainMenu.nib \ + Cocoa/License.html 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/ + cp Cocoa/License.html $(BIN)/Sameboy.app/Contents/Resources/Credits.html $(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@mkdir -p $(dir $@) From 1a66f26a5e9948e6202b385599687f8b443f6b05 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Apr 2016 22:15:21 +0300 Subject: [PATCH 0007/1216] Added readme --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..d9462978 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# SameBoy + +SameBoy is an open source Gameboy (DMG) and Gameboy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for OS X, and an incomplete experimental SDL frontend for other operating systems. It also includes a text-based debugger with an expression evaluator. + +## Features +Features common to both Cocoa and SDL versions: + * Supports Gameboy (DMG) and Gameboy Color (CGB) emulation + * Lets you choose the model you want to emulate regardless of ROM + * Includes open source DMG and CGB boot ROMs: + * Complete support for (and documentation of) *all* game-specific palettes in the CGB boot ROM, for accurate emulation of Gameboy games on a Gameboy Color + * Supports manual palette selection with key combinations, with 4 additional new palettes (A + B + direction) + * Supports palette selection in a CGB game, forcing it to run in 'paletted' DMG mode, if ROM allows doing so. + * Support for games with a non-Nintendo logo in the header + * No long animation in the DMG boot + * Has a text-based debugger with an expression evaluator + * Emulates [PCM_12 and PCM_34 registers](https://github.com/LIJI32/GBVisualizer) + * Emulates LCD timing effects, supporting the Demotronic trick, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos + * Accurate instruction and memory timings + * Real time clock emulation + +Features currently supported only with the Cocoa version: + * Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars + * Retina display support, allowing a wider range of scaling factors without artifacts + * High quality 96KHz audio + * Battery save support + * Save states + * Optional frame blending + +## Compatibility +While SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), some games fail to run correctly. SameBoy is still relatively early in its development and accuracy and compatibility will be improved. + +## Compilation +SameBoy requires the following tools and libraries to build: + * clang + * make + * Cocoa port: OS X SDK and Xcode command line tools + * SDL port: SDL.framework (OS X) or libsdl (Other platforms) + * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation + +SameBoy was compiled and tested on OS X and Ubuntu. \ No newline at end of file From abfebf0eb219f03500f138ba00db63a678a30eb8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Apr 2016 22:53:29 +0300 Subject: [PATCH 0008/1216] Added debug/release configurations --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 8357c63f..8c9d3526 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ CC := clang CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE SDL_LDFLAGS := -lSDL LDFLAGS += -lc -lm +CONF ?= debug ifeq ($(shell uname -s),Darwin) CFLAGS += -F/Library/Frameworks @@ -20,6 +21,14 @@ LDFLAGS += -framework AppKit SDL_LDFLAGS := -framework SDL endif +ifeq ($(CONF),debug) +CFLAGS += -g +else ifeq ($(CONF), release) +CFLAGS += -O3 +else +$(error Invalid value for CONF: $(CONF). Use "debug" or "release") +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 From 1538ad451ca61b50b82cfbdca6e2e959186d41dd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Apr 2016 23:29:03 +0300 Subject: [PATCH 0009/1216] Copy the license to the SDL build directory --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8c9d3526..1788b30a 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ $(error Invalid value for CONF: $(CONF). Use "debug" or "release") endif cocoa: $(BIN)/Sameboy.app -sdl: $(BIN)/sdl/sameboy $(BIN)/sdl/dmg_boot.bin $(BIN)/sdl/cgb_boot.bin +sdl: $(BIN)/sdl/sameboy $(BIN)/sdl/dmg_boot.bin $(BIN)/sdl/cgb_boot.bin $(BIN)/sdl/LICENSE bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin CORE_SOURCES := $(shell echo Core/*.c) @@ -100,6 +100,9 @@ $(BIN)/sdl/%.bin: $(BIN)/BootROMs/%.bin -@mkdir -p $(dir $@) cp -f $^ $@ +$(BIN)/sdl/LICENSE: LICENSE + cp -f $^ $@ + clean: rm -rf build \ No newline at end of file From cc8a09763fbdc5fe178167baa13f068fb92577db Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Apr 2016 23:36:43 +0300 Subject: [PATCH 0010/1216] Version 0.1 --- Cocoa/Info.plist | 2 +- Makefile | 12 +++++++----- SDL/main.c | 4 ++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index f2ae5d0a..0998d0d4 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -54,7 +54,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.0 + Version @VERSION CFBundleSignature ???? CFBundleSupportedPlatforms diff --git a/Makefile b/Makefile index 1788b30a..4be3e79d 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ else default: sdl endif +VERSION := 0.1 + BIN := build/bin OBJ := build/obj @@ -15,7 +17,7 @@ LDFLAGS += -lc -lm CONF ?= debug ifeq ($(shell uname -s),Darwin) -CFLAGS += -F/Library/Frameworks +CFLAGS += -F/Library/Frameworks -DVERSION="$(VERSION)" 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 @@ -67,15 +69,15 @@ $(OBJ)/%.m.o: %.m $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(shell echo Cocoa/*.icns) \ - $(shell echo Cocoa/info.plist) \ + Cocoa/License.html \ + 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 \ - Cocoa/License.html + $(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/ + sed s/@VERSION/$(VERSION)/ < Cocoa/info.plist > $(BIN)/Sameboy.app/Contents/info.plist cp Cocoa/License.html $(BIN)/Sameboy.app/Contents/Resources/Credits.html $(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) diff --git a/SDL/main.c b/SDL/main.c index 00dabd81..ef01bed1 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -134,6 +134,10 @@ int main(int argc, char **argv) GB_gameboy_t gb; bool dmg = false; +#define str(x) #x +#define xstr(x) str(x) + fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); + if (argc == 1 || argc > 3) { usage: fprintf(stderr, "Usage: %s [--dmg] rom\n", argv[0]); From 8ff433bb97047432434990d67cb3bb7631981aab Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Apr 2016 12:51:07 +0300 Subject: [PATCH 0011/1216] Remove useless key from plist --- Cocoa/Info.plist | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 0998d0d4..149f6789 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -2,8 +2,6 @@ - BuildMachineOSBuild - 14F1509 CFBundleDevelopmentRegion en CFBundleDocumentTypes From da00e240e52e7b149c974bf7e22e03e29426face Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Apr 2016 16:29:02 +0300 Subject: [PATCH 0012/1216] Correct read of IF and write of IE --- Core/memory.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 459bf769..95938b33 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -94,8 +94,9 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) if (addr < 0xFF80) { switch (addr & 0xFF) { - case GB_IO_JOYP: case GB_IO_IF: + return gb->io_registers[GB_IO_IF] | 0xE0; + case GB_IO_JOYP: case GB_IO_DIV: case GB_IO_TIMA: case GB_IO_TMA: @@ -473,7 +474,7 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch if (addr == 0xFFFF) { /* Interrupt mask */ - gb->interrupt_enable = value; + gb->interrupt_enable = value & 0x1F; return; } From b7555e997638dc35eb6c6404adb69a9cbc4676a1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Apr 2016 16:29:27 +0300 Subject: [PATCH 0013/1216] Correct OAM interrupt behavior --- Core/display.c | 31 +++++++++++++++---------------- Core/gb.h | 4 ++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Core/display.c b/Core/display.c index 75d69ecd..c664a566 100644 --- a/Core/display.c +++ b/Core/display.c @@ -242,23 +242,19 @@ void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char in 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 = 144. - 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. + See http://forums.nesdev.com/viewtopic.php?f=20&t=13727 */ 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; + gb->ly144_bug_oam = false; + gb->ly144_bug_hblank = false; display_vblank(gb); } @@ -310,22 +306,25 @@ void display_run(GB_gameboy_t *gb) gb->io_registers[GB_IO_IF] |= 1; } - // LY = 151 interrupt bug - if (gb->io_registers[GB_IO_LY] == 151) { + // LY = 144 interrupt bug + if (gb->io_registers[GB_IO_LY] == 144) { 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 */ + if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->ly144_bug_oam) { /* User requests an interrupt on Mode 2 */ gb->io_registers[GB_IO_IF] |= 2; } - gb->ly151_bug_oam = true; + gb->ly144_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 */ + if (gb->io_registers[GB_IO_STAT] & 8 && !gb->ly144_bug_hblank) { /* User requests an interrupt on Mode 0 */ + /* + Todo: Verify if this actually happens. gb->io_registers[GB_IO_IF] |= 2; + */ } - gb->ly151_bug_hblank = true; + gb->ly144_bug_hblank = true; } } diff --git a/Core/gb.h b/Core/gb.h index 280e1395..ca810ba5 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -216,8 +216,8 @@ typedef struct GB_gameboy_s{ 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; + bool ly144_bug_oam; + bool ly144_bug_hblank; signed short previous_lcdc_x; signed short line_x_bias; bool effective_window_enabled; From 0787e5b2716d0c476cec9bba523e574940cb5f35 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Apr 2016 19:06:43 +0300 Subject: [PATCH 0014/1216] Debugger's next command can now exit a function --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index f850b8d9..8453bab1 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -321,7 +321,7 @@ unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned void debugger_run(GB_gameboy_t *gb) { char *input = NULL; - if (gb->debug_next_command && gb->debug_call_depth == 0) { + if (gb->debug_next_command && gb->debug_call_depth <= 0) { gb->debug_stopped = true; } if (gb->debug_fin_command && gb->debug_call_depth == -1) { From af7309b98d8c8e55caba1d7a3b17a4c3d504f735 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Apr 2016 19:15:07 +0300 Subject: [PATCH 0015/1216] Moved the call to display_run to advance_cycle. This fixes games with delicate timings such as X/Ekkusu, that expect STAT to change *during* an opcode. --- Core/gb.c | 1 - Core/timing.c | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index 9fbfffd5..840c13e8 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -404,7 +404,6 @@ 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) diff --git a/Core/timing.c b/Core/timing.c index 3e797bf6..b80ba770 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -1,6 +1,7 @@ #include "gb.h" #include "timing.h" #include "memory.h" +#include "display.h" void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) { @@ -25,6 +26,7 @@ void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) hdma_run(gb); timers_run(gb); apu_run(gb); + display_run(gb); } void timers_run(GB_gameboy_t *gb) From cda6621dc2b93a312207515cfd1c6fa9572d5f30 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 Apr 2016 22:12:00 +0300 Subject: [PATCH 0016/1216] Fixed mask for TAC and STAT --- Core/memory.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 95938b33..3e8c1548 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -96,13 +96,15 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) switch (addr & 0xFF) { case GB_IO_IF: return gb->io_registers[GB_IO_IF] | 0xE0; + case GB_IO_TAC: + return gb->io_registers[GB_IO_TAC] | 0xF8; + case GB_IO_STAT: + return gb->io_registers[GB_IO_STAT] | 0x80; case GB_IO_JOYP: 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: From 3e135a7c003c8cac94f84f3ed283f44fb81e3761 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 5 Apr 2016 23:21:51 +0300 Subject: [PATCH 0017/1216] More accurate emulation of the DMG-emulation-mode registers. --- Core/gb.h | 10 +++++++--- Core/memory.c | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index ca810ba5..f5061bb9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -88,8 +88,11 @@ enum { 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 */ + // Has some undocumented compatibility flags written at boot. + // Unfortunately it is not readable or writable after boot has finished, so research of this + // register is quite limited. The value written to this register, however, can be controlled + // in some cases. + GB_IO_DMG_EMULATION = 0x4c, /* General CGB features */ GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch @@ -117,7 +120,8 @@ enum { 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 + // 1 is written for DMG ROMs on a CGB. Does not appear to have an effect. + GB_IO_DMG_EMULATION_INDICATION = 0x6c, // (FEh) Bit 0 (Read/Write) /* Missing */ diff --git a/Core/memory.c b/Core/memory.c index 3e8c1548..9ac5a1fd 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -100,6 +100,8 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) return gb->io_registers[GB_IO_TAC] | 0xF8; case GB_IO_STAT: return gb->io_registers[GB_IO_STAT] | 0x80; + case GB_IO_DMG_EMULATION_INDICATION: + return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE; case GB_IO_JOYP: case GB_IO_DIV: case GB_IO_TIMA: @@ -155,7 +157,7 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) } case GB_IO_KEY1: - if (!gb->is_cgb) { + if (!gb->cgb_mode) { return 0xFF; } return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E); @@ -349,6 +351,7 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch case GB_IO_HDMA3: case GB_IO_HDMA4: case GB_IO_SB: + case GB_IO_DMG_EMULATION_INDICATION: gb->io_registers[addr & 0xFF] = value; return; @@ -382,8 +385,9 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch return; case GB_IO_DMG_EMULATION: - // Todo: Can it be disabled? What about values other than 1? - gb->cgb_mode = false; + if (gb->is_cgb && !gb->bios_finished) { + gb->cgb_mode = value != 4; /* The real "contents" of this register aren't quite known yet. */ + } return; case GB_IO_DMA: From eb0b642247e4ed30aa4c46e0d86f7fc8340e533e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 6 Apr 2016 01:43:35 +0300 Subject: [PATCH 0018/1216] Improved debugger command style and usability, made code more flexible --- Core/debugger.c | 254 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 194 insertions(+), 60 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 8453bab1..ecf2f1ac 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -315,6 +315,177 @@ unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned return literal; } +typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments); + +typedef struct { + const char *command; + unsigned char min_length; + debugger_command_imp_t *implementation; + const char *help_string; // Null if should not appear in help +} debugger_command_t; + +static const char *lstrip(const char *str) +{ + while (*str == ' ' || *str == '\t') { + str++; + } + return str; +} + +static bool cont(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: continue\n"); + return true; + } + gb->debug_stopped = false; + return false; +} + +static bool next(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: next\n"); + return true; + } + + gb->debug_stopped = false; + gb->debug_next_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool step(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: step\n"); + return true; + } + + return false; +} + +static bool finish(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: finish\n"); + return true; + } + + gb->debug_stopped = false; + gb->debug_fin_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool registers(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: registers\n"); + return true; + } + + 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); + return true; +} + +static bool breakpoint(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { + gb_log(gb, "Usage: breakpoint \n"); + return true; + } + + bool error; + unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + if (!error) { + gb_log(gb, "Breakpoint moved to %04x\n", gb->breakpoint = result); + } + return true; +} + +static bool print(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { + gb_log(gb, "Usage: print \n"); + return true; + } + + bool error; + unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + if (!error) { + gb_log(gb, "=%04x\n", result); + } + return true; +} + +static bool examine(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { + gb_log(gb, "Usage: examine \n"); + return true; + } + + bool error; + unsigned short addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &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"); + } + return true; +} + +static bool help(GB_gameboy_t *gb, char *arguments); +static const debugger_command_t commands[] = { + {"continue", 1, cont, "Continue running until next stop"}, + {"next", 1, next, "Run the next instruction, skipping over function calls"}, + {"step", 1, step, "Run the next instruction, stepping into function calls"}, + {"finish", 1, finish, "Run until the current function returns"}, + {"registers", 1, registers, "Print values of processor registers and other important registers"}, + {"breakpoint", 1, breakpoint, "Move the breakpoint to a new position"}, + {"print", 1, print, "Evaluate and print an expression"}, + {"eval", 2, print, NULL}, + {"examine", 2, examine, "Examine values at address"}, + {"x", 1, examine, NULL}, + {"help", 1, help, "List available commands"}, +}; + +static bool help(GB_gameboy_t *gb, char *arguments) +{ + /* Todo: command specific help */ + const debugger_command_t *command = commands; + for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) { + if (command->help_string) { + gb_attributed_log(gb, GB_LOG_BOLD, "%s", command->command); + gb_log(gb, ": %s\n", command->help_string); + } + } + return true; +} + +static const debugger_command_t *find_command(const char *string) +{ + const debugger_command_t *command = commands; + size_t length = strlen(string); + for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) { + if (command->min_length > length) continue; + if (memcmp(command->command, string, length) == 0) { /* Is a substring? */ + return command; + } + } + + return NULL; +} /* 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. */ @@ -343,68 +514,31 @@ next_command: 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; + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; } + else { + arguments = ""; + } + + const debugger_command_t *command = find_command(command_string); + if (command) { + if (command->implementation(gb, arguments)) { + goto next_command; + } + } + else { + gb_log(gb, "%s: no such command.\n", command_string); + goto next_command; + } + + /* Split to arguments and command */ + free(input); } } \ No newline at end of file From 1c2af7fa5b4dc86d271cf66be28bccb22017a6f0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 6 Apr 2016 22:57:37 +0300 Subject: [PATCH 0019/1216] Fixed the default debugger input to strip new lines, fixing the debugger in SDL --- Core/gb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 840c13e8..7102f8f9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -97,9 +97,15 @@ static char *default_input_callback(GB_gameboy_t *gb) size_t size = 0; printf(">"); getline(&expression, &size, stdin); + if (!expression) { return strdup(""); } + + size_t length = strlen(expression); + if (expression[length - 1] == '\n') { + expression[length - 1] = 0; + } return expression; } From eb3e0eaa1ed99b22c94871f5366d931e6a361c34 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 6 Apr 2016 22:58:30 +0300 Subject: [PATCH 0020/1216] Pause the debugger on SIGINT in the SDL port --- SDL/main.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index ef01bed1..ecd6128a 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -126,12 +126,18 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, unsigned char r, unsigned char g, u return SDL_MapRGB(screen->format, r, g, b); } +GB_gameboy_t gb; + +static void debugger_interrupt(int ignore) +{ + gb.debug_stopped = true; +} + #ifdef __APPLE__ extern void cocoa_disable_filtering(void); #endif int main(int argc, char **argv) { - GB_gameboy_t gb; bool dmg = false; #define str(x) #x @@ -174,6 +180,9 @@ usage: exit(1); } + + signal(SIGINT, debugger_interrupt); + SDL_Init( SDL_INIT_EVERYTHING ); screen = SDL_SetVideoMode(160, 144, 32, SDL_SWSURFACE ); #ifdef __APPLE__ From 1069637e456b2e9d2938075ba33a2f2ca43573ce Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 7 Apr 2016 00:25:41 +0300 Subject: [PATCH 0021/1216] Added support for multiple breakpoints --- Core/debugger.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++-- Core/gb.c | 5 ++- Core/gb.h | 3 +- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index ecf2f1ac..fe490583 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -396,6 +396,27 @@ static bool registers(GB_gameboy_t *gb, char *arguments) return true; } +/* Find the index of the closest breakpoint equal or greater to addr */ +static unsigned short find_breakpoint(GB_gameboy_t *gb, unsigned short addr) +{ + if (!gb->breakpoints) { + return 0; + } + int min = 0; + int max = gb->n_breakpoints; + while (min < max) { + unsigned short pivot = (min + max) / 2; + if (gb->breakpoints[pivot] == addr) return pivot; + if (gb->breakpoints[pivot] > addr) { + max = pivot - 1; + } + else { + min = pivot + 1; + } + } + return (unsigned short) min; +} + static bool breakpoint(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { @@ -405,12 +426,85 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) bool error; unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); - if (!error) { - gb_log(gb, "Breakpoint moved to %04x\n", gb->breakpoint = result); + + if (error) return true; + + unsigned short index = find_breakpoint(gb, result); + if (index < gb->n_breakpoints && gb->breakpoints[index] == result) { + gb_log(gb, "Breakpoint already set at %04x\n", result); + return true; } + + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->breakpoints[index] = result; + gb->n_breakpoints++; + + gb_log(gb, "Breakpoint set at %04x\n", result); return true; } +static bool delete(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { + gb_log(gb, "Delete all breakpoints? "); + char *answer = gb->input_callback(gb); + if (answer[0] == 'Y' || answer[0] == 'y') { + free(gb->breakpoints); + gb->breakpoints = NULL; + gb->n_breakpoints = 0; + } + return true; + } + + bool error; + unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + + if (error) return true; + + unsigned short index = find_breakpoint(gb, result); + if (index >= gb->n_breakpoints || gb->breakpoints[index] != result) { + gb_log(gb, "No breakpoint set at %04x\n", result); + return true; + } + + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->n_breakpoints--; + + gb_log(gb, "Breakpoint removed from %04x\n", result); + return true; +} + +static bool list(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: list\n"); + return true; + } + + if (gb->n_breakpoints == 0) { + gb_log(gb, "No breakpoints set.\n"); + return true; + } + + gb_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (unsigned short i = 0; i < gb->n_breakpoints; i++) { + gb_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i]); + } + + return true; +} + +static bool should_break(GB_gameboy_t *gb, unsigned short addr) +{ + unsigned short index = find_breakpoint(gb, addr); + if (index < gb->n_breakpoints && gb->breakpoints[index] == addr) { + return true; + } + return false; +} + static bool print(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { @@ -453,6 +547,8 @@ static const debugger_command_t commands[] = { {"finish", 1, finish, "Run until the current function returns"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"breakpoint", 1, breakpoint, "Move the breakpoint to a new position"}, + {"list", 1, list, "List all set breakpoints"}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, {"print", 1, print, "Evaluate and print an expression"}, {"eval", 2, print, NULL}, {"examine", 2, examine, "Examine values at address"}, @@ -505,7 +601,7 @@ next_command: if (input) { free(input); } - if (gb->pc == gb->breakpoint && !gb->debug_stopped) { + if (!gb->debug_stopped && should_break(gb, gb->pc)) { gb->debug_stopped = true; gb_log(gb, "Breakpoint: PC = %04x\n", gb->pc); cpu_disassemble(gb, gb->pc, 5); diff --git a/Core/gb.c b/Core/gb.c index 7102f8f9..62b7950b 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -124,7 +124,6 @@ void gb_init(GB_gameboy_t *gb) 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. */ @@ -152,7 +151,6 @@ void gb_init_cgb(GB_gameboy_t *gb) 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 @@ -175,6 +173,9 @@ void gb_free(GB_gameboy_t *gb) if (gb->audio_buffer) { free(gb->audio_buffer); } + if (gb->breakpoints) { + free(gb->breakpoints); + } } int gb_load_bios(GB_gameboy_t *gb, const char *path) diff --git a/Core/gb.h b/Core/gb.h index f5061bb9..cf140782 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -270,13 +270,14 @@ typedef struct GB_gameboy_s{ 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; + unsigned short n_breakpoints; + unsigned short *breakpoints; } GB_gameboy_t; From f448865b8a51fbd11b90125b298e8404db32592c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Apr 2016 01:53:21 +0300 Subject: [PATCH 0022/1216] Silently ignoring empty lines --- Cocoa/Document.m | 2 +- Core/debugger.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index d1e20af7..fbd483d7 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -309,7 +309,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (IBAction)consoleInput:(NSTextField *)sender { NSString *line = [sender stringValue]; - if ([line isEqualToString:@""]) { + if ([line isEqualToString:@""] && lastConsoleInput) { line = lastConsoleInput; } else if (line) { diff --git a/Core/debugger.c b/Core/debugger.c index fe490583..e2d15544 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -610,6 +610,9 @@ next_command: gb->debug_next_command = false; gb->debug_fin_command = false; input = gb->input_callback(gb); + if (!input[0]) { + goto next_command; + } char *command_string = input; char *arguments = strchr(input, ' '); From 448e46ddce803095f2c1193172277db5eaab1ff7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Apr 2016 12:37:09 +0300 Subject: [PATCH 0023/1216] Support for PCM_12 and PCM_34 in SDL port --- SDL/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SDL/main.c b/SDL/main.c index ecd6128a..2bb593bc 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -194,6 +194,11 @@ usage: gb_set_pixels_output(&gb, screen->pixels); gb_set_rgb_encode_callback(&gb, rgb_encode); + /* Despite sound not being supported in the SDL port, registers PCM_12 and PCM_34 require + a sample rate to be set in order to operate. This also means PCM_XX emulation is not + really accurate yet, as it depends on the sample rate. */ + gb_set_sample_rate(&gb, 96000); + while (true) { gb_run(&gb); } From d5a13900dd1f1147527f6c477bb8f164d87f3e1e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Apr 2016 12:41:41 +0300 Subject: [PATCH 0024/1216] Missing in Cocoa's License.html --- Cocoa/License.html | 1 + 1 file changed, 1 insertion(+) diff --git a/Cocoa/License.html b/Cocoa/License.html index 2b61afc7..158c0a5d 100644 --- a/Cocoa/License.html +++ b/Cocoa/License.html @@ -42,4 +42,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    + \ No newline at end of file From d580a33a7f664910001a75c8d5c879c9e86db546 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Apr 2016 13:10:01 +0300 Subject: [PATCH 0025/1216] Save user's preferences in the Cocoa port --- Cocoa/Document.m | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index fbd483d7..0c1f9cac 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -58,7 +58,12 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un if (self) { has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; debugger_input_queue = [[NSMutableArray alloc] init]; - [self initCGB]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { + [self initDMG]; + } + else { + [self initCGB]; + } } return self; } @@ -142,6 +147,10 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un else { [self initDMG]; } + if ([sender tag] != 0) { + /* User explictly selected a model, save the preference */ + [[NSUserDefaults standardUserDefaults] setBool:!gb.is_cgb forKey:@"EmulateDMG"]; + } [self readFromFile:self.fileName ofType:@"gb"]; [self start]; } @@ -165,6 +174,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un [super windowControllerDidLoadNib:aController]; self.consoleOutput.textContainerInset = NSMakeSize(4, 4); [self.view becomeFirstResponder]; + self.view.shouldBlendFrameWithPrevious = ![[NSUserDefaults standardUserDefaults] boolForKey:@"DisableFrameBlending"]; [self start]; } @@ -216,6 +226,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (IBAction)toggleBlend:(id)sender { self.view.shouldBlendFrameWithPrevious ^= YES; + [[NSUserDefaults standardUserDefaults] setBool:!self.view.shouldBlendFrameWithPrevious forKey:@"DisableFrameBlending"]; } - (BOOL)validateUserInterfaceItem:(id)anItem From c97033b81c623883e0b74e91319f4d05be157c65 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Apr 2016 13:54:34 +0300 Subject: [PATCH 0026/1216] Console output is now configurable in the Cocoa port as "Developer Mode" --- Cocoa/AppDelegate.h | 1 + Cocoa/AppDelegate.m | 15 +++++++++++---- Cocoa/Document.m | 18 +++++++++++++----- Cocoa/MainMenu.xib | 12 ++++++++++-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index 69b6e0f1..c38bd96f 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -2,6 +2,7 @@ @interface AppDelegate : NSObject +- (IBAction)toggleDeveloperMode:(id)sender; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index d443346e..1fe8d99f 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -6,12 +6,19 @@ @implementation AppDelegate -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - // Insert code here to initialize your application +- (IBAction)toggleDeveloperMode:(id)sender { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"]; } -- (void)applicationWillTerminate:(NSNotification *)aNotification { - // Insert code here to tear down your application +- (BOOL)validateMenuItem:(NSMenuItem *)anItem +{ + if ([anItem action] == @selector(toggleDeveloperMode:)) { + [(NSMenuItem*)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; + return true; + } + + return false; } @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 0c1f9cac..7d41360e 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1,6 +1,7 @@ #include #include "AudioClient.h" -#import "Document.h" +#include "Document.h" +#include "AppDelegate.h" #include "gb.h" @interface Document () @@ -234,15 +235,20 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un if([anItem action] == @selector(mute:)) { [(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; } - if ([anItem action] == @selector(togglePause:)) { + else if ([anItem action] == @selector(togglePause:)) { [(NSMenuItem*)anItem setState:!running]; } - if ([anItem action] == @selector(reset:) && anItem.tag != 0) { + else 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:)) { + else if ([anItem action] == @selector(toggleBlend:)) { [(NSMenuItem*)anItem setState:self.view.shouldBlendFrameWithPrevious]; } + else if ([anItem action] == @selector(interrupt:)) { + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { + return false; + } + } return [super validateUserInterfaceItem:anItem]; } @@ -307,7 +313,9 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un [self log:"[...]\n"]; } [self.consoleOutput scrollToEndOfDocument:nil]; - [self.consoleWindow orderBack:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { + [self.consoleWindow orderBack:nil]; + } } pendingLogLines--; }); diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 79d13ab6..fd5a96d3 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -1,7 +1,7 @@ - + - + @@ -342,6 +342,13 @@ + + + + + + + @@ -353,6 +360,7 @@ + From 6fd2daae06899c3d965b3151630dfb84b0c5b6d4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Apr 2016 14:05:21 +0300 Subject: [PATCH 0027/1216] Added changelog, updated version to 0.2 --- CHANGES.md | 22 ++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 00000000..87c33288 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,22 @@ +# Change Log + +## Version 0.2 +### New/Improved Features + * Support for multiple breakpoints + * Better debugger command style and error reporting; includes help command + * In the SDL port, ^C in the terminal will break the debugger instead of quitting SameBoy + * Cocoa port now saves preferences (Model and frame blending) + * It is not possible to toggle console output and debugging in the Cocoa port + +### Accuracy Improvements/Fixes + * Read/write masks corrected for several registers + * Corrected 144th OAM interrupt behavior + * LCD operation moved to advance_cycles, fixing some obscure timing issues (Fixes game: X / Ekkusu) + * More accurate emulation of the registers controlling DMG emulation on CGB + * Emulation of PCM_12 and PCM_34 in the SDL port + +### Bug Fixes + * Debugger's next command can now exit a function + +## Version 0.1 + * Initial public release \ No newline at end of file diff --git a/Makefile b/Makefile index 4be3e79d..4ce99d47 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ else default: sdl endif -VERSION := 0.1 +VERSION := 0.2 BIN := build/bin OBJ := build/obj From a3b44d20cb3813f5b4c724877f1a51a351260b46 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Apr 2016 02:00:10 +0300 Subject: [PATCH 0028/1216] Added mbc/cartridge command to debugger --- Core/debugger.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index e2d15544..bf8bc240 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -539,6 +539,50 @@ static bool examine(GB_gameboy_t *gb, char *arguments) return true; } +static bool mbc(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: mbc\n"); + return true; + } + + const GB_cartridge_t *cartridge = gb->cartridge_type; + + if (cartridge->has_ram) { + gb_log(gb, "Cartrdige includes%s RAM: %zx\n", cartridge->has_battery? "battery-backed ": "", gb->mbc_ram_size); + } + else { + gb_log(gb, "No cartridge RAM\n"); + } + + if (cartridge->mbc_type) { + gb_log(gb, "MBC%d\n", cartridge->mbc_type); + gb_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); + if (cartridge->has_ram) { + gb_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); + gb_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + } + if (cartridge->mbc_type == MBC1) { + gb_log(gb, "MBC1 banking mode is %s\n", gb->mbc_ram_banking? "RAM" : "ROM"); + } + + } + else { + gb_log(gb, "No MBC\n"); + } + + if (cartridge->has_rumble) { + gb_log(gb, "Cart contains a rumble pak\n"); + } + + if (cartridge->has_rtc) { + gb_log(gb, "Cart contains a real time clock\n"); + } + + + return true; +} + static bool help(GB_gameboy_t *gb, char *arguments); static const debugger_command_t commands[] = { {"continue", 1, cont, "Continue running until next stop"}, @@ -546,6 +590,8 @@ static const debugger_command_t commands[] = { {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, + {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, + {"mbc", 3, mbc, NULL}, {"breakpoint", 1, breakpoint, "Move the breakpoint to a new position"}, {"list", 1, list, "List all set breakpoints"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, From 80d03f3c918bbbe4a388508dbfb73658486ce1b7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Apr 2016 02:02:40 +0300 Subject: [PATCH 0029/1216] Added ROM-bank wrapping support, making bootleg game "Pocket Monster Adventures" boot. (Although this game is better not booting) --- Core/memory.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index 9ac5a1fd..981bf7eb 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -264,6 +264,11 @@ static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value break; } + /* Some games assume banks wrap around. We can do this if ROM size is a power of two */ + if (gb->mbc_rom_bank >= gb->rom_size / 0x4000 && (gb->rom_size & (gb->rom_size - 1)) == 0 && gb->rom_size != 0) { + gb->mbc_rom_bank %= gb->rom_size / 0x4000; + } + if (gb->cartridge_type->mbc_type != MBC5 && !gb->mbc_rom_bank) { gb->mbc_rom_bank = 1; } From de95e6f6fc3e748d4b27f543bf9899bd32320452 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Apr 2016 15:00:29 +0300 Subject: [PATCH 0030/1216] Fixed incorrect DMA validity check, fixing The Smurfs 3 --- Core/memory.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 981bf7eb..1904d301 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -396,11 +396,12 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch return; case GB_IO_DMA: - if (value <= 0xD0) { + if (value <= 0xF1) { /* According to Pan Docs */ for (unsigned char i = 0xA0; i--;) { gb->oam[i] = read_memory(gb, (value << 8) + i); } } + /* else { what? } */ /* Todo: measure this value */ gb->dma_cycles = 640; return; From 0a09fba091822fb64cbacca502c9b91a005b546c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Apr 2016 16:48:37 +0300 Subject: [PATCH 0031/1216] Correcting DIV and TIMA speed in CGB's double speed mode --- Core/timing.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/timing.c b/Core/timing.c index b80ba770..249647b1 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -13,6 +13,9 @@ void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) gb->dma_cycles = 0; } + gb->div_cycles += cycles; + gb->tima_cycles += cycles; + if (gb->cgb_double_speed) { cycles >>=1; } @@ -20,8 +23,6 @@ void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) // 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); From bf6dff30cb9ba39df10923f2480f19da18d56217 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 Apr 2016 20:58:14 +0300 Subject: [PATCH 0032/1216] Corrected debugger's finish behavior's on interrupt handlers --- Core/z80_cpu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 6af8afd1..c0dddbdd 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -929,7 +929,6 @@ 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) From 71ef40f4b67eaa0f808374c20d5cb87bc45bd9c1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 Apr 2016 22:24:53 +0300 Subject: [PATCH 0033/1216] "Standardized" the finish/next call depth as debugger "hooks" --- Core/debugger.c | 12 ++++++++++++ Core/debugger.h | 2 ++ Core/z80_cpu.c | 10 ++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index bf8bc240..0ac7a3de 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -629,6 +629,18 @@ static const debugger_command_t *find_command(const char *string) return NULL; } +void debugger_call_hook(GB_gameboy_t *gb) +{ + /* Called just after the CPU calls a function/enters an interrupt/etc... */ + gb->debug_call_depth++; +} + +void debugger_ret_hook(GB_gameboy_t *gb) +{ + /* Called just before the CPU runs ret/reti */ + gb->debug_call_depth--; +} + /* 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) diff --git a/Core/debugger.h b/Core/debugger.h index 949523a9..33691952 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -3,5 +3,7 @@ #include "gb.h" void debugger_run(GB_gameboy_t *gb); +void debugger_call_hook(GB_gameboy_t *gb); +void debugger_ret_hook(GB_gameboy_t *gb); #endif /* debugger_h */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index c0dddbdd..5018bce9 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -3,6 +3,7 @@ #include "z80_cpu.h" #include "timing.h" #include "memory.h" +#include "debugger.h" #include "gb.h" @@ -698,11 +699,11 @@ static void halt(GB_gameboy_t *gb, unsigned char opcode) static void ret_cc(GB_gameboy_t *gb, unsigned char opcode) { if (condition_code(gb, read_memory(gb, gb->pc++))) { + debugger_ret_hook(gb); 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); @@ -749,7 +750,7 @@ static void call_cc_a16(GB_gameboy_t *gb, unsigned char opcode) 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++; + debugger_call_hook(gb); } else { advance_cycles(gb, 12); @@ -914,15 +915,16 @@ static void rst(GB_gameboy_t *gb, unsigned char opcode) 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; + debugger_call_hook(gb); } static void ret(GB_gameboy_t *gb, unsigned char opcode) { + debugger_ret_hook(gb); 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) @@ -939,7 +941,7 @@ static void call_a16(GB_gameboy_t *gb, unsigned char opcode) 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++; + debugger_call_hook(gb); } static void ld_da8_a(GB_gameboy_t *gb, unsigned char opcode) From 8dd1b3c85427384d2b4c90658b105881a3d3aeef Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 Apr 2016 23:22:54 +0300 Subject: [PATCH 0034/1216] Added (experimental) stack-leak and stack-overflow detection command to the debugger. --- Core/debugger.c | 43 +++++++++++++++++++++++++++++++++++++++++++ Core/gb.h | 4 ++++ 2 files changed, 47 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index 0ac7a3de..ba1bf149 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -378,6 +378,19 @@ static bool finish(GB_gameboy_t *gb, char *arguments) return false; } +static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: sld\n"); + return true; + } + + gb->debug_stopped = false; + gb->stack_leak_detection = true; + gb->debug_call_depth = 0; + return false; +} + static bool registers(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { @@ -589,6 +602,7 @@ static const debugger_command_t commands[] = { {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, + {"sld", 3, stack_leak_detection, "Run until the current function returns, or a stack leak is detected (Experimental)"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, mbc, NULL}, @@ -632,13 +646,41 @@ static const debugger_command_t *find_command(const char *string) void debugger_call_hook(GB_gameboy_t *gb) { /* Called just after the CPU calls a function/enters an interrupt/etc... */ + + if (gb->stack_leak_detection) { + if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) { + gb_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); + gb->debug_stopped = true; + } + else { + gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; + gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; + } + } + gb->debug_call_depth++; } void debugger_ret_hook(GB_gameboy_t *gb) { /* Called just before the CPU runs ret/reti */ + gb->debug_call_depth--; + + if (gb->stack_leak_detection) { + if (gb->debug_call_depth < 0) { + gb_log(gb, "Function finished without a stack leak.\n"); + gb->debug_stopped = true; + } + else { + if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { + gb_log(gb, "Stack leak detected for function %04x!\n", gb->addr_for_call_depth[gb->debug_call_depth]); + gb_log(gb, "SP is %04x, should be %04x.\n", gb->registers[GB_REGISTER_SP], + gb->sp_for_call_depth[gb->debug_call_depth]); + gb->debug_stopped = true; + } + } + } } /* The debugger interface is quite primitive. One letter commands with a single parameter maximum. @@ -667,6 +709,7 @@ next_command: if (gb->debug_stopped) { gb->debug_next_command = false; gb->debug_fin_command = false; + gb->stack_leak_detection = false; input = gb->input_callback(gb); if (!input[0]) { goto next_command; diff --git a/Core/gb.h b/Core/gb.h index cf140782..9140da83 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -279,6 +279,10 @@ typedef struct GB_gameboy_s{ unsigned short n_breakpoints; unsigned short *breakpoints; + bool stack_leak_detection; + unsigned short sp_for_call_depth[0x200]; /* Should be much more than enough */ + unsigned short addr_for_call_depth[0x200]; + } GB_gameboy_t; #ifndef __printflike From 2a5375a0c82008d2a6e4b7a5abca4b7086e4d22d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 Apr 2016 22:43:16 +0300 Subject: [PATCH 0035/1216] Configurable keys for the Cocoa port --- Cocoa/AppDelegate.h | 2 + Cocoa/AppDelegate.m | 33 ++++++++++- Cocoa/GBButtons.h | 24 ++++++++ Cocoa/GBButtons.m | 4 ++ Cocoa/GBPreferencesWindow.h | 5 ++ Cocoa/GBPreferencesWindow.m | 60 +++++++++++++++++++ Cocoa/GBView.m | 105 +++++++++++++--------------------- Cocoa/MainMenu.xib | 9 +-- Cocoa/NSKeyboardShortcut.h | 22 +++++++ Cocoa/NSString+StringForKey.h | 5 ++ Cocoa/NSString+StringForKey.m | 12 ++++ Cocoa/Preferences.xib | 97 +++++++++++++++++++++++++++++++ Makefile | 3 +- 13 files changed, 309 insertions(+), 72 deletions(-) create mode 100644 Cocoa/GBButtons.h create mode 100644 Cocoa/GBButtons.m create mode 100644 Cocoa/GBPreferencesWindow.h create mode 100644 Cocoa/GBPreferencesWindow.m create mode 100644 Cocoa/NSKeyboardShortcut.h create mode 100644 Cocoa/NSString+StringForKey.h create mode 100644 Cocoa/NSString+StringForKey.m create mode 100644 Cocoa/Preferences.xib diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index c38bd96f..d4ffe7ce 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -2,6 +2,8 @@ @interface AppDelegate : NSObject +@property IBOutlet NSWindow *preferencesWindow; +- (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 1fe8d99f..2b178237 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -5,6 +5,28 @@ @end @implementation AppDelegate +{ + NSWindow *preferences_window; +} + +- (void) applicationDidFinishLaunching:(NSNotification *)notification +{ +#define KEY(x) ({unichar __x = x; [NSString stringWithCharacters:&(__x) length:1];}) + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"GBRight": KEY(NSRightArrowFunctionKey), + @"GBLeft": KEY(NSLeftArrowFunctionKey), + @"GBUp": KEY(NSUpArrowFunctionKey), + @"GBDown": KEY(NSDownArrowFunctionKey), + + @"GBA": @"x", + @"GBB": @"z", + @"GBSelect": @"\x7f", + @"GBStart": @"\r", + + @"GBTurbo": @" ", + }]; +#undef KEY +} - (IBAction)toggleDeveloperMode:(id)sender { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; @@ -15,10 +37,17 @@ { if ([anItem action] == @selector(toggleDeveloperMode:)) { [(NSMenuItem*)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; - return true; } - return false; + return true; } +- (IBAction) showPreferences: (id) sender +{ + NSArray *objects; + if (!_preferencesWindow) { + [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; + } + [_preferencesWindow makeKeyAndOrderFront:self]; +} @end diff --git a/Cocoa/GBButtons.h b/Cocoa/GBButtons.h new file mode 100644 index 00000000..e02440e8 --- /dev/null +++ b/Cocoa/GBButtons.h @@ -0,0 +1,24 @@ +#ifndef GBButtons_h +#define GBButtons_h + +typedef enum : NSUInteger { + GBRight, + GBLeft, + GBUp, + GBDown, + GBA, + GBB, + GBSelect, + GBStart, + GBTurbo, + GBButtonCount +} GBButton; + +extern NSString const *GBButtonNames[GBButtonCount]; + +static inline NSString *button_to_preference_name(GBButton button) +{ + return [NSString stringWithFormat:@"GB%@", GBButtonNames[button]]; +} + +#endif diff --git a/Cocoa/GBButtons.m b/Cocoa/GBButtons.m new file mode 100644 index 00000000..9784eeff --- /dev/null +++ b/Cocoa/GBButtons.m @@ -0,0 +1,4 @@ +#import +#import "GBButtons.h" + +NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo"}; \ No newline at end of file diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h new file mode 100644 index 00000000..0d83d5a8 --- /dev/null +++ b/Cocoa/GBPreferencesWindow.h @@ -0,0 +1,5 @@ +#import + +@interface GBPreferencesWindow : NSWindow +@property IBOutlet NSTableView *controlsTableView; +@end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m new file mode 100644 index 00000000..85d545e0 --- /dev/null +++ b/Cocoa/GBPreferencesWindow.m @@ -0,0 +1,60 @@ +#import "GBPreferencesWindow.h" +#import "NSString+StringForKey.h" +#import "GBButtons.h" + +@implementation GBPreferencesWindow +{ + bool is_button_being_modified; + NSInteger button_being_modified; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return GBButtonCount; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + if ([tableColumn.identifier isEqualToString:@"keyName"]) { + return GBButtonNames[row]; + } + + if (is_button_being_modified && button_being_modified == row) { + return @"Select a new key..."; + } + + return [NSString displayStringForKeyString:[[NSUserDefaults standardUserDefaults] stringForKey: + button_to_preference_name(row)]]; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + dispatch_async(dispatch_get_main_queue(), ^{ + is_button_being_modified = true; + button_being_modified = row; + tableView.enabled = NO; + [tableView reloadData]; + [self makeFirstResponder:self]; + }); + return NO; +} + +-(void)keyDown:(NSEvent *)theEvent +{ + if (!is_button_being_modified) { + if (self.firstResponder != self.controlsTableView) { + [super keyDown:theEvent]; + } + return; + } + + is_button_being_modified = false; + + [[NSUserDefaults standardUserDefaults] setObject:theEvent.charactersIgnoringModifiers + forKey:button_to_preference_name(button_being_modified)]; + self.controlsTableView.enabled = YES; + [self.controlsTableView reloadData]; + [self makeFirstResponder:self.controlsTableView]; +} + +@end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index ada01e9a..f72e01fb 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -1,6 +1,7 @@ -#import #import #import "GBView.h" +#import "GBButtons.h" +#import "NSString+StringForKey.h" @implementation GBView { @@ -76,77 +77,51 @@ -(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; + bool handled = false; - default: - [super keyDown:theEvent]; - break; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + for (GBButton i = 0; i < GBButtonCount; i++) { + if ([[defaults stringForKey:button_to_preference_name(i)] isEqualToString:theEvent.charactersIgnoringModifiers]) { + handled = true; + switch (i) { + case GBTurbo: + _gb->turbo = true; + break; + + default: + _gb->keys[i] = true; + break; + } + } + } + + if (!handled) { + [super keyDown:theEvent]; } } -(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; + bool handled = false; - default: - [super keyUp:theEvent]; - break; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + for (GBButton i = 0; i < GBButtonCount; i++) { + if ([[defaults stringForKey:button_to_preference_name(i)] isEqualToString:theEvent.charactersIgnoringModifiers]) { + handled = true; + switch (i) { + case GBTurbo: + _gb->turbo = false; + break; + + default: + _gb->keys[i] = false; + break; + } + } + } + + if (!handled) { + [super keyUp:theEvent]; } } diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index fd5a96d3..d3bc791d 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -26,7 +26,11 @@ - + + + + + @@ -305,9 +309,6 @@ - - -
    diff --git a/Cocoa/NSKeyboardShortcut.h b/Cocoa/NSKeyboardShortcut.h new file mode 100644 index 00000000..347fc76e --- /dev/null +++ b/Cocoa/NSKeyboardShortcut.h @@ -0,0 +1,22 @@ +#ifndef NSKeyboardShortcut_h +#define NSKeyboardShortcut_h + +/* This is private API, but it is a very simple and comprehensive way + to convert a key equivalent to its display name. */ + +@interface NSKeyboardShortcut : NSObject + ++ (id)shortcutWithPreferencesEncoding:(NSString *)encoding; ++ (id)shortcutWithKeyEquivalent:(NSString *)key_equivalent modifierMask:(unsigned long long)mask; +- (id)initWithKeyEquivalent:(NSString *)key_equivalent modifierMask:(unsigned long long)mask; + +@property(readonly) unsigned long long modifierMask; +@property(readonly) NSString *keyEquivalent; +@property(readonly) NSString *preferencesEncoding; +@property(readonly) NSString *localizedModifierMaskDisplayName; +@property(readonly) NSString *localizedKeyEquivalentDisplayName; +@property(readonly) NSString *localizedDisplayName; + +@end + +#endif \ No newline at end of file diff --git a/Cocoa/NSString+StringForKey.h b/Cocoa/NSString+StringForKey.h new file mode 100644 index 00000000..d8e872b9 --- /dev/null +++ b/Cocoa/NSString+StringForKey.h @@ -0,0 +1,5 @@ +#import + +@interface NSString (StringForKey) ++ (NSString *) displayStringForKeyString: (NSString *)key_string; +@end diff --git a/Cocoa/NSString+StringForKey.m b/Cocoa/NSString+StringForKey.m new file mode 100644 index 00000000..fb8fc09f --- /dev/null +++ b/Cocoa/NSString+StringForKey.m @@ -0,0 +1,12 @@ +#import "NSString+StringForKey.h" +#import "NSKeyboardShortcut.h" + +@implementation NSString (StringForKey) + ++ (NSString *) displayStringForKeyString: (NSString *)key_string +{ + + return [[NSKeyboardShortcut shortcutWithKeyEquivalent:key_string modifierMask:0] localizedDisplayName]; +} + +@end diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib new file mode 100644 index 00000000..2a584752 --- /dev/null +++ b/Cocoa/Preferences.xib @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Makefile b/Makefile index 4ce99d47..4ebbb967 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,8 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(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 + $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/MainMenu.nib \ + $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Preferences.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/ sed s/@VERSION/$(VERSION)/ < Cocoa/info.plist > $(BIN)/Sameboy.app/Contents/info.plist From 4a05c4243d038808ec1c820b0c0009cdb563d915 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 Apr 2016 22:48:07 +0300 Subject: [PATCH 0036/1216] Corrected description for the breakpoint command --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index ba1bf149..acb73311 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -606,7 +606,7 @@ static const debugger_command_t commands[] = { {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, mbc, NULL}, - {"breakpoint", 1, breakpoint, "Move the breakpoint to a new position"}, + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression"}, {"list", 1, list, "List all set breakpoints"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, {"print", 1, print, "Evaluate and print an expression"}, From b8bc84db4ee05a3873425752c84fb4e8a1ffbf4f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Apr 2016 01:26:48 +0300 Subject: [PATCH 0037/1216] Corrected read of HDMA5, fixing A Bug's Life --- Core/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 1904d301..57d05682 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -125,7 +125,7 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) case GB_IO_SB: return gb->io_registers[addr & 0xFF]; case GB_IO_HDMA5: - return gb->io_registers[GB_IO_HDMA5] | 0x7F; + return (gb->io_registers[GB_IO_HDMA5] & 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); case GB_IO_SVBK: if (!gb->cgb_mode) { return 0xFF; From d63a801821a4ddef2a209567b3e336393e20da06 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Apr 2016 22:09:06 +0300 Subject: [PATCH 0038/1216] Fixed a mistake that made the CGB boot ROM not initialize the wave pattern. Closes #2. --- BootROMs/cgb_boot.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 17fe37cc..de32f194 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -38,7 +38,7 @@ Start: ld a, $77 ldh [$24], a - ld hl, $30 + ld hl, $FF30 ; Init waveform xor a ld c, $10 From ddc96052712da36fa724e5f8c5302171c9401775 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Apr 2016 01:14:30 +0300 Subject: [PATCH 0039/1216] Load DMG's tilemap for specific games in the CGB boot (Fixes X's title screen and an unknown game with title checksum $43) --- BootROMs/cgb_boot.asm | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index de32f194..3349f358 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -1,4 +1,4 @@ -; Sameboy DMG bootstrap ROM +; 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. @@ -255,7 +255,7 @@ TitleChecksums: db $A2 ; STAR WARS-NOA db $49 ; db $4E ; WAVERACE - db $43 ; + db $43 | $80 ; db $68 ; LOLO2 db $E0 ; YOSHI'S COOKIE db $8B ; MYSTIC QUEST @@ -313,6 +313,7 @@ FirstChecksumWithDuplicate: ChecksumsEnd: PalettePerChecksum: +; | $80 means game requires DMG boot tilemap db 0 ; Default Palette db 4 ; ALLEY WAY db 5 ; YAKUMAN @@ -325,7 +326,7 @@ PalettePerChecksum: db 5 ; F1RACE db 19 ; YOSSY NO TAMAGO db 36 ; - db 7 ; X + db 7 | $80 ; X db 37 ; MARIOLAND2 db 30 ; YOSSY NO COOKIE db 44 ; ZELDA @@ -747,6 +748,11 @@ Preboot: EmulateDMG: ld a, 1 ldh [$6C], a ; DMG Emulation + call GetPaletteIndex + bit 7, a + call nz, LoadDMGTilemap + and $7F + ld b, a ld a, [InputPalette] and a jr z, .nothingDown @@ -757,7 +763,7 @@ EmulateDMG: ld a, [hl] jr .paletteFromKeys .nothingDown - call GetPaletteIndex + ld a, b .paletteFromKeys call WaitFrame call LoadPalettesFromIndex @@ -1075,6 +1081,25 @@ ReplaceColorInAllPalettes: dec c jr nz, .loop ret + +LoadDMGTilemap: + push af + call WaitFrame + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + pop af + ret SECTION "ROMMax", ROM0[$900] ; Prevent us from overflowing From 7dc575d01e0ecd086f99c647687337a03485a3d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Apr 2016 14:08:30 +0300 Subject: [PATCH 0040/1216] Fixed a bug where audio channel 3 was playing silently instead of being muted. --- Core/apu.c | 3 +++ Core/gb.h | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 49930b47..d49f0c1e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -361,6 +361,9 @@ void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value) break; case GB_IO_NR32: gb->apu.wave_shift = ((value >> 5) + 3) & 3; + if (gb->apu.wave_shift == 3) { + gb->apu.wave_shift = 4; + } break; case GB_IO_NR43: { diff --git a/Core/gb.h b/Core/gb.h index 9140da83..f4214bf7 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -57,7 +57,6 @@ enum { 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 */ From 0fa2d6ea2f827b89b308e6e86b8a095588f0575c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Apr 2016 14:09:56 +0300 Subject: [PATCH 0041/1216] Prevent creating .sav files for ROMs claiming they have a battery but have no cartridge RAM or RTC --- Core/gb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/gb.c b/Core/gb.c index 62b7950b..5cd48f1e 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -341,6 +341,7 @@ int gb_load_state(GB_gameboy_t *gb, const char *path) int gb_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ FILE *f = fopen(path, "w"); if (!f) { return errno; From 79e4c22c6b909b568d769743435df5db1b46ad9e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Apr 2016 15:18:40 +0300 Subject: [PATCH 0042/1216] Fixed string in MBC command --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index acb73311..33053b98 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -562,7 +562,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) const GB_cartridge_t *cartridge = gb->cartridge_type; if (cartridge->has_ram) { - gb_log(gb, "Cartrdige includes%s RAM: %zx\n", cartridge->has_battery? "battery-backed ": "", gb->mbc_ram_size); + gb_log(gb, "Cartrdige includes%s RAM: %zx\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); } else { gb_log(gb, "No cartridge RAM\n"); From dde983db8f784f5146370127562a93009ac9c557 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Apr 2016 15:21:22 +0300 Subject: [PATCH 0043/1216] Updated change log and incremented version to 0.3 --- CHANGES.md | 20 ++++++++++++++++++++ Makefile | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 87c33288..f32a4b11 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,25 @@ # Change Log +## Version 0.3 +### New/Improved Features + * New debugger command: cartridge (alias: mbc) shows information about the cartridge and the current status of the MBC + * Experimental debugger command: sld (Short for Stack Leak Detection) behaves similarly to finish, but stops if a stack leak or stack overflow is detected + * The Cocoa port now allows configuring controls + +### Accuracy Improvements/Fixes + * ROM banks now wrap (Fixes game: Pocket Monster Adventures (Bootleg)) + * Fixed incorrect DMA behavior, DMA might fail for specific source addresses (Fixes game: The Smurfs 3) + * Timer registers were counting too slow in CGB double speed mode + * Corrected read behavior of the HDMA5 register (Fixes game: A Bug's Life in CGB mode) + * Fixed a bug with the CGB boot ROM that prevented initialization of the wave RAM + * The CGB boot ROM now loads the DMG tilemap for specific games, just like the original ROM (Fixes game: X (intro animation)) + * Fixed a bug where audio channel 3 was playing silently while it should have been muted. + +### Bug Fixes + * Debugger's finish command now behaves correctly when interrupts are involved + * Corrected the description for the breakpoint command + * Sameboy will not create save files for ROMs without cartridge RAM or RTC, even if they report having a battery, preventing 0-bytes save files + ## Version 0.2 ### New/Improved Features * Support for multiple breakpoints diff --git a/Makefile b/Makefile index 4ebbb967..0fce36f9 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ else default: sdl endif -VERSION := 0.2 +VERSION := 0.3 BIN := build/bin OBJ := build/obj From 8d59bfcbdda3d97d9c5a6c245901bce7a84afee8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 28 Apr 2016 23:07:05 +0300 Subject: [PATCH 0044/1216] Filter support for Cocoa port + 7 basic filters --- Cocoa/AppDelegate.m | 2 + Cocoa/Document.m | 2 +- Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 39 +++++++++ Cocoa/GBShader.h | 6 ++ Cocoa/GBShader.m | 170 ++++++++++++++++++++++++++++++++++++ Cocoa/GBView.h | 2 + Cocoa/GBView.m | 30 +++++-- Cocoa/Preferences.xib | 44 ++++++++-- Makefile | 7 +- Shaders/AAScale2x.fsh | 47 ++++++++++ Shaders/AAScale4x.fsh | 87 ++++++++++++++++++ Shaders/Bilinear.fsh | 16 ++++ Shaders/MasterShader.fsh | 17 ++++ Shaders/NearestNeighbor.fsh | 6 ++ Shaders/Scale2x.fsh | 42 +++++++++ Shaders/Scale4x.fsh | 80 +++++++++++++++++ Shaders/SmoothBilinear.fsh | 18 ++++ 18 files changed, 601 insertions(+), 15 deletions(-) create mode 100644 Cocoa/GBShader.h create mode 100644 Cocoa/GBShader.m create mode 100644 Shaders/AAScale2x.fsh create mode 100644 Shaders/AAScale4x.fsh create mode 100644 Shaders/Bilinear.fsh create mode 100644 Shaders/MasterShader.fsh create mode 100644 Shaders/NearestNeighbor.fsh create mode 100644 Shaders/Scale2x.fsh create mode 100644 Shaders/Scale4x.fsh create mode 100644 Shaders/SmoothBilinear.fsh diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 2b178237..3da9ed8a 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -24,6 +24,8 @@ @"GBStart": @"\r", @"GBTurbo": @" ", + + @"GBFilter": @"NearestNeighbor", }]; #undef KEY } diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 7d41360e..bf56895e 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -41,7 +41,7 @@ static char *consoleInput(GB_gameboy_t *gb) static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b) { - return (r << 24) | (g << 16) | (b << 8); + return (r << 0) | (g << 8) | (b << 16); } @implementation Document diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 0d83d5a8..7e1876d2 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -2,4 +2,5 @@ @interface GBPreferencesWindow : NSWindow @property IBOutlet NSTableView *controlsTableView; +@property IBOutlet NSPopUpButton *graphicsFilterPopupButton; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 85d545e0..362abd16 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -6,6 +6,38 @@ { bool is_button_being_modified; NSInteger button_being_modified; + + NSPopUpButton *_graphicsFilterPopupButton; +} + ++ (NSArray *)filterList +{ + /* The filter list as ordered in the popup button */ + static NSArray * filters = nil; + if (!filters) { + filters = @[ + @"NearestNeighbor", + @"Bilinear", + @"SmoothBilinear", + @"Scale2x", + @"Scale4x", + @"AAScale2x", + @"AAScale4x", + ]; + } + return filters; +} + +- (NSPopUpButton *)graphicsFilterPopupButton +{ + return _graphicsFilterPopupButton; +} + +- (void)setGraphicsFilterPopupButton:(NSPopUpButton *)graphicsFilterPopupButton +{ + _graphicsFilterPopupButton = graphicsFilterPopupButton; + NSString *filter = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]; + [_graphicsFilterPopupButton selectItemAtIndex:[[[self class] filterList] indexOfObject:filter]]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView @@ -56,5 +88,12 @@ [self.controlsTableView reloadData]; [self makeFirstResponder:self.controlsTableView]; } +- (IBAction)graphicFilterChanged:(NSPopUpButton *)sender +{ + + [[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]] + forKey:@"GBFilter"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; +} @end diff --git a/Cocoa/GBShader.h b/Cocoa/GBShader.h new file mode 100644 index 00000000..d2c44773 --- /dev/null +++ b/Cocoa/GBShader.h @@ -0,0 +1,6 @@ +#import + +@interface GBShader : NSObject +- (instancetype)initWithName:(NSString *) shaderName; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale; +@end diff --git a/Cocoa/GBShader.m b/Cocoa/GBShader.m new file mode 100644 index 00000000..a3aa64ec --- /dev/null +++ b/Cocoa/GBShader.m @@ -0,0 +1,170 @@ +#import "GBShader.h" +#import + +/* + Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial + */ + +static NSString * const vertex_shader = @"\n\ +attribute vec2 aPosition;\n\ +\n\ +void main(void) {\n\ + gl_Position = vec4(aPosition, 0., 1.);\n\ +}\n\ +"; + +@implementation GBShader +{ + GLuint resolution_uniform; + GLuint texture_uniform; + GLuint previous_texture_uniform; + GLuint mix_previous_uniform; + + GLuint position_attribute; + GLuint texture; + GLuint previous_texture; + GLuint program; +} + ++ (NSString *) shaderSourceForName:(NSString *) name +{ + return [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name + ofType:@"fsh" + inDirectory:@"Shaders"] + encoding:NSUTF8StringEncoding + error:nil]; +} + +- (instancetype)initWithName:(NSString *) shaderName +{ + self = [super init]; + if (self) { + // Program + NSString *fragment_shader = [[self class] shaderSourceForName:@"MasterShader"]; + fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"{filter}" + withString:[[self class] shaderSourceForName:shaderName]]; + program = [[self class] programWithVertexShader:vertex_shader fragmentShader:fragment_shader]; + // Attributes + position_attribute = glGetAttribLocation(program, "aPosition"); + // Uniforms + resolution_uniform = glGetUniformLocation(program, "uResolution"); + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + texture_uniform = glGetUniformLocation(program, "image"); + + glGenTextures(1, &previous_texture); + glBindTexture(GL_TEXTURE_2D, previous_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + previous_texture_uniform = glGetUniformLocation(program, "previousImage"); + + mix_previous_uniform = glGetUniformLocation(program, "uMixPrevious"); + + // Configure OpenGL ES + [self configureOpenGLES]; + } + return self; +} + +- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale +{ + glUseProgram(program); + glUniform2f(resolution_uniform, size.width * scale, size.height * scale); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glUniform1i(texture_uniform, 0); + glUniform1i(mix_previous_uniform, previous != NULL); + if (previous) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, previous_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(previous_texture_uniform, 1); + } + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +- (void)configureOpenGLES +{ + // Program + glUseProgram(program); + + // Attributes + glEnableVertexAttribArray(position_attribute); + static GLfloat const quad[8] = { + -1.f, -1.f, + -1.f, +1.f, + +1.f, -1.f, + +1.f, +1.f, + }; + glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 0, quad); +} + ++ (GLuint)programWithVertexShader:(NSString*)vsh fragmentShader:(NSString*)fsh +{ + // Build shaders + GLuint vertex_shader = [self shaderWithContents:vsh type:GL_VERTEX_SHADER]; + GLuint fragment_shader = [self shaderWithContents:fsh type:GL_FRAGMENT_SHADER]; + // Create program + GLuint program = glCreateProgram(); + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + // Link program + glLinkProgram(program); + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); + NSLog(@"%@:- GLSL Program Error: %s", self, messages); + } + + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program; +} + +- (void)dealloc +{ + glDeleteProgram(program); + glDeleteTextures(1, &texture); + glDeleteTextures(1, &previous_texture); +} + ++ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type +{ + + const GLchar* source = [contents UTF8String]; + // Create the shader object + GLuint shader = glCreateShader(type); + // Load the shader source + glShaderSource(shader, 1, &source, 0); + // Compile the shader + glCompileShader(shader); + // Check for errors + GLint status = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); + NSLog(@"%@:- GLSL Shader Error: %s", self, messages); + } + + return shader; +} + +@end diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index a6879493..bd042f2b 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,4 +1,5 @@ #import +#import "GBShader.h" #include "gb.h" @interface GBView : NSOpenGLView @@ -6,4 +7,5 @@ - (uint32_t *) pixels; @property GB_gameboy_t *gb; @property BOOL shouldBlendFrameWithPrevious; +@property GBShader *shader; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index f72e01fb..0f67aa39 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -3,6 +3,8 @@ #import "GBButtons.h" #import "NSString+StringForKey.h" +static GBShader *shader = nil; + @implementation GBView { uint32_t *image_buffers[3]; @@ -15,6 +17,12 @@ image_buffers[1] = malloc(160 * 144 * 4); image_buffers[2] = malloc(160 * 144 * 4); _shouldBlendFrameWithPrevious = 1; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; +} + +- (void) filterChanged +{ + self.shader = nil; } - (unsigned char) numberOfBuffers @@ -27,6 +35,7 @@ free(image_buffers[0]); free(image_buffers[1]); free(image_buffers[2]); + [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (instancetype)initWithCoder:(NSCoder *)coder { @@ -49,16 +58,21 @@ } - (void)drawRect:(NSRect)dirtyRect { + if (!self.shader) { + self.shader = [[GBShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; + } 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); + [self.shader renderBitmap:image_buffers[current_buffer] + previous:image_buffers[(current_buffer + 2) % self.numberOfBuffers] + inSize:self.bounds.size + scale:scale]; + } + else { + [self.shader renderBitmap:image_buffers[current_buffer] + previous:NULL + inSize:self.bounds.size + scale:scale]; } glFlush(); } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 2a584752..4b8f7cd4 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,5 +1,5 @@ - + @@ -14,11 +14,11 @@ - + - - + + @@ -28,6 +28,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -90,8 +123,9 @@ + - + diff --git a/Makefile b/Makefile index 0fce36f9..014c17a7 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,8 @@ $(OBJ)/%.m.o: %.m # Cocoa Port +Shaders:$(shell echo Shaders/*.fsh) + $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(shell echo Cocoa/*.icns) \ Cocoa/License.html \ @@ -75,11 +77,14 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(BIN)/BootROMs/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.app/Contents/Resources/Base.lproj/Preferences.nib \ + Shaders 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/ 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/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@mkdir -p $(dir $@) diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh new file mode 100644 index 00000000..6f4e46ec --- /dev/null +++ b/Shaders/AAScale2x.fsh @@ -0,0 +1,47 @@ +vec4 scale2x(sampler2D image) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / textureDimensions; + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + // texel arrangement + // A B C + // D E F + // G H I + vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); + vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture2D(image, texCoord + vec2( 0, 0)); + vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); + vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec2 p = texCoord * textureDimensions; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return B == F && B != D && F != H ? F : E; + } else { + // Bottom Right + return H == F && D != H && B != F ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return D == B && B != F && D != H ? D : E; + } else { + // Bottom Left + return D == H && D != B && H != F ? D : E; + } + } +} + +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + return mix(texture2D(image, texCoord), scale2x(image), 0.5); +} \ No newline at end of file diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh new file mode 100644 index 00000000..993c3196 --- /dev/null +++ b/Shaders/AAScale4x.fsh @@ -0,0 +1,87 @@ +vec4 scale2x(sampler2D image, vec2 texCoord) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / textureDimensions; + // texel arrangement + // A B C + // D E F + // G H I + vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); + vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture2D(image, texCoord + vec2( 0, 0)); + vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); + vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec2 p = texCoord * textureDimensions; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return B == F && B != D && F != H ? F : E; + } else { + // Bottom Right + return H == F && D != H && B != F ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return D == B && B != F && D != H ? D : E; + } else { + // Bottom Left + return D == H && D != B && H != F ? D : E; + } + } +} + +vec4 aaScale2x(sampler2D image, vec2 texCoord) +{ + return mix(texture2D(image, texCoord), scale2x(image, texCoord), 0.5); +} + +vec4 filter(sampler2D image) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / (textureDimensions * 2.); + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + // texel arrangement + // A B C + // D E F + // G H I + vec4 A = aaScale2x(image, texCoord + vec2( -o.x, o.y)); + vec4 B = aaScale2x(image, texCoord + vec2( 0, o.y)); + vec4 C = aaScale2x(image, texCoord + vec2( o.x, o.y)); + vec4 D = aaScale2x(image, texCoord + vec2( -o.x, 0)); + vec4 E = aaScale2x(image, texCoord + vec2( 0, 0)); + vec4 F = aaScale2x(image, texCoord + vec2( o.x, 0)); + vec4 G = aaScale2x(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = aaScale2x(image, texCoord + vec2( 0, -o.y)); + vec4 I = aaScale2x(image, texCoord + vec2( o.x, -o.y)); + vec4 R; + vec2 p = texCoord * textureDimensions * 2.; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + R = B == F && B != D && F != H ? F : E; + } else { + // Bottom Right + R = H == F && D != H && B != F ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + R = D == B && B != F && D != H ? D : E; + } else { + // Bottom Left + R = D == H && D != B && H != F ? D : E; + } + } + + return mix(R, E, 0.5); +} \ No newline at end of file diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh new file mode 100644 index 00000000..6a604e96 --- /dev/null +++ b/Shaders/Bilinear.fsh @@ -0,0 +1,16 @@ +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + vec2 pixel = texCoord * textureDimensions; + + vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + + vec4 r1 = mix(q11, q21, fract(pixel.x)); + vec4 r2 = mix(q12, q22, fract(pixel.x)); + + return mix (r1, r2, fract(pixel.y)); +} \ No newline at end of file diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh new file mode 100644 index 00000000..3821e48f --- /dev/null +++ b/Shaders/MasterShader.fsh @@ -0,0 +1,17 @@ +uniform sampler2D image; +uniform sampler2D previousImage; +uniform bool uMixPrevious; + +uniform vec2 uResolution; +const vec2 textureDimensions = vec2(160, 144); + +{filter} + +void main() { + if (uMixPrevious) { + gl_FragColor = mix(filter(image), filter(previousImage), 0.5); + } + else { + gl_FragColor = filter(image); + } +} \ No newline at end of file diff --git a/Shaders/NearestNeighbor.fsh b/Shaders/NearestNeighbor.fsh new file mode 100644 index 00000000..75a6fc18 --- /dev/null +++ b/Shaders/NearestNeighbor.fsh @@ -0,0 +1,6 @@ +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + return texture2D(image, texCoord); +} \ No newline at end of file diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh new file mode 100644 index 00000000..38efe203 --- /dev/null +++ b/Shaders/Scale2x.fsh @@ -0,0 +1,42 @@ +/* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */ + +vec4 filter(sampler2D image) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / textureDimensions; + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + // texel arrangement + // A B C + // D E F + // G H I + vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); + vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture2D(image, texCoord + vec2( 0, 0)); + vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); + vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec2 p = texCoord * textureDimensions; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return B == F && B != D && F != H ? F : E; + } else { + // Bottom Right + return H == F && D != H && B != F ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return D == B && B != F && D != H ? D : E; + } else { + // Bottom Left + return D == H && D != B && H != F ? D : E; + } + } +} \ No newline at end of file diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh new file mode 100644 index 00000000..6c5667fe --- /dev/null +++ b/Shaders/Scale4x.fsh @@ -0,0 +1,80 @@ +vec4 scale2x(sampler2D image, vec2 texCoord) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / textureDimensions; + // texel arrangement + // A B C + // D E F + // G H I + vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); + vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture2D(image, texCoord + vec2( 0, 0)); + vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); + vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec2 p = texCoord * textureDimensions; + // p = the position within a pixel [0...1] + vec4 R; + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return B == F && B != D && F != H ? F : E; + } else { + // Bottom Right + return H == F && D != H && B != F ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return D == B && B != F && D != H ? D : E; + } else { + // Bottom Left + return D == H && D != B && H != F ? D : E; + } + } +} + +vec4 filter(sampler2D image) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / (textureDimensions * 2.); + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + // texel arrangement + // A B C + // D E F + // G H I + vec4 A = scale2x(image, texCoord + vec2( -o.x, o.y)); + vec4 B = scale2x(image, texCoord + vec2( 0, o.y)); + vec4 C = scale2x(image, texCoord + vec2( o.x, o.y)); + vec4 D = scale2x(image, texCoord + vec2( -o.x, 0)); + vec4 E = scale2x(image, texCoord + vec2( 0, 0)); + vec4 F = scale2x(image, texCoord + vec2( o.x, 0)); + vec4 G = scale2x(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = scale2x(image, texCoord + vec2( 0, -o.y)); + vec4 I = scale2x(image, texCoord + vec2( o.x, -o.y)); + vec2 p = texCoord * textureDimensions * 2.; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return B == F && B != D && F != H ? F : E; + } else { + // Bottom Right + return H == F && D != H && B != F ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return D == B && B != F && D != H ? D : E; + } else { + // Bottom Left + return D == H && D != B && H != F ? D : E; + } + } +} \ No newline at end of file diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh new file mode 100644 index 00000000..2489a036 --- /dev/null +++ b/Shaders/SmoothBilinear.fsh @@ -0,0 +1,18 @@ +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + vec2 pixel = texCoord * textureDimensions; + + vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + + vec2 smooth = smoothstep(0., 1., fract(pixel)); + + vec4 r1 = mix(q11, q21, fract(smooth.x)); + vec4 r2 = mix(q12, q22, fract(smooth.x)); + + return mix (r1, r2, fract(smooth.y)); +} \ No newline at end of file From da65b4e90df4f0538fb5935b4a87fa021d5be0c5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 23 May 2016 22:22:09 +0300 Subject: [PATCH 0045/1216] Audio support for the SDL port --- SDL/main.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 2bb593bc..2d2a6042 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -133,6 +133,12 @@ static void debugger_interrupt(int ignore) gb.debug_stopped = true; } + +static void audio_callback(void *gb, Uint8 *stream, int len) +{ + apu_copy_buffer(gb, (int16_t *) stream, len / sizeof(int16_t)); +} + #ifdef __APPLE__ extern void cocoa_disable_filtering(void); #endif @@ -188,21 +194,35 @@ usage: #ifdef __APPLE__ cocoa_disable_filtering(); #endif + /* Configure Screen */ 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); - /* Despite sound not being supported in the SDL port, registers PCM_12 and PCM_34 require - a sample rate to be set in order to operate. This also means PCM_XX emulation is not - really accurate yet, as it depends on the sample rate. */ + /* Configure Audio */ + SDL_AudioSpec want, have; + SDL_memset(&want, 0, sizeof(want)); + want.freq = 96000; + want.format = AUDIO_S16SYS; + want.channels = 1; + want.samples = 512; + want.callback = audio_callback; + want.userdata = &gb; + SDL_OpenAudio(&want, &have); gb_set_sample_rate(&gb, 96000); + + /* Start Audio */ + SDL_PauseAudio(0); + /* Run emulation */ while (true) { gb_run(&gb); } + /* Won't run unless we change the condition for the above loop */ + SDL_CloseAudio(); return 0; } From 94ea44da0c6b00c771534b58b246628bb3e7c998 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Jun 2016 00:06:55 +0300 Subject: [PATCH 0046/1216] Introducing the OmniScale (beta) algorithm to SameBoy --- Cocoa/GBPreferencesWindow.m | 2 + Cocoa/GBShader.m | 1 - Cocoa/GBView.h | 2 +- Cocoa/GBView.m | 7 +++ Cocoa/Preferences.xib | 10 ++- README.md | 1 + SCALING.md | 39 ++++++++++++ Shaders/AAOmniScale.fsh | 119 ++++++++++++++++++++++++++++++++++++ Shaders/Bilinear.fsh | 2 +- Shaders/MasterShader.fsh | 1 + Shaders/OmniScale.fsh | 107 ++++++++++++++++++++++++++++++++ Shaders/SmoothBilinear.fsh | 2 +- 12 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 SCALING.md create mode 100644 Shaders/AAOmniScale.fsh create mode 100644 Shaders/OmniScale.fsh diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 362abd16..d7dd5765 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -23,6 +23,8 @@ @"Scale4x", @"AAScale2x", @"AAScale4x", + @"OmniScale", + @"AAOmniScale", ]; } return filters; diff --git a/Cocoa/GBShader.m b/Cocoa/GBShader.m index a3aa64ec..d0922e78 100644 --- a/Cocoa/GBShader.m +++ b/Cocoa/GBShader.m @@ -41,7 +41,6 @@ void main(void) {\n\ if (self) { // Program NSString *fragment_shader = [[self class] shaderSourceForName:@"MasterShader"]; - fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"\n" withString:@""]; fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"{filter}" withString:[[self class] shaderSourceForName:shaderName]]; program = [[self class] programWithVertexShader:vertex_shader fragmentShader:fragment_shader]; diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index bd042f2b..a2baada2 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -6,6 +6,6 @@ - (void) flip; - (uint32_t *) pixels; @property GB_gameboy_t *gb; -@property BOOL shouldBlendFrameWithPrevious; +@property (nonatomic) BOOL shouldBlendFrameWithPrevious; @property GBShader *shader; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0f67aa39..b0657c11 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -22,9 +22,16 @@ static GBShader *shader = nil; - (void) filterChanged { + [self setNeedsDisplay:YES]; self.shader = nil; } +- (void) setShouldBlendFrameWithPrevious:(BOOL)shouldBlendFrameWithPrevious +{ + _shouldBlendFrameWithPrevious = shouldBlendFrameWithPrevious; + [self setNeedsDisplay:YES]; +} + - (unsigned char) numberOfBuffers { return _shouldBlendFrameWithPrevious? 3 : 2; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 4b8f7cd4..99066adb 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -38,9 +38,9 @@ - + - + @@ -54,6 +54,12 @@ + + + + + + diff --git a/README.md b/README.md index d9462978..34283888 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Features currently supported only with the Cocoa version: * Battery save support * Save states * Optional frame blending + * Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x) ## Compatibility While SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), some games fail to run correctly. SameBoy is still relatively early in its development and accuracy and compatibility will be improved. diff --git a/SCALING.md b/SCALING.md new file mode 100644 index 00000000..e3839c29 --- /dev/null +++ b/SCALING.md @@ -0,0 +1,39 @@ +# Scaling + +Starting with version 0.4, the Cocoa version of SameBoy supports several GPU-accelerated scaling algorithms, some of which made their premiere at SameBoy. This document describes the algorithms supported by SameBoy. + +## General-purpose Scaling Algorithms +Common algorithms that were not made specifically for pixel art + +### Nearest Neighbor +A simple pixelated scaling algorithm we all know and love. This is the default filter. + +### Bilinear +An algorithm that fills "missing" pixels using a bilinear interpolation, causing a blurry image + +### Smooth Bilinear +A variant of bilinear filtering that applies a smooth curve to the bilinear interpolation. The results look similar to the algorithm Apple uses when scaling non-Retina graphics for Retina Displays. + +## The ScaleNx Family +The ScaleNx family is a group of algorithm that scales pixel art by the specified factor using simple pattern-based rules. The Scale3x algorithm is not yet supported in SameBoy. + +### Scale2x +The most simple algorithm of the family. It scales the image by a 2x factor without introducing new colors. + +### Scale4x +This algorithm applies the Scale2x algorithm twice to scale the image by a 4x factor. + +### Anti-aliased Scale2x +A variant of Scale2x exclusive to SameBoy that blends the Scale2x output with the Nearest Neighbor output. The specific traits of Scale2x makes this blend produce nicely looking anti-aliased output. + +### Anti-aliased Scale4x +Another exclusive algorithm that works by applying the Anti-aliased Scale2x algorithm twice + +## The OmniScale Family (beta) +OmniScale is an exclusive algorithm developed for SameBoy. It combines pattern-based rules with a unique locally paletted bilinear filtering technique to scale an image by any factor, including non-integer factors. The algorithm is currently in beta, and its pattern-based rule do not currently detect 30- and 60-degree diagonals, making them look jaggy. + +### OmniScale +The base version of the algorithm, which generates aliased output with very few new colors introduced. + +### Anti-aliased OmniScale +A variant of OmniScale that produces anti-aliased output using 2x super-sampling. \ No newline at end of file diff --git a/Shaders/AAOmniScale.fsh b/Shaders/AAOmniScale.fsh new file mode 100644 index 00000000..53caa1f6 --- /dev/null +++ b/Shaders/AAOmniScale.fsh @@ -0,0 +1,119 @@ + +float quickDistance(vec4 a, vec4 b) +{ + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); +} + +vec4 omniScale(sampler2D image, vec2 texCoord) +{ + vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); + + vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + + vec2 pos = fract(pixel); + + /* Special handling for diaonals */ + bool hasDownDiagonal = false; + bool hasUpDiagonal = false; + if (q12 == q21 && q11 != q22) hasUpDiagonal = true; + else if (q12 != q21 && q11 == q22) hasDownDiagonal = true; + else if (q12 == q21 && q11 == q22) { + if (q11 == q12) return q11; + int diagonalBias = 0; + for (float y = -1.0; y < 3.0; y++) { + for (float x = -1.0; x < 3.0; x++) { + vec4 color = texture2D(image, (pixel + vec2(x, y)) / textureDimensions); + if (color == q11) diagonalBias++; + if (color == q12) diagonalBias--; + } + } + if (diagonalBias <= 0) { + hasDownDiagonal = true; + } + if (diagonalBias >= 0) { + hasUpDiagonal = true; + } + } + + if (hasUpDiagonal || hasDownDiagonal) { + vec4 downDiagonalResult, upDiagonalResult; + + if (hasUpDiagonal) { + float diagonalPos = pos.x + pos.y; + + if (diagonalPos < 0.5) { + upDiagonalResult = q11; + } + else if (diagonalPos > 1.5) { + upDiagonalResult = q22; + } + else { + upDiagonalResult = q12; + } + } + + if (hasDownDiagonal) { + float diagonalPos = 1.0 - pos.x + pos.y; + + if (diagonalPos < 0.5) { + downDiagonalResult = q21; + } + else if (diagonalPos > 1.5) { + downDiagonalResult = q12; + } + else { + downDiagonalResult = q11; + } + } + + if (!hasUpDiagonal) return downDiagonalResult; + if (!hasDownDiagonal) return upDiagonalResult; + return mix(downDiagonalResult, upDiagonalResult, 0.5); + } + + vec4 r1 = mix(q11, q21, fract(pos.x)); + vec4 r2 = mix(q12, q22, fract(pos.x)); + + vec4 unqunatized = mix(r1, r2, fract(pos.y)); + + float q11d = quickDistance(unqunatized, q11); + float q21d = quickDistance(unqunatized, q21); + float q12d = quickDistance(unqunatized, q12); + float q22d = quickDistance(unqunatized, q22); + + float best = min(q11d, + min(q21d, + min(q12d, + q22d))); + + if (q11d == best) { + return q11; + } + + if (q21d == best) { + return q21; + } + + if (q12d == best) { + return q12; + } + + return q22; +} + +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + vec2 pixel = vec2(1.0, 1.0) / uResolution; + // 4-pixel super sampling + + vec4 q11 = omniScale(image, texCoord + pixel * vec2(-0.25, -0.25)); + vec4 q21 = omniScale(image, texCoord + pixel * vec2(+0.25, -0.25)); + vec4 q12 = omniScale(image, texCoord + pixel * vec2(-0.25, +0.25)); + vec4 q22 = omniScale(image, texCoord + pixel * vec2(+0.25, +0.25)); + + return (q11 + q21 + q12 + q22) / 4.0; +} \ No newline at end of file diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh index 6a604e96..dfe1b1e4 100644 --- a/Shaders/Bilinear.fsh +++ b/Shaders/Bilinear.fsh @@ -2,7 +2,7 @@ vec4 filter(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - vec2 pixel = texCoord * textureDimensions; + vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 3821e48f..11a714d0 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -5,6 +5,7 @@ uniform bool uMixPrevious; uniform vec2 uResolution; const vec2 textureDimensions = vec2(160, 144); +#line 1 {filter} void main() { diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh new file mode 100644 index 00000000..1bfdd6cc --- /dev/null +++ b/Shaders/OmniScale.fsh @@ -0,0 +1,107 @@ + +float quickDistance(vec4 a, vec4 b) +{ + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); +} + +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); + + vec4 q11 = texture2D(image, (pixel ) / textureDimensions); + vec4 q12 = texture2D(image, (pixel + vec2(0.0, 1.0)) / textureDimensions); + vec4 q21 = texture2D(image, (pixel + vec2(1.0, 0.0)) / textureDimensions); + vec4 q22 = texture2D(image, (pixel + vec2(1.0, 1.0)) / textureDimensions); + + vec2 pos = fract(pixel); + + /* Special handling for diaonals */ + bool hasDownDiagonal = false; + bool hasUpDiagonal = false; + if (q12 == q21 && q11 != q22) hasUpDiagonal = true; + else if (q12 != q21 && q11 == q22) hasDownDiagonal = true; + else if (q12 == q21 && q11 == q22) { + if (q11 == q12) return q11; + int diagonalBias = 0; + for (float y = -1.0; y < 3.0; y++) { + for (float x = -1.0; x < 3.0; x++) { + vec4 color = texture2D(image, (pixel + vec2(x, y)) / textureDimensions); + if (color == q11) diagonalBias++; + if (color == q12) diagonalBias--; + } + } + if (diagonalBias <= 0) { + hasDownDiagonal = true; + } + if (diagonalBias >= 0) { + hasUpDiagonal = true; + } + } + + if (hasUpDiagonal || hasDownDiagonal) { + vec4 downDiagonalResult, upDiagonalResult; + + if (hasUpDiagonal) { + float diagonalPos = pos.x + pos.y; + + if (diagonalPos < 0.5) { + upDiagonalResult = q11; + } + else if (diagonalPos > 1.5) { + upDiagonalResult = q22; + } + else { + upDiagonalResult = q12; + } + } + + if (hasDownDiagonal) { + float diagonalPos = 1.0 - pos.x + pos.y; + + if (diagonalPos < 0.5) { + downDiagonalResult = q21; + } + else if (diagonalPos > 1.5) { + downDiagonalResult = q12; + } + else { + downDiagonalResult = q11; + } + } + + if (!hasUpDiagonal) return downDiagonalResult; + if (!hasDownDiagonal) return upDiagonalResult; + return mix(downDiagonalResult, upDiagonalResult, 0.5); + } + + vec4 r1 = mix(q11, q21, fract(pos.x)); + vec4 r2 = mix(q12, q22, fract(pos.x)); + + vec4 unqunatized = mix(r1, r2, fract(pos.y)); + + float q11d = quickDistance(unqunatized, q11); + float q21d = quickDistance(unqunatized, q21); + float q12d = quickDistance(unqunatized, q12); + float q22d = quickDistance(unqunatized, q22); + + float best = min(q11d, + min(q21d, + min(q12d, + q22d))); + + if (q11d == best) { + return q11; + } + + if (q21d == best) { + return q21; + } + + if (q12d == best) { + return q12; + } + + return q22; +} \ No newline at end of file diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index 2489a036..391cba45 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -2,7 +2,7 @@ vec4 filter(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - vec2 pixel = texCoord * textureDimensions; + vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); From c27ee9d879cb6ad1408122ee920a592c7aab301f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Jun 2016 00:37:00 +0300 Subject: [PATCH 0047/1216] Fixed a bug in the Cocoa port that made SameBoy ignore some input keys when the keyboard layout is set to a non-Latin/ASCII keyboard. This was solved by forcing an ASCII layout. --- Cocoa/GBView.m | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index b0657c11..86adcccb 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -1,4 +1,5 @@ #import +#import #import "GBView.h" #import "GBButtons.h" #import "NSString+StringForKey.h" @@ -152,6 +153,27 @@ static GBShader *shader = nil; glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); } +- (BOOL)becomeFirstResponder +{ + /* Non-Roman keyboard layouts breaks user input. */ + TSMDocumentID document = TSMGetActiveDocument(); + + CFArrayRef inpu_sources = TISCreateASCIICapableInputSourceList(); + TSMSetDocumentProperty(document, kTSMDocumentEnabledInputSourcesPropertyTag, + sizeof(CFArrayRef), &inpu_sources); + CFRelease(inpu_sources); + + return [super becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder +{ + TSMDocumentID document = TSMGetActiveDocument(); + TSMRemoveDocumentProperty(document, kTSMDocumentEnabledInputSourcesPropertyTag); + + return [super resignFirstResponder]; +} + - (BOOL)acceptsFirstResponder { return YES; From 4d8f2cfac8e9ce20747c69dc0e09dc418f5ab309 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Jun 2016 18:22:47 +0300 Subject: [PATCH 0048/1216] Added missing -framework Carbon --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 014c17a7..5aaf1620 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ CONF ?= debug ifeq ($(shell uname -s),Darwin) CFLAGS += -F/Library/Frameworks -DVERSION="$(VERSION)" 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 +LDFLAGS += -framework AppKit -framework Carbon SDL_LDFLAGS := -framework SDL endif From 6bc64a99026a33da5c9762ebf6d4d7eb7806d39b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 15:28:50 +0300 Subject: [PATCH 0049/1216] Added stereo support. Correct some PCM register behavior. --- Cocoa/Document.m | 7 ++-- Cocoa/{AudioClient.h => GBAudioClient.h} | 7 ++-- Cocoa/{AudioClient.m => GBAudioClient.m} | 20 +++++------ Core/apu.c | 42 ++++++++++++++---------- Core/apu.h | 11 +++++-- Core/gb.c | 2 +- Core/gb.h | 2 +- SDL/main.c | 4 +-- 8 files changed, 51 insertions(+), 44 deletions(-) rename Cocoa/{AudioClient.h => GBAudioClient.h} (72%) rename Cocoa/{AudioClient.m => GBAudioClient.m} (89%) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index bf56895e..2fb1710a 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1,5 +1,5 @@ #include -#include "AudioClient.h" +#include "GBAudioClient.h" #include "Document.h" #include "AppDelegate.h" #include "gb.h" @@ -15,7 +15,7 @@ NSString *lastConsoleInput; } -@property AudioClient *audioClient; +@property GBAudioClient *audioClient; - (void) vblank; - (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes; - (const char *) getDebuggerInput; @@ -103,8 +103,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un 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); + self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { apu_copy_buffer(&gb, buffer, nFrames); } andSampleRate:96000]; [self.audioClient start]; diff --git a/Cocoa/AudioClient.h b/Cocoa/GBAudioClient.h similarity index 72% rename from Cocoa/AudioClient.h rename to Cocoa/GBAudioClient.h index 9c618960..19b0b843 100644 --- a/Cocoa/AudioClient.h +++ b/Cocoa/GBAudioClient.h @@ -1,11 +1,12 @@ #import +#import "apu.h" -@interface AudioClient : NSObject -@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer); +@interface GBAudioClient : NSObject +@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *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 +-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block andSampleRate:(UInt32) rate; @end diff --git a/Cocoa/AudioClient.m b/Cocoa/GBAudioClient.m similarity index 89% rename from Cocoa/AudioClient.m rename to Cocoa/GBAudioClient.m index e1436930..81ddec44 100644 --- a/Cocoa/AudioClient.m +++ b/Cocoa/GBAudioClient.m @@ -1,9 +1,9 @@ #import #import -#import "AudioClient.h" +#import "GBAudioClient.h" static OSStatus render( - AudioClient *self, + GBAudioClient *self, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, @@ -11,23 +11,19 @@ static OSStatus render( 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; - + GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData; self.renderBlock(self.rate, inNumberFrames, buffer); - return noErr; } -@implementation AudioClient +@implementation GBAudioClient { AudioComponentInstance audioUnit; } --(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block +-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block andSampleRate:(UInt32) rate { if(!(self = [super init])) @@ -70,10 +66,10 @@ static OSStatus render( streamFormat.mFormatID = kAudioFormatLinearPCM; streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; - streamFormat.mBytesPerPacket = 2; + streamFormat.mBytesPerPacket = 4; streamFormat.mFramesPerPacket = 1; - streamFormat.mBytesPerFrame = 2; - streamFormat.mChannelsPerFrame = 1; + streamFormat.mBytesPerFrame = 4; + streamFormat.mChannelsPerFrame = 2; streamFormat.mBitsPerChannel = 2 * 8; err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, diff --git a/Core/apu.c b/Core/apu.c index d49f0c1e..ad1dd796 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -60,10 +60,10 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) /* 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) +void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, GB_sample_t *samples) { for (; n_samples--; samples++) { - *samples = 0; + samples->left = samples->right = 0; if (!gb->apu.global_enable) { continue; } @@ -71,39 +71,45 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam 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; + if (gb->apu.left_on [0]) samples->left += sample; + if (gb->apu.right_on[0]) samples->right += 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; + if (gb->apu.left_on [1]) samples->left += sample; + if (gb->apu.right_on[1]) samples->right += 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; + if (gb->apu.left_on [2]) samples->left += sample; + if (gb->apu.right_on[2]) samples->right += 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; + if (gb->apu.left_on [3]) samples->left += sample; + if (gb->apu.right_on[3]) samples->right += sample; gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; } - *samples *= gb->apu.left_volume; + samples->left *= gb->apu.left_volume; + samples->right *= gb->apu.right_volume; for (unsigned char i = 0; i < 4; i++) { /* Phase */ @@ -168,7 +174,7 @@ void apu_run(GB_gameboy_t *gb) 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; + GB_sample_t sample = {0, }; apu_render(gb, gb->sample_rate, 1, &sample); gb->apu_cycles -= ticks_per_sample; if (gb->audio_position == gb->buffer_size) { @@ -186,7 +192,7 @@ void apu_run(GB_gameboy_t *gb) } } -void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count) +void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) { gb->audio_copy_in_progress = true; @@ -198,11 +204,11 @@ void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count) 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); + memset(dest + gb->audio_position, 0, (count - gb->audio_position) * sizeof(*gb->audio_buffer)); count = gb->audio_position; } - memcpy(dest, gb->audio_buffer, count * 2); - memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * 2); + memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer)); + memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * sizeof(*gb->audio_buffer)); gb->audio_position -= count; gb->audio_copy_in_progress = false; diff --git a/Core/apu.h b/Core/apu.h index 003f1ca8..a0f7036b 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -11,6 +11,12 @@ struct GB_gameboy_s; typedef struct GB_gameboy_s GB_gameboy_t; +typedef struct +{ + int16_t left; + int16_t right; +} GB_sample_t; + /* Not all used on all channels */ typedef struct { @@ -48,12 +54,11 @@ typedef struct 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_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, GB_sample_t *samples); +void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_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/gb.c b/Core/gb.c index 5cd48f1e..89375461 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -445,7 +445,7 @@ void gb_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) free(gb->audio_buffer); } gb->buffer_size = sample_rate / 25; // 40ms delay - gb->audio_buffer = malloc(gb->buffer_size * 2); + gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer)); gb->sample_rate = sample_rate; gb->audio_position = 0; } diff --git a/Core/gb.h b/Core/gb.h index f4214bf7..1207e616 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -239,7 +239,7 @@ typedef struct GB_gameboy_s{ /* APU */ GB_apu_t apu; - int16_t *audio_buffer; + GB_sample_t *audio_buffer; unsigned int buffer_size; unsigned int sample_rate; unsigned int audio_position; diff --git a/SDL/main.c b/SDL/main.c index 2d2a6042..0dbdb713 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -136,7 +136,7 @@ static void debugger_interrupt(int ignore) static void audio_callback(void *gb, Uint8 *stream, int len) { - apu_copy_buffer(gb, (int16_t *) stream, len / sizeof(int16_t)); + apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); } #ifdef __APPLE__ @@ -206,7 +206,7 @@ usage: SDL_memset(&want, 0, sizeof(want)); want.freq = 96000; want.format = AUDIO_S16SYS; - want.channels = 1; + want.channels = 2; want.samples = 512; want.callback = audio_callback; want.userdata = &gb; From 3e1863ec51a62c6f34121900959ed2e32ae72558 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 15:48:40 +0300 Subject: [PATCH 0050/1216] Battery save support in the SDL version --- SDL/main.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 0dbdb713..07e8fedc 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -7,6 +7,8 @@ #include "gb.h" +static bool running = false; + void update_keys_status(GB_gameboy_t *gb) { static bool ctrl = false; @@ -15,7 +17,7 @@ void update_keys_status(GB_gameboy_t *gb) { switch( event.type ){ case SDL_QUIT: - exit(0); + running = false; case SDL_KEYDOWN: case SDL_KEYUP: gb->stopped = false; @@ -201,6 +203,25 @@ usage: gb_set_pixels_output(&gb, screen->pixels); gb_set_rgb_encode_callback(&gb, rgb_encode); + /* Configure battery */ + size_t path_length = strlen(argv[argc - 1]); + char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ + memcpy(battery_save_path, argv[argc - 1], path_length); + + /* Remove extension */ + for (size_t i = path_length; i--;) { + if (battery_save_path[i] == '/') break; + if (battery_save_path[i] == '.') { + battery_save_path[i] = 0; + break; + } + } + + /* Add .sav */ + strcat(battery_save_path, ".sav"); + + gb_load_battery(&gb, battery_save_path); + /* Configure Audio */ SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); @@ -217,12 +238,13 @@ usage: SDL_PauseAudio(0); /* Run emulation */ - while (true) { + running = true; + while (running) { gb_run(&gb); } - - /* Won't run unless we change the condition for the above loop */ SDL_CloseAudio(); + + gb_save_battery(&gb, battery_save_path); return 0; } From aca5873de2902fed1b42b9f469c6fa2b92fd2205 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 16:31:57 +0300 Subject: [PATCH 0051/1216] More accurate STAT interrupt. This fixes Altered Space and partially fixes Pinball Deluxe. It breaks GBVideoPlayer, however. --- Core/display.c | 48 +++++++++++++++++++++++++----------------------- Core/gb.h | 3 ++- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Core/display.c b/Core/display.c index c664a566..2d4879bc 100644 --- a/Core/display.c +++ b/Core/display.c @@ -250,6 +250,13 @@ void display_run(GB_gameboy_t *gb) */ unsigned char last_mode = gb->io_registers[GB_IO_STAT] & 3; + /* + STAT interrupt is implemented based on this finding: + http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531 + */ + unsigned char previous_stat_interrupt_line = gb->stat_interrupt_line; + gb->stat_interrupt_line = false; + if (gb->display_cycles >= LCDC_PERIOD) { /* VBlank! */ gb->display_cycles -= LCDC_PERIOD; @@ -278,14 +285,10 @@ void display_run(GB_gameboy_t *gb) 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; - } + gb->stat_interrupt_line = true; } /* Todo: This behavior is seen in BGB and it fixes some ROMs with delicate timing, such as Hitman's 8bit. @@ -299,10 +302,10 @@ void display_run(GB_gameboy_t *gb) gb->effective_window_enabled = false; gb->effective_window_y = 0xFF; + if (gb->io_registers[GB_IO_STAT] & 16) { /* User requests an interrupt on VBlank*/ + gb->stat_interrupt_line = true; + } 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; } @@ -328,7 +331,7 @@ void display_run(GB_gameboy_t *gb) } } - return; + goto updateSTAT; } // Todo: verify this window behavior. It is assumed from the expected behavior of 007 - The World Is Not Enough. @@ -338,16 +341,11 @@ void display_run(GB_gameboy_t *gb) 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; - } + if (gb->io_registers[GB_IO_STAT] & 0x20) { /* User requests an interrupt on Mode 2 */ + gb->stat_interrupt_line = true; } + /* 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]; @@ -355,7 +353,7 @@ void display_run(GB_gameboy_t *gb) /* 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; + goto updateSTAT; } signed short current_lcdc_x = ((gb->display_cycles % 456 - 80) & ~7) + gb->line_x_bias; @@ -372,19 +370,23 @@ void display_run(GB_gameboy_t *gb) if (gb->display_cycles % 456 < 80 + 172) { /* Mode 3 */ gb->io_registers[GB_IO_STAT] |= 3; /* Set mode to 3 */ - return; + goto updateSTAT; } /* if (gb->display_cycles % 456 < 80 + 172 + 204) */ { /* Mode 0*/ + if (gb->io_registers[GB_IO_STAT] & 8) { /* User requests an interrupt on Mode 0 */ + gb->stat_interrupt_line = true; + } 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; + } + +updateSTAT: + if (gb->stat_interrupt_line && !previous_stat_interrupt_line) { + gb->io_registers[GB_IO_IF] |= 2; } } diff --git a/Core/gb.h b/Core/gb.h index 1207e616..a1c9f154 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -6,7 +6,7 @@ #include #include "apu.h" -#define GB_STRUCT_VERSION 6 +#define GB_STRUCT_VERSION 7 enum { GB_REGISTER_AF, @@ -225,6 +225,7 @@ typedef struct GB_gameboy_s{ signed short line_x_bias; bool effective_window_enabled; unsigned char effective_window_y; + bool stat_interrupt_line; unsigned char bios[0x900]; bool bios_finished; From 759b497c077535b0cffa724aad19033135664c09 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 16:38:20 +0300 Subject: [PATCH 0052/1216] Fixed bug introduced by the stereo support --- Core/apu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/apu.c b/Core/apu.c index ad1dd796..371bcc20 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -89,6 +89,7 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; } + if (gb->apu.wave_enable) { int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, MAX_CH_AMP, From ee7e58e44b4ec2e880c0aff8db804ceeacd6c3f0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 17:29:49 +0300 Subject: [PATCH 0053/1216] Fixed a bug introduced by the last STAT fix. This restores GBVideoPlayer support. --- Core/display.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 2d4879bc..5bae908c 100644 --- a/Core/display.c +++ b/Core/display.c @@ -288,7 +288,10 @@ void display_run(GB_gameboy_t *gb) 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; - gb->stat_interrupt_line = true; + if (gb->io_registers[GB_IO_STAT] & 0x40) { + /* User requests LYC interrupt. */ + gb->stat_interrupt_line = true; + } } /* Todo: This behavior is seen in BGB and it fixes some ROMs with delicate timing, such as Hitman's 8bit. From 6c7dd761e2c6ffb65942502abf0f4f0b8036f174 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 18:03:12 +0300 Subject: [PATCH 0054/1216] Fixed a crash related to adding and deleting breakpoints --- Core/debugger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 33053b98..62615189 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -448,7 +448,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) return true; } - gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); gb->breakpoints[index] = result; gb->n_breakpoints++; @@ -481,9 +481,9 @@ static bool delete(GB_gameboy_t *gb, char *arguments) return true; } - gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); gb->n_breakpoints--; + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); gb_log(gb, "Breakpoint removed from %04x\n", result); return true; From 36d46567bacda6dfb32df579514958ce40bb8f31 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Jun 2016 18:07:39 +0300 Subject: [PATCH 0055/1216] Updated change log and incremented version to 0.4 --- CHANGES.md | 19 ++++++++++++++++++- Makefile | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f32a4b11..c0d944de 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,22 @@ # Change Log +## Version 0.4 +This version is not compatible with save states of older versions. + +### New/Improved Features + * Cocoa port now includes several scaling algorithms, including a beta of the exclusive OmniScale algorithm + * SDL port now includes sound support + * SDL port now includes battery save support + +### Accuracy Improvements/Fixes + * APU emulation now includes stereo support + * Improved PCM registers emulation + * More accurate STAT interrupt emulation. This fixes Altered Space. + +### Bug Fixes + * The Cocoa port ignored some key presses if the user keyboard layout included non-ASCII characters + * Fixed a bug that could cause a crash when adding or removing a breakpoint + ## Version 0.3 ### New/Improved Features * New debugger command: cartridge (alias: mbc) shows information about the cartridge and the current status of the MBC @@ -18,7 +35,7 @@ ### Bug Fixes * Debugger's finish command now behaves correctly when interrupts are involved * Corrected the description for the breakpoint command - * Sameboy will not create save files for ROMs without cartridge RAM or RTC, even if they report having a battery, preventing 0-bytes save files + * SameBoy will not create save files for ROMs without cartridge RAM or RTC, even if they report having a battery, preventing 0-bytes save files ## Version 0.2 ### New/Improved Features diff --git a/Makefile b/Makefile index 5aaf1620..208706cd 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ else default: sdl endif -VERSION := 0.3 +VERSION := 0.4 BIN := build/bin OBJ := build/obj From b7e999b242545710d6473b8a5b1db614938872f6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Jun 2016 14:52:09 +0300 Subject: [PATCH 0056/1216] Modified saved data to be more future-compatible. --- Core/gb.c | 136 ++++++++++++++++------------ Core/gb.h | 214 ++++++++++++++++++++++++++------------------- Core/save_struct.h | 12 +++ 3 files changed, 215 insertions(+), 147 deletions(-) create mode 100644 Core/save_struct.h diff --git a/Core/gb.c b/Core/gb.c index 89375461..beb34c07 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -208,113 +208,134 @@ int gb_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +static bool dump_section(FILE *f, const void *src, uint32_t size) +{ + if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) { + return false; + } + + if (fwrite(src, 1, size, f) != size) { + return false; + } + + return true; +} + +#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) + /* 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_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + if (!DUMP_SECTION(gb, f, core_state)) goto error; + if (!DUMP_SECTION(gb, f, hdma )) goto error; + if (!DUMP_SECTION(gb, f, mbc )) goto error; + if (!DUMP_SECTION(gb, f, hram )) goto error; + if (!DUMP_SECTION(gb, f, timing )) goto error; + if (!DUMP_SECTION(gb, f, apu )) goto error; + if (!DUMP_SECTION(gb, f, rtc )) goto error; + if (!DUMP_SECTION(gb, f, video )) goto error; + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { - fclose(f); - return EIO; + goto error; } if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { - fclose(f); - return EIO; + goto error; } if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { - fclose(f); - return EIO; + goto error; } errno = 0; + +error: fclose(f); return errno; } +/* Best-effort read function for maximum future compatibility. */ +static bool read_section(FILE *f, void *dest, uint32_t size) +{ + uint32_t saved_size = 0; + if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) { + return false; + } + + if (saved_size <= size) { + if (fread(dest, 1, saved_size, f) != saved_size) { + return false; + } + } + else { + if (fread(dest, 1, size, f) != size) { + return false; + } + fseek(f, saved_size - size, SEEK_CUR); + } + + return true; +} + +#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) + int gb_load_state(GB_gameboy_t *gb, const char *path) { GB_gameboy_t save; + /* Every unread value should be kept the same. */ + memcpy(&save, gb, sizeof(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 (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + if (!READ_SECTION(&save, f, core_state)) goto error; + if (!READ_SECTION(&save, f, hdma )) goto error; + if (!READ_SECTION(&save, f, mbc )) goto error; + if (!READ_SECTION(&save, f, hram )) goto error; + if (!READ_SECTION(&save, f, timing )) goto error; + if (!READ_SECTION(&save, f, apu )) goto error; + if (!READ_SECTION(&save, f, rtc )) goto error; + if (!READ_SECTION(&save, f, video )) goto error; 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; + errno = -1; + goto error; } if (gb->version != save.version) { gb_log(gb, "Save state is for a different version of SameBoy.\n"); - fclose(f); - return -1; + errno = -1; + goto error; } 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; + errno = -1; + goto error; } 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; + errno = -1; + goto error; } 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; + errno = -1; + goto error; } if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { @@ -332,8 +353,9 @@ int gb_load_state(GB_gameboy_t *gb, const char *path) return EIO; } - memcpy(gb, &save, offsetof(GB_gameboy_t, first_unsaved_data)); + memcpy(gb, &save, sizeof(save)); errno = 0; +error: fclose(f); return errno; } diff --git a/Core/gb.h b/Core/gb.h index a1c9f154..e8d99d40 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -5,8 +5,10 @@ #include #include #include "apu.h" +#include "save_struct.h" -#define GB_STRUCT_VERSION 7 + +#define GB_STRUCT_VERSION 8 enum { GB_REGISTER_AF, @@ -168,120 +170,152 @@ typedef struct { 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; +/* When state saving, each section is dumped independently of other sections. + This allows adding data to the end of the section without worrying about future compatibility. + Some other changes might be "safe" as well. */ - /* CPU and General Hardware Flags*/ - bool cgb_mode; - bool is_cgb; - bool cgb_double_speed; - bool halted; - bool stopped; +typedef struct GB_gameboy_s { + GB_SECTION(header, + uintptr_t magic; // States are currently platform dependent + int version; // and version dependent + ); + + GB_SECTION(core_state, + /* Registers */ + unsigned short pc; + unsigned short registers[GB_REGISTERS_16_BIT]; + bool ime; + unsigned char interrupt_enable; + unsigned char cgb_ram_bank; + + /* CPU and General Hardware Flags*/ + bool cgb_mode; + bool is_cgb; + bool cgb_double_speed; + bool halted; + bool stopped; + bool bios_finished; + ); /* 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; + GB_SECTION(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; + ); + + /* MBC */ + GB_SECTION(mbc, + unsigned short mbc_rom_bank; + unsigned char mbc_ram_bank; + size_t mbc_ram_size; + bool mbc_ram_enable; + bool mbc_ram_banking; + ); - /* 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 ly144_bug_oam; - bool ly144_bug_hblank; - signed short previous_lcdc_x; - signed short line_x_bias; - bool effective_window_enabled; - unsigned char effective_window_y; - bool stat_interrupt_line; - - unsigned char bios[0x900]; - bool bios_finished; + /* HRAM and HW Registers */ + GB_SECTION(hram, + unsigned char hram[0xFFFF - 0xFF80]; + unsigned char io_registers[0x80]; + ); /* Timing */ - signed long last_vblank; - unsigned long display_cycles; - unsigned long div_cycles; - unsigned long tima_cycles; - unsigned long dma_cycles; - double apu_cycles; + GB_SECTION(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; + GB_SECTION(apu, + GB_apu_t apu; + ); + + /* RTC */ + GB_SECTION(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; + ); + + /* Video Display */ + GB_SECTION(video, + 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 ly144_bug_oam; + bool ly144_bug_hblank; + signed short previous_lcdc_x; + unsigned char padding; + bool effective_window_enabled; + unsigned char effective_window_y; + bool stat_interrupt_line; + signed char line_x_bias; + ); + + /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ + + /* ROM */ + unsigned char *rom; + size_t rom_size; + const GB_cartridge_t *cartridge_type; + + /* Various RAMs */ + unsigned char *ram; + unsigned char *vram; + unsigned char *mbc_ram; + + /* I/O */ + uint32_t *screen; GB_sample_t *audio_buffer; + bool keys[8]; + + /* Audio Specific */ 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; + volatile bool audio_copy_in_progress; - 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; + /* Callbacks */ + void *user_data; GB_log_callback_t log_callback; GB_input_callback_t input_callback; GB_rgb_encode_callback_t rgb_encode_callback; - void *user_data; + GB_vblank_callback_t vblank_callback; + + /* Debugger */ int debug_call_depth; bool debug_fin_command, debug_next_command; unsigned short n_breakpoints; unsigned short *breakpoints; - bool stack_leak_detection; unsigned short sp_for_call_depth[0x200]; /* Should be much more than enough */ unsigned short addr_for_call_depth[0x200]; + bool debug_stopped; + + /* Misc */ + bool turbo; + unsigned long ram_size; // Different between CGB and DMG + unsigned char bios[0x900]; } GB_gameboy_t; diff --git a/Core/save_struct.h b/Core/save_struct.h new file mode 100644 index 00000000..d88922c4 --- /dev/null +++ b/Core/save_struct.h @@ -0,0 +1,12 @@ +/* Macros to make the GB_gameboy_t struct more future compatible when state saving */ +#ifndef save_struct_h +#define save_struct_h + +#define GB_PADDING(type, old_usage) type old_usage##__do_not_use + +#define GB_SECTION(name, ...) __attribute__ ((aligned (sizeof(void*)))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end +#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) +#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) +#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) + +#endif /* save_struct_h */ From d7d8da3fa9a1b2b44781ff6708760b00f499f98d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Jun 2016 17:58:00 +0300 Subject: [PATCH 0057/1216] More accurate emulation of the SCX register --- Core/display.c | 10 +++++----- Core/gb.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index 5bae908c..4daa502d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -99,7 +99,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y) y -= gb->effective_window_y; } else { - x += gb->io_registers[GB_IO_SCX]; + x += gb->effective_scx; y += gb->io_registers[GB_IO_SCY]; } if (gb->io_registers[GB_IO_LCDC] & 0x08 && !in_window) { @@ -353,13 +353,13 @@ void display_run(GB_gameboy_t *gb) 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; + + gb->effective_scx = gb->io_registers[GB_IO_SCX]; + gb->previous_lcdc_x = - (gb->effective_scx & 0x7); goto updateSTAT; } - signed short current_lcdc_x = ((gb->display_cycles % 456 - 80) & ~7) + gb->line_x_bias; + signed short current_lcdc_x = ((gb->display_cycles % 456 - 80) & ~7) - (gb->effective_scx & 0x7); for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { if (gb->previous_lcdc_x >= 160) { continue; diff --git a/Core/gb.h b/Core/gb.h index e8d99d40..e89cc59b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -268,7 +268,7 @@ typedef struct GB_gameboy_s { bool effective_window_enabled; unsigned char effective_window_y; bool stat_interrupt_line; - signed char line_x_bias; + unsigned char effective_scx; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 8153b765a2ef1708b1f9ecf93a21018271e2bd61 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 12 Jun 2016 19:39:05 +0300 Subject: [PATCH 0058/1216] General cleanup. Minor fixes to LCD Controller accuracy. --- Core/debugger.c | 2 - Core/display.c | 118 ++++++++++++++++++++++++------------------------ Core/gb.h | 4 +- 3 files changed, 61 insertions(+), 63 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 62615189..d757086c 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -683,8 +683,6 @@ void debugger_ret_hook(GB_gameboy_t *gb) } } -/* 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; diff --git a/Core/display.c b/Core/display.c index 4daa502d..2f38afbb 100644 --- a/Core/display.c +++ b/Core/display.c @@ -240,6 +240,23 @@ void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char in (background_palette? gb->background_palletes_rgb : gb->sprite_palletes_rgb)[index / 2] = gb->rgb_encode_callback(gb, r, g, b); } + +/* + Each line is 456 cycles, approximately: + Mode 2 - 80 cycles / OAM Transfer + Mode 3 - 172 cycles / Rendering + Mode 0 - 204 cycles / HBlank + + Mode 1 is VBlank + + Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt + */ + +#define MODE2_LENGTH 80 +#define MODE3_LENGTH 172 +#define MODE1_LENGTH 204 +#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE1_LENGTH) // = 456 + void display_run(GB_gameboy_t *gb) { /* @@ -248,42 +265,43 @@ void display_run(GB_gameboy_t *gb) See http://forums.nesdev.com/viewtopic.php?f=20&t=13727 */ - unsigned char last_mode = gb->io_registers[GB_IO_STAT] & 3; /* STAT interrupt is implemented based on this finding: http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531 */ + unsigned char previous_stat_interrupt_line = gb->stat_interrupt_line; gb->stat_interrupt_line = false; + unsigned char last_mode = gb->io_registers[GB_IO_STAT] & 3; + gb->io_registers[GB_IO_STAT] &= ~3; + if (gb->display_cycles >= LCDC_PERIOD) { /* VBlank! */ gb->display_cycles -= LCDC_PERIOD; - gb->ly144_bug_oam = false; - gb->ly144_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; + + /* Some games expect LY to be zero when the LCD is off. + Todo: Verify this behavior. + Keep in mind that this only affects the value being read from the Gameboy, not the actualy display state. + This also explains why the coincidence interrupt triggers when LYC = 0 and LY = 153. */ gb->io_registers[GB_IO_LY] = 0; 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 / LINE_LENGTH; - gb->io_registers[GB_IO_LY] = gb->display_cycles / 456; + /* 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 % LINE_LENGTH > 8) { + gb->io_registers[GB_IO_LY] = 0; + } gb->io_registers[GB_IO_STAT] &= ~4; if (gb->io_registers[GB_IO_LY] == gb->io_registers[GB_IO_LYC]) { @@ -294,13 +312,7 @@ void display_run(GB_gameboy_t *gb) } } - /* 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 */ + if (gb->display_cycles >= LINE_LENGTH * 144) { /* VBlank */ gb->io_registers[GB_IO_STAT] |= 1; /* Set mode to 1 */ gb->effective_window_enabled = false; gb->effective_window_y = 0xFF; @@ -314,23 +326,9 @@ void display_run(GB_gameboy_t *gb) // LY = 144 interrupt bug if (gb->io_registers[GB_IO_LY] == 144) { - if (gb->display_cycles % 456 < 80) { // Mode 2 - if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->ly144_bug_oam) { /* User requests an interrupt on Mode 2 */ - gb->io_registers[GB_IO_IF] |= 2; - } - gb->ly144_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->ly144_bug_hblank) { /* User requests an interrupt on Mode 0 */ - /* - Todo: Verify if this actually happens. - gb->io_registers[GB_IO_IF] |= 2; - */ - } - gb->ly144_bug_hblank = true; + /* User requests an interrupt on Mode 2 */ + if (gb->display_cycles % LINE_LENGTH < 92 && gb->io_registers[GB_IO_STAT] & 0x20) { // Mode 2 + gb->stat_interrupt_line = true; } } @@ -342,7 +340,7 @@ void display_run(GB_gameboy_t *gb) gb->effective_window_enabled = true; } - if (gb->display_cycles % 456 < 80) { /* Mode 2 */ + if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH) { /* Mode 2 */ gb->io_registers[GB_IO_STAT] |= 2; /* Set mode to 2 */ if (gb->io_registers[GB_IO_STAT] & 0x20) { /* User requests an interrupt on Mode 2 */ @@ -359,32 +357,34 @@ void display_run(GB_gameboy_t *gb) goto updateSTAT; } - signed short current_lcdc_x = ((gb->display_cycles % 456 - 80) & ~7) - (gb->effective_scx & 0x7); - for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { - if (gb->previous_lcdc_x >= 160) { - continue; + if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH + MODE3_LENGTH) { /* Mode 3 */ + if (last_mode != 3) { + } - if (gb->previous_lcdc_x < 0) { - continue; + signed short current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); + 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]); } - 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 */ goto updateSTAT; } - /* if (gb->display_cycles % 456 < 80 + 172 + 204) */ { /* Mode 0*/ - if (gb->io_registers[GB_IO_STAT] & 8) { /* User requests an interrupt on Mode 0 */ - gb->stat_interrupt_line = true; - } - if (last_mode != 0) { - if (gb->hdma_on_hblank) { - gb->hdma_on = true; - gb->hdma_cycles = 0; - } + /* Mode 0*/ + if (gb->io_registers[GB_IO_STAT] & 8) { /* User requests an interrupt on Mode 0 */ + gb->stat_interrupt_line = true; + } + + if (last_mode != 0) { + if (gb->hdma_on_hblank) { + gb->hdma_on = true; + gb->hdma_cycles = 0; } } diff --git a/Core/gb.h b/Core/gb.h index e89cc59b..cf2fca78 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -261,8 +261,8 @@ typedef struct GB_gameboy_s { unsigned char sprite_palletes_data[0x40]; uint32_t background_palletes_rgb[0x20]; uint32_t sprite_palletes_rgb[0x20]; - bool ly144_bug_oam; - bool ly144_bug_hblank; + GB_PADDING(bool, ly144_bug_oam); + GB_PADDING(bool, ly144_bug_hblank); signed short previous_lcdc_x; unsigned char padding; bool effective_window_enabled; From 52afba21d10c1e244ba364249050baf381cc5e9a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 14 Jun 2016 14:11:37 +0300 Subject: [PATCH 0059/1216] Added HQ2x shader, upgrading to OpenGL 3 --- Cocoa/GBPreferencesWindow.m | 1 + Cocoa/GBShader.m | 49 ++++++++++++------- Cocoa/GBView.m | 24 ++++++++++ Cocoa/Preferences.xib | 3 +- Shaders/AAOmniScale.fsh | 12 ++--- Shaders/AAScale2x.fsh | 22 ++++----- Shaders/AAScale4x.fsh | 22 ++++----- Shaders/Bilinear.fsh | 10 ++-- Shaders/HQ2x.fsh | 93 +++++++++++++++++++++++++++++++++++++ Shaders/MasterShader.fsh | 7 ++- Shaders/NearestNeighbor.fsh | 4 +- Shaders/OmniScale.fsh | 12 ++--- Shaders/Scale2x.fsh | 20 ++++---- Shaders/Scale4x.fsh | 20 ++++---- Shaders/SmoothBilinear.fsh | 10 ++-- 15 files changed, 224 insertions(+), 85 deletions(-) create mode 100644 Shaders/HQ2x.fsh diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index d7dd5765..be32e504 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -23,6 +23,7 @@ @"Scale4x", @"AAScale2x", @"AAScale4x", + @"HQ2x", @"OmniScale", @"AAOmniScale", ]; diff --git a/Cocoa/GBShader.m b/Cocoa/GBShader.m index d0922e78..200d0772 100644 --- a/Cocoa/GBShader.m +++ b/Cocoa/GBShader.m @@ -1,15 +1,18 @@ #import "GBShader.h" -#import +#import /* Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial + + This code probably makes no sense after I upgraded it to OpenGL 3, since OpenGL makes aboslute no sense and has zero + helpful documentation. */ static NSString * const vertex_shader = @"\n\ -attribute vec2 aPosition;\n\ -\n\ +#version 150 \n\ +in vec4 aPosition;\n\ void main(void) {\n\ - gl_Position = vec4(aPosition, 0., 1.);\n\ + gl_Position = aPosition;\n\ }\n\ "; @@ -69,8 +72,9 @@ void main(void) {\n\ mix_previous_uniform = glGetUniformLocation(program, "uMixPrevious"); - // Configure OpenGL ES - [self configureOpenGLES]; + // Configure OpenGL + [self configureOpenGL]; + } return self; } @@ -90,23 +94,38 @@ void main(void) {\n\ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); glUniform1i(previous_texture_uniform, 1); } + glBindFragDataLocation(program, 0, "frag_color"); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } -- (void)configureOpenGLES +- (void)configureOpenGL { // Program + glUseProgram(program); + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint vbo; + glGenBuffers(1, &vbo); + // Attributes - glEnableVertexAttribArray(position_attribute); - static GLfloat const quad[8] = { - -1.f, -1.f, - -1.f, +1.f, - +1.f, -1.f, - +1.f, +1.f, + + + static GLfloat const quad[16] = { + -1.f, -1.f, 0, 1, + -1.f, +1.f, 0, 1, + +1.f, -1.f, 0, 1, + +1.f, +1.f, 0, 1, }; - glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 0, quad); + + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW); + glEnableVertexAttribArray(position_attribute); + glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0); } + (GLuint)programWithVertexShader:(NSString*)vsh fragmentShader:(NSString*)fsh @@ -129,7 +148,6 @@ void main(void) {\n\ glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); NSLog(@"%@:- GLSL Program Error: %s", self, messages); } - // Delete shaders glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); @@ -162,7 +180,6 @@ void main(void) {\n\ glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); NSLog(@"%@:- GLSL Shader Error: %s", self, messages); } - return shader; } diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 86adcccb..b101634b 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -12,6 +12,30 @@ static GBShader *shader = nil; unsigned char current_buffer; } +- (void) awakeFromNib +{ + NSOpenGLPixelFormatAttribute attrs[] = + { + NSOpenGLPFAOpenGLProfile, + NSOpenGLProfileVersion3_2Core, + 0 + }; + + NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs] ; + + if (!pf) + { + NSLog(@"No OpenGL pixel format"); + } + + NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil] ; + + [self setPixelFormat:pf]; + + [self setOpenGLContext:context]; +} + + - (void) _init { image_buffers[0] = malloc(160 * 144 * 4); diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 99066adb..8f0a6b2b 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -38,7 +38,7 @@ - + @@ -54,6 +54,7 @@ + diff --git a/Shaders/AAOmniScale.fsh b/Shaders/AAOmniScale.fsh index 53caa1f6..6f325ac1 100644 --- a/Shaders/AAOmniScale.fsh +++ b/Shaders/AAOmniScale.fsh @@ -8,10 +8,10 @@ vec4 omniScale(sampler2D image, vec2 texCoord) { vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q11 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q12 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); vec2 pos = fract(pixel); @@ -25,7 +25,7 @@ vec4 omniScale(sampler2D image, vec2 texCoord) int diagonalBias = 0; for (float y = -1.0; y < 3.0; y++) { for (float x = -1.0; x < 3.0; x++) { - vec4 color = texture2D(image, (pixel + vec2(x, y)) / textureDimensions); + vec4 color = texture(image, (pixel + vec2(x, y)) / textureDimensions); if (color == q11) diagonalBias++; if (color == q12) diagonalBias--; } @@ -104,7 +104,7 @@ vec4 omniScale(sampler2D image, vec2 texCoord) return q22; } -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; vec2 pixel = vec2(1.0, 1.0) / uResolution; diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh index 6f4e46ec..bdd84037 100644 --- a/Shaders/AAScale2x.fsh +++ b/Shaders/AAScale2x.fsh @@ -8,15 +8,15 @@ vec4 scale2x(sampler2D image) // A B C // D E F // G H I - vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); - vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture2D(image, texCoord + vec2( 0, 0)); - vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); - vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture(image, texCoord + vec2( 0, o.y)); + vec4 C = texture(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture(image, texCoord + vec2( 0, 0)); + vec4 F = texture(image, texCoord + vec2( o.x, 0)); + vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); vec2 p = texCoord * textureDimensions; // p = the position within a pixel [0...1] p = fract(p); @@ -39,9 +39,9 @@ vec4 scale2x(sampler2D image) } } -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - return mix(texture2D(image, texCoord), scale2x(image), 0.5); + return mix(texture(image, texCoord), scale2x(image), 0.5); } \ No newline at end of file diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh index 993c3196..83380d13 100644 --- a/Shaders/AAScale4x.fsh +++ b/Shaders/AAScale4x.fsh @@ -6,15 +6,15 @@ vec4 scale2x(sampler2D image, vec2 texCoord) // A B C // D E F // G H I - vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); - vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture2D(image, texCoord + vec2( 0, 0)); - vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); - vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture(image, texCoord + vec2( 0, o.y)); + vec4 C = texture(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture(image, texCoord + vec2( 0, 0)); + vec4 F = texture(image, texCoord + vec2( o.x, 0)); + vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); vec2 p = texCoord * textureDimensions; // p = the position within a pixel [0...1] p = fract(p); @@ -39,10 +39,10 @@ vec4 scale2x(sampler2D image, vec2 texCoord) vec4 aaScale2x(sampler2D image, vec2 texCoord) { - return mix(texture2D(image, texCoord), scale2x(image, texCoord), 0.5); + return mix(texture(image, texCoord), scale2x(image, texCoord), 0.5); } -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { // o = offset, the width of a pixel vec2 o = 1.0 / (textureDimensions * 2.); diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh index dfe1b1e4..a519e12d 100644 --- a/Shaders/Bilinear.fsh +++ b/Shaders/Bilinear.fsh @@ -1,13 +1,13 @@ -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q11 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q12 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); vec4 r1 = mix(q11, q21, fract(pixel.x)); vec4 r2 = mix(q12, q22, fract(pixel.x)); diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh new file mode 100644 index 00000000..a729b5ed --- /dev/null +++ b/Shaders/HQ2x.fsh @@ -0,0 +1,93 @@ +/* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ + +/* Todo: Add the real YUV difference from HQ2x*/ +bool is_different(vec4 a, vec4 b) +{ + return length(a - b) > 0.15; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) +{ + return (c1 * w1 + c2 * w2) / (w1 + w2); +} + +vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) +{ + return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); +} + +vec4 scale(sampler2D image) +{ + // o = offset, the width of a pixel + vec2 o = 1.0 / textureDimensions; + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ + + // p = the position within a pixel [0...1] + vec2 p = fract(texCoord * textureDimensions); + + if (p.x > 0.5) o.x = -o.x; + if (p.y > 0.5) o.y = -o.y; + + + + vec4 w0 = texture(image, texCoord + vec2( -o.x, -o.y)); + vec4 w1 = texture(image, texCoord + vec2( 0, -o.y)); + vec4 w2 = texture(image, texCoord + vec2( o.x, -o.y)); + vec4 w3 = texture(image, texCoord + vec2( -o.x, 0)); + vec4 w4 = texture(image, texCoord + vec2( 0, 0)); + vec4 w5 = texture(image, texCoord + vec2( o.x, 0)); + vec4 w6 = texture(image, texCoord + vec2( -o.x, o.y)); + vec4 w7 = texture(image, texCoord + vec2( 0, o.y)); + vec4 w8 = texture(image, texCoord + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1; + if (is_different(w1, w4)) pattern |= 2; + if (is_different(w2, w4)) pattern |= 4; + if (is_different(w3, w4)) pattern |= 8; + if (is_different(w5, w4)) pattern |= 16; + if (is_different(w6, w4)) pattern |= 32; + if (is_different(w7, w4)) pattern |= 64; + if (is_different(w8, w4)) pattern |= 128; + + if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) + return interp_2px(w4, 3.0, w3, 1.0); + if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) + return interp_2px(w4, 3.0, w1, 1.0); + if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) + return w4; + if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) || + P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) || + P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) || + P(0xeb,0x8a)) && is_different(w3, w1)) + return interp_2px(w4, 3.0, w0, 1.0); + if (P(0x0b,0x08)) + return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); + if (P(0x0b,0x02)) + return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); + if (P(0x2f,0x2f)) + return interp_3px(w4, 1.04, w3, 1.0, w1, 1.0); + if (P(0xbf,0x37) || P(0xdb,0x13)) + return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); + if (P(0xdb,0x49) || P(0xef,0x6d)) + return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); + if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) + return interp_2px(w4, 3.0, w3, 1.0); + if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) + return interp_2px(w4, 3.0, w1, 1.0); + if (P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e)) + return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); + if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || + P(0xdf,0xde) || P(0xdf,0x1e)) + return interp_2px(w4, 3.0, w0, 1.0); + if (P(0x0a,0x00) || P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || + P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || + P(0x3b,0x1b)) + return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); + + return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); +} \ No newline at end of file diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 11a714d0..642026ee 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -1,3 +1,4 @@ +#version 150 uniform sampler2D image; uniform sampler2D previousImage; uniform bool uMixPrevious; @@ -5,14 +6,16 @@ uniform bool uMixPrevious; uniform vec2 uResolution; const vec2 textureDimensions = vec2(160, 144); +out vec4 frag_color; + #line 1 {filter} void main() { if (uMixPrevious) { - gl_FragColor = mix(filter(image), filter(previousImage), 0.5); + frag_color = mix(scale(image), scale(previousImage), 0.5); } else { - gl_FragColor = filter(image); + frag_color = scale(image); } } \ No newline at end of file diff --git a/Shaders/NearestNeighbor.fsh b/Shaders/NearestNeighbor.fsh index 75a6fc18..661e5fb3 100644 --- a/Shaders/NearestNeighbor.fsh +++ b/Shaders/NearestNeighbor.fsh @@ -1,6 +1,6 @@ -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - return texture2D(image, texCoord); + return texture(image, texCoord); } \ No newline at end of file diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index 1bfdd6cc..cd4257d3 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -4,16 +4,16 @@ float quickDistance(vec4 a, vec4 b) return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture2D(image, (pixel ) / textureDimensions); - vec4 q12 = texture2D(image, (pixel + vec2(0.0, 1.0)) / textureDimensions); - vec4 q21 = texture2D(image, (pixel + vec2(1.0, 0.0)) / textureDimensions); - vec4 q22 = texture2D(image, (pixel + vec2(1.0, 1.0)) / textureDimensions); + vec4 q11 = texture(image, (pixel ) / textureDimensions); + vec4 q12 = texture(image, (pixel + vec2(0.0, 1.0)) / textureDimensions); + vec4 q21 = texture(image, (pixel + vec2(1.0, 0.0)) / textureDimensions); + vec4 q22 = texture(image, (pixel + vec2(1.0, 1.0)) / textureDimensions); vec2 pos = fract(pixel); @@ -27,7 +27,7 @@ vec4 filter(sampler2D image) int diagonalBias = 0; for (float y = -1.0; y < 3.0; y++) { for (float x = -1.0; x < 3.0; x++) { - vec4 color = texture2D(image, (pixel + vec2(x, y)) / textureDimensions); + vec4 color = texture(image, (pixel + vec2(x, y)) / textureDimensions); if (color == q11) diagonalBias++; if (color == q12) diagonalBias--; } diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh index 38efe203..3968057f 100644 --- a/Shaders/Scale2x.fsh +++ b/Shaders/Scale2x.fsh @@ -1,6 +1,6 @@ /* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */ -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { // o = offset, the width of a pixel vec2 o = 1.0 / textureDimensions; @@ -10,15 +10,15 @@ vec4 filter(sampler2D image) // A B C // D E F // G H I - vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); - vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture2D(image, texCoord + vec2( 0, 0)); - vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); - vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture(image, texCoord + vec2( 0, o.y)); + vec4 C = texture(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture(image, texCoord + vec2( 0, 0)); + vec4 F = texture(image, texCoord + vec2( o.x, 0)); + vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); vec2 p = texCoord * textureDimensions; // p = the position within a pixel [0...1] p = fract(p); diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh index 6c5667fe..1c763fc4 100644 --- a/Shaders/Scale4x.fsh +++ b/Shaders/Scale4x.fsh @@ -6,15 +6,15 @@ vec4 scale2x(sampler2D image, vec2 texCoord) // A B C // D E F // G H I - vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture2D(image, texCoord + vec2( 0, o.y)); - vec4 C = texture2D(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture2D(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture2D(image, texCoord + vec2( 0, 0)); - vec4 F = texture2D(image, texCoord + vec2( o.x, 0)); - vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture2D(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y)); + vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); + vec4 B = texture(image, texCoord + vec2( 0, o.y)); + vec4 C = texture(image, texCoord + vec2( o.x, o.y)); + vec4 D = texture(image, texCoord + vec2( -o.x, 0)); + vec4 E = texture(image, texCoord + vec2( 0, 0)); + vec4 F = texture(image, texCoord + vec2( o.x, 0)); + vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); + vec4 H = texture(image, texCoord + vec2( 0, -o.y)); + vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); vec2 p = texCoord * textureDimensions; // p = the position within a pixel [0...1] vec4 R; @@ -38,7 +38,7 @@ vec4 scale2x(sampler2D image, vec2 texCoord) } } -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { // o = offset, the width of a pixel vec2 o = 1.0 / (textureDimensions * 2.); diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index 391cba45..443a10fc 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -1,13 +1,13 @@ -vec4 filter(sampler2D image) +vec4 scale(sampler2D image) { vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q11 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q12 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); + vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); vec2 smooth = smoothstep(0., 1., fract(pixel)); From e6c4b4d1b27a04efea1efee2b5c4361846ff79f2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 14 Jun 2016 14:12:13 +0300 Subject: [PATCH 0060/1216] Seems like I forgot to finish implementing the stop instruction! --- Core/display.c | 2 +- Core/z80_cpu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 2f38afbb..fac567c6 100644 --- a/Core/display.c +++ b/Core/display.c @@ -198,7 +198,7 @@ void display_vblank(GB_gameboy_t *gb) frames++; */ - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + if (!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) { /* LCD is off, memset screen to white */ memset(gb->screen, 0xFF, 160 * 144 * 4); } diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 5018bce9..e94beda5 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1333,7 +1333,7 @@ void cpu_run(GB_gameboy_t *gb) /* Run pseudo instructions rst 40-60*/ rst(gb, 0x87 + interrupt_bit * 8); } - else if(!gb->halted) { + else if(!gb->halted && !gb->stopped) { unsigned char opcode = read_memory(gb, gb->pc); opcodes[opcode](gb, opcode); } From 6f73ee053d340261d4af4f43f22b9fc6e9c6410f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 Jun 2016 01:06:52 +0300 Subject: [PATCH 0061/1216] The old OmniScale filter became OmniScale Legacy. A new OmniScale filter was added instead. --- Cocoa/GBPreferencesWindow.m | 3 +- Cocoa/Preferences.xib | 9 +- ...{AAOmniScale.fsh => AAOmniScaleLegacy.fsh} | 0 Shaders/HQ2x.fsh | 11 +- Shaders/OmniScale.fsh | 244 +++++++++++------- Shaders/OmniScaleLegacy.fsh | 107 ++++++++ 6 files changed, 280 insertions(+), 94 deletions(-) rename Shaders/{AAOmniScale.fsh => AAOmniScaleLegacy.fsh} (100%) create mode 100644 Shaders/OmniScaleLegacy.fsh diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index be32e504..0aee2c2a 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -25,7 +25,8 @@ @"AAScale4x", @"HQ2x", @"OmniScale", - @"AAOmniScale", + @"OmniScaleLegacy", + @"AAOmniScaleLegacy", ]; } return filters; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 8f0a6b2b..af4b38e7 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -38,7 +38,7 @@ - + @@ -54,11 +54,12 @@ - - + + + - + diff --git a/Shaders/AAOmniScale.fsh b/Shaders/AAOmniScaleLegacy.fsh similarity index 100% rename from Shaders/AAOmniScale.fsh rename to Shaders/AAOmniScaleLegacy.fsh diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index a729b5ed..14316b65 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -1,9 +1,16 @@ /* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ -/* Todo: Add the real YUV difference from HQ2x*/ +vec3 rgb_to_yuv(vec4 rgb) +{ + return vec3( 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b, + -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b, + 0.615 * rgb.r + 0.515 * rgb.g - 0.100 * rgb.b); +} + bool is_different(vec4 a, vec4 b) { - return length(a - b) > 0.15; + vec3 diff = abs(rgb_to_yuv(a) - rgb_to_yuv(b)); + return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; } #define P(m, r) ((pattern & (m)) == (r)) diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index cd4257d3..f0658a1a 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -1,107 +1,177 @@ +/* OmniScale is derived from the pattern based design of HQnx, but with the following general differences: + - The actual output calculating was completely redesigned as resolution independent graphic generator. This allows + scaling to any factor. + - HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients. + - "Quarters" can be interpolated in more ways than in the HQnx filters + - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels + per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. + */ -float quickDistance(vec4 a, vec4 b) +vec3 rgb_to_yuv(vec4 rgb) { - return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); + return vec3( 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b, + -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b, + 0.615 * rgb.r + 0.515 * rgb.g - 0.100 * rgb.b); } +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_yuv(a) - rgb_to_yuv(b)); + return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; +} + +#define P(m, r) ((pattern & (m)) == (r)) + vec4 scale(sampler2D image) { + // o = offset, the width of a pixel + vec2 o = 1.0 / textureDimensions; vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ - vec4 q11 = texture(image, (pixel ) / textureDimensions); - vec4 q12 = texture(image, (pixel + vec2(0.0, 1.0)) / textureDimensions); - vec4 q21 = texture(image, (pixel + vec2(1.0, 0.0)) / textureDimensions); - vec4 q22 = texture(image, (pixel + vec2(1.0, 1.0)) / textureDimensions); + // p = the position within a pixel [0...1] + vec2 p = fract(texCoord * textureDimensions); - vec2 pos = fract(pixel); - - /* Special handling for diaonals */ - bool hasDownDiagonal = false; - bool hasUpDiagonal = false; - if (q12 == q21 && q11 != q22) hasUpDiagonal = true; - else if (q12 != q21 && q11 == q22) hasDownDiagonal = true; - else if (q12 == q21 && q11 == q22) { - if (q11 == q12) return q11; - int diagonalBias = 0; - for (float y = -1.0; y < 3.0; y++) { - for (float x = -1.0; x < 3.0; x++) { - vec4 color = texture(image, (pixel + vec2(x, y)) / textureDimensions); - if (color == q11) diagonalBias++; - if (color == q12) diagonalBias--; - } - } - if (diagonalBias <= 0) { - hasDownDiagonal = true; - } - if (diagonalBias >= 0) { - hasUpDiagonal = true; - } + if (p.x > 0.5) { + o.x = -o.x; + p.x = 1.0 - p.x; + } + if (p.y > 0.5) { + o.y = -o.y; + p.y = 1.0 - p.y; } - if (hasUpDiagonal || hasDownDiagonal) { - vec4 downDiagonalResult, upDiagonalResult; - if (hasUpDiagonal) { - float diagonalPos = pos.x + pos.y; - if (diagonalPos < 0.5) { - upDiagonalResult = q11; - } - else if (diagonalPos > 1.5) { - upDiagonalResult = q22; - } - else { - upDiagonalResult = q12; - } + vec4 w0 = texture(image, texCoord + vec2( -o.x, -o.y)); + vec4 w1 = texture(image, texCoord + vec2( 0, -o.y)); + vec4 w2 = texture(image, texCoord + vec2( o.x, -o.y)); + vec4 w3 = texture(image, texCoord + vec2( -o.x, 0)); + vec4 w4 = texture(image, texCoord + vec2( 0, 0)); + vec4 w5 = texture(image, texCoord + vec2( o.x, 0)); + vec4 w6 = texture(image, texCoord + vec2( -o.x, o.y)); + vec4 w7 = texture(image, texCoord + vec2( 0, o.y)); + vec4 w8 = texture(image, texCoord + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1 << 0; + if (is_different(w1, w4)) pattern |= 1 << 1; + if (is_different(w2, w4)) pattern |= 1 << 2; + if (is_different(w3, w4)) pattern |= 1 << 3; + if (is_different(w5, w4)) pattern |= 1 << 4; + if (is_different(w6, w4)) pattern |= 1 << 5; + if (is_different(w7, w4)) pattern |= 1 << 6; + if (is_different(w8, w4)) pattern |= 1 << 7; + + if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) + return mix(w4, w3, 0.5 - p.x); + if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) + return mix(w4, w1, 0.5 - p.y); + if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) + return w4; + if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) || + P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) || + P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) || + P(0xeb,0x8a)) && is_different(w3, w1)) + return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); + if (P(0x0b,0x08)) + return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); + if (P(0x0b,0x02)) + return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); + if (P(0x2f,0x2f)) { + if (length(p - vec2(0.5)) < 0.5) { + return w4; } - - if (hasDownDiagonal) { - float diagonalPos = 1.0 - pos.x + pos.y; - - if (diagonalPos < 0.5) { - downDiagonalResult = q21; - } - else if (diagonalPos > 1.5) { - downDiagonalResult = q12; - } - else { - downDiagonalResult = q11; - } + if (is_different(w0, w1) || is_different(w0, w3)) { + return mix(w1, w3, p.y - p.x + 0.5); } - - if (!hasUpDiagonal) return downDiagonalResult; - if (!hasDownDiagonal) return upDiagonalResult; - return mix(downDiagonalResult, upDiagonalResult, 0.5); + return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + if (P(0xbf,0x37) || P(0xdb,0x13)) { + if (p.x - 2.0 * p.y > 0) { + return w1; + } + return mix(w3, w4, p.x + 0.5); + } + if (P(0xdb,0x49) || P(0xef,0x6d)) { + if (p.y - 2.0 * p.x > 0) { + return w3; + } + return mix(w1, w4, p.x * 2); + } + if ( P(0xbf,0x8f) || P(0x7e,0x0e)) { + if (p.x + 2.0 * p.y < 1.0) { + if (is_different(w0, w1) || is_different(w0, w3)) { + return mix(w1, w3, p.y - p.x + 0.5); + } + return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + return w4; + } + if (P(0x7e,0x2a) || P(0xef,0xab)) { + if (p.y + 2.0 * p.x < 1.0) { + if (is_different(w0, w1) || is_different(w0, w3)) { + return mix(w1, w3, p.y - p.x + 0.5); + } + return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + return w4; + } + if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) + return mix(w4, w3, 0.5 - p.x); + if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) + return mix(w4, w1, 0.5 - p.y); + if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || + P(0xdf,0xde) || P(0xdf,0x1e)) + return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + if (P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || + P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || + P(0x3b,0x1b)) { + if (p.x + p.y > 0.5) { + return w4; + } + if (is_different(w0, w1) || is_different(w0, w3)) { + return mix(w1, w3, p.y - p.x + 0.5); + } + return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); } - vec4 r1 = mix(q11, q21, fract(pos.x)); - vec4 r2 = mix(q12, q22, fract(pos.x)); + if (P(0x0b,0x01)) + //return vec4(1,0,0,0); + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); + + if (P(0x0b,0x00)) + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); + + if (p.x + p.y > 0.5) + return w4; + + /* We need more samples to "solve" this diagonal */ + vec4 x0 = texture(image, texCoord + vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture(image, texCoord + vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture(image, texCoord + vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture(image, texCoord + vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture(image, texCoord + vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture(image, texCoord + vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture(image, texCoord + vec2( -o.x * 2.0, o.y )); + + if (is_different(x0, w4)) pattern |= 1 << 8; + if (is_different(x1, w4)) pattern |= 1 << 9; + if (is_different(x2, w4)) pattern |= 1 << 10; + if (is_different(x3, w4)) pattern |= 1 << 11; + if (is_different(x4, w4)) pattern |= 1 << 12; + if (is_different(x5, w4)) pattern |= 1 << 13; + if (is_different(x6, w4)) pattern |= 1 << 14; + + int diagonal_bias = -7; + while (pattern != 0) { + diagonal_bias += pattern & 1; + pattern >>= 1; + } + + if (diagonal_bias <= 0) + return mix(w1, w3, p.y - p.x + 0.5); - vec4 unqunatized = mix(r1, r2, fract(pos.y)); - - float q11d = quickDistance(unqunatized, q11); - float q21d = quickDistance(unqunatized, q21); - float q12d = quickDistance(unqunatized, q12); - float q22d = quickDistance(unqunatized, q22); - - float best = min(q11d, - min(q21d, - min(q12d, - q22d))); - - if (q11d == best) { - return q11; - } - - if (q21d == best) { - return q21; - } - - if (q12d == best) { - return q12; - } - - return q22; + return w4; } \ No newline at end of file diff --git a/Shaders/OmniScaleLegacy.fsh b/Shaders/OmniScaleLegacy.fsh new file mode 100644 index 00000000..cd4257d3 --- /dev/null +++ b/Shaders/OmniScaleLegacy.fsh @@ -0,0 +1,107 @@ + +float quickDistance(vec4 a, vec4 b) +{ + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); +} + +vec4 scale(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); + + vec4 q11 = texture(image, (pixel ) / textureDimensions); + vec4 q12 = texture(image, (pixel + vec2(0.0, 1.0)) / textureDimensions); + vec4 q21 = texture(image, (pixel + vec2(1.0, 0.0)) / textureDimensions); + vec4 q22 = texture(image, (pixel + vec2(1.0, 1.0)) / textureDimensions); + + vec2 pos = fract(pixel); + + /* Special handling for diaonals */ + bool hasDownDiagonal = false; + bool hasUpDiagonal = false; + if (q12 == q21 && q11 != q22) hasUpDiagonal = true; + else if (q12 != q21 && q11 == q22) hasDownDiagonal = true; + else if (q12 == q21 && q11 == q22) { + if (q11 == q12) return q11; + int diagonalBias = 0; + for (float y = -1.0; y < 3.0; y++) { + for (float x = -1.0; x < 3.0; x++) { + vec4 color = texture(image, (pixel + vec2(x, y)) / textureDimensions); + if (color == q11) diagonalBias++; + if (color == q12) diagonalBias--; + } + } + if (diagonalBias <= 0) { + hasDownDiagonal = true; + } + if (diagonalBias >= 0) { + hasUpDiagonal = true; + } + } + + if (hasUpDiagonal || hasDownDiagonal) { + vec4 downDiagonalResult, upDiagonalResult; + + if (hasUpDiagonal) { + float diagonalPos = pos.x + pos.y; + + if (diagonalPos < 0.5) { + upDiagonalResult = q11; + } + else if (diagonalPos > 1.5) { + upDiagonalResult = q22; + } + else { + upDiagonalResult = q12; + } + } + + if (hasDownDiagonal) { + float diagonalPos = 1.0 - pos.x + pos.y; + + if (diagonalPos < 0.5) { + downDiagonalResult = q21; + } + else if (diagonalPos > 1.5) { + downDiagonalResult = q12; + } + else { + downDiagonalResult = q11; + } + } + + if (!hasUpDiagonal) return downDiagonalResult; + if (!hasDownDiagonal) return upDiagonalResult; + return mix(downDiagonalResult, upDiagonalResult, 0.5); + } + + vec4 r1 = mix(q11, q21, fract(pos.x)); + vec4 r2 = mix(q12, q22, fract(pos.x)); + + vec4 unqunatized = mix(r1, r2, fract(pos.y)); + + float q11d = quickDistance(unqunatized, q11); + float q21d = quickDistance(unqunatized, q21); + float q12d = quickDistance(unqunatized, q12); + float q22d = quickDistance(unqunatized, q22); + + float best = min(q11d, + min(q21d, + min(q12d, + q22d))); + + if (q11d == best) { + return q11; + } + + if (q21d == best) { + return q21; + } + + if (q12d == best) { + return q12; + } + + return q22; +} \ No newline at end of file From 5723b82293ceb53a03752d79f6f17de257acb97d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 Jun 2016 02:27:32 +0300 Subject: [PATCH 0062/1216] Fixed graphical glitches caused by the last change to LCDC emulation --- Core/display.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Core/display.c b/Core/display.c index fac567c6..e4229330 100644 --- a/Core/display.c +++ b/Core/display.c @@ -327,7 +327,7 @@ void display_run(GB_gameboy_t *gb) // LY = 144 interrupt bug if (gb->io_registers[GB_IO_LY] == 144) { /* User requests an interrupt on Mode 2 */ - if (gb->display_cycles % LINE_LENGTH < 92 && gb->io_registers[GB_IO_STAT] & 0x20) { // Mode 2 + if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH && gb->io_registers[GB_IO_STAT] & 0x20) { // Mode 2 gb->stat_interrupt_line = true; } } @@ -357,21 +357,22 @@ void display_run(GB_gameboy_t *gb) goto updateSTAT; } + + /* Render. This chunk is outside the Mode 3 if, because otherwise we might not render some pixels, since this + function only runs between atomic CPU changes, and not every clock. */ + signed short current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); + 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 % LINE_LENGTH < MODE2_LENGTH + MODE3_LENGTH) { /* Mode 3 */ - if (last_mode != 3) { - - } - signed short current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); - 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]); - } gb->io_registers[GB_IO_STAT] |= 3; /* Set mode to 3 */ goto updateSTAT; } From 846a9318babe37b98d7ac72f0fbd48c4997b0146 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 Jun 2016 23:47:41 +0300 Subject: [PATCH 0063/1216] Added anti aliasing to OmniScale. Fixed color differentiation for HQ2x and OmniScale --- Shaders/HQ2x.fsh | 12 ++-- Shaders/OmniScale.fsh | 145 ++++++++++++++++++++++++++++++++---------- 2 files changed, 118 insertions(+), 39 deletions(-) diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index 14316b65..7a4fb1ea 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -1,15 +1,17 @@ /* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ -vec3 rgb_to_yuv(vec4 rgb) +/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is + also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ +vec3 rgb_to_hq_colospace(vec4 rgb) { - return vec3( 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b, - -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b, - 0.615 * rgb.r + 0.515 * rgb.g - 0.100 * rgb.b); + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); } bool is_different(vec4 a, vec4 b) { - vec3 diff = abs(rgb_to_yuv(a) - rgb_to_yuv(b)); + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; } diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index f0658a1a..7e8b6cbe 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -7,17 +7,19 @@ per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. */ -vec3 rgb_to_yuv(vec4 rgb) +/* We use the same colorspace as the HQ algorithms. */ +vec3 rgb_to_hq_colospace(vec4 rgb) { - return vec3( 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b, - -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b, - 0.615 * rgb.r + 0.515 * rgb.g - 0.100 * rgb.b); + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); } + bool is_different(vec4 a, vec4 b) { - vec3 diff = abs(rgb_to_yuv(a) - rgb_to_yuv(b)); - return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.125 || diff.y > 0.027 || diff.z > 0.031; } #define P(m, r) ((pattern & (m)) == (r)) @@ -80,71 +82,141 @@ vec4 scale(sampler2D image) if (P(0x0b,0x02)) return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); if (P(0x2f,0x2f)) { - if (length(p - vec2(0.5)) < 0.5) { + float dist = length(p - vec2(0.5)); + float pixel_size = length(1.0 / (uResolution / textureDimensions)); + if (dist < 0.5 - pixel_size / 2) { return w4; } + vec4 r; if (is_different(w0, w1) || is_different(w0, w3)) { - return mix(w1, w3, p.y - p.x + 0.5); + r = mix(w1, w3, p.y - p.x + 0.5); } - return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist > 0.5 + pixel_size / 2) { + return r; + } + return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); } if (P(0xbf,0x37) || P(0xdb,0x13)) { - if (p.x - 2.0 * p.y > 0) { + float dist = p.x - 2.0 * p.y; + float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + if (dist > pixel_size / 2) { return w1; } - return mix(w3, w4, p.x + 0.5); + vec4 r = mix(w3, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w1, (dist + pixel_size / 2) / pixel_size); } if (P(0xdb,0x49) || P(0xef,0x6d)) { - if (p.y - 2.0 * p.x > 0) { + float dist = p.y - 2.0 * p.x; + float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + if (p.y - 2.0 * p.x > pixel_size / 2) { return w3; } - return mix(w1, w4, p.x * 2); - } - if ( P(0xbf,0x8f) || P(0x7e,0x0e)) { - if (p.x + 2.0 * p.y < 1.0) { - if (is_different(w0, w1) || is_different(w0, w3)) { - return mix(w1, w3, p.y - p.x + 0.5); - } - return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + vec4 r = mix(w1, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; } - return w4; + return mix(r, w3, (dist + pixel_size / 2) / pixel_size); } + if (P(0xbf,0x8f) || P(0x7e,0x0e)) { + float dist = p.x + 2.0 * p.y; + float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + + if (dist > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + + } + if (P(0x7e,0x2a) || P(0xef,0xab)) { - if (p.y + 2.0 * p.x < 1.0) { - if (is_different(w0, w1) || is_different(w0, w3)) { - return mix(w1, w3, p.y - p.x + 0.5); - } - return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + float dist = p.y + 2.0 * p.x; + float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + + if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { + return w4; } - return w4; + + vec4 r; + + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); } + if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) return mix(w4, w3, 0.5 - p.x); + if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) return mix(w4, w1, 0.5 - p.y); + if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || P(0xdf,0xde) || P(0xdf,0x1e)) return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + if (P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || P(0x3b,0x1b)) { - if (p.x + p.y > 0.5) { + float dist = p.x + p.y; + float pixel_size = length(1.0 / (uResolution / textureDimensions)); + + if (dist > 0.5 + pixel_size / 2) { return w4; } + + vec4 r; if (is_different(w0, w1) || is_different(w0, w3)) { - return mix(w1, w3, p.y - p.x + 0.5); + r = mix(w1, w3, p.y - p.x + 0.5); } - return mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 0.5 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); } if (P(0x0b,0x01)) - //return vec4(1,0,0,0); return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); if (P(0x0b,0x00)) return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); - if (p.x + p.y > 0.5) + float dist = p.x + p.y; + float pixel_size = length(1.0 / (uResolution / textureDimensions)); + + if (dist > 0.5 + pixel_size / 2) return w4; /* We need more samples to "solve" this diagonal */ @@ -170,8 +242,13 @@ vec4 scale(sampler2D image) pattern >>= 1; } - if (diagonal_bias <= 0) - return mix(w1, w3, p.y - p.x + 0.5); + if (diagonal_bias <= 0) { + vec4 r = mix(w1, w3, p.y - p.x + 0.5); + if (dist < 0.5 - pixel_size / 2) { + return r; + } + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } return w4; } \ No newline at end of file From 7e8d5fe57c7801d1049836cb66c8039780dc2975 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 00:01:51 +0300 Subject: [PATCH 0064/1216] Updated SCALING.md --- SCALING.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/SCALING.md b/SCALING.md index e3839c29..6af8d053 100644 --- a/SCALING.md +++ b/SCALING.md @@ -29,11 +29,20 @@ A variant of Scale2x exclusive to SameBoy that blends the Scale2x output with th ### Anti-aliased Scale4x Another exclusive algorithm that works by applying the Anti-aliased Scale2x algorithm twice -## The OmniScale Family (beta) -OmniScale is an exclusive algorithm developed for SameBoy. It combines pattern-based rules with a unique locally paletted bilinear filtering technique to scale an image by any factor, including non-integer factors. The algorithm is currently in beta, and its pattern-based rule do not currently detect 30- and 60-degree diagonals, making them look jaggy. +## The HQnx Family +A relatively modern family of scaling algorithms that makes an extensive use of lookup tables to create scaled anti-aliased output. The HQnx family includes several scaling factors and variants. -### OmniScale +### HQ2x +Currently HQ2x is the only HQnx algorithm in SameBoy. As the name implies, it scales the image by a factor of 2. + +### The OmniScale algorithm +OmniScale is an exclusive algorithm developed for SameBoy. It is inspired by HQnx's lookup tables, but improves on them by handling more cases. OmniScale can scale an image by any factor, including non-integer factors, and produces high quality anti-aliased output. + +## The OmniScale Legacy Family (beta) +An old prototype of the OmniScale algorithm. It combines pattern-based rules with a unique locally paletted bilinear filtering technique to scale an image by any factor, including non-integer factors. Its pattern-based rule do not currently detect 30- and 60-degree diagonals, making them look jaggy. The output OmniScale Legacy produces is quite unique, as it tends to produce non-trivial patterns. + +### OmniScale Legacy The base version of the algorithm, which generates aliased output with very few new colors introduced. -### Anti-aliased OmniScale +### Anti-aliased OmniScale Legacy A variant of OmniScale that produces anti-aliased output using 2x super-sampling. \ No newline at end of file From 07c5e8fcb172a7b88073b2f5d35b2dfbee1b4ca4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 02:05:52 +0300 Subject: [PATCH 0065/1216] Fixed smooth bilinear filter --- Shaders/SmoothBilinear.fsh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index 443a10fc..ab242f5d 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -9,10 +9,10 @@ vec4 scale(sampler2D image) vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec2 smooth = smoothstep(0., 1., fract(pixel)); + vec2 s = smoothstep(0., 1., fract(pixel)); - vec4 r1 = mix(q11, q21, fract(smooth.x)); - vec4 r2 = mix(q12, q22, fract(smooth.x)); + vec4 r1 = mix(q11, q21, fract(s.x)); + vec4 r2 = mix(q12, q22, fract(s.x)); - return mix (r1, r2, fract(smooth.y)); + return mix (r1, r2, fract(s.y)); } \ No newline at end of file From d58ddef07b8bb73c4b0732f9f547a0b75f7dce91 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 15:01:51 +0300 Subject: [PATCH 0066/1216] Proper initial value for the Joypad register --- Core/gb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index beb34c07..99eafaab 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -132,6 +132,8 @@ void gb_init(GB_gameboy_t *gb) 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 + + gb->io_registers[GB_IO_JOYP] = 0xF; } void gb_init_cgb(GB_gameboy_t *gb) @@ -154,6 +156,8 @@ void gb_init_cgb(GB_gameboy_t *gb) gb->cgb_ram_bank = 1; gb->input_callback = default_input_callback; gb->cartridge_type = &cart_defs[0]; // Default cartridge type + + gb->io_registers[GB_IO_JOYP] = 0xF; } void gb_free(GB_gameboy_t *gb) From 64832e0e9ea2933ee14f74f1a9258062c8f0c923 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 16:51:25 +0300 Subject: [PATCH 0067/1216] HDMA accuracy improvement --- Core/memory.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index 57d05682..16196421 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -461,6 +461,10 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch 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; + /* Todo: Verify this. Gambatte's DMA tests require this. */ + if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) { + gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4; + } gb->hdma_cycles = 0; return; From b99ed2676a8b1eca4a1572576fbbec4a70054d9f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 17:20:40 +0300 Subject: [PATCH 0068/1216] Use NSThread instead of GDC when invoking run --- Cocoa/Document.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 2fb1710a..df5b007f 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -118,9 +118,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (void) start { if (running) return; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self run]; - }); + [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; } - (void) stop From 180f0e16b02139f9b14c639431f6af79cc5f7c93 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 17:43:39 +0300 Subject: [PATCH 0069/1216] Cocoa port now remembers the previous window size --- Cocoa/Document.h | 1 + Cocoa/Document.m | 8 ++++++++ Cocoa/Document.xib | 1 + 3 files changed, 10 insertions(+) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 869abe6a..6a47aa7f 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -6,6 +6,7 @@ @property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSPanel *consoleWindow; @property (strong) IBOutlet NSTextField *consoleInput; +@property (strong) IBOutlet NSWindow *mainWindow; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index df5b007f..ea4f7652 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -173,6 +173,12 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un self.consoleOutput.textContainerInset = NSMakeSize(4, 4); [self.view becomeFirstResponder]; self.view.shouldBlendFrameWithPrevious = ![[NSUserDefaults standardUserDefaults] boolForKey:@"DisableFrameBlending"]; + CGRect window_frame = self.mainWindow.frame; + window_frame.size.width = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowWidth"], + window_frame.size.width); + window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"], + window_frame.size.height); + [self.mainWindow setFrame:window_frame display:YES]; [self start]; } @@ -199,6 +205,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (void)close { + [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"]; + [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; [self stop]; [self.consoleWindow close]; [super close]; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index d2cd3f95..b3066894 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -9,6 +9,7 @@ + From 32a1ad87b773d896e071dc0280039fc6a4d69954 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 17:48:02 +0300 Subject: [PATCH 0070/1216] Fixed a bug where a Cocoa emulator window will appear frozen (or partially frozen) until resized --- Cocoa/GBView.m | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index b101634b..28224301 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -4,8 +4,6 @@ #import "GBButtons.h" #import "NSString+StringForKey.h" -static GBShader *shader = nil; - @implementation GBView { uint32_t *image_buffers[3]; @@ -93,7 +91,10 @@ static GBShader *shader = nil; if (!self.shader) { self.shader = [[GBShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; } + double scale = self.window.backingScaleFactor; + glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); + if (_shouldBlendFrameWithPrevious) { [self.shader renderBitmap:image_buffers[current_buffer] previous:image_buffers[(current_buffer + 2) % self.numberOfBuffers] @@ -171,12 +172,6 @@ static GBShader *shader = nil; } } --(void)reshape -{ - double scale = self.window.backingScaleFactor; - glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); -} - - (BOOL)becomeFirstResponder { /* Non-Roman keyboard layouts breaks user input. */ From 70bd90740a8ca265b7e9bb6ab1960fb232979cf7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Jun 2016 20:29:11 +0300 Subject: [PATCH 0071/1216] Mass name and type changes. Save states are now compatible between 32- and 64-bit systems. Maybe. --- Cocoa/Document.m | 56 ++-- Core/apu.c | 36 +-- Core/apu.h | 39 +-- Core/debugger.c | 196 ++++++------ Core/debugger.h | 6 +- Core/display.c | 60 ++-- Core/display.h | 4 +- Core/gb.c | 60 ++-- Core/gb.h | 159 +++++----- Core/joypad.c | 12 +- Core/joypad.h | 4 +- Core/memory.c | 90 +++--- Core/memory.h | 6 +- Core/save_struct.h | 4 +- Core/timing.c | 14 +- Core/timing.h | 4 +- Core/z80_cpu.c | 668 ++++++++++++++++++++-------------------- Core/z80_cpu.h | 4 +- Core/z80_disassembler.c | 428 ++++++++++++------------- SDL/main.c | 34 +- 20 files changed, 948 insertions(+), 936 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ea4f7652..730812fc 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -17,7 +17,7 @@ @property GBAudioClient *audioClient; - (void) vblank; -- (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes; +- (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes; - (const char *) getDebuggerInput; @end @@ -27,7 +27,7 @@ static void vblank(GB_gameboy_t *gb) [self vblank]; } -static void consoleLog(GB_gameboy_t *gb, const char *string, gb_log_attributes attributes) +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]; @@ -39,7 +39,7 @@ static char *consoleInput(GB_gameboy_t *gb) return strdup([self getDebuggerInput]); } -static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b) +static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return (r << 0) | (g << 8) | (b << 16); } @@ -71,47 +71,47 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (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_init(&gb); + GB_load_boot_rom(&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_init_cgb(&gb); + GB_load_boot_rom(&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); + GB_set_pixels_output(&gb, self.view.pixels); } - (void) run { running = true; - gb_set_pixels_output(&gb, self.view.pixels); + GB_set_pixels_output(&gb, self.view.pixels); self.view.gb = &gb; - gb_set_sample_rate(&gb, 96000); + GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { - apu_copy_buffer(&gb, buffer, nFrames); + GB_apu_copy_buffer(&gb, buffer, nFrames); } andSampleRate:96000]; [self.audioClient start]; while (running) { - gb_run(&gb); + GB_run(&gb); } [self.audioClient stop]; - gb_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); stopping = false; } @@ -137,7 +137,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un { bool was_cgb = gb.is_cgb; [self stop]; - gb_free(&gb); + GB_free(&gb); is_inited = false; if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) { [self initCGB]; @@ -165,7 +165,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un - (void)dealloc { - gb_free(&gb); + GB_free(&gb); } - (void)windowControllerDidLoadNib:(NSWindowController *)aController { @@ -198,8 +198,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un if (is_inited++) { return YES; } - gb_load_rom(&gb, [fileName UTF8String]); - gb_load_battery(&gb, [[[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + GB_load_rom(&gb, [fileName UTF8String]); + GB_load_battery(&gb, [[[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); return YES; } @@ -283,7 +283,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un return rect; } -- (void) log: (const char *) string withAttributes: (gb_log_attributes) attributes +- (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. */ @@ -367,7 +367,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un if (!gb.debug_stopped) { [self stop]; } - gb_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); + GB_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); if (was_running) { [self start]; } @@ -379,7 +379,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un if (!gb.debug_stopped) { [self stop]; } - gb_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); + GB_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); if (was_running) { [self start]; } diff --git a/Core/apu.c b/Core/apu.c index 371bcc20..2db8c36e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -27,7 +27,7 @@ static int16_t generate_square(double phase, int16_t amplitude, double duty) return 0; } -static int16_t generate_wave(double phase, int16_t amplitude, signed char *wave, unsigned char shift) +static int16_t generate_wave(double phase, int16_t amplitude, int8_t *wave, uint8_t shift) { phase = fmod(phase, 2 * M_PI); return ((wave[(int)(phase / (2 * M_PI) * 32)]) >> shift) * (int)amplitude / 0xF; @@ -60,7 +60,7 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) /* 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, GB_sample_t *samples) +void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_samples, GB_sample_t *samples) { for (; n_samples--; samples++) { samples->left = samples->right = 0; @@ -112,7 +112,7 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam samples->left *= gb->apu.left_volume; samples->right *= gb->apu.right_volume; - for (unsigned char i = 0; i < 4; i++) { + for (uint8_t 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) { @@ -137,7 +137,7 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam 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++) { + for (uint8_t 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; @@ -151,7 +151,7 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam 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); + uint16_t temp = (uint16_t) (2048 - 131072 / gb->apu.wave_channels[0].frequency); // Apply sweep temp = temp + gb->apu.wave_channels[0].sweep_direction * @@ -169,19 +169,19 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam } } -void apu_run(GB_gameboy_t *gb) +void GB_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) { GB_sample_t sample = {0, }; - apu_render(gb, gb->sample_rate, 1, &sample); + GB_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"); + GB_log(gb, "Audio overflow\n"); should_log_overflow = false; } */ @@ -193,7 +193,7 @@ void apu_run(GB_gameboy_t *gb) } } -void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) +void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) { gb->audio_copy_in_progress = true; @@ -204,7 +204,7 @@ void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) } if (count > gb->audio_position) { - // gb_log(gb, "Audio underflow: %d\n", 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) * sizeof(*gb->audio_buffer)); count = gb->audio_position; } @@ -215,7 +215,7 @@ void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) gb->audio_copy_in_progress = false; } -void apu_init(GB_gameboy_t *gb) +void GB_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; @@ -227,12 +227,12 @@ void apu_init(GB_gameboy_t *gb) } } -unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg) +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) { /* Todo: what happens when reading from the wave from while it's playing? */ if (reg == GB_IO_NR52) { - unsigned char value = 0; + uint8_t value = 0; for (int i = 0; i < 4; i++) { value >>= 1; if (gb->apu.wave_channels[i].is_playing) { @@ -260,17 +260,17 @@ unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg) }; 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 (uint8_t)((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) +void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t 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; + uint8_t channel = 0; if (!gb->apu.global_enable && reg != GB_IO_NR52) { return; @@ -376,7 +376,7 @@ void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value) { double r = value & 0x7; if (r == 0) r = 0.5; - unsigned char s = value >> 4; + uint8_t s = value >> 4; gb->apu.wave_channels[3].frequency = 524288.0 / r / (1 << (s + 1)); gb->apu.lfsr_7_bit = value & 0x8; break; @@ -406,7 +406,7 @@ void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value) case GB_IO_NR52: if ((value & 0x80) && !gb->apu.global_enable) { - apu_init(gb); + GB_apu_init(gb); gb->apu.global_enable = true; } else if (!(value & 0x80) && gb->apu.global_enable) { diff --git a/Core/apu.h b/Core/apu.h index a0f7036b..835c569f 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -7,6 +7,7 @@ #define MAX_CH_AMP 0x1E00 #define CH_STEP (0x1E00/0xF) +#include "save_struct.h" struct GB_gameboy_s; typedef struct GB_gameboy_s GB_gameboy_t; @@ -20,30 +21,30 @@ typedef struct /* Not all used on all channels */ typedef struct { - double phase; - double frequency; + GB_aligned_double phase; + GB_aligned_double frequency; + GB_aligned_double duty; + GB_aligned_double sound_length; /* In seconds */ 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; + uint8_t envelope_steps; + uint8_t cur_envelope_steps; signed int envelope_direction; - unsigned char sweep_steps; - unsigned char cur_sweep_steps; + uint8_t sweep_steps; + uint8_t cur_sweep_steps; signed int sweep_direction; - unsigned char sweep_shift; + uint8_t 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; + GB_aligned_double envelope_step_timer; /* In seconds */ + GB_aligned_double sweep_step_timer; /* In seconds */ + int8_t wave_form[32]; + uint8_t wave_shift; bool wave_enable; uint16_t lfsr; bool lfsr_7_bit; @@ -54,11 +55,11 @@ typedef struct bool global_enable; } GB_apu_t; -void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, GB_sample_t *samples); -void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_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_run(GB_gameboy_t *gb); +void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_samples, GB_sample_t *samples); +void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count); +void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); +void GB_apu_init(GB_gameboy_t *gb); +void GB_apu_run(GB_gameboy_t *gb); #endif /* apu_h */ diff --git a/Core/debugger.c b/Core/debugger.c index d757086c..c661e55c 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -15,17 +15,17 @@ typedef struct { LVALUE_REG_L, } kind; union { - unsigned short *register_address; - unsigned short memory_address; + uint16_t *register_address; + uint16_t memory_address; }; } lvalue_t; -static unsigned short read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) +static uint16_t 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); + return GB_read_memory(gb, lvalue.memory_address); case LVALUE_REG16: return *lvalue.register_address; @@ -38,11 +38,11 @@ static unsigned short read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) } } -static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, unsigned short value) +static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) { switch (lvalue.kind) { case LVALUE_MEMORY: - write_memory(gb, lvalue.memory_address, value); + GB_write_memory(gb, lvalue.memory_address, value); return; case LVALUE_REG16: @@ -61,27 +61,27 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, unsigned short value } } -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) { +static uint16_t add(uint16_t a, uint16_t b) {return a + b;}; +static uint16_t sub(uint16_t a, uint16_t b) {return a - b;}; +static uint16_t mul(uint16_t a, uint16_t b) {return a * b;}; +static uint16_t _div(uint16_t a, uint16_t b) { if (b == 0) { return 0; } return a / b; }; -static unsigned short mod(unsigned short a, unsigned short b) { +static uint16_t mod(uint16_t a, uint16_t 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) +static uint16_t and(uint16_t a, uint16_t b) {return a & b;}; +static uint16_t or(uint16_t a, uint16_t b) {return a | b;}; +static uint16_t xor(uint16_t a, uint16_t b) {return a ^ b;}; +static uint16_t shleft(uint16_t a, uint16_t b) {return a << b;}; +static uint16_t shright(uint16_t a, uint16_t b) {return a >> b;}; +static uint16_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) { write_lvalue(gb, a, b); return read_lvalue(gb, a); @@ -90,8 +90,8 @@ static unsigned short assign(GB_gameboy_t *gb, lvalue_t a, unsigned short b) 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); + uint16_t (*operator)(uint16_t, uint16_t); + uint16_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); } operators[] = { // Yes. This is not C-like. But it makes much more sense. @@ -109,7 +109,7 @@ static struct { {"=", 2, NULL, assign}, }; -unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error); +uint16_t 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) { @@ -124,7 +124,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, u } if (length == 0) { - gb_log(gb, "Expected expression.\n"); + GB_log(gb, "Expected expression.\n"); *error = true; return (lvalue_t){0,}; } @@ -183,17 +183,17 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, u case 'p': if (string[2] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; } } - gb_log(gb, "Unknown register: %.*s\n", length, string); + 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); + 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) +uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error) { *error = false; // Strip whitespace @@ -206,7 +206,7 @@ unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned } if (length == 0) { - gb_log(gb, "Expected expression.\n"); + GB_log(gb, "Expected expression.\n"); *error = true; return -1; } @@ -236,7 +236,7 @@ unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned } if (string[i] == ']') depth--; } - if (depth == 0) return read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error)); + if (depth == 0) return GB_read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error)); } // Search for lowest priority operator signed int depth = 0; @@ -262,14 +262,14 @@ unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned } 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); + uint16_t 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); + uint16_t left = debugger_evaluate(gb, string, operator_pos, error); if (*error) return -1; return operators[operator_index].operator(left, right); } @@ -300,15 +300,15 @@ unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned case 'p': if (string[2] == 'c') return gb->pc; } } - gb_log(gb, "Unknown register: %.*s\n", length, string); + GB_log(gb, "Unknown register: %.*s\n", length, string); *error = true; return -1; } char *end; - unsigned short literal = (unsigned short) (strtol(string, &end, 16)); + uint16_t literal = (uint16_t) (strtol(string, &end, 16)); if (end != string + length) { - gb_log(gb, "Failed to parse: %.*s\n", length, string); + GB_log(gb, "Failed to parse: %.*s\n", length, string); *error = true; return -1; } @@ -319,7 +319,7 @@ typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments); typedef struct { const char *command; - unsigned char min_length; + uint8_t min_length; debugger_command_imp_t *implementation; const char *help_string; // Null if should not appear in help } debugger_command_t; @@ -335,7 +335,7 @@ static const char *lstrip(const char *str) static bool cont(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: continue\n"); + GB_log(gb, "Usage: continue\n"); return true; } gb->debug_stopped = false; @@ -345,7 +345,7 @@ static bool cont(GB_gameboy_t *gb, char *arguments) static bool next(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: next\n"); + GB_log(gb, "Usage: next\n"); return true; } @@ -358,7 +358,7 @@ static bool next(GB_gameboy_t *gb, char *arguments) static bool step(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: step\n"); + GB_log(gb, "Usage: step\n"); return true; } @@ -368,7 +368,7 @@ static bool step(GB_gameboy_t *gb, char *arguments) static bool finish(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: finish\n"); + GB_log(gb, "Usage: finish\n"); return true; } @@ -381,7 +381,7 @@ static bool finish(GB_gameboy_t *gb, char *arguments) static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: sld\n"); + GB_log(gb, "Usage: sld\n"); return true; } @@ -394,23 +394,23 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments) static bool registers(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: registers\n"); + GB_log(gb, "Usage: registers\n"); return true; } - 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); + 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/%u\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles); + GB_log(gb, "Display Controller: LY = %d/%u\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456); return true; } /* Find the index of the closest breakpoint equal or greater to addr */ -static unsigned short find_breakpoint(GB_gameboy_t *gb, unsigned short addr) +static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr) { if (!gb->breakpoints) { return 0; @@ -418,7 +418,7 @@ static unsigned short find_breakpoint(GB_gameboy_t *gb, unsigned short addr) int min = 0; int max = gb->n_breakpoints; while (min < max) { - unsigned short pivot = (min + max) / 2; + uint16_t pivot = (min + max) / 2; if (gb->breakpoints[pivot] == addr) return pivot; if (gb->breakpoints[pivot] > addr) { max = pivot - 1; @@ -427,24 +427,24 @@ static unsigned short find_breakpoint(GB_gameboy_t *gb, unsigned short addr) min = pivot + 1; } } - return (unsigned short) min; + return (uint16_t) min; } static bool breakpoint(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { - gb_log(gb, "Usage: breakpoint \n"); + GB_log(gb, "Usage: breakpoint \n"); return true; } bool error; - unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); if (error) return true; - unsigned short index = find_breakpoint(gb, result); + uint16_t index = find_breakpoint(gb, result); if (index < gb->n_breakpoints && gb->breakpoints[index] == result) { - gb_log(gb, "Breakpoint already set at %04x\n", result); + GB_log(gb, "Breakpoint already set at %04x\n", result); return true; } @@ -453,14 +453,14 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) gb->breakpoints[index] = result; gb->n_breakpoints++; - gb_log(gb, "Breakpoint set at %04x\n", result); + GB_log(gb, "Breakpoint set at %04x\n", result); return true; } static bool delete(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { - gb_log(gb, "Delete all breakpoints? "); + GB_log(gb, "Delete all breakpoints? "); char *answer = gb->input_callback(gb); if (answer[0] == 'Y' || answer[0] == 'y') { free(gb->breakpoints); @@ -471,13 +471,13 @@ static bool delete(GB_gameboy_t *gb, char *arguments) } bool error; - unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); if (error) return true; - unsigned short index = find_breakpoint(gb, result); + uint16_t index = find_breakpoint(gb, result); if (index >= gb->n_breakpoints || gb->breakpoints[index] != result) { - gb_log(gb, "No breakpoint set at %04x\n", result); + GB_log(gb, "No breakpoint set at %04x\n", result); return true; } @@ -485,33 +485,33 @@ static bool delete(GB_gameboy_t *gb, char *arguments) gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); - gb_log(gb, "Breakpoint removed from %04x\n", result); + GB_log(gb, "Breakpoint removed from %04x\n", result); return true; } static bool list(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: list\n"); + GB_log(gb, "Usage: list\n"); return true; } if (gb->n_breakpoints == 0) { - gb_log(gb, "No breakpoints set.\n"); + GB_log(gb, "No breakpoints set.\n"); return true; } - gb_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); - for (unsigned short i = 0; i < gb->n_breakpoints; i++) { - gb_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i]); + GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (uint16_t i = 0; i < gb->n_breakpoints; i++) { + GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i]); } return true; } -static bool should_break(GB_gameboy_t *gb, unsigned short addr) +static bool should_break(GB_gameboy_t *gb, uint16_t addr) { - unsigned short index = find_breakpoint(gb, addr); + uint16_t index = find_breakpoint(gb, addr); if (index < gb->n_breakpoints && gb->breakpoints[index] == addr) { return true; } @@ -521,14 +521,14 @@ static bool should_break(GB_gameboy_t *gb, unsigned short addr) static bool print(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { - gb_log(gb, "Usage: print \n"); + GB_log(gb, "Usage: print \n"); return true; } bool error; - unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); if (!error) { - gb_log(gb, "=%04x\n", result); + GB_log(gb, "=%04x\n", result); } return true; } @@ -536,18 +536,18 @@ static bool print(GB_gameboy_t *gb, char *arguments) static bool examine(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { - gb_log(gb, "Usage: examine \n"); + GB_log(gb, "Usage: examine \n"); return true; } bool error; - unsigned short addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); if (!error) { - gb_log(gb, "%4x: ", addr); + GB_log(gb, "%4x: ", addr); for (int i = 0; i < 16; i++) { - gb_log(gb, "%02x ", read_memory(gb, addr + i)); + GB_log(gb, "%02x ", GB_read_memory(gb, addr + i)); } - gb_log(gb, "\n"); + GB_log(gb, "\n"); } return true; } @@ -555,41 +555,41 @@ static bool examine(GB_gameboy_t *gb, char *arguments) static bool mbc(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { - gb_log(gb, "Usage: mbc\n"); + GB_log(gb, "Usage: mbc\n"); return true; } const GB_cartridge_t *cartridge = gb->cartridge_type; if (cartridge->has_ram) { - gb_log(gb, "Cartrdige includes%s RAM: %zx\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); + GB_log(gb, "Cartrdige includes%s RAM: %x\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); } else { - gb_log(gb, "No cartridge RAM\n"); + GB_log(gb, "No cartridge RAM\n"); } if (cartridge->mbc_type) { - gb_log(gb, "MBC%d\n", cartridge->mbc_type); - gb_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); + GB_log(gb, "MBC%d\n", cartridge->mbc_type); + GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); if (cartridge->has_ram) { - gb_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); - gb_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); + GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); } if (cartridge->mbc_type == MBC1) { - gb_log(gb, "MBC1 banking mode is %s\n", gb->mbc_ram_banking? "RAM" : "ROM"); + GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc_ram_banking? "RAM" : "ROM"); } } else { - gb_log(gb, "No MBC\n"); + GB_log(gb, "No MBC\n"); } if (cartridge->has_rumble) { - gb_log(gb, "Cart contains a rumble pak\n"); + GB_log(gb, "Cart contains a rumble pak\n"); } if (cartridge->has_rtc) { - gb_log(gb, "Cart contains a real time clock\n"); + GB_log(gb, "Cart contains a real time clock\n"); } @@ -622,8 +622,8 @@ static bool help(GB_gameboy_t *gb, char *arguments) const debugger_command_t *command = commands; for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) { if (command->help_string) { - gb_attributed_log(gb, GB_LOG_BOLD, "%s", command->command); - gb_log(gb, ": %s\n", command->help_string); + GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command); + GB_log(gb, ": %s\n", command->help_string); } } return true; @@ -643,13 +643,13 @@ static const debugger_command_t *find_command(const char *string) return NULL; } -void debugger_call_hook(GB_gameboy_t *gb) +void GB_debugger_call_hook(GB_gameboy_t *gb) { /* Called just after the CPU calls a function/enters an interrupt/etc... */ if (gb->stack_leak_detection) { if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) { - gb_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); + GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); gb->debug_stopped = true; } else { @@ -661,7 +661,7 @@ void debugger_call_hook(GB_gameboy_t *gb) gb->debug_call_depth++; } -void debugger_ret_hook(GB_gameboy_t *gb) +void GB_debugger_ret_hook(GB_gameboy_t *gb) { /* Called just before the CPU runs ret/reti */ @@ -669,13 +669,13 @@ void debugger_ret_hook(GB_gameboy_t *gb) if (gb->stack_leak_detection) { if (gb->debug_call_depth < 0) { - gb_log(gb, "Function finished without a stack leak.\n"); + GB_log(gb, "Function finished without a stack leak.\n"); gb->debug_stopped = true; } else { if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { - gb_log(gb, "Stack leak detected for function %04x!\n", gb->addr_for_call_depth[gb->debug_call_depth]); - gb_log(gb, "SP is %04x, should be %04x.\n", gb->registers[GB_REGISTER_SP], + GB_log(gb, "Stack leak detected for function %04x!\n", gb->addr_for_call_depth[gb->debug_call_depth]); + GB_log(gb, "SP is %04x, should be %04x.\n", gb->registers[GB_REGISTER_SP], gb->sp_for_call_depth[gb->debug_call_depth]); gb->debug_stopped = true; } @@ -683,7 +683,7 @@ void debugger_ret_hook(GB_gameboy_t *gb) } } -void debugger_run(GB_gameboy_t *gb) +void GB_debugger_run(GB_gameboy_t *gb) { char *input = NULL; if (gb->debug_next_command && gb->debug_call_depth <= 0) { @@ -693,7 +693,7 @@ void debugger_run(GB_gameboy_t *gb) gb->debug_stopped = true; } if (gb->debug_stopped) { - cpu_disassemble(gb, gb->pc, 5); + GB_cpu_disassemble(gb, gb->pc, 5); } next_command: if (input) { @@ -701,8 +701,8 @@ next_command: } if (!gb->debug_stopped && should_break(gb, gb->pc)) { gb->debug_stopped = true; - gb_log(gb, "Breakpoint: PC = %04x\n", gb->pc); - cpu_disassemble(gb, gb->pc, 5); + GB_log(gb, "Breakpoint: PC = %04x\n", gb->pc); + GB_cpu_disassemble(gb, gb->pc, 5); } if (gb->debug_stopped) { gb->debug_next_command = false; @@ -731,7 +731,7 @@ next_command: } } else { - gb_log(gb, "%s: no such command.\n", command_string); + GB_log(gb, "%s: no such command.\n", command_string); goto next_command; } diff --git a/Core/debugger.h b/Core/debugger.h index 33691952..0b2023cb 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -2,8 +2,8 @@ #define debugger_h #include "gb.h" -void debugger_run(GB_gameboy_t *gb); -void debugger_call_hook(GB_gameboy_t *gb); -void debugger_ret_hook(GB_gameboy_t *gb); +void GB_debugger_run(GB_gameboy_t *gb); +void GB_debugger_call_hook(GB_gameboy_t *gb); +void GB_debugger_ret_hook(GB_gameboy_t *gb); #endif /* debugger_h */ diff --git a/Core/display.c b/Core/display.c index e4229330..c83d3987 100644 --- a/Core/display.c +++ b/Core/display.c @@ -9,14 +9,14 @@ #pragma pack(push, 1) typedef struct { - unsigned char y; - unsigned char x; - unsigned char tile; - unsigned char flags; + uint8_t y; + uint8_t x; + uint8_t tile; + uint8_t flags; } GB_sprite_t; #pragma pack(pop) -static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y) +static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) { /* Bit 7 - LCD Display Enable (0=Off, 1=On) @@ -28,17 +28,17 @@ static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y) 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; + uint16_t map = 0x1800; + uint8_t tile = 0; + uint8_t attributes = 0; + uint8_t sprite_palette = 0; + uint16_t tile_address = 0; + uint8_t background_pixel = 0, sprite_pixel = 0; GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; - unsigned char sprites_in_line = 0; + uint8_t 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; + uint8_t 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 */ @@ -49,13 +49,13 @@ static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y) if (sprites_enabled) { // Loop all sprites - for (unsigned char i = 40; i--; sprite++) { + for (uint8_t 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; + uint8_t tile_x, tile_y, current_sprite_pixel; + uint16_t line_address; // Limit to 10 sprites in one scan line. if (++sprites_in_line == 11) break; // Does not overlap our pixel. @@ -129,7 +129,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y) tile_address = tile * 0x10; } else { - tile_address = (signed char) tile * 0x10 + 0x1000; + tile_address = (int8_t) tile * 0x10 + 0x1000; } if (attributes & 0x8) { tile_address += 0x2000; @@ -171,7 +171,7 @@ void display_vblank(GB_gameboy_t *gb) if (gb->turbo) { struct timeval now; gettimeofday(&now, NULL); - signed long nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; + int64_t nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) { return; } @@ -209,7 +209,7 @@ void display_vblank(GB_gameboy_t *gb) 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 ) { + if (labs((signed long)(nanoseconds - gb->last_vblank)) < FRAME_LENGTH ) { sleep.tv_nsec = (FRAME_LENGTH + gb->last_vblank - nanoseconds); nanosleep(&sleep, NULL); @@ -221,21 +221,21 @@ void display_vblank(GB_gameboy_t *gb) } } -static inline unsigned char scale_channel(unsigned char x) +static inline uint8_t scale_channel(uint8_t x) { x &= 0x1f; return (x << 3) | (x >> 2); } -void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char index) +void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t 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); + uint8_t *palette_data = background_palette? gb->background_palletes_data : gb->sprite_palletes_data; + uint16_t 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); + uint8_t r = scale_channel(color); + uint8_t g = scale_channel(color >> 5); + uint8_t 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); } @@ -257,7 +257,7 @@ void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char in #define MODE1_LENGTH 204 #define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE1_LENGTH) // = 456 -void display_run(GB_gameboy_t *gb) +void GB_display_run(GB_gameboy_t *gb) { /* Display controller bug: For some reason, the OAM STAT interrupt is called, as expected, for LY = 0..143. @@ -271,10 +271,10 @@ void display_run(GB_gameboy_t *gb) http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531 */ - unsigned char previous_stat_interrupt_line = gb->stat_interrupt_line; + uint8_t previous_stat_interrupt_line = gb->stat_interrupt_line; gb->stat_interrupt_line = false; - unsigned char last_mode = gb->io_registers[GB_IO_STAT] & 3; + uint8_t last_mode = gb->io_registers[GB_IO_STAT] & 3; gb->io_registers[GB_IO_STAT] &= ~3; if (gb->display_cycles >= LCDC_PERIOD) { @@ -360,7 +360,7 @@ void display_run(GB_gameboy_t *gb) /* Render. This chunk is outside the Mode 3 if, because otherwise we might not render some pixels, since this function only runs between atomic CPU changes, and not every clock. */ - signed short current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); + int16_t current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { if (gb->previous_lcdc_x >= 160) { continue; diff --git a/Core/display.h b/Core/display.h index 3d9ebd2e..c1542841 100644 --- a/Core/display.h +++ b/Core/display.h @@ -2,6 +2,6 @@ #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); +void GB_display_run(GB_gameboy_t *gb); +void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); #endif /* display_h */ diff --git a/Core/gb.c b/Core/gb.c index 99eafaab..b1b3f8bf 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -59,7 +59,7 @@ static const GB_cartridge_t cart_defs[256] = { { 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) +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); @@ -75,19 +75,19 @@ void gb_attributed_logv(GB_gameboy_t *gb, gb_log_attributes attributes, const ch free(string); } -void gb_attributed_log(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, ...) +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); + GB_attributed_logv(gb, attributes, fmt, args); va_end(args); } -void gb_log(GB_gameboy_t *gb,const char *fmt, ...) +void GB_log(GB_gameboy_t *gb,const char *fmt, ...) { va_list args; va_start(args, fmt); - gb_attributed_logv(gb, 0, fmt, args); + GB_attributed_logv(gb, 0, fmt, args); va_end(args); } @@ -109,7 +109,7 @@ static char *default_input_callback(GB_gameboy_t *gb) return expression; } -void gb_init(GB_gameboy_t *gb) +void GB_init(GB_gameboy_t *gb) { memset(gb, 0, sizeof(*gb)); gb->magic = (uintptr_t)'SAME'; @@ -136,7 +136,7 @@ void gb_init(GB_gameboy_t *gb) gb->io_registers[GB_IO_JOYP] = 0xF; } -void gb_init_cgb(GB_gameboy_t *gb) +void GB_init_cgb(GB_gameboy_t *gb) { memset(gb, 0, sizeof(*gb)); gb->magic = (uintptr_t)'SAME'; @@ -160,7 +160,7 @@ void gb_init_cgb(GB_gameboy_t *gb) gb->io_registers[GB_IO_JOYP] = 0xF; } -void gb_free(GB_gameboy_t *gb) +void GB_free(GB_gameboy_t *gb) { if (gb->ram) { free(gb->ram); @@ -182,16 +182,16 @@ void gb_free(GB_gameboy_t *gb) } } -int gb_load_bios(GB_gameboy_t *gb, const char *path) +int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "r"); if (!f) return errno; - fread(gb->bios, sizeof(gb->bios), 1, f); + fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); fclose(f); return 0; } -int gb_load_rom(GB_gameboy_t *gb, const char *path) +int GB_load_rom(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "r"); if (!f) return errno; @@ -228,7 +228,7 @@ static bool dump_section(FILE *f, const void *src, uint32_t size) #define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) /* Todo: we need a sane and protable save state format. */ -int gb_save_state(GB_gameboy_t *gb, const char *path) +int GB_save_state(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "w"); if (!f) { @@ -290,7 +290,7 @@ static bool read_section(FILE *f, void *dest, uint32_t size) #define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) -int gb_load_state(GB_gameboy_t *gb, const char *path) +int GB_load_state(GB_gameboy_t *gb, const char *path) { GB_gameboy_t save; @@ -313,31 +313,31 @@ int gb_load_state(GB_gameboy_t *gb, const char *path) if (!READ_SECTION(&save, f, video )) goto error; if (gb->magic != save.magic) { - gb_log(gb, "File is not a save state, or is from an incompatible operating system.\n"); + GB_log(gb, "File is not a save state, or is from an incompatible operating system.\n"); errno = -1; goto error; } if (gb->version != save.version) { - gb_log(gb, "Save state is for a different version of SameBoy.\n"); + GB_log(gb, "Save state is for a different version of SameBoy.\n"); errno = -1; goto error; } if (gb->mbc_ram_size != save.mbc_ram_size) { - gb_log(gb, "Save state has non-matching MBC RAM size.\n"); + GB_log(gb, "Save state has non-matching MBC RAM size.\n"); errno = -1; goto error; } if (gb->ram_size != save.ram_size) { - gb_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n"); + GB_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n"); errno = -1; goto error; } if (gb->vram_size != save.vram_size) { - gb_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n"); + GB_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n"); errno = -1; goto error; } @@ -364,7 +364,7 @@ error: return errno; } -int gb_save_battery(GB_gameboy_t *gb, const char *path) +int GB_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ @@ -395,7 +395,7 @@ int gb_save_battery(GB_gameboy_t *gb, const char *path) } /* Loading will silently stop if the format is incomplete */ -void gb_load_battery(GB_gameboy_t *gb, const char *path) +void GB_load_battery(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "r"); if (!f) { @@ -433,39 +433,39 @@ exit: return; } -void gb_run(GB_gameboy_t *gb) +void GB_run(GB_gameboy_t *gb) { - update_joyp(gb); - debugger_run(gb); - cpu_run(gb); + GB_update_joyp(gb); + GB_debugger_run(gb); + GB_cpu_run(gb); } -void gb_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) +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) +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) +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) +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) +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) +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) { if (gb->audio_buffer) { free(gb->audio_buffer); diff --git a/Core/gb.h b/Core/gb.h index cf2fca78..e7bbed2e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -1,5 +1,5 @@ -#ifndef gb_h -#define gb_h +#ifndef GB_h +#define GB_h #include #include #include @@ -8,7 +8,7 @@ #include "save_struct.h" -#define GB_STRUCT_VERSION 8 +#define GB_STRUCT_VERSION 9 enum { GB_REGISTER_AF, @@ -146,14 +146,14 @@ typedef enum { GB_LOG_DASHED_UNDERLINE = 2, GB_LOG_UNDERLINE = 4, GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE -} gb_log_attributes; +} 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 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 uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); typedef struct { enum { @@ -172,21 +172,32 @@ typedef struct { /* When state saving, each section is dumped independently of other sections. This allows adding data to the end of the section without worrying about future compatibility. - Some other changes might be "safe" as well. */ + Some other changes might be "safe" as well. + This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64 + bit platforms. */ + +/* We make sure bool is 1 for cross-platform save state compatibility. */ +/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */ +_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1"); typedef struct GB_gameboy_s { GB_SECTION(header, - uintptr_t magic; // States are currently platform dependent - int version; // and version dependent + /* The magic makes sure a state file is: + - Indeed a SameBoy state file. + - Has the same endianess has the current platform. */ + uint32_t magic; + /* The version field makes sure we don't load save state files with a completely different structure. + This happens when struct fields are removed/resized in an backward incompatible manner. */ + uint32_t version; ); GB_SECTION(core_state, /* Registers */ - unsigned short pc; - unsigned short registers[GB_REGISTERS_16_BIT]; + uint16_t pc; + uint16_t registers[GB_REGISTERS_16_BIT]; bool ime; - unsigned char interrupt_enable; - unsigned char cgb_ram_bank; + uint8_t interrupt_enable; + uint8_t cgb_ram_bank; /* CPU and General Hardware Flags*/ bool cgb_mode; @@ -194,23 +205,23 @@ typedef struct GB_gameboy_s { bool cgb_double_speed; bool halted; bool stopped; - bool bios_finished; + bool boot_rom_finished; ); /* HDMA */ GB_SECTION(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; + uint8_t hdma_steps_left; + uint16_t hdma_cycles; + uint16_t hdma_current_src, hdma_current_dest; ); /* MBC */ GB_SECTION(mbc, - unsigned short mbc_rom_bank; - unsigned char mbc_ram_bank; - size_t mbc_ram_size; + uint16_t mbc_rom_bank; + uint8_t mbc_ram_bank; + uint32_t mbc_ram_size; bool mbc_ram_enable; bool mbc_ram_banking; ); @@ -218,18 +229,18 @@ typedef struct GB_gameboy_s { /* HRAM and HW Registers */ GB_SECTION(hram, - unsigned char hram[0xFFFF - 0xFF80]; - unsigned char io_registers[0x80]; + uint8_t hram[0xFFFF - 0xFF80]; + uint8_t io_registers[0x80]; ); /* Timing */ GB_SECTION(timing, - signed long last_vblank; - unsigned long display_cycles; - unsigned long div_cycles; - unsigned long tima_cycles; - unsigned long dma_cycles; - double apu_cycles; + int64_t last_vblank; + uint32_t display_cycles; + uint32_t div_cycles; + uint32_t tima_cycles; + uint32_t dma_cycles; + GB_aligned_double apu_cycles; ); /* APU */ @@ -241,47 +252,45 @@ typedef struct GB_gameboy_s { GB_SECTION(rtc, union { struct { - unsigned char rtc_seconds; - unsigned char rtc_minutes; - unsigned char rtc_hours; - unsigned char rtc_days; - unsigned char rtc_high; + uint8_t rtc_seconds; + uint8_t rtc_minutes; + uint8_t rtc_hours; + uint8_t rtc_days; + uint8_t rtc_high; }; - unsigned char rtc_data[5]; + uint8_t rtc_data[5]; }; time_t last_rtc_second; ); /* Video Display */ GB_SECTION(video, - 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 vram_size; // Different between CGB and DMG + uint8_t cgb_vram_bank; + uint8_t oam[0xA0]; + uint8_t background_palletes_data[0x40]; + uint8_t sprite_palletes_data[0x40]; uint32_t background_palletes_rgb[0x20]; uint32_t sprite_palletes_rgb[0x20]; - GB_PADDING(bool, ly144_bug_oam); - GB_PADDING(bool, ly144_bug_hblank); - signed short previous_lcdc_x; - unsigned char padding; + int16_t previous_lcdc_x; + uint8_t padding; bool effective_window_enabled; - unsigned char effective_window_y; + uint8_t effective_window_y; bool stat_interrupt_line; - unsigned char effective_scx; + uint8_t effective_scx; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* ROM */ - unsigned char *rom; - size_t rom_size; + uint8_t *rom; + uint32_t rom_size; const GB_cartridge_t *cartridge_type; /* Various RAMs */ - unsigned char *ram; - unsigned char *vram; - unsigned char *mbc_ram; + uint8_t *ram; + uint8_t *vram; + uint8_t *mbc_ram; /* I/O */ uint32_t *screen; @@ -305,17 +314,17 @@ typedef struct GB_gameboy_s { /* Debugger */ int debug_call_depth; bool debug_fin_command, debug_next_command; - unsigned short n_breakpoints; - unsigned short *breakpoints; + uint16_t n_breakpoints; + uint16_t *breakpoints; bool stack_leak_detection; - unsigned short sp_for_call_depth[0x200]; /* Should be much more than enough */ - unsigned short addr_for_call_depth[0x200]; + uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ + uint16_t addr_for_call_depth[0x200]; bool debug_stopped; /* Misc */ bool turbo; - unsigned long ram_size; // Different between CGB and DMG - unsigned char bios[0x900]; + uint32_t ram_size; // Different between CGB and DMG + uint8_t boot_rom[0x900]; } GB_gameboy_t; @@ -325,23 +334,23 @@ typedef struct GB_gameboy_s { __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); +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_boot_rom(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 */ +#endif /* GB_h */ diff --git a/Core/joypad.c b/Core/joypad.c index aee016e8..89bcba84 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -2,10 +2,10 @@ #include "gb.h" #include "joypad.h" -void update_joyp(GB_gameboy_t *gb) +void GB_update_joyp(GB_gameboy_t *gb) { - unsigned char key_selection = 0; - unsigned char previous_state = 0; + uint8_t key_selection = 0; + uint8_t previous_state = 0; /* Todo: add delay to key selection */ previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; @@ -19,21 +19,21 @@ void update_joyp(GB_gameboy_t *gb) case 2: /* Direction keys */ - for (unsigned char i = 0; i < 4; i++) { + for (uint8_t 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++) { + for (uint8_t 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++) { + for (uint8_t 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; } diff --git a/Core/joypad.h b/Core/joypad.h index c148e3c4..e7487a5e 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -2,7 +2,7 @@ #define joypad_h #include "gb.h" -void update_joyp(GB_gameboy_t *gb); -void update_keys_status(GB_gameboy_t *gb); +void GB_update_joyp(GB_gameboy_t *gb); +void GB_update_keys_status(GB_gameboy_t *gb); #endif /* joypad_h */ diff --git a/Core/memory.c b/Core/memory.c index 16196421..812ba6b4 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -5,17 +5,17 @@ #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); +typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); +typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); -static unsigned char read_rom(GB_gameboy_t *gb, unsigned short addr) +static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) { - if (addr < 0x100 && !gb->bios_finished) { - return gb->bios[addr]; + if (addr < 0x100 && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; } - if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->bios_finished) { - return gb->bios[addr]; + if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; } if (!gb->rom_size) { @@ -24,7 +24,7 @@ static unsigned char read_rom(GB_gameboy_t *gb, unsigned short addr) return gb->rom[addr]; } -static unsigned char read_mbc_rom(GB_gameboy_t *gb, unsigned short addr) +static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) { if (gb->mbc_rom_bank >= gb->rom_size / 0x4000) { return 0xFF; @@ -32,15 +32,15 @@ static unsigned char read_mbc_rom(GB_gameboy_t *gb, unsigned short addr) return gb->rom[(addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000]; } -static unsigned char read_vram(GB_gameboy_t *gb, unsigned short addr) +static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) { if ((gb->io_registers[GB_IO_STAT] & 0x3) == 3) { return 0xFF; } - return gb->vram[(addr & 0x1FFF) + (unsigned short) gb->cgb_vram_bank * 0x2000]; + return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; } -static unsigned char read_mbc_ram(GB_gameboy_t *gb, unsigned short addr) +static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ @@ -50,27 +50,27 @@ static unsigned char read_mbc_ram(GB_gameboy_t *gb, unsigned short addr) 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); + 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); + 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) +static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) { return gb->ram[addr & 0x0FFF]; } -static unsigned char read_banked_ram(GB_gameboy_t *gb, unsigned short addr) +static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr) { return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000]; } -static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) +static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0xFE00) { @@ -150,7 +150,7 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) if (!gb->is_cgb) { return 0xFF; } - unsigned char index_reg = (addr & 0xFF) - 1; + uint8_t 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]; @@ -165,7 +165,7 @@ static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr) default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { - return apu_read(gb, addr & 0xFF); + return GB_apu_read(gb, addr & 0xFF); } return 0xFF; } @@ -192,7 +192,7 @@ static GB_read_function_t * const read_map[] = read_high_memory, read_high_memory, /* EXXX FXXX */ }; -unsigned char read_memory(GB_gameboy_t *gb, unsigned short addr) +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0xFF00 && gb->dma_cycles) { /* Todo: can we access IO registers during DMA? */ @@ -201,7 +201,7 @@ unsigned char read_memory(GB_gameboy_t *gb, unsigned short addr) return read_map[addr >> 12](gb, addr); } -static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (gb->cartridge_type->mbc_type == NO_MBC) return; switch (addr >> 12) { @@ -223,7 +223,7 @@ static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value 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_log(gb, "Bank overflow: [%x] <- %d\n", addr, value); } gb->mbc_rom_bank = (gb->mbc_rom_bank & 0xFF) | value << 8; break; @@ -274,16 +274,16 @@ static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value } } -static void write_vram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if ((gb->io_registers[GB_IO_STAT] & 0x3) == 3) { - //gb_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); + //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; + gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; } -static void write_mbc_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC write*/ @@ -294,30 +294,30 @@ static void write_mbc_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char v 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); + 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); + 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) +static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { gb->ram[addr & 0x0FFF] = value; } -static void write_banked_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t 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) +static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (addr < 0xFE00) { - gb_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); + GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); gb->ram[addr & 0x0FFF] = value; return; } @@ -331,7 +331,7 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch } if (addr < 0xFF00) { - gb_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr); + GB_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr); return; } @@ -386,19 +386,19 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch return; case GB_IO_BIOS: - gb->bios_finished = true; + gb->boot_rom_finished = true; return; case GB_IO_DMG_EMULATION: - if (gb->is_cgb && !gb->bios_finished) { + if (gb->is_cgb && !gb->boot_rom_finished) { gb->cgb_mode = value != 4; /* The real "contents" of this register aren't quite known yet. */ } return; case GB_IO_DMA: if (value <= 0xF1) { /* According to Pan Docs */ - for (unsigned char i = 0xA0; i--;) { - gb->oam[i] = read_memory(gb, (value << 8) + i); + for (uint8_t i = 0xA0; i--;) { + gb->oam[i] = GB_read_memory(gb, (value << 8) + i); } } /* else { what? } */ @@ -433,11 +433,11 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch if (!gb->is_cgb) { return; } - unsigned char index_reg = (addr & 0xFF) - 1; + uint8_t 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); + GB_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; @@ -477,11 +477,11 @@ static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned ch default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { - apu_write(gb, addr & 0xFF, value); + GB_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_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr); } gb->io_registers[addr & 0xFF] = 0x37; return; @@ -510,7 +510,7 @@ static GB_write_function_t * const write_map[] = write_high_memory, write_high_memory, /* EXXX FXXX */ }; -void write_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value) +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (addr < 0xFF00 && gb->dma_cycles) { /* Todo: can we access IO registers during DMA? */ @@ -519,18 +519,18 @@ void write_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value) write_map[addr >> 12](gb, addr, value); } -void hdma_run(GB_gameboy_t *gb) +void GB_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. + // The CGB boot rom 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)); + for (uint8_t i = 0; i < 0x10; i++) { + GB_write_memory(gb, gb->hdma_current_dest + i, GB_read_memory(gb, gb->hdma_current_src + i)); } } else { diff --git a/Core/memory.h b/Core/memory.h index bd46026c..f904f8c1 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -2,8 +2,8 @@ #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); +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +void GB_hdma_run(GB_gameboy_t *gb); #endif /* memory_h */ diff --git a/Core/save_struct.h b/Core/save_struct.h index d88922c4..eff2dc33 100644 --- a/Core/save_struct.h +++ b/Core/save_struct.h @@ -4,9 +4,11 @@ #define GB_PADDING(type, old_usage) type old_usage##__do_not_use -#define GB_SECTION(name, ...) __attribute__ ((aligned (sizeof(void*)))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end +#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end #define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) #define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) #define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) +#define GB_aligned_double __attribute__ ((aligned (8))) double + #endif /* save_struct_h */ diff --git a/Core/timing.c b/Core/timing.c index 249647b1..f07858da 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -3,7 +3,7 @@ #include "memory.h" #include "display.h" -void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost if (gb->dma_cycles > cycles){ @@ -24,16 +24,16 @@ void advance_cycles(GB_gameboy_t *gb, unsigned char cycles) gb->hdma_cycles += cycles; gb->display_cycles += cycles; gb->apu_cycles += cycles; - hdma_run(gb); - timers_run(gb); - apu_run(gb); - display_run(gb); + GB_hdma_run(gb); + GB_timers_run(gb); + GB_apu_run(gb); + GB_display_run(gb); } -void timers_run(GB_gameboy_t *gb) +void GB_timers_run(GB_gameboy_t *gb) { /* Standard Timers */ - static const unsigned long GB_TAC_RATIOS[] = {1024, 16, 64, 256}; + static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; if (gb->div_cycles >= DIV_CYCLES) { gb->div_cycles -= DIV_CYCLES; diff --git a/Core/timing.h b/Core/timing.h index 0364d0dc..1e419091 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -2,6 +2,6 @@ #define timing_h #include "gb.h" -void advance_cycles(GB_gameboy_t *gb, unsigned char cycles); -void timers_run(GB_gameboy_t *gb); +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); +void GB_timers_run(GB_gameboy_t *gb); #endif /* timing_h */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index e94beda5..d253ffb1 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -7,24 +7,24 @@ #include "gb.h" -typedef void GB_opcode_t(GB_gameboy_t *gb, unsigned char opcode); +typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); -static void ill(GB_gameboy_t *gb, unsigned char opcode) +static void ill(GB_gameboy_t *gb, uint8_t opcode) { - gb_log(gb, "Illegal Opcode. Halting."); + GB_log(gb, "Illegal Opcode. Halting."); gb->interrupt_enable = 0; gb->halted = true; } -static void nop(GB_gameboy_t *gb, unsigned char opcode) +static void nop(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->pc++; } -static void stop(GB_gameboy_t *gb, unsigned char opcode) +static void stop(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_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; @@ -47,37 +47,37 @@ static void stop(GB_gameboy_t *gb, unsigned char opcode) cc = condition code (z, nz, c, nc) */ -static void ld_rr_d16(GB_gameboy_t *gb, unsigned char opcode) +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t 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; + uint8_t register_id; + uint16_t value; + GB_advance_cycles(gb, 12); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + value = GB_read_memory(gb, gb->pc++); + value |= GB_read_memory(gb, gb->pc++) << 8; gb->registers[register_id] = value; } -static void ld_drr_a(GB_gameboy_t *gb, unsigned char opcode) +static void ld_drr_a(GB_gameboy_t *gb, uint8_t 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); + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); } -static void inc_rr(GB_gameboy_t *gb, unsigned char opcode) +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 8); - register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; gb->registers[register_id]++; } -static void inc_hr(GB_gameboy_t *gb, unsigned char opcode) +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 4); + uint8_t register_id; + GB_advance_cycles(gb, 4); gb->pc++; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] += 0x100; @@ -91,10 +91,10 @@ static void inc_hr(GB_gameboy_t *gb, unsigned char opcode) gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } } -static void dec_hr(GB_gameboy_t *gb, unsigned char opcode) +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 4); + uint8_t register_id; + GB_advance_cycles(gb, 4); gb->pc++; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] -= 0x100; @@ -110,21 +110,21 @@ static void dec_hr(GB_gameboy_t *gb, unsigned char opcode) } } -static void ld_hr_d8(GB_gameboy_t *gb, unsigned char opcode) +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 8); + uint8_t register_id; + GB_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; + gb->registers[register_id] |= GB_read_memory(gb, gb->pc++) << 8; } -static void rlca(GB_gameboy_t *gb, unsigned char opcode) +static void rlca(GB_gameboy_t *gb, uint8_t opcode) { bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; if (carry) { @@ -132,12 +132,12 @@ static void rlca(GB_gameboy_t *gb, unsigned char opcode) } } -static void rla(GB_gameboy_t *gb, unsigned char opcode) +static void rla(GB_gameboy_t *gb, uint8_t 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_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; if (carry) { @@ -148,22 +148,22 @@ static void rla(GB_gameboy_t *gb, unsigned char opcode) } } -static void ld_da16_sp(GB_gameboy_t *gb, unsigned char opcode){ - unsigned short addr; - advance_cycles(gb, 20); +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode){ + uint16_t addr; + GB_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); + addr = GB_read_memory(gb, gb->pc++); + addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + GB_write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); } -static void add_hl_rr(GB_gameboy_t *gb, unsigned char opcode) +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned short hl = gb->registers[GB_REGISTER_HL]; - unsigned short rr; - unsigned char register_id; - advance_cycles(gb, 8); + uint16_t hl = gb->registers[GB_REGISTER_HL]; + uint16_t rr; + uint8_t register_id; + GB_advance_cycles(gb, 8); gb->pc++; register_id = (opcode >> 4) + 1; rr = gb->registers[register_id]; @@ -180,29 +180,29 @@ static void add_hl_rr(GB_gameboy_t *gb, unsigned char opcode) } } -static void ld_a_drr(GB_gameboy_t *gb, unsigned char opcode) +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 8); - register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (GB_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; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[register_id]) << 8; } -static void dec_rr(GB_gameboy_t *gb, unsigned char opcode) +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 8); - register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; gb->registers[register_id]--; } -static void inc_lr(GB_gameboy_t *gb, unsigned char opcode) +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - unsigned char value; - advance_cycles(gb, 4); - register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + uint8_t register_id; + uint8_t value; + GB_advance_cycles(gb, 4); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; value = (gb->registers[register_id] & 0xFF) + 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; @@ -217,12 +217,12 @@ static void inc_lr(GB_gameboy_t *gb, unsigned char opcode) gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } } -static void dec_lr(GB_gameboy_t *gb, unsigned char opcode) +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - unsigned char value; - advance_cycles(gb, 4); - register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + uint8_t register_id; + uint8_t value; + GB_advance_cycles(gb, 4); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; value = (gb->registers[register_id] & 0xFF) - 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; @@ -239,20 +239,20 @@ static void dec_lr(GB_gameboy_t *gb, unsigned char opcode) } } -static void ld_lr_d8(GB_gameboy_t *gb, unsigned char opcode) +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 8); - register_id = (read_memory(gb, gb->pc++) >> 4) + 1; + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; gb->registers[register_id] &= 0xFF00; - gb->registers[register_id] |= read_memory(gb, gb->pc++); + gb->registers[register_id] |= GB_read_memory(gb, gb->pc++); } -static void rrca(GB_gameboy_t *gb, unsigned char opcode) +static void rrca(GB_gameboy_t *gb, uint8_t opcode) { bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; if (carry) { @@ -260,12 +260,12 @@ static void rrca(GB_gameboy_t *gb, unsigned char opcode) } } -static void rra(GB_gameboy_t *gb, unsigned char opcode) +static void rra(GB_gameboy_t *gb, uint8_t 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_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; if (carry) { @@ -276,14 +276,14 @@ static void rra(GB_gameboy_t *gb, unsigned char opcode) } } -static void jr_r8(GB_gameboy_t *gb, unsigned char opcode) +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 12); + GB_advance_cycles(gb, 12); gb->pc++; - gb->pc += (signed char) read_memory(gb, gb->pc++); + gb->pc += (int8_t) GB_read_memory(gb, gb->pc++); } -static bool condition_code(GB_gameboy_t *gb, unsigned char opcode) +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) { switch ((opcode >> 3) & 0x3) { case 0: @@ -299,22 +299,22 @@ static bool condition_code(GB_gameboy_t *gb, unsigned char opcode) return false; } -static void jr_cc_r8(GB_gameboy_t *gb, unsigned char opcode) +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { - if (condition_code(gb, read_memory(gb, gb->pc++))) { - advance_cycles(gb, 12); - gb->pc += (signed char)read_memory(gb, gb->pc++); + if (condition_code(gb, GB_read_memory(gb, gb->pc++))) { + GB_advance_cycles(gb, 12); + gb->pc += (int8_t)GB_read_memory(gb, gb->pc++); } else { - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc += 1; } } -static void daa(GB_gameboy_t *gb, unsigned char opcode) +static void daa(GB_gameboy_t *gb, uint8_t opcode) { /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= ~GB_ZERO_FLAG; gb->pc++; if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { @@ -333,7 +333,7 @@ static void daa(GB_gameboy_t *gb, unsigned char opcode) } else { if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { - unsigned short number = gb->registers[GB_REGISTER_AF] >> 8; + uint16_t number = gb->registers[GB_REGISTER_AF] >> 8; if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { number += 0x100; } @@ -346,7 +346,7 @@ static void daa(GB_gameboy_t *gb, unsigned char opcode) gb->registers[GB_REGISTER_AF] |= number << 8; } else { - unsigned short number = gb->registers[GB_REGISTER_AF] >> 8; + uint16_t number = gb->registers[GB_REGISTER_AF] >> 8; if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { number += 0x100; } @@ -365,68 +365,68 @@ static void daa(GB_gameboy_t *gb, unsigned char opcode) } } -static void cpl(GB_gameboy_t *gb, unsigned char opcode) +static void cpl(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_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) +static void scf(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_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) +static void ccf(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_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) +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc++; - write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); + GB_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) +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc++; - write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); + GB_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) +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_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; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; } -static void ld_a_dhld(GB_gameboy_t *gb, unsigned char opcode) +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_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; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; } -static void inc_dhl(GB_gameboy_t *gb, unsigned char opcode) +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value; - advance_cycles(gb, 8); + uint8_t value; + GB_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); + value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; + GB_advance_cycles(gb, 4); + GB_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) { @@ -438,14 +438,14 @@ static void inc_dhl(GB_gameboy_t *gb, unsigned char opcode) } } -static void dec_dhl(GB_gameboy_t *gb, unsigned char opcode) +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value; - advance_cycles(gb, 8); + uint8_t value; + GB_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); + value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; + GB_advance_cycles(gb, 4); + GB_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; @@ -458,25 +458,25 @@ static void dec_dhl(GB_gameboy_t *gb, unsigned char opcode) } } -static void ld_dhl_d8(GB_gameboy_t *gb, unsigned char opcode) +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 12); + GB_advance_cycles(gb, 12); gb->pc++; - write_memory(gb, gb->registers[GB_REGISTER_HL], read_memory(gb, gb->pc++)); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], GB_read_memory(gb, gb->pc++)); } -unsigned char get_src_value(GB_gameboy_t *gb, unsigned char opcode) +uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char src_register_id; - unsigned char src_low; + uint8_t src_register_id; + uint8_t 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]); + GB_advance_cycles(gb, 4); + return GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); } if (src_low) { return gb->registers[src_register_id] & 0xFF; @@ -484,10 +484,10 @@ unsigned char get_src_value(GB_gameboy_t *gb, unsigned char opcode) return gb->registers[src_register_id] >> 8; } -static void set_src_value(GB_gameboy_t *gb, unsigned char opcode, unsigned char value) +static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) { - unsigned char src_register_id; - unsigned char src_low; + uint8_t src_register_id; + uint8_t src_low; src_register_id = ((opcode >> 1) + 1) & 3; src_low = opcode & 1; @@ -497,8 +497,8 @@ static void set_src_value(GB_gameboy_t *gb, unsigned char opcode, unsigned char gb->registers[GB_REGISTER_AF] |= value << 8; } else { - advance_cycles(gb, 4); - write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); } } else { @@ -513,12 +513,12 @@ static void set_src_value(GB_gameboy_t *gb, unsigned char opcode, unsigned char } } -static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode) +static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char dst_register_id; - unsigned char dst_low; - unsigned char value; - advance_cycles(gb, 4); + uint8_t dst_register_id; + uint8_t dst_low; + uint8_t value; + GB_advance_cycles(gb, 4); gb->pc++; dst_register_id = ((opcode >> 4) + 1) & 3; @@ -533,8 +533,8 @@ static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode) gb->registers[GB_REGISTER_AF] |= value << 8; } else { - advance_cycles(gb, 4); - write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); } } else { @@ -550,15 +550,15 @@ static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode) } -static void add_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 4); + uint8_t value, a; + GB_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) { + if ((uint8_t)(a + value) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) > 0x0F) { @@ -569,17 +569,17 @@ static void add_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void adc_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a, carry; - advance_cycles(gb, 4); + uint8_t value, a, carry; + GB_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) { + if ((uint8_t)(a + value + carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { @@ -590,10 +590,10 @@ static void adc_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void sub_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 4); + uint8_t value, a; + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -609,17 +609,17 @@ static void sub_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void sbc_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a, carry; - advance_cycles(gb, 4); + uint8_t value, a, carry; + GB_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) { + if ((uint8_t) (a - value - carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF) + carry) { @@ -630,10 +630,10 @@ static void sbc_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void and_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 4); + uint8_t value, a; + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -643,10 +643,10 @@ static void and_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void xor_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 4); + uint8_t value, a; + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -656,10 +656,10 @@ static void xor_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void or_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 4); + uint8_t value, a; + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -669,10 +669,10 @@ static void or_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void cp_a_r(GB_gameboy_t *gb, unsigned char opcode) +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 4); + uint8_t value, a; + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -689,95 +689,95 @@ static void cp_a_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void halt(GB_gameboy_t *gb, unsigned char opcode) +static void halt(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->halted = true; gb->pc++; } -static void ret_cc(GB_gameboy_t *gb, unsigned char opcode) +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) { - if (condition_code(gb, read_memory(gb, gb->pc++))) { - debugger_ret_hook(gb); - advance_cycles(gb, 20); - gb->pc = read_memory(gb, gb->registers[GB_REGISTER_SP]) | - (read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + if (condition_code(gb, GB_read_memory(gb, gb->pc++))) { + GB_debugger_ret_hook(gb); + GB_advance_cycles(gb, 20); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); gb->registers[GB_REGISTER_SP] += 2; } else { - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); } } -static void pop_rr(GB_gameboy_t *gb, unsigned char opcode) +static void pop_rr(GB_gameboy_t *gb, uint8_t 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); + uint8_t register_id; + GB_advance_cycles(gb, 12); + register_id = ((GB_read_memory(gb, gb->pc++) >> 4) + 1) & 3; + gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (GB_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) +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t 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); + GB_advance_cycles(gb, 16); + gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); } else { - advance_cycles(gb, 12); + GB_advance_cycles(gb, 12); gb->pc += 2; } } -static void jp_a16(GB_gameboy_t *gb, unsigned char opcode) +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 16); + GB_advance_cycles(gb, 16); gb->pc++; - gb->pc = read_memory(gb, gb->pc) | (read_memory(gb, gb->pc + 1) << 8); + gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); } -static void call_cc_a16(GB_gameboy_t *gb, unsigned char opcode) +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { gb->pc++; if (condition_code(gb, opcode)) { - advance_cycles(gb, 24); + GB_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); - debugger_call_hook(gb); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); + gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); + GB_debugger_call_hook(gb); } else { - advance_cycles(gb, 12); + GB_advance_cycles(gb, 12); gb->pc += 2; } } -static void push_rr(GB_gameboy_t *gb, unsigned char opcode) +static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char register_id; - advance_cycles(gb, 16); + uint8_t register_id; + GB_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); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + GB_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) +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 8); + uint8_t value, a; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_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) { + if ((uint8_t) (a + value) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) > 0x0F) { @@ -788,12 +788,12 @@ static void add_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void adc_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a, carry; - advance_cycles(gb, 8); + uint8_t value, a, carry; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_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; @@ -809,12 +809,12 @@ static void adc_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void sub_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 8); + uint8_t value, a; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_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) { @@ -828,17 +828,17 @@ static void sub_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void sbc_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a, carry; - advance_cycles(gb, 8); + uint8_t value, a, carry; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_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) { + if ((uint8_t) (a - value - carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF) + carry) { @@ -849,12 +849,12 @@ static void sbc_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void and_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 8); + uint8_t value, a; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_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) { @@ -862,12 +862,12 @@ static void and_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void xor_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 8); + uint8_t value, a; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_read_memory(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; if ((a ^ value) == 0) { @@ -875,12 +875,12 @@ static void xor_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void or_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 8); + uint8_t value, a; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_read_memory(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a | value) << 8; if ((a | value) == 0) { @@ -888,12 +888,12 @@ static void or_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void cp_a_d8(GB_gameboy_t *gb, unsigned char opcode) +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value, a; - advance_cycles(gb, 8); + uint8_t value, a; + GB_advance_cycles(gb, 8); gb->pc++; - value = read_memory(gb, gb->pc++); + value = GB_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; @@ -908,83 +908,83 @@ static void cp_a_d8(GB_gameboy_t *gb, unsigned char opcode) } } -static void rst(GB_gameboy_t *gb, unsigned char opcode) +static void rst(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 16); + GB_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_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 1) & 0xFF); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 1) >> 8); gb->pc = opcode ^ 0xC7; - debugger_call_hook(gb); + GB_debugger_call_hook(gb); } -static void ret(GB_gameboy_t *gb, unsigned char opcode) +static void ret(GB_gameboy_t *gb, uint8_t opcode) { - debugger_ret_hook(gb); - 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_debugger_ret_hook(gb); + GB_advance_cycles(gb, 16); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); gb->registers[GB_REGISTER_SP] += 2; } -static void reti(GB_gameboy_t *gb, unsigned char opcode) +static void reti(GB_gameboy_t *gb, uint8_t opcode) { ret(gb, opcode); gb->ime = true; } -static void call_a16(GB_gameboy_t *gb, unsigned char opcode) +static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 24); + GB_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); - debugger_call_hook(gb); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); + gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); + GB_debugger_call_hook(gb); } -static void ld_da8_a(GB_gameboy_t *gb, unsigned char opcode) +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_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); + uint8_t temp = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); } -static void ld_a_da8(GB_gameboy_t *gb, unsigned char opcode) +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_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; + uint8_t temp = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + temp) << 8; } -static void ld_dc_a(GB_gameboy_t *gb, unsigned char opcode) +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc++; - write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); + GB_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) +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_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; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; } -static void add_sp_r8(GB_gameboy_t *gb, unsigned char opcode) +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { - signed short offset; - unsigned short sp = gb->registers[GB_REGISTER_SP]; - advance_cycles(gb, 16); + int16_t offset; + uint16_t sp = gb->registers[GB_REGISTER_SP]; + GB_advance_cycles(gb, 16); gb->pc++; - offset = (signed char) read_memory(gb, gb->pc++); + offset = (int8_t) GB_read_memory(gb, gb->pc++); gb->registers[GB_REGISTER_SP] += offset; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -999,54 +999,54 @@ static void add_sp_r8(GB_gameboy_t *gb, unsigned char opcode) } } -static void jp_hl(GB_gameboy_t *gb, unsigned char opcode) +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->pc = gb->registers[GB_REGISTER_HL]; } -static void ld_da16_a(GB_gameboy_t *gb, unsigned char opcode) +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) { - unsigned short addr; - advance_cycles(gb, 16); + uint16_t addr; + GB_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); + addr = GB_read_memory(gb, gb->pc++); + addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); } -static void ld_a_da16(GB_gameboy_t *gb, unsigned char opcode) +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) { - unsigned short addr; - advance_cycles(gb, 16); + uint16_t addr; + GB_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; + addr = GB_read_memory(gb, gb->pc++); + addr |= GB_read_memory(gb, gb->pc++) << 8 ; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, addr) << 8; } -static void di(GB_gameboy_t *gb, unsigned char opcode) +static void di(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->pc++; gb->ime = false; } -static void ei(GB_gameboy_t *gb, unsigned char opcode) +static void ei(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); gb->pc++; gb->ime = true; } -static void ld_hl_sp_r8(GB_gameboy_t *gb, unsigned char opcode) +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { - signed short offset; - advance_cycles(gb, 12); + int16_t offset; + GB_advance_cycles(gb, 12); gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF00; - offset = (signed char) read_memory(gb, gb->pc++); + offset = (int8_t) GB_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) { @@ -1058,18 +1058,18 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, unsigned char opcode) } } -static void ld_sp_hl(GB_gameboy_t *gb, unsigned char opcode) +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) { - advance_cycles(gb, 8); + GB_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) +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; - unsigned char value; - advance_cycles(gb, 8); + uint8_t value; + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; @@ -1083,11 +1083,11 @@ static void rlc_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void rrc_r(GB_gameboy_t *gb, unsigned char opcode) +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; - unsigned char value; - advance_cycles(gb, 8); + uint8_t value; + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x01) != 0; @@ -1102,12 +1102,12 @@ static void rrc_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void rl_r(GB_gameboy_t *gb, unsigned char opcode) +static void rl_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; - unsigned char value; + uint8_t value; bool bit7; - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; @@ -1124,13 +1124,13 @@ static void rl_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void rr_r(GB_gameboy_t *gb, unsigned char opcode) +static void rr_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; - unsigned char value; + uint8_t value; bool bit1; - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; @@ -1147,11 +1147,11 @@ static void rr_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void sla_r(GB_gameboy_t *gb, unsigned char opcode) +static void sla_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value; + uint8_t value; bool carry; - advance_cycles(gb, 8); + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; @@ -1165,11 +1165,11 @@ static void sla_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void sra_r(GB_gameboy_t *gb, unsigned char opcode) +static void sra_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char bit7; - unsigned char value; - advance_cycles(gb, 8); + uint8_t bit7; + uint8_t value; + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); bit7 = value & 0x80; @@ -1184,10 +1184,10 @@ static void sra_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void srl_r(GB_gameboy_t *gb, unsigned char opcode) +static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value; - advance_cycles(gb, 8); + uint8_t value; + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1200,10 +1200,10 @@ static void srl_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void swap_r(GB_gameboy_t *gb, unsigned char opcode) +static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value; - advance_cycles(gb, 8); + uint8_t value; + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1213,11 +1213,11 @@ static void swap_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void bit_r(GB_gameboy_t *gb, unsigned char opcode) +static void bit_r(GB_gameboy_t *gb, uint8_t opcode) { - unsigned char value; - unsigned char bit; - advance_cycles(gb, 8); + uint8_t value; + uint8_t bit; + GB_advance_cycles(gb, 8); gb->pc++; value = get_src_value(gb, opcode); bit = 1 << ((opcode >> 3) & 7); @@ -1236,9 +1236,9 @@ static void bit_r(GB_gameboy_t *gb, unsigned char opcode) } } -static void cb_prefix(GB_gameboy_t *gb, unsigned char opcode) +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) { - opcode = read_memory(gb, ++gb->pc); + opcode = GB_read_memory(gb, ++gb->pc); switch (opcode >> 3) { case 0: rlc_r(gb, opcode); @@ -1307,7 +1307,7 @@ static GB_opcode_t *opcodes[256] = { ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, }; -void cpu_run(GB_gameboy_t *gb) +void GB_cpu_run(GB_gameboy_t *gb) { bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; if (interrupt) { @@ -1315,13 +1315,13 @@ void cpu_run(GB_gameboy_t *gb) } if (gb->hdma_on) { - advance_cycles(gb, 4); + GB_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]; + uint8_t interrupt_bit = 0; + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; while (!(interrupt_queue & 1)) { interrupt_queue >>= 1; interrupt_bit++; @@ -1334,10 +1334,10 @@ void cpu_run(GB_gameboy_t *gb) rst(gb, 0x87 + interrupt_bit * 8); } else if(!gb->halted && !gb->stopped) { - unsigned char opcode = read_memory(gb, gb->pc); + uint8_t opcode = GB_read_memory(gb, gb->pc); opcodes[opcode](gb, opcode); } else { - advance_cycles(gb, 4); + GB_advance_cycles(gb, 4); } } diff --git a/Core/z80_cpu.h b/Core/z80_cpu.h index 0e369458..55a0ff83 100644 --- a/Core/z80_cpu.h +++ b/Core/z80_cpu.h @@ -1,7 +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); +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); +void GB_cpu_run(GB_gameboy_t *gb); #endif /* z80_cpu_h */ diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c index d997fc63..0097fe08 100644 --- a/Core/z80_disassembler.c +++ b/Core/z80_disassembler.c @@ -5,161 +5,161 @@ #include "gb.h" -typedef void GB_opcode_t(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc); +typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); -static void ill(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, ".BYTE %02x\n", opcode); + GB_log(gb, ".BYTE %02x\n", opcode); (*pc)++; } -static void nop(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void nop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "NOP\n"); + GB_log(gb, "NOP\n"); (*pc)++; } -static void stop(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void stop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "STOP\n"); + 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) +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *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); + uint8_t register_id; + uint16_t value; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + value = GB_read_memory(gb, (*pc)++); + value |= GB_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) +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "LD [%s], a\n", register_names[register_id]); + uint8_t register_id; + register_id = (GB_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) +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "INC %s\n", register_names[register_id]); + uint8_t register_id; + register_id = (GB_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) +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; + uint8_t register_id; (*pc)++; register_id = ((opcode >> 4) + 1) & 0x03; - gb_log(gb, "INC %c\n", register_names[register_id][0]); + 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) +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; + uint8_t register_id; (*pc)++; register_id = ((opcode >> 4) + 1) & 0x03; - gb_log(gb, "DEC %c\n", register_names[register_id][0]); + 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) +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; + uint8_t 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)++)); + GB_log(gb, "LD %c, %02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++)); } -static void rlca(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rlca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RLCA\n"); + GB_log(gb, "RLCA\n"); } -static void rla(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RLA\n"); + GB_log(gb, "RLA\n"); } -static void ld_da16_sp(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc){ - unsigned short addr; +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){ + uint16_t addr; (*pc)++; - addr = read_memory(gb, (*pc)++); - addr |= read_memory(gb, (*pc)++) << 8; - gb_log(gb, "LD [%04x], sp\n", addr); + addr = GB_read_memory(gb, (*pc)++); + addr |= GB_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) +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; + uint8_t register_id; (*pc)++; register_id = (opcode >> 4) + 1; - gb_log(gb, "ADD hl, %s\n", register_names[register_id]); + 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) +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "LD a, [%s]\n", register_names[register_id]); + uint8_t register_id; + register_id = (GB_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) +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "DEC %s\n", register_names[register_id]); + uint8_t register_id; + register_id = (GB_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) +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "INC %c\n", register_names[register_id][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) +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "DEC %c\n", register_names[register_id][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) +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = (read_memory(gb, (*pc)++) >> 4) + 1; + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; - gb_log(gb, "LD %c, %02x\n", register_names[register_id][1], read_memory(gb, (*pc)++)); + GB_log(gb, "LD %c, %02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++)); } -static void rrca(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rrca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "RRCA\n"); + GB_log(gb, "RRCA\n"); (*pc)++; } -static void rra(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "RRA\n"); + GB_log(gb, "RRA\n"); (*pc)++; } -static void jr_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_attributed_log(gb, GB_LOG_UNDERLINE, "JR %04x\n", *pc + (signed char) read_memory(gb, (*pc)) + 1); + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %04x\n", *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1); (*pc)++; } -static const char *condition_code(unsigned char opcode) +static const char *condition_code(uint8_t opcode) { switch ((opcode >> 3) & 0x3) { case 0: @@ -175,83 +175,83 @@ static const char *condition_code(unsigned char opcode) return NULL; } -static void jr_cc_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *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); + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %04x\n", condition_code(opcode), *pc + (int8_t)GB_read_memory(gb, (*pc)) + 1); (*pc)++; } -static void daa(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void daa(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "DAA\n"); + GB_log(gb, "DAA\n"); (*pc)++; } -static void cpl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void cpl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "CPL\n"); + GB_log(gb, "CPL\n"); (*pc)++; } -static void scf(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void scf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "SCF\n"); + GB_log(gb, "SCF\n"); (*pc)++; } -static void ccf(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ccf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "CCF\n"); + GB_log(gb, "CCF\n"); (*pc)++; } -static void ld_dhli_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "LD [hli], a\n"); + GB_log(gb, "LD [hli], a\n"); (*pc)++; } -static void ld_dhld_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "LD [hld], a\n"); + GB_log(gb, "LD [hld], a\n"); (*pc)++; } -static void ld_a_dhli(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "LD a, [hli]\n"); + GB_log(gb, "LD a, [hli]\n"); (*pc)++; } -static void ld_a_dhld(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "LD a, [hld]\n"); + GB_log(gb, "LD a, [hld]\n"); (*pc)++; } -static void inc_dhl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "INC [hl]\n"); + GB_log(gb, "INC [hl]\n"); (*pc)++; } -static void dec_dhl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - gb_log(gb, "DEC [hl]\n"); + GB_log(gb, "DEC [hl]\n"); (*pc)++; } -static void ld_dhl_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LD [hl], %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "LD [hl], %02x\n", GB_read_memory(gb, (*pc)++)); } -static const char *get_src_name(unsigned char opcode) +static const char *get_src_name(uint8_t opcode) { - unsigned char src_register_id; - unsigned char src_low; + uint8_t src_register_id; + uint8_t src_low; src_register_id = ((opcode >> 1) + 1) & 3; src_low = !(opcode & 1); if (src_register_id == GB_REGISTER_AF && src_low) { @@ -265,10 +265,10 @@ static const char *get_src_name(unsigned char opcode) return high_register_names[src_register_id]; } -static const char *get_dst_name(unsigned char opcode) +static const char *get_dst_name(uint8_t opcode) { - unsigned char dst_register_id; - unsigned char dst_low; + uint8_t dst_register_id; + uint8_t dst_low; dst_register_id = ((opcode >> 4) + 1) & 3; dst_low = opcode & 8; if (dst_register_id == GB_REGISTER_AF && dst_low) { @@ -282,326 +282,326 @@ static const char *get_dst_name(unsigned char opcode) return high_register_names[dst_register_id]; } -static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode)); + 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) +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "ADD %s\n", get_src_name(opcode)); + 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) +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "ADC %s\n", get_src_name(opcode)); + 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) +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "SUB %s\n", get_src_name(opcode)); + 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) +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "SBC %s\n", get_src_name(opcode)); + 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) +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "AND %s\n", get_src_name(opcode)); + 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) +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "XOR %s\n", get_src_name(opcode)); + 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) +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "OR %s\n", get_src_name(opcode)); + 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) +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "CP %s\n", get_src_name(opcode)); + GB_log(gb, "CP %s\n", get_src_name(opcode)); } -static void halt(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void halt(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "HALT\n"); + GB_log(gb, "HALT\n"); } -static void ret_cc(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode)); + 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) +static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = ((read_memory(gb, (*pc)++) >> 4) + 1) & 3; - gb_log(gb, "POP %s\n", register_names[register_id]); + uint8_t register_id; + register_id = ((GB_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) +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *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)); + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } -static void jp_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "JP %04x\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "JP %04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } -static void call_cc_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "CALL %s, %04x\n", condition_code(opcode), read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "CALL %s, %04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } -static void push_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void push_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char register_id; - register_id = ((read_memory(gb, (*pc)++) >> 4) + 1) & 3; - gb_log(gb, "PUSH %s\n", register_names[register_id]); + uint8_t register_id; + register_id = ((GB_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) +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "ADD %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "ADD %02x\n", GB_read_memory(gb, (*pc)++)); } -static void adc_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "ADC %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "ADC %02x\n", GB_read_memory(gb, (*pc)++)); } -static void sub_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "SUB %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "SUB %02x\n", GB_read_memory(gb, (*pc)++)); } -static void sbc_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LBC %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "LBC %02x\n", GB_read_memory(gb, (*pc)++)); } -static void and_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "AND %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "AND %02x\n", GB_read_memory(gb, (*pc)++)); } -static void xor_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "XOR %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "XOR %02x\n", GB_read_memory(gb, (*pc)++)); } -static void or_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "OR %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "OR %02x\n", GB_read_memory(gb, (*pc)++)); } -static void cp_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "CP %02x\n", read_memory(gb, (*pc)++)); + GB_log(gb, "CP %02x\n", GB_read_memory(gb, (*pc)++)); } -static void rst(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rst(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RST %02x\n", opcode ^ 0xC7); + GB_log(gb, "RST %02x\n", opcode ^ 0xC7); } -static void ret(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ret(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n"); + GB_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n"); } -static void reti(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n"); + GB_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n"); } -static void call_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "CALL %04x\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "CALL %04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } -static void ld_da8_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - unsigned char temp = read_memory(gb, (*pc)++); - gb_log(gb, "LDH [%02x], a\n", temp); + uint8_t temp = GB_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) +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - unsigned char temp = read_memory(gb, (*pc)++); - gb_log(gb, "LDH a, [%02x]\n", temp); + uint8_t temp = GB_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) +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LDH [c], a\n"); + GB_log(gb, "LDH [c], a\n"); } -static void ld_a_dc(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LDH a, [c]\n"); + GB_log(gb, "LDH a, [c]\n"); } -static void add_sp_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - signed char temp = read_memory(gb, (*pc)++); - gb_log(gb, "ADD SP, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); + int8_t temp = GB_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) +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "JP hl\n"); + GB_log(gb, "JP hl\n"); } -static void ld_da16_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LD [%04x], a\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "LD [%04x], a\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } -static void ld_a_da16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LD a, [%04x]\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "LD a, [%04x]\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } -static void di(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void di(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "DI\n"); + GB_log(gb, "DI\n"); } -static void ei(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ei(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "EI\n"); + GB_log(gb, "EI\n"); } -static void ld_hl_sp_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - signed char temp = read_memory(gb, (*pc)++); - gb_log(gb, "LD hl, sp, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); + int8_t temp = GB_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) +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "LD sp, hl\n"); + GB_log(gb, "LD sp, hl\n"); } -static void rlc_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RLC %s\n", get_src_name(opcode)); + GB_log(gb, "RLC %s\n", get_src_name(opcode)); } -static void rrc_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RRC %s\n", get_src_name(opcode)); + GB_log(gb, "RRC %s\n", get_src_name(opcode)); } -static void rl_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RL %s\n", get_src_name(opcode)); + GB_log(gb, "RL %s\n", get_src_name(opcode)); } -static void rr_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void rr_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RR %s\n", get_src_name(opcode)); + GB_log(gb, "RR %s\n", get_src_name(opcode)); } -static void sla_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void sla_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "SLA %s\n", get_src_name(opcode)); + GB_log(gb, "SLA %s\n", get_src_name(opcode)); } -static void sra_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void sra_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "SRA %s\n", get_src_name(opcode)); + GB_log(gb, "SRA %s\n", get_src_name(opcode)); } -static void srl_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void srl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "SRL %s\n", get_src_name(opcode)); + GB_log(gb, "SRL %s\n", get_src_name(opcode)); } -static void swap_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void swap_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - gb_log(gb, "RLC %s\n", get_src_name(opcode)); + GB_log(gb, "RLC %s\n", get_src_name(opcode)); } -static void bit_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc) +static void bit_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - unsigned char bit; + uint8_t bit; (*pc)++; bit = ((opcode >> 3) & 7); if ((opcode & 0xC0) == 0x40) { /* Bit */ - gb_log(gb, "BIT %s, %d\n", get_src_name(opcode), 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); + 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); + 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) +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - opcode = read_memory(gb, ++*pc); + opcode = GB_read_memory(gb, ++*pc); switch (opcode >> 3) { case 0: rlc_r(gb, opcode, pc); @@ -670,11 +670,11 @@ static GB_opcode_t *opcodes[256] = { 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) +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count) { while (count--) { - gb_log(gb, "%s%04x: ", pc == gb->pc? "-> ": " ", pc); - unsigned char opcode = read_memory(gb, pc); + GB_log(gb, "%s%04x: ", pc == gb->pc? "-> ": " ", pc); + uint8_t opcode = GB_read_memory(gb, pc); opcodes[opcode](gb, opcode, &pc); } } diff --git a/SDL/main.c b/SDL/main.c index 07e8fedc..48a5aae1 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -9,7 +9,7 @@ static bool running = false; -void update_keys_status(GB_gameboy_t *gb) +void GB_update_keys_status(GB_gameboy_t *gb) { static bool ctrl = false; SDL_Event event; @@ -74,9 +74,9 @@ void vblank(GB_gameboy_t *gb) { SDL_Surface *screen = gb->user_data; SDL_Flip(screen); - update_keys_status(gb); + GB_update_keys_status(gb); - gb_set_pixels_output(gb, screen->pixels); + GB_set_pixels_output(gb, screen->pixels); } #ifdef __APPLE__ @@ -123,7 +123,7 @@ static char *executable_relative_path(const char *filename) } static SDL_Surface *screen = NULL; -static uint32_t rgb_encode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b) +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return SDL_MapRGB(screen->format, r, g, b); } @@ -138,7 +138,7 @@ static void debugger_interrupt(int ignore) static void audio_callback(void *gb, Uint8 *stream, int len) { - apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); + GB_apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); } #ifdef __APPLE__ @@ -169,21 +169,21 @@ usage: if (dmg) { - gb_init(&gb); - if (gb_load_bios(&gb, executable_relative_path("dmg_boot.bin"))) { + GB_init(&gb); + if (GB_load_boot_rom(&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"))) { + GB_init_cgb(&gb); + if (GB_load_boot_rom(&gb, executable_relative_path("cgb_boot.bin"))) { perror("Failed to load boot ROM"); exit(1); } } - if (gb_load_rom(&gb, argv[argc - 1])) { + if (GB_load_rom(&gb, argv[argc - 1])) { perror("Failed to load ROM"); exit(1); } @@ -198,10 +198,10 @@ usage: #endif /* Configure Screen */ SDL_LockSurface(screen); - gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + 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); + GB_set_pixels_output(&gb, screen->pixels); + GB_set_rgb_encode_callback(&gb, rgb_encode); /* Configure battery */ size_t path_length = strlen(argv[argc - 1]); @@ -220,7 +220,7 @@ usage: /* Add .sav */ strcat(battery_save_path, ".sav"); - gb_load_battery(&gb, battery_save_path); + GB_load_battery(&gb, battery_save_path); /* Configure Audio */ SDL_AudioSpec want, have; @@ -232,7 +232,7 @@ usage: want.callback = audio_callback; want.userdata = &gb; SDL_OpenAudio(&want, &have); - gb_set_sample_rate(&gb, 96000); + GB_set_sample_rate(&gb, 96000); /* Start Audio */ SDL_PauseAudio(0); @@ -240,11 +240,11 @@ usage: /* Run emulation */ running = true; while (running) { - gb_run(&gb); + GB_run(&gb); } SDL_CloseAudio(); - gb_save_battery(&gb, battery_save_path); + GB_save_battery(&gb, battery_save_path); return 0; } From 79fd9ed6ad9914a96906867052e4ba9d56327791 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Jul 2016 13:12:04 +0300 Subject: [PATCH 0072/1216] Added boolean operators to the debugger --- Core/debugger.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index c661e55c..547d94c8 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -87,6 +87,14 @@ static uint16_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) return read_lvalue(gb, a); } +static uint16_t bool_and(uint16_t a, uint16_t b) {return a && b;}; +static uint16_t bool_or(uint16_t a, uint16_t b) {return a || b;}; +static uint16_t equals(uint16_t a, uint16_t b) {return a == b;}; +static uint16_t lower(uint16_t a, uint16_t b) {return a < b;}; +static uint16_t greater(uint16_t a, uint16_t b) {return a > b;}; +static uint16_t lower_equals(uint16_t a, uint16_t b) {return a <= b;}; +static uint16_t greater_equals(uint16_t a, uint16_t b) {return a >= b;}; + static struct { const char *string; char priority; @@ -98,15 +106,22 @@ static struct { // Deal with it. {"+", 0, add}, {"-", 0, sub}, + {"||", 0, bool_or}, {"|", 0, or}, {"*", 1, mul}, {"/", 1, _div}, {"%", 1, mod}, + {"&&", 1, bool_and}, {"&", 1, and}, {"^", 1, xor}, {"<<", 2, shleft}, + {"<=", 3, lower_equals}, + {"<", 3, lower}, {">>", 2, shright}, - {"=", 2, NULL, assign}, + {">=", 3, greater_equals}, + {">", 3, greater}, + {"==", 3, equals}, + {"=", 4, NULL, assign}, }; uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error); @@ -252,10 +267,14 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int le 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) { + unsigned long operator_length = strlen(operators[j].string); + if (memcmp(string + i, operators[j].string, operator_length) == 0) { // Found an operator! operator_pos = i; operator_index = j; + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + break; } } } From 8eee70aed96d68aaa646fae7758f073da191e6de Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Jul 2016 18:04:25 +0300 Subject: [PATCH 0073/1216] Fixed disassembler bugs --- Core/z80_disassembler.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c index 0097fe08..14a059f7 100644 --- a/Core/z80_disassembler.c +++ b/Core/z80_disassembler.c @@ -253,10 +253,9 @@ static const char *get_src_name(uint8_t opcode) uint8_t src_register_id; uint8_t src_low; src_register_id = ((opcode >> 1) + 1) & 3; - src_low = !(opcode & 1); - if (src_register_id == GB_REGISTER_AF && src_low) { - - return "[hl]"; + src_low = (opcode & 1); + if (src_register_id == GB_REGISTER_AF) { + return src_low? "a": "[hl]"; } if (src_low) { return register_names[src_register_id] + 1; @@ -271,9 +270,8 @@ static const char *get_dst_name(uint8_t opcode) uint8_t 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_register_id == GB_REGISTER_AF) { + return dst_low? "a": "[hl]"; } if (dst_low) { return register_names[dst_register_id] + 1; From 19aea4096be158ca2e557ed39fa5abd66cb75d75 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Jul 2016 18:24:21 +0300 Subject: [PATCH 0074/1216] Added condition breakpoint. Fixed a possible crash when deleting a breakpoint. --- Core/debugger.c | 84 ++++++++++++++++++++++++++++++++++++++++++------- Core/gb.h | 4 ++- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 547d94c8..45c05a61 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -20,6 +20,11 @@ typedef struct { }; } lvalue_t; +struct GB_breakpoint_s { + uint16_t addr; + char *condition; +}; + static uint16_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) { /* Not used until we add support for operators like += */ @@ -438,8 +443,8 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr) int max = gb->n_breakpoints; while (min < max) { uint16_t pivot = (min + max) / 2; - if (gb->breakpoints[pivot] == addr) return pivot; - if (gb->breakpoints[pivot] > addr) { + if (gb->breakpoints[pivot].addr == addr) return pivot; + if (gb->breakpoints[pivot].addr > addr) { max = pivot - 1; } else { @@ -452,24 +457,55 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr) static bool breakpoint(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { - GB_log(gb, "Usage: breakpoint \n"); + GB_log(gb, "Usage: breakpoint [ if ]\n"); return true; } + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + if (error) return true; + + } + bool error; uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); if (error) return true; uint16_t index = find_breakpoint(gb, result); - if (index < gb->n_breakpoints && gb->breakpoints[index] == result) { + if (index < gb->n_breakpoints && gb->breakpoints[index].addr == result) { GB_log(gb, "Breakpoint already set at %04x\n", result); + if (!gb->breakpoints[index].condition && condition) { + GB_log(gb, "Added condition to breakpoint\n"); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && condition) { + GB_log(gb, "Replaced breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && !condition) { + GB_log(gb, "Removed breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = NULL; + } return true; } gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); - gb->breakpoints[index] = result; + gb->breakpoints[index].addr = result; + if (condition) { + gb->breakpoints[index].condition = strdup(condition); + } + else { + gb->breakpoints[index].condition = NULL; + } gb->n_breakpoints++; GB_log(gb, "Breakpoint set at %04x\n", result); @@ -482,6 +518,11 @@ static bool delete(GB_gameboy_t *gb, char *arguments) GB_log(gb, "Delete all breakpoints? "); char *answer = gb->input_callback(gb); if (answer[0] == 'Y' || answer[0] == 'y') { + for (unsigned i = gb->n_breakpoints; i--;) { + if (gb->breakpoints[i].condition) { + free(gb->breakpoints[i].condition); + } + } free(gb->breakpoints); gb->breakpoints = NULL; gb->n_breakpoints = 0; @@ -495,12 +536,16 @@ static bool delete(GB_gameboy_t *gb, char *arguments) if (error) return true; uint16_t index = find_breakpoint(gb, result); - if (index >= gb->n_breakpoints || gb->breakpoints[index] != result) { + if (index >= gb->n_breakpoints || gb->breakpoints[index].addr != result) { GB_log(gb, "No breakpoint set at %04x\n", result); return true; } - memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + if (gb->breakpoints[index].condition) { + free(gb->breakpoints[index].condition); + } + + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); @@ -522,7 +567,12 @@ static bool list(GB_gameboy_t *gb, char *arguments) GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); for (uint16_t i = 0; i < gb->n_breakpoints; i++) { - GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i]); + if (gb->breakpoints[i].condition) { + GB_log(gb, " %d. %04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition); + } + else { + GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i].addr); + } } return true; @@ -531,8 +581,19 @@ static bool list(GB_gameboy_t *gb, char *arguments) static bool should_break(GB_gameboy_t *gb, uint16_t addr) { uint16_t index = find_breakpoint(gb, addr); - if (index < gb->n_breakpoints && gb->breakpoints[index] == addr) { - return true; + if (index < gb->n_breakpoints && gb->breakpoints[index].addr == addr) { + if (!gb->breakpoints[index].condition) { + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, + (unsigned int)strlen(gb->breakpoints[index].condition), &error); + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return true; + } + return condition; } return false; } @@ -611,7 +672,6 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) GB_log(gb, "Cart contains a real time clock\n"); } - return true; } @@ -625,7 +685,7 @@ static const debugger_command_t commands[] = { {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, mbc, NULL}, - {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression"}, + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression. Can also modify the condition of existing breakpoints."}, {"list", 1, list, "List all set breakpoints"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, {"print", 1, print, "Evaluate and print an expression"}, diff --git a/Core/gb.h b/Core/gb.h index e7bbed2e..68df73ca 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -180,6 +180,8 @@ typedef struct { /* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */ _Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1"); +struct GB_breakpoint_s; + typedef struct GB_gameboy_s { GB_SECTION(header, /* The magic makes sure a state file is: @@ -315,7 +317,7 @@ typedef struct GB_gameboy_s { int debug_call_depth; bool debug_fin_command, debug_next_command; uint16_t n_breakpoints; - uint16_t *breakpoints; + struct GB_breakpoint_s *breakpoints; bool stack_leak_detection; uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ uint16_t addr_for_call_depth[0x200]; From cc9af4a5c54d078074cf1a77754f2ae3afd34053 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 Jul 2016 18:39:40 +0300 Subject: [PATCH 0075/1216] Updated change log and incremented version to 0.5 --- CHANGES.md | 29 +++++++++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c0d944de..f4ae4e22 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,34 @@ # Change Log +## Version 0.5 +This version is not compatible with save states of older versions. + +### New/Improved Features + * Updated save state format, should now be much more future compatible + * Save state compatibility between 32 and 64 bit versions of SameBoy + * Cocoa version is now using OpenGL 3 + * HQ2x filter added (Cocoa only) + * A new, redesigned OmniScale filter; old filter is renamed to OmniScale Legacy (Cocoa only) + * Cocoa port now "remembers" the last window size + * Added boolean operators to the debugger + * Added conditional breakpoints + +### Accuracy Improvements/Fixes + * Better emulation of certain behaviors of the SCX register + * Fixed emulation of the STOP instruction + * Minor fix to the accuracy of the JOYPAD register + * Minor improvements to HDMA accuracy + + +### Bug Fixes + * Improved concurrency in the Cocoa port + * Fixed a bug where an emulator window in the Cocoa port will freeze until resized + * Fixed incorrect disassembler outputs + * Fixed a potential crash when deleting a breakpoint + +### Misc Internal Changes + * Large code refactoring, getting ready to stabilize API + ## Version 0.4 This version is not compatible with save states of older versions. diff --git a/Makefile b/Makefile index 208706cd..945df7fa 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ else default: sdl endif -VERSION := 0.4 +VERSION := 0.5 BIN := build/bin OBJ := build/obj From 9321df9630e7514036fa0131606c24f07a740cdc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Jul 2016 20:58:06 +0300 Subject: [PATCH 0076/1216] Prevent the Cocoa port from being both "paused" and "debug_paused" --- Cocoa/Document.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 730812fc..1dbefaf9 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -216,6 +216,9 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { [self log:"^C\n"]; gb.debug_stopped = true; + if (!running) { + [self start]; + } [self.consoleInput becomeFirstResponder]; } @@ -241,7 +244,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) [(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; } else if ([anItem action] == @selector(togglePause:)) { - [(NSMenuItem*)anItem setState:!running]; + [(NSMenuItem*)anItem setState:(!running) || (gb.debug_stopped)]; + return !gb.debug_stopped; } else if ([anItem action] == @selector(reset:) && anItem.tag != 0) { [(NSMenuItem*)anItem setState:(anItem.tag == 1 && !gb.is_cgb) || (anItem.tag == 2 && gb.is_cgb)]; From 39f91f0dd61488d6e9fb0faac1279a7e46191aec Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Jul 2016 20:58:25 +0300 Subject: [PATCH 0077/1216] Proportional Reiszing --- Cocoa/Document.xib | 14 ++++++++++---- Cocoa/GBBorderView.h | 5 +++++ Cocoa/GBBorderView.m | 11 +++++++++++ Cocoa/GBView.m | 20 +++++++++++++++++++- 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 Cocoa/GBBorderView.h create mode 100644 Cocoa/GBBorderView.m diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index b3066894..754e92fa 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -1,7 +1,7 @@ - + - + @@ -26,10 +26,16 @@ - + - + + + + + + + diff --git a/Cocoa/GBBorderView.h b/Cocoa/GBBorderView.h new file mode 100644 index 00000000..477add17 --- /dev/null +++ b/Cocoa/GBBorderView.h @@ -0,0 +1,5 @@ +#import + +@interface GBBorderView : NSView + +@end diff --git a/Cocoa/GBBorderView.m b/Cocoa/GBBorderView.m new file mode 100644 index 00000000..c81adb4a --- /dev/null +++ b/Cocoa/GBBorderView.m @@ -0,0 +1,11 @@ +#import "GBBorderView.h" + +@implementation GBBorderView + +- (void)drawRect:(NSRect)dirtyRect { + [[NSColor blackColor] setFill]; + NSRectFill(dirtyRect); + [super drawRect:dirtyRect]; +} + +@end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 28224301..0b1261f2 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -87,6 +87,25 @@ return self; } +- (void)setFrame:(NSRect)frame +{ + frame = self.superview.frame; + double ratio = frame.size.width / frame.size.height; + if (ratio >= 160.0/144.0) { + double new_width = round(frame.size.height / 144.0 * 160.0); + frame.origin.x = floor((frame.size.width - new_width) / 2); + frame.size.width = new_width; + frame.origin.y = 0; + } + else { + double new_height = round(frame.size.width / 160.0 * 144.0); + frame.origin.y = floor((frame.size.height - new_height) / 2); + frame.size.height = new_height; + frame.origin.x = 0; + } + [super setFrame:frame]; +} + - (void)drawRect:(NSRect)dirtyRect { if (!self.shader) { self.shader = [[GBShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; @@ -110,7 +129,6 @@ glFlush(); } - - (void) flip { current_buffer = (current_buffer + 1) % self.numberOfBuffers; From c9d4a4ebb54a6325f8da29b2b137f6425ad3eb8d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Jul 2016 21:32:58 +0300 Subject: [PATCH 0078/1216] Proper fullscreen support (Including Yosemite and older) --- Cocoa/Document.m | 16 +++++++++++++++- Cocoa/Document.xib | 1 + Cocoa/Preferences.xib | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 1dbefaf9..24482c31 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -11,7 +11,8 @@ reasonable alternative to this. */ unsigned long pendingLogLines; bool tooMuchLogs; - + bool fullScreen; + NSString *lastConsoleInput; } @@ -262,8 +263,21 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) } +- (void) windowWillEnterFullScreen:(NSNotification *)notification +{ + fullScreen = true; +} + +- (void) windowWillExitFullScreen:(NSNotification *)notification +{ + fullScreen = false; +} + - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame { + if (fullScreen) { + return newFrame; + } NSRect rect = window.contentView.frame; int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 754e92fa..472f71f7 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -18,6 +18,7 @@ + diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index af4b38e7..43b27b47 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -13,6 +13,7 @@ + From 1268bf3a357c379acdf5e522ff3868c6b8743d0c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 5 Jul 2016 21:23:55 +0300 Subject: [PATCH 0079/1216] Keeping aspect ratio is now optional (but default) --- Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 19 +++++++++++++++++++ Cocoa/GBView.m | 32 ++++++++++++++++++++------------ Cocoa/Preferences.xib | 21 ++++++++++++++++----- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 7e1876d2..f4f3c6b0 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -3,4 +3,5 @@ @interface GBPreferencesWindow : NSWindow @property IBOutlet NSTableView *controlsTableView; @property IBOutlet NSPopUpButton *graphicsFilterPopupButton; +@property (strong) IBOutlet NSButton *aspectRatioCheckbox; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 0aee2c2a..aa04b73a 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -8,6 +8,7 @@ NSInteger button_being_modified; NSPopUpButton *_graphicsFilterPopupButton; + NSButton *_aspectRatioCheckbox; } + (NSArray *)filterList @@ -100,4 +101,22 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; } +- (IBAction)changeAspectRatio:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState + forKey:@"GBAspectRatioUnkept"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil]; +} + +- (NSButton *)aspectRatioCheckbox +{ + return _aspectRatioCheckbox; +} + +- (void)setAspectRatioCheckbox:(NSButton *)aspectRatioCheckbox +{ + _aspectRatioCheckbox = aspectRatioCheckbox; + [_aspectRatioCheckbox setState: ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]]; +} + @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0b1261f2..f6581de0 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -41,6 +41,7 @@ image_buffers[2] = malloc(160 * 144 * 4); _shouldBlendFrameWithPrevious = 1; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; } - (void) filterChanged @@ -49,6 +50,11 @@ self.shader = nil; } +- (void) ratioKeepingChanged +{ + [self setFrame:self.superview.frame]; +} + - (void) setShouldBlendFrameWithPrevious:(BOOL)shouldBlendFrameWithPrevious { _shouldBlendFrameWithPrevious = shouldBlendFrameWithPrevious; @@ -90,18 +96,20 @@ - (void)setFrame:(NSRect)frame { frame = self.superview.frame; - double ratio = frame.size.width / frame.size.height; - if (ratio >= 160.0/144.0) { - double new_width = round(frame.size.height / 144.0 * 160.0); - frame.origin.x = floor((frame.size.width - new_width) / 2); - frame.size.width = new_width; - frame.origin.y = 0; - } - else { - double new_height = round(frame.size.width / 160.0 * 144.0); - frame.origin.y = floor((frame.size.height - new_height) / 2); - frame.size.height = new_height; - frame.origin.x = 0; + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { + double ratio = frame.size.width / frame.size.height; + if (ratio >= 160.0/144.0) { + double new_width = round(frame.size.height / 144.0 * 160.0); + frame.origin.x = floor((frame.size.width - new_width) / 2); + frame.size.width = new_width; + frame.origin.y = 0; + } + else { + double new_height = round(frame.size.width / 160.0 * 144.0); + frame.origin.y = floor((frame.size.height - new_height) / 2); + frame.size.height = new_height; + frame.origin.x = 0; + } } [super setFrame:frame]; } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 43b27b47..a9326469 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -15,10 +15,10 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -70,6 +70,16 @@ + @@ -130,11 +140,12 @@ + - + From dce0e5fdebb4ab7add5eaf785a82d763c79048e6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 5 Jul 2016 23:34:33 +0300 Subject: [PATCH 0080/1216] Hide mouse cursor when running (Cocoa) --- Cocoa/Document.m | 7 ++++++ Cocoa/GBView.h | 1 + Cocoa/GBView.m | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 24482c31..bc276cf5 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -94,6 +94,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void) vblank { + self.view.mouseHidingEnabled = YES; [self.view flip]; GB_set_pixels_output(&gb, self.view.pixels); } @@ -107,11 +108,13 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { GB_apu_copy_buffer(&gb, buffer, nFrames); } andSampleRate:96000]; + self.view.mouseHidingEnabled = YES; [self.audioClient start]; while (running) { GB_run(&gb); } [self.audioClient stop]; + self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); stopping = false; } @@ -309,6 +312,10 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return; } pendingLogLines++; + + /* Make sure mouse is not hidden while debugging */ + self.view.mouseHidingEnabled = NO; + NSString *nsstring = @(string); // For ref-counting dispatch_async(dispatch_get_main_queue(), ^{ NSFont *font = [NSFont userFixedPitchFontOfSize:12]; diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index a2baada2..f8763ac5 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -8,4 +8,5 @@ @property GB_gameboy_t *gb; @property (nonatomic) BOOL shouldBlendFrameWithPrevious; @property GBShader *shader; +@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index f6581de0..0053c667 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -8,6 +8,9 @@ { uint32_t *image_buffers[3]; unsigned char current_buffer; + BOOL mouse_hidden; + NSTrackingArea *tracking_area; + BOOL _mouseHidingEnabled; } - (void) awakeFromNib @@ -42,6 +45,11 @@ _shouldBlendFrameWithPrevious = 1; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; + tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect + owner:self + userInfo:nil]; + [self addTrackingArea:tracking_area]; } - (void) filterChanged @@ -71,6 +79,10 @@ free(image_buffers[0]); free(image_buffers[1]); free(image_buffers[2]); + if (mouse_hidden) { + mouse_hidden = false; + [NSCursor unhide]; + } [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (instancetype)initWithCoder:(NSCoder *)coder @@ -111,6 +123,7 @@ frame.origin.x = 0; } } + [super setFrame:frame]; } @@ -223,4 +236,46 @@ { return YES; } + +- (void)mouseEntered:(NSEvent *)theEvent +{ + if (!mouse_hidden) { + mouse_hidden = true; + if (_mouseHidingEnabled) { + [NSCursor hide]; + } + } + [super mouseEntered:theEvent]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + if (mouse_hidden) { + mouse_hidden = false; + if (_mouseHidingEnabled) { + [NSCursor unhide]; + } + } + [super mouseExited:theEvent]; +} + +- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled +{ + if (mouseHidingEnabled == _mouseHidingEnabled) return; + + _mouseHidingEnabled = mouseHidingEnabled; + + if (mouse_hidden && _mouseHidingEnabled) { + [NSCursor hide]; + } + + if (mouse_hidden && !_mouseHidingEnabled) { + [NSCursor unhide]; + } +} + +- (BOOL)isMouseHidingEnabled +{ + return _mouseHidingEnabled; +} @end From de4983099a5889f769df7446ee4d8bc306162df1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 7 Jul 2016 00:29:25 +0300 Subject: [PATCH 0081/1216] Added (conditional) r/w watchpoints. Fixed a bug where breakpoint condition syntax is not checked. Added != operator. --- Core/debugger.c | 305 ++++++++++++++++++++++++++++++++++++++++++++---- Core/debugger.h | 2 + Core/gb.h | 20 +++- Core/memory.c | 3 + 4 files changed, 300 insertions(+), 30 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 45c05a61..38cf57ca 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -25,6 +25,15 @@ struct GB_breakpoint_s { char *condition; }; +#define GB_WATCHPOINT_R (1) +#define GB_WATCHPOINT_W (2) + +struct GB_watchpoint_s { + uint16_t addr; + char *condition; + uint8_t flags; +}; + static uint16_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) { /* Not used until we add support for operators like += */ @@ -95,6 +104,7 @@ static uint16_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) static uint16_t bool_and(uint16_t a, uint16_t b) {return a && b;}; static uint16_t bool_or(uint16_t a, uint16_t b) {return a || b;}; static uint16_t equals(uint16_t a, uint16_t b) {return a == b;}; +static uint16_t different(uint16_t a, uint16_t b) {return a != b;}; static uint16_t lower(uint16_t a, uint16_t b) {return a < b;}; static uint16_t greater(uint16_t a, uint16_t b) {return a > b;}; static uint16_t lower_equals(uint16_t a, uint16_t b) {return a <= b;}; @@ -127,11 +137,16 @@ static struct { {">", 3, greater}, {"==", 3, equals}, {"=", 4, NULL, assign}, + {"!=", 3, different}, }; -uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error); +uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + unsigned int length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); -static lvalue_t debugger_evaluate_lvalue(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, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; // Strip whitespace @@ -160,7 +175,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, u } if (string[i] == ')') depth--; } - if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error); + if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); } else if (string[0] == '[' && string[length - 1] == ']') { // Attempt to strip square parentheses (memory dereference) @@ -175,7 +190,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, u if (string[i] == ']') depth--; } if (depth == 0) { - return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error)}; + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; } } @@ -213,7 +228,9 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, u return (lvalue_t){0,}; } -uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error) +uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + unsigned int length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; // Strip whitespace @@ -242,7 +259,7 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int le } if (string[i] == ')') depth--; } - if (depth == 0) return debugger_evaluate(gb, string + 1, length - 2, error); + if (depth == 0) return debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); } else if (string[0] == '[' && string[length - 1] == ']') { // Attempt to strip square parentheses (memory dereference) @@ -256,7 +273,7 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int le } if (string[i] == ']') depth--; } - if (depth == 0) return GB_read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error)); + if (depth == 0) return GB_read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)); } // Search for lowest priority operator signed int depth = 0; @@ -286,14 +303,14 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int le } if (operator_index != -1) { unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); - uint16_t right = debugger_evaluate(gb, string + right_start, length - right_start, error); + uint16_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); if (*error) return -1; if (operators[operator_index].lvalue_operator) { - lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error); + lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); if (*error) return -1; return operators[operator_index].lvalue_operator(gb, left, right); } - uint16_t left = debugger_evaluate(gb, string, operator_pos, error); + uint16_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); if (*error) return -1; return operators[operator_index].operator(left, right); } @@ -324,6 +341,20 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int le case 'p': if (string[2] == 'c') return gb->pc; } } + else if (length == 4) { + if (watchpoint_address && memcmp(string, "$old", 4) == 0) { + return GB_read_memory(gb, *watchpoint_address); + } + + if (watchpoint_new_value && memcmp(string, "$new", 4) == 0) { + return *watchpoint_new_value; + } + + /* $new is identical to $old in read conditions */ + if (watchpoint_address && memcmp(string, "$new", 4) == 0) { + return GB_read_memory(gb, *watchpoint_address); + } + } GB_log(gb, "Unknown register: %.*s\n", length, string); *error = true; return -1; @@ -467,13 +498,13 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) condition += strlen(" if "); /* Verify condition is sane (Todo: This might have side effects!) */ bool error; - debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, NULL, NULL); if (error) return true; } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (error) return true; @@ -531,7 +562,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (error) return true; @@ -553,6 +584,154 @@ static bool delete(GB_gameboy_t *gb, char *arguments) return true; } +/* Find the index of the closest watchpoint equal or greater to addr */ +static uint16_t find_watchpoint(GB_gameboy_t *gb, uint16_t addr) +{ + if (!gb->watchpoints) { + return 0; + } + int min = 0; + int max = gb->n_watchpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->watchpoints[pivot].addr == addr) return pivot; + if (gb->watchpoints[pivot].addr > addr) { + max = pivot - 1; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static bool watch(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { +print_usage: + GB_log(gb, "Usage: watch (r|w|rw) [ if ]\n"); + return true; + } + + uint8_t flags = 0; + while (*arguments != ' ' && *arguments) { + switch (*arguments) { + case 'r': + flags |= GB_WATCHPOINT_R; + break; + case 'w': + flags |= GB_WATCHPOINT_W; + break; + default: + goto print_usage; + } + arguments++; + } + + if (!flags) { + goto print_usage; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + /* To make $new and $old legal */ + uint16_t dummy = 0; + uint8_t dummy2 = 0; + debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, &dummy, &dummy2); + if (error) return true; + + } + + bool error; + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + + if (error) return true; + + uint16_t index = find_watchpoint(gb, result); + if (index < gb->n_watchpoints && gb->watchpoints[index].addr == result) { + GB_log(gb, "Watchpoint already set at %04x\n", result); + if (!gb->watchpoints[index].flags != flags) { + GB_log(gb, "Modified watchpoint type\n"); + gb->watchpoints[index].flags = flags; + } + if (!gb->watchpoints[index].condition && condition) { + GB_log(gb, "Added condition to watchpoint\n"); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && condition) { + GB_log(gb, "Replaced watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && !condition) { + GB_log(gb, "Removed watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = NULL; + } + return true; + } + + gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0])); + memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0])); + gb->watchpoints[index].addr = result; + gb->watchpoints[index].flags = flags; + if (condition) { + gb->watchpoints[index].condition = strdup(condition); + } + else { + gb->watchpoints[index].condition = NULL; + } + gb->n_watchpoints++; + + GB_log(gb, "Watchpoint set at %04x\n", result); + return true; +} + +static bool unwatch(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { + GB_log(gb, "Delete all watchpoints? "); + char *answer = gb->input_callback(gb); + if (answer[0] == 'Y' || answer[0] == 'y') { + for (unsigned i = gb->n_watchpoints; i--;) { + if (gb->watchpoints[i].condition) { + free(gb->watchpoints[i].condition); + } + } + free(gb->watchpoints); + gb->watchpoints = NULL; + gb->n_watchpoints = 0; + } + return true; + } + + bool error; + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + + if (error) return true; + + uint16_t index = find_watchpoint(gb, result); + if (index >= gb->n_watchpoints || gb->watchpoints[index].addr != result) { + GB_log(gb, "No watchpoint set at %04x\n", result); + return true; + } + + if (gb->watchpoints[index].condition) { + free(gb->watchpoints[index].condition); + } + + memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0])); + gb->n_watchpoints--; + gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); + + GB_log(gb, "Watchpoint removed from %04x\n", result); + return true; +} + static bool list(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments))) { @@ -562,16 +741,36 @@ static bool list(GB_gameboy_t *gb, char *arguments) if (gb->n_breakpoints == 0) { GB_log(gb, "No breakpoints set.\n"); - return true; + } + else { + GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (uint16_t i = 0; i < gb->n_breakpoints; i++) { + if (gb->breakpoints[i].condition) { + GB_log(gb, " %d. %04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition); + } + else { + GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i].addr); + } + } } - GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); - for (uint16_t i = 0; i < gb->n_breakpoints; i++) { - if (gb->breakpoints[i].condition) { - GB_log(gb, " %d. %04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition); - } - else { - GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i].addr); + if (gb->n_watchpoints == 0) { + GB_log(gb, "No watchpoints set.\n"); + } + else { + GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); + for (uint16_t i = 0; i < gb->n_watchpoints; i++) { + if (gb->watchpoints[i].condition) { + GB_log(gb, " %d. %04x (%c%c, Condition: %s)\n", i + 1, gb->watchpoints[i].addr, + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', + gb->watchpoints[i].condition); + } + else { + GB_log(gb, " %d. %04x (%c%c)\n", i + 1, gb->watchpoints[i].addr, + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); + } } } @@ -587,7 +786,7 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr) } bool error; bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, - (unsigned int)strlen(gb->breakpoints[index].condition), &error); + (unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL); if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -606,7 +805,7 @@ static bool print(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { GB_log(gb, "=%04x\n", result); } @@ -621,7 +820,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + uint16_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { GB_log(gb, "%4x: ", addr); for (int i = 0; i < 16; i++) { @@ -686,8 +885,10 @@ static const debugger_command_t commands[] = { {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, mbc, NULL}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression. Can also modify the condition of existing breakpoints."}, - {"list", 1, list, "List all set breakpoints"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, + {"watch", 1, watch, "Add a new watchpoint at the specified address/expression. Can also modify the condition and type of existing watchpoints."}, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints"}, + {"list", 1, list, "List all set breakpoints and watchpoints"}, {"print", 1, print, "Evaluate and print an expression"}, {"eval", 2, print, NULL}, {"examine", 2, examine, "Examine values at address"}, @@ -762,6 +963,60 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb) } } +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + uint16_t index = find_breakpoint(gb, addr); + if (index < gb->n_watchpoints && gb->watchpoints[index].addr == addr) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { + return; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%04x] = %02x\n", addr, value); + return; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, &value); + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%04x] = %02x\n", addr, value); + } + } +} + +void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) +{ + uint16_t index = find_breakpoint(gb, addr); + if (index < gb->n_watchpoints && gb->watchpoints[index].addr == addr) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { + return; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%04x]\n", addr); + return; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, NULL); + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%04x]\n", addr); + } + } +} + void GB_debugger_run(GB_gameboy_t *gb) { char *input = NULL; diff --git a/Core/debugger.h b/Core/debugger.h index 0b2023cb..9f0f9220 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -5,5 +5,7 @@ void GB_debugger_run(GB_gameboy_t *gb); void GB_debugger_call_hook(GB_gameboy_t *gb); void GB_debugger_ret_hook(GB_gameboy_t *gb); +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); #endif /* debugger_h */ diff --git a/Core/gb.h b/Core/gb.h index 68df73ca..a71b20f9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -170,6 +170,9 @@ typedef struct { bool has_rumble; } GB_cartridge_t; +struct GB_breakpoint_s; +struct GB_watchpoint_s; + /* When state saving, each section is dumped independently of other sections. This allows adding data to the end of the section without worrying about future compatibility. Some other changes might be "safe" as well. @@ -180,8 +183,6 @@ typedef struct { /* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */ _Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1"); -struct GB_breakpoint_s; - typedef struct GB_gameboy_s { GB_SECTION(header, /* The magic makes sure a state file is: @@ -313,15 +314,24 @@ typedef struct GB_gameboy_s { GB_rgb_encode_callback_t rgb_encode_callback; GB_vblank_callback_t vblank_callback; - /* Debugger */ - int debug_call_depth; + /*** Debugger ***/ + bool debug_stopped; bool debug_fin_command, debug_next_command; + + /* Breakpoints */ uint16_t n_breakpoints; struct GB_breakpoint_s *breakpoints; + + /* SLD */ bool stack_leak_detection; + int debug_call_depth; uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ uint16_t addr_for_call_depth[0x200]; - bool debug_stopped; + + /* Watchpoints */ + uint16_t n_watchpoints; + struct GB_watchpoint_s *watchpoints; + /* Misc */ bool turbo; diff --git a/Core/memory.c b/Core/memory.c index 812ba6b4..845e714d 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -4,6 +4,7 @@ #include "joypad.h" #include "display.h" #include "memory.h" +#include "debugger.h" typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); @@ -194,6 +195,7 @@ static GB_read_function_t * const read_map[] = uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { + GB_debugger_test_read_watchpoint(gb, addr); if (addr < 0xFF00 && gb->dma_cycles) { /* Todo: can we access IO registers during DMA? */ return 0xFF; @@ -512,6 +514,7 @@ static GB_write_function_t * const write_map[] = void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { + GB_debugger_test_write_watchpoint(gb, addr, value); if (addr < 0xFF00 && gb->dma_cycles) { /* Todo: can we access IO registers during DMA? */ return; From b4208be4f4b27bc6558475843f9f4f42ebf6493e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jul 2016 14:37:11 +0300 Subject: [PATCH 0082/1216] Multiple watchpoints were broken --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 38cf57ca..3b09c350 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -965,7 +965,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb) void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - uint16_t index = find_breakpoint(gb, addr); + uint16_t index = find_watchpoint(gb, addr); if (index < gb->n_watchpoints && gb->watchpoints[index].addr == addr) { if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { return; From 78a809795ed0c4020f320ee22fa3dd725325970c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jul 2016 14:45:25 +0300 Subject: [PATCH 0083/1216] Renaming MBC constants --- Core/debugger.c | 2 +- Core/gb.c | 60 ++++++++++++++++++++++++------------------------- Core/gb.h | 12 +++++----- Core/memory.c | 14 ++++++------ 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 3b09c350..f289b551 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -854,7 +854,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); } - if (cartridge->mbc_type == MBC1) { + if (cartridge->mbc_type == GB_MBC1) { GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc_ram_banking? "RAM" : "ROM"); } diff --git a/Core/gb.c b/Core/gb.c index b1b3f8bf..33230ed0 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -17,46 +17,46 @@ 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 + /* MBC RAM BAT. RTC RUMB. */ + { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , false, false, false, false}, // 01h MBC1 + { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM + { GB_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 + { GB_MBC2 , true , false, false, false}, // 05h MBC2 + { GB_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 + { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_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 + { GB_NO_MBC, false, false, false, false}, // 0Bh MMM01 + { GB_NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { GB_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 + { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , false, false, false, false}, // 11h MBC3 + { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM + { GB_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 + { GB_MBC4 , false, false, false, false}, // 15h MBC4 + { GB_MBC4 , true , false, false, false}, // 16h MBC4+RAM + { GB_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 + { GB_MBC5 , false, false, false, false}, // 19h MBC5 + { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_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 + { GB_NO_MBC, false, false, false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 + { GB_NO_MBC, false, false, false, false}, // FEh HuC3 + { GB_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) diff --git a/Core/gb.h b/Core/gb.h index a71b20f9..168a5cfb 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -157,12 +157,12 @@ typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_ typedef struct { enum { - NO_MBC, - MBC1, - MBC2, - MBC3, - MBC4, // Does this exist??? - MBC5, + GB_NO_MBC, + GB_MBC1, + GB_MBC2, + GB_MBC3, + GB_MBC4, // Does this exist??? + GB_MBC5, } mbc_type; bool has_ram; bool has_battery; diff --git a/Core/memory.c b/Core/memory.c index 845e714d..01803fce 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -205,7 +205,7 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->cartridge_type->mbc_type == NO_MBC) return; + if (gb->cartridge_type->mbc_type == GB_NO_MBC) return; switch (addr >> 12) { case 0: case 1: @@ -214,16 +214,16 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 2: bank_low: /* Bank number, lower bits */ - if (gb->cartridge_type->mbc_type == MBC1) { + if (gb->cartridge_type->mbc_type == GB_MBC1) { value &= 0x1F; } - if (gb->cartridge_type->mbc_type != MBC5 && !value) { + if (gb->cartridge_type->mbc_type != GB_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 (gb->cartridge_type->mbc_type != GB_MBC5) goto bank_low; if (value > 1) { GB_log(gb, "Bank overflow: [%x] <- %d\n", addr, value); } @@ -231,7 +231,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case 4: case 5: - if (gb->cartridge_type->mbc_type == MBC1) { + if (gb->cartridge_type->mbc_type == GB_MBC1) { if (gb->mbc_ram_banking) { gb->mbc_ram_bank = value & 0x3; } @@ -249,7 +249,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case 6: case 7: - if (gb->cartridge_type->mbc_type == MBC1) { + if (gb->cartridge_type->mbc_type == GB_MBC1) { value &= 1; if (value & !gb->mbc_ram_banking) { @@ -271,7 +271,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->mbc_rom_bank %= gb->rom_size / 0x4000; } - if (gb->cartridge_type->mbc_type != MBC5 && !gb->mbc_rom_bank) { + if (gb->cartridge_type->mbc_type != GB_MBC5 && !gb->mbc_rom_bank) { gb->mbc_rom_bank = 1; } } From c6bafe3fc34c8ad8401a5d39833a6d93c6d978e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jul 2016 17:34:55 +0300 Subject: [PATCH 0084/1216] Rewrote MBC support --- Core/debugger.c | 2 +- Core/gb.c | 57 ++++--------------- Core/gb.h | 25 ++++++++- Core/mbc.c | 78 ++++++++++++++++++++++++++ Core/mbc.h | 8 +++ Core/memory.c | 145 ++++++++++++++++++------------------------------ 6 files changed, 175 insertions(+), 140 deletions(-) create mode 100644 Core/mbc.c create mode 100644 Core/mbc.h diff --git a/Core/debugger.c b/Core/debugger.c index f289b551..d6573fc0 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -855,7 +855,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); } if (cartridge->mbc_type == GB_MBC1) { - GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc_ram_banking? "RAM" : "ROM"); + GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); } } diff --git a/Core/gb.c b/Core/gb.c index 33230ed0..90b08da9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -14,50 +14,7 @@ #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. */ - { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY - { GB_MBC1 , false, false, false, false}, // 01h MBC1 - { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM - { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY - [5] = - { GB_MBC2 , true , false, false, false}, // 05h MBC2 - { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY - [8] = - { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM - { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY - [0xB] = - // Todo: What are these? - { GB_NO_MBC, false, false, false, false}, // 0Bh MMM01 - { GB_NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM - { GB_NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY - [0xF] = - { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY - { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY - { GB_MBC3 , false, false, false, false}, // 11h MBC3 - { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM - { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY - [0x15] = - // Todo: Do these exist? - { GB_MBC4 , false, false, false, false}, // 15h MBC4 - { GB_MBC4 , true , false, false, false}, // 16h MBC4+RAM - { GB_MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY - [0x19] = - { GB_MBC5 , false, false, false, false}, // 19h MBC5 - { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM - { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY - { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE - { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM - { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY - [0xFC] = - // Todo: What are these? - { GB_NO_MBC, false, false, false, false}, // FCh POCKET CAMERA - { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 - { GB_NO_MBC, false, false, false, false}, // FEh HuC3 - { GB_NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY -}; +#include "mbc.h" void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { @@ -131,7 +88,7 @@ void GB_init(GB_gameboy_t *gb) 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 + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_JOYP] = 0xF; } @@ -155,7 +112,7 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->last_vblank = clock(); gb->cgb_ram_bank = 1; gb->input_callback = default_input_callback; - gb->cartridge_type = &cart_defs[0]; // Default cartridge type + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_JOYP] = 0xF; } @@ -197,12 +154,18 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) if (!f) return errno; fseek(f, 0, SEEK_END); gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */ + /* And then round to a power of two */ + while (gb->rom_size & (gb->rom_size - 1)) { + /* I promise this works. */ + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } 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]]; + gb->cartridge_type = &GB_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]]; diff --git a/Core/gb.h b/Core/gb.h index 168a5cfb..aecb463e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -226,7 +226,30 @@ typedef struct GB_gameboy_s { uint8_t mbc_ram_bank; uint32_t mbc_ram_size; bool mbc_ram_enable; - bool mbc_ram_banking; + union { + struct { + uint8_t bank_low:5; + uint8_t bank_high:3; + uint8_t mode:1; + } mbc1; + + struct { + uint8_t rom_bank:4; + } mbc2; + + struct { + uint8_t rom_bank:7; + uint8_t padding:1; + uint8_t ram_bank:4; + } mbc3; + + struct { + uint8_t rom_bank_low; + uint8_t rom_bank_high:1; + uint8_t ram_bank:4; + } mbc5; + }; + uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ ); diff --git a/Core/mbc.c b/Core/mbc.c new file mode 100644 index 00000000..c03ce7e6 --- /dev/null +++ b/Core/mbc.c @@ -0,0 +1,78 @@ +#include +#include "gb.h" + +const GB_cartridge_t GB_cart_defs[256] = { + // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type + /* MBC RAM BAT. RTC RUMB. */ + { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , false, false, false, false}, // 01h MBC1 + { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY + [5] = + { GB_MBC2 , true , false, false, false}, // 05h MBC2 + { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY + [8] = + { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + [0xB] = + // Todo: What are these? + { GB_NO_MBC, false, false, false, false}, // 0Bh MMM01 + { GB_NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { GB_NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + [0xF] = + { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , false, false, false, false}, // 11h MBC3 + { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY + [0x15] = + // Todo: Do these exist? + { GB_MBC4 , false, false, false, false}, // 15h MBC4 + { GB_MBC4 , true , false, false, false}, // 16h MBC4+RAM + { GB_MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY + [0x19] = + { GB_MBC5 , false, false, false, false}, // 19h MBC5 + { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0xFC] = + // Todo: What are these? + { GB_NO_MBC, false, false, false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 + { GB_NO_MBC, false, false, false, false}, // FEh HuC3 + { GB_NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY +}; + +void GB_update_mbc_mappings(GB_gameboy_t *gb) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: case GB_MBC4: return; + case GB_MBC1: + /* Standard MBC1 wiring: */ + if (gb->mbc1.mode == 0) { + gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); + gb->mbc_ram_bank = 0; + } + else { + gb->mbc_rom_bank = gb->mbc1.bank_low; + gb->mbc_ram_bank = gb->mbc1.bank_high; + } + break; + case GB_MBC2: + gb->mbc_rom_bank = gb->mbc2.rom_bank; + break; + case GB_MBC3: + gb->mbc_rom_bank = gb->mbc3.rom_bank; + gb->mbc_ram_bank = gb->mbc3.ram_bank; + break; + case GB_MBC5: + gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); + gb->mbc_ram_bank = gb->mbc5.ram_bank; + break; + } + if (gb->mbc_rom_bank == 0 && gb->cartridge_type->mbc_type != GB_MBC5) { + gb->mbc_rom_bank = 1; + } +} \ No newline at end of file diff --git a/Core/mbc.h b/Core/mbc.h new file mode 100644 index 00000000..7c64fcf0 --- /dev/null +++ b/Core/mbc.h @@ -0,0 +1,8 @@ +#ifndef MBC_h +#define MBC_h +#include "gb.h" + +extern const GB_cartridge_t GB_cart_defs[256]; +void GB_update_mbc_mappings(GB_gameboy_t *gb); + +#endif /* MBC_h */ diff --git a/Core/memory.c b/Core/memory.c index 01803fce..647cd6f5 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -5,6 +5,7 @@ #include "display.h" #include "memory.h" #include "debugger.h" +#include "mbc.h" typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); @@ -22,15 +23,14 @@ static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) if (!gb->rom_size) { return 0xFF; } - return gb->rom[addr]; + unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; } static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) { - if (gb->mbc_rom_bank >= gb->rom_size / 0x4000) { - return 0xFF; - } - return gb->rom[(addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000]; + unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; } static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) @@ -43,22 +43,19 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { + if (!gb->mbc_ram_enable) return 0xFF; + 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); + + if (!gb->mbc_ram) { 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]; + + return gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; } static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) @@ -205,75 +202,41 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->cartridge_type->mbc_type == GB_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 == GB_MBC1) { - value &= 0x1F; - } - if (gb->cartridge_type->mbc_type != GB_MBC5 && !value) { - value++; - } - gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x100) | value; - break; - case 3: - if (gb->cartridge_type->mbc_type != GB_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 == GB_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; - /* Some games assume banks wrap around. We can do this if RAM size is a power of two */ - if (gb->mbc_ram_bank >= gb->mbc_ram_size / 0x2000 && (gb->mbc_ram_size & (gb->mbc_ram_size - 1)) == 0 && gb->mbc_ram_size != 0) { - gb->mbc_ram_bank %= gb->mbc_ram_size / 0x2000; - } + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: case GB_MBC4: return; + case GB_MBC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->mbc1.mode = value; break; } break; - case 6: - case 7: - if (gb->cartridge_type->mbc_type == GB_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; + case GB_MBC2: + switch (addr & 0xF000) { + /* Todo: is this correct? */ + case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value & 0x1; break; + case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; + } + break; + case GB_MBC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; + case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break; + case 0x6000: case 0x7000: /* Todo: Clock latching support */ break; + } + break; + case GB_MBC5: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: gb->mbc5.rom_bank_low = value; break; + case 0x3000: gb->mbc5.rom_bank_high = value; break; + case 0x4000: case 0x5000: gb->mbc5.ram_bank = value; break; } break; } - - /* Some games assume banks wrap around. We can do this if ROM size is a power of two */ - if (gb->mbc_rom_bank >= gb->rom_size / 0x4000 && (gb->rom_size & (gb->rom_size - 1)) == 0 && gb->rom_size != 0) { - gb->mbc_rom_bank %= gb->rom_size / 0x4000; - } - - if (gb->cartridge_type->mbc_type != GB_MBC5 && !gb->mbc_rom_bank) { - gb->mbc_rom_bank = 1; - } + GB_update_mbc_mappings(gb); } static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) @@ -287,23 +250,23 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { - /* RTC write*/ + if (!gb->mbc_ram_enable) return; + + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + /* RTC read */ gb->rtc_data[gb->mbc_ram_bank - 8] = value; gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */ + } + + if (gb->cartridge_type->mbc_type == GB_MBC2) { + value &= 0xF; + } + + if (!gb->mbc_ram) { 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; + + gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; } static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) From bd7f8f2555fa139404b2526fd3d76e402e710ca8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jul 2016 19:25:13 +0300 Subject: [PATCH 0085/1216] Support for an alternative MBC1 wiring, should solve most N-in-1 carts (Issue #3) --- Core/debugger.c | 7 ++++++- Core/gb.c | 7 +------ Core/gb.h | 4 ++++ Core/mbc.c | 51 ++++++++++++++++++++++++++++++++++++++++++++----- Core/mbc.h | 2 +- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index d6573fc0..59399adc 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -854,9 +854,14 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); } - if (cartridge->mbc_type == GB_MBC1) { + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_MBC1M_WIRING) { + GB_log(gb, "MBC1 uses MBC1M wiring. \n"); + GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); + GB_log(gb, "MBC1 multicart banking mode is %s\n", gb->mbc1.mode == 1 ? "enabled" : "disabled"); + } } else { diff --git a/Core/gb.c b/Core/gb.c index 90b08da9..85279af6 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -165,12 +165,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ fread(gb->rom, gb->rom_size, 1, f); fclose(f); - gb->cartridge_type = &GB_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); - } + GB_configure_cart(gb); return 0; } diff --git a/Core/gb.h b/Core/gb.h index aecb463e..d92f496e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -312,6 +312,10 @@ typedef struct GB_gameboy_s { uint8_t *rom; uint32_t rom_size; const GB_cartridge_t *cartridge_type; + enum { + GB_STANDARD_MBC1_WIRING, + GB_MBC1M_WIRING, + } mbc1_wiring; /* Various RAMs */ uint8_t *ram; diff --git a/Core/mbc.c b/Core/mbc.c index c03ce7e6..fddccb47 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -1,4 +1,6 @@ #include +#include +#include #include "gb.h" const GB_cartridge_t GB_cart_defs[256] = { @@ -50,14 +52,32 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) switch (gb->cartridge_type->mbc_type) { case GB_NO_MBC: case GB_MBC4: return; case GB_MBC1: - /* Standard MBC1 wiring: */ + /* Todo: some obscure behaviors of MBC1 are not supported. See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */ if (gb->mbc1.mode == 0) { - gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); - gb->mbc_ram_bank = 0; + switch (gb->mbc1_wiring) { + case GB_STANDARD_MBC1_WIRING: + gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); + gb->mbc_ram_bank = 0; + break; + + case GB_MBC1M_WIRING: + gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4); + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } } else { - gb->mbc_rom_bank = gb->mbc1.bank_low; - gb->mbc_ram_bank = gb->mbc1.bank_high; + switch (gb->mbc1_wiring) { + case GB_STANDARD_MBC1_WIRING: + gb->mbc_rom_bank = gb->mbc1.bank_low; + gb->mbc_ram_bank = gb->mbc1.bank_high; + break; + + case GB_MBC1M_WIRING: + gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4); + gb->mbc_rom0_bank = gb->mbc1.bank_high << 4; + gb->mbc_ram_bank = 0; + } } break; case GB_MBC2: @@ -75,4 +95,25 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) if (gb->mbc_rom_bank == 0 && gb->cartridge_type->mbc_type != GB_MBC5) { gb->mbc_rom_bank = 1; } +} + +void GB_configure_cart(GB_gameboy_t *gb) +{ + gb->cartridge_type = &GB_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); + } + + /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these). + See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */ + + /* Attempt to "guess" wiring */ + if (gb->cartridge_type->mbc_type == GB_MBC1) { + if (gb->rom_size >= 0x44000 && memcmp(gb->rom + 0x104, gb->rom + 0x40104, 0x30) == 0) { + gb->mbc1_wiring = GB_MBC1M_WIRING; + } + } } \ No newline at end of file diff --git a/Core/mbc.h b/Core/mbc.h index 7c64fcf0..cd992b05 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -4,5 +4,5 @@ extern const GB_cartridge_t GB_cart_defs[256]; void GB_update_mbc_mappings(GB_gameboy_t *gb); - +void GB_configure_cart(GB_gameboy_t *gb); #endif /* MBC_h */ From e9b3a3817171c92b66595f21d588a9c79d9e15a5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 12 Jul 2016 23:30:18 +0300 Subject: [PATCH 0086/1216] Uniform syntax for debugger input and output --- Core/debugger.c | 112 +++++++++++++++++++++------------------- Core/z80_disassembler.c | 54 +++++++++---------- 2 files changed, 86 insertions(+), 80 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 59399adc..5b28decc 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -195,9 +195,9 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, } // Registers - if (string[0] == '$') { - if (length == 2) { - switch (string[1]) { + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { 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]}; @@ -208,14 +208,14 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, 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}; + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; + case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; } } GB_log(gb, "Unknown register: %.*s\n", length, string); @@ -318,9 +318,9 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, // Not an expression - must be a register or a literal // Registers - if (string[0] == '$') { - if (length == 2) { - switch (string[1]) { + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { 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; @@ -331,27 +331,27 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, 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; + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') return gb->registers[GB_REGISTER_AF]; + case 'b': if (string[1] == 'c') return gb->registers[GB_REGISTER_BC]; + case 'd': if (string[1] == 'e') return gb->registers[GB_REGISTER_DE]; + case 'h': if (string[1] == 'l') return gb->registers[GB_REGISTER_HL]; + case 's': if (string[1] == 'p') return gb->registers[GB_REGISTER_SP]; + case 'p': if (string[1] == 'c') return gb->pc; } } - else if (length == 4) { - if (watchpoint_address && memcmp(string, "$old", 4) == 0) { + else if (length == 3) { + if (watchpoint_address && memcmp(string, "old", 3) == 0) { return GB_read_memory(gb, *watchpoint_address); } - if (watchpoint_new_value && memcmp(string, "$new", 4) == 0) { + if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { return *watchpoint_new_value; } /* $new is identical to $old in read conditions */ - if (watchpoint_address && memcmp(string, "$new", 4) == 0) { + if (watchpoint_address && memcmp(string, "new", 3) == 0) { return GB_read_memory(gb, *watchpoint_address); } } @@ -361,7 +361,13 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } char *end; - uint16_t literal = (uint16_t) (strtol(string, &end, 16)); + int base = 10; + if (string[0] == '$') { + string++; + base = 16; + length--; + } + uint16_t literal = (uint16_t) (strtol(string, &end, base)); if (end != string + length) { GB_log(gb, "Failed to parse: %.*s\n", length, string); *error = true; @@ -453,12 +459,12 @@ static bool registers(GB_gameboy_t *gb, char *arguments) return true; } - 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, "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/%u\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles); GB_log(gb, "Display Controller: LY = %d/%u\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456); return true; @@ -510,7 +516,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) uint16_t index = find_breakpoint(gb, result); if (index < gb->n_breakpoints && gb->breakpoints[index].addr == result) { - GB_log(gb, "Breakpoint already set at %04x\n", result); + GB_log(gb, "Breakpoint already set at $%04x\n", result); if (!gb->breakpoints[index].condition && condition) { GB_log(gb, "Added condition to breakpoint\n"); gb->breakpoints[index].condition = strdup(condition); @@ -539,7 +545,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) } gb->n_breakpoints++; - GB_log(gb, "Breakpoint set at %04x\n", result); + GB_log(gb, "Breakpoint set at $%04x\n", result); return true; } @@ -568,7 +574,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments) uint16_t index = find_breakpoint(gb, result); if (index >= gb->n_breakpoints || gb->breakpoints[index].addr != result) { - GB_log(gb, "No breakpoint set at %04x\n", result); + GB_log(gb, "No breakpoint set at $%04x\n", result); return true; } @@ -580,7 +586,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments) gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); - GB_log(gb, "Breakpoint removed from %04x\n", result); + GB_log(gb, "Breakpoint removed from $%04x\n", result); return true; } @@ -653,7 +659,7 @@ print_usage: uint16_t index = find_watchpoint(gb, result); if (index < gb->n_watchpoints && gb->watchpoints[index].addr == result) { - GB_log(gb, "Watchpoint already set at %04x\n", result); + GB_log(gb, "Watchpoint already set at $%04x\n", result); if (!gb->watchpoints[index].flags != flags) { GB_log(gb, "Modified watchpoint type\n"); gb->watchpoints[index].flags = flags; @@ -687,7 +693,7 @@ print_usage: } gb->n_watchpoints++; - GB_log(gb, "Watchpoint set at %04x\n", result); + GB_log(gb, "Watchpoint set at $%04x\n", result); return true; } @@ -716,7 +722,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) uint16_t index = find_watchpoint(gb, result); if (index >= gb->n_watchpoints || gb->watchpoints[index].addr != result) { - GB_log(gb, "No watchpoint set at %04x\n", result); + GB_log(gb, "No watchpoint set at $%04x\n", result); return true; } @@ -728,7 +734,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) gb->n_watchpoints--; gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); - GB_log(gb, "Watchpoint removed from %04x\n", result); + GB_log(gb, "Watchpoint removed from $%04x\n", result); return true; } @@ -746,10 +752,10 @@ static bool list(GB_gameboy_t *gb, char *arguments) GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); for (uint16_t i = 0; i < gb->n_breakpoints; i++) { if (gb->breakpoints[i].condition) { - GB_log(gb, " %d. %04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition); + GB_log(gb, " %d. $%04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition); } else { - GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i].addr); + GB_log(gb, " %d. $%04x\n", i + 1, gb->breakpoints[i].addr); } } } @@ -761,13 +767,13 @@ static bool list(GB_gameboy_t *gb, char *arguments) GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); for (uint16_t i = 0; i < gb->n_watchpoints; i++) { if (gb->watchpoints[i].condition) { - GB_log(gb, " %d. %04x (%c%c, Condition: %s)\n", i + 1, gb->watchpoints[i].addr, + GB_log(gb, " %d. $%04x (%c%c, Condition: %s)\n", i + 1, gb->watchpoints[i].addr, (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', gb->watchpoints[i].condition); } else { - GB_log(gb, " %d. %04x (%c%c)\n", i + 1, gb->watchpoints[i].addr, + GB_log(gb, " %d. $%04x (%c%c)\n", i + 1, gb->watchpoints[i].addr, (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); } @@ -807,7 +813,7 @@ static bool print(GB_gameboy_t *gb, char *arguments) bool error; uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { - GB_log(gb, "=%04x\n", result); + GB_log(gb, "=$%04x\n", result); } return true; } @@ -959,8 +965,8 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb) } else { if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { - GB_log(gb, "Stack leak detected for function %04x!\n", gb->addr_for_call_depth[gb->debug_call_depth]); - GB_log(gb, "SP is %04x, should be %04x.\n", gb->registers[GB_REGISTER_SP], + GB_log(gb, "Stack leak detected for function $%04x!\n", gb->addr_for_call_depth[gb->debug_call_depth]); + GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], gb->sp_for_call_depth[gb->debug_call_depth]); gb->debug_stopped = true; } @@ -977,7 +983,7 @@ void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t } if (!gb->watchpoints[index].condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%04x] = %02x\n", addr, value); + GB_log(gb, "Watchpoint: [$%04x] = $%02x\n", addr, value); return; } bool error; @@ -990,7 +996,7 @@ void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t } if (condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%04x] = %02x\n", addr, value); + GB_log(gb, "Watchpoint: [$%04x] = $%02x\n", addr, value); } } } @@ -1004,7 +1010,7 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) } if (!gb->watchpoints[index].condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%04x]\n", addr); + GB_log(gb, "Watchpoint: [$%04x]\n", addr); return; } bool error; @@ -1017,7 +1023,7 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) } if (condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%04x]\n", addr); + GB_log(gb, "Watchpoint: [$%04x]\n", addr); } } } @@ -1040,7 +1046,7 @@ next_command: } if (!gb->debug_stopped && should_break(gb, gb->pc)) { gb->debug_stopped = true; - GB_log(gb, "Breakpoint: PC = %04x\n", gb->pc); + GB_log(gb, "Breakpoint: PC = $%04x\n", gb->pc); GB_cpu_disassemble(gb, gb->pc, 5); } if (gb->debug_stopped) { diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c index 14a059f7..c312a908 100644 --- a/Core/z80_disassembler.c +++ b/Core/z80_disassembler.c @@ -9,7 +9,7 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { - GB_log(gb, ".BYTE %02x\n", opcode); + GB_log(gb, ".BYTE $%02x\n", opcode); (*pc)++; } @@ -34,7 +34,7 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; value = GB_read_memory(gb, (*pc)++); value |= GB_read_memory(gb, (*pc)++) << 8; - GB_log(gb, "LD %s, %04x\n", register_names[register_id], value); + GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value); } static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -72,7 +72,7 @@ static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) uint8_t register_id; (*pc)++; register_id = ((opcode >> 4) + 1) & 0x03; - GB_log(gb, "LD %c, %02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++)); + GB_log(gb, "LD %c, $%02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++)); } static void rlca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -92,7 +92,7 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){ (*pc)++; addr = GB_read_memory(gb, (*pc)++); addr |= GB_read_memory(gb, (*pc)++) << 8; - GB_log(gb, "LD [%04x], sp\n", addr); + GB_log(gb, "LD [$%04x], sp\n", addr); } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -137,7 +137,7 @@ static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) uint8_t register_id; register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; - GB_log(gb, "LD %c, %02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++)); + GB_log(gb, "LD %c, $%02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++)); } static void rrca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -155,7 +155,7 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %04x\n", *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1); + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1); (*pc)++; } @@ -178,7 +178,7 @@ static const char *condition_code(uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %04x\n", condition_code(opcode), *pc + (int8_t)GB_read_memory(gb, (*pc)) + 1); + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), *pc + (int8_t)GB_read_memory(gb, (*pc)) + 1); (*pc)++; } @@ -245,7 +245,7 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "LD [hl], %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "LD [hl], $%02x\n", GB_read_memory(gb, (*pc)++)); } static const char *get_src_name(uint8_t opcode) @@ -356,21 +356,21 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "JP %04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "JP $%04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "CALL %s, %04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } @@ -384,55 +384,55 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "ADD %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "ADD $%02x\n", GB_read_memory(gb, (*pc)++)); } static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "ADC %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "ADC $%02x\n", GB_read_memory(gb, (*pc)++)); } static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "SUB %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "SUB $%02x\n", GB_read_memory(gb, (*pc)++)); } static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "LBC %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "LBC $%02x\n", GB_read_memory(gb, (*pc)++)); } static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "AND %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "AND $%02x\n", GB_read_memory(gb, (*pc)++)); } static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "XOR %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "XOR $%02x\n", GB_read_memory(gb, (*pc)++)); } static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "OR %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "OR $%02x\n", GB_read_memory(gb, (*pc)++)); } static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "CP %02x\n", GB_read_memory(gb, (*pc)++)); + GB_log(gb, "CP $%02x\n", GB_read_memory(gb, (*pc)++)); } static void rst(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "RST %02x\n", opcode ^ 0xC7); + GB_log(gb, "RST $%02x\n", opcode ^ 0xC7); } @@ -451,7 +451,7 @@ static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "CALL %04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "CALL $%04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } @@ -459,14 +459,14 @@ static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; uint8_t temp = GB_read_memory(gb, (*pc)++); - GB_log(gb, "LDH [%02x], a\n", temp); + GB_log(gb, "LDH [$%02x], a\n", temp); } static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; uint8_t temp = GB_read_memory(gb, (*pc)++); - GB_log(gb, "LDH a, [%02x]\n", temp); + GB_log(gb, "LDH a, [$%02x]\n", temp); } static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -485,7 +485,7 @@ static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; int8_t temp = GB_read_memory(gb, (*pc)++); - GB_log(gb, "ADD SP, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); + GB_log(gb, "ADD SP, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); } static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -497,14 +497,14 @@ static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "LD [%04x], a\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "LD [$%04x], a\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "LD a, [%04x]\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + GB_log(gb, "LD a, [$%04x]\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); (*pc) += 2; } @@ -524,7 +524,7 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; int8_t temp = GB_read_memory(gb, (*pc)++); - GB_log(gb, "LD hl, sp, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); + GB_log(gb, "LD hl, sp, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); } static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) From d49404d2480148601896871e4f04228245558a90 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 Jul 2016 23:00:50 +0300 Subject: [PATCH 0087/1216] Debugger can now read .sym files, and display them. (No expression support yet) --- Cocoa/Document.m | 3 + Core/debugger.c | 159 ++++++++++++++++++++++++++++++++-------- Core/debugger.h | 4 +- Core/gb.c | 5 ++ Core/gb.h | 3 + Core/symbol_hash.c | 66 +++++++++++++++++ Core/symbol_hash.h | 23 ++++++ Core/z80_disassembler.c | 117 ++++++++++++++++++++++++++--- 8 files changed, 338 insertions(+), 42 deletions(-) create mode 100644 Core/symbol_hash.c create mode 100644 Core/symbol_hash.h diff --git a/Cocoa/Document.m b/Cocoa/Document.m index bc276cf5..b2786302 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -3,6 +3,7 @@ #include "Document.h" #include "AppDelegate.h" #include "gb.h" +#include "debugger.h" @interface Document () { @@ -204,6 +205,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) } GB_load_rom(&gb, [fileName UTF8String]); GB_load_battery(&gb, [[[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + GB_debugger_load_symbol_file(&gb, [[[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]); + return YES; } diff --git a/Core/debugger.c b/Core/debugger.c index 5b28decc..04d7e978 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -34,6 +34,40 @@ struct GB_watchpoint_s { uint8_t flags; }; + +static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) +{ + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); + + if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + if (!symbol) { + sprintf(output, "$%04x", value); + } + + else if (symbol->addr == value) { + if (prefer_name) { + sprintf(output, "%s ($%04x)", symbol->name, value); + } + else { + sprintf(output, "$%04x (%s)", value, symbol->name); + } + } + + else { + if (prefer_name) { + sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + } + else { + sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + } + } + return output; +} + static uint16_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) { /* Not used until we add support for operators like += */ @@ -459,12 +493,13 @@ static bool registers(GB_gameboy_t *gb, char *arguments) return true; } - 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, "AF = $%04x\n", gb->registers[GB_REGISTER_AF]); /* AF can't really be an address */ + GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); + GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); + GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); + GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); + GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); + GB_log(gb, "TIMA = %d/%u\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles); GB_log(gb, "Display Controller: LY = %d/%u\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456); return true; @@ -516,7 +551,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) uint16_t index = find_breakpoint(gb, result); if (index < gb->n_breakpoints && gb->breakpoints[index].addr == result) { - GB_log(gb, "Breakpoint already set at $%04x\n", result); + GB_log(gb, "Breakpoint already set at %s\n", value_to_string(gb, result, true)); if (!gb->breakpoints[index].condition && condition) { GB_log(gb, "Added condition to breakpoint\n"); gb->breakpoints[index].condition = strdup(condition); @@ -545,7 +580,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) } gb->n_breakpoints++; - GB_log(gb, "Breakpoint set at $%04x\n", result); + GB_log(gb, "Breakpoint set at %s\n", value_to_string(gb, result, true)); return true; } @@ -574,7 +609,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments) uint16_t index = find_breakpoint(gb, result); if (index >= gb->n_breakpoints || gb->breakpoints[index].addr != result) { - GB_log(gb, "No breakpoint set at $%04x\n", result); + GB_log(gb, "No breakpoint set at %s\n", value_to_string(gb, result, true)); return true; } @@ -586,7 +621,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments) gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); - GB_log(gb, "Breakpoint removed from $%04x\n", result); + GB_log(gb, "Breakpoint removed from %s\n", value_to_string(gb, result, true)); return true; } @@ -659,7 +694,7 @@ print_usage: uint16_t index = find_watchpoint(gb, result); if (index < gb->n_watchpoints && gb->watchpoints[index].addr == result) { - GB_log(gb, "Watchpoint already set at $%04x\n", result); + GB_log(gb, "Watchpoint already set at %s\n", value_to_string(gb, result, true)); if (!gb->watchpoints[index].flags != flags) { GB_log(gb, "Modified watchpoint type\n"); gb->watchpoints[index].flags = flags; @@ -693,7 +728,7 @@ print_usage: } gb->n_watchpoints++; - GB_log(gb, "Watchpoint set at $%04x\n", result); + GB_log(gb, "Watchpoint set at %s\n", value_to_string(gb, result, true)); return true; } @@ -722,7 +757,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) uint16_t index = find_watchpoint(gb, result); if (index >= gb->n_watchpoints || gb->watchpoints[index].addr != result) { - GB_log(gb, "No watchpoint set at $%04x\n", result); + GB_log(gb, "No watchpoint set at %s\n", value_to_string(gb, result, true)); return true; } @@ -734,7 +769,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) gb->n_watchpoints--; gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); - GB_log(gb, "Watchpoint removed from $%04x\n", result); + GB_log(gb, "Watchpoint removed from %s\n", value_to_string(gb, result, true)); return true; } @@ -752,10 +787,12 @@ static bool list(GB_gameboy_t *gb, char *arguments) GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); for (uint16_t i = 0; i < gb->n_breakpoints; i++) { if (gb->breakpoints[i].condition) { - GB_log(gb, " %d. $%04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition); + GB_log(gb, " %d. %s (Condition: %s)\n", i + 1, + value_to_string(gb, gb->breakpoints[i].addr, true), + gb->breakpoints[i].condition); } else { - GB_log(gb, " %d. $%04x\n", i + 1, gb->breakpoints[i].addr); + GB_log(gb, " %d. %s\n", i + 1, value_to_string(gb, gb->breakpoints[i].addr, true)); } } } @@ -767,15 +804,15 @@ static bool list(GB_gameboy_t *gb, char *arguments) GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); for (uint16_t i = 0; i < gb->n_watchpoints; i++) { if (gb->watchpoints[i].condition) { - GB_log(gb, " %d. $%04x (%c%c, Condition: %s)\n", i + 1, gb->watchpoints[i].addr, - (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', - (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', - gb->watchpoints[i].condition); + GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, value_to_string(gb, gb->watchpoints[i].addr, true), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', + gb->watchpoints[i].condition); } else { - GB_log(gb, " %d. $%04x (%c%c)\n", i + 1, gb->watchpoints[i].addr, - (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', - (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); + GB_log(gb, " %d. %s (%c%c)\n", i + 1, value_to_string(gb, gb->watchpoints[i].addr, true), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); } } } @@ -813,7 +850,7 @@ static bool print(GB_gameboy_t *gb, char *arguments) bool error; uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { - GB_log(gb, "=$%04x\n", result); + GB_log(gb, "=%s\n", value_to_string(gb, result, false)); } return true; } @@ -965,7 +1002,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb) } else { if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { - GB_log(gb, "Stack leak detected for function $%04x!\n", gb->addr_for_call_depth[gb->debug_call_depth]); + GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], gb->sp_for_call_depth[gb->debug_call_depth]); gb->debug_stopped = true; @@ -983,7 +1020,7 @@ void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t } if (!gb->watchpoints[index].condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [$%04x] = $%02x\n", addr, value); + GB_log(gb, "Watchpoint: [%s] = $%02x\n", value_to_string(gb, addr, true), value); return; } bool error; @@ -996,7 +1033,7 @@ void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t } if (condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [$%04x] = $%02x\n", addr, value); + GB_log(gb, "Watchpoint: [%s] = $%02x\n", value_to_string(gb, addr, true), value); } } } @@ -1010,7 +1047,7 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) } if (!gb->watchpoints[index].condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [$%04x]\n", addr); + GB_log(gb, "Watchpoint: [%s]\n", value_to_string(gb, addr, true)); return; } bool error; @@ -1023,7 +1060,7 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) } if (condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [$%04x]\n", addr); + GB_log(gb, "Watchpoint: [%s]\n", value_to_string(gb, addr, true)); } } } @@ -1046,7 +1083,7 @@ next_command: } if (!gb->debug_stopped && should_break(gb, gb->pc)) { gb->debug_stopped = true; - GB_log(gb, "Breakpoint: PC = $%04x\n", gb->pc); + GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_cpu_disassemble(gb, gb->pc, 5); } if (gb->debug_stopped) { @@ -1084,4 +1121,66 @@ next_command: free(input); } +} + +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return; + + char *line = NULL; + size_t size = 0; + size_t length = 0; + while ((length = getline(&line, &size, f)) != -1) { + for (unsigned i = 0; i < length; i++) { + if (line[i] == ';' || line[i] == '\n' || line[i] == '\r') { + line[i] = 0; + length = i; + break; + } + } + if (length == 0) continue; + + unsigned int bank, address; + char symbol[length]; + + if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { + if (!gb->bank_symbols[bank]) { + gb->bank_symbols[bank] = GB_map_alloc(); + } + GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + } + } + free(line); + fclose(f); +} + +const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) +{ + unsigned char bank = 0; + if (addr < 0x4000) { + bank = gb->mbc_rom0_bank; + } + else if (addr < 0x8000) { + bank = gb->mbc_rom_bank; + } + + else if (addr < 0xD000) { + bank = 0; + } + else if (addr < 0xE000) { + bank = gb->cgb_ram_bank; + } + + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); + if (symbol) return symbol; + if (bank != 0) GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ + return NULL; +} + +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) +{ + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); + if (symbol && symbol->addr == addr) return symbol->name; + return NULL; } \ No newline at end of file diff --git a/Core/debugger.h b/Core/debugger.h index 9f0f9220..78f60cee 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -7,5 +7,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb); void GB_debugger_ret_hook(GB_gameboy_t *gb); void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); - +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); +const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); #endif /* debugger_h */ diff --git a/Core/gb.c b/Core/gb.c index 85279af6..4d75a2ed 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -137,6 +137,11 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } + for (unsigned char i = 0; i--;) { + if (gb->bank_symbols[i]) { + GB_map_free(gb->bank_symbols[i]); + } + } } int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) diff --git a/Core/gb.h b/Core/gb.h index d92f496e..8ec1a2fa 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -6,6 +6,7 @@ #include #include "apu.h" #include "save_struct.h" +#include "symbol_hash.h" #define GB_STRUCT_VERSION 9 @@ -359,6 +360,8 @@ typedef struct GB_gameboy_s { uint16_t n_watchpoints; struct GB_watchpoint_s *watchpoints; + /* Symbol table */ + GB_symbol_map_t *bank_symbols[0x100]; /* Misc */ bool turbo; diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c new file mode 100644 index 00000000..7c4532a9 --- /dev/null +++ b/Core/symbol_hash.c @@ -0,0 +1,66 @@ +#include "gb.h" + +static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) +{ + if (!map->symbols) { + return 0; + } + ssize_t min = 0; + ssize_t max = map->n_symbols; + while (min < max) { + size_t pivot = (min + max) / 2; + if (map->symbols[pivot].addr == addr) return pivot; + if (map->symbols[pivot].addr > addr) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (size_t) min; +} + +void GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) +{ + size_t index = GB_map_find_symbol_index(map, addr); + + if (index < map->n_symbols && map->symbols[index].addr == addr) return; + + map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0])); + memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0])); + map->symbols[index].addr = addr; + map->symbols[index].name = strdup(name); + map->n_symbols++; +} + +const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) +{ + size_t index = GB_map_find_symbol_index(map, addr); + if (index < map->n_symbols && map->symbols[index].addr != addr) { + index--; + } + if (index < map->n_symbols) { + return &map->symbols[index]; + } + return NULL; +} + +GB_symbol_map_t *GB_map_alloc(void) +{ + GB_symbol_map_t *map = malloc(sizeof(*map)); + memset(map, 0, sizeof(*map)); + return map; +} + +void GB_map_free(GB_symbol_map_t *map) +{ + for (unsigned char i = 0; i < map->n_symbols; i++) { + free(map->symbols[i].name); + } + + if (map->symbols) { + free(map->symbols); + } + + free(map); +} \ No newline at end of file diff --git a/Core/symbol_hash.h b/Core/symbol_hash.h new file mode 100644 index 00000000..8044e3f5 --- /dev/null +++ b/Core/symbol_hash.h @@ -0,0 +1,23 @@ +#ifndef symbol_hash_h +#define symbol_hash_h + +#include +#include + +typedef struct { + uint16_t addr; + char *name; +} GB_bank_symbol_t; + + +typedef struct { + GB_bank_symbol_t *symbols; + size_t n_symbols; +} GB_symbol_map_t; + +void GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); +const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); +GB_symbol_map_t *GB_map_alloc(void); +void GB_map_free(GB_symbol_map_t *map); + +#endif /* symbol_hash_h */ diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c index c312a908..8bfada27 100644 --- a/Core/z80_disassembler.c +++ b/Core/z80_disassembler.c @@ -3,6 +3,7 @@ #include "z80_cpu.h" #include "memory.h" #include "gb.h" +#include "debugger.h" typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); @@ -34,7 +35,13 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; value = GB_read_memory(gb, (*pc)++); value |= GB_read_memory(gb, (*pc)++) << 8; - GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value); + const char *symbol = GB_debugger_name_for_address(gb, value); + if (symbol) { + GB_log(gb, "LD %s, %s ; =$%04x\n", register_names[register_id], symbol, value); + } + else { + GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value); + } } static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -92,7 +99,13 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){ (*pc)++; addr = GB_read_memory(gb, (*pc)++); addr |= GB_read_memory(gb, (*pc)++) << 8; - GB_log(gb, "LD [$%04x], sp\n", addr); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD [%s], sp ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD [$%04x], sp\n", addr); + } } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) @@ -155,7 +168,14 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1); + uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %s ; =$%04x\n", symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", addr); + } (*pc)++; } @@ -178,7 +198,14 @@ static const char *condition_code(uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), *pc + (int8_t)GB_read_memory(gb, (*pc)) + 1); + uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), addr); + } (*pc)++; } @@ -356,21 +383,42 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), addr); + } (*pc) += 2; } static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "JP $%04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "JP %s ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "JP $%04x\n", addr); + } (*pc) += 2; } static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "CALL %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), addr); + } (*pc) += 2; } @@ -451,7 +499,14 @@ static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "CALL $%04x\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "CALL %s ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "CALL $%04x\n", addr); + } (*pc) += 2; } @@ -497,14 +552,28 @@ static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "LD [$%04x], a\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD [%s], a ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD [$%04x], a\n", addr); + } (*pc) += 2; } static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "LD a, [$%04x]\n", GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8)); + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD a, [%s] ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD a, [$%04x]\n", addr); + } (*pc) += 2; } @@ -668,10 +737,36 @@ static GB_opcode_t *opcodes[256] = { ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, }; + + void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count) { + const GB_bank_symbol_t *function_symbol = GB_debugger_find_symbol(gb, pc); + + if (function_symbol && pc - function_symbol->addr > 0x1000) { + function_symbol = NULL; + } + + if (function_symbol && pc != function_symbol->addr) { + GB_log(gb, "%s:\n", function_symbol->name); + } + + uint16_t current_function = function_symbol? function_symbol->addr : 0; + while (count--) { - GB_log(gb, "%s%04x: ", pc == gb->pc? "-> ": " ", pc); + function_symbol = GB_debugger_find_symbol(gb, pc); + if (function_symbol && function_symbol->addr == pc) { + if (current_function != function_symbol->addr) { + GB_log(gb, "\n"); + } + GB_log(gb, "%s:\n", function_symbol->name); + } + if (function_symbol) { + GB_log(gb, "%s%04x <+%03x>: ", pc == gb->pc? " ->": " ", pc, pc - function_symbol->addr); + } + else { + GB_log(gb, "%s%04x: ", pc == gb->pc? " ->": " ", pc); + } uint8_t opcode = GB_read_memory(gb, pc); opcodes[opcode](gb, opcode, &pc); } From c3f1eb26b16c979a92dcba0e39ded7dda2e68bec Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 Jul 2016 23:07:36 +0300 Subject: [PATCH 0088/1216] Fixed potential overflow --- Core/debugger.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index 04d7e978..51110e62 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -44,6 +44,11 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer symbol = NULL; } + /* Avoid overflow */ + if (strlen(symbol->name) > 240) { + symbol = NULL; + } + if (!symbol) { sprintf(output, "$%04x", value); } From 46714108ac4afe248fd0ed723fefd6d360decb90 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 Jul 2016 23:46:18 +0300 Subject: [PATCH 0089/1216] ...And a crash. --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 51110e62..f78c544e 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -45,7 +45,7 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer } /* Avoid overflow */ - if (strlen(symbol->name) > 240) { + if (symbol && strlen(symbol->name) > 240) { symbol = NULL; } From f6b10ed439038e8adca546aa53b74b268ed80f49 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 Jul 2016 23:51:46 +0300 Subject: [PATCH 0090/1216] Operators priorities were inverted! --- Core/debugger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index f78c544e..8583dab2 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -175,7 +175,7 @@ static struct { {">=", 3, greater_equals}, {">", 3, greater}, {"==", 3, equals}, - {"=", 4, NULL, assign}, + {"=", -1, NULL, assign}, {"!=", 3, different}, }; @@ -327,7 +327,7 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, 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 (operator_index != -1 && operators[operator_index].priority < operators[j].priority) continue; unsigned long operator_length = strlen(operators[j].string); if (memcmp(string + i, operators[j].string, operator_length) == 0) { // Found an operator! From ea082b777dcfe78c4ad22246565a55bcfd128b23 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 14 Jul 2016 01:46:55 +0300 Subject: [PATCH 0091/1216] ...And another crash --- Core/symbol_hash.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 7c4532a9..86d50290 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -35,6 +35,7 @@ void GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) { + if (!map) return NULL; size_t index = GB_map_find_symbol_index(map, addr); if (index < map->n_symbols && map->symbols[index].addr != addr) { index--; From 65f37bccbd87f91af5e8a21a7b05ce4435972a6a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 14 Jul 2016 20:46:00 +0300 Subject: [PATCH 0092/1216] Initial 25-bit debugger values support --- Core/debugger.c | 226 +++++++++++++++++++++++++++++++----------------- Core/gb.c | 2 +- Core/gb.h | 2 +- 3 files changed, 151 insertions(+), 79 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 8583dab2..f73ae0f4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -20,6 +20,14 @@ typedef struct { }; } lvalue_t; +typedef struct { + bool has_bank; + uint16_t bank:9; + uint16_t value; +} value_t; + +#define VALUE_16(x) ((value_t){false, 0, (x)}) + struct GB_breakpoint_s { uint16_t addr; char *condition; @@ -73,21 +81,61 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer return output; } -static uint16_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) +static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name) +{ + if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); + + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); + + if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + /* Avoid overflow */ + if (symbol && strlen(symbol->name) > 240) { + symbol = NULL; + } + + if (!symbol) { + sprintf(output, "$%02x:$%04x", value.bank, value.value); + } + + else if (symbol->addr == value.value) { + if (prefer_name) { + sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); + } + else { + sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); + } + } + + else { + if (prefer_name) { + sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); + } + else { + sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + } + } + return output; +} + +static value_t 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 GB_read_memory(gb, lvalue.memory_address); + return VALUE_16(GB_read_memory(gb, lvalue.memory_address)); case LVALUE_REG16: - return *lvalue.register_address; + return VALUE_16(*lvalue.register_address); case LVALUE_REG_L: - return *lvalue.register_address & 0x00FF; + return VALUE_16(*lvalue.register_address & 0x00FF); case LVALUE_REG_H: - return *lvalue.register_address >> 8; + return VALUE_16(*lvalue.register_address >> 8); } } @@ -114,46 +162,57 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) } } -static uint16_t add(uint16_t a, uint16_t b) {return a + b;}; -static uint16_t sub(uint16_t a, uint16_t b) {return a - b;}; -static uint16_t mul(uint16_t a, uint16_t b) {return a * b;}; -static uint16_t _div(uint16_t a, uint16_t b) { - if (b == 0) { - return 0; +/* 16 bit value 16 bit value = 16 bit value + 25 bit address 16 bit value = 25 bit address + 16 bit value 25 bit address = 25 bit address + 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) + + Boolean operators always return a 16-bit value + */ +#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) + +static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} +static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} +static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} +static value_t _div(value_t a, value_t b) { + if (b.value == 0) { + return FIX_BANK(0); } - return a / b; + return FIX_BANK(a.value / b.value); }; -static uint16_t mod(uint16_t a, uint16_t b) { - if (b == 0) { - return 0; +static value_t mod(value_t a, value_t b) { + if (b.value == 0) { + return FIX_BANK(0); } - return a % b; + return FIX_BANK(a.value % b.value); }; -static uint16_t and(uint16_t a, uint16_t b) {return a & b;}; -static uint16_t or(uint16_t a, uint16_t b) {return a | b;}; -static uint16_t xor(uint16_t a, uint16_t b) {return a ^ b;}; -static uint16_t shleft(uint16_t a, uint16_t b) {return a << b;}; -static uint16_t shright(uint16_t a, uint16_t b) {return a >> b;}; -static uint16_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) +static value_t and(value_t a, value_t b) {return FIX_BANK(a.value & b.value);} +static value_t or(value_t a, value_t b) {return FIX_BANK(a.value | b.value);} +static value_t xor(value_t a, value_t b) {return FIX_BANK(a.value ^ b.value);} +static value_t shleft(value_t a, value_t b) {return FIX_BANK(a.value << b.value);} +static value_t shright(value_t a, value_t b) {return FIX_BANK(a.value >> b.value);} +static value_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) { write_lvalue(gb, a, b); return read_lvalue(gb, a); } -static uint16_t bool_and(uint16_t a, uint16_t b) {return a && b;}; -static uint16_t bool_or(uint16_t a, uint16_t b) {return a || b;}; -static uint16_t equals(uint16_t a, uint16_t b) {return a == b;}; -static uint16_t different(uint16_t a, uint16_t b) {return a != b;}; -static uint16_t lower(uint16_t a, uint16_t b) {return a < b;}; -static uint16_t greater(uint16_t a, uint16_t b) {return a > b;}; -static uint16_t lower_equals(uint16_t a, uint16_t b) {return a <= b;}; -static uint16_t greater_equals(uint16_t a, uint16_t b) {return a >= b;}; +static value_t bool_and(value_t a, value_t b) {return VALUE_16(a.value && b.value);} +static value_t bool_or(value_t a, value_t b) {return VALUE_16(a.value || b.value);} +static value_t equals(value_t a, value_t b) {return VALUE_16(a.value == b.value);} +static value_t different(value_t a, value_t b) {return VALUE_16(a.value != b.value);} +static value_t lower(value_t a, value_t b) {return VALUE_16(a.value < b.value);} +static value_t greater(value_t a, value_t b) {return VALUE_16(a.value > b.value);} +static value_t lower_equals(value_t a, value_t b) {return VALUE_16(a.value <= b.value);} +static value_t greater_equals(value_t a, value_t b) {return VALUE_16(a.value >= b.value);} +static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.value};} + static struct { const char *string; char priority; - uint16_t (*operator)(uint16_t, uint16_t); - uint16_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); + value_t (*operator)(value_t, value_t); + value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); } operators[] = { // Yes. This is not C-like. But it makes much more sense. @@ -177,9 +236,10 @@ static struct { {"==", 3, equals}, {"=", -1, NULL, assign}, {"!=", 3, different}, + {":", 4, bank}, }; -uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, +value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); @@ -229,7 +289,8 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, if (string[i] == ']') depth--; } if (depth == 0) { - return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + /* Todo: Warn the user when dereferencing a specific bank, but it's not mapped */ + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value).value}; } } @@ -267,9 +328,10 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, return (lvalue_t){0,}; } -uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +#define ERROR ((value_t){0,}) +value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + unsigned int length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; // Strip whitespace @@ -284,7 +346,7 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, { GB_log(gb, "Expected expression.\n"); *error = true; - return -1; + return ERROR; } if (string[0] == '(' && string[length - 1] == ')') { // Attempt to strip parentheses @@ -312,7 +374,13 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (string[i] == ']') depth--; } - if (depth == 0) return GB_read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)); + /* Todo: Warn the user when dereferencing a specific bank, but it's not mapped */ + if (depth == 0) + return VALUE_16( + GB_read_memory(gb, + debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value).value + ) + ); } // Search for lowest priority operator signed int depth = 0; @@ -342,15 +410,15 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (operator_index != -1) { unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); - uint16_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); - if (*error) return -1; + value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); + if (*error) return ERROR; if (operators[operator_index].lvalue_operator) { lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); - if (*error) return -1; - return operators[operator_index].lvalue_operator(gb, left, right); + if (*error) return ERROR; + return operators[operator_index].lvalue_operator(gb, left, right.value); } - uint16_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); - if (*error) return -1; + value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) return ERROR; return operators[operator_index].operator(left, right); } @@ -360,43 +428,43 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (length == 1) { switch (string[0]) { - 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; + case 'a': return VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); + case 'f': return VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); + case 'b': return VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); + case 'c': return VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); + case 'd': return VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); + case 'e': return VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); + case 'h': return VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); + case 'l': return VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); } } else if (length == 2) { switch (string[0]) { - case 'a': if (string[1] == 'f') return gb->registers[GB_REGISTER_AF]; - case 'b': if (string[1] == 'c') return gb->registers[GB_REGISTER_BC]; - case 'd': if (string[1] == 'e') return gb->registers[GB_REGISTER_DE]; - case 'h': if (string[1] == 'l') return gb->registers[GB_REGISTER_HL]; - case 's': if (string[1] == 'p') return gb->registers[GB_REGISTER_SP]; - case 'p': if (string[1] == 'c') return gb->pc; + case 'a': if (string[1] == 'f') return VALUE_16(gb->registers[GB_REGISTER_AF]); + case 'b': if (string[1] == 'c') return VALUE_16(gb->registers[GB_REGISTER_BC]); + case 'd': if (string[1] == 'e') return VALUE_16(gb->registers[GB_REGISTER_DE]); + case 'h': if (string[1] == 'l') return VALUE_16(gb->registers[GB_REGISTER_HL]); + case 's': if (string[1] == 'p') return VALUE_16(gb->registers[GB_REGISTER_SP]); + case 'p': if (string[1] == 'c') return VALUE_16(gb->pc); } } else if (length == 3) { if (watchpoint_address && memcmp(string, "old", 3) == 0) { - return GB_read_memory(gb, *watchpoint_address); + return VALUE_16(GB_read_memory(gb, *watchpoint_address)); } if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { - return *watchpoint_new_value; + return VALUE_16(*watchpoint_new_value); } /* $new is identical to $old in read conditions */ if (watchpoint_address && memcmp(string, "new", 3) == 0) { - return GB_read_memory(gb, *watchpoint_address); + return VALUE_16(GB_read_memory(gb, *watchpoint_address)); } } GB_log(gb, "Unknown register: %.*s\n", length, string); *error = true; - return -1; + return ERROR; } char *end; @@ -410,9 +478,9 @@ uint16_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (end != string + length) { GB_log(gb, "Failed to parse: %.*s\n", length, string); *error = true; - return -1; + return ERROR; } - return literal; + return VALUE_16(literal); } typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments); @@ -550,7 +618,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; if (error) return true; @@ -608,7 +676,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; if (error) return true; @@ -693,7 +761,7 @@ print_usage: } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; if (error) return true; @@ -756,7 +824,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; if (error) return true; @@ -834,7 +902,7 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr) } bool error; bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, - (unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL); + (unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -853,9 +921,9 @@ static bool print(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { - GB_log(gb, "=%s\n", value_to_string(gb, result, false)); + GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); } return true; } @@ -868,7 +936,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; if (!error) { GB_log(gb, "%4x: ", addr); for (int i = 0; i < 16; i++) { @@ -1030,7 +1098,7 @@ void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t } bool error; bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, &value); + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, &value).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -1057,7 +1125,7 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) } bool error; bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, NULL); + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, NULL).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -1150,6 +1218,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) char symbol[length]; if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { + bank &= 0x1FF; if (!gb->bank_symbols[bank]) { gb->bank_symbols[bank] = GB_map_alloc(); } @@ -1162,7 +1231,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) { - unsigned char bank = 0; + uint16_t bank = 0; if (addr < 0x4000) { bank = gb->mbc_rom0_bank; } @@ -1177,9 +1246,12 @@ const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) bank = gb->cgb_ram_bank; } + if (bank > 0x1FF) return NULL; + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); if (symbol) return symbol; - if (bank != 0) GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ + if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ + return NULL; } diff --git a/Core/gb.c b/Core/gb.c index 4d75a2ed..13f29ba3 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -137,7 +137,7 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } - for (unsigned char i = 0; i--;) { + for (int i = 0x200; i--;) { if (gb->bank_symbols[i]) { GB_map_free(gb->bank_symbols[i]); } diff --git a/Core/gb.h b/Core/gb.h index 8ec1a2fa..04ebb95b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -361,7 +361,7 @@ typedef struct GB_gameboy_s { struct GB_watchpoint_s *watchpoints; /* Symbol table */ - GB_symbol_map_t *bank_symbols[0x100]; + GB_symbol_map_t *bank_symbols[0x200]; /* Misc */ bool turbo; From ce837b372720f26a53dca4cd83d8d380cd527ccf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 14 Jul 2016 21:15:24 +0300 Subject: [PATCH 0093/1216] Bank-specific examine support --- Core/debugger.c | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index f73ae0f4..f0adfcd8 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -936,13 +936,45 @@ static bool examine(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; + value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { - GB_log(gb, "%4x: ", addr); - for (int i = 0; i < 16; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr + i)); + if (addr.has_bank) { + uint16_t old_rom0_bank = gb->mbc_rom0_bank; + uint16_t old_rom_bank = gb->mbc_rom_bank; + uint8_t old_mbc_ram_bank = gb->mbc_ram_bank; + bool old_mbc_ram_enable = gb->mbc_ram_enable; + uint8_t old_ram_bank = gb->cgb_ram_bank; + uint8_t old_vram_bank = gb->cgb_vram_bank; + + gb->mbc_rom0_bank = addr.bank; + gb->mbc_rom_bank = addr.bank; + gb->mbc_ram_bank = addr.bank; + gb->mbc_ram_enable = true; + if (gb->is_cgb) { + gb->cgb_ram_bank = addr.bank & 7; + gb->cgb_vram_bank = addr.bank & 1; + } + + GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); + for (int i = 0; i < 16; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + } + GB_log(gb, "\n"); + + gb->mbc_rom0_bank = old_rom0_bank; + gb->mbc_rom_bank = old_rom_bank; + gb->mbc_ram_bank = old_mbc_ram_bank; + gb->mbc_ram_enable = old_mbc_ram_enable; + gb->cgb_ram_bank = old_ram_bank; + gb->cgb_vram_bank = old_vram_bank; + } + else { + GB_log(gb, "%04x: ", addr.value); + for (int i = 0; i < 16; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + } + GB_log(gb, "\n"); } - GB_log(gb, "\n"); } return true; } From 909f3ba75ee8e84df33961cbe70a2675f8d2fa1b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 14 Jul 2016 23:25:16 +0300 Subject: [PATCH 0094/1216] Bank-specific breakpoints and watchpoints --- Core/debugger.c | 254 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 173 insertions(+), 81 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index f0adfcd8..4d2f4db4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -29,19 +29,55 @@ typedef struct { #define VALUE_16(x) ((value_t){false, 0, (x)}) struct GB_breakpoint_s { - uint16_t addr; + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; char *condition; }; +#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + #define GB_WATCHPOINT_R (1) #define GB_WATCHPOINT_W (2) struct GB_watchpoint_s { - uint16_t addr; + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; char *condition; uint8_t flags; }; +#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) { @@ -579,18 +615,21 @@ static bool registers(GB_gameboy_t *gb, char *arguments) } /* Find the index of the closest breakpoint equal or greater to addr */ -static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr) +static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) { if (!gb->breakpoints) { return 0; } + + uint32_t key = BP_KEY(addr); + int min = 0; int max = gb->n_breakpoints; while (min < max) { uint16_t pivot = (min + max) / 2; - if (gb->breakpoints[pivot].addr == addr) return pivot; - if (gb->breakpoints[pivot].addr > addr) { - max = pivot - 1; + if (gb->breakpoints[pivot].key == key) return pivot; + if (gb->breakpoints[pivot].key > key) { + max = pivot; } else { min = pivot + 1; @@ -606,6 +645,11 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) return true; } + if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) { + GB_log(gb, "Too many breakpoints set\n"); + return true; + } + char *condition = NULL; if ((condition = strstr(arguments, " if "))) { *condition = 0; @@ -618,13 +662,14 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); if (error) return true; uint16_t index = find_breakpoint(gb, result); - if (index < gb->n_breakpoints && gb->breakpoints[index].addr == result) { - GB_log(gb, "Breakpoint already set at %s\n", value_to_string(gb, result, true)); + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true)); if (!gb->breakpoints[index].condition && condition) { GB_log(gb, "Added condition to breakpoint\n"); gb->breakpoints[index].condition = strdup(condition); @@ -644,7 +689,8 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); - gb->breakpoints[index].addr = result; + gb->breakpoints[index].key = key; + if (condition) { gb->breakpoints[index].condition = strdup(condition); } @@ -653,7 +699,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) } gb->n_breakpoints++; - GB_log(gb, "Breakpoint set at %s\n", value_to_string(gb, result, true)); + GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } @@ -676,13 +722,14 @@ static bool delete(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); if (error) return true; uint16_t index = find_breakpoint(gb, result); - if (index >= gb->n_breakpoints || gb->breakpoints[index].addr != result) { - GB_log(gb, "No breakpoint set at %s\n", value_to_string(gb, result, true)); + if (index >= gb->n_breakpoints || gb->breakpoints[index].key != key) { + GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } @@ -694,23 +741,24 @@ static bool delete(GB_gameboy_t *gb, char *arguments) gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); - GB_log(gb, "Breakpoint removed from %s\n", value_to_string(gb, result, true)); + GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true)); return true; } /* Find the index of the closest watchpoint equal or greater to addr */ -static uint16_t find_watchpoint(GB_gameboy_t *gb, uint16_t addr) +static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) { if (!gb->watchpoints) { return 0; } + uint32_t key = WP_KEY(addr); int min = 0; int max = gb->n_watchpoints; while (min < max) { uint16_t pivot = (min + max) / 2; - if (gb->watchpoints[pivot].addr == addr) return pivot; - if (gb->watchpoints[pivot].addr > addr) { - max = pivot - 1; + if (gb->watchpoints[pivot].key == key) return pivot; + if (gb->watchpoints[pivot].key > key) { + max = pivot; } else { min = pivot + 1; @@ -727,6 +775,11 @@ print_usage: return true; } + if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) { + GB_log(gb, "Too many watchpoints set\n"); + return true; + } + uint8_t flags = 0; while (*arguments != ' ' && *arguments) { switch (*arguments) { @@ -761,13 +814,14 @@ print_usage: } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); if (error) return true; uint16_t index = find_watchpoint(gb, result); - if (index < gb->n_watchpoints && gb->watchpoints[index].addr == result) { - GB_log(gb, "Watchpoint already set at %s\n", value_to_string(gb, result, true)); + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true)); if (!gb->watchpoints[index].flags != flags) { GB_log(gb, "Modified watchpoint type\n"); gb->watchpoints[index].flags = flags; @@ -791,7 +845,7 @@ print_usage: gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0])); memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0])); - gb->watchpoints[index].addr = result; + gb->watchpoints[index].key = key; gb->watchpoints[index].flags = flags; if (condition) { gb->watchpoints[index].condition = strdup(condition); @@ -801,7 +855,7 @@ print_usage: } gb->n_watchpoints++; - GB_log(gb, "Watchpoint set at %s\n", value_to_string(gb, result, true)); + GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } @@ -824,13 +878,14 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) } bool error; - uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); if (error) return true; uint16_t index = find_watchpoint(gb, result); - if (index >= gb->n_watchpoints || gb->watchpoints[index].addr != result) { - GB_log(gb, "No watchpoint set at %s\n", value_to_string(gb, result, true)); + if (index >= gb->n_watchpoints || gb->watchpoints[index].key != key) { + GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } @@ -842,7 +897,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) gb->n_watchpoints--; gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); - GB_log(gb, "Watchpoint removed from %s\n", value_to_string(gb, result, true)); + GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true)); return true; } @@ -859,13 +914,14 @@ static bool list(GB_gameboy_t *gb, char *arguments) else { GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); for (uint16_t i = 0; i < gb->n_breakpoints; i++) { + value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; if (gb->breakpoints[i].condition) { GB_log(gb, " %d. %s (Condition: %s)\n", i + 1, - value_to_string(gb, gb->breakpoints[i].addr, true), + debugger_value_to_string(gb, addr, addr.has_bank), gb->breakpoints[i].condition); } else { - GB_log(gb, " %d. %s\n", i + 1, value_to_string(gb, gb->breakpoints[i].addr, true)); + GB_log(gb, " %d. %s\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank)); } } } @@ -876,14 +932,15 @@ static bool list(GB_gameboy_t *gb, char *arguments) else { GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); for (uint16_t i = 0; i < gb->n_watchpoints; i++) { + value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr}; if (gb->watchpoints[i].condition) { - GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, value_to_string(gb, gb->watchpoints[i].addr, true), + GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', gb->watchpoints[i].condition); } else { - GB_log(gb, " %d. %s (%c%c)\n", i + 1, value_to_string(gb, gb->watchpoints[i].addr, true), + GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb,addr, addr.has_bank), (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); } @@ -893,10 +950,12 @@ static bool list(GB_gameboy_t *gb, char *arguments) return true; } -static bool should_break(GB_gameboy_t *gb, uint16_t addr) +static bool _should_break(GB_gameboy_t *gb, value_t addr) { uint16_t index = find_breakpoint(gb, addr); - if (index < gb->n_breakpoints && gb->breakpoints[index].addr == addr) { + uint32_t key = BP_KEY(addr); + + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { if (!gb->breakpoints[index].condition) { return true; } @@ -913,6 +972,18 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr) return false; } +static bool should_break(GB_gameboy_t *gb, uint16_t addr) +{ + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_should_break(gb, full_addr)) return true; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + return _should_break(gb, full_addr); +} + static bool print(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { @@ -1116,58 +1187,94 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb) } } -void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value) { uint16_t index = find_watchpoint(gb, addr); - if (index < gb->n_watchpoints && gb->watchpoints[index].addr == addr) { + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { - return; + return false; } if (!gb->watchpoints[index].condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s] = $%02x\n", value_to_string(gb, addr, true), value); - return; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; } bool error; bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, &value).value; + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); - return; + return false; } if (condition) { gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s] = $%02x\n", value_to_string(gb, addr, true), value); + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; } } + return false; +} + +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_write_watchpoint(gb, full_addr, value); +} + +static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + uint16_t index = find_breakpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + } + return false; } void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) { - uint16_t index = find_breakpoint(gb, addr); - if (index < gb->n_watchpoints && gb->watchpoints[index].addr == addr) { - if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { - return; - } - if (!gb->watchpoints[index].condition) { - gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s]\n", value_to_string(gb, addr, true)); - return; - } - bool error; - bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, NULL).value; - if (error) { - /* Should never happen */ - GB_log(gb, "An internal error has occured\n"); - return; - } - if (condition) { - gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s]\n", value_to_string(gb, addr, true)); - } - } + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_read_watchpoint(gb, full_addr); } void GB_debugger_run(GB_gameboy_t *gb) @@ -1263,22 +1370,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) { - uint16_t bank = 0; - if (addr < 0x4000) { - bank = gb->mbc_rom0_bank; - } - else if (addr < 0x8000) { - bank = gb->mbc_rom_bank; - } - - else if (addr < 0xD000) { - bank = 0; - } - else if (addr < 0xE000) { - bank = gb->cgb_ram_bank; - } - - if (bank > 0x1FF) return NULL; + uint16_t bank = bank_for_addr(gb, addr); const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); if (symbol) return symbol; From c3a831db7dea8954fae4d6100c91975a7bbc3413 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 14 Jul 2016 23:27:48 +0300 Subject: [PATCH 0095/1216] Debugger's pc "variable" now returns a full address --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 4d2f4db4..94eaa08d 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -481,7 +481,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, case 'd': if (string[1] == 'e') return VALUE_16(gb->registers[GB_REGISTER_DE]); case 'h': if (string[1] == 'l') return VALUE_16(gb->registers[GB_REGISTER_HL]); case 's': if (string[1] == 'p') return VALUE_16(gb->registers[GB_REGISTER_SP]); - case 'p': if (string[1] == 'c') return VALUE_16(gb->pc); + case 'p': if (string[1] == 'c') return (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; } } else if (length == 3) { From e20e81befd72ba08db31f7d96cf283e632b1ad3f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jul 2016 14:31:27 +0300 Subject: [PATCH 0096/1216] Symbol support in the expression evaluator --- Core/debugger.c | 16 ++++++++++++++-- Core/gb.c | 7 +++++++ Core/gb.h | 3 ++- Core/symbol_hash.c | 43 +++++++++++++++++++++++++++++++++++++++++-- Core/symbol_hash.h | 17 +++++++++++++++-- 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 94eaa08d..3e8223fb 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -498,7 +498,16 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, return VALUE_16(GB_read_memory(gb, *watchpoint_address)); } } - GB_log(gb, "Unknown register: %.*s\n", length, string); + + char symbol_name[length + 1]; + memcpy(symbol_name, string, length); + symbol_name[length] = 0; + const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name); + if (symbol) { + return (value_t){true, symbol->bank, symbol->addr}; + } + + GB_log(gb, "Unknown register or symbol: %.*s\n", length, string); *error = true; return ERROR; } @@ -1361,7 +1370,10 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) if (!gb->bank_symbols[bank]) { gb->bank_symbols[bank] = GB_map_alloc(); } - GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + if (allocated_symbol) { + GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); + } } } free(line); diff --git a/Core/gb.c b/Core/gb.c index 13f29ba3..82847e46 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -142,6 +142,13 @@ void GB_free(GB_gameboy_t *gb) GB_map_free(gb->bank_symbols[i]); } } + for (int i = 0x400; i--;) { + if (gb->reversed_symbol_map.buckets[i]) { + GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next; + free(gb->reversed_symbol_map.buckets[i]); + gb->reversed_symbol_map.buckets[i] = next; + } + } } int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) diff --git a/Core/gb.h b/Core/gb.h index 04ebb95b..f5221ea2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -360,8 +360,9 @@ typedef struct GB_gameboy_s { uint16_t n_watchpoints; struct GB_watchpoint_s *watchpoints; - /* Symbol table */ + /* Symbol tables */ GB_symbol_map_t *bank_symbols[0x200]; + GB_reversed_symbol_map_t reversed_symbol_map; /* Misc */ bool turbo; diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 86d50290..7f5cbe26 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -20,17 +20,18 @@ static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) return (size_t) min; } -void GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) +GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) { size_t index = GB_map_find_symbol_index(map, addr); - if (index < map->n_symbols && map->symbols[index].addr == addr) return; + if (index < map->n_symbols && map->symbols[index].addr == addr) return NULL; map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0])); memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0])); map->symbols[index].addr = addr; map->symbols[index].name = strdup(name); map->n_symbols++; + return &map->symbols[index]; } const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) @@ -64,4 +65,42 @@ void GB_map_free(GB_symbol_map_t *map) } free(map); +} + +static int hash_name(const char *name) +{ + int r = 0; + while (*name) { + r <<= 1; + if (r & 0x400) { + r ^= 0x401; + } + r += (unsigned char)*(name++); + } + + return r & 0x3FF; +} + +void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol) +{ + int hash = hash_name(bank_symbol->name); + GB_symbol_t *symbol = malloc(sizeof(*symbol)); + symbol->name = bank_symbol->name; + symbol->addr = bank_symbol->addr; + symbol->bank = bank; + symbol->next = map->buckets[hash]; + map->buckets[hash] = symbol; +} + +const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name) +{ + int hash = hash_name(name); + GB_symbol_t *symbol = map->buckets[hash]; + + while (symbol) { + if (strcmp(symbol->name, name) == 0) return symbol; + symbol = symbol->next; + } + + return NULL; } \ No newline at end of file diff --git a/Core/symbol_hash.h b/Core/symbol_hash.h index 8044e3f5..0544f947 100644 --- a/Core/symbol_hash.h +++ b/Core/symbol_hash.h @@ -5,19 +5,32 @@ #include typedef struct { - uint16_t addr; char *name; + uint16_t addr; } GB_bank_symbol_t; +typedef struct GB_symbol_s { + struct GB_symbol_s *next; + const char *name; + uint16_t bank; + uint16_t addr; +} GB_symbol_t; typedef struct { GB_bank_symbol_t *symbols; size_t n_symbols; } GB_symbol_map_t; -void GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); +GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); GB_symbol_map_t *GB_map_alloc(void); void GB_map_free(GB_symbol_map_t *map); +typedef struct { + GB_symbol_t *buckets[0x400]; +} GB_reversed_symbol_map_t; + +void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol); +const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name); + #endif /* symbol_hash_h */ From eaca0634aa7a0f5c972fb9412d69074d816431b4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jul 2016 17:06:46 +0300 Subject: [PATCH 0097/1216] Reading and writing absolute addresses in the expression evaluator. --- Core/debugger.c | 121 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 39 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 3e8223fb..4babcb36 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -6,6 +6,11 @@ #include "z80_cpu.h" #include "gb.h" +typedef struct { + bool has_bank; + uint16_t bank:9; + uint16_t value; +} value_t; typedef struct { enum { @@ -16,16 +21,10 @@ typedef struct { } kind; union { uint16_t *register_address; - uint16_t memory_address; + value_t memory_address; }; } lvalue_t; -typedef struct { - bool has_bank; - uint16_t bank:9; - uint16_t value; -} value_t; - #define VALUE_16(x) ((value_t){false, 0, (x)}) struct GB_breakpoint_s { @@ -79,6 +78,48 @@ static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) return 0; } +typedef struct { + uint16_t rom0_bank; + uint16_t rom_bank; + uint8_t mbc_ram_bank; + bool mbc_ram_enable; + uint8_t ram_bank; + uint8_t vram_bank; +} banking_state_t; + +static inline void save_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + state->rom0_bank = gb->mbc_rom0_bank; + state->rom_bank = gb->mbc_rom_bank; + state->mbc_ram_bank = gb->mbc_ram_bank; + state->mbc_ram_enable = gb->mbc_ram_enable; + state->ram_bank = gb->cgb_ram_bank; + state->vram_bank = gb->cgb_vram_bank; +} + +static inline void restore_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + + gb->mbc_rom0_bank = state->rom0_bank; + gb->mbc_rom_bank = state->rom_bank; + gb->mbc_ram_bank = state->mbc_ram_bank; + gb->mbc_ram_enable = state->mbc_ram_enable; + gb->cgb_ram_bank = state->ram_bank; + gb->cgb_vram_bank = state->vram_bank; +} + +static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) +{ + gb->mbc_rom0_bank = bank; + gb->mbc_rom_bank = bank; + gb->mbc_ram_bank = bank; + gb->mbc_ram_enable = true; + if (gb->is_cgb) { + gb->cgb_ram_bank = bank & 7; + gb->cgb_vram_bank = bank & 1; + } +} + static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) { static __thread char output[256]; @@ -162,7 +203,15 @@ static value_t 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 VALUE_16(GB_read_memory(gb, lvalue.memory_address)); + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); case LVALUE_REG16: return VALUE_16(*lvalue.register_address); @@ -179,7 +228,15 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) { switch (lvalue.kind) { case LVALUE_MEMORY: - GB_write_memory(gb, lvalue.memory_address, value); + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); return; case LVALUE_REG16: @@ -325,8 +382,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, if (string[i] == ']') depth--; } if (depth == 0) { - /* Todo: Warn the user when dereferencing a specific bank, but it's not mapped */ - return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value).value}; + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; } } @@ -410,13 +466,17 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (string[i] == ']') depth--; } - /* Todo: Warn the user when dereferencing a specific bank, but it's not mapped */ - if (depth == 0) - return VALUE_16( - GB_read_memory(gb, - debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value).value - ) - ); + + if (depth == 0) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + value_t r = VALUE_16(GB_read_memory(gb, addr.value)); + restore_banking_state(gb, &state); + return r; + } + } // Search for lowest priority operator signed int depth = 0; @@ -1019,21 +1079,9 @@ static bool examine(GB_gameboy_t *gb, char *arguments) value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { if (addr.has_bank) { - uint16_t old_rom0_bank = gb->mbc_rom0_bank; - uint16_t old_rom_bank = gb->mbc_rom_bank; - uint8_t old_mbc_ram_bank = gb->mbc_ram_bank; - bool old_mbc_ram_enable = gb->mbc_ram_enable; - uint8_t old_ram_bank = gb->cgb_ram_bank; - uint8_t old_vram_bank = gb->cgb_vram_bank; - - gb->mbc_rom0_bank = addr.bank; - gb->mbc_rom_bank = addr.bank; - gb->mbc_ram_bank = addr.bank; - gb->mbc_ram_enable = true; - if (gb->is_cgb) { - gb->cgb_ram_bank = addr.bank & 7; - gb->cgb_vram_bank = addr.bank & 1; - } + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); for (int i = 0; i < 16; i++) { @@ -1041,12 +1089,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments) } GB_log(gb, "\n"); - gb->mbc_rom0_bank = old_rom0_bank; - gb->mbc_rom_bank = old_rom_bank; - gb->mbc_ram_bank = old_mbc_ram_bank; - gb->mbc_ram_enable = old_mbc_ram_enable; - gb->cgb_ram_bank = old_ram_bank; - gb->cgb_vram_bank = old_vram_bank; + restore_banking_state(gb, &old_state); } else { GB_log(gb, "%04x: ", addr.value); From a68b06226a6728c672ca480396c7ad01829811a6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jul 2016 23:20:14 +0300 Subject: [PATCH 0098/1216] Fixed crash on free --- Core/symbol_hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 7f5cbe26..5af04f09 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -56,7 +56,7 @@ GB_symbol_map_t *GB_map_alloc(void) void GB_map_free(GB_symbol_map_t *map) { - for (unsigned char i = 0; i < map->n_symbols; i++) { + for (unsigned i = 0; i < map->n_symbols; i++) { free(map->symbols[i].name); } From 9d5376001624885014644a991292d267ad3dc844 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 17 Jul 2016 22:43:23 +0300 Subject: [PATCH 0099/1216] Fixing Linux build --- Makefile | 4 ++-- SDL/main.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 945df7fa..04a02a87 100644 --- a/Makefile +++ b/Makefile @@ -11,13 +11,13 @@ OBJ := build/obj CC := clang -CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE +CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" SDL_LDFLAGS := -lSDL LDFLAGS += -lc -lm CONF ?= debug ifeq ($(shell uname -s),Darwin) -CFLAGS += -F/Library/Frameworks -DVERSION="$(VERSION)" +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 -framework Carbon SDL_LDFLAGS := -framework SDL diff --git a/SDL/main.c b/SDL/main.c index 48a5aae1..595cc8b0 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "gb.h" From 67f3a3a9d8561ae45cb98e17c9e69fa62b0e2737 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 17 Jul 2016 23:08:07 +0300 Subject: [PATCH 0100/1216] Symbol support in SDL port --- SDL/main.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 595cc8b0..d61967d9 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -7,6 +7,7 @@ #include #include "gb.h" +#include "debugger.h" static bool running = false; @@ -142,6 +143,24 @@ static void audio_callback(void *gb, Uint8 *stream, int len) GB_apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); } +static void replace_extension(const char *src, size_t length, char *dest, const char *ext) +{ + memcpy(dest, src, length); + dest[length] = 0; + + /* Remove extension */ + for (size_t i = length; i--;) { + if (dest[i] == '/') break; + if (dest[i] == '.') { + dest[i] = 0; + break; + } + } + + /* Add new extension */ + strcat(dest, ext); +} + #ifdef __APPLE__ extern void cocoa_disable_filtering(void); #endif @@ -204,25 +223,18 @@ usage: GB_set_pixels_output(&gb, screen->pixels); GB_set_rgb_encode_callback(&gb, rgb_encode); - /* Configure battery */ size_t path_length = strlen(argv[argc - 1]); + + /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ - memcpy(battery_save_path, argv[argc - 1], path_length); - - /* Remove extension */ - for (size_t i = path_length; i--;) { - if (battery_save_path[i] == '/') break; - if (battery_save_path[i] == '.') { - battery_save_path[i] = 0; - break; - } - } - - /* Add .sav */ - strcat(battery_save_path, ".sav"); - + replace_extension(argv[argc - 1], path_length, battery_save_path, ".sav"); GB_load_battery(&gb, battery_save_path); + /* Configure symbols */ + char symbols_path[path_length + 5]; + replace_extension(argv[argc - 1], path_length, symbols_path, ".sym"); + GB_debugger_load_symbol_file(&gb, symbols_path); + /* Configure Audio */ SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); From aa6438fa06b6e3e754ba48e5e9ad812c80ec7bba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 18 Jul 2016 00:39:43 +0300 Subject: [PATCH 0101/1216] Async debugger commands --- Cocoa/Document.m | 28 +++++++++++++++++ Core/debugger.c | 79 ++++++++++++++++++++++++++++++++++-------------- Core/debugger.h | 1 + Core/display.c | 2 ++ Core/gb.c | 5 +++ Core/gb.h | 3 ++ Core/timing.c | 37 +++++++++++------------ Core/timing.h | 1 + Core/z80_cpu.c | 6 ++++ 9 files changed, 120 insertions(+), 42 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index b2786302..ea307fb8 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -13,6 +13,7 @@ unsigned long pendingLogLines; bool tooMuchLogs; bool fullScreen; + bool in_sync_input; NSString *lastConsoleInput; } @@ -21,6 +22,7 @@ - (void) vblank; - (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes; - (const char *) getDebuggerInput; +- (const char *) getAsyncDebuggerInput; @end static void vblank(GB_gameboy_t *gb) @@ -41,6 +43,13 @@ static char *consoleInput(GB_gameboy_t *gb) return strdup([self getDebuggerInput]); } +static char *asyncConsoleInput(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)(gb->user_data); + const char *ret = [self getAsyncDebuggerInput]; + return ret? strdup(ret) : NULL; +} + static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return (r << 0) | (g << 8) | (b << 16); @@ -78,6 +87,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) 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_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); GB_set_rgb_encode_callback(&gb, rgbEncode); gb.user_data = (__bridge void *)(self); } @@ -89,6 +99,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) 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_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); GB_set_rgb_encode_callback(&gb, rgbEncode); gb.user_data = (__bridge void *)(self); } @@ -370,6 +381,10 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) else { line = @""; } + + if (!in_sync_input) { + [self log:">"]; + } [self log:[line UTF8String]]; [self log:"\n"]; [has_debugger_input lock]; @@ -382,10 +397,23 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (const char *) getDebuggerInput { [self log:">"]; + in_sync_input = true; [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]; + in_sync_input = false; + return [input UTF8String]; +} + +- (const char *) getAsyncDebuggerInput +{ + [has_debugger_input lock]; + NSString *input = [debugger_input_queue firstObject]; + if (input) { + [debugger_input_queue removeObjectAtIndex:0]; + } + [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; return [input UTF8String]; } diff --git a/Core/debugger.c b/Core/debugger.c index 4babcb36..4d278a42 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -605,18 +605,29 @@ static const char *lstrip(const char *str) return str; } +#define STOPPED_ONLY \ +if (!gb->debug_stopped) { \ +GB_log(gb, "Program is running. \n"); \ +return false; \ +} + static bool cont(GB_gameboy_t *gb, char *arguments) { + STOPPED_ONLY + if (strlen(lstrip(arguments))) { GB_log(gb, "Usage: continue\n"); return true; } + gb->debug_stopped = false; return false; } static bool next(GB_gameboy_t *gb, char *arguments) { + STOPPED_ONLY + if (strlen(lstrip(arguments))) { GB_log(gb, "Usage: next\n"); return true; @@ -630,6 +641,8 @@ static bool next(GB_gameboy_t *gb, char *arguments) static bool step(GB_gameboy_t *gb, char *arguments) { + STOPPED_ONLY + if (strlen(lstrip(arguments))) { GB_log(gb, "Usage: step\n"); return true; @@ -640,6 +653,8 @@ static bool step(GB_gameboy_t *gb, char *arguments) static bool finish(GB_gameboy_t *gb, char *arguments) { + STOPPED_ONLY + if (strlen(lstrip(arguments))) { GB_log(gb, "Usage: finish\n"); return true; @@ -653,6 +668,8 @@ static bool finish(GB_gameboy_t *gb, char *arguments) static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments) { + STOPPED_ONLY + if (strlen(lstrip(arguments))) { GB_log(gb, "Usage: sld\n"); return true; @@ -1329,6 +1346,34 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) _GB_debugger_test_read_watchpoint(gb, full_addr); } +/* Returns true if debugger waits for more commands */ +bool GB_debugger_do_command(GB_gameboy_t *gb, char *input) +{ + if (!input[0]) { + return true; + } + + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + else { + arguments = ""; + } + + const debugger_command_t *command = find_command(command_string); + if (command) { + return command->implementation(gb, arguments); + } + else { + GB_log(gb, "%s: no such command.\n", command_string); + return true; + } +} + void GB_debugger_run(GB_gameboy_t *gb) { char *input = NULL; @@ -1355,34 +1400,22 @@ next_command: gb->debug_fin_command = false; gb->stack_leak_detection = false; input = gb->input_callback(gb); - if (!input[0]) { + + if (GB_debugger_do_command(gb, input)) { goto next_command; } - char *command_string = input; - char *arguments = strchr(input, ' '); - if (arguments) { - /* Actually "split" the string. */ - arguments[0] = 0; - arguments++; - } - else { - arguments = ""; - } + free(input); + } +} - const debugger_command_t *command = find_command(command_string); - if (command) { - if (command->implementation(gb, arguments)) { - goto next_command; - } - } - else { - GB_log(gb, "%s: no such command.\n", command_string); - goto next_command; - } - - /* Split to arguments and command */ +void GB_debugger_handle_async_commands(GB_gameboy_t *gb) +{ + if (!gb->async_input_callback) return; + char *input = NULL; + while ((input = gb->async_input_callback(gb))) { + GB_debugger_do_command(gb, input); free(input); } } diff --git a/Core/debugger.h b/Core/debugger.h index 78f60cee..7e751855 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -3,6 +3,7 @@ #include "gb.h" void GB_debugger_run(GB_gameboy_t *gb); +void GB_debugger_handle_async_commands(GB_gameboy_t *gb); void GB_debugger_call_hook(GB_gameboy_t *gb); void GB_debugger_ret_hook(GB_gameboy_t *gb); void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); diff --git a/Core/display.c b/Core/display.c index c83d3987..06ff945f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -219,6 +219,8 @@ void display_vblank(GB_gameboy_t *gb) gb->last_vblank = nanoseconds; } } + + gb->vblank_just_occured = true; } static inline uint8_t scale_channel(uint8_t x) diff --git a/Core/gb.c b/Core/gb.c index 82847e46..1d341e05 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -430,6 +430,11 @@ void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) gb->input_callback = callback; } +void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ + gb->async_input_callback = callback; +} + void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) { gb->rgb_encode_callback = callback; diff --git a/Core/gb.h b/Core/gb.h index f5221ea2..24fc2917 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -339,6 +339,7 @@ typedef struct GB_gameboy_s { void *user_data; GB_log_callback_t log_callback; GB_input_callback_t input_callback; + GB_input_callback_t async_input_callback; GB_rgb_encode_callback_t rgb_encode_callback; GB_vblank_callback_t vblank_callback; @@ -368,6 +369,7 @@ typedef struct GB_gameboy_s { bool turbo; uint32_t ram_size; // Different between CGB and DMG uint8_t boot_rom[0x900]; + bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank } GB_gameboy_t; @@ -393,6 +395,7 @@ 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_async_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); diff --git a/Core/timing.c b/Core/timing.c index f07858da..9390c1c2 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -50,31 +50,30 @@ void GB_timers_run(GB_gameboy_t *gb) } } } +} - /* 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) +void GB_rtc_run(GB_gameboy_t *gb) +{ + 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_seconds = 0; - if (++gb->rtc_minutes == 60) + gb->rtc_minutes = 0; + if (++gb->rtc_hours == 24) { - gb->rtc_minutes = 0; - if (++gb->rtc_hours == 24) + gb->rtc_hours = 0; + if (++gb->rtc_days == 0) { - gb->rtc_hours = 0; - if (++gb->rtc_days == 0) + if (gb->rtc_high & 1) /* Bit 8 of days*/ { - if (gb->rtc_high & 1) /* Bit 8 of days*/ - { - gb->rtc_high |= 0x80; /* Overflow bit */ - } - gb->rtc_high ^= 1; + gb->rtc_high |= 0x80; /* Overflow bit */ } + gb->rtc_high ^= 1; } } } diff --git a/Core/timing.h b/Core/timing.h index 1e419091..75e66ee1 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -4,4 +4,5 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); void GB_timers_run(GB_gameboy_t *gb); +void GB_rtc_run(GB_gameboy_t *gb); #endif /* timing_h */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index d253ffb1..b57e979d 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1309,6 +1309,7 @@ static GB_opcode_t *opcodes[256] = { void GB_cpu_run(GB_gameboy_t *gb) { + gb->vblank_just_occured = false; bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; if (interrupt) { gb->halted = false; @@ -1340,4 +1341,9 @@ void GB_cpu_run(GB_gameboy_t *gb) else { GB_advance_cycles(gb, 4); } + + if (gb->vblank_just_occured) { + GB_rtc_run(gb); + GB_debugger_handle_async_commands(gb); + } } From b30822fd0b63b4999c7dbd07611f8f24e38ef298 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 18 Jul 2016 13:10:19 +0300 Subject: [PATCH 0102/1216] Async commands in SDL port, better handling of ^C and ^D --- Core/gb.c | 30 ++++++++++++++++++++++++++++-- SDL/main.c | 4 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 1d341e05..69453b9f 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "gb.h" #include "memory.h" #include "timing.h" @@ -52,8 +53,12 @@ static char *default_input_callback(GB_gameboy_t *gb) { char *expression = NULL; size_t size = 0; - printf(">"); - getline(&expression, &size, stdin); + + if (getline(&expression, &size, stdin) == -1) { + /* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */ + GB_set_async_input_callback(gb, NULL); /* Disable async input */ + return strdup("c"); + } if (!expression) { return strdup(""); @@ -66,6 +71,22 @@ static char *default_input_callback(GB_gameboy_t *gb) return expression; } +static char *default_async_input_callback(GB_gameboy_t *gb) +{ + fd_set set; + FD_ZERO(&set); + FD_SET(STDIN_FILENO, &set); + struct timeval time = {0,}; + if (select(1, &set, NULL, NULL, &time) == 1) { + if (feof(stdin)) { + GB_set_async_input_callback(gb, NULL); /* Disable async input */ + return NULL; + } + return default_input_callback(gb); + } + return NULL; +} + void GB_init(GB_gameboy_t *gb) { memset(gb, 0, sizeof(*gb)); @@ -88,6 +109,7 @@ void GB_init(GB_gameboy_t *gb) 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->async_input_callback = default_async_input_callback; gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_JOYP] = 0xF; @@ -112,6 +134,7 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->last_vblank = clock(); gb->cgb_ram_bank = 1; gb->input_callback = default_input_callback; + gb->async_input_callback = default_async_input_callback; gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_JOYP] = 0xF; @@ -427,6 +450,9 @@ void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback) void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) { + if (gb->input_callback == default_input_callback) { + gb->async_input_callback = NULL; + } gb->input_callback = callback; } diff --git a/SDL/main.c b/SDL/main.c index d61967d9..fbb3e332 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -134,6 +134,10 @@ GB_gameboy_t gb; static void debugger_interrupt(int ignore) { + /* ^C twice to exit */ + if (gb.debug_stopped) { + exit(0); + } gb.debug_stopped = true; } From da0911d69bc29f72e02ba7e4e7d3d2ba0e37fd58 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 18 Jul 2016 14:30:21 +0300 Subject: [PATCH 0103/1216] Fixed SDL crash --- Core/debugger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 4d278a42..b3846260 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1411,10 +1411,9 @@ next_command: void GB_debugger_handle_async_commands(GB_gameboy_t *gb) { - if (!gb->async_input_callback) return; char *input = NULL; - while ((input = gb->async_input_callback(gb))) { + while (gb->async_input_callback && (input = gb->async_input_callback(gb))) { GB_debugger_do_command(gb, input); free(input); } From 0fbc72f1971d2bc05d294f12638d73b813669e20 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 18 Jul 2016 14:37:06 +0300 Subject: [PATCH 0104/1216] SDL save states --- SDL/main.c | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index fbb3e332..da00977c 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -10,10 +10,17 @@ #include "debugger.h" static bool running = false; +static char *filename; +static void replace_extension(const char *src, size_t length, char *dest, const char *ext); +GB_gameboy_t gb; void GB_update_keys_status(GB_gameboy_t *gb) { static bool ctrl = false; + static bool shift = false; +#ifdef __APPLE__ + static bool cmd = false; +#endif SDL_Event event; while (SDL_PollEvent(&event)) { @@ -52,8 +59,20 @@ void GB_update_keys_status(GB_gameboy_t *gb) gb->turbo = event.type == SDL_KEYDOWN; break; case SDLK_LCTRL: + case SDLK_RCTRL: ctrl = event.type == SDL_KEYDOWN; break; + case SDLK_LSHIFT: + case SDLK_RSHIFT: + shift = event.type == SDL_KEYDOWN; + break; +#ifdef __APPLE__ + case SDLK_LMETA: + case SDLK_RMETA: + cmd = event.type == SDL_KEYDOWN; + break; +#endif + case SDLK_c: if (ctrl && event.type == SDL_KEYDOWN) { ctrl = false; @@ -63,6 +82,26 @@ void GB_update_keys_status(GB_gameboy_t *gb) break; default: + /* Save states */ + if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) { +#ifdef __APPLE__ + if (cmd) { +#else + if (ctrl) { +#endif + char save_path[strlen(filename) + 4]; + char save_extension[] =".s0"; + save_extension[2] += event.key.keysym.sym - SDLK_0; + replace_extension(filename, strlen(filename), save_path, save_extension); + + if (shift) { + GB_load_state(gb, save_path); + } + else { + GB_save_state(gb, save_path); + } + } + } break; } break; @@ -130,8 +169,6 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return SDL_MapRGB(screen->format, r, g, b); } -GB_gameboy_t gb; - static void debugger_interrupt(int ignore) { /* ^C twice to exit */ @@ -207,7 +244,9 @@ usage: } } - if (GB_load_rom(&gb, argv[argc - 1])) { + filename = argv[argc - 1]; + + if (GB_load_rom(&gb, filename)) { perror("Failed to load ROM"); exit(1); } @@ -227,16 +266,16 @@ usage: GB_set_pixels_output(&gb, screen->pixels); GB_set_rgb_encode_callback(&gb, rgb_encode); - size_t path_length = strlen(argv[argc - 1]); + size_t path_length = strlen(filename); /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ - replace_extension(argv[argc - 1], path_length, battery_save_path, ".sav"); + replace_extension(filename, path_length, battery_save_path, ".sav"); GB_load_battery(&gb, battery_save_path); /* Configure symbols */ char symbols_path[path_length + 5]; - replace_extension(argv[argc - 1], path_length, symbols_path, ".sym"); + replace_extension(filename, path_length, symbols_path, ".sym"); GB_debugger_load_symbol_file(&gb, symbols_path); /* Configure Audio */ From 1d35c04ab18e309cc65e705dbbc39ac272e7873b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 18 Jul 2016 22:05:11 +0300 Subject: [PATCH 0105/1216] Infrared API --- Core/gb.c | 10 ++++++++++ Core/gb.h | 9 ++++++++- Core/memory.c | 24 +++++++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 69453b9f..e512f213 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -466,6 +466,16 @@ void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callb gb->rgb_encode_callback = callback; } +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) +{ + gb->infrared_callback = callback; +} + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state) +{ + gb->infrared_input = state; +} + void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) { if (gb->audio_buffer) { diff --git a/Core/gb.h b/Core/gb.h index 24fc2917..563fef1f 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -155,6 +155,7 @@ 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, uint8_t r, uint8_t g, uint8_t b); +typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on); typedef struct { enum { @@ -210,6 +211,10 @@ typedef struct GB_gameboy_s { bool halted; bool stopped; bool boot_rom_finished; + + /* Misc state*/ + /* IR */ + bool infrared_input; ); /* HDMA */ @@ -342,6 +347,7 @@ typedef struct GB_gameboy_s { GB_input_callback_t async_input_callback; GB_rgb_encode_callback_t rgb_encode_callback; GB_vblank_callback_t vblank_callback; + GB_infrared_callback_t infrared_callback; /*** Debugger ***/ bool debug_stopped; @@ -398,5 +404,6 @@ void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); void GB_set_async_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); - +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); +void GB_set_infrared_input(GB_gameboy_t *gb, bool state); #endif /* GB_h */ diff --git a/Core/memory.c b/Core/memory.c index 647cd6f5..490cd87a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -160,7 +160,16 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E); - + case GB_IO_RP: { + if (!gb->is_cgb) return 0xFF; + /* You will read your own IR LED if it's on. */ + bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1); + uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; + if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) { + ret |= read_value; + } + return ret; + } default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { return GB_apu_read(gb, addr & 0xFF); @@ -440,6 +449,19 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } return; + case GB_IO_RP: { + if (!gb->is_cgb) { + return; + } + if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, value & 1); + } + } + gb->io_registers[GB_IO_RP] = value; + return; + } + default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { GB_apu_write(gb, addr & 0xFF, value); From b740b7f3bac1eceb1d8b025fd581528713b23edf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 20 Jul 2016 23:26:54 +0300 Subject: [PATCH 0106/1216] Fixed Cocoa memory leak --- Cocoa/Document.m | 1 + Cocoa/GBShader.m | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ea307fb8..3aec2201 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -126,6 +126,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) GB_run(&gb); } [self.audioClient stop]; + self.audioClient = nil; self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); stopping = false; diff --git a/Cocoa/GBShader.m b/Cocoa/GBShader.m index 200d0772..63bfaac0 100644 --- a/Cocoa/GBShader.m +++ b/Cocoa/GBShader.m @@ -160,6 +160,10 @@ void main(void) {\n\ glDeleteProgram(program); glDeleteTextures(1, &texture); glDeleteTextures(1, &previous_texture); + + /* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */ + /* We're probably not freeing thing in the right place. */ + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; } + (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type From 185e71fe12af816c8f38d3b22190e7a89cc1e918 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 21 Jul 2016 01:03:13 +0300 Subject: [PATCH 0107/1216] Improvements to IR API, since timing is VERY important --- Core/gb.c | 11 +++++++++++ Core/gb.h | 16 +++++++++++++++- Core/memory.c | 5 +++-- Core/timing.c | 14 ++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index e512f213..79848fb9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -474,6 +474,17 @@ void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) void GB_set_infrared_input(GB_gameboy_t *gb, bool state) { gb->infrared_input = state; + gb->cycles_since_input_ir_change = 0; + gb->ir_queue_length = 0; +} + +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change) +{ + if (gb->ir_queue_length == GB_MAX_IR_QUEUE) { + GB_log(gb, "IR Queue is full\n"); + return; + } + gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change}; } void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) diff --git a/Core/gb.h b/Core/gb.h index 563fef1f..9de63cd6 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -28,6 +28,8 @@ enum { GB_ZERO_FLAG = 128, }; +#define GB_MAX_IR_QUEUE 256 + enum { /* Joypad and Serial */ GB_IO_JOYP = 0x00, // Joypad (R/W) @@ -155,7 +157,7 @@ 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, uint8_t r, uint8_t g, uint8_t b); -typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on); +typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); typedef struct { enum { @@ -172,6 +174,11 @@ typedef struct { bool has_rumble; } GB_cartridge_t; +typedef struct { + bool state; + long delay; +} GB_ir_queue_item_t; + struct GB_breakpoint_s; struct GB_watchpoint_s; @@ -349,6 +356,12 @@ typedef struct GB_gameboy_s { GB_vblank_callback_t vblank_callback; GB_infrared_callback_t infrared_callback; + /* IR */ + long cycles_since_ir_change; + long cycles_since_input_ir_change; + GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; + size_t ir_queue_length; + /*** Debugger ***/ bool debug_stopped; bool debug_fin_command, debug_next_command; @@ -406,4 +419,5 @@ 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); void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); void GB_set_infrared_input(GB_gameboy_t *gb, bool state); +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); #endif /* GB_h */ diff --git a/Core/memory.c b/Core/memory.c index 490cd87a..89f67b53 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -166,7 +166,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1); uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) { - ret |= read_value; + ret |= 2; } return ret; } @@ -455,7 +455,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) { if (gb->infrared_callback) { - gb->infrared_callback(gb, value & 1); + gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); + gb->cycles_since_ir_change = 0; } } gb->io_registers[GB_IO_RP] = value; diff --git a/Core/timing.c b/Core/timing.c index 9390c1c2..beffa71f 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -3,6 +3,17 @@ #include "memory.h" #include "display.h" +static void GB_ir_run(GB_gameboy_t *gb) +{ + if (gb->ir_queue_length == 0) return; + if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) { + gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay; + gb->infrared_input = gb->ir_queue[0].state; + gb->ir_queue_length--; + memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length)); + } +} + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost @@ -24,10 +35,13 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->hdma_cycles += cycles; gb->display_cycles += cycles; gb->apu_cycles += cycles; + gb->cycles_since_ir_change += cycles; + gb->cycles_since_input_ir_change += cycles; GB_hdma_run(gb); GB_timers_run(gb); GB_apu_run(gb); GB_display_run(gb); + GB_ir_run(gb); } void GB_timers_run(GB_gameboy_t *gb) From e6d4cac00ee7e251b4b152c0b2d08e5cedaa6946 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 21 Jul 2016 15:20:25 +0300 Subject: [PATCH 0108/1216] Fix logical bug when changing watchpoint flags --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index b3846260..bddc9eaa 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -908,7 +908,7 @@ print_usage: uint16_t index = find_watchpoint(gb, result); if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true)); - if (!gb->watchpoints[index].flags != flags) { + if (gb->watchpoints[index].flags != flags) { GB_log(gb, "Modified watchpoint type\n"); gb->watchpoints[index].flags = flags; } From 47e3300b669742576b6dfd7edb3e2fc3f030cf96 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Aug 2016 23:31:10 +0300 Subject: [PATCH 0109/1216] Improved DMA accuracy, mooneyegb test ROMs no longer crash miserably. (but still fail) --- Core/gb.c | 4 ++-- Core/gb.h | 11 ++++++--- Core/memory.c | 65 +++++++++++++++++++++++++++++++++++++++++---------- Core/memory.h | 1 + Core/timing.c | 9 ++----- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 79848fb9..0a40c46a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -230,7 +230,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; if (!DUMP_SECTION(gb, f, core_state)) goto error; - if (!DUMP_SECTION(gb, f, hdma )) goto error; + if (!DUMP_SECTION(gb, f, dma )) goto error; if (!DUMP_SECTION(gb, f, mbc )) goto error; if (!DUMP_SECTION(gb, f, hram )) goto error; if (!DUMP_SECTION(gb, f, timing )) goto error; @@ -297,7 +297,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; if (!READ_SECTION(&save, f, core_state)) goto error; - if (!READ_SECTION(&save, f, hdma )) goto error; + if (!READ_SECTION(&save, f, dma )) goto error; if (!READ_SECTION(&save, f, mbc )) goto error; if (!READ_SECTION(&save, f, hram )) goto error; if (!READ_SECTION(&save, f, timing )) goto error; diff --git a/Core/gb.h b/Core/gb.h index 9de63cd6..3ad0cf07 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -224,13 +224,18 @@ typedef struct GB_gameboy_s { bool infrared_input; ); - /* HDMA */ - GB_SECTION(hdma, + /* DMA and HDMA */ + GB_SECTION(dma, bool hdma_on; bool hdma_on_hblank; uint8_t hdma_steps_left; uint16_t hdma_cycles; uint16_t hdma_current_src, hdma_current_dest; + + uint8_t dma_steps_left; + uint8_t dma_current_dest; + uint16_t dma_current_src; + uint16_t dma_cycles; ); /* MBC */ @@ -278,7 +283,7 @@ typedef struct GB_gameboy_s { uint32_t display_cycles; uint32_t div_cycles; uint32_t tima_cycles; - uint32_t dma_cycles; + GB_PADDING(uint32_t, dma_cycles); GB_aligned_double apu_cycles; ); diff --git a/Core/memory.c b/Core/memory.c index 89f67b53..d81c386b 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -10,6 +10,36 @@ typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +typedef enum { + GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */ + GB_BUS_RAM, /* In CGB only. */ + GB_BUS_VRAM, + GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */ +} GB_bus_t; + +static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x8000) { + return GB_BUS_MAIN; + } + if (addr < 0xA000) { + return GB_BUS_VRAM; + } + if (addr < 0xC000) { + return GB_BUS_MAIN; + } + if (addr < 0xFE00) { + return gb->is_cgb? GB_BUS_RAM : GB_BUS_MAIN; + } + return GB_BUS_INTERNAL; +} + +static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) +{ + if (!gb->dma_steps_left) return false; + return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); +} + static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0x100 && !gb->boot_rom_finished) { @@ -76,7 +106,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFEA0) { - if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2 || gb->dma_steps_left) { return 0xFF; } return gb->oam[addr & 0xFF]; @@ -202,9 +232,8 @@ static GB_read_function_t * const read_map[] = uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { GB_debugger_test_read_watchpoint(gb, addr); - if (addr < 0xFF00 && gb->dma_cycles) { - /* Todo: can we access IO registers during DMA? */ - return 0xFF; + if (is_addr_in_dma_use(gb, addr)) { + addr = gb->dma_current_src; } return read_map[addr >> 12](gb, addr); } @@ -297,7 +326,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (addr < 0xFEA0) { - if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2 || gb->dma_steps_left) { return; } gb->oam[addr & 0xFF] = value; @@ -371,13 +400,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_DMA: if (value <= 0xF1) { /* According to Pan Docs */ - for (uint8_t i = 0xA0; i--;) { - gb->oam[i] = GB_read_memory(gb, (value << 8) + i); - } + gb->dma_cycles = 0; + gb->dma_current_dest = 0; + gb->dma_current_src = value << 8; + gb->dma_steps_left = 0xa0; } /* else { what? } */ - /* Todo: measure this value */ - gb->dma_cycles = 640; + return; case GB_IO_SVBK: if (!gb->cgb_mode) { @@ -501,13 +530,25 @@ static GB_write_function_t * const write_map[] = void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { GB_debugger_test_write_watchpoint(gb, addr, value); - if (addr < 0xFF00 && gb->dma_cycles) { - /* Todo: can we access IO registers during DMA? */ + if (is_addr_in_dma_use(gb, addr)) { + /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ return; } write_map[addr >> 12](gb, addr, value); } +void GB_dma_run(GB_gameboy_t *gb) +{ + while (gb->dma_cycles >= 4 && gb->dma_steps_left) { + /* Todo: measure this value */ + gb->dma_cycles -= 4; + gb->dma_steps_left--; + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); + /* dma_current_src must be the correct value during GB_read_memory */ + gb->dma_current_src++; + } +} + void GB_hdma_run(GB_gameboy_t *gb) { if (!gb->hdma_on) return; diff --git a/Core/memory.h b/Core/memory.h index f904f8c1..b106742e 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -4,6 +4,7 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +void GB_dma_run(GB_gameboy_t *gb); void GB_hdma_run(GB_gameboy_t *gb); #endif /* memory_h */ diff --git a/Core/timing.c b/Core/timing.c index beffa71f..babafb59 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -17,13 +17,7 @@ static void GB_ir_run(GB_gameboy_t *gb) void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost - if (gb->dma_cycles > cycles){ - gb->dma_cycles -= cycles; - } - else { - gb->dma_cycles = 0; - } - + gb->dma_cycles += cycles; gb->div_cycles += cycles; gb->tima_cycles += cycles; @@ -37,6 +31,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->apu_cycles += cycles; gb->cycles_since_ir_change += cycles; gb->cycles_since_input_ir_change += cycles; + GB_dma_run(gb); GB_hdma_run(gb); GB_timers_run(gb); GB_apu_run(gb); From d098458ee45bdc65666b2f38dfcf572ebb8a5c06 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Aug 2016 16:36:38 +0300 Subject: [PATCH 0110/1216] Major improvements to accuracy: Fixed instruction timing, DMA timing, and IO reg masking. Passes most of mooneye-gb acceptance tests. --- Core/gb.h | 3 +- Core/memory.c | 34 +++++++++++---- Core/z80_cpu.c | 111 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 108 insertions(+), 40 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 3ad0cf07..53b5dea8 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -207,7 +207,7 @@ typedef struct GB_gameboy_s { /* Registers */ uint16_t pc; uint16_t registers[GB_REGISTERS_16_BIT]; - bool ime; + uint8_t ime; uint8_t interrupt_enable; uint8_t cgb_ram_bank; @@ -218,6 +218,7 @@ typedef struct GB_gameboy_s { bool halted; bool stopped; bool boot_rom_finished; + bool ime_toggle; /* ei (and di in CGB) have delayed effects.*/ /* Misc state*/ /* IR */ diff --git a/Core/memory.c b/Core/memory.c index d81c386b..c8b5e685 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -129,7 +129,21 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_STAT: return gb->io_registers[GB_IO_STAT] | 0x80; case GB_IO_DMG_EMULATION_INDICATION: + if (!gb->is_cgb) { + return 0xFF; + } return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE; + + 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: + if (!gb->is_cgb) { + return 0xFF; + } + /* Fall through */ case GB_IO_JOYP: case GB_IO_DIV: case GB_IO_TIMA: @@ -144,15 +158,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) 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: + if (!gb->is_cgb) { + return 0xFF; + } return (gb->io_registers[GB_IO_HDMA5] & 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); case GB_IO_SVBK: if (!gb->cgb_mode) { @@ -380,6 +391,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: + gb->div_cycles = 0; gb->io_registers[GB_IO_DIV] = 0; return; @@ -507,7 +519,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (addr == 0xFFFF) { /* Interrupt mask */ - gb->interrupt_enable = value & 0x1F; + gb->interrupt_enable = value; return; } @@ -539,7 +551,9 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) void GB_dma_run(GB_gameboy_t *gb) { - while (gb->dma_cycles >= 4 && gb->dma_steps_left) { + /* + 1 as a compensation over the fact that DMA is never started in the first internal cycle of an opcode, + and SameBoy isn't sub-cycle accurate (yet?) . */ + while (gb->dma_cycles >= 4 + 1 && gb->dma_steps_left) { /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; @@ -552,7 +566,9 @@ void GB_dma_run(GB_gameboy_t *gb) void GB_hdma_run(GB_gameboy_t *gb) { if (!gb->hdma_on) return; - while (gb->hdma_cycles >= 8) { + /* + 1 as a compensation over the fact that HDMA is never started in the first internal cycle of an opcode, + and SameBoy isn't sub-cycle accurate (yet?) . */ + while (gb->hdma_cycles >= 8 + 1) { gb->hdma_cycles -= 8; // The CGB boot rom uses the dest in "absolute" space, while some games use it relative to VRAM. // This "normalizes" the dest to the CGB address space. diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index b57e979d..35d3cd5b 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -61,9 +61,11 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 8); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + gb->pc++; GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); } static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -183,10 +185,12 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 8); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + register_id = (opcode >> 4) + 1; + GB_advance_cycles(gb, 4); + gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[register_id]) << 8; + GB_advance_cycles(gb, 4); } static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -713,10 +717,13 @@ static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 12); - register_id = ((GB_read_memory(gb, gb->pc++) >> 4) + 1) & 3; - gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | - (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 3; + gb->pc++; + GB_advance_cycles(gb, 4); + gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->registers[register_id] |= GB_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; } @@ -725,8 +732,13 @@ static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { gb->pc++; if (condition_code(gb, opcode)) { - GB_advance_cycles(gb, 16); - gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 4); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = addr; + } else { GB_advance_cycles(gb, 12); @@ -736,20 +748,30 @@ static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 16); gb->pc++; - gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); -} + GB_advance_cycles(gb, 4); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = addr;} static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { gb->pc++; if (condition_code(gb, opcode)) { - GB_advance_cycles(gb, 24); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); - gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = addr; + GB_debugger_call_hook(gb); } else { @@ -761,12 +783,14 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 16); + GB_advance_cycles(gb, 8); gb->pc++; register_id = ((opcode >> 4) + 1) & 3; gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + GB_advance_cycles(gb, 4); } static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) @@ -910,10 +934,12 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 16); + GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 1) & 0xFF); GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 1) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 1) & 0xFF); + GB_advance_cycles(gb, 4); gb->pc = opcode ^ 0xC7; GB_debugger_call_hook(gb); } @@ -921,9 +947,11 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) static void ret(GB_gameboy_t *gb, uint8_t opcode) { GB_debugger_ret_hook(gb); - GB_advance_cycles(gb, 16); - gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | - (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + GB_advance_cycles(gb, 4); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_SP] += 2; } @@ -935,12 +963,18 @@ static void reti(GB_gameboy_t *gb, uint8_t opcode) static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 24); gb->pc++; + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); - gb->pc = GB_read_memory(gb, gb->pc) | (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = addr; GB_debugger_call_hook(gb); } @@ -982,9 +1016,10 @@ static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; uint16_t sp = gb->registers[GB_REGISTER_SP]; - GB_advance_cycles(gb, 16); + GB_advance_cycles(gb, 4); gb->pc++; offset = (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 12); gb->registers[GB_REGISTER_SP] += offset; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1030,23 +1065,30 @@ static void di(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); gb->pc++; - gb->ime = false; + + /* di is delayed in CGB */ + if (!gb->is_cgb) { + gb->ime = false; + } } static void ei(GB_gameboy_t *gb, uint8_t opcode) { + /* ei is actually "disable interrupts for one instruction, then enable them". */ GB_advance_cycles(gb, 4); gb->pc++; - gb->ime = true; + gb->ime = false; + gb->ime_toggle = true; } static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; - GB_advance_cycles(gb, 12); + GB_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF00; offset = (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { @@ -1321,6 +1363,10 @@ void GB_cpu_run(GB_gameboy_t *gb) } if (gb->ime && interrupt) { + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } uint8_t interrupt_bit = 0; uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; while (!(interrupt_queue & 1)) { @@ -1329,12 +1375,17 @@ void GB_cpu_run(GB_gameboy_t *gb) } gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); gb->ime = false; + gb->ime_toggle = false; nop(gb, 0); gb->pc -= 2; /* Run pseudo instructions rst 40-60*/ rst(gb, 0x87 + interrupt_bit * 8); } else if(!gb->halted && !gb->stopped) { + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } uint8_t opcode = GB_read_memory(gb, gb->pc); opcodes[opcode](gb, opcode); } From 55cbe5d4d0972213d37abf4e7afbb33d9542d730 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Aug 2016 17:22:12 +0300 Subject: [PATCH 0111/1216] Accuracy improvements to timers --- Core/debugger.c | 1 - Core/gb.h | 3 ++- Core/memory.c | 10 ++++++-- Core/timing.c | 66 ++++++++++++++++++++++++++++++++++++------------- Core/timing.h | 3 ++- 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index bddc9eaa..cb7e7ea0 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -695,7 +695,6 @@ static bool registers(GB_gameboy_t *gb, char *arguments) GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); - GB_log(gb, "TIMA = %d/%u\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles); GB_log(gb, "Display Controller: LY = %d/%u\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456); return true; } diff --git a/Core/gb.h b/Core/gb.h index 53b5dea8..52890cf6 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -142,6 +142,7 @@ enum { #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 #define DIV_CYCLES (0x100) +#define INTERNAL_DIV_CYCLES (0x400) #define FRAME_LENGTH 16742706 // in nanoseconds typedef enum { @@ -283,7 +284,7 @@ typedef struct GB_gameboy_s { int64_t last_vblank; uint32_t display_cycles; uint32_t div_cycles; - uint32_t tima_cycles; + GB_PADDING(uint32_t, tima_cycles); GB_PADDING(uint32_t, dma_cycles); GB_aligned_double apu_cycles; ); diff --git a/Core/memory.c b/Core/memory.c index c8b5e685..e4fb9a88 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -6,6 +6,7 @@ #include "memory.h" #include "debugger.h" #include "mbc.h" +#include "timing.h" typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); @@ -353,7 +354,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Hardware registers */ switch (addr & 0xFF) { - case GB_IO_TAC: case GB_IO_SCX: case GB_IO_IF: case GB_IO_TIMA: @@ -374,6 +374,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[addr & 0xFF] = value; return; + case GB_IO_TAC: + GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value); + gb->io_registers[GB_IO_TAC] = value; + return; + + case GB_IO_LCDC: if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->display_cycles = 0; @@ -391,7 +397,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: - gb->div_cycles = 0; + GB_set_internal_div_counter(gb, 0); gb->io_registers[GB_IO_DIV] = 0; return; diff --git a/Core/timing.c b/Core/timing.c index babafb59..4b15da1c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -18,8 +18,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost gb->dma_cycles += cycles; - gb->div_cycles += cycles; - gb->tima_cycles += cycles; + + for (int i = 0; i < cycles; i += 4) { + GB_set_internal_div_counter(gb, gb->div_cycles + 4); + } if (gb->cgb_double_speed) { cycles >>=1; @@ -33,30 +35,60 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_input_ir_change += cycles; GB_dma_run(gb); GB_hdma_run(gb); - GB_timers_run(gb); GB_apu_run(gb); GB_display_run(gb); GB_ir_run(gb); } -void GB_timers_run(GB_gameboy_t *gb) -{ - /* Standard Timers */ - static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; +/* Standard Timers */ +static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; - if (gb->div_cycles >= DIV_CYCLES) { - gb->div_cycles -= DIV_CYCLES; +static void increase_tima(GB_gameboy_t *gb) +{ + 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; + } +} + +static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) +{ + return (old & (max >> 1)) && !(new & (max >> 1)); +} + +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +{ + /* DIV and TIMA increase when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + if (counter_overflow_check(gb->div_cycles, value, DIV_CYCLES)) { gb->io_registers[GB_IO_DIV]++; } + if ((gb->io_registers[GB_IO_TAC] & 4) && + counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + gb->div_cycles = value; +} - 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; - } +/* + This glitch is based on the expected results of mooneye-gb rapid_toggle test. + This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. + According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented. +*/ +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) +{ + /* Glitch only happens when old_tac is enabled. */ + if (!(old_tac & 4)) return; + + unsigned int old_clocks = GB_TAC_RATIOS[old_tac & 3]; + unsigned int new_clocks = GB_TAC_RATIOS[new_tac & 3]; + + /* The bit used for overflow testing must have been 1 */ + if (gb->div_cycles & (old_clocks >> 1)) { + /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ + if (!(new_tac & 4) || gb->div_cycles & (new_clocks >> 1)) { + increase_tima(gb); } } } diff --git a/Core/timing.h b/Core/timing.h index 75e66ee1..426ad9d9 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -3,6 +3,7 @@ #include "gb.h" void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void GB_timers_run(GB_gameboy_t *gb); +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value); void GB_rtc_run(GB_gameboy_t *gb); +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); #endif /* timing_h */ From 0f98ac5ff9740f5652adc640cc5124502d4c1a65 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 13:56:29 +0300 Subject: [PATCH 0112/1216] Emulate TIMA reloading --- Core/gb.h | 7 +++++++ Core/memory.c | 21 ++++++++++++++++++--- Core/timing.c | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 52890cf6..aa8a62d0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -193,6 +193,12 @@ struct GB_watchpoint_s; /* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */ _Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1"); +enum { + GB_TIMA_RUNNING = 0, + GB_TIMA_RELOADING = 1, + GB_TIMA_RELOADED = 2 +}; + typedef struct GB_gameboy_s { GB_SECTION(header, /* The magic makes sure a state file is: @@ -287,6 +293,7 @@ typedef struct GB_gameboy_s { GB_PADDING(uint32_t, tima_cycles); GB_PADDING(uint32_t, dma_cycles); GB_aligned_double apu_cycles; + uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ ); /* APU */ diff --git a/Core/memory.c b/Core/memory.c index e4fb9a88..1038f38a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -147,7 +147,6 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) /* Fall through */ case GB_IO_JOYP: case GB_IO_DIV: - case GB_IO_TIMA: case GB_IO_TMA: case GB_IO_LCDC: case GB_IO_SCY: @@ -161,6 +160,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_WX: case GB_IO_SB: return gb->io_registers[addr & 0xFF]; + case GB_IO_TIMA: + if (gb->tima_reload_state == GB_TIMA_RELOADING) { + return 0; + } + return gb->io_registers[GB_IO_TIMA]; case GB_IO_HDMA5: if (!gb->is_cgb) { return 0xFF; @@ -356,8 +360,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) 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: @@ -373,6 +375,19 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_DMG_EMULATION_INDICATION: gb->io_registers[addr & 0xFF] = value; return; + + case GB_IO_TIMA: + if (gb->tima_reload_state != GB_TIMA_RELOADED) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TMA: + gb->io_registers[GB_IO_TMA] = value; + if (gb->tima_reload_state == GB_TIMA_RELOADED) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; case GB_IO_TAC: GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value); diff --git a/Core/timing.c b/Core/timing.c index 4b15da1c..48f28dcf 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -14,15 +14,33 @@ static void GB_ir_run(GB_gameboy_t *gb) } } +static void advance_tima_state_machine(GB_gameboy_t *gb) +{ + if (gb->tima_reload_state == GB_TIMA_RELOADED) { + gb->tima_reload_state = GB_TIMA_RUNNING; + } + else if (gb->tima_reload_state == GB_TIMA_RELOADING) { + gb->tima_reload_state = GB_TIMA_RELOADED; + } +} + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost gb->dma_cycles += cycles; + advance_tima_state_machine(gb); for (int i = 0; i < cycles; i += 4) { GB_set_internal_div_counter(gb, gb->div_cycles + 4); } + if (cycles > 4) { + advance_tima_state_machine(gb); + if (cycles > 8) { + advance_tima_state_machine(gb); + } + } + if (gb->cgb_double_speed) { cycles >>=1; } @@ -49,6 +67,7 @@ static void increase_tima(GB_gameboy_t *gb) 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; + gb->tima_reload_state = GB_TIMA_RELOADING; } } From 8dd5462525ea084436b4bb3bf6380f819dd9e6d3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 13:57:38 +0300 Subject: [PATCH 0113/1216] Correct DMA timing --- Core/gb.h | 2 +- Core/memory.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index aa8a62d0..2f46ef00 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -243,7 +243,7 @@ typedef struct GB_gameboy_s { uint8_t dma_steps_left; uint8_t dma_current_dest; uint16_t dma_current_src; - uint16_t dma_cycles; + int16_t dma_cycles; ); /* MBC */ diff --git a/Core/memory.c b/Core/memory.c index 1038f38a..a014cb94 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -432,8 +432,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DMA: - if (value <= 0xF1) { /* According to Pan Docs */ - gb->dma_cycles = 0; + if (value <= 0xE0) { + gb->dma_cycles = -7; gb->dma_current_dest = 0; gb->dma_current_src = value << 8; gb->dma_steps_left = 0xa0; @@ -574,7 +574,7 @@ void GB_dma_run(GB_gameboy_t *gb) { /* + 1 as a compensation over the fact that DMA is never started in the first internal cycle of an opcode, and SameBoy isn't sub-cycle accurate (yet?) . */ - while (gb->dma_cycles >= 4 + 1 && gb->dma_steps_left) { + while (gb->dma_cycles >= 4 && gb->dma_steps_left) { /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; @@ -589,7 +589,7 @@ void GB_hdma_run(GB_gameboy_t *gb) if (!gb->hdma_on) return; /* + 1 as a compensation over the fact that HDMA is never started in the first internal cycle of an opcode, and SameBoy isn't sub-cycle accurate (yet?) . */ - while (gb->hdma_cycles >= 8 + 1) { + while (gb->hdma_cycles >= 8) { gb->hdma_cycles -= 8; // The CGB boot rom uses the dest in "absolute" space, while some games use it relative to VRAM. // This "normalizes" the dest to the CGB address space. From 4a50000e83f4be2a5a461e710d644264f7dc1127 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 13:58:19 +0300 Subject: [PATCH 0114/1216] Corrected timing for many instructions --- Core/z80_cpu.c | 158 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 54 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 35d3cd5b..4b0ec417 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -51,10 +51,13 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; uint16_t value; - GB_advance_cycles(gb, 12); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); value |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); gb->registers[register_id] = value; } @@ -72,7 +75,8 @@ static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; GB_advance_cycles(gb, 8); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + register_id = (opcode >> 4) + 1; + gb->pc++; gb->registers[register_id]++; } @@ -115,11 +119,12 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] &= 0xFF; gb->registers[register_id] |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); } static void rlca(GB_gameboy_t *gb, uint8_t opcode) @@ -150,14 +155,20 @@ static void rla(GB_gameboy_t *gb, uint8_t opcode) } } -static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode){ +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify order is correct */ uint16_t addr; - GB_advance_cycles(gb, 20); + GB_advance_cycles(gb, 4); gb->pc++; addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); GB_write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + GB_advance_cycles(gb, 4); GB_write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); + GB_advance_cycles(gb, 4); } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -197,7 +208,8 @@ static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; GB_advance_cycles(gb, 8); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + register_id = (opcode >> 4) + 1; + gb->pc++; gb->registers[register_id]--; } @@ -206,7 +218,8 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; uint8_t value; GB_advance_cycles(gb, 4); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + register_id = (opcode >> 4) + 1; + gb->pc++; value = (gb->registers[register_id] & 0xFF) + 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; @@ -226,7 +239,8 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; uint8_t value; GB_advance_cycles(gb, 4); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + register_id = (opcode >> 4) + 1; + gb->pc++; value = (gb->registers[register_id] & 0xFF) - 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; @@ -246,10 +260,12 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 8); - register_id = (GB_read_memory(gb, gb->pc++) >> 4) + 1; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + gb->pc++; gb->registers[register_id] &= 0xFF00; gb->registers[register_id] |= GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); } static void rrca(GB_gameboy_t *gb, uint8_t opcode) @@ -282,9 +298,11 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode) static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 12); + /* Todo: Verify cycles are not 8 and 4 instead */ + GB_advance_cycles(gb, 4); gb->pc++; gb->pc += (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); } static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) @@ -305,9 +323,11 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { - if (condition_code(gb, GB_read_memory(gb, gb->pc++))) { - GB_advance_cycles(gb, 12); + gb->pc++; + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); gb->pc += (int8_t)GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); } else { GB_advance_cycles(gb, 8); @@ -395,42 +415,47 @@ static void ccf(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); } static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); } static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; + GB_advance_cycles(gb, 4); } static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; + GB_advance_cycles(gb, 4); } static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((value & 0x0F) == 0) { @@ -445,11 +470,12 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; @@ -464,9 +490,12 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 12); + GB_advance_cycles(gb, 4); gb->pc++; - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], GB_read_memory(gb, gb->pc++)); + uint8_t data = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], data); + GB_advance_cycles(gb, 4); } uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) @@ -479,8 +508,9 @@ uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) if (src_low) { return gb->registers[GB_REGISTER_AF] >> 8; } + uint8_t ret = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); GB_advance_cycles(gb, 4); - return GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); + return ret; } if (src_low) { return gb->registers[src_register_id] & 0xFF; @@ -501,8 +531,8 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) gb->registers[GB_REGISTER_AF] |= value << 8; } else { - GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); } } else { @@ -529,16 +559,14 @@ static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode) 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 { - GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); } } else { @@ -702,11 +730,14 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) { + /* Todo: Verify timing */ if (condition_code(gb, GB_read_memory(gb, gb->pc++))) { GB_debugger_ret_hook(gb); - GB_advance_cycles(gb, 20); - gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | - (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_SP] += 2; } else { @@ -720,10 +751,10 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 3; gb->pc++; - GB_advance_cycles(gb, 4); gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); GB_advance_cycles(gb, 4); gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 4); 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; } @@ -796,9 +827,10 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode) static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a + value) << 8; if ((uint8_t) (a + value) == 0) { @@ -815,9 +847,10 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); 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; @@ -836,9 +869,10 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; if (a == value) { @@ -855,9 +889,10 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); 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; @@ -876,9 +911,10 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; if ((a & value) == 0) { @@ -889,9 +925,10 @@ static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; if ((a ^ value) == 0) { @@ -902,9 +939,10 @@ static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a | value) << 8; if ((a | value) == 0) { @@ -915,9 +953,10 @@ static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; @@ -980,36 +1019,40 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; uint8_t temp = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); } static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; gb->pc++; uint8_t temp = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + temp) << 8; + GB_advance_cycles(gb, 4); } static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; GB_write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); } static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; gb->pc++; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; + GB_advance_cycles(gb, 4); } static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) @@ -1043,22 +1086,28 @@ static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; - GB_advance_cycles(gb, 16); + GB_advance_cycles(gb, 4); gb->pc++; addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); GB_write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); } static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; - GB_advance_cycles(gb, 16); + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; gb->pc++; addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8 ; + GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, addr) << 8; + GB_advance_cycles(gb, 4); } static void di(GB_gameboy_t *gb, uint8_t opcode) @@ -1111,7 +1160,7 @@ static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; @@ -1129,7 +1178,7 @@ static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x01) != 0; @@ -1149,7 +1198,7 @@ static void rl_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; uint8_t value; bool bit7; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; @@ -1172,7 +1221,7 @@ static void rr_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool bit1; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; @@ -1193,7 +1242,7 @@ static void sla_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; bool carry; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; @@ -1211,7 +1260,7 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t bit7; uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); bit7 = value & 0x80; @@ -1229,7 +1278,7 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1245,7 +1294,7 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode) static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1259,7 +1308,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; uint8_t bit; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 4); gb->pc++; value = get_src_value(gb, opcode); bit = 1 << ((opcode >> 3) & 7); @@ -1280,6 +1329,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) { + GB_advance_cycles(gb, 4); opcode = GB_read_memory(gb, ++gb->pc); switch (opcode >> 3) { case 0: From 85a33ed8ef6019c1b04a29f8df79dfcf6b5dbab5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 14:24:43 +0300 Subject: [PATCH 0115/1216] Emulating DMA delay correctly --- Core/gb.h | 1 + Core/memory.c | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 2f46ef00..ae461bc4 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -244,6 +244,7 @@ typedef struct GB_gameboy_s { uint8_t dma_current_dest; uint16_t dma_current_src; int16_t dma_cycles; + bool is_dma_restarting; ); /* MBC */ diff --git a/Core/memory.c b/Core/memory.c index a014cb94..f34fe865 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -37,7 +37,7 @@ static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) { - if (!gb->dma_steps_left) return false; + if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting)) return false; return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); } @@ -107,7 +107,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFEA0) { - if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2 || gb->dma_steps_left) { + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2 || (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { return 0xFF; } return gb->oam[addr & 0xFF]; @@ -342,7 +342,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (addr < 0xFEA0) { - if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2 || gb->dma_steps_left) { + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2 || (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { return; } gb->oam[addr & 0xFF] = value; @@ -433,6 +433,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_DMA: if (value <= 0xE0) { + if (gb->dma_steps_left) { + /* This is not correct emulation, since we're not really delaying the second DMA. + One write that should have happened in the first DMA will not happen. However, + since that byte will be overwritten by the second DMA before it can actually be + read, it doesn't actually matter. */ + gb->is_dma_restarting = true; + } gb->dma_cycles = -7; gb->dma_current_dest = 0; gb->dma_current_src = value << 8; @@ -572,8 +579,6 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) void GB_dma_run(GB_gameboy_t *gb) { - /* + 1 as a compensation over the fact that DMA is never started in the first internal cycle of an opcode, - and SameBoy isn't sub-cycle accurate (yet?) . */ while (gb->dma_cycles >= 4 && gb->dma_steps_left) { /* Todo: measure this value */ gb->dma_cycles -= 4; @@ -581,14 +586,15 @@ void GB_dma_run(GB_gameboy_t *gb) gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); /* dma_current_src must be the correct value during GB_read_memory */ gb->dma_current_src++; + if (!gb->dma_steps_left) { + gb->is_dma_restarting = false; + } } } void GB_hdma_run(GB_gameboy_t *gb) { if (!gb->hdma_on) return; - /* + 1 as a compensation over the fact that HDMA is never started in the first internal cycle of an opcode, - and SameBoy isn't sub-cycle accurate (yet?) . */ while (gb->hdma_cycles >= 8) { gb->hdma_cycles -= 8; // The CGB boot rom uses the dest in "absolute" space, while some games use it relative to VRAM. From d03a1fbd16e3ed7ab205803ba8ab348d5e89e31d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 14:36:33 +0300 Subject: [PATCH 0116/1216] Fixed TMA writing while reloading. --- Core/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index f34fe865..c12c1861 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -384,7 +384,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_TMA: gb->io_registers[GB_IO_TMA] = value; - if (gb->tima_reload_state == GB_TIMA_RELOADED) { + if (gb->tima_reload_state != GB_TIMA_RUNNING) { gb->io_registers[GB_IO_TIMA] = value; } return; From 553f700b794edab2ce0726251038627e18f2f505 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 15:57:32 +0300 Subject: [PATCH 0117/1216] Fixed needless deep generation, which caused errors when compiling the Cocoa GUI when SDL is not installed --- Makefile | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 04a02a87..ebd2d3f6 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,16 @@ ifeq ($(shell uname -s),Darwin) -default: cocoa +DEFAULT := cocoa else -default: sdl +DEFAULT := sdl endif VERSION := 0.5 +default: $(DEFAULT) + +ifeq ($(MAKECMDGOALS),) +MAKECMDGOALS := $(DEFAULT) +endif + BIN := build/bin OBJ := build/obj @@ -50,8 +56,15 @@ SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES)) ALL_OBJECTS := $(CORE_OBJECTS) $(COCOA_OBJECTS) $(SDL_OBJECTS) # Automatic dependency generation - --include $(ALL_OBJECTS:.o=.dep) +ifneq ($(MAKECMDGOALS),clean) +-include $(CORE_OBJECTS:.o=.dep) +ifneq ($(filter $(MAKECMDGOALS),sdl),) +-include $(SDL_OBJECTS:.o=.dep) +endif +ifneq ($(filter $(MAKECMDGOALS),cocoa),) +-include $(COCOA_OBJECTS:.o=.dep) +endif +endif $(OBJ)/%.dep: % -@mkdir -p $(dir $@) From 722550c5bcfef16e9e181f2a975fbbc2ef9e16b1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 16:18:23 +0300 Subject: [PATCH 0118/1216] Enabled link time optimization when building in release, improving speed by about 6% --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ebd2d3f6..0559bedc 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,8 @@ endif ifeq ($(CONF),debug) CFLAGS += -g else ifeq ($(CONF), release) -CFLAGS += -O3 +CFLAGS += -O3 -flto +LDFLAGS += -flto else $(error Invalid value for CONF: $(CONF). Use "debug" or "release") endif From 68740c70e4c95c439db7854524e670bc9fd451e6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 16:19:04 +0300 Subject: [PATCH 0119/1216] Stripping executables on release to reduce file size --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 0559bedc..6a1a55f3 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,9 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@mkdir -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit +ifeq ($(CONF), release) + strip $@ +endif $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib ibtool --compile $@ $^ @@ -110,6 +113,9 @@ $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib $(BIN)/sdl/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) -@mkdir -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) +ifeq ($(CONF), release) + strip $@ +endif $(BIN)/BootROMs/%.bin: BootROMs/%.asm -@mkdir -p $(dir $@) From e95d2c4abed179763edfa8f317667aa23b33f602 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 17:16:16 +0300 Subject: [PATCH 0120/1216] Fixed DI instruction on CGB --- Core/z80_cpu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 4b0ec417..1d490a38 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1119,6 +1119,9 @@ static void di(GB_gameboy_t *gb, uint8_t opcode) if (!gb->is_cgb) { gb->ime = false; } + else if (gb->ime) { + gb->ime_toggle = true; + } } static void ei(GB_gameboy_t *gb, uint8_t opcode) From 5816b6a688daa77af55f5ecef9645f715b8e6363 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 16:55:01 +0300 Subject: [PATCH 0121/1216] Updated change log and incremented version to 0.6 --- CHANGES.md | 39 +++++++++++++++++++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f4ae4e22..10c9df11 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,44 @@ # Change Log +## Version 0.6 + +### New/Improved Features + * Conditional r/w/rw debugger watchpoints + * Added the "!=" operator to the debugger + * Redefined the debugger input and output syntax, being more consistent with RGBDS + * Debugger now reads .sym files (Tested with 3 popular formats). It shows symbols when possible, and allows using them in expressions + * Debugger data types can now be either 16-bit values, or 25-bit full addresses that include a bank identifier + * Debugger commands and expressions support full addresses as parameters. This allows, for example, breaking on an address on a specific ROM bank, or reading a value from a specific MBC RAM bank. + * Some debugger commands may now run without breaking the debugger. (Similar to LLDB or GDB's async mode) + * SDL port now supports save states + * Improved performance by about 6% by enabling link-time optimizations + * Reduced file size by stripping executables + * Several Cocoa-only UX improvements: + * Prevented being paused "twice" (Both by the pause option and the debugger) + * Optional proportional resizing + * Proper fullscreen support + * Mouse cursor is now hidden while running + +### Accuracy Improvements/Fixes +This version includes major accuracy improvements, which allow it to pass 54 tests out of [mooneye-gb](https://github.com/Gekkio/mooneye-gb)'s 58 acceptance tests (2 of which fail due to not including the original boot ROM); more than any other emulator. + + * Rewritten MBC support, with MBC1M support (Fixing some N-in-1 cartridges) + * Major accuracy improvements to OAM DMA. + * Corrected a lot of instruction memory-access timings + * Corrected some IO register masks + * Major accuracy improvements to timers (TIMA/DIV) + +### Bug Fixes + * Corrected operator priorities in the debugger + * Fixed a bug where a breakpoint might have been ignored + * Reduced CPU usage when running games with a real time clock + * Handling ^C and ^D more sanely in SDL port + * Fixed memory leak in Cocoa + +### Misc Internal Changes + * Added infrared API, but it is not actually used by any of the GUIs + * Fixed build system bugs that caused needless dep file generation, and made the Cocoa build fail sometimes if SDL is not installed + ## Version 0.5 This version is not compatible with save states of older versions. diff --git a/Makefile b/Makefile index 6a1a55f3..f28b3512 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,13 @@ else DEFAULT := sdl endif -VERSION := 0.5 default: $(DEFAULT) ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif +VERSION := 0.6 BIN := build/bin OBJ := build/obj From af10e07ed76ee5654b8018631c15c6e3b244d8fe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 18:57:13 +0300 Subject: [PATCH 0122/1216] Initing OBP0/1 correctly --- Core/gb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 0a40c46a..1ec611b7 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -112,6 +112,8 @@ void GB_init(GB_gameboy_t *gb) gb->async_input_callback = default_async_input_callback; gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; + gb->io_registers[GB_IO_JOYP] = 0xF; } @@ -137,6 +139,8 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->async_input_callback = default_async_input_callback; gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; + gb->io_registers[GB_IO_JOYP] = 0xF; } From cc8664b0a8594e00154c0e8139036124d46be7b4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 18:57:33 +0300 Subject: [PATCH 0123/1216] Correctly emulating a disconnected serial cable --- Core/memory.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index c12c1861..28454dcf 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -216,6 +216,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return ret; } + case GB_IO_SC: /* Serial not supported yet */ + return 0x7E; default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { return GB_apu_read(gb, addr & 0xFF); From bebb5c7a41bf50c1fde702d82a1c0405dfac82de Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 18:58:44 +0300 Subject: [PATCH 0124/1216] Correctly emulating the unused OAM memory in DMG mode --- Core/memory.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 28454dcf..77dbfb4c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -114,11 +114,15 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF00) { - /* Unusable, simulate Gameboy Color */ + /* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */ 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 (gb->is_cgb) { + return (addr & 0xF0) | ((addr >> 4) & 0xF); + } + return 0; + } if (addr < 0xFF80) { From 109af49933e9bfcd8123f3ab2c445ca791e22839 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Aug 2016 19:03:59 +0300 Subject: [PATCH 0125/1216] Updated DMG boot ROM to finish with the same register values as the original boot ROM --- BootROMs/dmg_boot.asm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm index bff23ed2..e22dee0a 100644 --- a/BootROMs/dmg_boot.asm +++ b/BootROMs/dmg_boot.asm @@ -89,6 +89,15 @@ Start: ; Wait ~2.5 seconds ld b, 150 call WaitBFrames + +; Set registers to match the original DMG boot + ld hl, $01B0 + push hl + pop af + ld hl, $014D + ld bc, $0013 + ld de, $00D8 + ; Boot the game jp BootGame @@ -135,7 +144,6 @@ PlaySound: TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c -SECTION "BootGame", ROM0[$fc] +SECTION "BootGame", ROM0[$fe] BootGame: - ld a, 1 ldh [$50], a \ No newline at end of file From a5670b66435e3d7fdadc8c9542596c8d32dead71 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 7 Aug 2016 00:39:32 +0300 Subject: [PATCH 0126/1216] Fixed boot ROM trimming --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f28b3512..033a62c0 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,7 @@ $(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 > $@ + head -c $(if $(findstring dmg,$@), 256, 2304) $@.tmp2 > $@ @rm $@.tmp $@.tmp2 $(BIN)/sdl/%.bin: $(BIN)/BootROMs/%.bin From 806d0775a4a61a6c9c9a5897f481973569f57011 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 9 Aug 2016 22:48:53 +0300 Subject: [PATCH 0127/1216] Added backtrace command to debugger --- Core/debugger.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- Core/debugger.h | 2 +- Core/gb.h | 10 +++++++++- Core/z80_cpu.c | 9 ++++++--- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index cb7e7ea0..1f7efce1 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1166,12 +1166,29 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) return true; } +static bool backtrace(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + GB_log(gb, "Usage: backtrace\n"); + return true; + } + + GB_log(gb, " 1. %s\n", value_to_string(gb, gb->pc, true)); + for (unsigned int i = gb->backtrace_size; i--;) { + GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); + } + + return true; +} + static bool help(GB_gameboy_t *gb, char *arguments); static const debugger_command_t commands[] = { {"continue", 1, cont, "Continue running until next stop"}, {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, + {"backtrace", 2, backtrace, "Display the current call stack"}, + {"bt", 2, backtrace, NULL}, {"sld", 3, stack_leak_detection, "Run until the current function returns, or a stack leak is detected (Experimental)"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, @@ -1185,6 +1202,7 @@ static const debugger_command_t commands[] = { {"eval", 2, print, NULL}, {"examine", 2, examine, "Examine values at address"}, {"x", 1, examine, NULL}, + {"help", 1, help, "List available commands"}, }; @@ -1215,7 +1233,7 @@ static const debugger_command_t *find_command(const char *string) return NULL; } -void GB_debugger_call_hook(GB_gameboy_t *gb) +void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) { /* Called just after the CPU calls a function/enters an interrupt/etc... */ @@ -1230,6 +1248,23 @@ void GB_debugger_call_hook(GB_gameboy_t *gb) } } + if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { + gb->backtrace_size--; + } + else { + break; + } + } + + gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; + gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); + gb->backtrace_returns[gb->backtrace_size].addr = call_addr; + gb->backtrace_size++; + } + gb->debug_call_depth++; } @@ -1253,6 +1288,15 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb) } } } + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { + gb->backtrace_size--; + } + else { + break; + } + } } static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value) diff --git a/Core/debugger.h b/Core/debugger.h index 7e751855..4e7808f8 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -4,7 +4,7 @@ void GB_debugger_run(GB_gameboy_t *gb); void GB_debugger_handle_async_commands(GB_gameboy_t *gb); -void GB_debugger_call_hook(GB_gameboy_t *gb); +void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); void GB_debugger_ret_hook(GB_gameboy_t *gb); void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); diff --git a/Core/gb.h b/Core/gb.h index ae461bc4..4c3fe93c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -385,12 +385,20 @@ typedef struct GB_gameboy_s { uint16_t n_breakpoints; struct GB_breakpoint_s *breakpoints; - /* SLD */ + /* SLD (Todo: merge with backtrace) */ bool stack_leak_detection; int debug_call_depth; uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ uint16_t addr_for_call_depth[0x200]; + /* Backtrace */ + unsigned int backtrace_size; + uint16_t backtrace_sps[0x200]; + struct { + uint16_t bank; + uint16_t addr; + } backtrace_returns[0x200]; + /* Watchpoints */ uint16_t n_watchpoints; struct GB_watchpoint_s *watchpoints; diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 1d490a38..21b08381 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -789,6 +789,7 @@ static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { + uint16_t call_addr = gb->pc; gb->pc++; if (condition_code(gb, opcode)) { GB_advance_cycles(gb, 4); @@ -803,7 +804,7 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) GB_advance_cycles(gb, 4); gb->pc = addr; - GB_debugger_call_hook(gb); + GB_debugger_call_hook(gb, call_addr); } else { GB_advance_cycles(gb, 12); @@ -973,6 +974,7 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { + uint16_t call_addr = gb->pc; GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_SP] -= 2; GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 1) >> 8); @@ -980,7 +982,7 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 1) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = opcode ^ 0xC7; - GB_debugger_call_hook(gb); + GB_debugger_call_hook(gb, call_addr); } static void ret(GB_gameboy_t *gb, uint8_t opcode) @@ -1002,6 +1004,7 @@ static void reti(GB_gameboy_t *gb, uint8_t opcode) static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { + uint16_t call_addr = gb->pc; gb->pc++; GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_SP] -= 2; @@ -1014,7 +1017,7 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = addr; - GB_debugger_call_hook(gb); + GB_debugger_call_hook(gb, call_addr); } static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) From e79ddee70584149eb38cbb3ad76b1d86a56e75a6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Aug 2016 22:49:17 +0300 Subject: [PATCH 0128/1216] Basic memory hex viewer/editor, using a (heavily stripped down) HexFiend framework --- Cocoa/Document.h | 5 + Cocoa/Document.m | 100 +- Cocoa/Document.xib | 14 + Cocoa/GBCompleteByteSlice.h | 7 + Cocoa/GBCompleteByteSlice.m | 26 + Cocoa/GBMemoryByteArray.h | 7 + Cocoa/GBMemoryByteArray.m | 64 + Cocoa/License.html | 38 +- Cocoa/MainMenu.xib | 9 +- HexFiend/HFAnnotatedTree.h | 57 + HexFiend/HFAnnotatedTree.m | 432 ++++ HexFiend/HFBTree.h | 40 + HexFiend/HFBTree.m | 1099 ++++++++++ HexFiend/HFBTreeByteArray.h | 30 + HexFiend/HFBTreeByteArray.m | 279 +++ HexFiend/HFByteArray.h | 180 ++ HexFiend/HFByteArray.m | 218 ++ HexFiend/HFByteArray_Internal.h | 8 + HexFiend/HFByteSlice.h | 53 + HexFiend/HFByteSlice.m | 85 + HexFiend/HFByteSlice_Private.h | 7 + HexFiend/HFController.h | 395 ++++ HexFiend/HFController.m | 1903 +++++++++++++++++ HexFiend/HFFullMemoryByteArray.h | 21 + HexFiend/HFFullMemoryByteArray.m | 70 + HexFiend/HFFullMemoryByteSlice.h | 21 + HexFiend/HFFullMemoryByteSlice.m | 46 + HexFiend/HFFunctions.h | 533 +++++ HexFiend/HFFunctions.m | 1172 ++++++++++ HexFiend/HFFunctions_Private.h | 52 + HexFiend/HFGlyphTrie.h | 49 + HexFiend/HFGlyphTrie.m | 93 + HexFiend/HFHexTextRepresenter.h | 21 + HexFiend/HFHexTextRepresenter.m | 203 ++ HexFiend/HFHexTextView.h | 15 + HexFiend/HFHexTextView.m | 13 + HexFiend/HFLayoutRepresenter.h | 80 + HexFiend/HFLayoutRepresenter.m | 361 ++++ HexFiend/HFLineCountingRepresenter.h | 67 + HexFiend/HFLineCountingRepresenter.m | 250 +++ HexFiend/HFLineCountingView.h | 32 + HexFiend/HFLineCountingView.m | 689 ++++++ HexFiend/HFPasteboardOwner.h | 51 + HexFiend/HFPasteboardOwner.m | 287 +++ HexFiend/HFPrivilegedHelperConnection.h | 3 + HexFiend/HFRepresenter.h | 121 ++ HexFiend/HFRepresenter.m | 120 ++ HexFiend/HFRepresenterHexTextView.h | 21 + HexFiend/HFRepresenterHexTextView.m | 95 + .../HFRepresenterStringEncodingTextView.h | 37 + .../HFRepresenterStringEncodingTextView.m | 540 +++++ HexFiend/HFRepresenterTextView.h | 146 ++ HexFiend/HFRepresenterTextView.m | 1760 +++++++++++++++ HexFiend/HFRepresenterTextViewCallout.h | 31 + HexFiend/HFRepresenterTextViewCallout.m | 477 +++++ HexFiend/HFRepresenterTextView_Internal.h | 11 + HexFiend/HFRepresenter_Internal.h | 7 + HexFiend/HFSharedMemoryByteSlice.h | 32 + HexFiend/HFSharedMemoryByteSlice.m | 209 ++ HexFiend/HFStatusBarRepresenter.h | 31 + HexFiend/HFStatusBarRepresenter.m | 266 +++ HexFiend/HFStringEncodingTextRepresenter.h | 26 + HexFiend/HFStringEncodingTextRepresenter.m | 121 ++ HexFiend/HFTextRepresenter.h | 39 + HexFiend/HFTextRepresenter.m | 373 ++++ HexFiend/HFTextRepresenter_Internal.h | 33 + HexFiend/HFTextRepresenter_KeyBinding.m | 128 ++ HexFiend/HFTextVisualStyleRun.h | 23 + HexFiend/HFTextVisualStyleRun.m | 79 + HexFiend/HFTypes.h | 13 + HexFiend/HFVerticalScrollerRepresenter.h | 21 + HexFiend/HFVerticalScrollerRepresenter.m | 133 ++ HexFiend/HexFiend.h | 78 + HexFiend/HexFiend_2_Framework_Prefix.pch | 99 + HexFiend/License.txt | 21 + Makefile | 11 +- 76 files changed, 14278 insertions(+), 9 deletions(-) create mode 100644 Cocoa/GBCompleteByteSlice.h create mode 100644 Cocoa/GBCompleteByteSlice.m create mode 100644 Cocoa/GBMemoryByteArray.h create mode 100644 Cocoa/GBMemoryByteArray.m create mode 100644 HexFiend/HFAnnotatedTree.h create mode 100644 HexFiend/HFAnnotatedTree.m create mode 100644 HexFiend/HFBTree.h create mode 100644 HexFiend/HFBTree.m create mode 100644 HexFiend/HFBTreeByteArray.h create mode 100644 HexFiend/HFBTreeByteArray.m create mode 100644 HexFiend/HFByteArray.h create mode 100644 HexFiend/HFByteArray.m create mode 100644 HexFiend/HFByteArray_Internal.h create mode 100644 HexFiend/HFByteSlice.h create mode 100644 HexFiend/HFByteSlice.m create mode 100644 HexFiend/HFByteSlice_Private.h create mode 100644 HexFiend/HFController.h create mode 100644 HexFiend/HFController.m create mode 100644 HexFiend/HFFullMemoryByteArray.h create mode 100644 HexFiend/HFFullMemoryByteArray.m create mode 100644 HexFiend/HFFullMemoryByteSlice.h create mode 100644 HexFiend/HFFullMemoryByteSlice.m create mode 100644 HexFiend/HFFunctions.h create mode 100644 HexFiend/HFFunctions.m create mode 100644 HexFiend/HFFunctions_Private.h create mode 100644 HexFiend/HFGlyphTrie.h create mode 100644 HexFiend/HFGlyphTrie.m create mode 100644 HexFiend/HFHexTextRepresenter.h create mode 100644 HexFiend/HFHexTextRepresenter.m create mode 100644 HexFiend/HFHexTextView.h create mode 100644 HexFiend/HFHexTextView.m create mode 100644 HexFiend/HFLayoutRepresenter.h create mode 100644 HexFiend/HFLayoutRepresenter.m create mode 100644 HexFiend/HFLineCountingRepresenter.h create mode 100644 HexFiend/HFLineCountingRepresenter.m create mode 100644 HexFiend/HFLineCountingView.h create mode 100644 HexFiend/HFLineCountingView.m create mode 100644 HexFiend/HFPasteboardOwner.h create mode 100755 HexFiend/HFPasteboardOwner.m create mode 100644 HexFiend/HFPrivilegedHelperConnection.h create mode 100644 HexFiend/HFRepresenter.h create mode 100644 HexFiend/HFRepresenter.m create mode 100644 HexFiend/HFRepresenterHexTextView.h create mode 100644 HexFiend/HFRepresenterHexTextView.m create mode 100644 HexFiend/HFRepresenterStringEncodingTextView.h create mode 100644 HexFiend/HFRepresenterStringEncodingTextView.m create mode 100644 HexFiend/HFRepresenterTextView.h create mode 100644 HexFiend/HFRepresenterTextView.m create mode 100644 HexFiend/HFRepresenterTextViewCallout.h create mode 100644 HexFiend/HFRepresenterTextViewCallout.m create mode 100644 HexFiend/HFRepresenterTextView_Internal.h create mode 100644 HexFiend/HFRepresenter_Internal.h create mode 100644 HexFiend/HFSharedMemoryByteSlice.h create mode 100644 HexFiend/HFSharedMemoryByteSlice.m create mode 100644 HexFiend/HFStatusBarRepresenter.h create mode 100644 HexFiend/HFStatusBarRepresenter.m create mode 100644 HexFiend/HFStringEncodingTextRepresenter.h create mode 100644 HexFiend/HFStringEncodingTextRepresenter.m create mode 100644 HexFiend/HFTextRepresenter.h create mode 100644 HexFiend/HFTextRepresenter.m create mode 100644 HexFiend/HFTextRepresenter_Internal.h create mode 100644 HexFiend/HFTextRepresenter_KeyBinding.m create mode 100644 HexFiend/HFTextVisualStyleRun.h create mode 100644 HexFiend/HFTextVisualStyleRun.m create mode 100644 HexFiend/HFTypes.h create mode 100644 HexFiend/HFVerticalScrollerRepresenter.h create mode 100644 HexFiend/HFVerticalScrollerRepresenter.m create mode 100644 HexFiend/HexFiend.h create mode 100644 HexFiend/HexFiend_2_Framework_Prefix.pch create mode 100644 HexFiend/License.txt diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 6a47aa7f..9e1e73dc 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -7,7 +7,12 @@ @property (strong) IBOutlet NSPanel *consoleWindow; @property (strong) IBOutlet NSTextField *consoleInput; @property (strong) IBOutlet NSWindow *mainWindow; +@property (strong) IBOutlet NSView *memoryView; +@property (strong) IBOutlet NSPanel *memoryWindow; +-(uint8_t) readMemory:(uint16_t) addr; +-(void) writeMemory:(uint16_t) addr value:(uint8_t)value; +-(void) performAtomicBlock: (void (^)())block; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 3aec2201..2a7a89d7 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -4,6 +4,9 @@ #include "AppDelegate.h" #include "gb.h" #include "debugger.h" +#include "memory.h" +#include "HexFiend/HexFiend.h" +#include "GBMemoryByteArray.h" @interface Document () { @@ -14,6 +17,7 @@ bool tooMuchLogs; bool fullScreen; bool in_sync_input; + HFController *hex_controller; NSString *lastConsoleInput; } @@ -62,7 +66,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) volatile bool stopping; NSConditionLock *has_debugger_input; NSMutableArray *debugger_input_queue; - bool is_inited; + volatile bool is_inited; } - (instancetype)init { @@ -122,9 +126,12 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) } andSampleRate:96000]; self.view.mouseHidingEnabled = YES; [self.audioClient start]; + NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; while (running) { GB_run(&gb); } + [hex_timer invalidate]; [self.audioClient stop]; self.audioClient = nil; self.view.mouseHidingEnabled = NO; @@ -154,8 +161,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { bool was_cgb = gb.is_cgb; [self stop]; - GB_free(&gb); is_inited = false; + GB_free(&gb); if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) { [self initCGB]; } @@ -200,6 +207,51 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) } +- (void) initMemoryView +{ + hex_controller = [[HFController alloc] init]; + [hex_controller setBytesPerColumn:1]; + [hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]]; + [hex_controller setEditMode:HFOverwriteMode]; + + [hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]]; + + /* Here we're going to make three representers - one for the hex, one for the ASCII, and one for the scrollbar. To lay these all out properly, we'll use a fourth HFLayoutRepresenter. */ + HFLayoutRepresenter *layoutRep = [[HFLayoutRepresenter alloc] init]; + HFHexTextRepresenter *hexRep = [[HFHexTextRepresenter alloc] init]; + HFStringEncodingTextRepresenter *asciiRep = [[HFStringEncodingTextRepresenter alloc] init]; + HFVerticalScrollerRepresenter *scrollRep = [[HFVerticalScrollerRepresenter alloc] init]; + HFLineCountingRepresenter *lineRep = [[HFLineCountingRepresenter alloc] init]; + HFStatusBarRepresenter *statusRep = [[HFStatusBarRepresenter alloc] init]; + + lineRep.lineNumberFormat = HFLineNumberFormatHexadecimal; + + /* Add all our reps to the controller. */ + [hex_controller addRepresenter:layoutRep]; + [hex_controller addRepresenter:hexRep]; + [hex_controller addRepresenter:asciiRep]; + [hex_controller addRepresenter:scrollRep]; + [hex_controller addRepresenter:lineRep]; + [hex_controller addRepresenter:statusRep]; + + /* Tell the layout rep which reps it should lay out. */ + [layoutRep addRepresenter:hexRep]; + [layoutRep addRepresenter:scrollRep]; + [layoutRep addRepresenter:asciiRep]; + [layoutRep addRepresenter:lineRep]; + [layoutRep addRepresenter:statusRep]; + + + [(NSView *)[hexRep view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + /* Grab the layout rep's view and stick it into our container. */ + NSView *layoutView = [layoutRep view]; + NSRect layoutViewFrame = self.memoryView.frame; + [layoutView setFrame:layoutViewFrame]; + [layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin]; + [self.memoryView addSubview:layoutView]; +} + + (BOOL)autosavesInPlace { return YES; } @@ -333,6 +385,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) NSString *nsstring = @(string); // For ref-counting dispatch_async(dispatch_get_main_queue(), ^{ + [hex_controller reloadData]; + NSFont *font = [NSFont userFixedPitchFontOfSize:12]; NSUnderlineStyle underline = NSUnderlineStyleNone; if (attributes & GB_LOG_BOLD) { @@ -452,4 +506,44 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) [self log:log withAttributes:0]; } -@end +- (uint8_t) readMemory:(uint16_t)addr +{ + while (!is_inited); + return GB_read_memory(&gb, addr); +} + +- (void) writeMemory:(uint16_t)addr value:(uint8_t)value +{ + while (!is_inited); + GB_write_memory(&gb, addr, value); +} + +- (void) performAtomicBlock: (void (^)())block +{ + while (!is_inited); + bool was_running = running; + if (was_running) { + [self stop]; + } + block(); + if (was_running) { + [self start]; + } +} + +- (void) reloadMemoryView +{ + if (self.memoryWindow.isVisible) { + [hex_controller reloadData]; + } +} + +- (IBAction) showMemory:(id)sender +{ + if (!hex_controller) { + [self initMemoryView]; + } + [self.memoryWindow makeKeyAndOrderFront:sender]; +} + +@end \ No newline at end of file diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 472f71f7..33357b61 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -10,6 +10,8 @@ + + @@ -113,5 +115,17 @@ + + + + + + + + + + + + diff --git a/Cocoa/GBCompleteByteSlice.h b/Cocoa/GBCompleteByteSlice.h new file mode 100644 index 00000000..24f3ba00 --- /dev/null +++ b/Cocoa/GBCompleteByteSlice.h @@ -0,0 +1,7 @@ +#import "Document.h" +#import "HexFiend/HexFiend.h" +#import "HexFiend/HFByteSlice.h" + +@interface GBCompleteByteSlice : HFByteSlice +- (instancetype) initWithByteArray:(HFByteArray *)array; +@end diff --git a/Cocoa/GBCompleteByteSlice.m b/Cocoa/GBCompleteByteSlice.m new file mode 100644 index 00000000..44e7ee69 --- /dev/null +++ b/Cocoa/GBCompleteByteSlice.m @@ -0,0 +1,26 @@ +#import "GBCompleteByteSlice.h" + +@implementation GBCompleteByteSlice +{ + HFByteArray *_array; +} + +- (instancetype) initWithByteArray:(HFByteArray *)array +{ + if ((self = [super init])) { + _array = array; + } + return self; +} + +- (unsigned long long)length +{ + return [_array length]; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range +{ + [_array copyBytes:dst range:range]; +} + +@end diff --git a/Cocoa/GBMemoryByteArray.h b/Cocoa/GBMemoryByteArray.h new file mode 100644 index 00000000..95991751 --- /dev/null +++ b/Cocoa/GBMemoryByteArray.h @@ -0,0 +1,7 @@ +#import "Document.h" +#import "HexFiend/HexFiend.h" +#import "HexFiend/HFByteArray.h" + +@interface GBMemoryByteArray : HFByteArray +- (instancetype) initWithDocument:(Document *)document; +@end diff --git a/Cocoa/GBMemoryByteArray.m b/Cocoa/GBMemoryByteArray.m new file mode 100644 index 00000000..8a8c1e8f --- /dev/null +++ b/Cocoa/GBMemoryByteArray.m @@ -0,0 +1,64 @@ +#import "GBMemoryByteArray.h" +#import "GBCompleteByteSlice.h" + + +@implementation GBMemoryByteArray +{ + Document *_document; +} + +- (instancetype) initWithDocument:(Document *)document +{ + if ((self = [super init])) { + _document = document; + } + return self; +} + +- (unsigned long long)length +{ + return 0x10000; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range +{ + uint16_t addr = (uint16_t) range.location; + unsigned long long length = range.length; + while (length) { + *(dst++) = [_document readMemory:addr++]; + length--; + } +} + +- (NSArray *)byteSlices +{ + return @[[[GBCompleteByteSlice alloc] initWithByteArray:self]]; +} + +- (HFByteArray *)subarrayWithRange:(HFRange)range +{ + unsigned char arr[range.length]; + [self copyBytes:arr range:range]; + HFByteArray *ret = [[HFBTreeByteArray alloc] init]; + HFFullMemoryByteSlice *slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData dataWithBytes:arr length:range.length]]; + [ret insertByteSlice:slice inRange:HFRangeMake(0, 0)]; + return ret; +} + +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange +{ + if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */ + [_document performAtomicBlock:^{ + uint8_t values[lrange.length]; + [slice copyBytes:values range:HFRangeMake(0, lrange.length)]; + uint16_t addr = (uint16_t) lrange.location; + uint8_t *src = values; + unsigned long long length = lrange.length; + while (length) { + [_document writeMemory:addr++ value:*(src++)]; + length--; + } + }]; +} + +@end diff --git a/Cocoa/License.html b/Cocoa/License.html index 158c0a5d..d064d29c 100644 --- a/Cocoa/License.html +++ b/Cocoa/License.html @@ -18,12 +18,19 @@ font-size: 11px; font-weight: normal; } + + h3 { + text-align:center; + font-size: 11px; + font-weight: bold; + } -

    MIT License

    -

    Copyright © 2015-2016 Lior Halphon

    +

    SameBoy

    +

    MIT License

    +

    Copyright © 2015-2016 Lior Halphon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -42,5 +49,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    + +

    Third-party Libraries

    +

    HexFiend

    +

    Copyright © 2005-2009, Peter Ammon +All rights reserved.

    + +

    Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met:

    + +
      +
    • Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer.
    • +
    • Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution.
    • +
    + +

    THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE

    \ No newline at end of file diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index d3bc791d..ae3a5008 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -1,5 +1,5 @@ - + @@ -368,6 +368,13 @@
    + + + + + + +
    diff --git a/HexFiend/HFAnnotatedTree.h b/HexFiend/HFAnnotatedTree.h new file mode 100644 index 00000000..32122a18 --- /dev/null +++ b/HexFiend/HFAnnotatedTree.h @@ -0,0 +1,57 @@ +// +// HFAnnotatedTree.h +// HexFiend_2 +// +// Copyright 2010 ridiculous_fish. All rights reserved. +// + +#import + +typedef unsigned long long (*HFAnnotatedTreeAnnotaterFunction_t)(id left, id right); + + +@interface HFAnnotatedTreeNode : NSObject { + HFAnnotatedTreeNode *left; + HFAnnotatedTreeNode *right; + HFAnnotatedTreeNode *parent; + uint32_t level; +@public + unsigned long long annotation; +} + +/* Pure virtual method, which must be overridden. */ +- (NSComparisonResult)compare:(HFAnnotatedTreeNode *)node; + +/* Returns the next in-order node. */ +- (id)nextNode; + +- (id)leftNode; +- (id)rightNode; +- (id)parentNode; + +#if ! NDEBUG +- (void)verifyIntegrity; +- (void)verifyAnnotation:(HFAnnotatedTreeAnnotaterFunction_t)annotater; +#endif + + +@end + + +@interface HFAnnotatedTree : NSObject { + HFAnnotatedTreeAnnotaterFunction_t annotater; + HFAnnotatedTreeNode *root; +} + +- (instancetype)initWithAnnotater:(HFAnnotatedTreeAnnotaterFunction_t)annotater; +- (void)insertNode:(HFAnnotatedTreeNode *)node; +- (void)removeNode:(HFAnnotatedTreeNode *)node; +- (id)rootNode; +- (id)firstNode; +- (BOOL)isEmpty; + +#if ! NDEBUG +- (void)verifyIntegrity; +#endif + +@end diff --git a/HexFiend/HFAnnotatedTree.m b/HexFiend/HFAnnotatedTree.m new file mode 100644 index 00000000..9e64b9ae --- /dev/null +++ b/HexFiend/HFAnnotatedTree.m @@ -0,0 +1,432 @@ +// +// HFAnnotatedTree.m +// HexFiend_2 +// +// Copyright 2010 ridiculous_fish. All rights reserved. +// + +#import "HFAnnotatedTree.h" + +#if NDEBUG +#define VERIFY_INTEGRITY() do { } while (0) +#else +#define VERIFY_INTEGRITY() [self verifyIntegrity] +#endif + +/* HFAnnotatedTree is an AA tree. */ + +static unsigned long long null_annotater(id left, id right) { USE(left); USE(right); return 0; } +static void skew(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree); +static BOOL split(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree); +static void rebalanceAfterLeafAdd(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree); +static void delete(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree); +static void verify_integrity(HFAnnotatedTreeNode *n); + +static HFAnnotatedTreeNode *next_node(HFAnnotatedTreeNode *node); + +static void insert(HFAnnotatedTreeNode *root, HFAnnotatedTreeNode *node, HFAnnotatedTree *tree); + +static inline HFAnnotatedTreeNode *get_parent(HFAnnotatedTreeNode *node); +static inline HFAnnotatedTreeNode *get_root(HFAnnotatedTree *tree); +static inline HFAnnotatedTreeNode *create_root(void); +static inline HFAnnotatedTreeAnnotaterFunction_t get_annotater(HFAnnotatedTree *tree); + +static void reannotate(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree); + +static HFAnnotatedTreeNode *first_node(HFAnnotatedTreeNode *node); + +static HFAnnotatedTreeNode *left_child(HFAnnotatedTreeNode *node); +static HFAnnotatedTreeNode *right_child(HFAnnotatedTreeNode *node); + +@implementation HFAnnotatedTree + +- (instancetype)initWithAnnotater:(HFAnnotatedTreeAnnotaterFunction_t)annot { + self = [super init]; + annotater = annot ? annot : null_annotater; + /* root is always an HFAnnotatedTreeNode with a left child but no right child */ + root = create_root(); + return self; +} + +- (void)dealloc { + [root release]; + [super dealloc]; +} + +- (id)rootNode { + return root; +} + +- (id)firstNode { + return first_node(root); +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + HFAnnotatedTree *copied = [[[self class] alloc] init]; + copied->annotater = annotater; + [copied->root release]; + copied->root = [root mutableCopyWithZone:zone]; + return copied; +} + +- (BOOL)isEmpty { + /* We're empty if our root has no children. */ + return left_child(root) == nil && right_child(root) == nil; +} + +- (void)insertNode:(HFAnnotatedTreeNode *)node { + HFASSERT(node != nil); + HFASSERT(get_parent(node) == nil); + /* Insert into the root */ + insert(root, [node retain], self); + VERIFY_INTEGRITY(); +} + +- (void)removeNode:(HFAnnotatedTreeNode *)node { + HFASSERT(node != nil); + HFASSERT(get_parent(node) != nil); + delete(node, self); + [node release]; + VERIFY_INTEGRITY(); +} + +#if ! NDEBUG +- (void)verifyIntegrity { + [root verifyIntegrity]; + [root verifyAnnotation:annotater]; +} +#endif + +static HFAnnotatedTreeNode *get_root(HFAnnotatedTree *tree) { + return tree->root; +} + +static HFAnnotatedTreeAnnotaterFunction_t get_annotater(HFAnnotatedTree *tree) { + return tree->annotater; +} + +@end + +@implementation HFAnnotatedTreeNode + +- (void)dealloc { + [left release]; + [right release]; + [super dealloc]; +} + +- (NSComparisonResult)compare:(HFAnnotatedTreeNode *)node { + USE(node); + UNIMPLEMENTED(); +} + +- (id)nextNode { + return next_node(self); +} + +- (id)leftNode { return left; } +- (id)rightNode { return right; } +- (id)parentNode { return parent; } + +- (id)mutableCopyWithZone:(NSZone *)zone { + HFAnnotatedTreeNode *copied = [[[self class] alloc] init]; + if (left) { + copied->left = [left mutableCopyWithZone:zone]; + copied->left->parent = copied; + } + if (right) { + copied->right = [right mutableCopyWithZone:zone]; + copied->right->parent = copied; + } + copied->level = level; + copied->annotation = annotation; + return copied; +} + +static HFAnnotatedTreeNode *left_child(HFAnnotatedTreeNode *node) { + return node->left; +} + +static HFAnnotatedTreeNode *right_child(HFAnnotatedTreeNode *node) { + return node->right; +} + + +static HFAnnotatedTreeNode *create_root(void) { + HFAnnotatedTreeNode *result = [[HFAnnotatedTreeNode alloc] init]; + result->level = UINT_MAX; //the root has a huge level + return result; +} + +static void reannotate(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { + HFASSERT(node != nil); + HFASSERT(tree != nil); + const HFAnnotatedTreeAnnotaterFunction_t annotater = get_annotater(tree); + node->annotation = annotater(node->left, node->right); +} + +static void insert(HFAnnotatedTreeNode *root, HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { + /* Insert node at the proper place in the tree. root is the root node, and we always insert to the left of root */ + BOOL left = YES; + HFAnnotatedTreeNode *parentNode = root, *currentChild; + /* Descend the tree until we find where to insert */ + while ((currentChild = (left ? parentNode->left : parentNode->right)) != nil) { + parentNode = currentChild; + left = ([parentNode compare:node] >= 0); //if parentNode is larger than the child, then the child goes to the left of node + } + + /* Now insert, potentially unbalancing the tree */ + if (left) { + parentNode->left = node; + } + else { + parentNode->right = node; + } + + /* Tell our node about its new parent */ + node->parent = parentNode; + + /* Rebalance and update annotations */ + rebalanceAfterLeafAdd(node, tree); +} + +static void skew(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree) { + HFAnnotatedTreeNode *newp = oldparent->left; + + if (oldparent->parent->left == oldparent) { + /* oldparent is the left child of its parent. Substitute in our left child. */ + oldparent->parent->left = newp; + } + else { + /* oldparent is the right child of its parent. Substitute in our left child. */ + oldparent->parent->right = newp; + } + + /* Tell the child about its new parent */ + newp->parent = oldparent->parent; + + /* Adopt its right child as our left child, and tell it about its new parent */ + oldparent->left = newp->right; + if (oldparent->left) oldparent->left->parent = oldparent; + + /* We are now the right child of the new parent */ + newp->right = oldparent; + oldparent->parent = newp; + + /* If we're now a leaf, our level is 1. Otherwise, it's one more than the level of our child. */ + oldparent->level = oldparent->left ? oldparent->left->level + 1 : 1; + + /* oldparent and newp both had their children changed, so need to be reannotated */ + reannotate(oldparent, tree); + reannotate(newp, tree); +} + +static BOOL split(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree) { + HFAnnotatedTreeNode *newp = oldparent->right; + if (newp && newp->right && newp->right->level == oldparent->level) { + if (oldparent->parent->left == oldparent) oldparent->parent->left = newp; + else oldparent->parent->right = newp; + newp->parent = oldparent->parent; + oldparent->parent = newp; + + oldparent->right = newp->left; + if (oldparent->right) oldparent->right->parent = oldparent; + newp->left = oldparent; + newp->level = oldparent->level + 1; + + /* oldparent and newp both had their children changed, so need to be reannotated */ + reannotate(oldparent, tree); + reannotate(newp, tree); + + return YES; + } + return NO; +} + +static void rebalanceAfterLeafAdd(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { // n is a node that has just been inserted and is now a leaf node. + node->level = 1; + node->left = nil; + node->right = nil; + reannotate(node, tree); + HFAnnotatedTreeNode * const root = get_root(tree); + HFAnnotatedTreeNode *probe; + for (probe = node->parent; probe != root; probe = probe->parent) { + reannotate(probe, tree); + // At this point probe->parent->level == probe->level + if (probe->level != (probe->left ? probe->left->level + 1 : 1)) { + // At this point the tree is correct, except (AA2) for n->parent + skew(probe, tree); + // We handle it (a left add) by changing it into a right add using Skew + // If the original add was to the left side of a node that is on the + // right side of a horisontal link, probe now points to the rights side + // of the second horisontal link, which is correct. + + // However if the original add was to the left of node with a horizontal + // link, we must get to the right side of the second link. + if (!probe->right || probe->level != probe->right->level) probe = probe->parent; + } + if (! split(probe->parent, tree)) break; + } + while (probe) { + reannotate(probe, tree); + probe = probe->parent; + } +} + +static void delete(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree) { // If n is not a leaf, we first swap it out with the leaf node that just + // precedes it. + HFAnnotatedTreeNode *leaf = n, *tmp; + + if (n->left) { + /* Descend the right subtree of our left child, to get the closest predecessor */ + for (leaf = n->left; leaf->right; leaf = leaf->right) {} + // When we stop, leaf has no 'right' child so it cannot have a left one + } + else if (n->right) { + /* We have no children that precede us, but we have a child after us, so use our closest successor */ + leaf = n->right; + } + + /* tmp is either the parent who loses the child, or tmp is our right subtree. Either way, we will have to reduce its level. */ + tmp = leaf->parent == n ? leaf : leaf->parent; + + /* Tell leaf's parent to forget about leaf */ + if (leaf->parent->left == leaf) { + leaf->parent->left = NULL; + } + else { + leaf->parent->right = NULL; + } + reannotate(leaf->parent, tree); + + if (n != leaf) { + /* Replace ourself as our parent's child with leaf */ + if (n->parent->left == n) n->parent->left = leaf; + else n->parent->right = leaf; + + /* Leaf's parent is our parent */ + leaf->parent = n->parent; + + /* Our left and right children are now leaf's left and right children */ + if (n->left) n->left->parent = leaf; + leaf->left = n->left; + if (n->right) n->right->parent = leaf; + leaf->right = n->right; + + /* Leaf's level is our level */ + leaf->level = n->level; + } + /* Since we adopted n's children, transferring the retain, tell n to forget about them so it doesn't release them */ + n->left = nil; + n->right = nil; + + // free (n); + + HFAnnotatedTreeNode * const root = get_root(tree); + while (tmp != root) { + reannotate(tmp, tree); + // One of tmp's childern had its level reduced + if (tmp->level > (tmp->left ? tmp->left->level + 1 : 1)) { // AA2 failed + tmp->level--; + if (split(tmp, tree)) { + if (split(tmp, tree)) skew(tmp->parent->parent, tree); + break; + } + tmp = tmp->parent; + } + else if (tmp->level <= (tmp->right ? tmp->right->level + 1 : 1)){ + break; + } + else { // AA3 failed + skew(tmp, tree); + //if (tmp->right) tmp->right->level = tmp->right->left ? tmp->right->left->level + 1 : 1; + if (tmp->level > tmp->parent->level) { + skew(tmp, tree); + split(tmp->parent->parent, tree); + break; + } + tmp = tmp->parent->parent; + } + } + while (tmp) { + reannotate(tmp, tree); + tmp = tmp->parent; + } +} + +static HFAnnotatedTreeNode *next_node(HFAnnotatedTreeNode *node) { + /* Return the next in-order node */ + HFAnnotatedTreeNode *result; + if (node->right) { + /* We have a right child, which is after us. Descend its left subtree. */ + result = node->right; + while (result->left) { + result = result->left; + } + } + else { + /* We have no right child. If we are our parent's left child, then our parent is after us. Otherwise, we're our parent's right child and it was before us, so ascend while we're the parent's right child. */ + result = node; + while (result->parent && result->parent->right == result) { + result = result->parent; + } + /* Now result is the left child of the parent (or has NULL parents), so its parent is the next node */ + result = result->parent; + } + /* Don't return the root */ + if (result != nil && result->parent == nil) { + result = next_node(result); + } + return result; +} + +static HFAnnotatedTreeNode *first_node(HFAnnotatedTreeNode *node) { + /* Return the first node */ + HFAnnotatedTreeNode *result = nil, *cursor = node->left; + while (cursor) { + /* Descend the left subtree */ + result = cursor; + cursor = cursor->left; + } + return result; +} + +static HFAnnotatedTreeNode *get_parent(HFAnnotatedTreeNode *node) { + HFASSERT(node != nil); + return node->parent; +} + +static void __attribute__((unused))verify_integrity(HFAnnotatedTreeNode *n) { + HFASSERT(!n->left || n->left->parent == n); + HFASSERT(!n->right || n->right->parent == n); + HFASSERT(!next_node(n) || [n compare:next_node(n)] <= 0); + HFASSERT(!n->parent || n->parent->level >= n->level); + if (n->parent == nil) { + /* root node */ + HFASSERT(n->level == UINT_MAX); + } + else { + /* non-root node */ + HFASSERT(n->level == (n->left == NULL ? 1 : n->left->level + 1)); + HFASSERT((n->level <= 1) || (n->right && n->level - n->right->level <= 1)); + } + HFASSERT(!n->parent || !n->parent->parent || + n->parent->parent->level > n->level); +} + +#if ! NDEBUG +- (void)verifyIntegrity { + [left verifyIntegrity]; + [right verifyIntegrity]; + verify_integrity(self); +} + +- (void)verifyAnnotation:(HFAnnotatedTreeAnnotaterFunction_t)annotater { + [left verifyAnnotation:annotater]; + [right verifyAnnotation:annotater]; + unsigned long long expectedAnnotation = annotater(left, right); + HFASSERT(annotation == expectedAnnotation); +} +#endif + +@end diff --git a/HexFiend/HFBTree.h b/HexFiend/HFBTree.h new file mode 100644 index 00000000..3bb8bd90 --- /dev/null +++ b/HexFiend/HFBTree.h @@ -0,0 +1,40 @@ +// +// HFBTree.h +// HexFiend +// +// + +#import + +typedef unsigned long long HFBTreeIndex; + +@class HFBTreeNode; + +@protocol HFBTreeEntry +- (unsigned long long)length; +@end + +@interface HFBTree : NSObject { + unsigned int depth; + HFBTreeNode *root; +} + +- (void)insertEntry:(id)entry atOffset:(HFBTreeIndex)offset; +- (id)entryContainingOffset:(HFBTreeIndex)offset beginningOffset:(HFBTreeIndex *)outBeginningOffset; +- (void)removeEntryAtOffset:(HFBTreeIndex)offset; +- (void)removeAllEntries; + +#if HFUNIT_TESTS +- (void)checkIntegrityOfCachedLengths; +- (void)checkIntegrityOfBTreeStructure; +#endif + +- (NSEnumerator *)entryEnumerator; +- (NSArray *)allEntries; + +- (HFBTreeIndex)length; + +/* Applies the given function to the entry at the given offset, continuing with subsequent entries until the function returns NO. Do not modify the tree from within this function. */ +- (void)applyFunction:(BOOL (*)(id entry, HFBTreeIndex offset, void *userInfo))func toEntriesStartingAtOffset:(HFBTreeIndex)offset withUserInfo:(void *)userInfo; + +@end diff --git a/HexFiend/HFBTree.m b/HexFiend/HFBTree.m new file mode 100644 index 00000000..5bb7806a --- /dev/null +++ b/HexFiend/HFBTree.m @@ -0,0 +1,1099 @@ +// +// HFBTree.m +// BTree +// +// Created by peter on 2/6/09. +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import "HFBTree.h" +#include + +#define FIXUP_LENGTHS 0 + +#define BTREE_BRANCH_ORDER 10 +#define BTREE_LEAF_ORDER 10 + +#define BTREE_ORDER 10 +#define BTREE_NODE_MINIMUM_VALUE_COUNT (BTREE_ORDER / 2) + +#define BTREE_LEAF_MINIMUM_VALUE_COUNT (BTREE_LEAF_ORDER / 2) + +#define BAD_INDEX ((ChildIndex_t)(-1)) +typedef unsigned int ChildIndex_t; + +/* How deep can our tree get? 128 is huge. */ +#define MAX_DEPTH 128 +#define BAD_DEPTH ((TreeDepth_t)(-1)) +typedef unsigned int TreeDepth_t; + +#define TreeEntry NSObject +#define HFBTreeLength(x) [(TreeEntry *)(x) length] + + +@class HFBTreeNode, HFBTreeBranch, HFBTreeLeaf; + +static TreeEntry *btree_search(HFBTree *tree, HFBTreeIndex offset, HFBTreeIndex *outBeginningOffset); +static id btree_insert_returning_retained_value_for_parent(HFBTree *tree, TreeEntry *entry, HFBTreeIndex offset); +static BOOL btree_remove(HFBTree *tree, HFBTreeIndex offset); +static void __attribute__((unused)) btree_recursive_check_integrity(HFBTree *tree, HFBTreeNode *branchOrLeaf, TreeDepth_t depth, HFBTreeNode **linkHelper); +#if FIXUP_LENGTHS +static HFBTreeIndex btree_recursive_fixup_cached_lengths(HFBTree *tree, HFBTreeNode *branchOrLeaf); +#endif +static HFBTreeIndex __attribute__((unused)) btree_recursive_check_integrity_of_cached_lengths(HFBTreeNode *branchOrLeaf); +static BOOL btree_are_cached_lengths_correct(HFBTreeNode *branchOrLeaf, HFBTreeIndex *outLength); +#if FIXUP_LENGTHS +static NSUInteger btree_entry_count(HFBTreeNode *branchOrLeaf); +#endif +static ChildIndex_t count_node_values(HFBTreeNode *node); +static HFBTreeIndex sum_child_lengths(const id *children, const BOOL isLeaf); +static HFBTreeNode *mutable_copy_node(HFBTreeNode *node, TreeDepth_t depth, HFBTreeNode **linkingHelper); + +#if NDEBUG +#define VERIFY_LENGTH(a) +#else +#define VERIFY_LENGTH(a) btree_recursive_check_integrity_of_cached_lengths((a)) +#endif + +#define IS_BRANCH(a) [(a) isKindOfClass:[HFBTreeBranch class]] +#define IS_LEAF(a) [(a) isKindOfClass:[HFBTreeLeaf class]] + +#define ASSERT_IS_BRANCH(a) HFASSERT(IS_BRANCH(a)) +#define ASSERT_IS_LEAF(a) HFASSERT(IS_LEAF(a)) + +#define GET_LENGTH(node, parentIsLeaf) ((parentIsLeaf) ? HFBTreeLength(node) : CHECK_CAST((node), HFBTreeNode)->subtreeLength) + +#define CHECK_CAST(a, b) ({HFASSERT([(a) isKindOfClass:[b class]]); (b *)(a);}) +#define CHECK_CAST_OR_NULL(a, b) ({HFASSERT((a == nil) || [(a) isKindOfClass:[b class]]); (b *)(a);}) + +#define DEFEAT_INLINE 1 + +#if DEFEAT_INLINE +#define FORCE_STATIC_INLINE static +#else +#define FORCE_STATIC_INLINE static __inline__ __attribute__((always_inline)) +#endif + +@interface HFBTreeEnumerator : NSEnumerator { + HFBTreeLeaf *currentLeaf; + ChildIndex_t childIndex; +} + +- (instancetype)initWithLeaf:(HFBTreeLeaf *)leaf; + +@end + +@interface HFBTreeNode : NSObject { + @public + NSUInteger rc; + HFBTreeIndex subtreeLength; + HFBTreeNode *left, *right; + id children[BTREE_ORDER]; +} + +@end + +@implementation HFBTreeNode + +- (id)retain { + HFAtomicIncrement(&rc, NO); + return self; +} + +- (oneway void)release { + NSUInteger result = HFAtomicDecrement(&rc, NO); + if (result == (NSUInteger)(-1)) { + [self dealloc]; + } +} + +- (NSUInteger)retainCount { + return 1 + rc; +} + +- (void)dealloc { + for (ChildIndex_t i=0; i < BTREE_BRANCH_ORDER; i++) { + if (! children[i]) break; + [children[i] release]; + } + [super dealloc]; +} + +- (NSString *)shortDescription { + return [NSString stringWithFormat:@"<%@: %p (%llu)>", [self class], self, subtreeLength]; +} + +@end + +@interface HFBTreeBranch : HFBTreeNode +@end + +@implementation HFBTreeBranch + +- (NSString *)description { + const char *lengthsMatchString = (subtreeLength == sum_child_lengths(children, NO) ? "" : " INCONSISTENT "); + NSMutableString *s = [NSMutableString stringWithFormat:@"<%@: %p (length: %llu%s) (children: %u) (", [self class], self, subtreeLength, lengthsMatchString, count_node_values(self)]; + NSUInteger i; + for (i=0; i < BTREE_ORDER; i++) { + if (children[i] == nil) break; + [s appendFormat:@"%s%@", (i == 0 ? "" : ", "), [children[i] shortDescription]]; + } + [s appendString:@")>"]; + return s; +} + +@end + +@interface HFBTreeLeaf : HFBTreeNode +@end + +@implementation HFBTreeLeaf + +- (NSString *)description { + NSMutableString *s = [NSMutableString stringWithFormat:@"<%@: %p (%u) (", [self class], self, count_node_values(self)]; + NSUInteger i; + for (i=0; i < BTREE_ORDER; i++) { + if (children[i] == nil) break; + [s appendFormat:@"%s%@", (i == 0 ? "" : ", "), children[i]]; + } + [s appendString:@")>"]; + return s; +} + +@end + +@implementation HFBTree + +- (instancetype)init { + self = [super init]; + depth = BAD_DEPTH; + root = nil; + return self; +} + +- (void)dealloc { + [root release]; + [super dealloc]; +} + +#if HFUNIT_TESTS +- (void)checkIntegrityOfCachedLengths { + if (root == nil) { + /* nothing */ + } + else { + btree_recursive_check_integrity_of_cached_lengths(root); + } +} + +- (void)checkIntegrityOfBTreeStructure { + if (depth == BAD_DEPTH) { + HFASSERT(root == nil); + } + else { + HFBTreeNode *linkHelper[MAX_DEPTH + 1] = {}; + btree_recursive_check_integrity(self, root, depth, linkHelper); + } +} +#endif + +- (HFBTreeIndex)length { + if (root == nil) return 0; + return ((HFBTreeNode *)root)->subtreeLength; +} + +- (void)insertEntry:(id)entryObj atOffset:(HFBTreeIndex)offset { + TreeEntry *entry = (TreeEntry *)entryObj; //avoid a conflicting types warning + HFASSERT(entry); + HFASSERT(offset <= [self length]); + if (! root) { + HFASSERT([self length] == 0); + HFASSERT(depth == BAD_DEPTH); + HFBTreeLeaf *leaf = [[HFBTreeLeaf alloc] init]; + leaf->children[0] = [entry retain]; + leaf->subtreeLength = HFBTreeLength(entry); + root = leaf; + depth = 0; + } + else { + HFBTreeNode *newParentValue = btree_insert_returning_retained_value_for_parent(self, entry, offset); + if (newParentValue) { + HFBTreeBranch *newRoot = [[HFBTreeBranch alloc] init]; + newRoot->children[0] = root; //transfer our retain + newRoot->children[1] = newParentValue; //transfer the retain we got from the function + newRoot->subtreeLength = HFSum(root->subtreeLength, newParentValue->subtreeLength); + root = newRoot; + depth++; + HFASSERT(depth <= MAX_DEPTH); + } +#if FIXUP_LENGTHS + HFBTreeIndex outLength = -1; + if (! btree_are_cached_lengths_correct(root, &outLength)) { + puts("Fixed up length after insertion"); + btree_recursive_fixup_cached_lengths(self, root); + } +#endif + } +} + +- (TreeEntry *)entryContainingOffset:(HFBTreeIndex)offset beginningOffset:(HFBTreeIndex *)outBeginningOffset { + HFASSERT(root != nil); + return btree_search(self, offset, outBeginningOffset); +} + +- (void)removeAllEntries { + [root release]; + root = nil; + depth = BAD_DEPTH; +} + +- (void)removeEntryAtOffset:(HFBTreeIndex)offset { + HFASSERT(root != nil); +#if FIXUP_LENGTHS + const NSUInteger beforeCount = btree_entry_count(root); +#endif + BOOL deleteRoot = btree_remove(self, offset); + if (deleteRoot) { + HFASSERT(count_node_values(root) <= 1); + id newRoot = [root->children[0] retain]; //may be nil! + [root release]; + root = newRoot; + depth--; + } +#if FIXUP_LENGTHS + const NSUInteger afterCount = btree_entry_count(root); + if (beforeCount != afterCount + 1) { + NSLog(@"Bad counts: before %lu, after %lu", beforeCount, afterCount); + } + HFBTreeIndex outLength = -1; + static NSUInteger fixupCount; + if (! btree_are_cached_lengths_correct(root, &outLength)) { + fixupCount++; + printf("Fixed up length after deletion (%lu)\n", (unsigned long)fixupCount); + btree_recursive_fixup_cached_lengths(self, root); + } + else { + //printf("Length post-deletion was OK! (%lu)\n", fixupCount); + } +#endif +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + USE(zone); + HFBTree *result = [[[self class] alloc] init]; + result->depth = depth; + HFBTreeNode *linkingHelper[MAX_DEPTH + 1]; + bzero(linkingHelper, (1 + depth) * sizeof *linkingHelper); + result->root = mutable_copy_node(root, depth, linkingHelper); + return result; +} + +FORCE_STATIC_INLINE ChildIndex_t count_node_values(HFBTreeNode *node) { + ChildIndex_t count; + for (count=0; count < BTREE_LEAF_ORDER; count++) { + if (node->children[count] == nil) break; + } + return count; +} + +FORCE_STATIC_INLINE HFBTreeIndex sum_child_lengths(const id *children, const BOOL isLeaf) { + HFBTreeIndex result = 0; + for (ChildIndex_t childIndex = 0; childIndex < BTREE_ORDER; childIndex++) { + id child = children[childIndex]; + if (! child) break; + HFBTreeIndex childLength = GET_LENGTH(child, isLeaf); + result = HFSum(result, childLength); + } + return result; +} + +FORCE_STATIC_INLINE HFBTreeIndex sum_N_child_lengths(const id *children, ChildIndex_t numChildren, const BOOL isLeaf) { + HFBTreeIndex result = 0; + for (ChildIndex_t childIndex = 0; childIndex < numChildren; childIndex++) { + id child = children[childIndex]; + HFASSERT(child != NULL); + HFBTreeIndex childLength = GET_LENGTH(child, isLeaf); + result = HFSum(result, childLength); + } + return result; +} + +FORCE_STATIC_INLINE ChildIndex_t index_containing_offset(HFBTreeNode *node, HFBTreeIndex offset, HFBTreeIndex * restrict outOffset, const BOOL isLeaf) { + ChildIndex_t childIndex; + HFBTreeIndex previousSum = 0; + const id *children = node->children; + for (childIndex = 0; childIndex < BTREE_ORDER; childIndex++) { + HFASSERT(children[childIndex] != nil); + HFBTreeIndex childLength = GET_LENGTH(children[childIndex], isLeaf); + HFBTreeIndex newSum = HFSum(childLength, previousSum); + if (newSum > offset) { + break; + } + previousSum = newSum; + } + *outOffset = previousSum; + return childIndex; +} + +FORCE_STATIC_INLINE id child_containing_offset(HFBTreeNode *node, HFBTreeIndex offset, HFBTreeIndex * restrict outOffset, const BOOL isLeaf) { + return node->children[index_containing_offset(node, offset, outOffset, isLeaf)]; +} + +FORCE_STATIC_INLINE ChildIndex_t index_for_child_at_offset(HFBTreeNode *node, HFBTreeIndex offset, const BOOL isLeaf) { + ChildIndex_t childIndex; + HFBTreeIndex previousSum = 0; + id *const children = node->children; + for (childIndex = 0; childIndex < BTREE_ORDER; childIndex++) { + if (previousSum == offset) break; + HFASSERT(children[childIndex] != nil); + HFBTreeIndex childLength = GET_LENGTH(children[childIndex], isLeaf); + previousSum = HFSum(childLength, previousSum); + HFASSERT(previousSum <= offset); + } + HFASSERT(childIndex <= BTREE_ORDER); //note we allow the child index to be one past the end (in which case we are sure to split the node) + HFASSERT(previousSum == offset); //but we still require the offset to be the sum of all the lengths of this node + return childIndex; +} + +FORCE_STATIC_INLINE ChildIndex_t child_index_for_insertion_at_offset(HFBTreeBranch *branch, HFBTreeIndex insertionOffset, HFBTreeIndex *outPriorCombinedOffset) { + ChildIndex_t indexForInsertion; + HFBTreeIndex priorCombinedOffset = 0; + id *const children = branch->children; + for (indexForInsertion = 0; indexForInsertion < BTREE_BRANCH_ORDER; indexForInsertion++) { + if (! children[indexForInsertion]) break; + HFBTreeNode *childNode = CHECK_CAST(children[indexForInsertion], HFBTreeNode); + HFBTreeIndex subtreeLength = childNode->subtreeLength; + HFASSERT(subtreeLength > 0); + HFBTreeIndex newOffset = HFSum(priorCombinedOffset, subtreeLength); + if (newOffset >= insertionOffset) { + break; + } + priorCombinedOffset = newOffset; + } + *outPriorCombinedOffset = priorCombinedOffset; + return indexForInsertion; +} + +FORCE_STATIC_INLINE ChildIndex_t child_index_for_deletion_at_offset(HFBTreeBranch *branch, HFBTreeIndex deletionOffset, HFBTreeIndex *outPriorCombinedOffset) { + ChildIndex_t indexForDeletion; + HFBTreeIndex priorCombinedOffset = 0; + for (indexForDeletion = 0; indexForDeletion < BTREE_BRANCH_ORDER; indexForDeletion++) { + HFASSERT(branch->children[indexForDeletion] != nil); + HFBTreeNode *childNode = CHECK_CAST(branch->children[indexForDeletion], HFBTreeNode); + HFBTreeIndex subtreeLength = childNode->subtreeLength; + HFASSERT(subtreeLength > 0); + HFBTreeIndex newOffset = HFSum(priorCombinedOffset, subtreeLength); + if (newOffset > deletionOffset) { + /* Key difference between insertion and deletion: insertion uses >=, while deletion uses > */ + break; + } + priorCombinedOffset = newOffset; + } + *outPriorCombinedOffset = priorCombinedOffset; + return indexForDeletion; +} + +FORCE_STATIC_INLINE void insert_value_into_array(id value, NSUInteger insertionIndex, id *array, NSUInteger arrayCount) { + HFASSERT(insertionIndex <= arrayCount); + HFASSERT(arrayCount > 0); + NSUInteger pushingIndex = arrayCount - 1; + while (pushingIndex > insertionIndex) { + array[pushingIndex] = array[pushingIndex - 1]; + pushingIndex--; + } + array[insertionIndex] = [value retain]; +} + + +FORCE_STATIC_INLINE void remove_value_from_array(NSUInteger removalIndex, id *array, NSUInteger arrayCount) { + HFASSERT(removalIndex < arrayCount); + HFASSERT(arrayCount > 0); + HFASSERT(array[removalIndex] != nil); + [array[removalIndex] release]; + for (NSUInteger pullingIndex = removalIndex + 1; pullingIndex < arrayCount; pullingIndex++) { + array[pullingIndex - 1] = array[pullingIndex]; + } + array[arrayCount - 1] = nil; +} + +static void split_array(const restrict id *values, ChildIndex_t valueCount, restrict id *left, restrict id *right, ChildIndex_t leftArraySizeForClearing) { + const ChildIndex_t midPoint = valueCount/2; + ChildIndex_t inputIndex = 0, outputIndex = 0; + while (inputIndex < midPoint) { + left[outputIndex++] = values[inputIndex++]; + } + + /* Clear the remainder of our left array. Right array does not have to be cleared. */ + HFASSERT(outputIndex <= leftArraySizeForClearing); + while (outputIndex < leftArraySizeForClearing) { + left[outputIndex++] = nil; + } + + /* Move the second half of our values into the right array */ + outputIndex = 0; + while (inputIndex < valueCount) { + right[outputIndex++] = values[inputIndex++]; + } +} + +FORCE_STATIC_INLINE HFBTreeNode *add_child_to_node_possibly_creating_split(HFBTreeNode *node, id value, ChildIndex_t insertionLocation, BOOL isLeaf) { + ChildIndex_t childCount = count_node_values(node); + HFASSERT(insertionLocation <= childCount); + if (childCount < BTREE_ORDER) { + /* No need to make a split */ + insert_value_into_array(value, insertionLocation, node->children, childCount + 1); + node->subtreeLength = HFSum(node->subtreeLength, GET_LENGTH(value, isLeaf)); + return nil; + } + + HFASSERT(node->children[BTREE_ORDER - 1] != nil); /* we require that it be full */ + id allEntries[BTREE_ORDER + 1]; + memcpy(allEntries, node->children, BTREE_ORDER * sizeof *node->children); + allEntries[BTREE_ORDER] = nil; + + /* insert_value_into_array applies a retain, so allEntries owns a retain on its values */ + insert_value_into_array(value, insertionLocation, allEntries, BTREE_ORDER + 1); + HFBTreeNode *newNode = [[[node class] alloc] init]; + + /* figure out our total length */ + HFBTreeIndex totalLength = HFSum(node->subtreeLength, GET_LENGTH(value, isLeaf)); + + /* Distribute half our values to the new leaf */ + split_array(allEntries, sizeof allEntries / sizeof *allEntries, node->children, newNode->children, BTREE_ORDER); + + /* figure out how much is in the new array */ + HFBTreeIndex newNodeLength = sum_child_lengths(newNode->children, isLeaf); + + /* update our lengths */ + HFASSERT(newNodeLength < totalLength); + newNode->subtreeLength = newNodeLength; + node->subtreeLength = totalLength - newNodeLength; + + /* Link it in */ + HFBTreeNode *rightNode = node->right; + newNode->right = rightNode; + if (rightNode) rightNode->left = newNode; + newNode->left = node; + node->right = newNode; + return newNode; +} + +FORCE_STATIC_INLINE void add_values_to_array(const id * restrict srcValues, NSUInteger amountToCopy, id * restrict targetValues, NSUInteger amountToPush) { + // a pushed value at index X goes to index X + amountToCopy + NSUInteger pushIndex = amountToPush; + while (pushIndex--) { + targetValues[amountToCopy + pushIndex] = targetValues[pushIndex]; + } + for (NSUInteger i = 0; i < amountToCopy; i++) { + targetValues[i] = [srcValues[i] retain]; + } +} + +FORCE_STATIC_INLINE void remove_values_from_array(id * restrict array, NSUInteger amountToRemove, NSUInteger totalArrayLength) { + HFASSERT(totalArrayLength >= amountToRemove); + /* Release existing values */ + NSUInteger i; + for (i=0; i < amountToRemove; i++) { + [array[i] release]; + } + /* Move remaining values */ + for (i=amountToRemove; i < totalArrayLength; i++) { + array[i - amountToRemove] = array[i]; + } + /* Clear the end */ + for (i=totalArrayLength - amountToRemove; i < totalArrayLength; i++) { + array[i] = nil; + } +} + +FORCE_STATIC_INLINE BOOL rebalance_node_by_distributing_to_neighbors(HFBTreeNode *node, ChildIndex_t childCount, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childCount < BTREE_NODE_MINIMUM_VALUE_COUNT); + BOOL result = NO; + HFBTreeNode *leftNeighbor = node->left, *rightNeighbor = node->right; + const ChildIndex_t leftSpaceAvailable = (leftNeighbor ? BTREE_ORDER - count_node_values(leftNeighbor) : 0); + const ChildIndex_t rightSpaceAvailable = (rightNeighbor ? BTREE_ORDER - count_node_values(rightNeighbor) : 0); + if (leftSpaceAvailable + rightSpaceAvailable >= childCount) { + /* We have enough space to redistribute. Try to do it in such a way that both neighbors end up with the same number of items. */ + ChildIndex_t itemCountForLeft = 0, itemCountForRight = 0, itemCountRemaining = childCount; + if (leftSpaceAvailable > rightSpaceAvailable) { + ChildIndex_t amountForLeft = MIN(leftSpaceAvailable - rightSpaceAvailable, itemCountRemaining); + itemCountForLeft += amountForLeft; + itemCountRemaining -= amountForLeft; + } + else if (rightSpaceAvailable > leftSpaceAvailable) { + ChildIndex_t amountForRight = MIN(rightSpaceAvailable - leftSpaceAvailable, itemCountRemaining); + itemCountForRight += amountForRight; + itemCountRemaining -= amountForRight; + } + /* Now distribute the remainder (if any) evenly, preferring the remainder to go left, because it is slightly cheaper to append to the left than prepend to the right */ + itemCountForRight += itemCountRemaining / 2; + itemCountForLeft += itemCountRemaining - (itemCountRemaining / 2); + HFASSERT(itemCountForLeft <= leftSpaceAvailable); + HFASSERT(itemCountForRight <= rightSpaceAvailable); + HFASSERT(itemCountForLeft + itemCountForRight == childCount); + + if (itemCountForLeft > 0) { + /* append to the end */ + HFBTreeIndex additionalLengthForLeft = sum_N_child_lengths(node->children, itemCountForLeft, isLeaf); + leftNeighbor->subtreeLength = HFSum(leftNeighbor->subtreeLength, additionalLengthForLeft); + add_values_to_array(node->children, itemCountForLeft, leftNeighbor->children + BTREE_ORDER - leftSpaceAvailable, 0); + HFASSERT(leftNeighbor->subtreeLength == sum_child_lengths(leftNeighbor->children, isLeaf)); + *modifiedLeftNeighbor = YES; + } + if (itemCountForRight > 0) { + /* append to the beginning */ + HFBTreeIndex additionalLengthForRight = sum_N_child_lengths(node->children + itemCountForLeft, itemCountForRight, isLeaf); + rightNeighbor->subtreeLength = HFSum(rightNeighbor->subtreeLength, additionalLengthForRight); + add_values_to_array(node->children + itemCountForLeft, itemCountForRight, rightNeighbor->children, BTREE_ORDER - rightSpaceAvailable); + HFASSERT(rightNeighbor->subtreeLength == sum_child_lengths(rightNeighbor->children, isLeaf)); + *modifiedRightNeighbor = YES; + } + /* Remove ourself from the linked list */ + if (leftNeighbor) { + leftNeighbor->right = rightNeighbor; + } + if (rightNeighbor) { + rightNeighbor->left = leftNeighbor; + } + /* Even though we've essentially orphaned ourself, we need to force ourselves consistent (by making ourselves empty) because our parent still references us, and we don't want to make our parent inconsistent. */ + for (ChildIndex_t childIndex = 0; node->children[childIndex] != nil; childIndex++) { + [node->children[childIndex] release]; + node->children[childIndex] = nil; + } + node->subtreeLength = 0; + + result = YES; + } + return result; +} + + +FORCE_STATIC_INLINE BOOL share_children(HFBTreeNode *node, ChildIndex_t childCount, HFBTreeNode *neighbor, BOOL isRightNeighbor, BOOL isLeaf) { + ChildIndex_t neighborCount = count_node_values(neighbor); + ChildIndex_t totalChildren = (childCount + neighborCount); + BOOL result = NO; + if (totalChildren <= 2 * BTREE_LEAF_ORDER && totalChildren >= 2 * BTREE_LEAF_MINIMUM_VALUE_COUNT) { + ChildIndex_t finalMyCount = totalChildren / 2; + ChildIndex_t finalNeighborCount = totalChildren - finalMyCount; + HFASSERT(finalNeighborCount < neighborCount); + HFASSERT(finalMyCount > childCount); + ChildIndex_t amountToTransfer = finalMyCount - childCount; + HFBTreeIndex lengthChange; + if (isRightNeighbor) { + /* Transfer from left end of right neighbor to this right end of this leaf. This retains the values. */ + add_values_to_array(neighbor->children, amountToTransfer, node->children + childCount, 0); + /* Remove from beginning of right neighbor. This releases them. */ + remove_values_from_array(neighbor->children, amountToTransfer, neighborCount); + lengthChange = sum_N_child_lengths(node->children + childCount, amountToTransfer, isLeaf); + } + else { + /* Transfer from right end of left neighbor to left end of this leaf */ + add_values_to_array(neighbor->children + neighborCount - amountToTransfer, amountToTransfer, node->children, childCount); + /* Remove from end of left neighbor */ + remove_values_from_array(neighbor->children + neighborCount - amountToTransfer, amountToTransfer, amountToTransfer); + lengthChange = sum_N_child_lengths(node->children, amountToTransfer, isLeaf); + } + HFASSERT(lengthChange <= neighbor->subtreeLength); + neighbor->subtreeLength -= lengthChange; + node->subtreeLength = HFSum(node->subtreeLength, lengthChange); + HFASSERT(count_node_values(node) == finalMyCount); + HFASSERT(count_node_values(neighbor) == finalNeighborCount); + result = YES; + } + return result; +} + +static BOOL rebalance_node_by_sharing_with_neighbors(HFBTreeNode *node, ChildIndex_t childCount, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childCount < BTREE_LEAF_MINIMUM_VALUE_COUNT); + BOOL result = NO; + HFBTreeNode *leftNeighbor = node->left, *rightNeighbor = node->right; + if (leftNeighbor) { + result = share_children(node, childCount, leftNeighbor, NO, isLeaf); + if (result) *modifiedLeftNeighbor = YES; + } + if (! result && rightNeighbor) { + result = share_children(node, childCount, rightNeighbor, YES, isLeaf); + if (result) *modifiedRightNeighbor = YES; + } + return result; +} + +/* Return YES if this leaf should be removed after rebalancing. Other nodes are never removed. */ +FORCE_STATIC_INLINE BOOL rebalance_node_after_deletion(HFBTreeNode *node, ChildIndex_t childCount, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childCount < BTREE_LEAF_MINIMUM_VALUE_COUNT); + /* We may only delete this leaf, and not adjacent leaves. Thus our rebalancing strategy is: + If the items to the left or right have sufficient space to hold us, then push our values left or right, and delete this node. + Otherwise, steal items from the left until we have the same number of items. */ + BOOL deleteNode = NO; + if (rebalance_node_by_distributing_to_neighbors(node, childCount, isLeaf, modifiedLeftNeighbor, modifiedRightNeighbor)) { + deleteNode = YES; + //puts("rebalance_node_by_distributing_to_neighbors"); + } + else if (rebalance_node_by_sharing_with_neighbors(node, childCount, isLeaf, modifiedLeftNeighbor, modifiedRightNeighbor)) { + deleteNode = NO; + //puts("rebalance_node_by_sharing_with_neighbors"); + } + else { + [NSException raise:NSInternalInconsistencyException format:@"Unable to rebalance after deleting node %@", node]; + } + return deleteNode; +} + + +FORCE_STATIC_INLINE BOOL remove_value_from_node_with_possible_rebalance(HFBTreeNode *node, ChildIndex_t childIndex, BOOL isRootNode, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childIndex < BTREE_ORDER); + HFASSERT(node != nil); + HFASSERT(node->children[childIndex] != nil); + HFBTreeIndex entryLength = GET_LENGTH(node->children[childIndex], isLeaf); + HFASSERT(entryLength <= node->subtreeLength); + node->subtreeLength -= entryLength; + BOOL deleteInputNode = NO; + +#if ! NDEBUG + const id savedChild = node->children[childIndex]; + NSUInteger childMultiplicity = 0; + NSUInteger v; + for (v = 0; v < BTREE_ORDER; v++) { + if (node->children[v] == savedChild) childMultiplicity++; + if (node->children[v] == nil) break; + } + +#endif + + /* Figure out how many children we have; start at one more than childIndex since we know that childIndex is a valid index */ + ChildIndex_t childCount; + for (childCount = childIndex + 1; childCount < BTREE_ORDER; childCount++) { + if (! node->children[childCount]) break; + } + + /* Remove our value at childIndex; this sends it a release message */ + remove_value_from_array(childIndex, node->children, childCount); + HFASSERT(childCount > 0); + childCount--; + +#if ! NDEBUG + for (v = 0; v < childCount; v++) { + if (node->children[v] == savedChild) childMultiplicity--; + } + HFASSERT(childMultiplicity == 1); +#endif + + if (childCount < BTREE_LEAF_MINIMUM_VALUE_COUNT && ! isRootNode) { + /* We have too few items; try to rebalance (this will always be possible except from the root node) */ + deleteInputNode = rebalance_node_after_deletion(node, childCount, isLeaf, modifiedLeftNeighbor, modifiedRightNeighbor); + } + else { + //NSLog(@"Deletion from %@ with %u remaining, %s root node, so no need to rebalance\n", node, childCount, isRootNode ? "is" : "is not"); + } + + return deleteInputNode; +} + +FORCE_STATIC_INLINE void update_node_having_changed_size_of_child(HFBTreeNode *node, BOOL isLeaf) { + HFBTreeIndex newLength = sum_child_lengths(node->children, isLeaf); + /* This should only be called if the length actually changes - so assert as such */ + /* I no longer think the above line is true. It's possible that we can delete a node, and then after a rebalance, we can become the same size we were before. */ + //HFASSERT(node->subtreeLength != newLength); + node->subtreeLength = newLength; +} + +struct SubtreeInfo_t { + HFBTreeBranch *branch; + ChildIndex_t childIndex; //childIndex is the index of the child of branch, not branch's index in its parent +}; + +static HFBTreeLeaf *btree_descend(HFBTree *tree, struct SubtreeInfo_t *outDescentInfo, HFBTreeIndex *insertionOffset, BOOL isForDelete) { + TreeDepth_t maxDepth = tree->depth; + HFASSERT(maxDepth != BAD_DEPTH && maxDepth <= MAX_DEPTH); + id currentBranchOrLeaf = tree->root; + HFBTreeIndex offsetForSubtree = *insertionOffset; + for (TreeDepth_t currentDepth = 0; currentDepth < maxDepth; currentDepth++) { + ASSERT_IS_BRANCH(currentBranchOrLeaf); + HFBTreeBranch *currentBranch = currentBranchOrLeaf; + HFBTreeIndex priorCombinedOffset = (HFBTreeIndex)-1; + ChildIndex_t nextChildIndex = (isForDelete ? child_index_for_deletion_at_offset : child_index_for_insertion_at_offset)(currentBranch, offsetForSubtree, &priorCombinedOffset); + outDescentInfo[currentDepth].branch = currentBranch; + outDescentInfo[currentDepth].childIndex = nextChildIndex; + offsetForSubtree -= priorCombinedOffset; + currentBranchOrLeaf = currentBranch->children[nextChildIndex]; + if (isForDelete) { + HFBTreeNode *node = currentBranchOrLeaf; + HFASSERT(node->subtreeLength > offsetForSubtree); + } + } + ASSERT_IS_LEAF(currentBranchOrLeaf); + *insertionOffset = offsetForSubtree; + return currentBranchOrLeaf; +} + +struct LeafInfo_t { + HFBTreeLeaf *leaf; + ChildIndex_t entryIndex; + HFBTreeIndex offsetOfEntryInTree; +}; + +static struct LeafInfo_t btree_find_leaf(HFBTree *tree, HFBTreeIndex offset) { + TreeDepth_t depth = tree->depth; + HFBTreeNode *currentNode = tree->root; + HFBTreeIndex remainingOffset = offset; + while (depth--) { + HFBTreeIndex beginningOffsetOfNode; + currentNode = child_containing_offset(currentNode, remainingOffset, &beginningOffsetOfNode, NO); + HFASSERT(beginningOffsetOfNode <= remainingOffset); + remainingOffset = remainingOffset - beginningOffsetOfNode; + } + ASSERT_IS_LEAF(currentNode); + HFBTreeIndex startOffsetOfEntry; + ChildIndex_t entryIndex = index_containing_offset(currentNode, remainingOffset, &startOffsetOfEntry, YES); + /* The offset of this entry is the requested offset minus the difference between its starting offset within the leaf and the requested offset within the leaf */ + HFASSERT(remainingOffset >= startOffsetOfEntry); + HFBTreeIndex offsetIntoEntry = remainingOffset - startOffsetOfEntry; + HFASSERT(offset >= offsetIntoEntry); + HFBTreeIndex beginningOffset = offset - offsetIntoEntry; + return (struct LeafInfo_t){.leaf = CHECK_CAST(currentNode, HFBTreeLeaf), .entryIndex = entryIndex, .offsetOfEntryInTree = beginningOffset}; +} + +static TreeEntry *btree_search(HFBTree *tree, HFBTreeIndex offset, HFBTreeIndex *outBeginningOffset) { + struct LeafInfo_t leafInfo = btree_find_leaf(tree, offset); + *outBeginningOffset = leafInfo.offsetOfEntryInTree; + return leafInfo.leaf->children[leafInfo.entryIndex]; +} + +static id btree_insert_returning_retained_value_for_parent(HFBTree *tree, TreeEntry *entry, HFBTreeIndex insertionOffset) { + struct SubtreeInfo_t descentInfo[MAX_DEPTH]; +#if ! NDEBUG + memset(descentInfo, -1, sizeof descentInfo); +#endif + HFBTreeIndex subtreeOffset = insertionOffset; + HFBTreeLeaf *leaf = btree_descend(tree, descentInfo, &subtreeOffset, NO); + ASSERT_IS_LEAF(leaf); + + ChildIndex_t insertionLocation = index_for_child_at_offset(leaf, subtreeOffset, YES); + HFBTreeNode *retainedValueToInsertIntoParentBranch = add_child_to_node_possibly_creating_split(leaf, entry, insertionLocation, YES); + + /* Walk up */ + TreeDepth_t depth = tree->depth; + HFASSERT(depth != BAD_DEPTH); + HFBTreeIndex entryLength = HFBTreeLength(entry); + while (depth--) { + HFBTreeBranch *branch = descentInfo[depth].branch; + branch->subtreeLength = HFSum(branch->subtreeLength, entryLength); + ChildIndex_t childIndex = descentInfo[depth].childIndex; + if (retainedValueToInsertIntoParentBranch) { + HFASSERT(branch->subtreeLength > retainedValueToInsertIntoParentBranch->subtreeLength); + /* Since we copied some stuff out from under ourselves, subtract its length */ + branch->subtreeLength -= retainedValueToInsertIntoParentBranch->subtreeLength; + HFBTreeNode *newRetainedValueToInsertIntoParentBranch = add_child_to_node_possibly_creating_split(branch, retainedValueToInsertIntoParentBranch, childIndex + 1, NO); + [retainedValueToInsertIntoParentBranch release]; + retainedValueToInsertIntoParentBranch = newRetainedValueToInsertIntoParentBranch; + } + } + return retainedValueToInsertIntoParentBranch; +} + +static BOOL btree_remove(HFBTree *tree, HFBTreeIndex deletionOffset) { + struct SubtreeInfo_t descentInfo[MAX_DEPTH]; +#if ! NDEBUG + memset(descentInfo, -1, sizeof descentInfo); +#endif + HFBTreeIndex subtreeOffset = deletionOffset; + HFBTreeLeaf *leaf = btree_descend(tree, descentInfo, &subtreeOffset, YES); + ASSERT_IS_LEAF(leaf); + + HFBTreeIndex previousOffsetSum = 0; + ChildIndex_t childIndex; + for (childIndex = 0; childIndex < BTREE_LEAF_ORDER; childIndex++) { + if (previousOffsetSum == subtreeOffset) break; + TreeEntry *entry = leaf->children[childIndex]; + HFASSERT(entry != nil); //if it were nil, then the offset is too large + HFBTreeIndex childLength = HFBTreeLength(entry); + previousOffsetSum = HFSum(childLength, previousOffsetSum); + } + HFASSERT(childIndex < BTREE_LEAF_ORDER); + HFASSERT(previousOffsetSum == subtreeOffset); + + TreeDepth_t depth = tree->depth; + HFASSERT(depth != BAD_DEPTH); + BOOL modifiedLeft = NO, modifiedRight = NO; + BOOL deleteNode = remove_value_from_node_with_possible_rebalance(leaf, childIndex, depth==0/*isRootNode*/, YES, &modifiedLeft, &modifiedRight); + HFASSERT(btree_are_cached_lengths_correct(leaf, NULL)); + while (depth--) { + HFBTreeBranch *branch = descentInfo[depth].branch; + ChildIndex_t branchChildIndex = descentInfo[depth].childIndex; + BOOL leftNeighborNeedsUpdating = modifiedLeft && branchChildIndex == 0; //if our child tweaked its left neighbor, and its left neighbor is not also a child of us, we need to inform its parent (which is our left neighbor) + BOOL rightNeighborNeedsUpdating = modifiedRight && (branchChildIndex + 1 == BTREE_BRANCH_ORDER || branch->children[branchChildIndex + 1] == NULL); //same goes for right + if (leftNeighborNeedsUpdating) { + HFASSERT(branch->left != NULL); +// NSLog(@"Updating lefty %p", branch->left); + update_node_having_changed_size_of_child(branch->left, NO); + } +#if ! NDEBUG + if (branch->left) HFASSERT(btree_are_cached_lengths_correct(branch->left, NULL)); +#endif + if (rightNeighborNeedsUpdating) { + HFASSERT(branch->right != NULL); +// NSLog(@"Updating righty %p", branch->right); + update_node_having_changed_size_of_child(branch->right, NO); + } +#if ! NDEBUG + if (branch->right) HFASSERT(btree_are_cached_lengths_correct(branch->right, NULL)); +#endif + update_node_having_changed_size_of_child(branch, NO); + modifiedLeft = NO; + modifiedRight = NO; + if (deleteNode) { + deleteNode = remove_value_from_node_with_possible_rebalance(branch, branchChildIndex, depth==0/*isRootNode*/, NO, &modifiedLeft, &modifiedRight); + } + else { + // update_node_having_changed_size_of_child(branch, NO); + // no need to delete parent nodes, so leave deleteNode as NO + } + /* Our parent may have to modify its left or right neighbor if we had to modify our left or right neighbor or if one of our children modified a neighbor that is not also a child of us. */ + modifiedLeft = modifiedLeft || leftNeighborNeedsUpdating; + modifiedRight = modifiedRight || rightNeighborNeedsUpdating; + } + + if (! deleteNode) { + /* Delete the root if it has one node and a depth of at least 1, or zero nodes and a depth of 0 */ + deleteNode = (tree->depth >= 1 && tree->root->children[1] == nil) || (tree->depth == 0 && tree->root->children[0] == nil); + } + return deleteNode; +} + +/* linkingHelper stores the last seen node for each depth. */ +static HFBTreeNode *mutable_copy_node(HFBTreeNode *node, TreeDepth_t depth, HFBTreeNode **linkingHelper) { + if (node == nil) return nil; + HFASSERT(depth != BAD_DEPTH); + Class class = (depth == 0 ? [HFBTreeLeaf class] : [HFBTreeBranch class]); + HFBTreeNode *result = [[class alloc] init]; + result->subtreeLength = node->subtreeLength; + + /* Link us in */ + HFBTreeNode *leftNeighbor = linkingHelper[0]; + if (leftNeighbor != nil) { + leftNeighbor->right = result; + result->left = leftNeighbor; + } + + /* Leave us for our future right neighbor to find */ + linkingHelper[0] = (void *)result; + + HFBTreeIndex index; + for (index = 0; index < BTREE_ORDER; index++) { + id child = node->children[index]; + if (! node->children[index]) break; + if (depth > 0) { + result->children[index] = mutable_copy_node(child, depth - 1, linkingHelper + 1); + } + else { + result->children[index] = [(TreeEntry *)child retain]; + } + } + return result; +} + +__attribute__((unused)) +static BOOL non_nulls_are_grouped_at_start(const id *ptr, NSUInteger count) { + BOOL hasSeenNull = NO; + for (NSUInteger i=0; i < count; i++) { + BOOL ptrIsNull = (ptr[i] == nil); + hasSeenNull = hasSeenNull || ptrIsNull; + if (hasSeenNull && ! ptrIsNull) { + return NO; + } + } + return YES; +} + + +static void btree_recursive_check_integrity(HFBTree *tree, HFBTreeNode *branchOrLeaf, TreeDepth_t depth, HFBTreeNode **linkHelper) { + HFASSERT(linkHelper[0] == branchOrLeaf->left); + if (linkHelper[0]) HFASSERT(linkHelper[0]->right == branchOrLeaf); + linkHelper[0] = branchOrLeaf; + + if (depth == 0) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + HFASSERT(non_nulls_are_grouped_at_start(leaf->children, BTREE_LEAF_ORDER)); + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + HFASSERT(non_nulls_are_grouped_at_start(branch->children, BTREE_BRANCH_ORDER)); + for (ChildIndex_t i = 0; i < BTREE_BRANCH_ORDER; i++) { + if (! branch->children[i]) break; + btree_recursive_check_integrity(tree, branch->children[i], depth - 1, linkHelper + 1); + } + } + ChildIndex_t childCount = count_node_values(branchOrLeaf); + if (depth < tree->depth) { // only the root may have fewer than BTREE_NODE_MINIMUM_VALUE_COUNT + HFASSERT(childCount >= BTREE_NODE_MINIMUM_VALUE_COUNT); + } + HFASSERT(childCount <= BTREE_ORDER); +} + +static HFBTreeIndex btree_recursive_check_integrity_of_cached_lengths(HFBTreeNode *branchOrLeaf) { + HFBTreeIndex result = 0; + if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i = 0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + result = HFSum(result, HFBTreeLength(leaf->children[i])); + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i = 0; i < BTREE_BRANCH_ORDER; i++) { + if (branch->children[i]) { + HFBTreeIndex subtreeLength = btree_recursive_check_integrity_of_cached_lengths(branch->children[i]); + result = HFSum(result, subtreeLength); + } + } + } + HFASSERT(result == branchOrLeaf->subtreeLength); + return result; +} + +static BOOL btree_are_cached_lengths_correct(HFBTreeNode *branchOrLeaf, HFBTreeIndex *outLength) { + if (! branchOrLeaf) { + if (outLength) *outLength = 0; + return YES; + } + HFBTreeIndex length = 0; + if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i=0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + length = HFSum(length, HFBTreeLength(leaf->children[i])); + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i=0; i < BTREE_BRANCH_ORDER; i++) { + if (! branch->children[i]) break; + HFBTreeIndex subLength = (HFBTreeIndex)-1; + if (! btree_are_cached_lengths_correct(branch->children[i], &subLength)) { + return NO; + } + length = HFSum(length, subLength); + } + } + if (outLength) *outLength = length; + return length == branchOrLeaf->subtreeLength; +} + +#if FIXUP_LENGTHS +static NSUInteger btree_entry_count(HFBTreeNode *branchOrLeaf) { + NSUInteger result = 0; + if (branchOrLeaf == nil) { + // do nothing + } + else if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i=0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + result++; + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i=0; i < BTREE_LEAF_ORDER; i++) { + if (! branch->children[i]) break; + result += btree_entry_count(branch->children[i]); + } + } + return result; +} + +static HFBTreeIndex btree_recursive_fixup_cached_lengths(HFBTree *tree, HFBTreeNode *branchOrLeaf) { + HFBTreeIndex result = 0; + if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i = 0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + result = HFSum(result, HFBTreeLength(leaf->children[i])); + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i = 0; i < BTREE_BRANCH_ORDER; i++) { + if (! branch->children[i]) break; + btree_recursive_fixup_cached_lengths(tree, branch->children[i]); + result = HFSum(result, CHECK_CAST(branch->children[i], HFBTreeNode)->subtreeLength); + } + } + branchOrLeaf->subtreeLength = result; + return result; +} +#endif + +FORCE_STATIC_INLINE void btree_apply_function_to_entries(HFBTree *tree, HFBTreeIndex offset, BOOL (*func)(id, HFBTreeIndex, void *), void *userInfo) { + struct LeafInfo_t leafInfo = btree_find_leaf(tree, offset); + HFBTreeLeaf *leaf = leafInfo.leaf; + ChildIndex_t entryIndex = leafInfo.entryIndex; + HFBTreeIndex leafOffset = leafInfo.offsetOfEntryInTree; + BOOL continueApplying = YES; + while (leaf != NULL) { + for (; entryIndex < BTREE_LEAF_ORDER; entryIndex++) { + TreeEntry *entry = leaf->children[entryIndex]; + if (! entry) break; + continueApplying = func(entry, leafOffset, userInfo); + if (! continueApplying) break; + leafOffset = HFSum(leafOffset, HFBTreeLength(entry)); + } + if (! continueApplying) break; + leaf = CHECK_CAST_OR_NULL(leaf->right, HFBTreeLeaf); + entryIndex = 0; + } +} + +- (NSEnumerator *)entryEnumerator { + if (! root) return [@[] objectEnumerator]; + HFBTreeLeaf *leaf = btree_find_leaf(self, 0).leaf; + return [[[HFBTreeEnumerator alloc] initWithLeaf:leaf] autorelease]; +} + + +static BOOL add_to_array(id entry, HFBTreeIndex offset __attribute__((unused)), void *array) { + [(id)array addObject:entry]; + return YES; +} + +- (NSArray *)allEntries { + if (! root) return @[]; + NSUInteger treeCapacity = 1; + unsigned int depthIndex = depth; + while (depthIndex--) treeCapacity *= BTREE_ORDER; + NSMutableArray *result = [NSMutableArray arrayWithCapacity: treeCapacity/2]; //assume we're half full + btree_apply_function_to_entries(self, 0, add_to_array, result); + return result; +} + +- (void)applyFunction:(BOOL (*)(id entry, HFBTreeIndex offset, void *userInfo))func toEntriesStartingAtOffset:(HFBTreeIndex)offset withUserInfo:(void *)userInfo { + NSParameterAssert(func != NULL); + if (! root) return; + btree_apply_function_to_entries(self, offset, func, userInfo); +} + +@end + + +@implementation HFBTreeEnumerator + +- (instancetype)initWithLeaf:(HFBTreeLeaf *)leaf { + NSParameterAssert(leaf != nil); + ASSERT_IS_LEAF(leaf); + currentLeaf = leaf; + return self; +} + +- (id)nextObject { + if (! currentLeaf) return nil; + if (childIndex >= BTREE_LEAF_ORDER || currentLeaf->children[childIndex] == nil) { + childIndex = 0; + currentLeaf = CHECK_CAST_OR_NULL(currentLeaf->right, HFBTreeLeaf); + } + if (currentLeaf == nil) return nil; + HFASSERT(currentLeaf->children[childIndex] != nil); + return currentLeaf->children[childIndex++]; +} + +@end diff --git a/HexFiend/HFBTreeByteArray.h b/HexFiend/HFBTreeByteArray.h new file mode 100644 index 00000000..3c1aac07 --- /dev/null +++ b/HexFiend/HFBTreeByteArray.h @@ -0,0 +1,30 @@ +// +// HFBTreeByteArray.h +// HexFiend_2 +// +// Created by peter on 4/28/09. +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import + +@class HFBTree; + +/*! @class HFBTreeByteArray +@brief The principal efficient implementation of HFByteArray. + +HFBTreeByteArray is an efficient subclass of HFByteArray that stores @link HFByteSlice HFByteSlices@endlink, using a 10-way B+ tree. This allows for insertion, deletion, and searching in approximately log-base-10 time. + +Create an HFBTreeByteArray via \c -init. It has no methods other than those on HFByteArray. +*/ + +@interface HFBTreeByteArray : HFByteArray { +@private + HFBTree *btree; +} + +/*! Designated initializer for HFBTreeByteArray. +*/ +- (instancetype)init; + +@end diff --git a/HexFiend/HFBTreeByteArray.m b/HexFiend/HFBTreeByteArray.m new file mode 100644 index 00000000..33ba9673 --- /dev/null +++ b/HexFiend/HFBTreeByteArray.m @@ -0,0 +1,279 @@ +// +// HFBTreeByteArray.m +// HexFiend_2 +// +// Created by peter on 4/28/09. +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import + +@implementation HFBTreeByteArray + +- (instancetype)init { + if ((self = [super init])) { + btree = [[HFBTree alloc] init]; + } + return self; +} + +- (void)dealloc { + [btree release]; + [super dealloc]; +} + +- (unsigned long long)length { + return [btree length]; +} + +- (NSArray *)byteSlices { + return [btree allEntries]; +} + +- (NSEnumerator *)byteSliceEnumerator { + return [btree entryEnumerator]; +} + +- (NSString*)description { + NSMutableArray* result = [NSMutableArray array]; + NSEnumerator *enumer = [self byteSliceEnumerator]; + HFByteSlice *slice; + unsigned long long offset = 0; + while ((slice = [enumer nextObject])) { + unsigned long long length = [slice length]; + [result addObject:[NSString stringWithFormat:@"{%llu - %llu}", offset, length]]; + offset = HFSum(offset, length); + } + if (! [result count]) return @"(empty tree)"; + return [NSString stringWithFormat:@"<%@: %p>: %@", [self class], self, [result componentsJoinedByString:@" "]]; + +} + +struct HFBTreeByteArrayCopyInfo_t { + unsigned char *dst; + unsigned long long startingOffset; + NSUInteger remainingLength; +}; + +static BOOL copy_bytes(id entry, HFBTreeIndex offset, void *userInfo) { + struct HFBTreeByteArrayCopyInfo_t *info = userInfo; + HFByteSlice *slice = entry; + HFASSERT(slice != nil); + HFASSERT(info != NULL); + HFASSERT(offset <= info->startingOffset); + + unsigned long long sliceLength = [slice length]; + HFASSERT(sliceLength > 0); + unsigned long long offsetIntoSlice = info->startingOffset - offset; + HFASSERT(offsetIntoSlice < sliceLength); + NSUInteger amountToCopy = ll2l(MIN(info->remainingLength, sliceLength - offsetIntoSlice)); + HFRange srcRange = HFRangeMake(info->startingOffset - offset, amountToCopy); + [slice copyBytes:info->dst range:srcRange]; + info->dst += amountToCopy; + info->startingOffset = HFSum(info->startingOffset, amountToCopy); + info->remainingLength -= amountToCopy; + return info->remainingLength > 0; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); + HFASSERT(HFMaxRange(range) <= [self length]); + if (range.length > 0) { + struct HFBTreeByteArrayCopyInfo_t copyInfo = {.dst = dst, .remainingLength = ll2l(range.length), .startingOffset = range.location}; + [btree applyFunction:copy_bytes toEntriesStartingAtOffset:range.location withUserInfo:©Info]; + } +} + +- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset { + return [btree entryContainingOffset:offset beginningOffset:actualOffset]; +} + +/* Given a HFByteArray and a range contained within it, return the first byte slice containing that range, and the range within that slice. Modifies the given range to reflect what you get when the returned slice is removed. */ +static inline HFByteSlice *findInitialSlice(HFBTree *btree, HFRange *inoutArrayRange, HFRange *outRangeWithinSlice) { + const HFRange arrayRange = *inoutArrayRange; + const unsigned long long arrayRangeEnd = HFMaxRange(arrayRange); + + unsigned long long offsetIntoSlice, lengthFromOffsetIntoSlice; + + unsigned long long beginningOffset; + HFByteSlice *slice = [btree entryContainingOffset:arrayRange.location beginningOffset:&beginningOffset]; + const unsigned long long sliceLength = [slice length]; + HFASSERT(beginningOffset <= arrayRange.location); + offsetIntoSlice = arrayRange.location - beginningOffset; + HFASSERT(offsetIntoSlice < sliceLength); + + unsigned long long sliceEndInArray = HFSum(sliceLength, beginningOffset); + if (sliceEndInArray <= arrayRangeEnd) { + /* Our slice ends before or at the requested range end */ + lengthFromOffsetIntoSlice = sliceLength - offsetIntoSlice; + } + else { + /* Our slice ends after the requested range end */ + unsigned long long overflow = sliceEndInArray - arrayRangeEnd; + HFASSERT(HFSum(overflow, offsetIntoSlice) < sliceLength); + lengthFromOffsetIntoSlice = sliceLength - HFSum(overflow, offsetIntoSlice); + } + + /* Set the out range to the input range minus the range consumed by the slice */ + inoutArrayRange->location = MIN(sliceEndInArray, arrayRangeEnd); + inoutArrayRange->length = arrayRangeEnd - inoutArrayRange->location; + + /* Set the out range within the slice to what we computed */ + *outRangeWithinSlice = HFRangeMake(offsetIntoSlice, lengthFromOffsetIntoSlice); + + return slice; +} + +- (BOOL)fastPathInsertByteSlice:(HFByteSlice *)slice atOffset:(unsigned long long)offset { + HFASSERT(offset > 0); + unsigned long long priorSliceOffset; + HFByteSlice *priorSlice = [btree entryContainingOffset:offset - 1 beginningOffset:&priorSliceOffset]; + HFByteSlice *appendedSlice = [priorSlice byteSliceByAppendingSlice:slice]; + if (appendedSlice) { + [btree removeEntryAtOffset:priorSliceOffset]; + [btree insertEntry:appendedSlice atOffset:priorSliceOffset]; + return YES; + } + else { + return NO; + } +} + +- (void)insertByteSlice:(HFByteSlice *)slice atOffset:(unsigned long long)offset { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + + if (offset == 0) { + [btree insertEntry:slice atOffset:0]; + } + else if (offset == [btree length]) { + if (! [self fastPathInsertByteSlice:slice atOffset:offset]) { + [btree insertEntry:slice atOffset:offset]; + } + } + else { + unsigned long long beginningOffset; + HFByteSlice *overlappingSlice = [btree entryContainingOffset:offset beginningOffset:&beginningOffset]; + if (beginningOffset == offset) { + if (! [self fastPathInsertByteSlice:slice atOffset:offset]) { + [btree insertEntry:slice atOffset:offset]; + } + } + else { + HFASSERT(offset > beginningOffset); + unsigned long long offsetIntoSlice = offset - beginningOffset; + unsigned long long sliceLength = [overlappingSlice length]; + HFASSERT(sliceLength > offsetIntoSlice); + HFByteSlice *left = [overlappingSlice subsliceWithRange:HFRangeMake(0, offsetIntoSlice)]; + HFByteSlice *right = [overlappingSlice subsliceWithRange:HFRangeMake(offsetIntoSlice, sliceLength - offsetIntoSlice)]; + [btree removeEntryAtOffset:beginningOffset]; + + [btree insertEntry:right atOffset:beginningOffset]; + + /* Try the fast appending path */ + HFByteSlice *joinedSlice = [left byteSliceByAppendingSlice:slice]; + if (joinedSlice) { + [btree insertEntry:joinedSlice atOffset:beginningOffset]; + } + else { + [btree insertEntry:slice atOffset:beginningOffset]; + [btree insertEntry:left atOffset:beginningOffset]; + } + } + } +} + +- (void)deleteBytesInRange:(HFRange)range { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + HFRange remainingRange = range; + + HFASSERT(HFMaxRange(range) <= [self length]); + if (range.length == 0) return; //nothing to delete + + //fast path for deleting everything + if (range.location == 0 && range.length == [self length]) { + [btree removeAllEntries]; + return; + } + + unsigned long long beforeLength = [self length]; + + unsigned long long rangeStartLocation = range.location; + HFByteSlice *beforeSlice = nil, *afterSlice = nil; + while (remainingRange.length > 0) { + HFRange rangeWithinSlice; + HFByteSlice *slice = findInitialSlice(btree, &remainingRange, &rangeWithinSlice); + const unsigned long long sliceLength = [slice length]; + const unsigned long long rangeWithinSliceEnd = HFMaxRange(rangeWithinSlice); + HFRange lefty = HFRangeMake(0, rangeWithinSlice.location); + HFRange righty = HFRangeMake(rangeWithinSliceEnd, sliceLength - rangeWithinSliceEnd); + HFASSERT(lefty.length == 0 || beforeSlice == nil); + HFASSERT(righty.length == 0 || afterSlice == nil); + + unsigned long long beginningOffset = remainingRange.location - HFMaxRange(rangeWithinSlice); + + if (lefty.length > 0){ + beforeSlice = [slice subsliceWithRange:lefty]; + rangeStartLocation = beginningOffset; + } + if (righty.length > 0) afterSlice = [slice subsliceWithRange:righty]; + + [btree removeEntryAtOffset:beginningOffset]; + remainingRange.location = beginningOffset; + } + if (afterSlice) { + [self insertByteSlice:afterSlice atOffset:rangeStartLocation]; + } + if (beforeSlice) { + [self insertByteSlice:beforeSlice atOffset:rangeStartLocation]; + } + + unsigned long long afterLength = [self length]; + HFASSERT(beforeLength - afterLength == range.length); +} + +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + + if (lrange.length > 0) { + [self deleteBytesInRange:lrange]; + } + if ([slice length] > 0) { + [self insertByteSlice:slice atOffset:lrange.location]; + } +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + USE(zone); + HFBTreeByteArray *result = [[[self class] alloc] init]; + [result->btree release]; + result->btree = [btree mutableCopy]; + return result; +} + +- (id)subarrayWithRange:(HFRange)range { + if (range.location == 0 && range.length == [self length]) { + return [[self mutableCopy] autorelease]; + } + HFBTreeByteArray *result = [[[[self class] alloc] init] autorelease]; + HFRange remainingRange = range; + unsigned long long offsetInResult = 0; + while (remainingRange.length > 0) { + HFRange rangeWithinSlice; + HFByteSlice *slice = findInitialSlice(btree, &remainingRange, &rangeWithinSlice); + HFByteSlice *subslice; + if (rangeWithinSlice.location == 0 && rangeWithinSlice.length == [slice length]) { + subslice = slice; + } + else { + subslice = [slice subsliceWithRange:rangeWithinSlice]; + } + [result insertByteSlice:subslice atOffset:offsetInResult]; + offsetInResult = HFSum(offsetInResult, rangeWithinSlice.length); + } + return result; +} + +@end diff --git a/HexFiend/HFByteArray.h b/HexFiend/HFByteArray.h new file mode 100644 index 00000000..dd452d5b --- /dev/null +++ b/HexFiend/HFByteArray.h @@ -0,0 +1,180 @@ +// +// HFByteArray.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +@class HFByteSlice, HFFileReference, HFByteRangeAttributeArray; + +typedef NS_ENUM(NSUInteger, HFByteArrayDataStringType) { + HFHexDataStringType, + HFASCIIDataStringType +}; + + +/*! @class HFByteArray +@brief The principal Model class for HexFiend's MVC architecture. + +HFByteArray implements the Model portion of HexFiend.framework. It is logically a mutable, resizable array of bytes, with a 64 bit length. It is somewhat analagous to a 64 bit version of NSMutableData, except that it is designed to enable efficient (faster than O(n)) implementations of insertion and deletion. + +HFByteArray, being an abstract class, will raise an exception if you attempt to instantiate it directly. For most uses, instantiate HFBTreeByteArray instead, with the usual [[class alloc] init]. + +HFByteArray also exposes itself as an array of @link HFByteSlice HFByteSlices@endlink, which are logically immutable arrays of bytes. which is useful for operations such as file saving that need to access the underlying byte slices. + +HFByteArray contains a generation count, which is incremented whenever the HFByteArray changes (to allow caches to be implemented on top of it). It also includes the notion of locking: a locked HFByteArray will raise an exception if written to, but it may still be read. + +ByteArrays have the usual threading restrictions for non-concurrent data structures. It is safe to read an HFByteArray concurrently from multiple threads. It is not safe to read an HFByteArray while it is being modified from another thread, nor is it safe to modify one simultaneously from two threads. + +HFByteArray is an abstract class. It will raise an exception if you attempt to instantiate it directly. The principal concrete subclass is HFBTreeByteArray. +*/ + +@class HFByteRangeAttributeArray; + +@interface HFByteArray : NSObject { +@private + NSUInteger changeLockCounter; + NSUInteger changeGenerationCount; +} + +/*! @name Initialization + */ +//@{ +/*! Initialize to a byte array containing only the given slice. */ +- (instancetype)initWithByteSlice:(HFByteSlice *)slice; + +/*! Initialize to a byte array containing the slices of the given array. */ +- (instancetype)initWithByteArray:(HFByteArray *)array; +//@} + + +/*! @name Accessing raw data +*/ +//@{ + +/*! Returns the length of the HFByteArray as a 64 bit unsigned long long. This is an abstract method that concrete subclasses must override. */ +- (unsigned long long)length; + +/*! Copies a range of bytes into a buffer. This is an abstract method that concrete subclasses must override. */ +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range; +//@} + +/*! @name Accessing byte slices + Methods to access the byte slices underlying the HFByteArray. +*/ +//@{ +/*! Returns the contents of the receiver as an array of byte slices. This is an abstract method that concrete subclasses must override. */ +- (NSArray *)byteSlices; + +/*! Returns an NSEnumerator representing the byte slices of the receiver. This is implemented as enumerating over the result of -byteSlices, but subclasses can override this to be more efficient. */ +- (NSEnumerator *)byteSliceEnumerator; + +/*! Returns the byte slice containing the byte at the given index, and the actual offset of this slice. */ +- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset; +//@} + +/*! @name Modifying the byte array + Methods to modify the given byte array. +*/ +//@{ +/*! Insert an HFByteSlice in the given range. The maximum value of the range must not exceed the length of the subarray. The length of the given slice is not required to be equal to length of the range - in other words, this method may change the length of the receiver. This is an abstract method that concrete subclasses must override. */ +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange; + +/*! Insert an HFByteArray in the given range. This is implemented via calling insertByteSlice:inRange: with the byte slices from the given byte array. */ +- (void)insertByteArray:(HFByteArray *)array inRange:(HFRange)lrange; + +/*! Delete bytes in the given range. This is implemented on the base class by creating an empty byte array and inserting it in the range to be deleted, via insertByteSlice:inRange:. */ +- (void)deleteBytesInRange:(HFRange)range; + +/*! Returns a new HFByteArray containing the given range. This is an abstract method that concrete subclasses must override. */ +- (HFByteArray *)subarrayWithRange:(HFRange)range; +//@} + +/*! @name Write locking and generation count + Methods to lock and query the lock that prevents writes. +*/ +//@{ + +/*! Increment the change lock. Until the change lock reaches 0, all modifications to the receiver will raise an exception. */ +- (void)incrementChangeLockCounter; + +/*! Decrement the change lock. If the change lock reaches 0, modifications will be allowed again. */ +- (void)decrementChangeLockCounter; + +/*! Query if the changes are locked. This method is KVO compliant. */ +- (BOOL)changesAreLocked; +//@} + +/* @name Generation count + Manipulate the generation count */ +// @{ +/*! Increments the generation count, unless the receiver is locked, in which case it raises an exception. All subclasses of HFByteArray should call this method at the beginning of any overridden method that may modify the receiver. + @param sel The selector that would modify the receiver (e.g. deleteBytesInRange:). This is usually _cmd. */ +- (void)incrementGenerationOrRaiseIfLockedForSelector:(SEL)sel; + +/*! Return the change generation count. Every change to the ByteArray increments this by one or more. This can be used for caching layers on top of HFByteArray, to known when to expire their cache. */ +- (NSUInteger)changeGenerationCount; + +//@} + + + +/*! @name Searching +*/ +//@{ +/*! Searches the receiver for a byte array matching findBytes within the given range, and returns the index that it was found. This is a concrete method on HFByteArray. + @param findBytes The HFByteArray containing the data to be found (the needle to the receiver's haystack). + @param range The range of the receiver in which to search. The end of the range must not exceed the receiver's length. + @param forwards If this is YES, then the first match within the range is returned. Otherwise the last is returned. + @param progressTracker An HFProgressTracker to allow progress reporting and cancelleation for the search operation. + @return The index in the receiver of bytes equal to findBytes, or ULLONG_MAX if the byte array was not found (or the operation was cancelled) +*/ +- (unsigned long long)indexOfBytesEqualToBytes:(HFByteArray *)findBytes inRange:(HFRange)range searchingForwards:(BOOL)forwards trackingProgress:(id)progressTracker; +//@} + +@end + + +/*! @category HFByteArray(HFFileWriting) + @brief HFByteArray methods for writing to files, and preparing other HFByteArrays for potentially destructive file writes. +*/ +@interface HFByteArray (HFFileWriting) +/*! Attempts to write the receiver to a file. This is a concrete method on HFByteArray. + @param targetURL A URL to the file to be written to. It is OK for the receiver to contain one or more instances of HFByteSlice that are sourced from the file. + @param progressTracker An HFProgressTracker to allow progress reporting and cancelleation for the write operation. + @param error An out NSError parameter. + @return YES if the write succeeded, NO if it failed. +*/ +- (BOOL)writeToFile:(NSURL *)targetURL trackingProgress:(id)progressTracker error:(NSError **)error; + +/*! Returns the ranges of the file that would be modified, if the receiver were written to it. This is useful (for example) in determining if the clipboard can be preserved after a save operation. This is a concrete method on HFByteArray. + @param reference An HFFileReference to the file to be modified + @return An array of @link HFRangeWrapper HFRangeWrappers@endlink, representing the ranges of the file that would be affected. If no range would be affected, the result is an empty array. +*/ +- (NSArray *)rangesOfFileModifiedIfSavedToFile:(HFFileReference *)reference; + +/*! Attempts to modify the receiver so that it no longer depends on any of the HFRanges in the array within the given file. It is not necessary to perform this operation on the byte array that is being written to the file. + @param ranges An array of HFRangeWrappers, representing ranges in the given file that the receiver should no longer depend on. + @param reference The HFFileReference that the receiver should no longer depend on. + @param hint A dictionary that can be used to improve the efficiency of the operation, by allowing multiple byte arrays to share the same state. If you plan to call this method on multiple byte arrays, pass the first one an empty NSMutableDictionary, and pass the same dictionary to subsequent calls. + @return A YES return indicates the operation was successful, and the receiver no longer contains byte slices that source data from any of the ranges of the given file (or never did). A NO return indicates that breaking the dependencies would require too much memory, and so the receiver still depends on some of those ranges. +*/ +- (BOOL)clearDependenciesOnRanges:(NSArray *)ranges inFile:(HFFileReference *)reference hint:(NSMutableDictionary *)hint; + +@end + + +/*! @category HFByteArray(HFAttributes) + @brief HFByteArray methods for attributes of byte arrays. +*/ +@interface HFByteArray (HFAttributes) + +/*! Returns a byte range attribute array for the bytes in the given range. */ +- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range; + +/*! Returns the HFByteArray level byte range attribute array. Default is to return nil. */ +- (HFByteRangeAttributeArray *)byteRangeAttributeArray; + +@end diff --git a/HexFiend/HFByteArray.m b/HexFiend/HFByteArray.m new file mode 100644 index 00000000..55f25cc8 --- /dev/null +++ b/HexFiend/HFByteArray.m @@ -0,0 +1,218 @@ +// +// HFByteArray.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + + +@implementation HFByteArray + +- (instancetype)init { + if ([self class] == [HFByteArray class]) { + [NSException raise:NSInvalidArgumentException format:@"init sent to HFByteArray, but HFByteArray is an abstract class. Instantiate one of its subclasses instead, like HFBTreeByteArray."]; + } + return [super init]; +} + +- (instancetype)initWithByteSlice:(HFByteSlice *)slice { + if(!(self = [self init])) return nil; + self = [self init]; + [self insertByteSlice:slice inRange:HFRangeMake(0, 0)]; + return self; +} + +- (instancetype)initWithByteArray:(HFByteArray *)array { + if(!(self = [self init])) return nil; + NSEnumerator *e = [array byteSliceEnumerator]; + HFByteSlice *slice; + while((slice = [e nextObject])) { + [self insertByteSlice:slice inRange:HFRangeMake([self length], 0)]; + } + return self; +} + +- (NSArray *)byteSlices { UNIMPLEMENTED(); } +- (unsigned long long)length { UNIMPLEMENTED(); } +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { USE(dst); USE(range); UNIMPLEMENTED_VOID(); } +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { USE(slice); USE(lrange); UNIMPLEMENTED_VOID(); } + +- (NSEnumerator *)byteSliceEnumerator { + return [[self byteSlices] objectEnumerator]; +} + +- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset { + HFByteSlice *slice; + unsigned long long current = 0; + NSEnumerator *enumer = [self byteSliceEnumerator]; + while ((slice = [enumer nextObject])) { + unsigned long long sum = HFSum([slice length], current); + if (sum > offset) break; + current = sum; + } + if (actualOffset) *actualOffset = current; + return slice; +} + +- (void)insertByteArray:(HFByteArray*)array inRange:(HFRange)lrange { + REQUIRE_NOT_NULL(array); + HFASSERT(HFRangeIsSubrangeOfRange(lrange, HFRangeMake(0, [self length]))); +#ifndef NDEBUG + unsigned long long expectedLength = [self length] - lrange.length + [array length]; +#endif + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + NSEnumerator *sliceEnumerator; + HFByteSlice *byteSlice; + if (array == self) { + /* Guard against self insertion */ + sliceEnumerator = [[array byteSlices] objectEnumerator]; + } + else { + sliceEnumerator = [array byteSliceEnumerator]; + } + while ((byteSlice = [sliceEnumerator nextObject])) { + [self insertByteSlice:byteSlice inRange:lrange]; + lrange.location += [byteSlice length]; + lrange.length = 0; + } + /* If there were no slices, delete the lrange */ + if (lrange.length > 0) { + [self deleteBytesInRange:lrange]; + } +#ifndef NDEBUG + HFASSERT(expectedLength == [self length]); +#endif +} + +- (HFByteArray *)subarrayWithRange:(HFRange)range { USE(range); UNIMPLEMENTED(); } + +- (id)mutableCopyWithZone:(NSZone *)zone { + USE(zone); + return [[self subarrayWithRange:HFRangeMake(0, [self length])] retain]; +} + +- (id)copyWithZone:(NSZone *)zone { + USE(zone); + return [[self subarrayWithRange:HFRangeMake(0, [self length])] retain]; +} + +- (void)deleteBytesInRange:(HFRange)lrange { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + HFByteSlice* slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData data]]; + [self insertByteSlice:slice inRange:lrange]; + [slice release]; +} + +- (BOOL)isEqual:v { + REQUIRE_NOT_NULL(v); + if (self == v) return YES; + else if (! [v isKindOfClass:[HFByteArray class]]) return NO; + else { + HFByteArray* obj = v; + unsigned long long length = [self length]; + if (length != [obj length]) return NO; + unsigned long long offset; + unsigned char buffer1[1024]; + unsigned char buffer2[sizeof buffer1 / sizeof *buffer1]; + for (offset = 0; offset < length; offset += sizeof buffer1) { + size_t amountToGrab = sizeof buffer1; + if (amountToGrab > length - offset) amountToGrab = ll2l(length - offset); + [self copyBytes:buffer1 range:HFRangeMake(offset, amountToGrab)]; + [obj copyBytes:buffer2 range:HFRangeMake(offset, amountToGrab)]; + if (memcmp(buffer1, buffer2, amountToGrab)) return NO; + } + } + return YES; +} + +- (unsigned long long)indexOfBytesEqualToBytes:(HFByteArray *)findBytes inRange:(HFRange)range searchingForwards:(BOOL)forwards trackingProgress:(id)progressTracker { + UNIMPLEMENTED(); +} + +- (BOOL)_debugIsEqual:(HFByteArray *)v { + REQUIRE_NOT_NULL(v); + if (! [v isKindOfClass:[HFByteArray class]]) return NO; + HFByteArray* obj = v; + unsigned long long length = [self length]; + if (length != [obj length]) { + printf("Lengths differ: %llu versus %llu\n", length, [obj length]); + abort(); + return NO; + } + + unsigned long long offset; + unsigned char buffer1[1024]; + unsigned char buffer2[sizeof buffer1 / sizeof *buffer1]; + for (offset = 0; offset < length; offset += sizeof buffer1) { + memset(buffer1, 0, sizeof buffer1); + memset(buffer2, 0, sizeof buffer2); + size_t amountToGrab = sizeof buffer1; + if (amountToGrab > length - offset) amountToGrab = ll2l(length - offset); + [self copyBytes:buffer1 range:HFRangeMake(offset, amountToGrab)]; + [obj copyBytes:buffer2 range:HFRangeMake(offset, amountToGrab)]; + size_t i; + for (i=0; i < amountToGrab; i++) { + if (buffer1[i] != buffer2[i]) { + printf("Inconsistency found at %llu (%02x versus %02x)\n", i + offset, buffer1[i], buffer2[i]); + abort(); + return NO; + } + } + } + return YES; +} + +- (NSData *)_debugData { + NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)[self length]]; + [self copyBytes:[data mutableBytes] range:HFRangeMake(0, [self length])]; + return data; +} + +- (BOOL)_debugIsEqualToData:(NSData *)val { + REQUIRE_NOT_NULL(val); + HFByteArray *byteArray = [[NSClassFromString(@"HFFullMemoryByteArray") alloc] init]; + HFByteSlice *byteSlice = [[HFFullMemoryByteSlice alloc] initWithData:val]; + [byteArray insertByteSlice:byteSlice inRange:HFRangeMake(0, 0)]; + [byteSlice release]; + BOOL result = [self _debugIsEqual:byteArray]; + [byteArray release]; + return result; +} + +- (void)incrementChangeLockCounter { + [self willChangeValueForKey:@"changesAreLocked"]; + if (HFAtomicIncrement(&changeLockCounter, NO) == 0) { + [NSException raise:NSInvalidArgumentException format:@"change lock counter overflow for %@", self]; + } + [self didChangeValueForKey:@"changesAreLocked"]; +} + +- (void)decrementChangeLockCounter { + [self willChangeValueForKey:@"changesAreLocked"]; + if (HFAtomicDecrement(&changeLockCounter, NO) == NSUIntegerMax) { + [NSException raise:NSInvalidArgumentException format:@"change lock counter underflow for %@", self]; + } + [self didChangeValueForKey:@"changesAreLocked"]; +} + +- (BOOL)changesAreLocked { + return !! changeLockCounter; +} + +- (NSUInteger)changeGenerationCount { + return changeGenerationCount; +} + +- (void)incrementGenerationOrRaiseIfLockedForSelector:(SEL)sel { + if (changeLockCounter) { + [NSException raise:NSInvalidArgumentException format:@"Selector %@ sent to a locked byte array %@", NSStringFromSelector(sel), self]; + } + else { + HFAtomicIncrement(&changeGenerationCount, YES); + } +} + +@end diff --git a/HexFiend/HFByteArray_Internal.h b/HexFiend/HFByteArray_Internal.h new file mode 100644 index 00000000..648dd92c --- /dev/null +++ b/HexFiend/HFByteArray_Internal.h @@ -0,0 +1,8 @@ +#import + +@interface HFByteArray (HFInternal) + +- (BOOL)_debugIsEqual:(HFByteArray *)val; +- (BOOL)_debugIsEqualToData:(NSData *)val; + +@end diff --git a/HexFiend/HFByteSlice.h b/HexFiend/HFByteSlice.h new file mode 100644 index 00000000..b17da08d --- /dev/null +++ b/HexFiend/HFByteSlice.h @@ -0,0 +1,53 @@ +// +// HFByteSlice.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +@class HFFileReference, HFByteRangeAttributeArray; + +/*! @class HFByteSlice +@brief A class representing a source of data for an HFByteArray. + +HFByteSlice is an abstract class encapsulating primitive data sources (files, memory buffers, etc.). Each source must support random access reads, and have a well defined length. All HFByteSlices are \b immutable. + +The two principal subclasses of HFByteSlice are HFSharedMemoryByteSlice and HFFileByteSlice, which respectively encapsulate data from memory and from a file. +*/ +@interface HFByteSlice : NSObject { + NSUInteger retainCount; +} + +/*! Return the length of the byte slice as a 64 bit value. This is an abstract method that concrete subclasses must override. */ +- (unsigned long long)length; + +/*! Copies a range of data from the byte slice into an in-memory buffer. This is an abstract method that concrete subclasses must override. */ +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range; + +/*! Returns a new slice containing a subrange of the given slice. This is an abstract method that concrete subclasses must override. */ +- (HFByteSlice *)subsliceWithRange:(HFRange)range; + +/*! Attempts to create a new byte slice by appending one byte slice to another. This does not modify the receiver or the slice argument (after all, both are immutable). This is provided as an optimization, and is allowed to return nil if the appending cannot be done efficiently. The default implementation returns nil. +*/ +- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice; + +/*! Returns YES if the receiver is sourced from a file. The default implementation returns NO. This is used to estimate cost when writing to a file. +*/ +- (BOOL)isSourcedFromFile; + +/*! For a given file reference, returns the range within the file that the receiver is sourced from. If the receiver is not sourced from this file, returns {ULLONG_MAX, ULLONG_MAX}. The default implementation returns {ULLONG_MAX, ULLONG_MAX}. This is used during file saving to to determine how to properly overwrite a given file. +*/ +- (HFRange)sourceRangeForFile:(HFFileReference *)reference; + +@end + +/*! @category HFByteSlice(HFAttributes) + @brief Methods for querying attributes of individual byte slices. */ +@interface HFByteSlice (HFAttributes) + +/*! Returns the attributes for the bytes in the given range. */ +- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range; + +@end diff --git a/HexFiend/HFByteSlice.m b/HexFiend/HFByteSlice.m new file mode 100644 index 00000000..fadb442a --- /dev/null +++ b/HexFiend/HFByteSlice.m @@ -0,0 +1,85 @@ +// +// HFByteSlice.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + + +@implementation HFByteSlice + +- (instancetype)init { + if ([self class] == [HFByteSlice class]) { + [NSException raise:NSInvalidArgumentException format:@"init sent to HFByteArray, but HFByteArray is an abstract class. Instantiate one of its subclasses instead."]; + } + return [super init]; +} + +- (unsigned long long)length { UNIMPLEMENTED(); } + +- (void)copyBytes:(unsigned char*)dst range:(HFRange)range { USE(dst); USE(range); UNIMPLEMENTED_VOID(); } + +- (HFByteSlice *)subsliceWithRange:(HFRange)range { USE(range); UNIMPLEMENTED(); } + +- (void)constructNewByteSlicesAboutRange:(HFRange)range first:(HFByteSlice **)first second:(HFByteSlice **)second { + const unsigned long long length = [self length]; + + //clip the range to our extent + range.location = llmin(range.location, length); + range.length = llmin(range.length, length - range.location); + + HFRange firstRange = {0, range.location}; + HFRange secondRange = {range.location + range.length, [self length] - (range.location + range.length)}; + + if (first) { + if (firstRange.length > 0) + *first = [self subsliceWithRange:firstRange]; + else + *first = nil; + } + + if (second) { + if (secondRange.length > 0) + *second = [self subsliceWithRange:secondRange]; + else + *second = nil; + } +} + +- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice { + USE(slice); + return nil; +} + +- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range { + USE(range); + return nil; +} + +- (BOOL)isSourcedFromFile { + return NO; +} + +- (HFRange)sourceRangeForFile:(HFFileReference *)reference { + USE(reference); + return HFRangeMake(ULLONG_MAX, ULLONG_MAX); +} + +- (id)retain { + HFAtomicIncrement(&retainCount, NO); + return self; +} + +- (oneway void)release { + if (HFAtomicDecrement(&retainCount, NO) == (NSUInteger)(-1)) { + [self dealloc]; + } +} + +- (NSUInteger)retainCount { + return 1 + retainCount; +} + +@end diff --git a/HexFiend/HFByteSlice_Private.h b/HexFiend/HFByteSlice_Private.h new file mode 100644 index 00000000..2827bfd3 --- /dev/null +++ b/HexFiend/HFByteSlice_Private.h @@ -0,0 +1,7 @@ +#import + +@interface HFByteSlice (HFByteSlice_Private) + +- (void)constructNewByteSlicesAboutRange:(HFRange)range first:(HFByteSlice **)first second:(HFByteSlice **)second; + +@end diff --git a/HexFiend/HFController.h b/HexFiend/HFController.h new file mode 100644 index 00000000..7bdc0700 --- /dev/null +++ b/HexFiend/HFController.h @@ -0,0 +1,395 @@ +// +// HFController.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +#import + +/*! @header HFController + @abstract The HFController.h header contains the HFController class, which is a central class in Hex Fiend. +*/ + +@class HFRepresenter, HFByteArray, HFFileReference, HFControllerCoalescedUndo, HFByteRangeAttributeArray; + +/*! @enum HFControllerPropertyBits + The HFControllerPropertyBits bitmask is used to inform the HFRepresenters of a change in the current state that they may need to react to. A bitmask of the changed properties is passed to representerChangedProperties:. It is common for multiple properties to be included in such a bitmask. +*/ +typedef NS_OPTIONS(NSUInteger, HFControllerPropertyBits) { + HFControllerContentValue = 1 << 0, /*!< Indicates that the contents of the ByteArray has changed within the document. There is no indication as to what the change is. If redisplaying everything is expensive, Representers should cache their displayed data and compute any changes manually. */ + HFControllerContentLength = 1 << 1, /*!< Indicates that the length of the ByteArray has changed. */ + HFControllerDisplayedLineRange = 1 << 2, /*!< Indicates that the displayedLineRange property of the document has changed (e.g. the user scrolled). */ + HFControllerSelectedRanges = 1 << 3, /*!< Indicates that the selectedContentsRanges property of the document has changed (e.g. the user selected some other range). */ + HFControllerSelectionPulseAmount = 1 << 4, /*!< Indicates that the amount of "pulse" to show in the Find pulse indicator has changed. */ + HFControllerBytesPerLine = 1 << 5, /*!< Indicates that the number of bytes to show per line has changed. */ + HFControllerBytesPerColumn = 1 << 6, /*!< Indicates that the number of bytes per column (byte grouping) has changed. */ + HFControllerEditable = 1 << 7, /*!< Indicates that the document has become (or is no longer) editable. */ + HFControllerFont = 1 << 8, /*!< Indicates that the font property has changed. */ + HFControllerAntialias = 1 << 9, /*!< Indicates that the shouldAntialias property has changed. */ + HFControllerLineHeight = 1 << 10, /*!< Indicates that the lineHeight property has changed. */ + HFControllerViewSizeRatios = 1 << 11, /*!< Indicates that the optimum size for each view may have changed; used by HFLayoutController after font changes. */ + HFControllerByteRangeAttributes = 1 << 12, /*!< Indicates that some attributes of the ByteArray has changed within the document. There is no indication as to what the change is. */ + HFControllerByteGranularity = 1 << 13, /*!< Indicates that the byte granularity has changed. For example, when moving from ASCII to UTF-16, the byte granularity increases from 1 to 2. */ + HFControllerBookmarks = 1 << 14, /*!< Indicates that a bookmark has been added or removed. */ + HFControllerColorBytes = 1 << 15, /*!< Indicates that the shouldColorBytes property has changed. */ + HFControllerShowCallouts = 1 << 16, /*!< Indicates that the shouldShowCallouts property has changed. */ + HFControllerHideNullBytes = 1 << 17, /*!< Indicates that the shouldHideNullBytes property has changed. */ +}; + +/*! @enum HFControllerMovementDirection + +The HFControllerMovementDirection enum is used to specify a direction (either left or right) in various text editing APIs. HexFiend does not support left-to-right languages. +*/ +typedef NS_ENUM(NSInteger, HFControllerMovementDirection) { + HFControllerDirectionLeft, + HFControllerDirectionRight +}; + +/*! @enum HFControllerSelectionTransformation + +The HFControllerSelectionTransformation enum is used to specify what happens to the selection in various APIs. This is mainly interesting for text-editing style Representers. +*/ +typedef NS_ENUM(NSInteger, HFControllerSelectionTransformation) { + HFControllerDiscardSelection, /*!< The selection should be discarded. */ + HFControllerShiftSelection, /*!< The selection should be moved, without changing its length. */ + HFControllerExtendSelection /*!< The selection should be extended, changing its length. */ +}; + +/*! @enum HFControllerMovementGranularity + +The HFControllerMovementGranularity enum is used to specify the granularity of text movement in various APIs. This is mainly interesting for text-editing style Representers. +*/ +typedef NS_ENUM(NSInteger, HFControllerMovementGranularity) { + HFControllerMovementByte, /*!< Move by individual bytes */ + HFControllerMovementColumn, /*!< Move by a column */ + HFControllerMovementLine, /*!< Move by lines */ + HFControllerMovementPage, /*!< Move by pages */ + HFControllerMovementDocument /*!< Move by the whole document */ +}; + +/*! @enum HFEditMode + +HFEditMode enumerates the different edit modes that a document might be in. + */ +typedef NS_ENUM(NSInteger, HFEditMode) { + HFInsertMode, + HFOverwriteMode, + HFReadOnlyMode, +} ; + +/*! @class HFController +@brief A central class that acts as the controller layer for HexFiend.framework + +HFController acts as the controller layer in the MVC architecture of HexFiend. The HFController plays several significant central roles, including: + - Mediating between the data itself (in the HFByteArray) and the views of the data (the @link HFRepresenter HFRepresenters@endlink). + - Propagating changes to the views. + - Storing properties common to all Representers, such as the currently diplayed range, the currently selected range(s), the font, etc. + - Handling text editing actions, such as selection changes or insertions/deletions. + +An HFController is the top point of ownership for a HexFiend object graph. It retains both its ByteArray (model) and its array of Representers (views). + +You create an HFController via [[HFController alloc] init]. After that, give it an HFByteArray via setByteArray:, and some Representers via addRepresenter:. Then insert the Representers' views in a window, and you're done. + +*/ +@interface HFController : NSObject { +@private + NSMutableArray *representers; + HFByteArray *byteArray; + NSMutableArray *selectedContentsRanges; + HFRange displayedContentsRange; + HFFPRange displayedLineRange; + NSUInteger bytesPerLine; + NSUInteger bytesPerColumn; + CGFloat lineHeight; + + NSUInteger currentPropertyChangeToken; + NSMutableArray *additionalPendingTransactions; + HFControllerPropertyBits propertiesToUpdateInCurrentTransaction; + + NSUndoManager *undoManager; + NSMutableSet *undoOperations; + HFControllerCoalescedUndo *undoCoalescer; + + unsigned long long selectionAnchor; + HFRange selectionAnchorRange; + + CFAbsoluteTime pulseSelectionStartTime, pulseSelectionCurrentTime; + NSTimer *pulseSelectionTimer; + + /* Basic cache support */ + HFRange cachedRange; + NSData *cachedData; + NSUInteger cachedGenerationIndex; + + struct { + unsigned antialias:1; + unsigned colorbytes:1; + unsigned showcallouts:1; + unsigned hideNullBytes:1; + HFEditMode editMode:2; + unsigned editable:1; + unsigned selectable:1; + unsigned selectionInProgress:1; + unsigned shiftExtendSelection:1; + unsigned commandExtendSelection:1; + unsigned livereload:1; + } _hfflags; +} + +/*! @name Representer handling. + Methods for modifying the list of HFRepresenters attached to a controller. Attached representers receive the controllerDidChange: message when various properties of the controller change. A representer may only be attached to one controller at a time. Representers are retained by the controller. +*/ +//@{ +/// Gets the current array of representers attached to this controller. +@property (readonly, copy) NSArray *representers; + +/// Adds a new representer to this controller. +- (void)addRepresenter:(HFRepresenter *)representer; + +/// Removes an existing representer from this controller. The representer must be present in the array of representers. +- (void)removeRepresenter:(HFRepresenter *)representer; + +//@} + +/*! @name Property transactions + Methods for temporarily delaying notifying representers of property changes. There is a property transaction stack, and all property changes are collected until the last token is popped off the stack, at which point all representers are notified of all collected changes via representerChangedProperties:. To use this, call beginPropertyChangeTransaction, and record the token that is returned. Pass it to endPropertyChangeTransaction: to notify representers of all changed properties in bulk. + + Tokens cannot be popped out of order - they are used only as a correctness check. +*/ +//@{ +/*! Begins delaying property change transactions. Returns a token that should be passed to endPropertyChangeTransactions:. */ +- (NSUInteger)beginPropertyChangeTransaction; + +/*! Pass a token returned from beginPropertyChangeTransaction to this method to pop the transaction off the stack and, if the stack is empty, to notify Representers of all collected changes. Tokens cannot be popped out of order - they are used strictly as a correctness check. */ +- (void)endPropertyChangeTransaction:(NSUInteger)token; +//@} + +/*! @name Byte array + Set and get the byte array. */ +//@{ + +/*! The byte array must be non-nil. In general, HFRepresenters should not use this to determine what bytes to display. Instead they should use copyBytes:range: or dataForRange: below. */ +@property (nonatomic, strong) HFByteArray *byteArray; + +/*! Replaces the entire byte array with a new one, preserving as much of the selection as possible. Unlike setByteArray:, this method is undoable, and intended to be used from representers that make a global change (such as Replace All). */ +- (void)replaceByteArray:(HFByteArray *)newArray; +//@} + +/*! @name Properties shared between all representers + The following properties are considered global among all HFRepresenters attached to the receiver. +*/ +//@{ +/*! Returns the number of lines on which the cursor may be placed. This is always at least 1, and is equivalent to (unsigned long long)(HFRoundUpToNextMultiple(contentsLength, bytesPerLine) / bytesPerLine) */ +- (unsigned long long)totalLineCount; + +/*! Indicates the number of bytes per line, which is a global property among all the line-oriented representers. */ +- (NSUInteger)bytesPerLine; + +/*! Returns the height of a line, in points. This is generally determined by the font. Representers that wish to align things to lines should use this. */ +- (CGFloat)lineHeight; + +//@} + +/*! @name Selection pulsing + Used to show the current selection after a change, similar to Find in Safari +*/ +//{@ + +/*! Begins selection pulsing (e.g. following a successful Find operation). Representers will receive callbacks indicating that HFControllerSelectionPulseAmount has changed. */ +- (void)pulseSelection; + +/*! Return the amount that the "Find pulse indicator" should show. 0 means no pulse, 1 means maximum pulse. This is useful for Representers that support find and replace. */ +- (double)selectionPulseAmount; +//@} + +/*! @name Selection handling + Methods for manipulating the current selected ranges. Hex Fiend supports discontiguous selection. +*/ +//{@ + +/*! An array of HFRangeWrappers, representing the selected ranges. It satisfies the following: + The array is non-nil. + There always is at least one selected range. + If any range has length 0, that range is the only range. + No range extends beyond the contentsLength, with the exception of a single zero-length range at the end. + + When setting, the setter MUST obey the above criteria. A zero length range when setting or getting represents the cursor position. */ +@property (nonatomic, copy) NSArray *selectedContentsRanges; + +/*! Selects the entire contents. */ +- (IBAction)selectAll:(id)sender; + +/*! Returns the smallest value in the selected contents ranges, or the insertion location if the selection is empty. */ +- (unsigned long long)minimumSelectionLocation; + +/*! Returns the largest HFMaxRange of the selected contents ranges, or the insertion location if the selection is empty. */ +- (unsigned long long)maximumSelectionLocation; + +/*! Convenience method for creating a byte array containing all of the selected bytes. If the selection has length 0, this returns an empty byte array. */ +- (HFByteArray *)byteArrayForSelectedContentsRanges; +//@} + +/* Number of bytes used in each column for a text-style representer. */ +@property (nonatomic) NSUInteger bytesPerColumn; + +/*! @name Edit Mode + Determines what mode we're in, read-only, overwrite or insert. */ +@property (nonatomic) HFEditMode editMode; + +/*! @name Displayed line range + Methods for setting and getting the current range of displayed lines. +*/ +//{@ +/*! Get the current displayed line range. The displayed line range is an HFFPRange (range of long doubles) containing the lines that are currently displayed. + + The values may be fractional. That is, if only the bottom half of line 4 through the top two thirds of line 8 is shown, then the displayedLineRange.location will be 4.5 and the displayedLineRange.length will be 3.17 ( = 7.67 - 4.5). Representers are expected to be able to handle such fractional values. + + When setting the displayed line range, the given range must be nonnegative, and the maximum of the range must be no larger than the total line count. + +*/ +@property (nonatomic) HFFPRange displayedLineRange; + +/*! Modify the displayedLineRange so that as much of the given range as can fit is visible. If possible, moves by as little as possible so that the visible ranges before and afterward intersect with each other. */ +- (void)maximizeVisibilityOfContentsRange:(HFRange)range; + +/*! Modify the displayedLineRange as to center the given contents range. If the range is near the bottom or top, this will center as close as possible. If contents range is too large to fit, it centers the top of the range. contentsRange may be empty. */ +- (void)centerContentsRange:(HFRange)range; + +//@} + +/*! The current font. */ +@property (nonatomic, copy) NSFont *font; + +/*! The undo manager. If no undo manager is set, then undo is not supported. By default the undo manager is nil. +*/ +@property (nonatomic, strong) NSUndoManager *undoManager; + +/*! Whether the user can edit the document. */ +@property (nonatomic) BOOL editable; + +/*! Whether the text should be antialiased. Note that Mac OS X settings may prevent antialiasing text below a certain point size. */ +@property (nonatomic) BOOL shouldAntialias; + +/*! When enabled, characters have a background color that correlates to their byte values. */ +@property (nonatomic) BOOL shouldColorBytes; + +/*! When enabled, byte bookmarks display callout-style labels attached to them. */ +@property (nonatomic) BOOL shouldShowCallouts; + +/*! When enabled, null bytes are hidden in the hex view. */ +@property (nonatomic) BOOL shouldHideNullBytes; + +/*! When enabled, unmodified documents are auto refreshed to their latest on disk state. */ +@property (nonatomic) BOOL shouldLiveReload; + +/*! Representer initiated property changes + Called from a representer to indicate when some internal property of the representer has changed which requires that some properties be recalculated. +*/ +//@{ +/*! Callback for a representer-initiated change to some property. For example, if some property of a view changes that would cause the number of bytes per line to change, then the representer should call this method which will trigger the HFController to recompute the relevant properties. */ + +- (void)representer:(HFRepresenter *)rep changedProperties:(HFControllerPropertyBits)properties; +//@} + +/*! @name Mouse selection + Methods to handle mouse selection. Representers that allow text selection should call beginSelectionWithEvent:forByteIndex: upon receiving a mouseDown event, and then continueSelectionWithEvent:forByteIndex: for mouseDragged events, terminating with endSelectionWithEvent:forByteIndex: upon receiving the mouse up. HFController will compute the correct selected ranges and propagate any changes via the HFControllerPropertyBits mechanism. */ +//@{ +/*! Begin a selection session, with a mouse down at the given byte index. */ +- (void)beginSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex; + +/*! Continue a selection session, whe the user drags over the given byte index. */ +- (void)continueSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex; + +/*! End a selection session, with a mouse up at the given byte index. */ +- (void)endSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex; + +/*! @name Scrollling + Support for the mouse wheel and scroll bars. */ +//@{ +/*! Trigger scrolling appropriate for the given scroll event. */ +- (void)scrollWithScrollEvent:(NSEvent *)scrollEvent; + +/*! Trigger scrolling by the given number of lines. If lines is positive, then the document is scrolled down; otherwise it is scrolled up. */ +- (void)scrollByLines:(long double)lines; + +//@} + +/*! @name Keyboard navigation + Support for chaging the selection via the keyboard +*/ + +/*! General purpose navigation function. Modify the selection in the given direction by the given number of bytes. The selection is modifed according to the given transformation. If useAnchor is set, then anchored selection is used; otherwise any anchor is discarded. + + This has a few limitations: + - Only HFControllerDirectionLeft and HFControllerDirectionRight movement directions are supported. + - Anchored selection is not supported for HFControllerShiftSelection (useAnchor must be NO) +*/ +- (void)moveInDirection:(HFControllerMovementDirection)direction byByteCount:(unsigned long long)amountToMove withSelectionTransformation:(HFControllerSelectionTransformation)transformation usingAnchor:(BOOL)useAnchor; + +/*! Navigation designed for key events. */ +- (void)moveInDirection:(HFControllerMovementDirection)direction withGranularity:(HFControllerMovementGranularity)granularity andModifySelection:(BOOL)extendSelection; +- (void)moveToLineBoundaryInDirection:(HFControllerMovementDirection)direction andModifySelection:(BOOL)extendSelection; + +/*! @name Text editing + Methods to support common text editing operations */ +//@{ + +/*! Replaces the selection with the given data. For something like a hex view representer, it takes two keypresses to create a whole byte; the way this is implemented, the first keypress goes into the data as a complete byte, and the second one (if any) replaces it. If previousByteCount > 0, then that many prior bytes are replaced, without breaking undo coalescing. For previousByteCount to be > 0, the following must be true: There is only one selected range, and it is of length 0, and its location >= previousByteCount + + These functions return YES if they succeed, and NO if they fail. Currently they may fail only in overwrite mode, if you attempt to insert data that would require lengthening the byte array. + + These methods are undoable. + */ +- (BOOL)insertByteArray:(HFByteArray *)byteArray replacingPreviousBytes:(unsigned long long)previousByteCount allowUndoCoalescing:(BOOL)allowUndoCoalescing; +- (BOOL)insertData:(NSData *)data replacingPreviousBytes:(unsigned long long)previousByteCount allowUndoCoalescing:(BOOL)allowUndoCoalescing; + +/*! Deletes the selection. This operation is undoable. */ +- (void)deleteSelection; + +/*! If the selection is empty, deletes one byte in a given direction, which must be HFControllerDirectionLeft or HFControllerDirectionRight; if the selection is not empty, deletes the selection. Undoable. */ +- (void)deleteDirection:(HFControllerMovementDirection)direction; + +//@} + +/*! @name Reading data + Methods for reading data */ + +/*! Returns an NSData representing the given HFRange. The length of the HFRange must be of a size that can reasonably be fit in memory. This method may cache the result. */ +- (NSData *)dataForRange:(HFRange)range; + +/*! Copies data within the given HFRange into an in-memory buffer. This is equivalent to [[controller byteArray] copyBytes:bytes range:range]. */ +- (void)copyBytes:(unsigned char *)bytes range:(HFRange)range; + +/*! Returns total number of bytes. This is equivalent to [[controller byteArray] length]. */ +- (unsigned long long)contentsLength; + +- (void) reloadData; + +@end + +/*! A notification posted whenever any of the HFController's properties change. The object is the HFController. The userInfo contains one key, HFControllerChangedPropertiesKey, which contains an NSNumber with the changed properties as a HFControllerPropertyBits bitmask. This is useful for external objects to be notified of changes. HFRepresenters added to the HFController are notified via the controllerDidChange: message. +*/ +extern NSString * const HFControllerDidChangePropertiesNotification; + +/*! @name HFControllerDidChangePropertiesNotification keys +*/ +//@{ +extern NSString * const HFControllerChangedPropertiesKey; //!< A key in the HFControllerDidChangeProperties containing a bitmask of the changed properties, as a HFControllerPropertyBits +//@} + +/*! A notification posted from prepareForChangeInFile:fromWritingByteArray: because we are about to write a ByteArray to a file. The object is the FileReference. + Currently, HFControllers do not listen for this notification. This is because under GC there is no way of knowing whether the controller is live or not. However, pasteboard owners do listen for it, because as long as we own a pasteboard we are guaranteed to be live. +*/ +extern NSString * const HFPrepareForChangeInFileNotification; + +/*! @name HFPrepareForChangeInFileNotification keys +*/ +//@{ +extern NSString * const HFChangeInFileByteArrayKey; //!< A key in the HFPrepareForChangeInFileNotification specifying the byte array that will be written +extern NSString * const HFChangeInFileModifiedRangesKey; //!< A key in the HFPrepareForChangeInFileNotification specifying the array of HFRangeWrappers indicating which parts of the file will be modified +extern NSString * const HFChangeInFileShouldCancelKey; //!< A key in the HFPrepareForChangeInFileNotification specifying an NSValue containing a pointer to a BOOL. If set to YES, then someone was unable to prepare and the file should not be saved. It's a good idea to check if this value points to YES; if so your notification handler does not have to do anything. +extern NSString * const HFChangeInFileHintKey; //!< The hint parameter that you may pass to clearDependenciesOnRanges:inFile:hint: +//@} diff --git a/HexFiend/HFController.m b/HexFiend/HFController.m new file mode 100644 index 00000000..aaae78ee --- /dev/null +++ b/HexFiend/HFController.m @@ -0,0 +1,1903 @@ +// +// HFController.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +/* Used for the anchor range and location */ +#define NO_SELECTION ULLONG_MAX + +#if ! NDEBUG +#define VALIDATE_SELECTION() [self _ensureSelectionIsValid] +#else +#define VALIDATE_SELECTION() do { } while (0) +#endif + +#define BENCHMARK_BYTEARRAYS 0 + +#define BEGIN_TRANSACTION() NSUInteger token = [self beginPropertyChangeTransaction] +#define END_TRANSACTION() [self endPropertyChangeTransaction:token] + +static const CGFloat kScrollMultiplier = (CGFloat)1.5; + +static const CFTimeInterval kPulseDuration = .2; + +static void *KVOContextChangesAreLocked = &KVOContextChangesAreLocked; + +NSString * const HFPrepareForChangeInFileNotification = @"HFPrepareForChangeInFileNotification"; +NSString * const HFChangeInFileByteArrayKey = @"HFChangeInFileByteArrayKey"; +NSString * const HFChangeInFileModifiedRangesKey = @"HFChangeInFileModifiedRangesKey"; +NSString * const HFChangeInFileShouldCancelKey = @"HFChangeInFileShouldCancelKey"; +NSString * const HFChangeInFileHintKey = @"HFChangeInFileHintKey"; + +NSString * const HFControllerDidChangePropertiesNotification = @"HFControllerDidChangePropertiesNotification"; +NSString * const HFControllerChangedPropertiesKey = @"HFControllerChangedPropertiesKey"; + + +typedef NS_ENUM(NSInteger, HFControllerSelectAction) { + eSelectResult, + eSelectAfterResult, + ePreserveSelection, + NUM_SELECTION_ACTIONS +}; + +@interface HFController (ForwardDeclarations) +- (void)_commandInsertByteArrays:(NSArray *)byteArrays inRanges:(NSArray *)ranges withSelectionAction:(HFControllerSelectAction)selectionAction; +- (void)_removeUndoManagerNotifications; +- (void)_removeAllUndoOperations; +- (void)_registerUndoOperationForInsertingByteArrays:(NSArray *)byteArrays inRanges:(NSArray *)ranges withSelectionAction:(HFControllerSelectAction)selectionAction; + +- (void)_updateBytesPerLine; +- (void)_updateDisplayedRange; +@end + +@interface NSEvent (HFLionStuff) +- (CGFloat)scrollingDeltaY; +- (BOOL)hasPreciseScrollingDeltas; +- (CGFloat)deviceDeltaY; +@end + +static inline Class preferredByteArrayClass(void) { + return [HFBTreeByteArray class]; +} + +@implementation HFController + +- (void)_sharedInit { + selectedContentsRanges = [[NSMutableArray alloc] initWithObjects:[HFRangeWrapper withRange:HFRangeMake(0, 0)], nil]; + byteArray = [[preferredByteArrayClass() alloc] init]; + [byteArray addObserver:self forKeyPath:@"changesAreLocked" options:0 context:KVOContextChangesAreLocked]; + selectionAnchor = NO_SELECTION; + undoOperations = [[NSMutableSet alloc] init]; +} + +- (instancetype)init { + self = [super init]; + [self _sharedInit]; + bytesPerLine = 16; + bytesPerColumn = 1; + _hfflags.editable = YES; + _hfflags.antialias = YES; + _hfflags.showcallouts = YES; + _hfflags.hideNullBytes = NO; + _hfflags.selectable = YES; + representers = [[NSMutableArray alloc] init]; + [self setFont:[NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]]; + return self; +} + +- (void)dealloc { + [representers makeObjectsPerformSelector:@selector(_setController:) withObject:nil]; + [representers release]; + [selectedContentsRanges release]; + [self _removeUndoManagerNotifications]; + [self _removeAllUndoOperations]; + [undoOperations release]; + [undoManager release]; + [undoCoalescer release]; + [_font release]; + [byteArray removeObserver:self forKeyPath:@"changesAreLocked"]; + [byteArray release]; + [cachedData release]; + [additionalPendingTransactions release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [coder encodeObject:representers forKey:@"HFRepresenters"]; + [coder encodeInt64:bytesPerLine forKey:@"HFBytesPerLine"]; + [coder encodeInt64:bytesPerColumn forKey:@"HFBytesPerColumn"]; + [coder encodeObject:_font forKey:@"HFFont"]; + [coder encodeDouble:lineHeight forKey:@"HFLineHeight"]; + [coder encodeBool:_hfflags.antialias forKey:@"HFAntialias"]; + [coder encodeBool:_hfflags.colorbytes forKey:@"HFColorBytes"]; + [coder encodeBool:_hfflags.showcallouts forKey:@"HFShowCallouts"]; + [coder encodeBool:_hfflags.hideNullBytes forKey:@"HFHidesNullBytes"]; + [coder encodeBool:_hfflags.livereload forKey:@"HFLiveReload"]; + [coder encodeInt:_hfflags.editMode forKey:@"HFEditMode"]; + [coder encodeBool:_hfflags.editable forKey:@"HFEditable"]; + [coder encodeBool:_hfflags.selectable forKey:@"HFSelectable"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super init]; + [self _sharedInit]; + bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"]; + bytesPerColumn = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerColumn"]; + _font = [[coder decodeObjectForKey:@"HFFont"] retain]; + lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"]; + _hfflags.antialias = [coder decodeBoolForKey:@"HFAntialias"]; + _hfflags.colorbytes = [coder decodeBoolForKey:@"HFColorBytes"]; + _hfflags.livereload = [coder decodeBoolForKey:@"HFLiveReload"]; + + if ([coder containsValueForKey:@"HFEditMode"]) + _hfflags.editMode = [coder decodeIntForKey:@"HFEditMode"]; + else { + _hfflags.editMode = ([coder decodeBoolForKey:@"HFOverwriteMode"] + ? HFOverwriteMode : HFInsertMode); + } + + _hfflags.editable = [coder decodeBoolForKey:@"HFEditable"]; + _hfflags.selectable = [coder decodeBoolForKey:@"HFSelectable"]; + _hfflags.hideNullBytes = [coder decodeBoolForKey:@"HFHidesNullBytes"]; + representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain]; + return self; +} + +- (NSArray *)representers { + return [[representers copy] autorelease]; +} + +- (void)notifyRepresentersOfChanges:(HFControllerPropertyBits)bits { + FOREACH(HFRepresenter*, rep, representers) { + [rep controllerDidChange:bits]; + } + + /* Post the HFControllerDidChangePropertiesNotification */ + NSNumber *number = [[NSNumber alloc] initWithUnsignedInteger:bits]; + NSDictionary *userInfo = [[NSDictionary alloc] initWithObjects:&number forKeys:(id *)&HFControllerChangedPropertiesKey count:1]; + [number release]; + [[NSNotificationCenter defaultCenter] postNotificationName:HFControllerDidChangePropertiesNotification object:self userInfo:userInfo]; + [userInfo release]; +} + +- (void)_firePropertyChanges { + NSMutableArray *pendingTransactions = additionalPendingTransactions; + NSUInteger pendingTransactionCount = [pendingTransactions count]; + additionalPendingTransactions = nil; + HFControllerPropertyBits propertiesToUpdate = propertiesToUpdateInCurrentTransaction; + propertiesToUpdateInCurrentTransaction = 0; + if (pendingTransactionCount > 0 || propertiesToUpdate != 0) { + BEGIN_TRANSACTION(); + while (pendingTransactionCount--) { + HFControllerPropertyBits propertiesInThisTransaction = [pendingTransactions[0] unsignedIntegerValue]; + [pendingTransactions removeObjectAtIndex:0]; + HFASSERT(propertiesInThisTransaction != 0); + [self notifyRepresentersOfChanges:propertiesInThisTransaction]; + } + [pendingTransactions release]; + if (propertiesToUpdate) { + [self notifyRepresentersOfChanges:propertiesToUpdate]; + } + END_TRANSACTION(); + } +} + +/* Inserts a "fence" so that all prior property change bits will be complete before any new ones */ +- (void)_insertPropertyChangeFence { + if (currentPropertyChangeToken == 0) { + HFASSERT(additionalPendingTransactions == nil); + /* There can be no prior property changes */ + HFASSERT(propertiesToUpdateInCurrentTransaction == 0); + return; + } + if (propertiesToUpdateInCurrentTransaction == 0) { + /* Nothing to fence */ + return; + } + if (additionalPendingTransactions == nil) additionalPendingTransactions = [[NSMutableArray alloc] init]; + [additionalPendingTransactions addObject:@(propertiesToUpdateInCurrentTransaction)]; + propertiesToUpdateInCurrentTransaction = 0; +} + +- (void)_addPropertyChangeBits:(HFControllerPropertyBits)bits { + propertiesToUpdateInCurrentTransaction |= bits; + if (currentPropertyChangeToken == 0) { + [self _firePropertyChanges]; + } +} + +- (NSUInteger)beginPropertyChangeTransaction { + HFASSERT(currentPropertyChangeToken < NSUIntegerMax); + return ++currentPropertyChangeToken; +} + +- (void)endPropertyChangeTransaction:(NSUInteger)token { + if (currentPropertyChangeToken != token) { + [NSException raise:NSInvalidArgumentException format:@"endPropertyChangeTransaction passed token %lu, but expected token %lu", (unsigned long)token, (unsigned long)currentPropertyChangeToken]; + } + HFASSERT(currentPropertyChangeToken > 0); + if (--currentPropertyChangeToken == 0) [self _firePropertyChanges]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context == KVOContextChangesAreLocked) { + HFASSERT([keyPath isEqual:@"changesAreLocked"]); + [self _addPropertyChangeBits:HFControllerEditable]; + } + else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void)addRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound); + HFASSERT([representer controller] == nil); + [representer _setController:self]; + [representers addObject:representer]; + [representer controllerDidChange: -1]; +} + +- (void)removeRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound); + [representers removeObjectIdenticalTo:representer]; + [representer _setController:nil]; +} + +- (HFRange)_maximumDisplayedRangeSet { + unsigned long long contentsLength = [self contentsLength]; + HFRange maximumDisplayedRangeSet = HFRangeMake(0, HFRoundUpToNextMultipleSaturate(contentsLength, bytesPerLine)); + return maximumDisplayedRangeSet; +} + +- (unsigned long long)totalLineCount { + return HFDivideULLRoundingUp(HFRoundUpToNextMultipleSaturate([self contentsLength] - 1, bytesPerLine), bytesPerLine); +} + +- (HFFPRange)displayedLineRange { +#if ! NDEBUG + HFASSERT(displayedLineRange.location >= 0); + HFASSERT(displayedLineRange.length >= 0); + HFASSERT(displayedLineRange.location + displayedLineRange.length <= HFULToFP([self totalLineCount])); +#endif + return displayedLineRange; +} + +- (void)setDisplayedLineRange:(HFFPRange)range { +#if ! NDEBUG + HFASSERT(range.location >= 0); + HFASSERT(range.length >= 0); + HFASSERT(range.location + range.length <= HFULToFP([self totalLineCount])); +#endif + if (! HFFPRangeEqualsRange(range, displayedLineRange)) { + displayedLineRange = range; + [self _addPropertyChangeBits:HFControllerDisplayedLineRange]; + } +} + +- (CGFloat)lineHeight { + return lineHeight; +} + +- (void)setFont:(NSFont *)val { + if (val != _font) { + CGFloat priorLineHeight = [self lineHeight]; + + [_font release]; + _font = [val copy]; + + NSLayoutManager *manager = [[NSLayoutManager alloc] init]; + lineHeight = [manager defaultLineHeightForFont:_font]; + [manager release]; + + HFControllerPropertyBits bits = HFControllerFont; + if (lineHeight != priorLineHeight) bits |= HFControllerLineHeight; + [self _addPropertyChangeBits:bits]; + [self _insertPropertyChangeFence]; + [self _addPropertyChangeBits:HFControllerViewSizeRatios]; + [self _updateDisplayedRange]; + } +} + +- (BOOL)shouldAntialias { + return _hfflags.antialias; +} + +- (void)setShouldAntialias:(BOOL)antialias { + antialias = !! antialias; + if (antialias != _hfflags.antialias) { + _hfflags.antialias = antialias; + [self _addPropertyChangeBits:HFControllerAntialias]; + } +} + +- (BOOL)shouldColorBytes { + return _hfflags.colorbytes; +} + +- (void)setShouldColorBytes:(BOOL)colorbytes { + colorbytes = !! colorbytes; + if (colorbytes != _hfflags.colorbytes) { + _hfflags.colorbytes = colorbytes; + [self _addPropertyChangeBits:HFControllerColorBytes]; + } +} + +- (BOOL)shouldShowCallouts { + return _hfflags.showcallouts; +} + +- (void)setShouldShowCallouts:(BOOL)showcallouts { + showcallouts = !! showcallouts; + if (showcallouts != _hfflags.showcallouts) { + _hfflags.showcallouts = showcallouts; + [self _addPropertyChangeBits:HFControllerShowCallouts]; + } +} + +- (BOOL)shouldHideNullBytes { + return _hfflags.hideNullBytes; +} + +- (void)setShouldHideNullBytes:(BOOL)hideNullBytes +{ + hideNullBytes = !! hideNullBytes; + if (hideNullBytes != _hfflags.hideNullBytes) { + _hfflags.hideNullBytes = hideNullBytes; + [self _addPropertyChangeBits:HFControllerHideNullBytes]; + } +} + +- (BOOL)shouldLiveReload { + return _hfflags.livereload; +} + +- (void)setShouldLiveReload:(BOOL)livereload { + _hfflags.livereload = !!livereload; + +} + +- (void)setBytesPerColumn:(NSUInteger)val { + if (val != bytesPerColumn) { + bytesPerColumn = val; + [self _addPropertyChangeBits:HFControllerBytesPerColumn]; + } +} + +- (NSUInteger)bytesPerColumn { + return bytesPerColumn; +} + +- (BOOL)_shouldInvertSelectedRangesByAnchorRange { + return _hfflags.selectionInProgress && _hfflags.commandExtendSelection; +} + +- (NSArray *)_invertedSelectedContentsRanges { + HFASSERT([selectedContentsRanges count] > 0); + HFASSERT(selectionAnchorRange.location != NO_SELECTION); + if (selectionAnchorRange.length == 0) return [NSArray arrayWithArray:selectedContentsRanges]; + + NSArray *cleanedRanges = [HFRangeWrapper organizeAndMergeRanges:selectedContentsRanges]; + NSMutableArray *result = [NSMutableArray array]; + + /* Our algorithm works as follows - add any ranges outside of the selectionAnchorRange, clipped by the selectionAnchorRange. Then extract every "index" in our cleaned selected arrays that are within the selectionAnchorArray. An index is the location where a range starts or stops. Then use those indexes to create the inverted arrays. A range parity of 1 means that we are adding the range. */ + + /* Add all the ranges that are outside of selectionAnchorRange, clipping them if necessary */ + HFASSERT(HFSumDoesNotOverflow(selectionAnchorRange.location, selectionAnchorRange.length)); + FOREACH(HFRangeWrapper*, outsideWrapper, cleanedRanges) { + HFRange range = [outsideWrapper HFRange]; + if (range.location < selectionAnchorRange.location) { + HFRange clippedRange; + clippedRange.location = range.location; + HFASSERT(MIN(HFMaxRange(range), selectionAnchorRange.location) >= clippedRange.location); + clippedRange.length = MIN(HFMaxRange(range), selectionAnchorRange.location) - clippedRange.location; + [result addObject:[HFRangeWrapper withRange:clippedRange]]; + } + if (HFMaxRange(range) > HFMaxRange(selectionAnchorRange)) { + HFRange clippedRange; + clippedRange.location = MAX(range.location, HFMaxRange(selectionAnchorRange)); + HFASSERT(HFMaxRange(range) >= clippedRange.location); + clippedRange.length = HFMaxRange(range) - clippedRange.location; + [result addObject:[HFRangeWrapper withRange:clippedRange]]; + } + } + + HFASSERT(HFSumDoesNotOverflow(selectionAnchorRange.location, selectionAnchorRange.length)); + + NEW_ARRAY(unsigned long long, partitions, 2*[cleanedRanges count] + 2); + NSUInteger partitionCount, partitionIndex = 0; + + partitions[partitionIndex++] = selectionAnchorRange.location; + FOREACH(HFRangeWrapper*, wrapper, cleanedRanges) { + HFRange range = [wrapper HFRange]; + if (! HFIntersectsRange(range, selectionAnchorRange)) continue; + + partitions[partitionIndex++] = MAX(selectionAnchorRange.location, range.location); + partitions[partitionIndex++] = MIN(HFMaxRange(selectionAnchorRange), HFMaxRange(range)); + } + + // For some reason, using HFMaxRange confuses the static analyzer + partitions[partitionIndex++] = HFSum(selectionAnchorRange.location, selectionAnchorRange.length); + + partitionCount = partitionIndex; + HFASSERT((partitionCount % 2) == 0); + + partitionIndex = 0; + while (partitionIndex < partitionCount) { + HFASSERT(partitionIndex + 1 < partitionCount); + HFASSERT(partitions[partitionIndex] <= partitions[partitionIndex + 1]); + if (partitions[partitionIndex] < partitions[partitionIndex + 1]) { + HFRange range = HFRangeMake(partitions[partitionIndex], partitions[partitionIndex + 1] - partitions[partitionIndex]); + [result addObject:[HFRangeWrapper withRange:range]]; + } + partitionIndex += 2; + } + + FREE_ARRAY(partitions); + + if ([result count] == 0) [result addObject:[HFRangeWrapper withRange:HFRangeMake(selectionAnchor, 0)]]; + + return [HFRangeWrapper organizeAndMergeRanges:result]; +} + +#if ! NDEBUG +- (void)_ensureSelectionIsValid { + HFASSERT(selectedContentsRanges != nil); + HFASSERT([selectedContentsRanges count] > 0); + BOOL onlyOneWrapper = ([selectedContentsRanges count] == 1); + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + EXPECT_CLASS(wrapper, HFRangeWrapper); + HFRange range = [wrapper HFRange]; + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + if (onlyOneWrapper == NO) HFASSERT(range.length > 0); /* If we have more than one wrapper, then none of them should be zero length */ + } +} +#endif + +- (void)_setSingleSelectedContentsRange:(HFRange)newSelection { + HFASSERT(HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))); + BOOL selectionChanged; + if ([selectedContentsRanges count] == 1) { + selectionChanged = ! HFRangeEqualsRange([selectedContentsRanges[0] HFRange], newSelection); + } + else { + selectionChanged = YES; + } + + if (selectionChanged) { + [selectedContentsRanges removeAllObjects]; + [selectedContentsRanges addObject:[HFRangeWrapper withRange:newSelection]]; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + VALIDATE_SELECTION(); +} + +- (NSArray *)selectedContentsRanges { + VALIDATE_SELECTION(); + if ([self _shouldInvertSelectedRangesByAnchorRange]) return [self _invertedSelectedContentsRanges]; + else return [NSArray arrayWithArray:selectedContentsRanges]; +} + +- (unsigned long long)contentsLength { + if (! byteArray) return 0; + else return [byteArray length]; +} + +- (NSData *)dataForRange:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); // it doesn't make sense to ask for a buffer larger than can be stored in memory + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + + if(range.length == 0) { + // Don't throw out cache for an empty request! Also makes the analyzer happier. + return [NSData data]; + } + + NSUInteger newGenerationIndex = [byteArray changeGenerationCount]; + if (cachedData == nil || newGenerationIndex != cachedGenerationIndex || ! HFRangeIsSubrangeOfRange(range, cachedRange)) { + [cachedData release]; + cachedGenerationIndex = newGenerationIndex; + cachedRange = range; + NSUInteger length = ll2l(range.length); + unsigned char *data = check_malloc(length); + [byteArray copyBytes:data range:range]; + cachedData = [[NSData alloc] initWithBytesNoCopy:data length:length freeWhenDone:YES]; + } + + if (HFRangeEqualsRange(range, cachedRange)) { + return cachedData; + } + else { + HFASSERT(cachedRange.location <= range.location); + NSRange cachedDataSubrange; + cachedDataSubrange.location = ll2l(range.location - cachedRange.location); + cachedDataSubrange.length = ll2l(range.length); + return [cachedData subdataWithRange:cachedDataSubrange]; + } +} + +- (void)copyBytes:(unsigned char *)bytes range:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); // it doesn't make sense to ask for a buffer larger than can be stored in memory + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + [byteArray copyBytes:bytes range:range]; +} + +- (void)_updateDisplayedRange { + HFRange proposedNewDisplayRange; + HFFPRange proposedNewLineRange; + HFRange maxRangeSet = [self _maximumDisplayedRangeSet]; + NSUInteger maxBytesForViewSize = NSUIntegerMax; + double maxLines = DBL_MAX; + FOREACH(HFRepresenter*, rep, representers) { + NSView *view = [rep view]; + double repMaxLines = [rep maximumAvailableLinesForViewHeight:NSHeight([view frame])]; + if (repMaxLines != DBL_MAX) { + /* bytesPerLine may be ULONG_MAX. We want to compute the smaller of maxBytesForViewSize and ceil(repMaxLines) * bytesPerLine. If the latter expression overflows, the smaller is the former. */ + NSUInteger repMaxLinesUInt = (NSUInteger)ceil(repMaxLines); + NSUInteger maxLinesTimesBytesPerLine = repMaxLinesUInt * bytesPerLine; + /* Check if we overflowed */ + BOOL overflowed = (repMaxLinesUInt != 0 && (maxLinesTimesBytesPerLine / repMaxLinesUInt != bytesPerLine)); + if (! overflowed) { + maxBytesForViewSize = MIN(maxLinesTimesBytesPerLine, maxBytesForViewSize); + } + } + maxLines = MIN(repMaxLines, maxLines); + } + if (maxLines == DBL_MAX) { + proposedNewDisplayRange = HFRangeMake(0, 0); + proposedNewLineRange = (HFFPRange){0, 0}; + } + else { + unsigned long long maximumDisplayedBytes = MIN(maxRangeSet.length, maxBytesForViewSize); + HFASSERT(HFMaxRange(maxRangeSet) >= maximumDisplayedBytes); + + proposedNewDisplayRange.location = MIN(HFMaxRange(maxRangeSet) - maximumDisplayedBytes, displayedContentsRange.location); + proposedNewDisplayRange.location -= proposedNewDisplayRange.location % bytesPerLine; + proposedNewDisplayRange.length = MIN(HFMaxRange(maxRangeSet) - proposedNewDisplayRange.location, maxBytesForViewSize); + if (maxBytesForViewSize % bytesPerLine != 0) { + NSLog(@"Bad max bytes: %lu (%lu)", (unsigned long)maxBytesForViewSize, (unsigned long)bytesPerLine); + } + if (HFMaxRange(maxRangeSet) != ULLONG_MAX && (HFMaxRange(maxRangeSet) - proposedNewDisplayRange.location) % bytesPerLine != 0) { + NSLog(@"Bad max range minus: %llu (%lu)", HFMaxRange(maxRangeSet) - proposedNewDisplayRange.location, (unsigned long)bytesPerLine); + } + + long double lastLine = HFULToFP([self totalLineCount]); + proposedNewLineRange.length = MIN(maxLines, lastLine); + proposedNewLineRange.location = MIN(displayedLineRange.location, lastLine - proposedNewLineRange.length); + } + HFASSERT(HFRangeIsSubrangeOfRange(proposedNewDisplayRange, maxRangeSet)); + HFASSERT(proposedNewDisplayRange.location % bytesPerLine == 0); + if (! HFRangeEqualsRange(proposedNewDisplayRange, displayedContentsRange) || ! HFFPRangeEqualsRange(proposedNewLineRange, displayedLineRange)) { + displayedContentsRange = proposedNewDisplayRange; + displayedLineRange = proposedNewLineRange; + [self _addPropertyChangeBits:HFControllerDisplayedLineRange]; + } +} + +- (void)_ensureVisibilityOfLocation:(unsigned long long)location { + HFASSERT(location <= [self contentsLength]); + unsigned long long lineInt = location / bytesPerLine; + long double line = HFULToFP(lineInt); + HFASSERT(line >= 0); + line = MIN(line, HFULToFP([self totalLineCount]) - 1); + HFFPRange lineRange = [self displayedLineRange]; + HFFPRange newLineRange = lineRange; + if (line < lineRange.location) { + newLineRange.location = line; + } + else if (line >= lineRange.location + lineRange.length) { + HFASSERT(lineRange.location + lineRange.length >= 1); + newLineRange.location = lineRange.location + (line - (lineRange.location + lineRange.length - 1)); + } + [self setDisplayedLineRange:newLineRange]; +} + +- (void)maximizeVisibilityOfContentsRange:(HFRange)range { + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + + // Find the minimum move necessary to make range visible + HFFPRange displayRange = [self displayedLineRange]; + HFFPRange newDisplayRange = displayRange; + unsigned long long startLine = range.location / bytesPerLine; + unsigned long long endLine = HFDivideULLRoundingUp(HFRoundUpToNextMultipleSaturate(HFMaxRange(range), bytesPerLine), bytesPerLine); + HFASSERT(endLine > startLine || endLine == ULLONG_MAX); + long double linesInRange = HFULToFP(endLine - startLine); + long double linesToDisplay = MIN(displayRange.length, linesInRange); + HFASSERT(linesToDisplay <= linesInRange); + long double linesToMoveDownToMakeLastLineVisible = HFULToFP(endLine) - (displayRange.location + displayRange.length); + long double linesToMoveUpToMakeFirstLineVisible = displayRange.location - HFULToFP(startLine); + //HFASSERT(linesToMoveUpToMakeFirstLineVisible <= 0 || linesToMoveDownToMakeLastLineVisible <= 0); + // in general, we expect either linesToMoveUpToMakeFirstLineVisible to be <= zero, or linesToMoveDownToMakeLastLineVisible to be <= zero. However, if the available space is smaller than one line, then that won't be true. + if (linesToMoveDownToMakeLastLineVisible > 0) { + newDisplayRange.location += linesToMoveDownToMakeLastLineVisible; + } + else if (linesToMoveUpToMakeFirstLineVisible > 0 && linesToDisplay >= 1) { + // the >= 1 check prevents some wacky behavior when we have less than one line's worth of space, that caused bouncing between the top and bottom of the line + newDisplayRange.location -= linesToMoveUpToMakeFirstLineVisible; + } + + // Use the minimum movement if it would be visually helpful; otherwise just center. + if (HFFPIntersectsRange(displayRange, newDisplayRange)) { + [self setDisplayedLineRange:newDisplayRange]; + } else { + [self centerContentsRange:range]; + } +} + +- (void)centerContentsRange:(HFRange)range { + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + HFFPRange displayRange = [self displayedLineRange]; + const long double numDisplayedLines = displayRange.length; + HFFPRange newDisplayRange; + unsigned long long startLine = range.location / bytesPerLine; + unsigned long long endLine = HFDivideULLRoundingUp(HFRoundUpToNextMultipleSaturate(HFMaxRange(range), bytesPerLine), bytesPerLine); + HFASSERT(endLine > startLine || endLine == ULLONG_MAX); + long double linesInRange = HFULToFP(endLine - startLine); + + /* Handle the case of a line range bigger than we can display by choosing the top lines. */ + if (numDisplayedLines <= linesInRange) { + newDisplayRange = (HFFPRange){startLine, numDisplayedLines}; + } + else { + /* Construct a newDisplayRange that centers {startLine, endLine} */ + long double center = startLine + (endLine - startLine) / 2.; + newDisplayRange = (HFFPRange){center - numDisplayedLines / 2., numDisplayedLines}; + } + + /* Move the newDisplayRange up or down as necessary */ + newDisplayRange.location = fmaxl(newDisplayRange.location, (long double)0.); + newDisplayRange.location = fminl(newDisplayRange.location, HFULToFP([self totalLineCount]) - numDisplayedLines); + [self setDisplayedLineRange:newDisplayRange]; +} + +/* Clips the selection to a given length. If this would clip the entire selection, returns a zero length selection at the end. Indicates HFControllerSelectedRanges if the selection changes. */ +- (void)_clipSelectedContentsRangesToLength:(unsigned long long)newLength { + NSMutableArray *newTempSelection = [selectedContentsRanges mutableCopy]; + NSUInteger i, max = [newTempSelection count]; + for (i=0; i < max; i++) { + HFRange range = [newTempSelection[i] HFRange]; + if (HFMaxRange(range) > newLength) { + if (range.location > newLength) { + /* The range starts past our new max. Just remove this range entirely */ + [newTempSelection removeObjectAtIndex:i]; + i--; + max--; + } + else { + /* Need to clip this range */ + range.length = newLength - range.location; + newTempSelection[i] = [HFRangeWrapper withRange:range]; + } + } + } + [newTempSelection setArray:[HFRangeWrapper organizeAndMergeRanges:newTempSelection]]; + + /* If there are multiple empty ranges, remove all but the first */ + BOOL foundEmptyRange = NO; + max = [newTempSelection count]; + for (i=0; i < max; i++) { + HFRange range = [newTempSelection[i] HFRange]; + HFASSERT(HFMaxRange(range) <= newLength); + if (range.length == 0) { + if (foundEmptyRange) { + [newTempSelection removeObjectAtIndex:i]; + i--; + max--; + } + foundEmptyRange = YES; + } + } + if (max == 0) { + /* Removed all ranges - insert one at the end */ + [newTempSelection addObject:[HFRangeWrapper withRange:HFRangeMake(newLength, 0)]]; + } + + /* If something changed, set the new selection and post the change bit */ + if (! [selectedContentsRanges isEqualToArray:newTempSelection]) { + [selectedContentsRanges setArray:newTempSelection]; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + + [newTempSelection release]; +} + +- (void)setByteArray:(HFByteArray *)val { + REQUIRE_NOT_NULL(val); + BEGIN_TRANSACTION(); + [byteArray removeObserver:self forKeyPath:@"changesAreLocked"]; + [val retain]; + [byteArray release]; + byteArray = val; + [cachedData release]; + cachedData = nil; + [byteArray addObserver:self forKeyPath:@"changesAreLocked" options:0 context:KVOContextChangesAreLocked]; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits: HFControllerContentValue | HFControllerContentLength]; + [self _clipSelectedContentsRangesToLength:[byteArray length]]; + END_TRANSACTION(); +} + +- (HFByteArray *)byteArray { + return byteArray; +} + +- (void)_undoNotification:note { + USE(note); +} + +- (void)_removeUndoManagerNotifications { + if (undoManager) { + NSNotificationCenter *noter = [NSNotificationCenter defaultCenter]; + [noter removeObserver:self name:NSUndoManagerWillUndoChangeNotification object:undoManager]; + } +} + +- (void)_addUndoManagerNotifications { + if (undoManager) { + NSNotificationCenter *noter = [NSNotificationCenter defaultCenter]; + [noter addObserver:self selector:@selector(_undoNotification:) name:NSUndoManagerWillUndoChangeNotification object:undoManager]; + } +} + +- (void)_removeAllUndoOperations { + /* Remove all the undo operations, because some undo operation is unsupported. Note that if we were smarter we would keep a stack of undo operations and only remove ones "up to" a certain point. */ + [undoManager removeAllActionsWithTarget:self]; + [undoOperations makeObjectsPerformSelector:@selector(invalidate)]; + [undoOperations removeAllObjects]; +} + +- (void)setUndoManager:(NSUndoManager *)manager { + [self _removeUndoManagerNotifications]; + [self _removeAllUndoOperations]; + [manager retain]; + [undoManager release]; + undoManager = manager; + [self _addUndoManagerNotifications]; +} + +- (NSUndoManager *)undoManager { + return undoManager; +} + +- (NSUInteger)bytesPerLine { + return bytesPerLine; +} + +- (BOOL)editable { + return _hfflags.editable && ! [byteArray changesAreLocked] && _hfflags.editMode != HFReadOnlyMode; +} + +- (void)setEditable:(BOOL)flag { + if (flag != _hfflags.editable) { + _hfflags.editable = flag; + [self _addPropertyChangeBits:HFControllerEditable]; + } +} + +- (void)_updateBytesPerLine { + NSUInteger newBytesPerLine = NSUIntegerMax; + FOREACH(HFRepresenter*, rep, representers) { + NSView *view = [rep view]; + CGFloat width = [view frame].size.width; + NSUInteger repMaxBytesPerLine = [rep maximumBytesPerLineForViewWidth:width]; + HFASSERT(repMaxBytesPerLine > 0); + newBytesPerLine = MIN(repMaxBytesPerLine, newBytesPerLine); + } + if (newBytesPerLine != bytesPerLine) { + HFASSERT(newBytesPerLine > 0); + bytesPerLine = newBytesPerLine; + BEGIN_TRANSACTION(); + [self _addPropertyChangeBits:HFControllerBytesPerLine]; + END_TRANSACTION(); + } +} + +- (void)representer:(HFRepresenter *)rep changedProperties:(HFControllerPropertyBits)properties { + USE(rep); + HFControllerPropertyBits remainingProperties = properties; + BEGIN_TRANSACTION(); + if (remainingProperties & HFControllerBytesPerLine) { + [self _updateBytesPerLine]; + remainingProperties &= ~HFControllerBytesPerLine; + } + if (remainingProperties & HFControllerDisplayedLineRange) { + [self _updateDisplayedRange]; + remainingProperties &= ~HFControllerDisplayedLineRange; + } + if (remainingProperties & HFControllerByteRangeAttributes) { + [self _addPropertyChangeBits:HFControllerByteRangeAttributes]; + remainingProperties &= ~HFControllerByteRangeAttributes; + } + if (remainingProperties & HFControllerViewSizeRatios) { + [self _addPropertyChangeBits:HFControllerViewSizeRatios]; + remainingProperties &= ~HFControllerViewSizeRatios; + } + if (remainingProperties) { + NSLog(@"Unknown properties: %lx", (long)remainingProperties); + } + END_TRANSACTION(); +} + +- (HFByteArray *)byteArrayForSelectedContentsRanges { + HFByteArray *result = nil; + HFByteArray *bytes = [self byteArray]; + VALIDATE_SELECTION(); + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + HFByteArray *additionalBytes = [bytes subarrayWithRange:range]; + if (! result) { + result = additionalBytes; + } + else { + [result insertByteArray:additionalBytes inRange:HFRangeMake([result length], 0)]; + } + } + return result; +} + +/* Flattens the selected range to a single range (the selected range becomes any character within or between the selected ranges). Modifies the selectedContentsRanges and returns the new single HFRange. Does not call notifyRepresentersOfChanges: */ +- (HFRange)_flattenSelectionRange { + HFASSERT([selectedContentsRanges count] >= 1); + + HFRange resultRange = [selectedContentsRanges[0] HFRange]; + if ([selectedContentsRanges count] == 1) return resultRange; //already flat + + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange selectedRange = [wrapper HFRange]; + if (selectedRange.location < resultRange.location) { + /* Extend our result range backwards */ + resultRange.length += resultRange.location - selectedRange.location; + resultRange.location = selectedRange.location; + } + if (HFRangeExtendsPastRange(selectedRange, resultRange)) { + HFASSERT(selectedRange.location >= resultRange.location); //must be true by if statement above + resultRange.length = HFSum(selectedRange.location - resultRange.location, selectedRange.length); + } + } + [self _setSingleSelectedContentsRange:resultRange]; + return resultRange; +} + +- (unsigned long long)_minimumSelectionLocation { + HFASSERT([selectedContentsRanges count] >= 1); + unsigned long long minSelection = ULLONG_MAX; + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + minSelection = MIN(minSelection, range.location); + } + return minSelection; +} + +- (unsigned long long)_maximumSelectionLocation { + HFASSERT([selectedContentsRanges count] >= 1); + unsigned long long maxSelection = 0; + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + maxSelection = MAX(maxSelection, HFMaxRange(range)); + } + return maxSelection; +} + +- (unsigned long long)minimumSelectionLocation { + return [self _minimumSelectionLocation]; +} + +- (unsigned long long)maximumSelectionLocation { + return [self _maximumSelectionLocation]; +} + +/* Put the selection at the left or right end of the current selection, with zero length. Modifies the selectedContentsRanges and returns the new single HFRange. Does not call notifyRepresentersOfChanges: */ +- (HFRange)_telescopeSelectionRangeInDirection:(HFControllerMovementDirection)direction { + HFRange resultRange; + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + resultRange.location = (direction == HFControllerDirectionLeft ? [self _minimumSelectionLocation] : [self _maximumSelectionLocation]); + resultRange.length = 0; + [self _setSingleSelectedContentsRange:resultRange]; + return resultRange; +} + +- (void)beginSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)characterIndex { + USE(event); + HFASSERT(characterIndex <= [self contentsLength]); + + /* Determine how to perform the selection - normally, with command key, or with shift key. Command + shift is the same as command. The shift key closes the selection - the selected range becomes the single range containing the first and last selected character. */ + _hfflags.shiftExtendSelection = NO; + _hfflags.commandExtendSelection = NO; + NSUInteger flags = [event modifierFlags]; + if (flags & NSCommandKeyMask) _hfflags.commandExtendSelection = YES; + else if (flags & NSShiftKeyMask) _hfflags.shiftExtendSelection = YES; + + selectionAnchor = NO_SELECTION; + selectionAnchorRange = HFRangeMake(NO_SELECTION, 0); + + _hfflags.selectionInProgress = YES; + if (_hfflags.commandExtendSelection) { + /* The selection anchor is used to track the "invert" range. All characters within this range have their selection inverted. This is tracked by the _shouldInvertSelectedRangesByAnchorRange method. */ + selectionAnchor = characterIndex; + selectionAnchorRange = HFRangeMake(characterIndex, 0); + } + else if (_hfflags.shiftExtendSelection) { + /* The selection anchor is used to track the single (flattened) selected range. */ + HFRange selectedRange = [self _flattenSelectionRange]; + unsigned long long distanceFromRangeStart = HFAbsoluteDifference(selectedRange.location, characterIndex); + unsigned long long distanceFromRangeEnd = HFAbsoluteDifference(HFMaxRange(selectedRange), characterIndex); + if (selectedRange.length == 0) { + HFASSERT(distanceFromRangeStart == distanceFromRangeEnd); + selectionAnchor = selectedRange.location; + selectedRange.location = MIN(characterIndex, selectedRange.location); + selectedRange.length = distanceFromRangeStart; + } + else if (distanceFromRangeStart >= distanceFromRangeEnd) { + /* Push the "end forwards" */ + selectedRange.length = distanceFromRangeStart; + selectionAnchor = selectedRange.location; + } + else { + /* Push the "start back" */ + selectedRange.location = selectedRange.location + selectedRange.length - distanceFromRangeEnd; + selectedRange.length = distanceFromRangeEnd; + selectionAnchor = HFSum(selectedRange.length, selectedRange.location); + } + HFASSERT(HFRangeIsSubrangeOfRange(selectedRange, HFRangeMake(0, [self contentsLength]))); + selectionAnchorRange = selectedRange; + [self _setSingleSelectedContentsRange:selectedRange]; + } + else { + /* No modifier key selection. The selection anchor is not used. */ + [self _setSingleSelectedContentsRange:HFRangeMake(characterIndex, 0)]; + selectionAnchor = characterIndex; + } +} + +- (void)continueSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex { + USE(event); + HFASSERT(_hfflags.selectionInProgress); + HFASSERT(byteIndex <= [self contentsLength]); + BEGIN_TRANSACTION(); + if (_hfflags.commandExtendSelection) { + /* Clear any zero-length ranges, unless there's only one */ + NSUInteger rangeCount = [selectedContentsRanges count]; + NSUInteger rangeIndex = rangeCount; + while (rangeIndex-- > 0) { + if (rangeCount > 1 && [selectedContentsRanges[rangeIndex] HFRange].length == 0) { + [selectedContentsRanges removeObjectAtIndex:rangeIndex]; + rangeCount--; + } + } + selectionAnchorRange.location = MIN(byteIndex, selectionAnchor); + selectionAnchorRange.length = MAX(byteIndex, selectionAnchor) - selectionAnchorRange.location; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + else if (_hfflags.shiftExtendSelection) { + HFASSERT(selectionAnchorRange.location != NO_SELECTION); + HFASSERT(selectionAnchor != NO_SELECTION); + HFRange range; + if (! HFLocationInRange(byteIndex, selectionAnchorRange)) { + /* The character index is outside of the selection anchor range. The new range is just the selected anchor range combined with the character index. */ + range.location = MIN(byteIndex, selectionAnchorRange.location); + unsigned long long rangeEnd = MAX(byteIndex, HFSum(selectionAnchorRange.location, selectionAnchorRange.length)); + HFASSERT(rangeEnd >= range.location); + range.length = rangeEnd - range.location; + } + else { + /* The character is within the selection anchor range. We use the selection anchor index to determine which "side" of the range is selected. */ + range.location = MIN(selectionAnchor, byteIndex); + range.length = HFAbsoluteDifference(selectionAnchor, byteIndex); + } + [self _setSingleSelectedContentsRange:range]; + } + else { + /* No modifier key selection */ + HFRange range; + range.location = MIN(byteIndex, selectionAnchor); + range.length = MAX(byteIndex, selectionAnchor) - range.location; + [self _setSingleSelectedContentsRange:range]; + } + END_TRANSACTION(); + VALIDATE_SELECTION(); +} + +- (void)endSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)characterIndex { + USE(event); + HFASSERT(_hfflags.selectionInProgress); + HFASSERT(characterIndex <= [self contentsLength]); + if (_hfflags.commandExtendSelection) { + selectionAnchorRange.location = MIN(characterIndex, selectionAnchor); + selectionAnchorRange.length = MAX(characterIndex, selectionAnchor) - selectionAnchorRange.location; + + /* "Commit" our selectionAnchorRange */ + NSArray *newSelection = [self _invertedSelectedContentsRanges]; + [selectedContentsRanges setArray:newSelection]; + } + else if (_hfflags.shiftExtendSelection) { + HFASSERT(selectionAnchorRange.location != NO_SELECTION); + HFASSERT(selectionAnchor != NO_SELECTION); + HFRange range; + if (! HFLocationInRange(characterIndex, selectionAnchorRange)) { + /* The character index is outside of the selection anchor range. The new range is just the selected anchor range combined with the character index. */ + range.location = MIN(characterIndex, selectionAnchorRange.location); + unsigned long long rangeEnd = MAX(characterIndex, HFSum(selectionAnchorRange.location, selectionAnchorRange.length)); + HFASSERT(rangeEnd >= range.location); + range.length = rangeEnd - range.location; + } + else { + /* The character is within the selection anchor range. We use the selection anchor index to determine which "side" of the range is selected. */ + range.location = MIN(selectionAnchor, characterIndex); + range.length = HFAbsoluteDifference(selectionAnchor, characterIndex); + } + [self _setSingleSelectedContentsRange:range]; + } + else { + /* No modifier key selection */ + HFRange range; + range.location = MIN(characterIndex, selectionAnchor); + range.length = MAX(characterIndex, selectionAnchor) - range.location; + [self _setSingleSelectedContentsRange:range]; + } + + _hfflags.selectionInProgress = NO; + _hfflags.shiftExtendSelection = NO; + _hfflags.commandExtendSelection = NO; + selectionAnchor = NO_SELECTION; +} + +- (double)selectionPulseAmount { + double result = 0; + if (pulseSelectionStartTime > 0) { + CFTimeInterval diff = pulseSelectionCurrentTime - pulseSelectionStartTime; + if (diff > 0 && diff < kPulseDuration) { + result = 1. - fabs(diff * 2 - kPulseDuration) / kPulseDuration; + } + } + return result; +} + +- (void)firePulseTimer:(NSTimer *)timer { + USE(timer); + HFASSERT(pulseSelectionStartTime != 0); + pulseSelectionCurrentTime = CFAbsoluteTimeGetCurrent(); + [self _addPropertyChangeBits:HFControllerSelectionPulseAmount]; + if (pulseSelectionCurrentTime - pulseSelectionStartTime > kPulseDuration) { + [pulseSelectionTimer invalidate]; + [pulseSelectionTimer release]; + pulseSelectionTimer = nil; + } +} + +- (void)pulseSelection { + pulseSelectionStartTime = CFAbsoluteTimeGetCurrent(); + if (pulseSelectionTimer == nil) { + pulseSelectionTimer = [[NSTimer scheduledTimerWithTimeInterval:(1. / 30.) target:self selector:@selector(firePulseTimer:) userInfo:nil repeats:YES] retain]; + } +} + +- (void)scrollByLines:(long double)lines { + HFFPRange lineRange = [self displayedLineRange]; + HFASSERT(HFULToFP([self totalLineCount]) >= lineRange.length); + long double maxScroll = HFULToFP([self totalLineCount]) - lineRange.length; + if (lines < 0) { + lineRange.location -= MIN(lineRange.location, -lines); + } + else { + lineRange.location = MIN(maxScroll, lineRange.location + lines); + } + [self setDisplayedLineRange:lineRange]; +} + +- (void)scrollWithScrollEvent:(NSEvent *)scrollEvent { + HFASSERT(scrollEvent != NULL); + HFASSERT([scrollEvent type] == NSScrollWheel); + CGFloat preciseScroll = 0; + BOOL hasPreciseScroll; + + /* Prefer precise deltas */ + if ([scrollEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) { + hasPreciseScroll = [scrollEvent hasPreciseScrollingDeltas]; + if (hasPreciseScroll) { + /* In this case, we're going to scroll by a certain number of points */ + preciseScroll = [scrollEvent scrollingDeltaY]; + } + } else if ([scrollEvent respondsToSelector:@selector(deviceDeltaY)]) { + /* Legacy (SnowLeopard) support */ + hasPreciseScroll = ([scrollEvent subtype] == 1); + if (hasPreciseScroll) { + preciseScroll = [scrollEvent deviceDeltaY]; + } + } else { + hasPreciseScroll = NO; + } + + long double scrollY = 0; + if (! hasPreciseScroll) { + scrollY = -kScrollMultiplier * [scrollEvent deltaY]; + } else { + scrollY = -preciseScroll / [self lineHeight]; + } + [self scrollByLines:scrollY]; +} + +- (void)setSelectedContentsRanges:(NSArray *)selectedRanges { + REQUIRE_NOT_NULL(selectedRanges); + [selectedContentsRanges setArray:selectedRanges]; + VALIDATE_SELECTION(); + selectionAnchor = NO_SELECTION; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; +} + +- (IBAction)selectAll:sender { + USE(sender); + if (_hfflags.selectable) { + [self _setSingleSelectedContentsRange:HFRangeMake(0, [self contentsLength])]; + } +} + +- (void)_addRangeToSelection:(HFRange)range { + [selectedContentsRanges addObject:[HFRangeWrapper withRange:range]]; + [selectedContentsRanges setArray:[HFRangeWrapper organizeAndMergeRanges:selectedContentsRanges]]; + VALIDATE_SELECTION(); +} + +- (void)_removeRangeFromSelection:(HFRange)inputRange withCursorLocationIfAllSelectionRemoved:(unsigned long long)cursorLocation { + NSUInteger selectionCount = [selectedContentsRanges count]; + HFASSERT(selectionCount > 0 && selectionCount <= NSUIntegerMax / 2); + NSUInteger rangeIndex = 0; + NSArray *wrappers; + NEW_ARRAY(HFRange, tempRanges, selectionCount * 2); + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + if (! HFIntersectsRange(range, inputRange)) { + tempRanges[rangeIndex++] = range; + } + else { + if (range.location < inputRange.location) { + tempRanges[rangeIndex++] = HFRangeMake(range.location, inputRange.location - range.location); + } + if (HFMaxRange(range) > HFMaxRange(inputRange)) { + tempRanges[rangeIndex++] = HFRangeMake(HFMaxRange(inputRange), HFMaxRange(range) - HFMaxRange(inputRange)); + } + } + } + if (rangeIndex == 0 || (rangeIndex == 1 && tempRanges[0].length == 0)) { + /* We removed all of our ranges. Telescope us. */ + HFASSERT(cursorLocation <= [self contentsLength]); + [self _setSingleSelectedContentsRange:HFRangeMake(cursorLocation, 0)]; + } + else { + wrappers = [HFRangeWrapper withRanges:tempRanges count:rangeIndex]; + [selectedContentsRanges setArray:[HFRangeWrapper organizeAndMergeRanges:wrappers]]; + } + FREE_ARRAY(tempRanges); + VALIDATE_SELECTION(); +} + +- (void)_moveDirectionDiscardingSelection:(HFControllerMovementDirection)direction byAmount:(unsigned long long)amountToMove { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + BEGIN_TRANSACTION(); + BOOL selectionWasEmpty = ([selectedContentsRanges count] == 1 && [selectedContentsRanges[0] HFRange].length == 0); + BOOL directionIsForward = (direction == HFControllerDirectionRight); + HFRange selectedRange = [self _telescopeSelectionRangeInDirection: (directionIsForward ? HFControllerDirectionRight : HFControllerDirectionLeft)]; + HFASSERT(selectedRange.length == 0); + HFASSERT([self contentsLength] >= selectedRange.location); + /* A movement of just 1 with a selection only clears the selection; it does not move the cursor */ + if (selectionWasEmpty || amountToMove > 1) { + if (direction == HFControllerDirectionLeft) { + selectedRange.location -= MIN(amountToMove, selectedRange.location); + } + else { + selectedRange.location += MIN(amountToMove, [self contentsLength] - selectedRange.location); + } + } + selectionAnchor = NO_SELECTION; + [self _setSingleSelectedContentsRange:selectedRange]; + [self _ensureVisibilityOfLocation:selectedRange.location]; + END_TRANSACTION(); +} + +/* In _extendSelectionInDirection:byAmount:, we only allow left/right movement. up/down is not allowed. */ +- (void)_extendSelectionInDirection:(HFControllerMovementDirection)direction byAmount:(unsigned long long)amountToMove { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + unsigned long long minSelection = [self _minimumSelectionLocation]; + unsigned long long maxSelection = [self _maximumSelectionLocation]; + BOOL selectionChanged = NO; + unsigned long long locationToMakeVisible = NO_SELECTION; + unsigned long long contentsLength = [self contentsLength]; + if (selectionAnchor == NO_SELECTION) { + /* Pick the anchor opposite the choice of direction */ + if (direction == HFControllerDirectionLeft) selectionAnchor = maxSelection; + else selectionAnchor = minSelection; + } + if (direction == HFControllerDirectionLeft) { + if (minSelection >= selectionAnchor && maxSelection > minSelection) { + unsigned long long amountToRemove = llmin(maxSelection - selectionAnchor, amountToMove); + unsigned long long amountToAdd = llmin(amountToMove - amountToRemove, selectionAnchor); + if (amountToRemove > 0) [self _removeRangeFromSelection:HFRangeMake(maxSelection - amountToRemove, amountToRemove) withCursorLocationIfAllSelectionRemoved:minSelection]; + if (amountToAdd > 0) [self _addRangeToSelection:HFRangeMake(selectionAnchor - amountToAdd, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = (amountToAdd > 0 ? selectionAnchor - amountToAdd : maxSelection - amountToRemove); + } + else { + if (minSelection > 0) { + NSUInteger amountToAdd = ll2l(llmin(minSelection, amountToMove)); + if (amountToAdd > 0) [self _addRangeToSelection:HFRangeMake(minSelection - amountToAdd, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = minSelection - amountToAdd; + } + } + } + else if (direction == HFControllerDirectionRight) { + if (maxSelection <= selectionAnchor && maxSelection > minSelection) { + HFASSERT(contentsLength >= maxSelection); + unsigned long long amountToRemove = ll2l(llmin(maxSelection - minSelection, amountToMove)); + unsigned long long amountToAdd = amountToMove - amountToRemove; + if (amountToRemove > 0) [self _removeRangeFromSelection:HFRangeMake(minSelection, amountToRemove) withCursorLocationIfAllSelectionRemoved:maxSelection]; + if (amountToAdd > 0) [self _addRangeToSelection:HFRangeMake(maxSelection, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = llmin(contentsLength, (amountToAdd > 0 ? maxSelection + amountToAdd : minSelection + amountToRemove)); + } + else { + if (maxSelection < contentsLength) { + NSUInteger amountToAdd = ll2l(llmin(contentsLength - maxSelection, amountToMove)); + [self _addRangeToSelection:HFRangeMake(maxSelection, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = maxSelection + amountToAdd; + } + } + } + if (selectionChanged) { + BEGIN_TRANSACTION(); + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + if (locationToMakeVisible != NO_SELECTION) [self _ensureVisibilityOfLocation:locationToMakeVisible]; + END_TRANSACTION(); + } +} + +/* Returns the distance to the next "word" (at least 1, unless we are empty). Here a word is identified as a column. If there are no columns, a word is a line. This is used for word movement (e.g. option + right arrow) */ +- (unsigned long long)_distanceToWordBoundaryForDirection:(HFControllerMovementDirection)direction { + unsigned long long result = 0, locationToConsider; + + /* Figure out how big a word is. By default, it's the column width, unless we have no columns, in which case it's the bytes per line. */ + NSUInteger wordGranularity = [self bytesPerColumn]; + if (wordGranularity == 0) wordGranularity = MAX(1u, [self bytesPerLine]); + if (selectionAnchor == NO_SELECTION) { + /* Pick the anchor inline with the choice of direction */ + if (direction == HFControllerDirectionLeft) locationToConsider = [self _minimumSelectionLocation]; + else locationToConsider = [self _maximumSelectionLocation]; + } else { + /* Just use the anchor */ + locationToConsider = selectionAnchor; + } + if (direction == HFControllerDirectionRight) { + result = HFRoundUpToNextMultipleSaturate(locationToConsider, wordGranularity) - locationToConsider; + } else { + result = locationToConsider % wordGranularity; + if (result == 0) result = wordGranularity; + } + return result; + +} + +/* Anchored selection is not allowed; neither is up/down movement */ +- (void)_shiftSelectionInDirection:(HFControllerMovementDirection)direction byAmount:(unsigned long long)amountToMove { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + HFASSERT(selectionAnchor == NO_SELECTION); + NSUInteger i, max = [selectedContentsRanges count]; + const unsigned long long maxLength = [self contentsLength]; + NSMutableArray *newRanges = [NSMutableArray arrayWithCapacity:max]; + BOOL hasAddedNonemptyRange = NO; + for (i=0; i < max; i++) { + HFRange range = [selectedContentsRanges[i] HFRange]; + HFASSERT(range.location <= maxLength && HFMaxRange(range) <= maxLength); + if (direction == HFControllerDirectionRight) { + unsigned long long offset = MIN(maxLength - range.location, amountToMove); + unsigned long long lengthToSubtract = MIN(range.length, amountToMove - offset); + range.location += offset; + range.length -= lengthToSubtract; + } + else { /* direction == HFControllerDirectionLeft */ + unsigned long long negOffset = MIN(amountToMove, range.location); + unsigned long long lengthToSubtract = MIN(range.length, amountToMove - negOffset); + range.location -= negOffset; + range.length -= lengthToSubtract; + } + [newRanges addObject:[HFRangeWrapper withRange:range]]; + hasAddedNonemptyRange = hasAddedNonemptyRange || (range.length > 0); + } + + newRanges = [[[HFRangeWrapper organizeAndMergeRanges:newRanges] mutableCopy] autorelease]; + + BOOL hasFoundEmptyRange = NO; + max = [newRanges count]; + for (i=0; i < max; i++) { + HFRange range = [newRanges[i] HFRange]; + if (range.length == 0) { + if (hasFoundEmptyRange || hasAddedNonemptyRange) { + [newRanges removeObjectAtIndex:i]; + i--; + max--; + } + hasFoundEmptyRange = YES; + } + } + [selectedContentsRanges setArray:newRanges]; + VALIDATE_SELECTION(); + [self _addPropertyChangeBits:HFControllerSelectedRanges]; +} + +__attribute__((unused)) +static BOOL rangesAreInAscendingOrder(NSEnumerator *rangeEnumerator) { + unsigned long long index = 0; + HFRangeWrapper *rangeWrapper; + while ((rangeWrapper = [rangeEnumerator nextObject])) { + HFRange range = [rangeWrapper HFRange]; + if (range.location < index) return NO; + index = HFSum(range.location, range.length); + } + return YES; +} + +- (BOOL)_registerCondemnedRangesForUndo:(NSArray *)ranges selectingRangesAfterUndo:(BOOL)selectAfterUndo { + HFASSERT(ranges != NULL); + HFASSERT(ranges != selectedContentsRanges); //selectedContentsRanges is mutable - we really don't want to stash it away with undo + BOOL result = NO; + NSUndoManager *manager = [self undoManager]; + NSUInteger rangeCount = [ranges count]; + if (! manager || ! rangeCount) return NO; + + HFASSERT(rangesAreInAscendingOrder([ranges objectEnumerator])); + + NSMutableArray *rangesToRestore = [NSMutableArray arrayWithCapacity:rangeCount]; + NSMutableArray *correspondingByteArrays = [NSMutableArray arrayWithCapacity:rangeCount]; + HFByteArray *bytes = [self byteArray]; + + /* Enumerate the ranges in forward order so when we insert them, we insert later ranges before earlier ones, so we don't have to worry about shifting indexes */ + FOREACH(HFRangeWrapper *, rangeWrapper, ranges) { + HFRange range = [rangeWrapper HFRange]; + if (range.length > 0) { + [rangesToRestore addObject:[HFRangeWrapper withRange:HFRangeMake(range.location, 0)]]; + [correspondingByteArrays addObject:[bytes subarrayWithRange:range]]; + result = YES; + } + } + + if (result) [self _registerUndoOperationForInsertingByteArrays:correspondingByteArrays inRanges:rangesToRestore withSelectionAction:(selectAfterUndo ? eSelectResult : eSelectAfterResult)]; + return result; +} + +- (void)_commandDeleteRanges:(NSArray *)rangesToDelete { + HFASSERT(rangesToDelete != selectedContentsRanges); //selectedContentsRanges is mutable - we really don't want to stash it away with undo + HFASSERT(rangesAreInAscendingOrder([rangesToDelete objectEnumerator])); + + /* Delete all the selection - in reverse order */ + unsigned long long minSelection = ULLONG_MAX; + BOOL somethingWasDeleted = NO; + [self _registerCondemnedRangesForUndo:rangesToDelete selectingRangesAfterUndo:YES]; + NSUInteger rangeIndex = [rangesToDelete count]; + HFASSERT(rangeIndex > 0); + while (rangeIndex--) { + HFRange range = [rangesToDelete[rangeIndex] HFRange]; + minSelection = llmin(range.location, minSelection); + if (range.length > 0) { + [byteArray deleteBytesInRange:range]; + somethingWasDeleted = YES; + } + } + + HFASSERT(minSelection != ULLONG_MAX); + if (somethingWasDeleted) { + BEGIN_TRANSACTION(); + [self _addPropertyChangeBits:HFControllerContentValue | HFControllerContentLength]; + [self _setSingleSelectedContentsRange:HFRangeMake(minSelection, 0)]; + [self _updateDisplayedRange]; + END_TRANSACTION(); + } + else { + NSBeep(); + } +} + +- (void)_commandInsertByteArrays:(NSArray *)byteArrays inRanges:(NSArray *)ranges withSelectionAction:(HFControllerSelectAction)selectionAction { + HFASSERT(selectionAction < NUM_SELECTION_ACTIONS); + REQUIRE_NOT_NULL(byteArrays); + REQUIRE_NOT_NULL(ranges); + HFASSERT([ranges count] == [byteArrays count]); + NSUInteger index, max = [ranges count]; + HFByteArray *bytes = [self byteArray]; + HFASSERT(rangesAreInAscendingOrder([ranges objectEnumerator])); + + NSMutableArray *byteArraysToInsertOnUndo = [NSMutableArray arrayWithCapacity:max]; + NSMutableArray *rangesToInsertOnUndo = [NSMutableArray arrayWithCapacity:max]; + + BEGIN_TRANSACTION(); + if (selectionAction == eSelectResult || selectionAction == eSelectAfterResult) { + [selectedContentsRanges removeAllObjects]; + } + unsigned long long endOfInsertedRanges = ULLONG_MAX; + for (index = 0; index < max; index++) { + HFRange range = [ranges[index] HFRange]; + HFByteArray *oldBytes = [bytes subarrayWithRange:range]; + [byteArraysToInsertOnUndo addObject:oldBytes]; + HFByteArray *newBytes = byteArrays[index]; + EXPECT_CLASS(newBytes, [HFByteArray class]); + [bytes insertByteArray:newBytes inRange:range]; + HFRange insertedRange = HFRangeMake(range.location, [newBytes length]); + HFRangeWrapper *insertedRangeWrapper = [HFRangeWrapper withRange:insertedRange]; + [rangesToInsertOnUndo addObject:insertedRangeWrapper]; + if (selectionAction == eSelectResult) { + [selectedContentsRanges addObject:insertedRangeWrapper]; + } + else { + endOfInsertedRanges = HFMaxRange(insertedRange); + } + } + if (selectionAction == eSelectAfterResult) { + HFASSERT([ranges count] > 0); + [selectedContentsRanges addObject:[HFRangeWrapper withRange:HFRangeMake(endOfInsertedRanges, 0)]]; + } + + if (selectionAction == ePreserveSelection) { + HFASSERT([selectedContentsRanges count] > 0); + [self _clipSelectedContentsRangesToLength:[self contentsLength]]; + } + + VALIDATE_SELECTION(); + HFASSERT([byteArraysToInsertOnUndo count] == [rangesToInsertOnUndo count]); + [self _registerUndoOperationForInsertingByteArrays:byteArraysToInsertOnUndo inRanges:rangesToInsertOnUndo withSelectionAction:(selectionAction == ePreserveSelection ? ePreserveSelection : eSelectAfterResult)]; + [self _updateDisplayedRange]; + [self maximizeVisibilityOfContentsRange:[selectedContentsRanges[0] HFRange]]; + [self _addPropertyChangeBits:HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges]; + END_TRANSACTION(); +} + +/* The user has hit undo after typing a string. */ +- (void)_commandReplaceBytesAfterBytesFromBeginning:(unsigned long long)leftOffset upToBytesFromEnd:(unsigned long long)rightOffset withByteArray:(HFByteArray *)bytesToReinsert { + HFASSERT(bytesToReinsert != NULL); + + BEGIN_TRANSACTION(); + HFByteArray *bytes = [self byteArray]; + unsigned long long contentsLength = [self contentsLength]; + HFASSERT(leftOffset <= contentsLength); + HFASSERT(rightOffset <= contentsLength); + HFASSERT(contentsLength - rightOffset >= leftOffset); + HFRange rangeToReplace = HFRangeMake(leftOffset, contentsLength - rightOffset - leftOffset); + [self _registerCondemnedRangesForUndo:[HFRangeWrapper withRanges:&rangeToReplace count:1] selectingRangesAfterUndo:NO]; + [bytes insertByteArray:bytesToReinsert inRange:rangeToReplace]; + [self _updateDisplayedRange]; + [self _setSingleSelectedContentsRange:HFRangeMake(rangeToReplace.location, [bytesToReinsert length])]; + [self _addPropertyChangeBits:HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges]; + END_TRANSACTION(); +} + +/* We use NSNumbers instead of long longs here because Tiger/PPC NSInvocation had trouble with long longs */ +- (void)_commandValueObjectsReplaceBytesAfterBytesFromBeginning:(NSNumber *)leftOffset upToBytesFromEnd:(NSNumber *)rightOffset withByteArray:(HFByteArray *)bytesToReinsert { + HFASSERT(leftOffset != NULL); + HFASSERT(rightOffset != NULL); + EXPECT_CLASS(leftOffset, NSNumber); + EXPECT_CLASS(rightOffset, NSNumber); + [self _commandReplaceBytesAfterBytesFromBeginning:[leftOffset unsignedLongLongValue] upToBytesFromEnd:[rightOffset unsignedLongLongValue] withByteArray:bytesToReinsert]; +} + +- (void)moveInDirection:(HFControllerMovementDirection)direction byByteCount:(unsigned long long)amountToMove withSelectionTransformation:(HFControllerSelectionTransformation)transformation usingAnchor:(BOOL)useAnchor { + if (! useAnchor) selectionAnchor = NO_SELECTION; + switch (transformation) { + case HFControllerDiscardSelection: + [self _moveDirectionDiscardingSelection:direction byAmount:amountToMove]; + break; + + case HFControllerShiftSelection: + [self _shiftSelectionInDirection:direction byAmount:amountToMove]; + break; + + case HFControllerExtendSelection: + [self _extendSelectionInDirection:direction byAmount:amountToMove]; + break; + + default: + [NSException raise:NSInvalidArgumentException format:@"Invalid transformation %ld", (long)transformation]; + break; + } + if (! useAnchor) selectionAnchor = NO_SELECTION; +} + +- (void)moveInDirection:(HFControllerMovementDirection)direction withGranularity:(HFControllerMovementGranularity)granularity andModifySelection:(BOOL)extendSelection { + HFASSERT(granularity == HFControllerMovementByte || granularity == HFControllerMovementColumn || granularity == HFControllerMovementLine || granularity == HFControllerMovementPage || granularity == HFControllerMovementDocument); + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + unsigned long long bytesToMove = 0; + switch (granularity) { + case HFControllerMovementByte: + bytesToMove = 1; + break; + case HFControllerMovementColumn: + /* This is a tricky case because the amount we have to move depends on our position in the column. */ + bytesToMove = [self _distanceToWordBoundaryForDirection:direction]; + break; + case HFControllerMovementLine: + bytesToMove = [self bytesPerLine]; + break; + case HFControllerMovementPage: + bytesToMove = HFProductULL([self bytesPerLine], HFFPToUL(MIN(floorl([self displayedLineRange].length), 1.))); + break; + case HFControllerMovementDocument: + bytesToMove = [self contentsLength]; + break; + } + HFControllerSelectionTransformation transformation = (extendSelection ? HFControllerExtendSelection : HFControllerDiscardSelection); + [self moveInDirection:direction byByteCount:bytesToMove withSelectionTransformation:transformation usingAnchor:YES]; +} + +- (void)moveToLineBoundaryInDirection:(HFControllerMovementDirection)direction andModifySelection:(BOOL)modifySelection { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + BEGIN_TRANSACTION(); + unsigned long long locationToMakeVisible; + HFRange additionalSelection; + + if (direction == HFControllerDirectionLeft) { + /* If we are at the beginning of a line, this should be a no-op */ + unsigned long long minLocation = [self _minimumSelectionLocation]; + unsigned long long newMinLocation = (minLocation / bytesPerLine) * bytesPerLine; + locationToMakeVisible = newMinLocation; + additionalSelection = HFRangeMake(newMinLocation, minLocation - newMinLocation); + } + else { + /* This always advances to the next line */ + unsigned long long maxLocation = [self _maximumSelectionLocation]; + unsigned long long proposedNewMaxLocation = HFRoundUpToNextMultipleSaturate(maxLocation, bytesPerLine); + unsigned long long newMaxLocation = MIN([self contentsLength], proposedNewMaxLocation); + HFASSERT(newMaxLocation >= maxLocation); + locationToMakeVisible = newMaxLocation; + additionalSelection = HFRangeMake(maxLocation, newMaxLocation - maxLocation); + } + + if (modifySelection) { + if (additionalSelection.length > 0) { + [self _addRangeToSelection:additionalSelection]; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + } + else { + [self _setSingleSelectedContentsRange:HFRangeMake(locationToMakeVisible, 0)]; + } + [self _ensureVisibilityOfLocation:locationToMakeVisible]; + END_TRANSACTION(); +} + +- (void)deleteSelection { + if ([self editMode] == HFOverwriteMode || ! [self editable]) { + NSBeep(); + } + else { + [self _commandDeleteRanges:[HFRangeWrapper organizeAndMergeRanges:selectedContentsRanges]]; + } +} + +// Called after Replace All is finished. +- (void)replaceByteArray:(HFByteArray *)newArray { + REQUIRE_NOT_NULL(newArray); + EXPECT_CLASS(newArray, HFByteArray); + HFRange entireRange = HFRangeMake(0, [self contentsLength]); + if ([self editMode] == HFOverwriteMode && [newArray length] != entireRange.length) { + NSBeep(); + } + else { + [self _commandInsertByteArrays:@[newArray] inRanges:[HFRangeWrapper withRanges:&entireRange count:1] withSelectionAction:ePreserveSelection]; + } +} + +- (BOOL)insertData:(NSData *)data replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing { + REQUIRE_NOT_NULL(data); + BOOL result; +#if ! NDEBUG + const unsigned long long startLength = [byteArray length]; + unsigned long long expectedNewLength; + if ([self editMode] == HFOverwriteMode) { + expectedNewLength = startLength; + } + else { + expectedNewLength = startLength + [data length] - previousBytes; + FOREACH(HFRangeWrapper*, wrapper, [self selectedContentsRanges]) expectedNewLength -= [wrapper HFRange].length; + } +#endif + HFByteSlice *slice = [[HFSharedMemoryByteSlice alloc] initWithUnsharedData:data]; + HFASSERT([slice length] == [data length]); + HFByteArray *array = [[preferredByteArrayClass() alloc] init]; + [array insertByteSlice:slice inRange:HFRangeMake(0, 0)]; + HFASSERT([array length] == [data length]); + result = [self insertByteArray:array replacingPreviousBytes:previousBytes allowUndoCoalescing:allowUndoCoalescing]; + [slice release]; + [array release]; +#if ! NDEBUG + HFASSERT((result && [byteArray length] == expectedNewLength) || (! result && [byteArray length] == startLength)); +#endif + return result; +} + +- (BOOL)_insertionModeCoreInsertByteArray:(HFByteArray *)bytesToInsert replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing outNewSingleSelectedRange:(HFRange *)outSelectedRange { + HFASSERT([self editMode] == HFInsertMode); + REQUIRE_NOT_NULL(bytesToInsert); + + /* Guard against overflow. If [bytesToInsert length] + [self contentsLength] - previousBytes overflows, then we can't do it */ + HFASSERT([self contentsLength] >= previousBytes); + if (! HFSumDoesNotOverflow([bytesToInsert length], [self contentsLength] - previousBytes)) { + return NO; //don't do anything + } + + + unsigned long long amountDeleted = 0, amountAdded = [bytesToInsert length]; + HFByteArray *bytes = [self byteArray]; + + /* Delete all the selection - in reverse order - except the last (really first) one, which we will overwrite. */ + NSArray *allRangesToRemove = [HFRangeWrapper organizeAndMergeRanges:[self selectedContentsRanges]]; + HFRange rangeToReplace = [allRangesToRemove[0] HFRange]; + HFASSERT(rangeToReplace.location == [self _minimumSelectionLocation]); + NSUInteger rangeIndex, rangeCount = [allRangesToRemove count]; + HFASSERT(rangeCount > 0); + NSMutableArray *rangesToDelete = [NSMutableArray arrayWithCapacity:rangeCount - 1]; + for (rangeIndex = rangeCount - 1; rangeIndex > 0; rangeIndex--) { + HFRangeWrapper *rangeWrapper = allRangesToRemove[rangeIndex]; + HFRange range = [rangeWrapper HFRange]; + if (range.length > 0) { + amountDeleted = HFSum(amountDeleted, range.length); + [rangesToDelete insertObject:rangeWrapper atIndex:0]; + } + } + + if ([rangesToDelete count] > 0) { + HFASSERT(rangesAreInAscendingOrder([rangesToDelete objectEnumerator])); + /* TODO: This is problematic because it overwrites the selection that gets set by _activateTypingUndoCoalescingForReplacingRange:, so we lose the first selection in a multiple selection scenario. */ + [self _registerCondemnedRangesForUndo:rangesToDelete selectingRangesAfterUndo:YES]; + NSEnumerator *enumer = [rangesToDelete reverseObjectEnumerator]; + HFRangeWrapper *rangeWrapper; + while ((rangeWrapper = [enumer nextObject])) { + [bytes deleteBytesInRange:[rangeWrapper HFRange]]; + } + } + + rangeToReplace.length = HFSum(rangeToReplace.length, previousBytes); + + /* Insert data */ +#if ! NDEBUG + unsigned long long expectedLength = [byteArray length] + [bytesToInsert length] - rangeToReplace.length; +#endif + [byteArray insertByteArray:bytesToInsert inRange:rangeToReplace]; +#if ! NDEBUG + HFASSERT(expectedLength == [byteArray length]); +#endif + + /* return the new selected range */ + *outSelectedRange = HFRangeMake(HFSum(rangeToReplace.location, amountAdded), 0); + return YES; +} + + +- (BOOL)_overwriteModeCoreInsertByteArray:(HFByteArray *)bytesToInsert replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing outRangeToRemoveFromSelection:(HFRange *)outRangeToRemove { + REQUIRE_NOT_NULL(bytesToInsert); + const unsigned long long byteArrayLength = [byteArray length]; + const unsigned long long bytesToInsertLength = [bytesToInsert length]; + HFRange firstSelectedRange = [selectedContentsRanges[0] HFRange]; + HFRange proposedRangeToOverwrite = HFRangeMake(firstSelectedRange.location, bytesToInsertLength); + HFASSERT(proposedRangeToOverwrite.location >= previousBytes); + proposedRangeToOverwrite.location -= previousBytes; + if (! HFRangeIsSubrangeOfRange(proposedRangeToOverwrite, HFRangeMake(0, byteArrayLength))) { + /* The user tried to overwrite past the end */ + NSBeep(); + return NO; + } + + [byteArray insertByteArray:bytesToInsert inRange:proposedRangeToOverwrite]; + + *outRangeToRemove = proposedRangeToOverwrite; + return YES; +} + +- (BOOL)insertByteArray:(HFByteArray *)bytesToInsert replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing { +#if ! NDEBUG + if (previousBytes > 0) { + NSArray *selectedRanges = [self selectedContentsRanges]; + HFASSERT([selectedRanges count] == 1); + HFRange selectedRange = [selectedRanges[0] HFRange]; + HFASSERT(selectedRange.location >= previousBytes); //don't try to delete more trailing bytes than we actually have! + } +#endif + REQUIRE_NOT_NULL(bytesToInsert); + + + BEGIN_TRANSACTION(); + unsigned long long beforeLength = [byteArray length]; + BOOL inOverwriteMode = [self editMode] == HFOverwriteMode; + HFRange modificationRange; //either range to remove from selection if in overwrite mode, or range to select if not + BOOL success; + if (inOverwriteMode) { + success = [self _overwriteModeCoreInsertByteArray:bytesToInsert replacingPreviousBytes:previousBytes allowUndoCoalescing:allowUndoCoalescing outRangeToRemoveFromSelection:&modificationRange]; + } + else { + success = [self _insertionModeCoreInsertByteArray:bytesToInsert replacingPreviousBytes:previousBytes allowUndoCoalescing:allowUndoCoalescing outNewSingleSelectedRange:&modificationRange]; + } + + if (success) { + /* Update our selection */ + [self _addPropertyChangeBits:HFControllerContentValue]; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits:HFControllerContentValue]; + if (inOverwriteMode) { + [self _removeRangeFromSelection:modificationRange withCursorLocationIfAllSelectionRemoved:HFMaxRange(modificationRange)]; + [self maximizeVisibilityOfContentsRange:[selectedContentsRanges[0] HFRange]]; + } + else { + [self _setSingleSelectedContentsRange:modificationRange]; + [self maximizeVisibilityOfContentsRange:modificationRange]; + } + if (beforeLength != [byteArray length]) [self _addPropertyChangeBits:HFControllerContentLength]; + } + END_TRANSACTION(); + return success; +} + +- (void)deleteDirection:(HFControllerMovementDirection)direction { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + if ([self editMode] != HFInsertMode || ! [self editable]) { + NSBeep(); + return; + } + unsigned long long minSelection = [self _minimumSelectionLocation]; + unsigned long long maxSelection = [self _maximumSelectionLocation]; + if (maxSelection != minSelection) { + [self deleteSelection]; + } + else { + HFRange rangeToDelete = HFRangeMake(minSelection, 1); + BOOL rangeIsValid; + if (direction == HFControllerDirectionLeft) { + rangeIsValid = (rangeToDelete.location > 0); + rangeToDelete.location--; + } + else { + rangeIsValid = (rangeToDelete.location < [self contentsLength]); + } + if (rangeIsValid) { + BEGIN_TRANSACTION(); + [byteArray deleteBytesInRange:rangeToDelete]; + [self _setSingleSelectedContentsRange:HFRangeMake(rangeToDelete.location, 0)]; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits:HFControllerSelectedRanges | HFControllerContentValue | HFControllerContentLength]; + END_TRANSACTION(); + } + } +} + +- (HFEditMode)editMode { + return _hfflags.editMode; +} + +- (void)setEditMode:(HFEditMode)val +{ + if (val != _hfflags.editMode) { + _hfflags.editMode = val; + // don't allow undo coalescing when switching modes + [self _addPropertyChangeBits:HFControllerEditable]; + } +} + +- (void)reloadData { + BEGIN_TRANSACTION(); + [cachedData release]; + cachedData = nil; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits: HFControllerContentValue]; + END_TRANSACTION(); +} + +#if BENCHMARK_BYTEARRAYS + ++ (void)_testByteArray { + HFByteArray* first = [[[HFFullMemoryByteArray alloc] init] autorelease]; + HFBTreeByteArray* second = [[[HFBTreeByteArray alloc] init] autorelease]; + first = nil; + // second = nil; + + //srandom(time(NULL)); + + unsigned opCount = 4096 * 512; + unsigned long long expectedLength = 0; + unsigned i; + for (i=1; i <= opCount; i++) { + @autoreleasepool { + NSUInteger op; + const unsigned long long length = [first length]; + unsigned long long offset; + unsigned long long number; + switch ((op = (random()%2))) { + case 0: { //insert + offset = random() % (1 + length); + HFByteSlice* slice = [[HFRandomDataByteSlice alloc] initWithRandomDataLength: 1 + random() % 1000]; + [first insertByteSlice:slice inRange:HFRangeMake(offset, 0)]; + [second insertByteSlice:slice inRange:HFRangeMake(offset, 0)]; + expectedLength += [slice length]; + [slice release]; + break; + } + case 1: { //delete + if (length > 0) { + offset = random() % length; + number = 1 + random() % (length - offset); + [first deleteBytesInRange:HFRangeMake(offset, number)]; + [second deleteBytesInRange:HFRangeMake(offset, number)]; + expectedLength -= number; + } + break; + } + } + } // @autoreleasepool + } +} + ++ (void)_testAttributeArrays { + HFByteRangeAttributeArray *naiveTree = [[HFNaiveByteRangeAttributeArray alloc] init]; + HFAnnotatedTreeByteRangeAttributeArray *smartTree = [[HFAnnotatedTreeByteRangeAttributeArray alloc] init]; + naiveTree = nil; + // smartTree = nil; + + NSString * const attributes[3] = {@"Alpha", @"Beta", @"Gamma"}; + + const NSUInteger supportedIndexEnd = NSNotFound; + NSUInteger round; + for (round = 0; round < 4096 * 256; round++) { + NSString *attribute = attributes[random() % (sizeof attributes / sizeof *attributes)]; + BOOL insert = ([smartTree isEmpty] || [naiveTree isEmpty] || (random() % 2)); + + unsigned long long end = random(); + unsigned long long start = random(); + if (end < start) { + unsigned long long temp = end; + end = start; + start = temp; + } + HFRange range = HFRangeMake(start, end - start); + + if (insert) { + [naiveTree addAttribute:attribute range:range]; + [smartTree addAttribute:attribute range:range]; + } + else { + [naiveTree removeAttribute:attribute range:range]; + [smartTree removeAttribute:attribute range:range]; + } + } + + [naiveTree release]; + [smartTree release]; +} + + ++ (void)initialize { + CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); + srandom(0); + [self _testByteArray]; + CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); + printf("Byte array time: %f\n", end - start); + + srandom(0); + start = CFAbsoluteTimeGetCurrent(); + [self _testAttributeArrays]; + end = CFAbsoluteTimeGetCurrent(); + printf("Attribute array time: %f\n", end - start); + + exit(0); +} + +#endif + +@end diff --git a/HexFiend/HFFullMemoryByteArray.h b/HexFiend/HFFullMemoryByteArray.h new file mode 100644 index 00000000..9debeb2e --- /dev/null +++ b/HexFiend/HFFullMemoryByteArray.h @@ -0,0 +1,21 @@ +// +// HFFullMemoryByteArray.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! + @class HFFullMemoryByteArray + @brief A naive subclass of HFByteArray suitable mainly for testing. Use HFBTreeByteArray instead. + + HFFullMemoryByteArray is a simple subclass of HFByteArray that does not store any byte slices. Because it stores all data in an NSMutableData, it is not efficient. It is mainly useful as a naive implementation for testing. Use HFBTreeByteArray instead. +*/ +@interface HFFullMemoryByteArray : HFByteArray { + NSMutableData *data; +} + + +@end diff --git a/HexFiend/HFFullMemoryByteArray.m b/HexFiend/HFFullMemoryByteArray.m new file mode 100644 index 00000000..f5a582b9 --- /dev/null +++ b/HexFiend/HFFullMemoryByteArray.m @@ -0,0 +1,70 @@ +// +// HFFullMemoryByteArray.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import + +@implementation HFFullMemoryByteArray + +- (instancetype)init { + self = [super init]; + data = [[NSMutableData alloc] init]; + return self; +} + +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (unsigned long long)length { + return [data length]; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { + HFASSERT(range.length == 0 || dst != NULL); + HFASSERT(HFSumDoesNotOverflow(range.location, range.length)); + HFASSERT(range.location + range.length <= [self length]); + unsigned char* bytes = [data mutableBytes]; + memmove(dst, bytes + ll2l(range.location), ll2l(range.length)); +} + +- (HFByteArray *)subarrayWithRange:(HFRange)lrange { + HFRange entireRange = HFRangeMake(0, [self length]); + HFASSERT(HFRangeIsSubrangeOfRange(lrange, entireRange)); + NSRange range; + range.location = ll2l(lrange.location); + range.length = ll2l(lrange.length); + HFFullMemoryByteArray* result = [[[self class] alloc] init]; + [result->data setData:[data subdataWithRange:range]]; + return [result autorelease]; +} + +- (NSArray *)byteSlices { + return @[[[[HFFullMemoryByteSlice alloc] initWithData:data] autorelease]]; +} + +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + HFASSERT([slice length] <= NSUIntegerMax); + NSUInteger length = ll2l([slice length]); + NSRange range; + HFASSERT(lrange.location <= NSUIntegerMax); + HFASSERT(lrange.length <= NSUIntegerMax); + HFASSERT(HFSumDoesNotOverflow(lrange.location, lrange.length)); + range.location = ll2l(lrange.location); + range.length = ll2l(lrange.length); + + void* buff = check_malloc(length); + [slice copyBytes:buff range:HFRangeMake(0, length)]; + [data replaceBytesInRange:range withBytes:buff length:length]; + free(buff); +} + +@end diff --git a/HexFiend/HFFullMemoryByteSlice.h b/HexFiend/HFFullMemoryByteSlice.h new file mode 100644 index 00000000..ec195cad --- /dev/null +++ b/HexFiend/HFFullMemoryByteSlice.h @@ -0,0 +1,21 @@ +// +// HFFullMemoryByteSlice.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFFullMemoryByteSlice + + @brief A simple subclass of HFByteSlice that wraps an NSData. For most uses, prefer HFSharedMemoryByteSlice. +*/ +@interface HFFullMemoryByteSlice : HFByteSlice { + NSData *data; +} + +/*! Init with a given NSData, which is copied via the \c -copy message. */ +- (instancetype)initWithData:(NSData *)val; + +@end diff --git a/HexFiend/HFFullMemoryByteSlice.m b/HexFiend/HFFullMemoryByteSlice.m new file mode 100644 index 00000000..2a38ccdf --- /dev/null +++ b/HexFiend/HFFullMemoryByteSlice.m @@ -0,0 +1,46 @@ +// +// HFFullMemoryByteSlice.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import "HFFullMemoryByteSlice.h" + + +@implementation HFFullMemoryByteSlice + +- (instancetype)initWithData:(NSData *)val { + REQUIRE_NOT_NULL(val); + self = [super init]; + data = [val copy]; + return self; +} + +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (unsigned long long)length { return [data length]; } + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)lrange { + NSRange range; + HFASSERT(lrange.location <= NSUIntegerMax); + HFASSERT(lrange.length <= NSUIntegerMax); + HFASSERT(lrange.location + lrange.length >= lrange.location); + range.location = ll2l(lrange.location); + range.length = ll2l(lrange.length); + [data getBytes:dst range:range]; +} + +- (HFByteSlice *)subsliceWithRange:(HFRange)range { + HFASSERT(range.length > 0); + HFASSERT(range.location < [self length]); + HFASSERT([self length] - range.location >= range.length); + HFASSERT(range.location <= NSUIntegerMax); + HFASSERT(range.length <= NSUIntegerMax); + return [[[[self class] alloc] initWithData:[data subdataWithRange:NSMakeRange(ll2l(range.location), ll2l(range.length))]] autorelease]; +} + +@end diff --git a/HexFiend/HFFunctions.h b/HexFiend/HFFunctions.h new file mode 100644 index 00000000..3d3586d0 --- /dev/null +++ b/HexFiend/HFFunctions.h @@ -0,0 +1,533 @@ +/* Functions and convenience methods for working with HFTypes */ + +#import +#import + +#define HFDEFAULT_FONT (@"Monaco") +#define HFDEFAULT_FONTSIZE ((CGFloat)10.) + +#define HFZeroRange (HFRange){0, 0} + +/*! + Makes an HFRange. An HFRange is like an NSRange except it uses unsigned long longs. +*/ +static inline HFRange HFRangeMake(unsigned long long loc, unsigned long long len) { + return (HFRange){loc, len}; +} + +/*! + Returns true if a given location is within a given HFRange. If the location is at the end of the range (range.location + range.length) this returns NO. +*/ +static inline BOOL HFLocationInRange(unsigned long long location, HFRange range) { + return location >= range.location && location - range.location < range.length; +} + +/*! + Like NSRangeToString but for HFRanges +*/ +static inline NSString* HFRangeToString(HFRange range) { + return [NSString stringWithFormat:@"{%llu, %llu}", range.location, range.length]; +} + +/*! + Converts a given HFFPRange to a string. +*/ +static inline NSString* HFFPRangeToString(HFFPRange range) { + return [NSString stringWithFormat:@"{%Lf, %Lf}", range.location, range.length]; +} + +/*! + Returns true if two HFRanges are equal. +*/ +static inline BOOL HFRangeEqualsRange(HFRange a, HFRange b) { + return a.location == b.location && a.length == b.length; +} + +/*! + Returns true if a + b does not overflow an unsigned long long. +*/ +static inline BOOL HFSumDoesNotOverflow(unsigned long long a, unsigned long long b) { + return a + b >= a; +} + +/*! + Returns true if a * b does not overflow an unsigned long long. +*/ +static inline BOOL HFProductDoesNotOverflow(unsigned long long a, unsigned long long b) { + if (b == 0) return YES; + unsigned long long result = a * b; + return result / b == a; +} + +/*! + Returns a * b as an NSUInteger. This asserts on overflow, unless NDEBUG is defined. +*/ +static inline NSUInteger HFProductInt(NSUInteger a, NSUInteger b) { + NSUInteger result = a * b; + assert(a == 0 || result / a == b); //detect overflow + return result; +} + +/*! + Returns a + b as an NSUInteger. This asserts on overflow unless NDEBUG is defined. +*/ +static inline NSUInteger HFSumInt(NSUInteger a, NSUInteger b) { + assert(a + b >= a); + return a + b; +} + +/*! + Returns a + b as an NSUInteger, saturating at NSUIntegerMax + */ +static inline NSUInteger HFSumIntSaturate(NSUInteger a, NSUInteger b) { + NSUInteger result = a + b; + return (result < a) ? NSUIntegerMax : result; +} + +/*! + Returns a + b as an unsigned long long, saturating at ULLONG_MAX + */ +static inline unsigned long long HFSumULLSaturate(unsigned long long a, unsigned long long b) { + unsigned long long result = a + b; + return (result < a) ? ULLONG_MAX : result; +} + +/*! + Returns a * b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined. +*/ +static inline unsigned long long HFProductULL(unsigned long long a, unsigned long long b) { + unsigned long long result = a * b; + assert(HFProductDoesNotOverflow(a, b)); //detect overflow + return result; +} + +/*! + Returns a + b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined. +*/ +static inline unsigned long long HFSum(unsigned long long a, unsigned long long b) { + assert(HFSumDoesNotOverflow(a, b)); + return a + b; +} + +/*! + Returns a + b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined. + */ +static inline unsigned long long HFMaxULL(unsigned long long a, unsigned long long b) { + return a < b ? b : a; +} + +/*! + Returns a - b as an unsigned long long. This asserts on underflow (if b > a), unless NDEBUG is defined. +*/ +static inline unsigned long long HFSubtract(unsigned long long a, unsigned long long b) { + assert(a >= b); + return a - b; +} + +/*! + Returns the smallest multiple of B that is equal to or larger than A, and asserts on overflow. +*/ +static inline unsigned long long HFRoundUpToMultiple(unsigned long long a, unsigned long long b) { + // The usual approach of ((a + (b - 1)) / b) * b doesn't handle overflow correctly + unsigned long long remainder = a % b; + if (remainder == 0) return a; + else return HFSum(a, b - remainder); +} + +/*! + Returns the smallest multiple of B that is equal to or larger than A, and asserts on overflow. + */ +static inline NSUInteger HFRoundUpToMultipleInt(NSUInteger a, NSUInteger b) { + // The usual approach of ((a + (b - 1)) / b) * b doesn't handle overflow correctly + NSUInteger remainder = a % b; + if (remainder == 0) return a; + else return (NSUInteger)HFSum(a, b - remainder); +} + +/*! + Returns the least common multiple of A and B, and asserts on overflow or if A or B is zero. + */ +static inline NSUInteger HFLeastCommonMultiple(NSUInteger a, NSUInteger b) { + assert(a > 0); + assert(b > 0); + + /* Compute GCD. It ends up in U. */ + NSUInteger t, u = a, v = b; + while (v > 0) { + t = v; + v = u % v; + u = t; + } + + /* Return the product divided by the GCD, in an overflow safe manner */ + return HFProductInt(a/u, b); +} + + +/*! + Returns the smallest multiple of B strictly larger than A, or ULLONG_MAX if it would overflow +*/ +static inline unsigned long long HFRoundUpToNextMultipleSaturate(unsigned long long a, unsigned long long b) { + assert(b > 0); + unsigned long long result = a + (b - a % b); + if (result < a) result = ULLONG_MAX; //the saturation...on overflow go to the max + return result; +} + +/*! Like NSMaxRange, but for an HFRange. */ +static inline unsigned long long HFMaxRange(HFRange a) { + assert(HFSumDoesNotOverflow(a.location, a.length)); + return a.location + a.length; +} + +/*! Returns YES if needle is fully contained within haystack. Equal ranges are always considered to be subranges of each other (even if they are empty). Furthermore, a zero length needle at the end of haystack is considered a subrange - for example, {6, 0} is a subrange of {3, 3}. */ +static inline BOOL HFRangeIsSubrangeOfRange(HFRange needle, HFRange haystack) { + // If needle starts before haystack, or if needle is longer than haystack, it is not a subrange of haystack + if (needle.location < haystack.location || needle.length > haystack.length) return NO; + + // Their difference in lengths determines the maximum difference in their start locations. We know that these expressions cannot overflow because of the above checks. + return haystack.length - needle.length >= needle.location - haystack.location; +} + +/*! Splits a range about a subrange, returning by reference the prefix and suffix (which may have length zero). */ +static inline void HFRangeSplitAboutSubrange(HFRange range, HFRange subrange, HFRange *outPrefix, HFRange *outSuffix) { + // Requires it to be a subrange + assert(HFRangeIsSubrangeOfRange(subrange, range)); + outPrefix->location = range.location; + outPrefix->length = HFSubtract(subrange.location, range.location); + outSuffix->location = HFMaxRange(subrange); + outSuffix->length = HFMaxRange(range) - outSuffix->location; +} + +/*! Returns YES if the given ranges intersect. Two ranges are considered to intersect if they share at least one index in common. Thus, zero-length ranges do not intersect anything. */ +static inline BOOL HFIntersectsRange(HFRange a, HFRange b) { + // Ranges are said to intersect if they share at least one value. Therefore, zero length ranges never intersect anything. + if (a.length == 0 || b.length == 0) return NO; + + // rearrange (a.location < b.location + b.length && b.location < a.location + a.length) to not overflow + // = ! (a.location >= b.location + b.length || b.location >= a.location + a.length) + BOOL clause1 = (a.location >= b.location && a.location - b.location >= b.length); + BOOL clause2 = (b.location >= a.location && b.location - a.location >= a.length); + return ! (clause1 || clause2); +} + +/*! Returns YES if the given ranges intersect. Two ranges are considered to intersect if any fraction overlaps; zero-length ranges do not intersect anything. */ +static inline BOOL HFFPIntersectsRange(HFFPRange a, HFFPRange b) { + // Ranges are said to intersect if they share at least one value. Therefore, zero length ranges never intersect anything. + if (a.length == 0 || b.length == 0) return NO; + + if (a.location <= b.location && a.location + a.length >= b.location) return YES; + if (b.location <= a.location && b.location + b.length >= a.location) return YES; + return NO; +} + +/*! Returns a range containing the union of the given ranges. These ranges must either intersect or be adjacent: there cannot be any "holes" between them. */ +static inline HFRange HFUnionRange(HFRange a, HFRange b) { + assert(HFIntersectsRange(a, b) || HFMaxRange(a) == b.location || HFMaxRange(b) == a.location); + HFRange result; + result.location = MIN(a.location, b.location); + assert(HFSumDoesNotOverflow(a.location, a.length)); + assert(HFSumDoesNotOverflow(b.location, b.length)); + result.length = MAX(a.location + a.length, b.location + b.length) - result.location; + return result; +} + + +/*! Returns whether a+b > c+d, as if there were no overflow (so ULLONG_MAX + 1 > 10 + 20) */ +static inline BOOL HFSumIsLargerThanSum(unsigned long long a, unsigned long long b, unsigned long long c, unsigned long long d) { +#if 1 + // Theory: compare a/2 + b/2 to c/2 + d/2, and if they're equal, compare a%2 + b%2 to c%2 + d%2. We may get into trouble if a and b are both even and c and d are both odd: e.g. a = 2, b = 2, c = 1, d = 3. We would compare 1 + 1 vs 0 + 1, and therefore that 2 + 2 > 1 + 3. To address this, if both remainders are 1, we add this to the sum. We know this cannot overflow because ULLONG_MAX is odd, so (ULLONG_MAX/2) + (ULLONG_MAX/2) + 1 does not overflow. + unsigned int rem1 = (unsigned)(a%2 + b%2); + unsigned int rem2 = (unsigned)(c%2 + d%2); + unsigned long long sum1 = a/2 + b/2 + rem1/2; + unsigned long long sum2 = c/2 + d/2 + rem2/2; + if (sum1 > sum2) return YES; + else if (sum1 < sum2) return NO; + else { + // sum1 == sum2, so compare the remainders. But we have already added in the remainder / 2, so compare the remainders mod 2. + if (rem1%2 > rem2%2) return YES; + else return NO; + } +#else + /* Faster version, but not thoroughly tested yet. */ + unsigned long long xor1 = a^b; + unsigned long long xor2 = c^d; + unsigned long long avg1 = (a&b)+(xor1/2); + unsigned long long avg2 = (c&d)+(xor2/2); + unsigned s1l = avg1 > avg2; + unsigned eq = (avg1 == avg2); + return s1l | ((xor1 & ~xor2) & eq); +#endif +} + +/*! Returns the absolute value of a - b. */ +static inline unsigned long long HFAbsoluteDifference(unsigned long long a, unsigned long long b) { + if (a > b) return a - b; + else return b - a; +} + +/*! Returns true if the end of A is larger than the end of B. */ +static inline BOOL HFRangeExtendsPastRange(HFRange a, HFRange b) { + return HFSumIsLargerThanSum(a.location, a.length, b.location, b.length); +} + +/*! Returns a range containing all indexes in common betwen the two ranges. If there are no indexes in common, returns {0, 0}. */ +static inline HFRange HFIntersectionRange(HFRange range1, HFRange range2) { + unsigned long long minend = HFRangeExtendsPastRange(range2, range1) ? range1.location + range1.length : range2.location + range2.length; + if (range2.location <= range1.location && range1.location - range2.location < range2.length) { + return HFRangeMake(range1.location, minend - range1.location); + } + else if (range1.location <= range2.location && range2.location - range1.location < range1.length) { + return HFRangeMake(range2.location, minend - range2.location); + } + return HFRangeMake(0, 0); +} + +/*! ceil() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFCeil(CGFloat a) { + if (sizeof(a) == sizeof(float)) return (CGFloat)ceilf((float)a); + else return (CGFloat)ceil((double)a); +} + +/*! floor() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFFloor(CGFloat a) { + if (sizeof(a) == sizeof(float)) return (CGFloat)floorf((float)a); + else return (CGFloat)floor((double)a); +} + +/*! round() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFRound(CGFloat a) { + if (sizeof(a) == sizeof(float)) return (CGFloat)roundf((float)a); + else return (CGFloat)round((double)a); +} + +/*! fmin() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFMin(CGFloat a, CGFloat b) { + if (sizeof(a) == sizeof(float)) return (CGFloat)fminf((float)a, (float)b); + else return (CGFloat)fmin((double)a, (double)b); +} + +/*! fmax() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFMax(CGFloat a, CGFloat b) { + if (sizeof(a) == sizeof(float)) return (CGFloat)fmaxf((float)a, (float)b); + else return (CGFloat)fmax((double)a, (double)b); +} + +/*! Returns true if the given HFFPRanges are equal. */ +static inline BOOL HFFPRangeEqualsRange(HFFPRange a, HFFPRange b) { + return a.location == b.location && a.length == b.length; +} + +/*! copysign() for a CGFloat */ +static inline CGFloat HFCopysign(CGFloat a, CGFloat b) { +#if CGFLOAT_IS_DOUBLE + return copysign(a, b); +#else + return copysignf(a, b); +#endif +} + +/*! Atomically increments an NSUInteger, returning the new value. Optionally invokes a memory barrier. */ +static inline NSUInteger HFAtomicIncrement(volatile NSUInteger *ptr, BOOL barrier) { + return _Generic(ptr, + volatile unsigned *: (barrier ? OSAtomicIncrement32Barrier : OSAtomicIncrement32)((volatile int32_t *)ptr), +#if ULONG_MAX == UINT32_MAX + volatile unsigned long *: (barrier ? OSAtomicIncrement32Barrier : OSAtomicIncrement32)((volatile int32_t *)ptr), +#else + volatile unsigned long *: (barrier ? OSAtomicIncrement64Barrier : OSAtomicIncrement64)((volatile int64_t *)ptr), +#endif + volatile unsigned long long *: (barrier ? OSAtomicIncrement64Barrier : OSAtomicIncrement64)((volatile int64_t *)ptr)); +} + +/*! Atomically decrements an NSUInteger, returning the new value. Optionally invokes a memory barrier. */ +static inline NSUInteger HFAtomicDecrement(volatile NSUInteger *ptr, BOOL barrier) { + return _Generic(ptr, + volatile unsigned *: (barrier ? OSAtomicDecrement32Barrier : OSAtomicDecrement32)((volatile int32_t *)ptr), +#if ULONG_MAX == UINT32_MAX + volatile unsigned long *: (barrier ? OSAtomicDecrement32Barrier : OSAtomicDecrement32)((volatile int32_t *)ptr), +#else + volatile unsigned long *: (barrier ? OSAtomicDecrement64Barrier : OSAtomicDecrement64)((volatile int64_t *)ptr), +#endif + volatile unsigned long long *: (barrier ? OSAtomicDecrement64Barrier : OSAtomicDecrement64)((volatile int64_t *)ptr)); +} + +/*! Converts a long double to unsigned long long. Assumes that val is already an integer - use floorl or ceill */ +static inline unsigned long long HFFPToUL(long double val) { + assert(val >= 0); + assert(val <= ULLONG_MAX); + unsigned long long result = (unsigned long long)val; + assert((long double)result == val); + return result; +} + +/*! Converts an unsigned long long to a long double. */ +static inline long double HFULToFP(unsigned long long val) { + long double result = (long double)val; + assert(HFFPToUL(result) == val); + return result; +} + +/*! Convenience to return information about a CGAffineTransform for logging. */ +static inline NSString *HFDescribeAffineTransform(CGAffineTransform t) { + return [NSString stringWithFormat:@"%f %f 0\n%f %f 0\n%f %f 1", t.a, t.b, t.c, t.d, t.tx, t.ty]; +} + +/*! Returns 1 + floor(log base 10 of val). If val is 0, returns 1. */ +static inline NSUInteger HFCountDigitsBase10(unsigned long long val) { + const unsigned long long kValues[] = {0ULL, 9ULL, 99ULL, 999ULL, 9999ULL, 99999ULL, 999999ULL, 9999999ULL, 99999999ULL, 999999999ULL, 9999999999ULL, 99999999999ULL, 999999999999ULL, 9999999999999ULL, 99999999999999ULL, 999999999999999ULL, 9999999999999999ULL, 99999999999999999ULL, 999999999999999999ULL, 9999999999999999999ULL}; + NSUInteger low = 0, high = sizeof kValues / sizeof *kValues; + while (high > low) { + NSUInteger mid = (low + high)/2; //low + high cannot overflow + if (val > kValues[mid]) { + low = mid + 1; + } + else { + high = mid; + } + } + return MAX(1u, low); +} + +/*! Returns 1 + floor(log base 16 of val). If val is 0, returns 1. This works by computing the log base 2 based on the number of leading zeros, and then dividing by 4. */ +static inline NSUInteger HFCountDigitsBase16(unsigned long long val) { + /* __builtin_clzll doesn't like being passed 0 */ + if (val == 0) return 1; + + /* Compute the log base 2 */ + NSUInteger leadingZeros = (NSUInteger)__builtin_clzll(val); + NSUInteger logBase2 = (CHAR_BIT * sizeof val) - leadingZeros - 1; + return 1 + logBase2/4; +} + +/*! Returns YES if the given string encoding is a superset of ASCII. */ +BOOL HFStringEncodingIsSupersetOfASCII(NSStringEncoding encoding); + +/*! Returns the "granularity" of an encoding, in bytes. ASCII is 1, UTF-16 is 2, etc. Variable width encodings return the smallest (e.g. Shift-JIS returns 1). */ +uint8_t HFStringEncodingCharacterLength(NSStringEncoding encoding); + +/*! Converts an unsigned long long to NSUInteger. The unsigned long long should be no more than ULONG_MAX. */ +static inline NSUInteger ll2l(unsigned long long val) { assert(val <= ULONG_MAX); return (unsigned long)val; } + +/*! Converts an unsigned long long to uintptr_t. The unsigned long long should be no more than UINTPTR_MAX. */ +static inline uintptr_t ll2p(unsigned long long val) { assert(val <= UINTPTR_MAX); return (uintptr_t)val; } + +/*! Returns an unsigned long long, which must be no more than ULLONG_MAX, as an unsigned long. */ +static inline CGFloat ld2f(long double val) { +#if ! NDEBUG + if (isfinite(val)) { + assert(val <= CGFLOAT_MAX); + assert(val >= -CGFLOAT_MAX); + if ((val > 0 && val < CGFLOAT_MIN) || (val < 0 && val > -CGFLOAT_MIN)) { + NSLog(@"Warning - conversion of long double %Lf to CGFloat will result in the non-normal CGFloat %f", val, (CGFloat)val); + } + } +#endif + return (CGFloat)val; +} + +/*! Returns the quotient of a divided by b, rounding up, for unsigned long longs. Will not overflow. */ +static inline unsigned long long HFDivideULLRoundingUp(unsigned long long a, unsigned long long b) { + if (a == 0) return 0; + else return ((a - 1) / b) + 1; +} + +/*! Returns the quotient of a divided by b, rounding up, for NSUIntegers. Will not overflow. */ +static inline NSUInteger HFDivideULRoundingUp(NSUInteger a, NSUInteger b) { + if (a == 0) return 0; + else return ((a - 1) / b) + 1; +} + +/*! Draws a shadow. */ +void HFDrawShadow(CGContextRef context, NSRect rect, CGFloat size, NSRectEdge rectEdge, BOOL active, NSRect clip); + +/*! Registers a view to have the given notificationSEL invoked (taking the NSNotification object) when the window becomes or loses key. If appToo is YES, this also registers with NSApplication for Activate and Deactivate methods. */ +void HFRegisterViewForWindowAppearanceChanges(NSView *view, SEL notificationSEL, BOOL appToo); + +/*! Unregisters a view to have the given notificationSEL invoked when the window becomes or loses key. If appToo is YES, this also unregisters with NSApplication. */ +void HFUnregisterViewForWindowAppearanceChanges(NSView *view, BOOL appToo); + +/*! Returns a description of the given byte count (e.g. "24 kilobytes") */ +NSString *HFDescribeByteCount(unsigned long long count); + +/*! @brief An object wrapper for the HFRange type. + + A simple class responsible for holding an immutable HFRange as an object. Methods that logically work on multiple HFRanges usually take or return arrays of HFRangeWrappers. */ +@interface HFRangeWrapper : NSObject { + @public + HFRange range; +} + +/*! Returns the HFRange for this HFRangeWrapper. */ +- (HFRange)HFRange; + +/*! Creates an autoreleased HFRangeWrapper for this HFRange. */ ++ (HFRangeWrapper *)withRange:(HFRange)range; + +/*! Creates an NSArray of HFRangeWrappers for this HFRange. */ ++ (NSArray *)withRanges:(const HFRange *)ranges count:(NSUInteger)count; + +/*! Given an NSArray of HFRangeWrappers, get all of the HFRanges into a C array. */ ++ (void)getRanges:(HFRange *)ranges fromArray:(NSArray *)array; + +/*! Given an array of HFRangeWrappers, returns a "cleaned up" array of equivalent ranges. This new array represents the same indexes, but overlapping ranges will have been merged, and the ranges will be sorted in ascending order. */ ++ (NSArray *)organizeAndMergeRanges:(NSArray *)inputRanges; + +@end + +/*! @brief A set of HFRanges. HFRangeSet takes the interpetation that all zero-length ranges are identical. + + Essentially, a mutable array of ranges that is maintained to be sorted and minimized (i.e. merged with overlapping neighbors). + + TODO: The HexFiend codebase currently uses arrays of HFRangeWrappers that have been run through organizeAndMergeRanges:, and not HFRangeSet. The advantage of HFRangeSet is that the sorting & merging is implied by the type, instead of just tacitly assumed. This should lead to less confusion and fewer extra applications of organizeAndMergeRanges. + + TODO: HFRangeSet needs to be tested! I guarantee it has bugs! (Which doesn't matter right now because it's all dead code...) + */ +@interface HFRangeSet : NSObject { + @private + CFMutableArrayRef array; +} + +/*! Create a range set with just one range. */ ++ (HFRangeSet *)withRange:(HFRange)range; + +/*! Create a range set with a C array of ranges. No prior sorting is necessary. */ ++ (HFRangeSet *)withRanges:(const HFRange *)ranges count:(NSUInteger)count; + +/*! Create a range set with an array of HFRangeWrappers. No prior sorting is necessary. */ ++ (HFRangeSet *)withRangeWrappers:(NSArray *)ranges; + +/*! Create a range set as a copy of another. */ ++ (HFRangeSet *)withRangeSet:(HFRangeSet *)rangeSet; + +/*! Equivalent to HFRangeSet *x = [HFRangeSet withRange:range]; [x removeRange:rangeSet]; */ ++ (HFRangeSet *)complementOfRangeSet:(HFRangeSet *)rangeSet inRange:(HFRange)range; + +- (void)addRange:(HFRange)range; /*!< Union with range */ +- (void)removeRange:(HFRange)range; /*!< Subtract range */ +- (void)clipToRange:(HFRange)range; /*!< Intersect with range */ +- (void)toggleRange:(HFRange)range; /*!< Symmetric difference with range */ + +- (void)addRangeSet:(HFRangeSet *)rangeSet; /*!< Union with range set */ +- (void)removeRangeSet:(HFRangeSet *)rangeSet; /*!< Subtract range set */ +- (void)clipToRangeSet:(HFRangeSet *)rangeSet; /*!< Intersect with range set */ +- (void)toggleRangeSet:(HFRangeSet *)rangeSet; /*!< Symmetric difference with range set */ + + +- (BOOL)isEqualToRangeSet:(HFRangeSet *)rangeSet; /*!< Test if two range sets are equivalent. */ +- (BOOL)isEmpty; /*!< Test if range set is empty. */ + +- (BOOL)containsAllRange:(HFRange)range; /*!< Check if the range set covers all of a range. Always true if 'range' is zero length. */ +- (BOOL)overlapsAnyRange:(HFRange)range; /*!< Check if the range set covers any of a range. Never true if 'range' is zero length. */ +- (BOOL)containsAllRangeSet:(HFRangeSet *)rangeSet; /*!< Check if this range is a superset of another. */ +- (BOOL)overlapsAnyRangeSet:(HFRangeSet *)rangeSet; /*!< Check if this range has a nonempty intersection with another. */ + +- (HFRange)spanningRange; /*!< Return a single range that covers the entire range set */ + +- (void)assertIntegrity; + +@end + +#ifndef NDEBUG +void HFStartTiming(const char *name); +void HFStopTiming(void); +#endif diff --git a/HexFiend/HFFunctions.m b/HexFiend/HFFunctions.m new file mode 100644 index 00000000..b41a12ff --- /dev/null +++ b/HexFiend/HFFunctions.m @@ -0,0 +1,1172 @@ +#import +#import + +#import "HFFunctions_Private.h" + +#ifndef NDEBUG +//#define USE_CHUD 1 +#endif + +#ifndef USE_CHUD +#define USE_CHUD 0 +#endif + +#if USE_CHUD +#import +#endif + +NSImage *HFImageNamed(NSString *name) { + HFASSERT(name != NULL); + NSImage *image = [NSImage imageNamed:name]; + if (image == NULL) { + NSString *imagePath = [[NSBundle bundleForClass:[HFController class]] pathForResource:name ofType:@"tiff"]; + if (! imagePath) { + NSLog(@"Unable to find image named %@.tiff", name); + } + else { + image = [[NSImage alloc] initByReferencingFile:imagePath]; + if (image == nil || ! [image isValid]) { + NSLog(@"Couldn't load image at path %@", imagePath); + [image release]; + image = nil; + } + else { + [image setName:name]; + } + } + } + return image; +} + +@implementation HFRangeWrapper + +- (HFRange)HFRange { return range; } + ++ (HFRangeWrapper *)withRange:(HFRange)range { + HFRangeWrapper *result = [[self alloc] init]; + result->range = range; + return [result autorelease]; +} + ++ (NSArray *)withRanges:(const HFRange *)ranges count:(NSUInteger)count { + HFASSERT(count == 0 || ranges != NULL); + NSUInteger i; + NSArray *result; + NEW_ARRAY(HFRangeWrapper *, wrappers, count); + for (i=0; i < count; i++) wrappers[i] = [self withRange:ranges[i]]; + result = [NSArray arrayWithObjects:wrappers count:count]; + FREE_ARRAY(wrappers); + return result; +} + +- (BOOL)isEqual:(id)obj { + if (! [obj isKindOfClass:[HFRangeWrapper class]]) return NO; + else return HFRangeEqualsRange(range, [obj HFRange]); +} + +- (NSUInteger)hash { + return (NSUInteger)(range.location + (range.length << 16)); +} + +- (id)copyWithZone:(NSZone *)zone { + USE(zone); + return [self retain]; +} + +- (NSString *)description { + return HFRangeToString(range); +} + +static int hfrange_compare(const void *ap, const void *bp) { + const HFRange *a = ap; + const HFRange *b = bp; + if (a->location < b->location) return -1; + else if (a->location > b->location) return 1; + else if (a->length < b->length) return -1; + else if (a->length > b->length) return 1; + else return 0; +} + ++ (NSArray *)organizeAndMergeRanges:(NSArray *)inputRanges { + HFASSERT(inputRanges != NULL); + NSUInteger leading = 0, trailing = 0, length = [inputRanges count]; + if (length == 0) return @[]; + else if (length == 1) return [NSArray arrayWithArray:inputRanges]; + + NEW_ARRAY(HFRange, ranges, length); + [self getRanges:ranges fromArray:inputRanges]; + qsort(ranges, length, sizeof ranges[0], hfrange_compare); + leading = 0; + while (leading < length) { + leading++; + if (leading < length) { + HFRange leadRange = ranges[leading], trailRange = ranges[trailing]; + if (HFIntersectsRange(leadRange, trailRange) || HFMaxRange(leadRange) == trailRange.location || HFMaxRange(trailRange) == leadRange.location) { + ranges[trailing] = HFUnionRange(leadRange, trailRange); + } + else { + trailing++; + ranges[trailing] = ranges[leading]; + } + } + } + NSArray *result = [HFRangeWrapper withRanges:ranges count:trailing + 1]; + FREE_ARRAY(ranges); + return result; +} + ++ (void)getRanges:(HFRange *)ranges fromArray:(NSArray *)array { + HFASSERT(ranges != NULL || [array count] == 0); + if (ranges) { + FOREACH(HFRangeWrapper*, wrapper, array) *ranges++ = [wrapper HFRange]; + } +} + +@end + +@implementation HFRangeSet +// HFRangeSet is implemented as a CFMutableArray of uintptr_t "fenceposts". The array +// is even in length, sorted, duplicate free, and considered to include the ranges +// [array[0], array[1]), [array[2], array[3]), ..., [array[2n], array[2n+1]) + +CFComparisonResult uintptrComparator(const void *val1, const void *val2, void *context) { + (void)context; + uintptr_t a = (uintptr_t)val1; + uintptr_t b = (uintptr_t)val2; + if(a < b) return kCFCompareLessThan; + if(a > b) return kCFCompareGreaterThan; + return kCFCompareEqualTo; +} + +static void HFRangeSetAddRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + + const void *x[2] = { (void*)a, (void*)b }; + if(idxa >= count) { + CFArrayReplaceValues(array, CFRangeMake(count, 0), x, 2); + return; + } + if(idxb == 0) { + CFArrayReplaceValues(array, CFRangeMake(0, 0), x, 2); + return; + } + + // Clear fenceposts strictly between 'a' and 'b', and then possibly + // add 'a' or 'b' as fenceposts. + CFIndex cutloc = (uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxa+1 : idxa; + CFIndex cutlen = idxb - cutloc; + + bool inca = cutloc % 2 == 0; // Include 'a' if it would begin an included range + bool incb = (count - cutlen + inca) % 2 == 1; // The set must be even, which tells us about 'b'. + + CFArrayReplaceValues(array, CFRangeMake(cutloc, cutlen), x+inca, inca+incb); + assert(CFArrayGetCount(array) % 2 == 0); +} + +static void HFRangeSetRemoveRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) return; + + // Remove fenceposts strictly between 'a' and 'b', and then possibly + // add 'a' or 'b' as fenceposts. + CFIndex cutloc = (uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxa+1 : idxa; + CFIndex cutlen = idxb - cutloc; + + bool inca = cutloc % 2 == 1; // Include 'a' if it would end an included range + bool incb = (count - cutlen + inca) % 2 == 1; // The set must be even, which tells us about 'b'. + + const void *x[2] = { (void*)a, (void*)b }; + CFArrayReplaceValues(array, CFRangeMake(cutloc, cutlen), x+inca, inca+incb); + assert(CFArrayGetCount(array) % 2 == 0); +} + +static void HFRangeSetToggleRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + + // In the fencepost representation, simply toggling the existence of + // fenceposts 'a' and 'b' achieves symmetric difference. + + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + if((uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a) { + CFArrayRemoveValueAtIndex(array, idxa); + } else { + CFArrayInsertValueAtIndex(array, idxa, (void*)a); + } + + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if((uintptr_t)CFArrayGetValueAtIndex(array, idxb) == b) { + CFArrayRemoveValueAtIndex(array, idxb); + } else { + CFArrayInsertValueAtIndex(array, idxb, (void*)b); + } + + assert(CFArrayGetCount(array) % 2 == 0); +} + +static BOOL HFRangeSetContainsAllRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) return NO; + + // Optimization: if the indexes are far enough apart, then obviouly there's a gap. + if(idxb - idxa >= 2) return NO; + + // The first fencepost >= 'b' must end an include range, a must be in the same range. + return idxb%2 == 1 && idxa == ((uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxb-1 : idxb); +} + +static BOOL HFRangeSetOverlapsAnyRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) return NO; + + // Optimization: if the indexes are far enough apart, then obviouly there's overlap. + if(idxb - idxa >= 2) return YES; + + if((uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a) { + // 'a' is an included fencepost, or instead 'b' makes it past an included fencepost. + return idxa % 2 == 0 || b > (uintptr_t)CFArrayGetValueAtIndex(array, idxa+1); + } else { + // 'a' lies in an included range, or instead 'b' makes it past an included fencepost. + return idxa % 2 == 1 || b > (uintptr_t)CFArrayGetValueAtIndex(array, idxa); + } +} + +- (instancetype)init { + if(!(self = [super init])) return nil; + array = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + return self; +} + +- (void)dealloc { + CFRelease(array); + [super dealloc]; +} + ++ (HFRangeSet *)withRange:(HFRange)range { + HFRangeSet *newSet = [[[HFRangeSet alloc] init] autorelease]; + if(range.length > 0) { + CFArrayAppendValue(newSet->array, (void*)ll2p(range.location)); + CFArrayAppendValue(newSet->array, (void*)ll2p(HFMaxRange(range))); + } + return newSet; +} + ++ (HFRangeSet *)withRanges:(const HFRange *)ranges count:(NSUInteger)count { + // FIXME: Stub. Don't rely on the thing we're replacing! + return [HFRangeSet withRangeWrappers:[HFRangeWrapper withRanges:ranges count:count]]; +} + ++ (HFRangeSet *)withRangeWrappers:(NSArray *)ranges { + HFRangeSet *newSet = [[[HFRangeSet alloc] init] autorelease]; + FOREACH(HFRangeWrapper *, wrapper, [HFRangeWrapper organizeAndMergeRanges:ranges]) { + if(wrapper->range.length > 0) { + CFArrayAppendValue(newSet->array, (void*)ll2p(wrapper->range.location)); + CFArrayAppendValue(newSet->array, (void*)ll2p(HFMaxRange(wrapper->range))); + } + } + return newSet; +} + ++ (HFRangeSet *)withRangeSet:(HFRangeSet *)rangeSet { + return [[rangeSet copy] autorelease]; +} + ++ (HFRangeSet *)complementOfRangeSet:(HFRangeSet *)rangeSet inRange:(HFRange)range { + if(range.length <= 0) { + // Complement in empty is... empty! + return [HFRangeSet withRange:HFZeroRange]; + } + uintptr_t a = ll2p(range.location); + uintptr_t b = ll2p(HFMaxRange(range)); + CFIndex count = CFArrayGetCount(rangeSet->array); + CFIndex idxa = CFArrayBSearchValues(rangeSet->array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(rangeSet->array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) + return [HFRangeSet withRange:range]; + + // Alright, the trivial responses are past. We'll need to build a new set. + // Given the fencepost representation of sets, we can efficiently produce an + // inverted set by just copying the fenceposts between 'a' and 'b', and then + // maybe including 'a' and 'b'. + + HFRangeSet *newSet = [[[HFRangeSet alloc] init] autorelease]; + + // newSet must contain all the fenceposts strictly between 'a' and 'b' + CFIndex copyloc = (uintptr_t)CFArrayGetValueAtIndex(rangeSet->array, idxa) == a ? idxa+1 : idxa; + CFIndex copylen = idxb - copyloc; + + // Include 'a' if it's needed to invert the parity of the copy. + if(copyloc % 2 == 0) CFArrayAppendValue(newSet->array, &a); + + CFArrayAppendArray(newSet->array, rangeSet->array, CFRangeMake(copyloc, copylen)); + + // Include 'b' if it's needed to close off the set. + if(CFArrayGetCount(newSet->array) % 2 == 1) + CFArrayAppendValue(newSet->array, &b); + + assert(CFArrayGetCount(newSet->array) % 2 == 0); + return newSet; +} + + +- (void)addRange:(HFRange)range { + if(range.length == 0) return; + HFRangeSetAddRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} +- (void)removeRange:(HFRange)range { + if(range.length == 0) return; + HFRangeSetRemoveRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} +- (void)toggleRange:(HFRange)range { + if(range.length == 0) return; + HFRangeSetToggleRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} + +- (void)clipToRange:(HFRange)range { + if(range.length <= 0) { + CFArrayRemoveAllValues(array); + return; + } + uintptr_t a = ll2p(range.location); + uintptr_t b = ll2p(HFMaxRange(range)); + CFIndex count = CFArrayGetCount(array); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) { + CFArrayRemoveAllValues(array); + return; + } + + // Keep only fenceposts strictly between 'a' and 'b', and then possibly + // add 'a' or 'b' as fenceposts. + CFIndex keeploc = (uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxa+1 : idxa; + CFIndex keeplen = idxb - keeploc; + + // Include 'a' if it's needed to keep the parity straight. + if(keeploc % 2 == 1) { + keeploc--; keeplen++; + CFArraySetValueAtIndex(array, keeploc, (void*)a); + } + + if(keeploc > 0) + CFArrayReplaceValues(array, CFRangeMake(0, keeploc), NULL, 0); + if(keeploc+keeplen < count) + CFArrayReplaceValues(array, CFRangeMake(0, keeplen), NULL, 0); + + // Include 'b' if it's needed to keep the length even. + if(keeplen % 2 == 1) { + CFArrayAppendValue(array, (void*)b); + } + + assert(CFArrayGetCount(array) % 2 == 0); +} + + +- (void)addRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + HFRangeSetAddRange(array, (uintptr_t)CFArrayGetValueAtIndex(a, i2), (uintptr_t)CFArrayGetValueAtIndex(a, i2+1)); + } +} +- (void)removeRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + HFRangeSetRemoveRange(array, (uintptr_t)CFArrayGetValueAtIndex(a, i2), (uintptr_t)CFArrayGetValueAtIndex(a, i2+1)); + } +} +- (void)toggleRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + HFRangeSetToggleRange(array, (uintptr_t)CFArrayGetValueAtIndex(a, i2), (uintptr_t)CFArrayGetValueAtIndex(a, i2+1)); + } +} + +- (void)clipToRangeSet:(HFRangeSet *)rangeSet { + HFRange span = [rangeSet spanningRange]; + [self clipToRange:span]; + [self removeRangeSet:[HFRangeSet complementOfRangeSet:rangeSet inRange:span]]; +} + +- (BOOL)isEqualToRangeSet:(HFRangeSet *)rangeSet { + // Because our arrays are fully normalized, this just checks for array equality. + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + if(c != CFArrayGetCount(array)) + return NO; + + // Optimization: For long arrays, check the last few first, + // since appending to ranges is probably a common usage pattern. + const CFIndex opt_end = 10; + if(c > 2*opt_end) { + for(CFIndex i = c - 2*opt_end; i < c; i++) { + if(CFArrayGetValueAtIndex(a, i) != CFArrayGetValueAtIndex(array, i)) + return NO; + } + c -= 2*opt_end; + } + + for(CFIndex i = 0; i < c; i++) { + if(CFArrayGetValueAtIndex(a, i) != CFArrayGetValueAtIndex(array, i)) + return NO; + } + + return YES; +} + +- (BOOL)isEmpty { + return CFArrayGetCount(array) == 0; +} + +- (BOOL)containsAllRange:(HFRange)range { + if(range.length == 0) return YES; + return HFRangeSetContainsAllRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} + +- (BOOL)overlapsAnyRange:(HFRange)range { + if(range.length == 0) return NO; + return HFRangeSetOverlapsAnyRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} + +- (BOOL)containsAllRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + + // Optimization: check if containment is possible. + if(!HFRangeIsSubrangeOfRange([rangeSet spanningRange], [self spanningRange])) { + return NO; + } + + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + uintptr_t x = (uintptr_t)CFArrayGetValueAtIndex(a, i2); + uintptr_t y = (uintptr_t)CFArrayGetValueAtIndex(a, i2+1); + if(!HFRangeSetContainsAllRange(array, x, y)) return NO; + } + return YES; +} + +- (BOOL)overlapsAnyRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + + // Optimization: check if overlap is possible. + if(!HFIntersectsRange([rangeSet spanningRange], [self spanningRange])) { + return NO; + } + + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + uintptr_t x = (uintptr_t)CFArrayGetValueAtIndex(a, i2); + uintptr_t y = (uintptr_t)CFArrayGetValueAtIndex(a, i2+1); + if(!HFRangeSetOverlapsAnyRange(array, x, y)) return YES; + } + return NO; +} + + +- (HFRange)spanningRange { + CFIndex count = CFArrayGetCount(array); + if(count == 0) return HFZeroRange; + + uintptr_t a = (uintptr_t)CFArrayGetValueAtIndex(array, 0); + uintptr_t b = (uintptr_t)CFArrayGetValueAtIndex(array, count-2) + (uintptr_t)CFArrayGetValueAtIndex(array, count-1); + + return HFRangeMake(a, b-a); +} + +- (void)assertIntegrity { + CFIndex count = CFArrayGetCount(array); + HFASSERT(count % 2 == 0); + if(count == 0) return; + + uintptr_t prev = (uintptr_t)CFArrayGetValueAtIndex(array, 0); + for(CFIndex i = 1; i < count; i++) { + uintptr_t val = (uintptr_t)CFArrayGetValueAtIndex(array, i); + HFASSERT(val > prev); + prev = val; + } +} + +- (BOOL)isEqual:(id)object { + if(![object isKindOfClass:[HFRangeSet class]]) + return false; + return [self isEqualToRangeSet:object]; +} + +- (NSUInteger)hash { + CFIndex count = CFArrayGetCount(array); + NSUInteger x = 0; + for(CFIndex i2 = 0; i2 < count; i2 += 2) { + uintptr_t a = (uintptr_t)CFArrayGetValueAtIndex(array, i2); + uintptr_t b = (uintptr_t)CFArrayGetValueAtIndex(array, i2+1); +#if 6364136223846793005 < NSUIntegerMax + x = (6364136223846793005 * (uint64_t)x + a); +#else + x = (NSUInteger)(1103515245 * (uint64_t)x + a); +#endif + x ^= (NSUInteger)b; + } + return x; +} + +- (id)copyWithZone:(NSZone *)zone { + HFRangeSet *newSet = [[HFRangeSet allocWithZone:zone] init]; + CFRelease(newSet->array); + newSet->array = (CFMutableArrayRef)[[NSMutableArray allocWithZone:zone] initWithArray:(NSArray*)array copyItems:NO]; + return newSet; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + NSUInteger count = CFArrayGetCount(array); + NEW_ARRAY(uint64_t, values, count); + + // Fill array with 64-bit, little endian bytes. + if(sizeof(const void *) == sizeof(uint64_t)) { + // Hooray, we can just use CFArrayGetValues + CFArrayGetValues(array, CFRangeMake(0, count), (const void **)&values); +#if __LITTLE_ENDIAN__ +#else + // Boo, we have to swap everything. + for(NSUInteger i = 0; i < count; i++) { + values[i] = CFSwapInt64HostToLittle(values[i]); + } +#endif + } else { + // Boo, we have to iterate through the array. + NSUInteger i = 0; + FOREACH(id, val, (NSArray*)array) { + values[i++] = CFSwapInt64HostToLittle((uint64_t)(const void *)val); + } + } + [aCoder encodeBytes:values length:count * sizeof(*values)]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if(!(self = [super init])) return nil; + + NSUInteger count; + uint64_t *values = [aDecoder decodeBytesWithReturnedLength:&count]; + array = CFArrayCreateMutable(kCFAllocatorDefault, count+1, NULL); + + for(NSUInteger i = 0; i < count; i++) { + uint64_t x = CFSwapInt64LittleToHost(values[i]); + if(x > UINTPTR_MAX) + goto fail; + CFArrayAppendValue(array, (const void *)(uintptr_t)x); + } + if(CFArrayGetCount(array)%2 != 0) + goto fail; + return self; + +fail: + CFRelease(array); + [super release]; + return nil; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { + NSUInteger base = state->state; + NSUInteger length = CFArrayGetCount(array)/2; + NSUInteger i = 0; + + while(i < len && base + i < length) { + uintptr_t a = (uintptr_t)CFArrayGetValueAtIndex(array, 2*i); + uintptr_t b = (uintptr_t)CFArrayGetValueAtIndex(array, 2*i+1); + stackbuf[i] = [HFRangeWrapper withRange:HFRangeMake(a, b-a)]; + } + + state->state = base + i; + state->itemsPtr = stackbuf; + state->mutationsPtr = &state->extra[0]; // Use simple mutation checking. + state->extra[0] = length; + + return i; +} + +@end + + +BOOL HFStringEncodingIsSupersetOfASCII(NSStringEncoding encoding) { + switch (CFStringConvertNSStringEncodingToEncoding(encoding)) { + case kCFStringEncodingMacRoman: return YES; + case kCFStringEncodingWindowsLatin1: return YES; + case kCFStringEncodingISOLatin1: return YES; + case kCFStringEncodingNextStepLatin: return YES; + case kCFStringEncodingASCII: return YES; + case kCFStringEncodingUnicode: return NO; + case kCFStringEncodingUTF8: return YES; + case kCFStringEncodingNonLossyASCII: return NO; +// case kCFStringEncodingUTF16: return NO; + case kCFStringEncodingUTF16BE: return NO; + case kCFStringEncodingUTF16LE: return NO; + case kCFStringEncodingUTF32: return NO; + case kCFStringEncodingUTF32BE: return NO; + case kCFStringEncodingUTF32LE: return NO; + case kCFStringEncodingMacJapanese: return NO; + case kCFStringEncodingMacChineseTrad: return YES; + case kCFStringEncodingMacKorean: return YES; + case kCFStringEncodingMacArabic: return NO; + case kCFStringEncodingMacHebrew: return NO; + case kCFStringEncodingMacGreek: return YES; + case kCFStringEncodingMacCyrillic: return YES; + case kCFStringEncodingMacDevanagari: return YES; + case kCFStringEncodingMacGurmukhi: return YES; + case kCFStringEncodingMacGujarati: return YES; + case kCFStringEncodingMacOriya: return YES; + case kCFStringEncodingMacBengali: return YES; + case kCFStringEncodingMacTamil: return YES; + case kCFStringEncodingMacTelugu: return YES; + case kCFStringEncodingMacKannada: return YES; + case kCFStringEncodingMacMalayalam: return YES; + case kCFStringEncodingMacSinhalese: return YES; + case kCFStringEncodingMacBurmese: return YES; + case kCFStringEncodingMacKhmer: return YES; + case kCFStringEncodingMacThai: return YES; + case kCFStringEncodingMacLaotian: return YES; + case kCFStringEncodingMacGeorgian: return YES; + case kCFStringEncodingMacArmenian: return YES; + case kCFStringEncodingMacChineseSimp: return YES; + case kCFStringEncodingMacTibetan: return YES; + case kCFStringEncodingMacMongolian: return YES; + case kCFStringEncodingMacEthiopic: return YES; + case kCFStringEncodingMacCentralEurRoman: return YES; + case kCFStringEncodingMacVietnamese: return YES; + case kCFStringEncodingMacExtArabic: return YES; + case kCFStringEncodingMacSymbol: return NO; + case kCFStringEncodingMacDingbats: return NO; + case kCFStringEncodingMacTurkish: return YES; + case kCFStringEncodingMacCroatian: return YES; + case kCFStringEncodingMacIcelandic: return YES; + case kCFStringEncodingMacRomanian: return YES; + case kCFStringEncodingMacCeltic: return YES; + case kCFStringEncodingMacGaelic: return YES; + case kCFStringEncodingMacFarsi: return YES; + case kCFStringEncodingMacUkrainian: return NO; + case kCFStringEncodingMacInuit: return YES; + case kCFStringEncodingMacVT100: return YES; + case kCFStringEncodingMacHFS: return YES; + case kCFStringEncodingISOLatin2: return YES; + case kCFStringEncodingISOLatin3: return YES; + case kCFStringEncodingISOLatin4: return YES; + case kCFStringEncodingISOLatinCyrillic: return YES; + case kCFStringEncodingISOLatinArabic: return NO; + case kCFStringEncodingISOLatinGreek: return YES; + case kCFStringEncodingISOLatinHebrew: return YES; + case kCFStringEncodingISOLatin5: return YES; + case kCFStringEncodingISOLatin6: return YES; + case kCFStringEncodingISOLatinThai: return YES; + case kCFStringEncodingISOLatin7: return YES; + case kCFStringEncodingISOLatin8: return YES; + case kCFStringEncodingISOLatin9: return YES; + case kCFStringEncodingISOLatin10: return YES; + case kCFStringEncodingDOSLatinUS: return YES; + case kCFStringEncodingDOSGreek: return YES; + case kCFStringEncodingDOSBalticRim: return YES; + case kCFStringEncodingDOSLatin1: return YES; + case kCFStringEncodingDOSGreek1: return YES; + case kCFStringEncodingDOSLatin2: return YES; + case kCFStringEncodingDOSCyrillic: return YES; + case kCFStringEncodingDOSTurkish: return YES; + case kCFStringEncodingDOSPortuguese: return YES; + case kCFStringEncodingDOSIcelandic: return YES; + case kCFStringEncodingDOSHebrew: return YES; + case kCFStringEncodingDOSCanadianFrench: return YES; + case kCFStringEncodingDOSArabic: return YES; + case kCFStringEncodingDOSNordic: return YES; + case kCFStringEncodingDOSRussian: return YES; + case kCFStringEncodingDOSGreek2: return YES; + case kCFStringEncodingDOSThai: return YES; + case kCFStringEncodingDOSJapanese: return YES; + case kCFStringEncodingDOSChineseSimplif: return YES; + case kCFStringEncodingDOSKorean: return YES; + case kCFStringEncodingDOSChineseTrad: return YES; + case kCFStringEncodingWindowsLatin2: return YES; + case kCFStringEncodingWindowsCyrillic: return YES; + case kCFStringEncodingWindowsGreek: return YES; + case kCFStringEncodingWindowsLatin5: return YES; + case kCFStringEncodingWindowsHebrew: return YES; + case kCFStringEncodingWindowsArabic: return YES; + case kCFStringEncodingWindowsBalticRim: return YES; + case kCFStringEncodingWindowsVietnamese: return YES; + case kCFStringEncodingWindowsKoreanJohab: return YES; + case kCFStringEncodingANSEL: return NO; + case kCFStringEncodingJIS_X0201_76: return NO; + case kCFStringEncodingJIS_X0208_83: return NO; + case kCFStringEncodingJIS_X0208_90: return NO; + case kCFStringEncodingJIS_X0212_90: return NO; + case kCFStringEncodingJIS_C6226_78: return NO; + case 0x0628/*kCFStringEncodingShiftJIS_X0213*/: return NO; + case kCFStringEncodingShiftJIS_X0213_MenKuTen: return NO; + case kCFStringEncodingGB_2312_80: return NO; + case kCFStringEncodingGBK_95: return NO; + case kCFStringEncodingGB_18030_2000: return NO; + case kCFStringEncodingKSC_5601_87: return NO; + case kCFStringEncodingKSC_5601_92_Johab: return NO; + case kCFStringEncodingCNS_11643_92_P1: return NO; + case kCFStringEncodingCNS_11643_92_P2: return NO; + case kCFStringEncodingCNS_11643_92_P3: return NO; + case kCFStringEncodingISO_2022_JP: return NO; + case kCFStringEncodingISO_2022_JP_2: return NO; + case kCFStringEncodingISO_2022_JP_1: return NO; + case kCFStringEncodingISO_2022_JP_3: return NO; + case kCFStringEncodingISO_2022_CN: return NO; + case kCFStringEncodingISO_2022_CN_EXT: return NO; + case kCFStringEncodingISO_2022_KR: return NO; + case kCFStringEncodingEUC_JP: return YES; + case kCFStringEncodingEUC_CN: return YES; + case kCFStringEncodingEUC_TW: return YES; + case kCFStringEncodingEUC_KR: return YES; + case kCFStringEncodingShiftJIS: return NO; + case kCFStringEncodingKOI8_R: return YES; + case kCFStringEncodingBig5: return YES; + case kCFStringEncodingMacRomanLatin1: return YES; + case kCFStringEncodingHZ_GB_2312: return NO; + case kCFStringEncodingBig5_HKSCS_1999: return YES; + case kCFStringEncodingVISCII: return YES; // though not quite + case kCFStringEncodingKOI8_U: return YES; + case kCFStringEncodingBig5_E: return YES; + case kCFStringEncodingNextStepJapanese: return YES; + case kCFStringEncodingEBCDIC_US: return NO; + case kCFStringEncodingEBCDIC_CP037: return NO; + default: + NSLog(@"Unknown string encoding %lu in %s", (unsigned long)encoding, __FUNCTION__); + return NO; + } +} + +uint8_t HFStringEncodingCharacterLength(NSStringEncoding encoding) { + switch (CFStringConvertNSStringEncodingToEncoding(encoding)) { + case kCFStringEncodingMacRoman: return 1; + case kCFStringEncodingWindowsLatin1: return 1; + case kCFStringEncodingISOLatin1: return 1; + case kCFStringEncodingNextStepLatin: return 1; + case kCFStringEncodingASCII: return 1; + case kCFStringEncodingUnicode: return 2; + case kCFStringEncodingUTF8: return 1; + case kCFStringEncodingNonLossyASCII: return 1; + // case kCFStringEncodingUTF16: return 2; + case kCFStringEncodingUTF16BE: return 2; + case kCFStringEncodingUTF16LE: return 2; + case kCFStringEncodingUTF32: return 4; + case kCFStringEncodingUTF32BE: return 4; + case kCFStringEncodingUTF32LE: return 4; + case kCFStringEncodingMacJapanese: return 1; + case kCFStringEncodingMacChineseTrad: return 1; // ?? + case kCFStringEncodingMacKorean: return 1; + case kCFStringEncodingMacArabic: return 1; + case kCFStringEncodingMacHebrew: return 1; + case kCFStringEncodingMacGreek: return 1; + case kCFStringEncodingMacCyrillic: return 1; + case kCFStringEncodingMacDevanagari: return 1; + case kCFStringEncodingMacGurmukhi: return 1; + case kCFStringEncodingMacGujarati: return 1; + case kCFStringEncodingMacOriya: return 1; + case kCFStringEncodingMacBengali: return 1; + case kCFStringEncodingMacTamil: return 1; + case kCFStringEncodingMacTelugu: return 1; + case kCFStringEncodingMacKannada: return 1; + case kCFStringEncodingMacMalayalam: return 1; + case kCFStringEncodingMacSinhalese: return 1; + case kCFStringEncodingMacBurmese: return 1; + case kCFStringEncodingMacKhmer: return 1; + case kCFStringEncodingMacThai: return 1; + case kCFStringEncodingMacLaotian: return 1; + case kCFStringEncodingMacGeorgian: return 1; + case kCFStringEncodingMacArmenian: return 1; + case kCFStringEncodingMacChineseSimp: return 1; + case kCFStringEncodingMacTibetan: return 1; + case kCFStringEncodingMacMongolian: return 1; + case kCFStringEncodingMacEthiopic: return 1; + case kCFStringEncodingMacCentralEurRoman: return 1; + case kCFStringEncodingMacVietnamese: return 1; + case kCFStringEncodingMacExtArabic: return 1; + case kCFStringEncodingMacSymbol: return 1; + case kCFStringEncodingMacDingbats: return 1; + case kCFStringEncodingMacTurkish: return 1; + case kCFStringEncodingMacCroatian: return 1; + case kCFStringEncodingMacIcelandic: return 1; + case kCFStringEncodingMacRomanian: return 1; + case kCFStringEncodingMacCeltic: return 1; + case kCFStringEncodingMacGaelic: return 1; + case kCFStringEncodingMacFarsi: return 1; + case kCFStringEncodingMacUkrainian: return 1; + case kCFStringEncodingMacInuit: return 1; + case kCFStringEncodingMacVT100: return 1; + case kCFStringEncodingMacHFS: return 1; + case kCFStringEncodingISOLatin2: return 1; + case kCFStringEncodingISOLatin3: return 1; + case kCFStringEncodingISOLatin4: return 1; + case kCFStringEncodingISOLatinCyrillic: return 1; + case kCFStringEncodingISOLatinArabic: return 1; + case kCFStringEncodingISOLatinGreek: return 1; + case kCFStringEncodingISOLatinHebrew: return 1; + case kCFStringEncodingISOLatin5: return 1; + case kCFStringEncodingISOLatin6: return 1; + case kCFStringEncodingISOLatinThai: return 1; + case kCFStringEncodingISOLatin7: return 1; + case kCFStringEncodingISOLatin8: return 1; + case kCFStringEncodingISOLatin9: return 1; + case kCFStringEncodingISOLatin10: return 1; + case kCFStringEncodingDOSLatinUS: return 1; + case kCFStringEncodingDOSGreek: return 1; + case kCFStringEncodingDOSBalticRim: return 1; + case kCFStringEncodingDOSLatin1: return 1; + case kCFStringEncodingDOSGreek1: return 1; + case kCFStringEncodingDOSLatin2: return 1; + case kCFStringEncodingDOSCyrillic: return 1; + case kCFStringEncodingDOSTurkish: return 1; + case kCFStringEncodingDOSPortuguese: return 1; + case kCFStringEncodingDOSIcelandic: return 1; + case kCFStringEncodingDOSHebrew: return 1; + case kCFStringEncodingDOSCanadianFrench: return 1; + case kCFStringEncodingDOSArabic: return 1; + case kCFStringEncodingDOSNordic: return 1; + case kCFStringEncodingDOSRussian: return 1; + case kCFStringEncodingDOSGreek2: return 1; + case kCFStringEncodingDOSThai: return 1; + case kCFStringEncodingDOSJapanese: return 1; + case kCFStringEncodingDOSChineseSimplif: return 1; + case kCFStringEncodingDOSKorean: return 1; + case kCFStringEncodingDOSChineseTrad: return 1; + case kCFStringEncodingWindowsLatin2: return 1; + case kCFStringEncodingWindowsCyrillic: return 1; + case kCFStringEncodingWindowsGreek: return 1; + case kCFStringEncodingWindowsLatin5: return 1; + case kCFStringEncodingWindowsHebrew: return 1; + case kCFStringEncodingWindowsArabic: return 1; + case kCFStringEncodingWindowsBalticRim: return 1; + case kCFStringEncodingWindowsVietnamese: return 1; + case kCFStringEncodingWindowsKoreanJohab: return 1; + case kCFStringEncodingANSEL: return 1; + case kCFStringEncodingJIS_X0201_76: return 1; + case kCFStringEncodingJIS_X0208_83: return 1; + case kCFStringEncodingJIS_X0208_90: return 1; + case kCFStringEncodingJIS_X0212_90: return 1; + case kCFStringEncodingJIS_C6226_78: return 1; + case 0x0628/*kCFStringEncodingShiftJIS_X0213*/: return 1; + case kCFStringEncodingShiftJIS_X0213_MenKuTen: return 1; + case kCFStringEncodingGB_2312_80: return 1; + case kCFStringEncodingGBK_95: return 1; + case kCFStringEncodingGB_18030_2000: return 1; + case kCFStringEncodingKSC_5601_87: return 1; + case kCFStringEncodingKSC_5601_92_Johab: return 1; + case kCFStringEncodingCNS_11643_92_P1: return 1; + case kCFStringEncodingCNS_11643_92_P2: return 1; + case kCFStringEncodingCNS_11643_92_P3: return 1; + case kCFStringEncodingISO_2022_JP: return 1; + case kCFStringEncodingISO_2022_JP_2: return 1; + case kCFStringEncodingISO_2022_JP_1: return 1; + case kCFStringEncodingISO_2022_JP_3: return 1; + case kCFStringEncodingISO_2022_CN: return 1; + case kCFStringEncodingISO_2022_CN_EXT: return 1; + case kCFStringEncodingISO_2022_KR: return 1; + case kCFStringEncodingEUC_JP: return 1; + case kCFStringEncodingEUC_CN: return 1; + case kCFStringEncodingEUC_TW: return 1; + case kCFStringEncodingEUC_KR: return 1; + case kCFStringEncodingShiftJIS: return 1; + case kCFStringEncodingKOI8_R: return 1; + case kCFStringEncodingBig5: return 2; //yay, a 2 + case kCFStringEncodingMacRomanLatin1: return 1; + case kCFStringEncodingHZ_GB_2312: return 2; + case kCFStringEncodingBig5_HKSCS_1999: return 1; + case kCFStringEncodingVISCII: return 1; + case kCFStringEncodingKOI8_U: return 1; + case kCFStringEncodingBig5_E: return 2; + case kCFStringEncodingNextStepJapanese: return YES; // ?? + case kCFStringEncodingEBCDIC_US: return 1; //lol + case kCFStringEncodingEBCDIC_CP037: return 1; + case kCFStringEncodingUTF7: return 1; + case kCFStringEncodingUTF7_IMAP : return 1; + default: + NSLog(@"Unknown string encoding %lx in %s", (long)encoding, __FUNCTION__); + return 1; + } +} + +/* Converts a hexadecimal digit into a corresponding 4 bit unsigned int; returns -1 on failure. The ... is a gcc extension. */ +static NSInteger char2hex(unichar c) { + switch (c) { + case '0' ... '9': return c - '0'; + case 'a' ... 'f': return c - 'a' + 10; + case 'A' ... 'F': return c - 'A' + 10; + default: return -1; + } +} + +static unsigned char hex2char(NSUInteger c) { + HFASSERT(c < 16); + return "0123456789ABCDEF"[c]; +} + +NSData *HFDataFromHexString(NSString *string, BOOL* isMissingLastNybble) { + REQUIRE_NOT_NULL(string); + NSUInteger stringIndex=0, resultIndex=0, max=[string length]; + NSMutableData* result = [NSMutableData dataWithLength:(max + 1)/2]; + unsigned char* bytes = [result mutableBytes]; + + NSUInteger numNybbles = 0; + unsigned char byteValue = 0; + + for (stringIndex = 0; stringIndex < max; stringIndex++) { + NSInteger val = char2hex([string characterAtIndex:stringIndex]); + if (val < 0) continue; + numNybbles++; + byteValue = byteValue * 16 + (unsigned char)val; + if (! (numNybbles % 2)) { + bytes[resultIndex++] = byteValue; + byteValue = 0; + } + } + + if (isMissingLastNybble) *isMissingLastNybble = (numNybbles % 2); + + //final nibble + if (numNybbles % 2) { + bytes[resultIndex++] = byteValue; + } + + [result setLength:resultIndex]; + return result; +} + +NSString *HFHexStringFromData(NSData *data) { + REQUIRE_NOT_NULL(data); + NSUInteger dataLength = [data length]; + NSUInteger stringLength = HFProductInt(dataLength, 2); + const unsigned char *bytes = [data bytes]; + unsigned char *charBuffer = check_malloc(stringLength); + NSUInteger charIndex = 0, byteIndex; + for (byteIndex = 0; byteIndex < dataLength; byteIndex++) { + unsigned char byte = bytes[byteIndex]; + charBuffer[charIndex++] = hex2char(byte >> 4); + charBuffer[charIndex++] = hex2char(byte & 0xF); + } + return [[[NSString alloc] initWithBytesNoCopy:charBuffer length:stringLength encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; +} + +void HFSetFDShouldCache(int fd, BOOL shouldCache) { + int result = fcntl(fd, F_NOCACHE, !shouldCache); + if (result == -1) { + int err = errno; + NSLog(@"fcntl(%d, F_NOCACHE, %d) returned error %d: %s", fd, !shouldCache, err, strerror(err)); + } +} + +NSString *HFDescribeByteCount(unsigned long long count) { + return HFDescribeByteCountWithPrefixAndSuffix(NULL, count, NULL); +} + +/* A big_num represents a number in some base. Here it is value = big * base + little. */ +typedef struct big_num { + unsigned int big; + unsigned long long little; +} big_num; + +static inline big_num divide_bignum_by_2(big_num a, unsigned long long base) { + //value = a.big * base + a.little; + big_num result; + result.big = a.big / 2; + unsigned int shiftedRemainder = (unsigned int)(a.little & 1); + result.little = a.little / 2; + if (a.big & 1) { + //need to add base/2 to result.little. We know that won't overflow because result.little is already a.little / 2 + result.little += base / 2; + + // If we shift off a bit for base/2, and we also shifted off a bit for a.little/2, then we have a carry bit we need to add + if ((base & 1) && shiftedRemainder) { + /* Is there a chance that adding 1 will overflow? We know base is odd (base & 1), so consider an example of base = 9. Then the largest that result.little could be is (9 - 1)/2 + base/2 = 8. We could add 1 and get back to base, but we can never exceed base, so we cannot overflow an unsigned long long. */ + result.little += 1; + HFASSERT(result.little <= base); + if (result.little == base) { + result.big++; + result.little = 0; + } + } + } + HFASSERT(result.little < base); + return result; +} + +static inline big_num add_big_nums(big_num a, big_num b, unsigned long long base) { + /* Perform the addition result += left. The addition is: + result.big = a.big + b.big + (a.little + b.little) / base + result.little = (a.little + b.little) % base + + a.little + b.little may overflow, so we have to take some care in how we calculate them. + Since both a.little and b.little are less than base, we know that if we overflow, we can subtract base from it to underflow and still get the same remainder. + */ + unsigned long long remainder = a.little + b.little; + unsigned int dividend = 0; + // remainder < a.little detects overflow, and remainder >= base detects the case where we did not overflow but are larger than base + if (remainder < a.little || remainder >= base) { + remainder -= base; + dividend++; + } + HFASSERT(remainder < base); + + big_num result = {a.big + b.big + dividend, remainder}; + return result; +} + + +/* Returns the first digit after the decimal point for a / b, rounded off, without overflow. This may return 10, indicating that the digit is 0 and we should carry. */ +static unsigned int computeRemainderPrincipalDigit(unsigned long long a, unsigned long long base) { + struct big_num result = {0, 0}, left = {(unsigned)(a / base), a % base}, right = {(unsigned)(100 / base), 100 % base}; + while (right.big > 0 || right.little > 0) { + /* Determine the least significant bit of right, which is right.big * base + right.little */ + unsigned int bigTermParity = (base & 1) && (right.big & 1); + unsigned int littleTermParity = (unsigned)(right.little & 1); + if (bigTermParity != littleTermParity) result = add_big_nums(result, left, base); + + right = divide_bignum_by_2(right, base); + left = add_big_nums(left, left, base); + } + + //result.big now contains 100 * a / base + unsigned int principalTwoDigits = (unsigned int)(result.big % 100); + unsigned int principalDigit = (principalTwoDigits / 10) + ((principalTwoDigits % 10) >= 5); + return principalDigit; +} + +NSString *HFDescribeByteCountWithPrefixAndSuffix(const char *stringPrefix, unsigned long long count, const char *stringSuffix) { + if (! stringPrefix) stringPrefix = ""; + if (! stringSuffix) stringSuffix = ""; + + if (count == 0) return [NSString stringWithFormat:@"%s0 bytes%s", stringPrefix, stringSuffix]; + + const struct { + unsigned long long size; + const char *suffix; + } suffixes[] = { + {1ULL<<0, "byte"}, + {1ULL<<10, "byte"}, + {1ULL<<20, "kilobyte"}, + {1ULL<<30, "megabyte"}, + {1ULL<<40, "gigabyte"}, + {1ULL<<50, "terabyte"}, + {1ULL<<60, "petabyte"}, + {ULLONG_MAX, "exabyte"} + }; + const unsigned numSuffixes = sizeof suffixes / sizeof *suffixes; + //HFASSERT((sizeof sizes / sizeof *sizes) == (sizeof suffixes / sizeof *suffixes)); + unsigned i; + unsigned long long base; + for (i=0; i < numSuffixes; i++) { + if (count < suffixes[i].size || suffixes[i].size == ULLONG_MAX) break; + } + + if (i >= numSuffixes) return [NSString stringWithFormat:@"%san unbelievable number of bytes%s", stringPrefix, stringSuffix]; + base = suffixes[i-1].size; + + unsigned long long dividend = count / base; + unsigned int remainderPrincipalDigit = computeRemainderPrincipalDigit(count % base, base); + HFASSERT(remainderPrincipalDigit <= 10); + if (remainderPrincipalDigit == 10) { + /* Carry */ + dividend++; + remainderPrincipalDigit = 0; + } + + BOOL needsPlural = (dividend != 1 || remainderPrincipalDigit > 0); + + char remainderBuff[64]; + if (remainderPrincipalDigit > 0) snprintf(remainderBuff, sizeof remainderBuff, ".%u", remainderPrincipalDigit); + else remainderBuff[0] = 0; + + char* resultPointer = NULL; + int numChars = asprintf(&resultPointer, "%s%llu%s %s%s%s", stringPrefix, dividend, remainderBuff, suffixes[i].suffix, needsPlural ? "s" : "", stringSuffix); + if (numChars < 0) return NULL; + return [[[NSString alloc] initWithBytesNoCopy:resultPointer length:numChars encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; +} + +static CGFloat interpolateShadow(CGFloat val) { + //A value of 1 means we are at the rightmost, and should return our max value. By adjusting the scale, we control how quickly the shadow drops off. + CGFloat scale = 1.4; + return (CGFloat)(expm1(val * scale) / expm1(scale)); +} + +void HFDrawShadow(CGContextRef ctx, NSRect rect, CGFloat shadowSize, NSRectEdge rectEdge, BOOL drawActive, NSRect clip) { + NSRect remainingRect, unused; + NSDivideRect(rect, &remainingRect, &unused, shadowSize, rectEdge); + + CGFloat maxAlpha = (drawActive ? .25 : .10); + + for (CGFloat i=0; i < shadowSize; i++) { + NSRect shadowLine; + NSDivideRect(remainingRect, &shadowLine, &remainingRect, 1, rectEdge); + + NSRect clippedLine = NSIntersectionRect(shadowLine, clip); + if (! NSIsEmptyRect(clippedLine)) { + CGFloat gray = 0.; + CGFloat alpha = maxAlpha * interpolateShadow((shadowSize - i) / shadowSize); + CGContextSetGrayFillColor(ctx, gray, alpha); + CGContextFillRect(ctx, NSRectToCGRect(clippedLine)); + } + } + +} + +void HFRegisterViewForWindowAppearanceChanges(NSView *self, SEL notificationSEL, BOOL appToo) { + NSWindow *window = [self window]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + if (window) { + [center addObserver:self selector:notificationSEL name:NSWindowDidBecomeKeyNotification object:window]; + [center addObserver:self selector:notificationSEL name:NSWindowDidResignKeyNotification object:window]; + } + if (appToo) { + [center addObserver:self selector:notificationSEL name:NSApplicationDidBecomeActiveNotification object:nil]; + [center addObserver:self selector:notificationSEL name:NSApplicationDidResignActiveNotification object:nil]; + } +} + +void HFUnregisterViewForWindowAppearanceChanges(NSView *self, BOOL appToo) { + NSWindow *window = [self window]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + if (window) { + [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window]; + [center removeObserver:self name:NSWindowDidResignKeyNotification object:window]; + } + if (appToo) { + [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil]; + [center removeObserver:self name:NSApplicationDidResignActiveNotification object:nil]; + } +} + +#if USE_CHUD +void HFStartTiming(const char *name) { + static BOOL inited; + if (! inited) { + inited = YES; + chudInitialize(); + chudSetErrorLogFile(stderr); + chudAcquireRemoteAccess(); + } + chudStartRemotePerfMonitor(name); + +} + +void HFStopTiming(void) { + chudStopRemotePerfMonitor(); +} +#else +void HFStartTiming(const char *name) { USE(name); } +void HFStopTiming(void) { } +#endif diff --git a/HexFiend/HFFunctions_Private.h b/HexFiend/HFFunctions_Private.h new file mode 100644 index 00000000..3595d3da --- /dev/null +++ b/HexFiend/HFFunctions_Private.h @@ -0,0 +1,52 @@ +#import + +@class HFController; + +static inline BOOL HFIsRunningOnMountainLionOrLater(void) { + return NSAppKitVersionNumber >= NSAppKitVersionNumber10_8; +} + +/* Returns the first index where the strings differ. If the strings do not differ in any characters but are of different lengths, returns the smaller length; if they are the same length and do not differ, returns NSUIntegerMax */ +static inline NSUInteger HFIndexOfFirstByteThatDiffers(const unsigned char *a, NSUInteger len1, const unsigned char *b, NSUInteger len2) { + NSUInteger endIndex = MIN(len1, len2); + for (NSUInteger i = 0; i < endIndex; i++) { + if (a[i] != b[i]) return i; + } + if (len1 != len2) return endIndex; + return NSUIntegerMax; +} + +/* Returns the last index where the strings differ. If the strings do not differ in any characters but are of different lengths, returns the larger length; if they are the same length and do not differ, returns NSUIntegerMax */ +static inline NSUInteger HFIndexOfLastByteThatDiffers(const unsigned char *a, NSUInteger len1, const unsigned char *b, NSUInteger len2) { + if (len1 != len2) return MAX(len1, len2); + NSUInteger i = len1; + while (i--) { + if (a[i] != b[i]) return i; + } + return NSUIntegerMax; +} + +static inline unsigned long long llmin(unsigned long long a, unsigned long long b) { + return a < b ? a : b; +} + +__private_extern__ NSImage *HFImageNamed(NSString *name); + +/* Returns an NSData from an NSString containing hexadecimal characters. Characters that are not hexadecimal digits are silently skipped. Returns by reference whether the last byte contains only one nybble, in which case it will be returned in the low 4 bits of the last byte. */ +__private_extern__ NSData *HFDataFromHexString(NSString *string, BOOL* isMissingLastNybble); + +__private_extern__ NSString *HFHexStringFromData(NSData *data); + +/* Modifies F_NOCACHE for a given file descriptor */ +__private_extern__ void HFSetFDShouldCache(int fd, BOOL shouldCache); + +__private_extern__ NSString *HFDescribeByteCountWithPrefixAndSuffix(const char *stringPrefix, unsigned long long count, const char *stringSuffix); + +/* Function for OSAtomicAdd64 that just does a non-atomic add on PowerPC. This should not be used where atomicity is critical; an example where this is used is updating a progress bar. */ +static inline int64_t HFAtomicAdd64(int64_t a, volatile int64_t *b) { +#if __ppc__ + return *b += a; +#else + return OSAtomicAdd64(a, b); +#endif +} diff --git a/HexFiend/HFGlyphTrie.h b/HexFiend/HFGlyphTrie.h new file mode 100644 index 00000000..36d4b08c --- /dev/null +++ b/HexFiend/HFGlyphTrie.h @@ -0,0 +1,49 @@ +/* HFGlyphTrie is used to represent a trie of glyphs that allows multiple concurrent readers, along with one writer. */ + +#import + +/* BranchFactor is in bits */ +#define kHFGlyphTrieBranchFactor 4 +#define kHFGlyphTrieBranchCount (1 << kHFGlyphTrieBranchFactor) + +typedef uint16_t HFGlyphFontIndex; +#define kHFGlyphFontIndexInvalid ((HFGlyphFontIndex)(-1)) + +#define kHFGlyphInvalid kCGFontIndexInvalid + +struct HFGlyph_t { + HFGlyphFontIndex fontIndex; + CGGlyph glyph; +}; + +static inline BOOL HFGlyphEqualsGlyph(struct HFGlyph_t a, struct HFGlyph_t b) __attribute__((unused)); +static inline BOOL HFGlyphEqualsGlyph(struct HFGlyph_t a, struct HFGlyph_t b) { + return a.glyph == b.glyph && a.fontIndex == b.fontIndex; +} + +struct HFGlyphTrieBranch_t { + __strong void *children[kHFGlyphTrieBranchCount]; +}; + +struct HFGlyphTrieLeaf_t { + struct HFGlyph_t glyphs[kHFGlyphTrieBranchCount]; +}; + +struct HFGlyphTrie_t { + uint8_t branchingDepth; + struct HFGlyphTrieBranch_t root; +}; + +/* Initializes a trie witha given key size */ +__private_extern__ void HFGlyphTrieInitialize(struct HFGlyphTrie_t *trie, uint8_t keySize); + +/* Inserts a glyph into the trie */ +__private_extern__ void HFGlyphTrieInsert(struct HFGlyphTrie_t *trie, NSUInteger key, struct HFGlyph_t value); + +/* Attempts to fetch a glyph. If the glyph is not present, returns an HFGlyph_t set to all bits 0. */ +__private_extern__ struct HFGlyph_t HFGlyphTrieGet(const struct HFGlyphTrie_t *trie, NSUInteger key); + +/* Frees all storage associated with a glyph tree. This is not necessary to call under GC. */ +__private_extern__ void HFGlyphTreeFree(struct HFGlyphTrie_t * trie); + + diff --git a/HexFiend/HFGlyphTrie.m b/HexFiend/HFGlyphTrie.m new file mode 100644 index 00000000..db94782a --- /dev/null +++ b/HexFiend/HFGlyphTrie.m @@ -0,0 +1,93 @@ +#import "HFGlyphTrie.h" +#import + +/* If branchingDepth is 1, then this is a leaf and there's nothing to free (a parent frees its children). If branchingDepth is 2, then this is a branch whose children are leaves, so we have to free the leaves but we do not recurse. If branchingDepth is greater than 2, we do have to recurse. */ +static void freeTrie(struct HFGlyphTrieBranch_t *branch, uint8_t branchingDepth) { + HFASSERT(branchingDepth >= 1); + NSUInteger i; + if (branchingDepth > 2) { + /* Recurse */ + for (i=0; i < kHFGlyphTrieBranchCount; i++) { + if (branch->children[i]) { + freeTrie(branch->children[i], branchingDepth - 1); + } + } + } + if (branchingDepth > 1) { + /* Free our children */ + for (i=0; i < kHFGlyphTrieBranchCount; i++) { + free(branch->children[i]); + } + } +} + +static void insertTrie(void *node, uint8_t branchingDepth, NSUInteger key, struct HFGlyph_t value) { + HFASSERT(node != NULL); + HFASSERT(branchingDepth >= 1); + if (branchingDepth == 1) { + /* Leaf */ + HFASSERT(key < kHFGlyphTrieBranchCount); + ((struct HFGlyphTrieLeaf_t *)node)->glyphs[key] = value; + } else { + /* Branch */ + struct HFGlyphTrieBranch_t *branch = node; + NSUInteger keySlice = key & ((1 << kHFGlyphTrieBranchFactor) - 1), keyRemainder = key >> kHFGlyphTrieBranchFactor; + __strong void *child = branch->children[keySlice]; + if (child == NULL) { + if (branchingDepth == 2) { + child = calloc(1, sizeof(struct HFGlyphTrieLeaf_t)); + } else { + child = calloc(1, sizeof(struct HFGlyphTrieBranch_t)); + } + /* We just zeroed out a block of memory and we are about to write its address somewhere where another thread could read it, so we need a memory barrier. */ + OSMemoryBarrier(); + branch->children[keySlice] = child; + } + insertTrie(child, branchingDepth - 1, keyRemainder, value); + } +} + +static struct HFGlyph_t getTrie(const void *node, uint8_t branchingDepth, NSUInteger key) { + HFASSERT(node != NULL); + HFASSERT(branchingDepth >= 1); + if (branchingDepth == 1) { + /* Leaf */ + HFASSERT(key < kHFGlyphTrieBranchCount); + return ((const struct HFGlyphTrieLeaf_t *)node)->glyphs[key]; + } else { + /* Branch */ + const struct HFGlyphTrieBranch_t *branch = node; + NSUInteger keySlice = key & ((1 << kHFGlyphTrieBranchFactor) - 1), keyRemainder = key >> kHFGlyphTrieBranchFactor; + if (branch->children[keySlice] == NULL) { + /* Not found */ + return (struct HFGlyph_t){0, 0}; + } else { + /* This dereference requires a data dependency barrier */ + return getTrie(branch->children[keySlice], branchingDepth - 1, keyRemainder); + } + } +} + +void HFGlyphTrieInsert(struct HFGlyphTrie_t *trie, NSUInteger key, struct HFGlyph_t value) { + insertTrie(&trie->root, trie->branchingDepth, key, value); +} + +struct HFGlyph_t HFGlyphTrieGet(const struct HFGlyphTrie_t *trie, NSUInteger key) { + struct HFGlyph_t result = getTrie(&trie->root, trie->branchingDepth, key); + return result; +} + +void HFGlyphTrieInitialize(struct HFGlyphTrie_t *trie, uint8_t keySize) { + /* If the branch factor is 4 (bits) and the key size is 2 bytes = 16 bits, initialize branching depth to 16/4 = 4 */ + uint8_t keyBits = keySize * CHAR_BIT; + HFASSERT(keyBits % kHFGlyphTrieBranchFactor == 0); + trie->branchingDepth = keyBits / kHFGlyphTrieBranchFactor; + memset(&trie->root, 0, sizeof(trie->root)); +} + +void HFGlyphTreeFree(struct HFGlyphTrie_t * trie) { + /* Don't try to free under GC. And don't free if it's never been initialized. */ + if (trie->branchingDepth > 0) { + freeTrie(&trie->root, trie->branchingDepth); + } +} diff --git a/HexFiend/HFHexTextRepresenter.h b/HexFiend/HFHexTextRepresenter.h new file mode 100644 index 00000000..ecbb1a0b --- /dev/null +++ b/HexFiend/HFHexTextRepresenter.h @@ -0,0 +1,21 @@ +// +// HFHexTextRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFHexTextRepresenter + + @brief HFHexTextRepresenter is an HFRepresenter responsible for showing data in hexadecimal form. + + HFHexTextRepresenter is an HFRepresenter responsible for showing data in hexadecimal form. It has no methods except those inherited from HFTextRepresenter. +*/ +@interface HFHexTextRepresenter : HFTextRepresenter { + unsigned long long omittedNybbleLocation; + unsigned char unpartneredLastNybble; +} + +@end diff --git a/HexFiend/HFHexTextRepresenter.m b/HexFiend/HFHexTextRepresenter.m new file mode 100644 index 00000000..f98382b6 --- /dev/null +++ b/HexFiend/HFHexTextRepresenter.m @@ -0,0 +1,203 @@ +// +// HFHexTextRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +@interface HFHexPasteboardOwner : HFPasteboardOwner { + NSUInteger _bytesPerColumn; +} +@property (nonatomic) NSUInteger bytesPerColumn; +@end + +static inline unsigned char hex2char(NSUInteger c) { + HFASSERT(c < 16); + return "0123456789ABCDEF"[c]; +} + +@implementation HFHexPasteboardOwner + +@synthesize bytesPerColumn = _bytesPerColumn; + +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { + if(!dataLength) return 0; + // -1 because no trailing space for an exact multiple. + unsigned long long spaces = _bytesPerColumn ? (dataLength-1)/_bytesPerColumn : 0; + if ((ULLONG_MAX - spaces)/2 <= dataLength) return ULLONG_MAX; + else return dataLength*2 + spaces; +} + +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker { + HFASSERT([type isEqual:NSStringPboardType]); + if(length == 0) { + [pboard setString:@"" forType:type]; + return; + } + HFByteArray *byteArray = [self byteArray]; + HFASSERT(length <= NSUIntegerMax); + NSUInteger dataLength = ll2l(length); + NSUInteger stringLength = ll2l([self stringLengthForDataLength:length]); + HFASSERT(stringLength < ULLONG_MAX); + NSUInteger offset = 0, stringOffset = 0, remaining = dataLength; + unsigned char * restrict const stringBuffer = check_malloc(stringLength); + while (remaining > 0) { + unsigned char dataBuffer[64 * 1024]; + NSUInteger amountToCopy = MIN(sizeof dataBuffer, remaining); + NSUInteger bound = offset + amountToCopy - 1; + [byteArray copyBytes:dataBuffer range:HFRangeMake(offset, amountToCopy)]; + + if(_bytesPerColumn > 0 && offset > 0) { // ensure offset > 0 to skip adding a leading space + NSUInteger left = _bytesPerColumn - (offset % _bytesPerColumn); + if(left != _bytesPerColumn) { + while(left-- > 0 && offset <= bound) { + unsigned char c = dataBuffer[offset++]; + stringBuffer[stringOffset] = hex2char(c >> 4); + stringBuffer[stringOffset + 1] = hex2char(c & 0xF); + stringOffset += 2; + } + } + if(offset <= bound) + stringBuffer[stringOffset++] = ' '; + } + + if(_bytesPerColumn > 0) while(offset+_bytesPerColumn <= bound) { + for(NSUInteger j = 0; j < _bytesPerColumn; j++) { + unsigned char c = dataBuffer[offset++]; + stringBuffer[stringOffset] = hex2char(c >> 4); + stringBuffer[stringOffset + 1] = hex2char(c & 0xF); + stringOffset += 2; + } + stringBuffer[stringOffset++] = ' '; + } + + while (offset <= bound) { + unsigned char c = dataBuffer[offset++]; + stringBuffer[stringOffset] = hex2char(c >> 4); + stringBuffer[stringOffset + 1] = hex2char(c & 0xF); + stringOffset += 2; + } + + remaining -= amountToCopy; + } + + NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; + [pboard setString:string forType:type]; + [string release]; +} + +@end + +@implementation HFHexTextRepresenter + +/* No extra NSCoder support needed */ + +- (Class)_textViewClass { + return [HFRepresenterHexTextView class]; +} + +- (void)initializeView { + [super initializeView]; + [[self view] setBytesBetweenVerticalGuides:4]; + unpartneredLastNybble = UCHAR_MAX; + omittedNybbleLocation = ULLONG_MAX; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(0, 0); +} + +- (void)_clearOmittedNybble { + unpartneredLastNybble = UCHAR_MAX; + omittedNybbleLocation = ULLONG_MAX; +} + +- (BOOL)_insertionShouldDeleteLastNybble { + /* Either both the omittedNybbleLocation and unpartneredLastNybble are invalid (set to their respective maxima), or neither are */ + HFASSERT((omittedNybbleLocation == ULLONG_MAX) == (unpartneredLastNybble == UCHAR_MAX)); + /* We should delete the last nybble if our omittedNybbleLocation is the point where we would insert */ + BOOL result = NO; + if (omittedNybbleLocation != ULLONG_MAX) { + HFController *controller = [self controller]; + NSArray *selectedRanges = [controller selectedContentsRanges]; + if ([selectedRanges count] == 1) { + HFRange selectedRange = [selectedRanges[0] HFRange]; + result = (selectedRange.length == 0 && selectedRange.location > 0 && selectedRange.location - 1 == omittedNybbleLocation); + } + } + return result; +} + +- (BOOL)_canInsertText:(NSString *)text { + REQUIRE_NOT_NULL(text); + NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"]; + return [text rangeOfCharacterFromSet:characterSet].location != NSNotFound; +} + +- (void)insertText:(NSString *)text { + REQUIRE_NOT_NULL(text); + if (! [self _canInsertText:text]) { + /* The user typed invalid data, and we can ignore it */ + return; + } + + BOOL shouldReplacePriorByte = [self _insertionShouldDeleteLastNybble]; + if (shouldReplacePriorByte) { + HFASSERT(unpartneredLastNybble < 16); + /* Prepend unpartneredLastNybble as a nybble */ + text = [NSString stringWithFormat:@"%1X%@", unpartneredLastNybble, text]; + } + BOOL isMissingLastNybble; + NSData *data = HFDataFromHexString(text, &isMissingLastNybble); + HFASSERT([data length] > 0); + HFASSERT(shouldReplacePriorByte != isMissingLastNybble); + HFController *controller = [self controller]; + BOOL success = [controller insertData:data replacingPreviousBytes: (shouldReplacePriorByte ? 1 : 0) allowUndoCoalescing:YES]; + if (isMissingLastNybble && success) { + HFASSERT([data length] > 0); + HFASSERT(unpartneredLastNybble == UCHAR_MAX); + [data getBytes:&unpartneredLastNybble range:NSMakeRange([data length] - 1, 1)]; + NSArray *selectedRanges = [controller selectedContentsRanges]; + HFASSERT([selectedRanges count] >= 1); + HFRange selectedRange = [selectedRanges[0] HFRange]; + HFASSERT(selectedRange.location > 0); + omittedNybbleLocation = HFSubtract(selectedRange.location, 1); + } + else { + [self _clearOmittedNybble]; + } +} + +- (NSData *)dataFromPasteboardString:(NSString *)string { + REQUIRE_NOT_NULL(string); + return HFDataFromHexString(string, NULL); +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & HFControllerHideNullBytes) { + [[self view] setHidesNullBytes:[[self controller] shouldHideNullBytes]]; + } + [super controllerDidChange:bits]; + if (bits & (HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges)) { + [self _clearOmittedNybble]; + } +} + +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges]; + HFASSERT(selection != NULL); + if ([selection length] == 0) { + NSBeep(); + } else { + HFHexPasteboardOwner *owner = [HFHexPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]]; + [owner setBytesPerLine:[self bytesPerLine]]; + owner.bytesPerColumn = self.bytesPerColumn; + } +} + +@end diff --git a/HexFiend/HFHexTextView.h b/HexFiend/HFHexTextView.h new file mode 100644 index 00000000..3222b65e --- /dev/null +++ b/HexFiend/HFHexTextView.h @@ -0,0 +1,15 @@ +// +// HFHexTextView.h +// HexFiend_2 +// +// Copyright 2007 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface HFHexTextView : NSTextView { + +} + +@end diff --git a/HexFiend/HFHexTextView.m b/HexFiend/HFHexTextView.m new file mode 100644 index 00000000..9e6ae47b --- /dev/null +++ b/HexFiend/HFHexTextView.m @@ -0,0 +1,13 @@ +// +// HFHexTextView.m +// HexFiend_2 +// +// Copyright 2007 __MyCompanyName__. All rights reserved. +// + +#import "HFHexTextView.h" + + +@implementation HFHexTextView + +@end diff --git a/HexFiend/HFLayoutRepresenter.h b/HexFiend/HFLayoutRepresenter.h new file mode 100644 index 00000000..a493304b --- /dev/null +++ b/HexFiend/HFLayoutRepresenter.h @@ -0,0 +1,80 @@ +// +// HFLayoutRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFLayoutRepresenter + @brief An HFRepresenter responsible for arranging the views of other HFRepresenters attached to the same HFController. + + HFLayoutRepresenter is an HFRepresenter that manages the views of other HFRepresenters. It arranges their views in its own view, mediating between them to determine their position and size, as well as global properties such as bytes per line. + + HFLayoutRepresenter has an array of representers attached to it. When you add an HFRepresenter to this array, HFLayoutRepresenter will add the view of the representer as a subview of its own view. + + \b Layout + + HFLayoutRepresenter is capable of arranging the views of other HFRepresenters to fit within the bounds of its view. The layout process depends on three things: + + -# The \c frame and \c autoresizingMask of the representers' views. + -# The \c minimumViewWidthForBytesPerLine: method, which determines the largest number of bytes per line that the representer can display for a given view width. + -# The representer's \c layoutPosition. This is an NSPoint, but it is not used geometrically. Instead, the relative values of the X and Y coordinates of the \c layoutPosition determine the relative positioning of the views, as described below. + + Thus, to have your subclass of HFRepresenter participate in the HFLayoutRepresenter system, override \c defaultLayoutPosition: to control its positioning, and possibly \\c minimumViewWidthForBytesPerLine: if your representer requires a certain width to display some bytes per line. Then ensure your view has its autoresizing mask set properly, and if its frame is fixed size, ensure that its frame is correct as well. + + The layout process, in detail, is: + + -# The views are sorted vertically by the Y component of their representers' \c layoutPosition into "slices." Smaller values appear towards the bottom of the layout view. There is no space between slices. + -# Views with equal Y components are sorted horizontally by the X component of their representers' \c layoutPosition, with smaller values appearing on the left. + -# The height of each slice is determined by the tallest view within it, excluding views that have \c NSViewHeightSizable set. If there is any leftover vertical space, it is distributed equally among all slices with at least one view with \c NSViewHeightSizable set. + -# If the layout representer is not set to maximize the bytes per line (BPL), then the BPL from the HFController is used. Otherwise: + -# Each representer is queried for its \c minimumViewWidthForBytesPerLine: + -# The largest BPL allowing each row to fit within the layout width is determined via a binary search. + -# The BPL is rounded down to a multiple of the bytes per column (if non-zero). + -# The BPL is then set on the controller. + -# For each row, each view is assigned its minimum view width for the BPL. + -# If there is any horizontal space left over, it is divided evenly between all views in that slice that have \c NSViewWidthSizable set in their autoresizing mask. + +*/ +@interface HFLayoutRepresenter : HFRepresenter { + NSMutableArray *representers; + BOOL maximizesBytesPerLine; +} + +/*! @name Managed representers + Managing the list of representers laid out by the receiver +*/ +//@{ +/// Return the array of representers managed by the receiver. */ +@property (readonly, copy) NSArray *representers; + +/*! Adds a new representer to the receiver, triggering relayout. */ +- (void)addRepresenter:(HFRepresenter *)representer; + +/*! Removes a representer to the receiver (which must be present in the receiver's array of representers), triggering relayout. */ +- (void)removeRepresenter:(HFRepresenter *)representer; +//@} + +/*! When enabled, the receiver will attempt to maximize the bytes per line so as to consume as much as possible of the bounds rect. If this is YES, then upon relayout, the receiver will recalculate the maximum number of bytes per line that can fit in its boundsRectForLayout. If this is NO, then the receiver will not change the bytes per line. */ +@property (nonatomic) BOOL maximizesBytesPerLine; + +/*! @name Layout + Methods to get information about layout, and to explicitly trigger it. +*/ +//@{ +/*! Returns the smallest width that produces the same layout (and, if maximizes bytesPerLine, the same bytes per line) as the proposed width. */ +- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth; + +/*! Returns the maximum bytes per line that can fit in the proposed width (ignoring maximizesBytesPerLine). This is always a multiple of the bytesPerColumn, and always at least bytesPerColumn. */ +- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth; + +/*! Returns the smallest width that can support the given bytes per line. */ +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine; + +/*! Relayouts are triggered when representers are added and removed, or when the view is resized. You may call this explicitly to trigger a relayout. */ +- (void)performLayout; +//@} + +@end diff --git a/HexFiend/HFLayoutRepresenter.m b/HexFiend/HFLayoutRepresenter.m new file mode 100644 index 00000000..c36c0ea1 --- /dev/null +++ b/HexFiend/HFLayoutRepresenter.m @@ -0,0 +1,361 @@ +// +// HFRepresenterLayoutView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +@interface HFRepresenterLayoutViewInfo : NSObject { +@public + HFRepresenter *rep; + NSView *view; + NSPoint layoutPosition; + NSRect frame; + NSUInteger autoresizingMask; +} + +@end + +@implementation HFRepresenterLayoutViewInfo + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ : %@>", view, NSStringFromRect(frame)]; +} + +@end + +@implementation HFLayoutRepresenter + +static NSInteger sortByLayoutPosition(id a, id b, void *self) { + USE(self); + NSPoint pointA = [a layoutPosition]; + NSPoint pointB = [b layoutPosition]; + if (pointA.y < pointB.y) return -1; + else if (pointA.y > pointB.y) return 1; + else if (pointA.x < pointB.x) return -1; + else if (pointA.x > pointB.x) return 1; + else return 0; +} + +- (NSArray *)arraysOfLayoutInfos { + if (! representers) return nil; + + NSMutableArray *result = [NSMutableArray array]; + NSArray *reps = [representers sortedArrayUsingFunction:sortByLayoutPosition context:self]; + NSMutableArray *currentReps = [NSMutableArray array]; + CGFloat currentRepY = - CGFLOAT_MAX; + FOREACH(HFRepresenter*, rep, reps) { + HFRepresenterLayoutViewInfo *info = [[HFRepresenterLayoutViewInfo alloc] init]; + info->rep = rep; + info->view = [rep view]; + info->frame = [info->view frame]; + info->layoutPosition = [rep layoutPosition]; + info->autoresizingMask = [info->view autoresizingMask]; + if (info->layoutPosition.y != currentRepY && [currentReps count] > 0) { + [result addObject:[[currentReps copy] autorelease]]; + [currentReps removeAllObjects]; + } + currentRepY = info->layoutPosition.y; + [currentReps addObject:info]; + [info release]; + } + if ([currentReps count]) [result addObject:[[currentReps copy] autorelease]]; + return result; +} + +- (NSRect)boundsRectForLayout { + NSRect result = [[self view] bounds]; + /* Sometimes when we are not yet in a window, we get wonky bounds, so be paranoid. */ + if (result.size.width < 0 || result.size.height < 0) result = NSZeroRect; + return result; +} + +- (CGFloat)_computeMinHeightForLayoutInfos:(NSArray *)infos { + CGFloat result = 0; + HFASSERT(infos != NULL); + HFASSERT([infos count] > 0); + FOREACH(HFRepresenterLayoutViewInfo *, info, infos) { + if (! (info->autoresizingMask & NSViewHeightSizable)) result = MAX(result, NSHeight([info->view frame])); + } + return result; +} + +- (void)_applyYLocation:(CGFloat)yLocation andMinHeight:(CGFloat)height toInfos:(NSArray *)layoutInfos { + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + info->frame.origin.y = yLocation; + if (info->autoresizingMask & NSViewHeightSizable) info->frame.size.height = height; + } +} + +- (void)_layoutInfosHorizontally:(NSArray *)infos inRect:(NSRect)layoutRect withBytesPerLine:(NSUInteger)bytesPerLine { + CGFloat nextX = NSMinX(layoutRect); + NSUInteger numHorizontallyResizable = 0; + FOREACH(HFRepresenterLayoutViewInfo *, info, infos) { + CGFloat minWidth = [info->rep minimumViewWidthForBytesPerLine:bytesPerLine]; + info->frame.origin.x = nextX; + info->frame.size.width = minWidth; + nextX += minWidth; + numHorizontallyResizable += !! (info->autoresizingMask & NSViewWidthSizable); + } + + CGFloat remainingWidth = NSMaxX(layoutRect) - nextX; + if (numHorizontallyResizable > 0 && remainingWidth > 0) { + NSView *view = [self view]; + CGFloat remainingPixels = [view convertSize:NSMakeSize(remainingWidth, 0) toView:nil].width; + HFASSERT(remainingPixels > 0); + CGFloat pixelsPerView = HFFloor(HFFloor(remainingPixels) / (CGFloat)numHorizontallyResizable); + if (pixelsPerView > 0) { + CGFloat pointsPerView = [view convertSize:NSMakeSize(pixelsPerView, 0) fromView:nil].width; + CGFloat pointsAdded = 0; + FOREACH(HFRepresenterLayoutViewInfo *, info, infos) { + info->frame.origin.x += pointsAdded; + if (info->autoresizingMask & NSViewWidthSizable) { + info->frame.size.width += pointsPerView; + pointsAdded += pointsPerView; + } + } + } + } +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + CGFloat result = 0; + NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos]; + + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + CGFloat minWidthForRow = 0; + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + minWidthForRow += [info->rep minimumViewWidthForBytesPerLine:bytesPerLine]; + } + result = MAX(result, minWidthForRow); + } + return result; +} + +- (NSUInteger)_computeBytesPerLineForArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos forLayoutInRect:(NSRect)layoutRect { + /* The granularity is our own granularity (probably 1), LCMed with the granularities of all other representers */ + NSUInteger granularity = [self byteGranularity]; + FOREACH(HFRepresenter *, representer, representers) { + granularity = HFLeastCommonMultiple(granularity, [representer byteGranularity]); + } + HFASSERT(granularity >= 1); + + NSUInteger newNumGranules = (NSUIntegerMax - 1) / granularity; + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + NSUInteger maxKnownGood = 0, minKnownBad = newNumGranules + 1; + while (maxKnownGood + 1 < minKnownBad) { + CGFloat requiredSpace = 0; + NSUInteger proposedNumGranules = maxKnownGood + (minKnownBad - maxKnownGood)/2; + NSUInteger proposedBytesPerLine = proposedNumGranules * granularity; + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + requiredSpace += [info->rep minimumViewWidthForBytesPerLine:proposedBytesPerLine]; + if (requiredSpace > NSWidth(layoutRect)) break; + } + if (requiredSpace > NSWidth(layoutRect)) minKnownBad = proposedNumGranules; + else maxKnownGood = proposedNumGranules; + } + newNumGranules = maxKnownGood; + } + return MAX(1u, newNumGranules) * granularity; +} + +- (BOOL)_anyLayoutInfoIsVerticallyResizable:(NSArray *)vals { + HFASSERT(vals != NULL); + FOREACH(HFRepresenterLayoutViewInfo *, info, vals) { + if (info->autoresizingMask & NSViewHeightSizable) return YES; + } + return NO; +} + +- (BOOL)_addVerticalHeight:(CGFloat)heightPoints andOffset:(CGFloat)offsetPoints toLayoutInfos:(NSArray *)layoutInfos { + BOOL isVerticallyResizable = [self _anyLayoutInfoIsVerticallyResizable:layoutInfos]; + CGFloat totalHeight = [self _computeMinHeightForLayoutInfos:layoutInfos] + heightPoints; + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + info->frame.origin.y += offsetPoints; + if (isVerticallyResizable) { + if (info->autoresizingMask & NSViewHeightSizable) { + info->frame.size.height = totalHeight; + } + else { + CGFloat diff = totalHeight - info->frame.size.height; + HFASSERT(diff >= 0); + info->frame.origin.y += HFFloor(diff); + } + } + } + return isVerticallyResizable; +} + +- (void)_distributeVerticalSpace:(CGFloat)space toArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos { + HFASSERT(space >= 0); + HFASSERT(arraysOfLayoutInfos != NULL); + + NSUInteger consumers = 0; + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + if ([self _anyLayoutInfoIsVerticallyResizable:layoutInfos]) consumers++; + } + if (consumers > 0) { + NSView *view = [self view]; + CGFloat availablePixels = [view convertSize:NSMakeSize(0, space) toView:nil].height; + HFASSERT(availablePixels > 0); + CGFloat pixelsPerView = HFFloor(HFFloor(availablePixels) / (CGFloat)consumers); + CGFloat pointsPerView = [view convertSize:NSMakeSize(0, pixelsPerView) fromView:nil].height; + CGFloat yOffset = 0; + if (pointsPerView > 0) { + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + if ([self _addVerticalHeight:pointsPerView andOffset:yOffset toLayoutInfos:layoutInfos]) { + yOffset += pointsPerView; + } + } + } + } +} + +- (void)performLayout { + HFController *controller = [self controller]; + if (! controller) return; + if (! representers) return; + + NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos]; + if (! [arraysOfLayoutInfos count]) return; + + NSUInteger transaction = [controller beginPropertyChangeTransaction]; + + NSRect layoutRect = [self boundsRectForLayout]; + + NSUInteger bytesPerLine; + if (maximizesBytesPerLine) bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect]; + else bytesPerLine = [controller bytesPerLine]; + + CGFloat yPosition = NSMinY(layoutRect); + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + HFASSERT([layoutInfos count] > 0); + CGFloat minHeight = [self _computeMinHeightForLayoutInfos:layoutInfos]; + [self _applyYLocation:yPosition andMinHeight:minHeight toInfos:layoutInfos]; + yPosition += minHeight; + [self _layoutInfosHorizontally:layoutInfos inRect:layoutRect withBytesPerLine:bytesPerLine]; + } + + CGFloat remainingVerticalSpace = NSMaxY(layoutRect) - yPosition; + if (remainingVerticalSpace > 0) { + [self _distributeVerticalSpace:remainingVerticalSpace toArraysOfLayoutInfos:arraysOfLayoutInfos]; + } + + FOREACH(NSArray *, layoutInfoArray, arraysOfLayoutInfos) { + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfoArray) { + [info->view setFrame:info->frame]; + } + } + + [controller endPropertyChangeTransaction:transaction]; +} + +- (NSArray *)representers { + return representers ? [[representers copy] autorelease] : @[]; +} + +- (instancetype)init { + self = [super init]; + maximizesBytesPerLine = YES; + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:[self view]]; + [representers release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeObject:representers forKey:@"HFRepresenters"]; + [coder encodeBool:maximizesBytesPerLine forKey:@"HFMaximizesBytesPerLine"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain]; + maximizesBytesPerLine = [coder decodeBoolForKey:@"HFMaximizesBytesPerLine"]; + NSView *view = [self view]; + [view setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view]; + return self; +} + +- (void)addRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + if (! representers) representers = [[NSMutableArray alloc] init]; + HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound); + [representers addObject:representer]; + HFASSERT([[representer view] superview] != [self view]); + [[self view] addSubview:[representer view]]; + [self performLayout]; +} + +- (void)removeRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound); + NSView *view = [representer view]; + HFASSERT([view superview] == [self view]); + [view removeFromSuperview]; + [representers removeObjectIdenticalTo:representer]; + [self performLayout]; +} + +- (void)frameChanged:(NSNotification *)note { + USE(note); + [self performLayout]; +} + +- (void)initializeView { + NSView *view = [self view]; + [view setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view]; +} + +- (NSView *)createView { + return [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; +} + +- (void)setMaximizesBytesPerLine:(BOOL)val { + maximizesBytesPerLine = val; +} + +- (BOOL)maximizesBytesPerLine { + return maximizesBytesPerLine; +} + +- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth { + NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos]; + if (! [arraysOfLayoutInfos count]) return 0; + + NSRect layoutRect = [self boundsRectForLayout]; + layoutRect.size.width = proposedWidth; + + NSUInteger bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect]; + return bytesPerLine; +} + +- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth { + NSUInteger bytesPerLine; + if ([self maximizesBytesPerLine]) { + bytesPerLine = [self maximumBytesPerLineForLayoutInProposedWidth:proposedWidth]; + } else { + bytesPerLine = [[self controller] bytesPerLine]; + } + CGFloat newWidth = [self minimumViewWidthForBytesPerLine:bytesPerLine]; + return newWidth; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + [super controllerDidChange:bits]; + if (bits & (HFControllerViewSizeRatios | HFControllerBytesPerColumn | HFControllerByteGranularity)) { + [self performLayout]; + } +} + +@end diff --git a/HexFiend/HFLineCountingRepresenter.h b/HexFiend/HFLineCountingRepresenter.h new file mode 100644 index 00000000..b711e4ee --- /dev/null +++ b/HexFiend/HFLineCountingRepresenter.h @@ -0,0 +1,67 @@ +// +// HFLineCountingRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @enum HFLineNumberFormat + HFLineNumberFormat is a simple enum used to determine whether line numbers are in decimal or hexadecimal format. +*/ +typedef NS_ENUM(NSUInteger, HFLineNumberFormat) { + HFLineNumberFormatDecimal, //!< Decimal line numbers + HFLineNumberFormatHexadecimal, //!< Hexadecimal line numbers + HFLineNumberFormatMAXIMUM //!< One more than the maximum valid line number format, so that line number formats can be cycled through easily +}; + +/*! @class HFLineCountingRepresenter + @brief The HFRepresenter used to show the "line number gutter." + + HFLineCountingRepresenter is the HFRepresenter used to show the "line number gutter." HFLineCountingRepresenter makes space for a certain number of digits. +*/ +@interface HFLineCountingRepresenter : HFRepresenter { + CGFloat lineHeight; + NSUInteger digitsToRepresentContentsLength; + NSUInteger minimumDigitCount; + HFLineNumberFormat lineNumberFormat; + NSInteger interiorShadowEdge; + CGFloat preferredWidth; + CGFloat digitAdvance; +} + +/// The minimum digit count. The receiver will always ensure it is big enough to display at least the minimum digit count. The default is 2. +@property (nonatomic) NSUInteger minimumDigitCount; + +/// The number of digits we are making space for. +@property (readonly) NSUInteger digitCount; + +/// The current width that the HFRepresenter prefers to be laid out with. +@property (readonly) CGFloat preferredWidth; + +/// The line number format. +@property (nonatomic) HFLineNumberFormat lineNumberFormat; + +/// Switches to the next line number format. This is called from the view. +- (void)cycleLineNumberFormat; + +/// The edge (as an NSRectEdge) on which the view draws an interior shadow. -1 means no edge. +@property (nonatomic) NSInteger interiorShadowEdge; + +/// The border color used at the edges specified by -borderedEdges. +@property (nonatomic, copy) NSColor *borderColor; + +/*! The edges on which borders are drawn. The edge returned by interiorShadowEdge always has a border drawn. The edges are specified by a bitwise or of 1 left shifted by the NSRectEdge values. For example, to draw a border on the min x and max y edges use: (1 << NSMinXEdge) | (1 << NSMaxYEdge). 0 (or -1) specfies no edges. */ +@property (nonatomic) NSInteger borderedEdges; + +/// The background color +@property (nonatomic, copy) NSColor *backgroundColor; + +@property NSUInteger valueOffset; + +@end + +/*! Notification posted when the HFLineCountingRepresenter's width has changed because the number of digits it wants to show has increased or decreased. The object is the HFLineCountingRepresenter; there is no user info. +*/ +extern NSString *const HFLineCountingRepresenterMinimumViewWidthChanged; diff --git a/HexFiend/HFLineCountingRepresenter.m b/HexFiend/HFLineCountingRepresenter.m new file mode 100644 index 00000000..d6b0560e --- /dev/null +++ b/HexFiend/HFLineCountingRepresenter.m @@ -0,0 +1,250 @@ +// +// HFLineCountingRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +NSString *const HFLineCountingRepresenterMinimumViewWidthChanged = @"HFLineCountingRepresenterMinimumViewWidthChanged"; + +/* Returns the maximum advance in points for a hexadecimal digit for the given font (interpreted as a screen font) */ +static CGFloat maximumDigitAdvanceForFont(NSFont *font) { + REQUIRE_NOT_NULL(font); + font = [font screenFont]; + CGFloat maxDigitAdvance = 0; + NSDictionary *attributesDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:font, NSFontAttributeName, nil]; + NSTextStorage *storage = [[NSTextStorage alloc] init]; + NSLayoutManager *manager = [[NSLayoutManager alloc] init]; + [storage setFont:font]; + [storage addLayoutManager:manager]; + + NSSize advancements[16] = {}; + NSGlyph glyphs[16]; + + /* Generate a glyph for every hex digit */ + for (NSUInteger i=0; i < 16; i++) { + char c = "0123456789ABCDEF"[i]; + NSString *string = [[NSString alloc] initWithBytes:&c length:1 encoding:NSASCIIStringEncoding]; + [storage replaceCharactersInRange:NSMakeRange(0, (i ? 1 : 0)) withString:string]; + [string release]; + glyphs[i] = [manager glyphAtIndex:0 isValidIndex:NULL]; + HFASSERT(glyphs[i] != NSNullGlyph); + } + + /* Get the advancements of each of those glyphs */ + [font getAdvancements:advancements forGlyphs:glyphs count:sizeof glyphs / sizeof *glyphs]; + + [manager release]; + [attributesDictionary release]; + [storage release]; + + /* Find the widest digit */ + for (NSUInteger i=0; i < sizeof glyphs / sizeof *glyphs; i++) { + maxDigitAdvance = HFMax(maxDigitAdvance, advancements[i].width); + } + return maxDigitAdvance; +} + +@implementation HFLineCountingRepresenter + +- (instancetype)init { + if ((self = [super init])) { + minimumDigitCount = 2; + digitsToRepresentContentsLength = minimumDigitCount; + interiorShadowEdge = NSMaxXEdge; + + _borderedEdges = (1 << NSMaxXEdge); + _borderColor = [[NSColor darkGrayColor] retain]; + _backgroundColor = [[NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeDouble:lineHeight forKey:@"HFLineHeight"]; + [coder encodeInt64:minimumDigitCount forKey:@"HFMinimumDigitCount"]; + [coder encodeInt64:lineNumberFormat forKey:@"HFLineNumberFormat"]; + [coder encodeObject:self.backgroundColor forKey:@"HFBackgroundColor"]; + [coder encodeObject:self.borderColor forKey:@"HFBorderColor"]; + [coder encodeInt64:self.borderedEdges forKey:@"HFBorderedEdges"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"]; + minimumDigitCount = (NSUInteger)[coder decodeInt64ForKey:@"HFMinimumDigitCount"]; + lineNumberFormat = (HFLineNumberFormat)[coder decodeInt64ForKey:@"HFLineNumberFormat"]; + + _borderedEdges = [coder decodeObjectForKey:@"HFBorderedEdges"] ? (NSInteger)[coder decodeInt64ForKey:@"HFBorderedEdges"] : 0; + _borderColor = [[coder decodeObjectForKey:@"HFBorderColor"] ?: [NSColor darkGrayColor] retain]; + _backgroundColor = [[coder decodeObjectForKey:@"HFBackgroundColor"] ?: [NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain]; + + return self; +} + +- (void)dealloc { + [_borderColor release]; + [_backgroundColor release]; + [super dealloc]; +} + +- (NSView *)createView { + HFLineCountingView *result = [[HFLineCountingView alloc] initWithFrame:NSMakeRect(0, 0, 60, 10)]; + [result setRepresenter:self]; + [result setAutoresizingMask:NSViewHeightSizable]; + return result; +} + +- (void)postMinimumViewWidthChangedNotification { + [[NSNotificationCenter defaultCenter] postNotificationName:HFLineCountingRepresenterMinimumViewWidthChanged object:self]; +} + +- (void)updateDigitAdvanceWithFont:(NSFont *)font { + CGFloat newDigitAdvance = maximumDigitAdvanceForFont(font); + if (digitAdvance != newDigitAdvance) { + digitAdvance = newDigitAdvance; + [self postMinimumViewWidthChangedNotification]; + } +} + +- (void)updateFontAndLineHeight { + HFLineCountingView *view = [self view]; + HFController *controller = [self controller]; + NSFont *font = controller ? [controller font] : [NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]; + [view setFont:font]; + [view setLineHeight: controller ? [controller lineHeight] : HFDEFAULT_FONTSIZE]; + [self updateDigitAdvanceWithFont:font]; +} + +- (void)updateLineNumberFormat { + [[self view] setLineNumberFormat:lineNumberFormat]; +} + +- (void)updateBytesPerLine { + [[self view] setBytesPerLine:[[self controller] bytesPerLine]]; +} + +- (void)updateLineRangeToDraw { + HFFPRange lineRange = {0, 0}; + HFController *controller = [self controller]; + if (controller) { + lineRange = [controller displayedLineRange]; + } + [[self view] setLineRangeToDraw:lineRange]; +} + +- (CGFloat)preferredWidth { + if (digitAdvance == 0) { + /* This may happen if we were loaded from a nib. We are lazy about fetching the controller's font to avoid ordering issues with nib unarchival. */ + [self updateFontAndLineHeight]; + } + return (CGFloat)10. + digitsToRepresentContentsLength * digitAdvance; +} + +- (void)updateMinimumViewWidth { + HFController *controller = [self controller]; + if (controller) { + unsigned long long contentsLength = [controller contentsLength]; + NSUInteger bytesPerLine = [controller bytesPerLine]; + /* We want to know how many lines are displayed. That's equal to the contentsLength divided by bytesPerLine rounded down, except in the case that we're at the end of a line, in which case we need to show one more. Hence adding 1 and dividing gets us the right result. */ + unsigned long long lineCount = contentsLength / bytesPerLine; + unsigned long long contentsLengthRoundedToLine = HFProductULL(lineCount, bytesPerLine) - 1; + NSUInteger digitCount = [HFLineCountingView digitsRequiredToDisplayLineNumber:contentsLengthRoundedToLine inFormat:lineNumberFormat]; + NSUInteger digitWidth = MAX(minimumDigitCount, digitCount); + if (digitWidth != digitsToRepresentContentsLength) { + digitsToRepresentContentsLength = digitWidth; + [self postMinimumViewWidthChangedNotification]; + } + } +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + USE(bytesPerLine); + return [self preferredWidth]; +} + +- (HFLineNumberFormat)lineNumberFormat { + return lineNumberFormat; +} + +- (void)setLineNumberFormat:(HFLineNumberFormat)format { + HFASSERT(format < HFLineNumberFormatMAXIMUM); + lineNumberFormat = format; + [self updateLineNumberFormat]; + [self updateMinimumViewWidth]; +} + + +- (void)cycleLineNumberFormat { + lineNumberFormat = (lineNumberFormat + 1) % HFLineNumberFormatMAXIMUM; + [self updateLineNumberFormat]; + [self updateMinimumViewWidth]; +} + +- (void)initializeView { + [self updateFontAndLineHeight]; + [self updateLineNumberFormat]; + [self updateBytesPerLine]; + [self updateLineRangeToDraw]; + [self updateMinimumViewWidth]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & HFControllerDisplayedLineRange) [self updateLineRangeToDraw]; + if (bits & HFControllerBytesPerLine) [self updateBytesPerLine]; + if (bits & (HFControllerFont | HFControllerLineHeight)) [self updateFontAndLineHeight]; + if (bits & (HFControllerContentLength)) [self updateMinimumViewWidth]; +} + +- (void)setMinimumDigitCount:(NSUInteger)width { + minimumDigitCount = width; + [self updateMinimumViewWidth]; +} + +- (NSUInteger)minimumDigitCount { + return minimumDigitCount; +} + +- (NSUInteger)digitCount { + return digitsToRepresentContentsLength; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(-1, 0); +} + +- (void)setInteriorShadowEdge:(NSInteger)edge { + self->interiorShadowEdge = edge; + if ([self isViewLoaded]) { + [[self view] setNeedsDisplay:YES]; + } +} + +- (NSInteger)interiorShadowEdge { + return interiorShadowEdge; +} + + +- (void)setBorderColor:(NSColor *)color { + [_borderColor autorelease]; + _borderColor = [color copy]; + if ([self isViewLoaded]) { + [[self view] setNeedsDisplay:YES]; + } +} + +- (void)setBackgroundColor:(NSColor *)color { + [_backgroundColor autorelease]; + _backgroundColor = [color copy]; + if ([self isViewLoaded]) { + [[self view] setNeedsDisplay:YES]; + } +} + +@end diff --git a/HexFiend/HFLineCountingView.h b/HexFiend/HFLineCountingView.h new file mode 100644 index 00000000..2eb90af9 --- /dev/null +++ b/HexFiend/HFLineCountingView.h @@ -0,0 +1,32 @@ +// +// HFLineCountingView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +@interface HFLineCountingView : NSView { + NSLayoutManager *layoutManager; + NSTextStorage *textStorage; + NSTextContainer *textContainer; + NSDictionary *textAttributes; + + unsigned long long storedLineIndex; + NSUInteger storedLineCount; + BOOL useStringDrawingPath; + BOOL registeredForAppNotifications; +} + +@property (nonatomic, copy) NSFont *font; +@property (nonatomic) CGFloat lineHeight; +@property (nonatomic) HFFPRange lineRangeToDraw; +@property (nonatomic) NSUInteger bytesPerLine; +@property (nonatomic) HFLineNumberFormat lineNumberFormat; +@property (nonatomic, assign) HFLineCountingRepresenter *representer; + ++ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format; + +@end diff --git a/HexFiend/HFLineCountingView.m b/HexFiend/HFLineCountingView.m new file mode 100644 index 00000000..6c9578d9 --- /dev/null +++ b/HexFiend/HFLineCountingView.m @@ -0,0 +1,689 @@ +// +// HFLineCountingView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +#define TIME_LINE_NUMBERS 0 + +#define HEX_LINE_NUMBERS_HAVE_0X_PREFIX 0 + +#define INVALID_LINE_COUNT NSUIntegerMax + +#if TIME_LINE_NUMBERS +@interface HFTimingTextView : NSTextView +@end +@implementation HFTimingTextView +- (void)drawRect:(NSRect)rect { + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); + [super drawRect:rect]; + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"TextView line number time: %f", endTime - startTime); +} +@end +#endif + +@implementation HFLineCountingView + +- (void)_sharedInitLineCountingView { + layoutManager = [[NSLayoutManager alloc] init]; + textStorage = [[NSTextStorage alloc] init]; + [textStorage addLayoutManager:layoutManager]; + textContainer = [[NSTextContainer alloc] init]; + [textContainer setLineFragmentPadding:(CGFloat)5]; + [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)]; + [layoutManager addTextContainer:textContainer]; +} + +- (instancetype)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self _sharedInitLineCountingView]; + } + return self; +} + +- (void)dealloc { + HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications); + [_font release]; + [layoutManager release]; + [textContainer release]; + [textStorage release]; + [textAttributes release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeObject:_font forKey:@"HFFont"]; + [coder encodeDouble:_lineHeight forKey:@"HFLineHeight"]; + [coder encodeObject:_representer forKey:@"HFRepresenter"]; + [coder encodeInt64:_bytesPerLine forKey:@"HFBytesPerLine"]; + [coder encodeInt64:_lineNumberFormat forKey:@"HFLineNumberFormat"]; + [coder encodeBool:useStringDrawingPath forKey:@"HFUseStringDrawingPath"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + [self _sharedInitLineCountingView]; + _font = [[coder decodeObjectForKey:@"HFFont"] retain]; + _lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"]; + _representer = [coder decodeObjectForKey:@"HFRepresenter"]; + _bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"]; + _lineNumberFormat = (NSUInteger)[coder decodeInt64ForKey:@"HFLineNumberFormat"]; + useStringDrawingPath = [coder decodeBoolForKey:@"HFUseStringDrawingPath"]; + return self; +} + +- (BOOL)isFlipped { return YES; } + +- (void)getLineNumberFormatString:(char *)outString length:(NSUInteger)length { + HFLineNumberFormat format = self.lineNumberFormat; + if (format == HFLineNumberFormatDecimal) { + strlcpy(outString, "%llu", length); + } + else if (format == HFLineNumberFormatHexadecimal) { +#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX + // we want a format string like 0x%08llX + snprintf(outString, length, "0x%%0%lullX", (unsigned long)self.representer.digitCount - 2); +#else + // we want a format string like %08llX + snprintf(outString, length, "%%0%lullX", (unsigned long)self.representer.digitCount); +#endif + } + else { + strlcpy(outString, "", length); + } +} + +- (void)windowDidChangeKeyStatus:(NSNotification *)note { + USE(note); + [self setNeedsDisplay:YES]; +} + +- (void)viewDidMoveToWindow { + HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications); + registeredForAppNotifications = YES; + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow { + HFUnregisterViewForWindowAppearanceChanges(self, NO); + [super viewWillMoveToWindow:newWindow]; +} + +- (void)drawGradientWithClip:(NSRect)clip { + [_representer.backgroundColor set]; + NSRectFill(clip); + + NSInteger shadowEdge = _representer.interiorShadowEdge; + + if (shadowEdge >= 0) { + const CGFloat shadowWidth = 6; + NSWindow *window = self.window; + BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]); + HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip); + } +} + +- (void)drawDividerWithClip:(NSRect)clipRect { + USE(clipRect); + + +#if 1 + NSInteger edges = _representer.borderedEdges; + NSRect bounds = self.bounds; + + + // -1 means to draw no edges + if (edges == -1) { + edges = 0; + } + + [_representer.borderColor set]; + + if ((edges & (1 << NSMinXEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.width = 1; + lineRect.origin.x = 0; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + if ((edges & (1 << NSMaxXEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.width = 1; + lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + if ((edges & (1 << NSMinYEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.height = 1; + lineRect.origin.y = 0; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + if ((edges & (1 << NSMaxYEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.height = 1; + lineRect.origin.y = NSMaxY(bounds) - lineRect.size.height; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + + // Backwards compatibility to always draw a border on the edge with the interior shadow + + NSRect lineRect = bounds; + lineRect.size.width = 1; + NSInteger shadowEdge = _representer.interiorShadowEdge; + if (shadowEdge == NSMaxXEdge) { + lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width; + } else if (shadowEdge == NSMinXEdge) { + lineRect.origin.x = NSMinX(bounds); + } else { + lineRect = NSZeroRect; + } + + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + +#else + + + if (NSIntersectsRect(lineRect, clipRect)) { + // this looks better when we have no shadow + [[NSColor lightGrayColor] set]; + NSRect bounds = self.bounds; + NSRect lineRect = bounds; + lineRect.origin.x += lineRect.size.width - 2; + lineRect.size.width = 1; + NSRectFill(NSIntersectionRect(lineRect, clipRect)); + [[NSColor whiteColor] set]; + lineRect.origin.x += 1; + NSRectFill(NSIntersectionRect(lineRect, clipRect)); + } +#endif +} + +static inline int common_prefix_length(const char *a, const char *b) { + int i; + for (i=0; ; i++) { + char ac = a[i]; + char bc = b[i]; + if (ac != bc || ac == 0 || bc == 0) break; + } + return i; +} + +/* Drawing with NSLayoutManager is necessary because the 10_2 typesetting behavior used by the old string drawing does the wrong thing for fonts like Bitstream Vera Sans Mono. Also it's an optimization for drawing the shadow. */ +- (void)drawLineNumbersWithClipLayoutManagerPerLine:(NSRect)clipRect { +#if TIME_LINE_NUMBERS + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); +#endif + NSUInteger previousTextStorageCharacterCount = [textStorage length]; + + CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + NSRect textRect = self.bounds; + textRect.size.height = _lineHeight; + textRect.origin.y -= verticalOffset * _lineHeight; + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + unsigned long long lineValue = lineIndex * _bytesPerLine; + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + char previousBuff[256]; + int previousStringLength = (int)previousTextStorageCharacterCount; + BOOL conversionResult = [[textStorage string] getCString:previousBuff maxLength:sizeof previousBuff encoding:NSASCIIStringEncoding]; + HFASSERT(conversionResult); + while (linesRemaining--) { + char formatString[64]; + [self getLineNumberFormatString:formatString length:sizeof formatString]; + + if (NSIntersectsRect(textRect, clipRect)) { + NSString *replacementCharacters = nil; + NSRange replacementRange; + char buff[256]; + int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue); + HFASSERT(newStringLength > 0); + int prefixLength = common_prefix_length(previousBuff, buff); + HFASSERT(prefixLength <= newStringLength); + HFASSERT(prefixLength <= previousStringLength); + replacementRange = NSMakeRange(prefixLength, previousStringLength - prefixLength); + replacementCharacters = [[NSString alloc] initWithBytesNoCopy:buff + prefixLength length:newStringLength - prefixLength encoding:NSASCIIStringEncoding freeWhenDone:NO]; + NSUInteger glyphCount; + [textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters]; + if (previousTextStorageCharacterCount == 0) { + NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil]; + [textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)]; + [atts release]; + } + glyphCount = [layoutManager numberOfGlyphs]; + if (glyphCount > 0) { + CGFloat maxX = NSMaxX([layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphCount - 1 effectiveRange:NULL]); + [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, glyphCount) atPoint:NSMakePoint(textRect.origin.x + textRect.size.width - maxX, textRect.origin.y)]; + } + previousTextStorageCharacterCount = newStringLength; + [replacementCharacters release]; + memcpy(previousBuff, buff, newStringLength + 1); + previousStringLength = newStringLength; + } + textRect.origin.y += _lineHeight; + lineIndex++; + lineValue = HFSum(lineValue, _bytesPerLine); + } +#if TIME_LINE_NUMBERS + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"Line number time: %f", endTime - startTime); +#endif +} + +- (void)drawLineNumbersWithClipStringDrawing:(NSRect)clipRect { + CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + NSRect textRect = self.bounds; + textRect.size.height = _lineHeight; + textRect.size.width -= 5; + textRect.origin.y -= verticalOffset * _lineHeight + 1; + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + unsigned long long lineValue = lineIndex * _bytesPerLine; + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + if (! textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + } + + char formatString[64]; + [self getLineNumberFormatString:formatString length:sizeof formatString]; + + while (linesRemaining--) { + if (NSIntersectsRect(textRect, clipRect)) { + char buff[256]; + int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue); + HFASSERT(newStringLength > 0); + NSString *string = [[NSString alloc] initWithBytesNoCopy:buff length:newStringLength encoding:NSASCIIStringEncoding freeWhenDone:NO]; + [string drawInRect:textRect withAttributes:textAttributes]; + [string release]; + } + textRect.origin.y += _lineHeight; + lineIndex++; + if (linesRemaining > 0) lineValue = HFSum(lineValue, _bytesPerLine); //we could do this unconditionally, but then we risk overflow + } +} + +- (NSUInteger)characterCountForLineRange:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); + NSUInteger characterCount; + + NSUInteger lineCount = ll2l(range.length); + const NSUInteger stride = _bytesPerLine; + HFLineCountingRepresenter *rep = self.representer; + HFLineNumberFormat format = self.lineNumberFormat; + if (format == HFLineNumberFormatDecimal) { + unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine); + characterCount = lineCount /* newlines */; + while (lineCount--) { + characterCount += HFCountDigitsBase10(lineValue); + lineValue += stride; + } + } + else if (format == HFLineNumberFormatHexadecimal) { + characterCount = ([rep digitCount] + 1) * lineCount; // +1 for newlines + } + else { + characterCount = -1; + } + return characterCount; +} + +- (NSString *)newLineStringForRange:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); + if(range.length == 0) + return [[NSString alloc] init]; // Placate the analyzer. + + NSUInteger lineCount = ll2l(range.length); + const NSUInteger stride = _bytesPerLine; + unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine); + NSUInteger characterCount = [self characterCountForLineRange:range]; + char *buffer = check_malloc(characterCount); + NSUInteger bufferIndex = 0; + + char formatString[64]; + [self getLineNumberFormatString:formatString length:sizeof formatString]; + + while (lineCount--) { + int charCount = sprintf(buffer + bufferIndex, formatString, lineValue); + HFASSERT(charCount > 0); + bufferIndex += charCount; + buffer[bufferIndex++] = '\n'; + lineValue += stride; + } + HFASSERT(bufferIndex == characterCount); + + NSString *string = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:bufferIndex encoding:NSASCIIStringEncoding freeWhenDone:YES]; + return string; +} + +- (void)updateLayoutManagerWithLineIndex:(unsigned long long)startingLineIndex lineCount:(NSUInteger)linesRemaining { + const BOOL debug = NO; + [textStorage beginEditing]; + + if (storedLineCount == INVALID_LINE_COUNT) { + /* This usually indicates that our bytes per line or line number format changed, and we need to just recalculate everything */ + NSString *string = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)]; + [textStorage replaceCharactersInRange:NSMakeRange(0, [textStorage length]) withString:string]; + [string release]; + + } + else { + HFRange leftRangeToReplace, rightRangeToReplace; + HFRange leftRangeToStore, rightRangeToStore; + + HFRange oldRange = HFRangeMake(storedLineIndex, storedLineCount); + HFRange newRange = HFRangeMake(startingLineIndex, linesRemaining); + HFRange rangeToPreserve = HFIntersectionRange(oldRange, newRange); + + if (rangeToPreserve.length == 0) { + leftRangeToReplace = oldRange; + leftRangeToStore = newRange; + rightRangeToReplace = HFZeroRange; + rightRangeToStore = HFZeroRange; + } + else { + if (debug) NSLog(@"Preserving %llu", rangeToPreserve.length); + HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, newRange)); + HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, oldRange)); + const unsigned long long maxPreserve = HFMaxRange(rangeToPreserve); + leftRangeToReplace = HFRangeMake(oldRange.location, rangeToPreserve.location - oldRange.location); + leftRangeToStore = HFRangeMake(newRange.location, rangeToPreserve.location - newRange.location); + rightRangeToReplace = HFRangeMake(maxPreserve, HFMaxRange(oldRange) - maxPreserve); + rightRangeToStore = HFRangeMake(maxPreserve, HFMaxRange(newRange) - maxPreserve); + } + + if (debug) NSLog(@"Changing %@ -> %@", HFRangeToString(oldRange), HFRangeToString(newRange)); + if (debug) NSLog(@"LEFT: %@ -> %@", HFRangeToString(leftRangeToReplace), HFRangeToString(leftRangeToStore)); + if (debug) NSLog(@"RIGHT: %@ -> %@", HFRangeToString(rightRangeToReplace), HFRangeToString(rightRangeToStore)); + + HFASSERT(leftRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(leftRangeToReplace, oldRange)); + HFASSERT(rightRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(rightRangeToReplace, oldRange)); + + if (leftRangeToReplace.length > 0 || leftRangeToStore.length > 0) { + NSUInteger charactersToDelete = [self characterCountForLineRange:leftRangeToReplace]; + NSRange rangeToDelete = NSMakeRange(0, charactersToDelete); + if (leftRangeToStore.length == 0) { + [textStorage deleteCharactersInRange:rangeToDelete]; + if (debug) NSLog(@"Left deleting text range %@", NSStringFromRange(rangeToDelete)); + } + else { + NSString *leftRangeString = [self newLineStringForRange:leftRangeToStore]; + [textStorage replaceCharactersInRange:rangeToDelete withString:leftRangeString]; + if (debug) NSLog(@"Replacing text range %@ with %@", NSStringFromRange(rangeToDelete), leftRangeString); + [leftRangeString release]; + } + } + + if (rightRangeToReplace.length > 0 || rightRangeToStore.length > 0) { + NSUInteger charactersToDelete = [self characterCountForLineRange:rightRangeToReplace]; + NSUInteger stringLength = [textStorage length]; + HFASSERT(charactersToDelete <= stringLength); + NSRange rangeToDelete = NSMakeRange(stringLength - charactersToDelete, charactersToDelete); + if (rightRangeToStore.length == 0) { + [textStorage deleteCharactersInRange:rangeToDelete]; + if (debug) NSLog(@"Right deleting text range %@", NSStringFromRange(rangeToDelete)); + } + else { + NSString *rightRangeString = [self newLineStringForRange:rightRangeToStore]; + [textStorage replaceCharactersInRange:rangeToDelete withString:rightRangeString]; + if (debug) NSLog(@"Replacing text range %@ with %@ (for range %@)", NSStringFromRange(rangeToDelete), rightRangeString, HFRangeToString(rightRangeToStore)); + [rightRangeString release]; + } + } + } + + if (! textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + [textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])]; + } + + [textStorage endEditing]; + +#if ! NDEBUG + NSString *comparisonString = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)]; + if (! [comparisonString isEqualToString:[textStorage string]]) { + NSLog(@"Not equal!"); + NSLog(@"Expected:\n%@", comparisonString); + NSLog(@"Actual:\n%@", [textStorage string]); + } + HFASSERT([comparisonString isEqualToString:[textStorage string]]); + [comparisonString release]; +#endif + + storedLineIndex = startingLineIndex; + storedLineCount = linesRemaining; +} + +- (void)drawLineNumbersWithClipSingleStringDrawing:(NSRect)clipRect { + USE(clipRect); + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + + CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1; + NSRect textRect = self.bounds; + textRect.size.width -= 5; + textRect.origin.y -= verticalOffset; + textRect.size.height += verticalOffset; + + if (! textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + [mutableStyle setMinimumLineHeight:_lineHeight]; + [mutableStyle setMaximumLineHeight:_lineHeight]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + } + + + NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)]; + [string drawInRect:textRect withAttributes:textAttributes]; + [string release]; +} + +- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect { + USE(clipRect); + const CGFloat cellTextContainerPadding = 2.f; + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + + CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1; + NSRect textRect = self.bounds; + textRect.size.width -= 5; + textRect.origin.y -= verticalOffset; + textRect.origin.x += cellTextContainerPadding; + textRect.size.height += verticalOffset; + + if (! textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + [mutableStyle setMinimumLineHeight:_lineHeight]; + [mutableStyle setMaximumLineHeight:_lineHeight]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + } + + NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)]; + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:textAttributes]; + [string release]; + NSCell *cell = [[NSCell alloc] initTextCell:@""]; + [cell setAttributedStringValue:attributedString]; + [cell drawWithFrame:textRect inView:self]; + [[NSColor purpleColor] set]; + NSFrameRect(textRect); + [cell release]; + [attributedString release]; +} + +- (void)drawLineNumbersWithClipFullLayoutManager:(NSRect)clipRect { + USE(clipRect); + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + if (lineIndex != storedLineIndex || linesRemaining != storedLineCount) { + [self updateLayoutManagerWithLineIndex:lineIndex lineCount:linesRemaining]; + } + + CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + + NSPoint textPoint = self.bounds.origin; + textPoint.y -= verticalOffset * _lineHeight; + [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) atPoint:textPoint]; +} + +- (void)drawLineNumbersWithClip:(NSRect)clipRect { +#if TIME_LINE_NUMBERS + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); +#endif + NSInteger drawingMode = (useStringDrawingPath ? 1 : 3); + switch (drawingMode) { + // Drawing can't be done right if fonts are wider than expected, but all + // of these have rather nasty behavior in that case. I've commented what + // that behavior is; the comment is hypothetical 'could' if it shouldn't + // actually be a problem in practice. + // TODO: Make a drawing mode that is "Fonts could get clipped if too wide" + // because that seems like better behavior than any of these. + case 0: + // Most fonts are too wide and every character gets piled on right (unreadable). + [self drawLineNumbersWithClipLayoutManagerPerLine:clipRect]; + break; + case 1: + // Last characters could get omitted (*not* clipped) if too wide. + // Also, most fonts have bottoms clipped (very unsigntly). + [self drawLineNumbersWithClipStringDrawing:clipRect]; + break; + case 2: + // Most fonts are too wide and wrap (breaks numbering). + [self drawLineNumbersWithClipFullLayoutManager:clipRect]; + break; + case 3: + // Fonts could wrap if too wide (breaks numbering). + // *Note that that this is the only mode that generally works.* + [self drawLineNumbersWithClipSingleStringDrawing:clipRect]; + break; + case 4: + // Most fonts are too wide and wrap (breaks numbering). + [self drawLineNumbersWithClipSingleStringCellDrawing:clipRect]; + break; + } +#if TIME_LINE_NUMBERS + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"Line number time: %f", endTime - startTime); +#endif +} + +- (void)drawRect:(NSRect)clipRect { + [self drawGradientWithClip:clipRect]; + [self drawDividerWithClip:clipRect]; + [self drawLineNumbersWithClip:clipRect]; +} + +- (void)setLineRangeToDraw:(HFFPRange)range { + if (! HFFPRangeEqualsRange(range, _lineRangeToDraw)) { + _lineRangeToDraw = range; + [self setNeedsDisplay:YES]; + } +} + +- (void)setBytesPerLine:(NSUInteger)val { + if (_bytesPerLine != val) { + _bytesPerLine = val; + storedLineCount = INVALID_LINE_COUNT; + [self setNeedsDisplay:YES]; + } +} + +- (void)setLineNumberFormat:(HFLineNumberFormat)format { + if (format != _lineNumberFormat) { + _lineNumberFormat = format; + storedLineCount = INVALID_LINE_COUNT; + [self setNeedsDisplay:YES]; + } +} + +- (BOOL)canUseStringDrawingPathForFont:(NSFont *)testFont { + NSString *name = [testFont fontName]; + // No, Menlo does not work here. + return [name isEqualToString:@"Monaco"] || [name isEqualToString:@"Courier"] || [name isEqualToString:@"Consolas"]; +} + +- (void)setFont:(NSFont *)val { + if (val != _font) { + [_font release]; + _font = [val copy]; + [textStorage deleteCharactersInRange:NSMakeRange(0, [textStorage length])]; //delete the characters so we know to set the font next time we render + [textAttributes release]; + textAttributes = nil; + storedLineCount = INVALID_LINE_COUNT; + useStringDrawingPath = [self canUseStringDrawingPathForFont:_font]; + [self setNeedsDisplay:YES]; + } +} + +- (void)setLineHeight:(CGFloat)height { + if (_lineHeight != height) { + _lineHeight = height; + [self setNeedsDisplay:YES]; + } +} + +- (void)setFrameSize:(NSSize)size { + [super setFrameSize:size]; + [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)]; +} + +- (void)mouseDown:(NSEvent *)event { + USE(event); + // [_representer cycleLineNumberFormat]; +} + +- (void)scrollWheel:(NSEvent *)scrollEvent { + [_representer.controller scrollWithScrollEvent:scrollEvent]; +} + ++ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format { + switch (format) { + case HFLineNumberFormatDecimal: return HFCountDigitsBase10(lineNumber); +#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX + case HFLineNumberFormatHexadecimal: return 2 + HFCountDigitsBase16(lineNumber); +#else + case HFLineNumberFormatHexadecimal: return HFCountDigitsBase16(lineNumber); +#endif + default: return 0; + } +} + +@end diff --git a/HexFiend/HFPasteboardOwner.h b/HexFiend/HFPasteboardOwner.h new file mode 100644 index 00000000..d09c5794 --- /dev/null +++ b/HexFiend/HFPasteboardOwner.h @@ -0,0 +1,51 @@ +// +// HFPasteboardOwner.h +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import + +@class HFByteArray; + +extern NSString *const HFPrivateByteArrayPboardType; + +@interface HFPasteboardOwner : NSObject { + @private + HFByteArray *byteArray; + NSPasteboard *pasteboard; //not retained + unsigned long long dataAmountToCopy; + NSUInteger bytesPerLine; + BOOL retainedSelfOnBehalfOfPboard; + BOOL backgroundCopyOperationFinished; + BOOL didStartModalSessionForBackgroundCopyOperation; +} + +/* Creates an HFPasteboardOwner to own the given pasteboard with the given types. Note that the NSPasteboard retains its owner. */ ++ (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types; +- (HFByteArray *)byteArray; + +/* Performs a copy to pasteboard with progress reporting. This must be overridden if you support types other than the private pboard type. */ +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker; + +/* NSPasteboard delegate methods, declared here to indicate that subclasses should call super */ +- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type; +- (void)pasteboardChangedOwner:(NSPasteboard *)pboard; + +/* Useful property that several pasteboard types want to know */ +@property (nonatomic) NSUInteger bytesPerLine; + +/* For efficiency, Hex Fiend writes pointers to HFByteArrays into pasteboards. In the case that the user quits and relaunches Hex Fiend, we don't want to read a pointer from the old process, so each process we generate a UUID. This is constant for the lifetime of the process. */ ++ (NSString *)uuid; + +/* Unpacks a byte array from a pasteboard, preferring HFPrivateByteArrayPboardType */ ++ (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard; + +/* Used to handle the case where copying data will require a lot of memory and give the user a chance to confirm. */ +- (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength; + +/* Must be overridden to return the length of a string containing this number of bytes. */ +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength; + +@end diff --git a/HexFiend/HFPasteboardOwner.m b/HexFiend/HFPasteboardOwner.m new file mode 100755 index 00000000..0ca341d6 --- /dev/null +++ b/HexFiend/HFPasteboardOwner.m @@ -0,0 +1,287 @@ +// +// HFPasteboardOwner.m +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import + +NSString *const HFPrivateByteArrayPboardType = @"HFPrivateByteArrayPboardType"; + +@implementation HFPasteboardOwner + ++ (void)initialize { + if (self == [HFPasteboardOwner class]) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prepareCommonPasteboardsForChangeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil]; + } +} + +- (instancetype)initWithPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types { + REQUIRE_NOT_NULL(pboard); + REQUIRE_NOT_NULL(array); + REQUIRE_NOT_NULL(types); + self = [super init]; + byteArray = [array retain]; + pasteboard = pboard; + [pasteboard declareTypes:types owner:self]; + + // get notified when we're about to write a file, so that if they're overwriting a file backing part of our byte array, we can properly clear or preserve our pasteboard + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil]; + + return self; +} ++ (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types { + return [[[self alloc] initWithPasteboard:pboard forByteArray:array withTypes:types] autorelease]; +} + +- (void)tearDownPasteboardReferenceIfExists { + if (pasteboard) { + pasteboard = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:HFPrepareForChangeInFileNotification object:nil]; + } + if (retainedSelfOnBehalfOfPboard) { + CFRelease(self); + retainedSelfOnBehalfOfPboard = NO; + } +} + + ++ (HFByteArray *)_unpackByteArrayFromDictionary:(NSDictionary *)byteArrayDictionary { + HFByteArray *result = nil; + if (byteArrayDictionary) { + NSString *uuid = byteArrayDictionary[@"HFUUID"]; + if ([uuid isEqual:[self uuid]]) { + result = (HFByteArray *)[byteArrayDictionary[@"HFByteArray"] unsignedLongValue]; + } + } + return result; +} + ++ (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard { + REQUIRE_NOT_NULL(pasteboard); + HFByteArray *result = [self _unpackByteArrayFromDictionary:[pasteboard propertyListForType:HFPrivateByteArrayPboardType]]; + return result; +} + +/* Try to fix up commonly named pasteboards when a file is about to be saved */ ++ (void)prepareCommonPasteboardsForChangeInFileNotification:(NSNotification *)notification { + const BOOL *cancellationPointer = [[notification userInfo][HFChangeInFileShouldCancelKey] pointerValue]; + if (*cancellationPointer) return; //don't do anything if someone requested cancellation + + NSDictionary *userInfo = [notification userInfo]; + NSArray *changedRanges = userInfo[HFChangeInFileModifiedRangesKey]; + HFFileReference *fileReference = [notification object]; + NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey]; + + NSString * const names[] = {NSGeneralPboard, NSFindPboard, NSDragPboard}; + NSUInteger i; + for (i=0; i < sizeof names / sizeof *names; i++) { + NSPasteboard *pboard = [NSPasteboard pasteboardWithName:names[i]]; + HFByteArray *byteArray = [self unpackByteArrayFromPasteboard:pboard]; + if (byteArray && ! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) { + /* This pasteboard no longer works */ + [pboard declareTypes:@[] owner:nil]; + } + } +} + +- (void)changeInFileNotification:(NSNotification *)notification { + HFASSERT(pasteboard != nil); + HFASSERT(byteArray != nil); + NSDictionary *userInfo = [notification userInfo]; + const BOOL *cancellationPointer = [userInfo[HFChangeInFileShouldCancelKey] pointerValue]; + if (*cancellationPointer) return; //don't do anything if someone requested cancellation + NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey]; + + NSArray *changedRanges = [notification userInfo][HFChangeInFileModifiedRangesKey]; + HFFileReference *fileReference = [notification object]; + if (! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) { + /* We can't do it */ + [self tearDownPasteboardReferenceIfExists]; + } +} + +- (void)dealloc { + [self tearDownPasteboardReferenceIfExists]; + [byteArray release]; + [super dealloc]; +} + +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker { + USE(length); + USE(pboard); + USE(type); + USE(tracker); + UNIMPLEMENTED_VOID(); +} + +- (void)backgroundMoveDataToPasteboard:(NSString *)type { + @autoreleasepool { + [self writeDataInBackgroundToPasteboard:pasteboard ofLength:dataAmountToCopy forType:type trackingProgress:nil]; + [self performSelectorOnMainThread:@selector(backgroundMoveDataFinished:) withObject:nil waitUntilDone:NO]; + } +} + +- (void)backgroundMoveDataFinished:unused { + USE(unused); + HFASSERT(backgroundCopyOperationFinished == NO); + backgroundCopyOperationFinished = YES; + if (! didStartModalSessionForBackgroundCopyOperation) { + /* We haven't started the modal session, so make sure it never happens */ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(beginModalSessionForBackgroundCopyOperation:) object:nil]; + CFRunLoopWakeUp(CFRunLoopGetCurrent()); + } + else { + /* We have started the modal session, so end it. */ + [NSApp stopModalWithCode:0]; + //stopModal: won't trigger unless we post a do-nothing event + NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:0 data1:0 data2:0]; + [NSApp postEvent:event atStart:NO]; + } +} + +- (void)beginModalSessionForBackgroundCopyOperation:(id)unused { + USE(unused); + HFASSERT(backgroundCopyOperationFinished == NO); + HFASSERT(didStartModalSessionForBackgroundCopyOperation == NO); + didStartModalSessionForBackgroundCopyOperation = YES; +} + +- (BOOL)moveDataWithProgressReportingToPasteboard:(NSPasteboard *)pboard forType:(NSString *)type { + // The -[NSRunLoop runMode:beforeDate:] call in the middle of this function can cause it to be + // called reentrantly, which was previously causing leaks and use-after-free crashes. For + // some reason this happens basically always when copying lots of data into VMware Fusion. + // I'm not even sure what the ideal behavior would be here, but am fairly certain that this + // is the best that can be done without rewriting a portion of the background copying code. + // TODO: Figure out what the ideal behavior should be here. + + HFASSERT(pboard == pasteboard); + [self retain]; //resolving the pasteboard may release us, which deallocates us, which deallocates our tracker...make sure we survive through this function + /* Give the user a chance to request a smaller amount if it's really big */ + unsigned long long availableAmount = [byteArray length]; + unsigned long long amountToCopy = [self amountToCopyForDataLength:availableAmount stringLength:[self stringLengthForDataLength:availableAmount]]; + if (amountToCopy > 0) { + + backgroundCopyOperationFinished = NO; + didStartModalSessionForBackgroundCopyOperation = NO; + dataAmountToCopy = amountToCopy; + [NSThread detachNewThreadSelector:@selector(backgroundMoveDataToPasteboard:) toTarget:self withObject:type]; + [self performSelector:@selector(beginModalSessionForBackgroundCopyOperation:) withObject:nil afterDelay:1.0 inModes:@[NSModalPanelRunLoopMode]]; + while (! backgroundCopyOperationFinished) { + [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate distantFuture]]; + } + } + [self release]; + return YES; +} + +- (void)pasteboardChangedOwner:(NSPasteboard *)pboard { + HFASSERT(pasteboard == pboard); + [self tearDownPasteboardReferenceIfExists]; +} + +- (HFByteArray *)byteArray { + return byteArray; +} + +- (void)pasteboard:(NSPasteboard *)pboard provideDataForType:(NSString *)type { + if (! pasteboard) { + /* Don't do anything, because we've torn down our pasteboard */ + return; + } + if ([type isEqualToString:HFPrivateByteArrayPboardType]) { + if (! retainedSelfOnBehalfOfPboard) { + retainedSelfOnBehalfOfPboard = YES; + CFRetain(self); + } + NSDictionary *dict = @{@"HFByteArray": @((unsigned long)byteArray), + @"HFUUID": [[self class] uuid]}; + [pboard setPropertyList:dict forType:type]; + } + else { + if (! [self moveDataWithProgressReportingToPasteboard:pboard forType:type]) { + [pboard setData:[NSData data] forType:type]; + } + } +} + +- (void)setBytesPerLine:(NSUInteger)val { bytesPerLine = val; } +- (NSUInteger)bytesPerLine { return bytesPerLine; } + ++ (NSString *)uuid { + static NSString *uuid; + if (! uuid) { + CFUUIDRef uuidRef = CFUUIDCreate(NULL); + uuid = (NSString *)CFUUIDCreateString(NULL, uuidRef); + CFRelease(uuidRef); + } + return uuid; +} + +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { USE(dataLength); UNIMPLEMENTED(); } + +- (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength { + unsigned long long dataLengthResult, stringLengthResult; + NSInteger alertReturn = NSIntegerMax; + const unsigned long long copyOption1 = MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT; + const unsigned long long copyOption2 = MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT; + NSString *option1String = HFDescribeByteCount(copyOption1); + NSString *option2String = HFDescribeByteCount(copyOption2); + NSString* dataSizeDescription = HFDescribeByteCount(stringLength); + if (stringLength >= MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT) { + NSString *option1 = [@"Copy " stringByAppendingString:option1String]; + NSString *option2 = [@"Copy " stringByAppendingString:option2String]; + alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. This is larger than the system clipboard supports. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription); + switch (alertReturn) { + case NSAlertDefaultReturn: + default: + stringLengthResult = 0; + break; + case NSAlertAlternateReturn: + stringLengthResult = copyOption1; + break; + case NSAlertOtherReturn: + stringLengthResult = copyOption2; + break; + } + + } + else if (stringLength >= MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT) { + NSString *option1 = [@"Copy " stringByAppendingString:HFDescribeByteCount(stringLength)]; + NSString *option2 = [@"Copy " stringByAppendingString:HFDescribeByteCount(copyOption2)]; + alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. Performing this copy may take a long time. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription); + switch (alertReturn) { + case NSAlertDefaultReturn: + default: + stringLengthResult = 0; + break; + case NSAlertAlternateReturn: + stringLengthResult = stringLength; + break; + case NSAlertOtherReturn: + stringLengthResult = copyOption2; + break; + } + } + else { + /* Small enough to copy it all */ + stringLengthResult = stringLength; + } + + /* Convert from string length to data length */ + if (stringLengthResult == stringLength) { + dataLengthResult = numBytes; + } + else { + unsigned long long divisor = stringLength / numBytes; + dataLengthResult = stringLengthResult / divisor; + } + + return dataLengthResult; +} + +@end diff --git a/HexFiend/HFPrivilegedHelperConnection.h b/HexFiend/HFPrivilegedHelperConnection.h new file mode 100644 index 00000000..bbee7a9a --- /dev/null +++ b/HexFiend/HFPrivilegedHelperConnection.h @@ -0,0 +1,3 @@ +#ifndef HF_NO_PRIVILEGED_FILE_OPERATIONS +#define HF_NO_PRIVILEGED_FILE_OPERATIONS +#endif \ No newline at end of file diff --git a/HexFiend/HFRepresenter.h b/HexFiend/HFRepresenter.h new file mode 100644 index 00000000..1a15f3e7 --- /dev/null +++ b/HexFiend/HFRepresenter.h @@ -0,0 +1,121 @@ +// +// HFRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +/*! @class HFRepresenter + @brief The principal view class of Hex Fiend's MVC architecture. + + HFRepresenter is a class that visually represents some property of the HFController, such as the data (in various formats), the scroll position, the line number, etc. An HFRepresenter is added to an HFController and then gets notified of changes to various properties, through the controllerDidChange: methods. + + HFRepresenters also have a view, accessible through the -view method. The HFRepresenter is expected to update its view to reflect the relevant properties of its HFController. If the user can interact with the view, then the HFRepresenter should pass any changes down to the HFController, which will subsequently notify all HFRepresenters of the change. + + HFRepresenter is an abstract class, with a different subclass for each possible view type. Because HFController interacts with HFRepresenters, rather than views directly, an HFRepresenter can use standard Cocoa views and controls. + + To add a new view type: + + -# Create a subclass of HFRepresenter + -# Override \c -createView to return a view (note that this method should transfer ownership) + -# Override \c -controllerDidChange:, checking the bitmask to see what properties have changed and updating your view as appropriate + -# If you plan on using this view together with other views, override \c +defaultLayoutPosition to control how your view gets positioned in an HFLayoutRepresenter + -# If your view's width depends on the properties of the controller, override some of the measurement methods, such as \c +maximumBytesPerLineForViewWidth:, so that your view gets sized correctly + +*/ +@interface HFRepresenter : NSObject { + @private + id view; + HFController *controller; + NSPoint layoutPosition; +} + +/*! @name View management + Methods related to accessing and initializing the representer's view. +*/ +//@{ +/*! Returns the view for the receiver, creating it if necessary. The view for the HFRepresenter is initially nil. When the \c -view method is called, if the view is nil, \c -createView is called and then the result is stored. This method should not be overridden; however you may want to call it to access the view. +*/ +- (id)view; + +/*! Returns YES if the view has been created, NO if it has not. To create the view, call the view method. + */ +- (BOOL)isViewLoaded; + +/*! Override point for creating the view displaying this representation. This is called on your behalf the first time the \c -view method is called, so you would not want to call this explicitly; however this method must be overridden. This follows the "create" rule, and so it should return a retained view. +*/ +- (NSView *)createView NS_RETURNS_RETAINED; + +/*! Override point for initialization of view, after the HFRepresenter has the view set as its -view property. The default implementation does nothing. +*/ +- (void)initializeView; + +//@} + +/*! @name Accessing the HFController +*/ +//@{ +/*! Returns the HFController for the receiver. This is set by the controller from the call to \c addRepresenter:. A representer can only be in one controller at a time. */ +- (HFController *)controller; +//@} + +/*! @name Property change notifications +*/ +//@{ +/*! Indicates that the properties indicated by the given bits did change, and the view should be updated as to reflect the appropriate properties. This is the main mechanism by which representers are notified of changes to the controller. +*/ +- (void)controllerDidChange:(HFControllerPropertyBits)bits; +//@} + +/*! @name HFController convenience methods + Convenience covers for certain HFController methods +*/ +//@{ +/*! Equivalent to [[self controller] bytesPerLine] */ +- (NSUInteger)bytesPerLine; + +/*! Equivalent to [[self controller] bytesPerColumn] */ +- (NSUInteger)bytesPerColumn; + +/*! Equivalent to [[self controller] representer:self changedProperties:properties] . You may call this when some internal aspect of the receiver's view (such as its frame) has changed in a way that may globally change some property of the controller, and the controller should recalculate those properties. For example, the text representers call this with HFControllerDisplayedLineRange when the view grows vertically, because more data may be displayed. +*/ +- (void)representerChangedProperties:(HFControllerPropertyBits)properties; +//@} + +/*! @name Measurement + Methods related to measuring the HFRepresenter, so that it can be laid out properly by an HFLayoutController. All of these methods are candidates for overriding. +*/ +//@{ +/*! Returns the maximum number of bytes per line for the given view size. The default value is NSUIntegerMax, which means that the representer can display any number of lines for the given view size. */ +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth; + +/*! Returns the minimum view frame size for the given bytes per line. Default is to return 0, which means that the representer can display the given bytes per line in any view size. Fixed width views should return their fixed width. */ +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine; + +/*! Returns the maximum number of lines that could be displayed at once for a given view height. Default is to return DBL_MAX. */ +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight; +//@} + +/*! Returns the required byte granularity. HFLayoutRepresenter will constrain the bytes per line to a multiple of the granularity, e.g. so that UTF-16 characters are not split across lines. If different representers have different granularities, then it will constrain it to a multiple of all granularities, which may be very large. The default implementation returns 1. */ +- (NSUInteger)byteGranularity; + +/*! @name Auto-layout methods + Methods for simple auto-layout by HFLayoutRepresenter. See the HFLayoutRepresenter class for discussion of how it lays out representer views. +*/ +//@{ + + +/// The layout position for the receiver. +@property (nonatomic) NSPoint layoutPosition; + +/*! Returns the default layout position for representers of this class. Within the -init method, the view's layout position is set to the default for this class. You may override this to control the default layout position. See HFLayoutRepresenter for a discussion of the significance of the layout postition. +*/ ++ (NSPoint)defaultLayoutPosition; + +//@} + + +@end diff --git a/HexFiend/HFRepresenter.m b/HexFiend/HFRepresenter.m new file mode 100644 index 00000000..510e3a0c --- /dev/null +++ b/HexFiend/HFRepresenter.m @@ -0,0 +1,120 @@ +// +// HFRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import "HFRepresenter.h" + +@implementation HFRepresenter + +- (id)view { + if (! view) { + view = [self createView]; + [self initializeView]; + } + return view; +} + +- (BOOL)isViewLoaded { + return !! view; +} + +- (void)initializeView { + +} + +- (instancetype)init { + self = [super init]; + [self setLayoutPosition:[[self class] defaultLayoutPosition]]; + return self; +} + +- (void)dealloc { + [view release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [coder encodeObject:controller forKey:@"HFController"]; + [coder encodePoint:layoutPosition forKey:@"HFLayoutPosition"]; + [coder encodeObject:view forKey:@"HFRepresenterView"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super init]; + layoutPosition = [coder decodePointForKey:@"HFLayoutPosition"]; + controller = [coder decodeObjectForKey:@"HFController"]; // not retained + view = [[coder decodeObjectForKey:@"HFRepresenterView"] retain]; + return self; +} + +- (NSView *)createView { + UNIMPLEMENTED(); +} + +- (HFController *)controller { + return controller; +} + +- (void)_setController:(HFController *)val { + controller = val; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + USE(bits); +} + +- (NSUInteger)bytesPerLine { + HFASSERT([self controller] != nil); + return [[self controller] bytesPerLine]; +} + +- (NSUInteger)bytesPerColumn { + HFASSERT([self controller] != nil); + return [[self controller] bytesPerColumn]; +} + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth { + USE(viewWidth); + return NSUIntegerMax; +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + USE(bytesPerLine); + return 0; +} + +- (NSUInteger)byteGranularity { + return 1; +} + +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight { + USE(viewHeight); + return DBL_MAX; +} + +- (void)selectAll:sender { + [[self controller] selectAll:sender]; +} + +- (void)representerChangedProperties:(HFControllerPropertyBits)properties { + [[self controller] representer:self changedProperties:properties]; +} + +- (void)setLayoutPosition:(NSPoint)position { + layoutPosition = position; +} + +- (NSPoint)layoutPosition { + return layoutPosition; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(0, 0); +} + +@end diff --git a/HexFiend/HFRepresenterHexTextView.h b/HexFiend/HFRepresenterHexTextView.h new file mode 100644 index 00000000..8098846e --- /dev/null +++ b/HexFiend/HFRepresenterHexTextView.h @@ -0,0 +1,21 @@ +// +// HFRepresenterHexTextView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + + +@interface HFRepresenterHexTextView : HFRepresenterTextView { + CGGlyph glyphTable[17]; + CGFloat glyphAdvancement; + CGFloat spaceAdvancement; + + BOOL hidesNullBytes; +} + +@property(nonatomic) BOOL hidesNullBytes; + +@end diff --git a/HexFiend/HFRepresenterHexTextView.m b/HexFiend/HFRepresenterHexTextView.m new file mode 100644 index 00000000..6df41d80 --- /dev/null +++ b/HexFiend/HFRepresenterHexTextView.m @@ -0,0 +1,95 @@ +// +// HFRepresenterHexTextView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +@implementation HFRepresenterHexTextView + +- (void)generateGlyphTable { + const UniChar hexchars[17] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',' '/* Plus a space char at the end for null bytes. */}; + _Static_assert(sizeof(CGGlyph[17]) == sizeof(glyphTable), "glyphTable is the wrong type"); + NSFont *font = [[self font] screenFont]; + + bool t = CTFontGetGlyphsForCharacters((CTFontRef)font, hexchars, glyphTable, 17); + HFASSERT(t); // We don't take kindly to strange fonts around here. + + CGFloat maxAdv = 0.0; + for(int i = 0; i < 17; i++) maxAdv = HFMax(maxAdv, [font advancementForGlyph:glyphTable[i]].width); + glyphAdvancement = maxAdv; + spaceAdvancement = maxAdv; +} + +- (void)setFont:(NSFont *)font { + [super setFont:font]; + [self generateGlyphTable]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + [self generateGlyphTable]; + return self; +} + +//no need for encodeWithCoder + +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount { + HFASSERT(bytes != NULL); + HFASSERT(glyphs != NULL); + HFASSERT(numBytes <= NSUIntegerMax); + HFASSERT(resultGlyphCount != NULL); + const NSUInteger bytesPerColumn = [self bytesPerColumn]; + NSUInteger glyphIndex = 0, byteIndex = 0; + NSUInteger remainingBytesInThisColumn = (bytesPerColumn ? bytesPerColumn - offsetIntoLine % bytesPerColumn : NSUIntegerMax); + CGFloat advanceBetweenColumns = [self advanceBetweenColumns]; + while (byteIndex < numBytes) { + unsigned char byte = bytes[byteIndex++]; + + CGFloat glyphAdvancementPlusAnySpace = glyphAdvancement; + if (--remainingBytesInThisColumn == 0) { + remainingBytesInThisColumn = bytesPerColumn; + glyphAdvancementPlusAnySpace += advanceBetweenColumns; + } + + BOOL useBlank = (hidesNullBytes && byte == 0); + advances[glyphIndex] = CGSizeMake(glyphAdvancement, 0); + glyphs[glyphIndex++] = (struct HFGlyph_t){.fontIndex = 0, .glyph = glyphTable[(useBlank? 16: byte >> 4)]}; + advances[glyphIndex] = CGSizeMake(glyphAdvancementPlusAnySpace, 0); + glyphs[glyphIndex++] = (struct HFGlyph_t){.fontIndex = 0, .glyph = glyphTable[(useBlank? 16: byte & 0xF)]}; + } + + *resultGlyphCount = glyphIndex; +} + +- (CGFloat)advancePerCharacter { + return 2 * glyphAdvancement; +} + +- (CGFloat)advanceBetweenColumns { + return glyphAdvancement; +} + +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount { + return 2 * byteCount; +} + +- (BOOL)hidesNullBytes { + return hidesNullBytes; +} + +- (void)setHidesNullBytes:(BOOL)flag +{ + flag = !! flag; + if (hidesNullBytes != flag) { + hidesNullBytes = flag; + [self setNeedsDisplay:YES]; + } +} + +@end diff --git a/HexFiend/HFRepresenterStringEncodingTextView.h b/HexFiend/HFRepresenterStringEncodingTextView.h new file mode 100644 index 00000000..2a87adae --- /dev/null +++ b/HexFiend/HFRepresenterStringEncodingTextView.h @@ -0,0 +1,37 @@ +// +// HFRepresenterStringEncodingTextView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +@interface HFRepresenterStringEncodingTextView : HFRepresenterTextView { + /* Tier 0 data (always up to date) */ + NSStringEncoding encoding; + uint8_t bytesPerChar; + + /* Tier 1 data (computed synchronously on-demand) */ + BOOL tier1DataIsStale; + struct HFGlyph_t replacementGlyph; + CGFloat glyphAdvancement; + + /* Tier 2 data (computed asynchronously on-demand) */ + struct HFGlyphTrie_t glyphTable; + + NSArray *fontCache; + + /* Background thread */ + OSSpinLock glyphLoadLock; + BOOL requestedCancel; + NSMutableArray *fonts; + NSMutableIndexSet *requestedCharacters; + NSOperationQueue *glyphLoader; +} + +/// Set and get the NSStringEncoding that is used +@property (nonatomic) NSStringEncoding encoding; + +@end diff --git a/HexFiend/HFRepresenterStringEncodingTextView.m b/HexFiend/HFRepresenterStringEncodingTextView.m new file mode 100644 index 00000000..fa8bcb1b --- /dev/null +++ b/HexFiend/HFRepresenterStringEncodingTextView.m @@ -0,0 +1,540 @@ +// +// HFRepresenterStringEncodingTextView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#include + +@implementation HFRepresenterStringEncodingTextView + +static NSString *copy1CharStringForByteValue(unsigned long long byteValue, NSUInteger bytesPerChar, NSStringEncoding encoding) { + NSString *result = nil; + unsigned char bytes[sizeof byteValue]; + /* If we are little endian, then the bytesPerChar doesn't matter, because it will all come out the same. If we are big endian, then it does matter. */ +#if ! __BIG_ENDIAN__ + *(unsigned long long *)bytes = byteValue; +#else + if (bytesPerChar == sizeof(uint8_t)) { + *(uint8_t *)bytes = (uint8_t)byteValue; + } else if (bytesPerChar == sizeof(uint16_t)) { + *(uint16_t *)bytes = (uint16_t)byteValue; + } else if (bytesPerChar == sizeof(uint32_t)) { + *(uint32_t *)bytes = (uint32_t)byteValue; + } else if (bytesPerChar == sizeof(uint64_t)) { + *(uint64_t *)bytes = (uint64_t)byteValue; + } else { + [NSException raise:NSInvalidArgumentException format:@"Unsupported bytesPerChar of %u", bytesPerChar]; + } +#endif + + /* ASCII is mishandled :( */ + BOOL encodingOK = YES; + if (encoding == NSASCIIStringEncoding && bytesPerChar == 1 && bytes[0] > 0x7F) { + encodingOK = NO; + } + + + + /* Now create a string from these bytes */ + if (encodingOK) { + result = [[NSString alloc] initWithBytes:bytes length:bytesPerChar encoding:encoding]; + + if ([result length] > 1) { + /* Try precomposing it */ + NSString *temp = [[result precomposedStringWithCompatibilityMapping] copy]; + [result release]; + result = temp; + } + + /* Ensure it has exactly one character */ + if ([result length] != 1) { + [result release]; + result = nil; + } + } + + /* All done */ + return result; +} + +static BOOL getGlyphs(CGGlyph *glyphs, NSString *string, NSFont *inputFont) { + NSUInteger length = [string length]; + HFASSERT(inputFont != nil); + NEW_ARRAY(UniChar, chars, length); + [string getCharacters:chars range:NSMakeRange(0, length)]; + bool result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length); + /* A NO return means some or all characters were not mapped. This is OK. We'll use the replacement glyph. Unless we're calculating the replacement glyph! Hmm...maybe we should have a series of replacement glyphs that we try? */ + + //////////////////////// + // Workaround for a Mavericks bug. Still present as of 10.9.5 + // TODO: Hmm, still? Should look into this again, either it's not a bug or Apple needs a poke. + if(!result) for(NSUInteger i = 0; i < length; i+=15) { + CFIndex x = length-i; + if(x > 15) x = 15; + result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars+i, glyphs+i, x); + if(!result) break; + } + //////////////////////// + + FREE_ARRAY(chars); + return result; +} + +static void generateGlyphs(NSFont *baseFont, NSMutableArray *fonts, struct HFGlyph_t *outGlyphs, NSInteger bytesPerChar, NSStringEncoding encoding, const NSUInteger *charactersToLoad, NSUInteger charactersToLoadCount, CGFloat *outMaxAdvance) { + /* If the caller wants the advance, initialize it to 0 */ + if (outMaxAdvance) *outMaxAdvance = 0; + + /* Invalid glyph marker */ + const struct HFGlyph_t invalidGlyph = {.fontIndex = kHFGlyphFontIndexInvalid, .glyph = -1}; + + NSCharacterSet *coveredSet = [baseFont coveredCharacterSet]; + NSMutableString *coveredGlyphFetchingString = [[NSMutableString alloc] init]; + NSMutableIndexSet *coveredGlyphIndexes = [[NSMutableIndexSet alloc] init]; + NSMutableString *substitutionFontsGlyphFetchingString = [[NSMutableString alloc] init]; + NSMutableIndexSet *substitutionGlyphIndexes = [[NSMutableIndexSet alloc] init]; + + /* Loop over all the characters, appending them to our glyph fetching string */ + NSUInteger idx; + for (idx = 0; idx < charactersToLoadCount; idx++) { + NSString *string = copy1CharStringForByteValue(charactersToLoad[idx], bytesPerChar, encoding); + if (string == nil) { + /* This byte value is not represented in this char set (e.g. upper 128 in ASCII) */ + outGlyphs[idx] = invalidGlyph; + } else { + if ([coveredSet characterIsMember:[string characterAtIndex:0]]) { + /* It's covered by our base font */ + [coveredGlyphFetchingString appendString:string]; + [coveredGlyphIndexes addIndex:idx]; + } else { + /* Maybe there's a substitution font */ + [substitutionFontsGlyphFetchingString appendString:string]; + [substitutionGlyphIndexes addIndex:idx]; + } + } + [string release]; + } + + + /* Fetch the non-substitute glyphs */ + { + NEW_ARRAY(CGGlyph, cgglyphs, [coveredGlyphFetchingString length]); + BOOL success = getGlyphs(cgglyphs, coveredGlyphFetchingString, baseFont); + HFASSERT(success == YES); + NSUInteger numGlyphs = [coveredGlyphFetchingString length]; + + /* Fill in our glyphs array */ + NSUInteger coveredGlyphIdx = [coveredGlyphIndexes firstIndex]; + for (NSUInteger i=0; i < numGlyphs; i++) { + outGlyphs[coveredGlyphIdx] = (struct HFGlyph_t){.fontIndex = 0, .glyph = cgglyphs[i]}; + coveredGlyphIdx = [coveredGlyphIndexes indexGreaterThanIndex:coveredGlyphIdx]; + + /* Record the advancement. Note that this may be more efficient to do in bulk. */ + if (outMaxAdvance) *outMaxAdvance = HFMax(*outMaxAdvance, [baseFont advancementForGlyph:cgglyphs[i]].width); + + } + HFASSERT(coveredGlyphIdx == NSNotFound); //we must have exhausted the table + FREE_ARRAY(cgglyphs); + } + + /* Now do substitution glyphs. */ + { + NSUInteger substitutionGlyphIndex = [substitutionGlyphIndexes firstIndex], numSubstitutionChars = [substitutionFontsGlyphFetchingString length]; + for (NSUInteger i=0; i < numSubstitutionChars; i++) { + CTFontRef substitutionFont = CTFontCreateForString((CTFontRef)baseFont, (CFStringRef)substitutionFontsGlyphFetchingString, CFRangeMake(i, 1)); + if (substitutionFont) { + /* We have a font for this string */ + CGGlyph glyph; + unichar c = [substitutionFontsGlyphFetchingString characterAtIndex:i]; + NSString *substring = [[NSString alloc] initWithCharacters:&c length:1]; + BOOL success = getGlyphs(&glyph, substring, (NSFont *)substitutionFont); + [substring release]; + + if (! success) { + /* Turns out there wasn't a glyph like we thought there would be, so set an invalid glyph marker */ + outGlyphs[substitutionGlyphIndex] = invalidGlyph; + } else { + /* Find the index in fonts. If none, add to it. */ + HFASSERT(fonts != nil); + NSUInteger fontIndex = [fonts indexOfObject:(id)substitutionFont]; + if (fontIndex == NSNotFound) { + [fonts addObject:(id)substitutionFont]; + fontIndex = [fonts count] - 1; + } + + /* Now make the glyph */ + HFASSERT(fontIndex < UINT16_MAX); + outGlyphs[substitutionGlyphIndex] = (struct HFGlyph_t){.fontIndex = (uint16_t)fontIndex, .glyph = glyph}; + } + + /* We're done with this */ + CFRelease(substitutionFont); + + } + substitutionGlyphIndex = [substitutionGlyphIndexes indexGreaterThanIndex:substitutionGlyphIndex]; + } + } + + [coveredGlyphFetchingString release]; + [coveredGlyphIndexes release]; + [substitutionFontsGlyphFetchingString release]; + [substitutionGlyphIndexes release]; +} + +static int compareGlyphFontIndexes(const void *p1, const void *p2) { + const struct HFGlyph_t *g1 = p1, *g2 = p2; + if (g1->fontIndex != g2->fontIndex) { + /* Prefer to sort by font index */ + return (g1->fontIndex > g2->fontIndex) - (g2->fontIndex > g1->fontIndex); + } else { + /* If they have equal font indexes, sort by glyph value */ + return (g1->glyph > g2->glyph) - (g2->glyph > g1->glyph); + } +} + +- (void)threadedPrecacheGlyphs:(const struct HFGlyph_t *)glyphs withFonts:(NSArray *)localFonts count:(NSUInteger)count { + /* This method draws glyphs anywhere, so that they get cached by CG and drawing them a second time can be fast. */ + NSUInteger i, validGlyphCount; + + /* We can use 0 advances */ + NEW_ARRAY(CGSize, advances, count); + bzero(advances, count * sizeof *advances); + + /* Make a local copy of the glyphs, and sort them according to their font index so that we can draw them with the fewest runs. */ + NEW_ARRAY(struct HFGlyph_t, validGlyphs, count); + + validGlyphCount = 0; + for (i=0; i < count; i++) { + if (glyphs[i].glyph <= kCGGlyphMax && glyphs[i].fontIndex != kHFGlyphFontIndexInvalid) { + validGlyphs[validGlyphCount++] = glyphs[i]; + } + } + qsort(validGlyphs, validGlyphCount, sizeof *validGlyphs, compareGlyphFontIndexes); + + /* Remove duplicate glyphs */ + NSUInteger trailing = 0; + struct HFGlyph_t lastGlyph = {.glyph = kCGFontIndexInvalid, .fontIndex = kHFGlyphFontIndexInvalid}; + for (i=0; i < validGlyphCount; i++) { + if (! HFGlyphEqualsGlyph(lastGlyph, validGlyphs[i])) { + lastGlyph = validGlyphs[i]; + validGlyphs[trailing++] = lastGlyph; + } + } + validGlyphCount = trailing; + + /* Draw the glyphs in runs */ + NEW_ARRAY(CGGlyph, cgglyphs, count); + NSImage *glyphDrawingImage = [[NSImage alloc] initWithSize:NSMakeSize(100, 100)]; + [glyphDrawingImage lockFocus]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + HFGlyphFontIndex runFontIndex = -1; + NSUInteger runLength = 0; + for (i=0; i <= validGlyphCount; i++) { + if (i == validGlyphCount || validGlyphs[i].fontIndex != runFontIndex) { + /* End the current run */ + if (runLength > 0) { + NSLog(@"Drawing with %@", [localFonts[runFontIndex] screenFont]); + [[localFonts[runFontIndex] screenFont] set]; + CGContextSetTextPosition(ctx, 0, 50); + CGContextShowGlyphsWithAdvances(ctx, cgglyphs, advances, runLength); + } + NSLog(@"Drew a run of length %lu", (unsigned long)runLength); + runLength = 0; + if (i < validGlyphCount) runFontIndex = validGlyphs[i].fontIndex; + } + if (i < validGlyphCount) { + /* Append to the current run */ + cgglyphs[runLength++] = validGlyphs[i].glyph; + } + } + + /* All done */ + [glyphDrawingImage unlockFocus]; + [glyphDrawingImage release]; + FREE_ARRAY(advances); + FREE_ARRAY(validGlyphs); + FREE_ARRAY(cgglyphs); +} + +- (void)threadedLoadGlyphs:(id)unused { + /* Note that this is running on a background thread */ + USE(unused); + + /* Do some things under the lock. Someone else may wish to read fonts, and we're going to write to it, so make a local copy. Also figure out what characters to load. */ + NSMutableArray *localFonts; + NSIndexSet *charactersToLoad; + OSSpinLockLock(&glyphLoadLock); + localFonts = [fonts mutableCopy]; + charactersToLoad = requestedCharacters; + /* Set requestedCharacters to nil so that the caller knows we aren't going to check again, and will have to re-invoke us. */ + requestedCharacters = nil; + OSSpinLockUnlock(&glyphLoadLock); + + /* The base font is the first font */ + NSFont *font = localFonts[0]; + + NSUInteger charVal, glyphIdx, charCount = [charactersToLoad count]; + NEW_ARRAY(struct HFGlyph_t, glyphs, charCount); + + /* Now generate our glyphs */ + NEW_ARRAY(NSUInteger, characters, charCount); + [charactersToLoad getIndexes:characters maxCount:charCount inIndexRange:NULL]; + generateGlyphs(font, localFonts, glyphs, bytesPerChar, encoding, characters, charCount, NULL); + FREE_ARRAY(characters); + + /* The first time we draw glyphs, it's slow, so pre-cache them by drawing them now. */ + // This was disabled because it blows up the CG glyph cache + // [self threadedPrecacheGlyphs:glyphs withFonts:localFonts count:charCount]; + + /* Replace fonts. Do this before we insert into the glyph trie, because the glyph trie references fonts that we're just now putting in the fonts array. */ + id oldFonts; + OSSpinLockLock(&glyphLoadLock); + oldFonts = fonts; + fonts = localFonts; + OSSpinLockUnlock(&glyphLoadLock); + [oldFonts release]; + + /* Now insert all of the glyphs into the glyph trie */ + glyphIdx = 0; + for (charVal = [charactersToLoad firstIndex]; charVal != NSNotFound; charVal = [charactersToLoad indexGreaterThanIndex:charVal]) { + HFGlyphTrieInsert(&glyphTable, charVal, glyphs[glyphIdx++]); + } + FREE_ARRAY(glyphs); + + /* Trigger a redisplay */ + [self performSelectorOnMainThread:@selector(triggerRedisplay:) withObject:nil waitUntilDone:NO]; + + /* All done. We inherited the retain on requestedCharacters, so release it. */ + [charactersToLoad release]; +} + +- (void)triggerRedisplay:unused { + USE(unused); + [self setNeedsDisplay:YES]; +} + +- (void)beginLoadGlyphsForCharacters:(NSIndexSet *)charactersToLoad { + /* Create the operation (and maybe the operation queue itself) */ + if (! glyphLoader) { + glyphLoader = [[NSOperationQueue alloc] init]; + [glyphLoader setMaxConcurrentOperationCount:1]; + } + if (! fonts) { + NSFont *font = [self font]; + fonts = [[NSMutableArray alloc] initWithObjects:&font count:1]; + } + + BOOL needToStartOperation; + OSSpinLockLock(&glyphLoadLock); + if (requestedCharacters) { + /* There's a pending request, so just add to it */ + [requestedCharacters addIndexes:charactersToLoad]; + needToStartOperation = NO; + } else { + /* There's no pending request, so we will create one */ + requestedCharacters = [charactersToLoad mutableCopy]; + needToStartOperation = YES; + } + OSSpinLockUnlock(&glyphLoadLock); + + if (needToStartOperation) { + NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadedLoadGlyphs:) object:charactersToLoad]; + [glyphLoader addOperation:op]; + [op release]; + } +} + +- (void)dealloc { + HFGlyphTreeFree(&glyphTable); + [fonts release]; + [super dealloc]; +} + +- (void)staleTieredProperties { + tier1DataIsStale = YES; + /* We have to free the glyph table */ + requestedCancel = YES; + [glyphLoader waitUntilAllOperationsAreFinished]; + requestedCancel = NO; + HFGlyphTreeFree(&glyphTable); + HFGlyphTrieInitialize(&glyphTable, bytesPerChar); + [fonts release]; + fonts = nil; + [fontCache release]; + fontCache = nil; +} + +- (void)setFont:(NSFont *)font { + [self staleTieredProperties]; + /* fonts is preloaded with our one font */ + if (! fonts) fonts = [[NSMutableArray alloc] init]; + [fonts addObject:font]; + [super setFont:font]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + encoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"]; + bytesPerChar = HFStringEncodingCharacterLength(encoding); + [self staleTieredProperties]; + return self; +} + +- (instancetype)initWithFrame:(NSRect)frameRect { + self = [super initWithFrame:frameRect]; + encoding = NSMacOSRomanStringEncoding; + bytesPerChar = HFStringEncodingCharacterLength(encoding); + [self staleTieredProperties]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeInt64:encoding forKey:@"HFStringEncoding"]; +} + +- (NSStringEncoding)encoding { + return encoding; +} + +- (void)setEncoding:(NSStringEncoding)val { + if (encoding != val) { + /* Our glyph table is now stale. Call this first to ensure our background operation is complete. */ + [self staleTieredProperties]; + + /* Store the new encoding. */ + encoding = val; + + /* Compute bytes per character */ + bytesPerChar = HFStringEncodingCharacterLength(encoding); + HFASSERT(bytesPerChar > 0); + + /* Ensure the tree knows about the new bytes per character */ + HFGlyphTrieInitialize(&glyphTable, bytesPerChar); + + /* Redraw ourselves with our new glyphs */ + [self setNeedsDisplay:YES]; + } +} + +- (void)loadTier1Data { + NSFont *font = [self font]; + + /* Use the max advance as the glyph advance */ + glyphAdvancement = HFCeil([font maximumAdvancement].width); + + /* Generate replacementGlyph */ + CGGlyph glyph[1]; + BOOL foundReplacement = NO; + if (! foundReplacement) foundReplacement = getGlyphs(glyph, @".", font); + if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"*", font); + if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"!", font); + if (! foundReplacement) { + /* Really we should just fall back to another font in this case */ + [NSException raise:NSInternalInconsistencyException format:@"Unable to find replacement glyph for font %@", font]; + } + replacementGlyph.fontIndex = 0; + replacementGlyph.glyph = glyph[0]; + + /* We're no longer stale */ + tier1DataIsStale = NO; +} + +/* Override of base class method for font substitution */ +- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx { + HFASSERT(idx != kHFGlyphFontIndexInvalid); + if (idx >= [fontCache count]) { + /* Our font cache is out of date. Take the lock and update the cache. */ + NSArray *newFonts = nil; + OSSpinLockLock(&glyphLoadLock); + HFASSERT(idx < [fonts count]); + newFonts = [fonts copy]; + OSSpinLockUnlock(&glyphLoadLock); + + /* Store the new cache */ + [fontCache release]; + fontCache = newFonts; + + /* Now our cache should be up to date */ + HFASSERT(idx < [fontCache count]); + } + return fontCache[idx]; +} + +/* Override of base class method in case we are 16 bit */ +- (NSUInteger)bytesPerCharacter { + return bytesPerChar; +} + +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount { + HFASSERT(bytes != NULL); + HFASSERT(glyphs != NULL); + HFASSERT(resultGlyphCount != NULL); + HFASSERT(advances != NULL); + USE(offsetIntoLine); + + /* Ensure we have advance, etc. before trying to use it */ + if (tier1DataIsStale) [self loadTier1Data]; + + CGSize advance = CGSizeMake(glyphAdvancement, 0); + NSMutableIndexSet *charactersToLoad = nil; //note: in UTF-32 this may have to move to an NSSet + + const uint8_t localBytesPerChar = bytesPerChar; + NSUInteger charIndex, numChars = numBytes / localBytesPerChar, byteIndex = 0; + for (charIndex = 0; charIndex < numChars; charIndex++) { + NSUInteger character = -1; + if (localBytesPerChar == 1) { + character = *(const uint8_t *)(bytes + byteIndex); + } else if (localBytesPerChar == 2) { + character = *(const uint16_t *)(bytes + byteIndex); + } else if (localBytesPerChar == 4) { + character = *(const uint32_t *)(bytes + byteIndex); + } + + struct HFGlyph_t glyph = HFGlyphTrieGet(&glyphTable, character); + if (glyph.glyph == 0 && glyph.fontIndex == 0) { + /* Unloaded glyph, so load it */ + if (! charactersToLoad) charactersToLoad = [[NSMutableIndexSet alloc] init]; + [charactersToLoad addIndex:character]; + glyph = replacementGlyph; + } else if (glyph.glyph == (uint16_t)-1 && glyph.fontIndex == kHFGlyphFontIndexInvalid) { + /* Missing glyph, so ignore it */ + glyph = replacementGlyph; + } else { + /* Valid glyph */ + } + + HFASSERT(glyph.fontIndex != kHFGlyphFontIndexInvalid); + + advances[charIndex] = advance; + glyphs[charIndex] = glyph; + byteIndex += localBytesPerChar; + } + *resultGlyphCount = numChars; + + if (charactersToLoad) { + [self beginLoadGlyphsForCharacters:charactersToLoad]; + [charactersToLoad release]; + } +} + +- (CGFloat)advancePerCharacter { + /* The glyph advancement is determined by our glyph table */ + if (tier1DataIsStale) [self loadTier1Data]; + return glyphAdvancement; +} + +- (CGFloat)advanceBetweenColumns { + return 0; //don't have any space between columns +} + +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount { + return byteCount / [self bytesPerCharacter]; +} + +@end diff --git a/HexFiend/HFRepresenterTextView.h b/HexFiend/HFRepresenterTextView.h new file mode 100644 index 00000000..7e9edbb6 --- /dev/null +++ b/HexFiend/HFRepresenterTextView.h @@ -0,0 +1,146 @@ +// +// HFRepresenterTextView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +/* Bytes per column philosophy + + _hftvflags.bytesPerColumn is the number of bytes that should be displayed consecutively, as one column. A space separates one column from the next. HexFiend 1.0 displayed 1 byte per column, and setting bytesPerColumn to 1 in this version reproduces that behavior. The vertical guidelines displayed by HexFiend 1.0 are only drawn when bytesPerColumn is set to 1. + + We use some number of bits to hold the number of bytes per column, so the highest value we can store is ((2 ^ numBits) - 1). We can't tell the user that the max is not a power of 2, so we pin the value to the highest representable power of 2, or (2 ^ (numBits - 1)). We allow integral values from 0 to the pinned maximum, inclusive; powers of 2 are not required. The setter method uses HFTV_BYTES_PER_COLUMN_MAX_VALUE to stay within the representable range. + + Since a value of zero is nonsensical, we can use it to specify no spaces at all. +*/ + +#define HFTV_BYTES_PER_COLUMN_MAX_VALUE (1 << (HFTV_BYTES_PER_COLUMN_BITFIELD_SIZE - 1)) + +@class HFTextRepresenter; + + +/* The base class for HFTextRepresenter views - such as the hex or ASCII text view */ +@interface HFRepresenterTextView : NSView { +@private; + HFTextRepresenter *representer; + NSArray *cachedSelectedRanges; + CGFloat verticalOffset; + CGFloat horizontalContainerInset; + CGFloat defaultLineHeight; + NSTimer *caretTimer; + NSWindow *pulseWindow; + NSRect pulseWindowBaseFrameInScreenCoordinates; + NSRect lastDrawnCaretRect; + NSRect caretRectToDraw; + NSUInteger bytesBetweenVerticalGuides; + NSUInteger startingLineBackgroundColorIndex; + NSArray *rowBackgroundColors; + NSMutableDictionary *callouts; + + void (^byteColoring)(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a); + + struct { + unsigned antialias:1; + unsigned drawCallouts:1; + unsigned editable:1; + unsigned caretVisible:1; + unsigned registeredForAppNotifications:1; + unsigned withinMouseDown:1; + unsigned receivedMouseUp:1; + } _hftvflags; +} + +- (instancetype)initWithRepresenter:(HFTextRepresenter *)rep; +- (void)clearRepresenter; + +- (HFTextRepresenter *)representer; + +@property (nonatomic, copy) NSFont *font; + +/* Set and get data. setData: will invalidate the correct regions (perhaps none) */ +@property (nonatomic, copy) NSData *data; +@property (nonatomic) CGFloat verticalOffset; +@property (nonatomic) NSUInteger startingLineBackgroundColorIndex; +@property (nonatomic, getter=isEditable) BOOL editable; +@property (nonatomic, copy) NSArray *styles; +@property (nonatomic) BOOL shouldAntialias; + +- (BOOL)behavesAsTextField; +- (BOOL)showsFocusRing; +- (BOOL)isWithinMouseDown; + +- (NSRect)caretRect; + +@property (nonatomic) BOOL shouldDrawCallouts; + +- (void)setByteColoring:(void (^)(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a))coloring; + +- (NSPoint)originForCharacterAtByteIndex:(NSInteger)index; +- (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point; + +/* The amount of padding space to inset from the left and right side. */ +@property (nonatomic) CGFloat horizontalContainerInset; + +/* The number of bytes between vertical guides. 0 means no drawing of guides. */ +@property (nonatomic) NSUInteger bytesBetweenVerticalGuides; + +/* To be invoked from drawRect:. */ +- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect; +- (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect; + +/* For font substitution. An index of 0 means the default (base) font. */ +- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx; + +/* Uniformly "rounds" the byte range so that it contains an integer number of characters. The algorithm is to "floor:" any character intersecting the min of the range are included, and any character extending beyond the end of the range is excluded. If both the min and the max are within a single character, then an empty range is returned. */ +- (NSRange)roundPartialByteRange:(NSRange)byteRange; + +- (void)drawTextWithClip:(NSRect)clipRect restrictingToTextInRanges:(NSArray *)restrictingToRanges; + +/* Must be overridden */ +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount; + +- (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount; + +/* Must be overridden - returns the max number of glyphs for a given number of bytes */ +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount; + +- (void)updateSelectedRanges; +- (void)terminateSelectionPulse; // Start fading the pulse. + +/* Given a rect edge, return an NSRect representing the maximum edge in that direction. The dimension in the direction of the edge is 0 (so if edge is NSMaxXEdge, the resulting width is 0). The returned rect is in the coordinate space of the receiver's view. If the byte range is not displayed, returns NSZeroRect. + */ +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)range; + +/* The background color for the line at the given index. You may override this to return different colors. You may return nil to draw no color in this line (and then the empty space color will appear) */ +- (NSColor *)backgroundColorForLine:(NSUInteger)line; +- (NSColor *)backgroundColorForEmptySpace; + +/* Defaults to 1, may override */ +- (NSUInteger)bytesPerCharacter; + +/* Cover method for [[self representer] bytesPerLine] and [[self representer] bytesPerColumn] */ +- (NSUInteger)bytesPerLine; +- (NSUInteger)bytesPerColumn; + +- (CGFloat)lineHeight; + +/* Following two must be overridden */ +- (CGFloat)advanceBetweenColumns; +- (CGFloat)advancePerCharacter; + +- (CGFloat)advancePerColumn; +- (CGFloat)totalAdvanceForBytesInRange:(NSRange)range; + +/* Returns the number of lines that could be shown in this view at its given height (expressed in its local coordinate space) */ +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight; + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth; +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine; + +- (IBAction)selectAll:sender; + + +@end diff --git a/HexFiend/HFRepresenterTextView.m b/HexFiend/HFRepresenterTextView.m new file mode 100644 index 00000000..95a763d4 --- /dev/null +++ b/HexFiend/HFRepresenterTextView.m @@ -0,0 +1,1760 @@ +// +// HFRepresenterTextView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import +#import +#import + +static const NSTimeInterval HFCaretBlinkFrequency = 0.56; + +@implementation HFRepresenterTextView + +- (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)inputFont { + NSUInteger length = [string length]; + UniChar chars[256]; + HFASSERT(length <= sizeof chars / sizeof *chars); + HFASSERT(inputFont != nil); + [string getCharacters:chars range:NSMakeRange(0, length)]; + if (! CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length)) { + /* Some or all characters were not mapped. This is OK. We'll use the replacement glyph. */ + } + return length; +} + +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)layoutManager glyphs:(CGGlyph *)glyphs { + HFASSERT(layoutManager != NULL); + HFASSERT(string != NULL); + NSGlyph nsglyphs[GLYPH_BUFFER_SIZE]; + [[[layoutManager textStorage] mutableString] setString:string]; + NSUInteger glyphIndex, glyphCount = [layoutManager getGlyphs:nsglyphs range:NSMakeRange(0, MIN(GLYPH_BUFFER_SIZE, [layoutManager numberOfGlyphs]))]; + if (glyphs != NULL) { + /* Convert from unsigned int NSGlyphs to unsigned short CGGlyphs */ + for (glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) { + /* Get rid of NSControlGlyph */ + NSGlyph modifiedGlyph = nsglyphs[glyphIndex] == NSControlGlyph ? NSNullGlyph : nsglyphs[glyphIndex]; + HFASSERT(modifiedGlyph <= USHRT_MAX); + glyphs[glyphIndex] = (CGGlyph)modifiedGlyph; + } + } + return glyphCount; +} + +/* Returns the number of glyphs for the given string, using the given text view, and generating the glyphs if the glyphs parameter is not NULL */ +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs { + HFASSERT(string != NULL); + HFASSERT(textView != NULL); + [textView setString:string]; + [textView setNeedsDisplay:YES]; //ligature generation doesn't seem to happen without this, for some reason. This seems very fragile! We should find a better way to get this ligature information!! + return [self _glyphsForString:string withGeneratingLayoutManager:[textView layoutManager] glyphs:glyphs]; +} + +- (NSArray *)displayedSelectedContentsRanges { + if (! cachedSelectedRanges) { + cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy]; + } + return cachedSelectedRanges; +} + +- (BOOL)_shouldHaveCaretTimer { + NSWindow *window = [self window]; + if (window == NULL) return NO; + if (! [window isKeyWindow]) return NO; + if (self != [window firstResponder]) return NO; + if (! _hftvflags.editable) return NO; + NSArray *ranges = [self displayedSelectedContentsRanges]; + if ([ranges count] != 1) return NO; + NSRange range = [ranges[0] rangeValue]; + if (range.length != 0) return NO; + return YES; +} + +- (NSUInteger)_effectiveBytesPerColumn { + /* returns the bytesPerColumn, unless it's larger than the bytes per character, in which case it returns 0 */ + NSUInteger bytesPerColumn = [self bytesPerColumn], bytesPerCharacter = [self bytesPerCharacter]; + return bytesPerColumn >= bytesPerCharacter ? bytesPerColumn : 0; +} + +// note: index may be negative +- (NSPoint)originForCharacterAtByteIndex:(NSInteger)index { + NSPoint result; + NSInteger bytesPerLine = (NSInteger)[self bytesPerLine]; + + // We want a nonnegative remainder + NSInteger lineIndex = index / bytesPerLine; + NSInteger byteIndexIntoLine = index % bytesPerLine; + while (byteIndexIntoLine < 0) { + byteIndexIntoLine += bytesPerLine; + lineIndex--; + } + + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + NSUInteger numConsumedColumns = (bytesPerColumn ? byteIndexIntoLine / bytesPerColumn : 0); + NSUInteger characterIndexIntoLine = byteIndexIntoLine / [self bytesPerCharacter]; + + result.x = [self horizontalContainerInset] + characterIndexIntoLine * [self advancePerCharacter] + numConsumedColumns * [self advanceBetweenColumns]; + result.y = (lineIndex - [self verticalOffset]) * [self lineHeight]; + + return result; +} + +- (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point { + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + HFASSERT(bytesPerLine % bytesPerCharacter == 0); + CGFloat advancePerCharacter = [self advancePerCharacter]; + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + CGFloat floatRow = (CGFloat)floor([self verticalOffset] + point.y / [self lineHeight]); + NSUInteger byteIndexWithinRow; + + // to compute the column, we need to solve for byteIndexIntoLine in something like this: point.x = [self advancePerCharacter] * charIndexIntoLine + [self spaceBetweenColumns] * floor(byteIndexIntoLine / [self bytesPerColumn]). Start by computing the column (or if bytesPerColumn is 0, we don't have columns) + CGFloat insetX = point.x - [self horizontalContainerInset]; + if (insetX < 0) { + //handle the case of dragging within the container inset + byteIndexWithinRow = 0; + } + else if (bytesPerColumn == 0) { + /* We don't have columns */ + byteIndexWithinRow = bytesPerCharacter * (NSUInteger)(insetX / advancePerCharacter); + } + else { + CGFloat advancePerColumn = [self advancePerColumn]; + HFASSERT(advancePerColumn > 0); + CGFloat floatColumn = insetX / advancePerColumn; + HFASSERT(floatColumn >= 0 && floatColumn <= NSUIntegerMax); + CGFloat startOfColumn = advancePerColumn * HFFloor(floatColumn); + HFASSERT(startOfColumn <= insetX); + CGFloat xOffsetWithinColumn = insetX - startOfColumn; + CGFloat charIndexWithinColumn = xOffsetWithinColumn / advancePerCharacter; //charIndexWithinColumn may be larger than bytesPerColumn if the user clicked on the space between columns + HFASSERT(charIndexWithinColumn >= 0 && charIndexWithinColumn <= NSUIntegerMax / bytesPerCharacter); + NSUInteger byteIndexWithinColumn = bytesPerCharacter * (NSUInteger)charIndexWithinColumn; + byteIndexWithinRow = bytesPerColumn * (NSUInteger)floatColumn + byteIndexWithinColumn; //this may trigger overflow to the next column, but that's OK + byteIndexWithinRow = MIN(byteIndexWithinRow, bytesPerLine); //don't let clicking to the right of the line overflow to the next line + } + HFASSERT(floatRow >= 0 && floatRow <= NSUIntegerMax); + NSUInteger row = (NSUInteger)floatRow; + return (row * bytesPerLine + byteIndexWithinRow) / bytesPerCharacter; +} + +- (NSRect)caretRect { + NSArray *ranges = [self displayedSelectedContentsRanges]; + HFASSERT([ranges count] == 1); + NSRange range = [ranges[0] rangeValue]; + HFASSERT(range.length == 0); + + NSPoint caretBaseline = [self originForCharacterAtByteIndex:range.location]; + return NSMakeRect(caretBaseline.x - 1, caretBaseline.y, 1, [self lineHeight]); +} + +- (void)_blinkCaret:(NSTimer *)timer { + HFASSERT(timer == caretTimer); + if (_hftvflags.caretVisible) { + _hftvflags.caretVisible = NO; + [self setNeedsDisplayInRect:lastDrawnCaretRect]; + caretRectToDraw = NSZeroRect; + } + else { + _hftvflags.caretVisible = YES; + caretRectToDraw = [self caretRect]; + [self setNeedsDisplayInRect:caretRectToDraw]; + } +} + +- (void)_updateCaretTimerWithFirstResponderStatus:(BOOL)treatAsHavingFirstResponder { + BOOL hasCaretTimer = !! caretTimer; + BOOL shouldHaveCaretTimer = treatAsHavingFirstResponder && [self _shouldHaveCaretTimer]; + if (shouldHaveCaretTimer == YES && hasCaretTimer == NO) { + caretTimer = [[NSTimer timerWithTimeInterval:HFCaretBlinkFrequency target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES] retain]; + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + [loop addTimer:caretTimer forMode:NSDefaultRunLoopMode]; + [loop addTimer:caretTimer forMode:NSModalPanelRunLoopMode]; + if ([self enclosingMenuItem] != NULL) { + [loop addTimer:caretTimer forMode:NSEventTrackingRunLoopMode]; + } + } + else if (shouldHaveCaretTimer == NO && hasCaretTimer == YES) { + [caretTimer invalidate]; + [caretTimer release]; + caretTimer = nil; + caretRectToDraw = NSZeroRect; + if (! NSIsEmptyRect(lastDrawnCaretRect)) { + [self setNeedsDisplayInRect:lastDrawnCaretRect]; + } + } + HFASSERT(shouldHaveCaretTimer == !! caretTimer); +} + +- (void)_updateCaretTimer { + [self _updateCaretTimerWithFirstResponderStatus: self == [[self window] firstResponder]]; +} + +/* When you click or type, the caret appears immediately - do that here */ +- (void)_forceCaretOnIfHasCaretTimer { + if (caretTimer) { + [caretTimer invalidate]; + [caretTimer release]; + caretTimer = nil; + [self _updateCaretTimer]; + + _hftvflags.caretVisible = YES; + caretRectToDraw = [self caretRect]; + [self setNeedsDisplayInRect:caretRectToDraw]; + } +} + +/* Returns the range of lines containing the selected contents ranges (as NSValues containing NSRanges), or {NSNotFound, 0} if ranges is nil or empty */ +- (NSRange)_lineRangeForContentsRanges:(NSArray *)ranges { + NSUInteger minLine = NSUIntegerMax; + NSUInteger maxLine = 0; + NSUInteger bytesPerLine = [self bytesPerLine]; + FOREACH(NSValue *, rangeValue, ranges) { + NSRange range = [rangeValue rangeValue]; + if (range.length > 0) { + NSUInteger lineForRangeStart = range.location / bytesPerLine; + NSUInteger lineForRangeEnd = NSMaxRange(range) / bytesPerLine; + HFASSERT(lineForRangeStart <= lineForRangeEnd); + minLine = MIN(minLine, lineForRangeStart); + maxLine = MAX(maxLine, lineForRangeEnd); + } + } + if (minLine > maxLine) return NSMakeRange(NSNotFound, 0); + else return NSMakeRange(minLine, maxLine - minLine + 1); +} + +- (NSRect)_rectForLineRange:(NSRange)lineRange { + HFASSERT(lineRange.location != NSNotFound); + NSUInteger bytesPerLine = [self bytesPerLine]; + NSRect bounds = [self bounds]; + NSRect result; + result.origin.x = NSMinX(bounds); + result.size.width = NSWidth(bounds); + result.origin.y = [self originForCharacterAtByteIndex:lineRange.location * bytesPerLine].y; + result.size.height = [self lineHeight] * lineRange.length; + return result; +} + +static int range_compare(const void *ap, const void *bp) { + const NSRange *a = ap; + const NSRange *b = bp; + if (a->location < b->location) return -1; + if (a->location > b->location) return 1; + if (a->length < b->length) return -1; + if (a->length > b->length) return 1; + return 0; +} + +enum LineCoverage_t { + eCoverageNone, + eCoveragePartial, + eCoverageFull +}; + +- (void)_linesWithParityChangesFromRanges:(const NSRange *)oldRanges count:(NSUInteger)oldRangeCount toRanges:(const NSRange *)newRanges count:(NSUInteger)newRangeCount intoIndexSet:(NSMutableIndexSet *)result { + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger oldParity=0, newParity=0; + NSUInteger oldRangeIndex = 0, newRangeIndex = 0; + NSUInteger currentCharacterIndex = MIN(oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location); + oldParity = (currentCharacterIndex >= oldRanges[oldRangeIndex].location); + newParity = (currentCharacterIndex >= newRanges[newRangeIndex].location); + // NSLog(@"Old %s, new %s at %u (%u, %u)", oldParity ? "on" : "off", newParity ? "on" : "off", currentCharacterIndex, oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location); + for (;;) { + NSUInteger oldDivision = NSUIntegerMax, newDivision = NSUIntegerMax; + /* Move up to the next parity change */ + if (oldRangeIndex < oldRangeCount) { + const NSRange oldRange = oldRanges[oldRangeIndex]; + oldDivision = oldRange.location + (oldParity ? oldRange.length : 0); + } + if (newRangeIndex < newRangeCount) { + const NSRange newRange = newRanges[newRangeIndex]; + newDivision = newRange.location + (newParity ? newRange.length : 0); + } + + NSUInteger division = MIN(oldDivision, newDivision); + HFASSERT(division > currentCharacterIndex); + + // NSLog(@"Division %u", division); + + if (division == NSUIntegerMax) break; + + if (oldParity != newParity) { + /* The parities did not match through this entire range, so add all intersected lines to the result index set */ + NSUInteger startLine = currentCharacterIndex / bytesPerLine; + NSUInteger endLine = HFDivideULRoundingUp(division, bytesPerLine); + HFASSERT(endLine >= startLine); + // NSLog(@"Adding lines %u -> %u", startLine, endLine); + [result addIndexesInRange:NSMakeRange(startLine, endLine - startLine)]; + } + if (division == oldDivision) { + oldRangeIndex += oldParity; + oldParity = ! oldParity; + // NSLog(@"Old range switching %s at %u", oldParity ? "on" : "off", division); + } + if (division == newDivision) { + newRangeIndex += newParity; + newParity = ! newParity; + // NSLog(@"New range switching %s at %u", newParity ? "on" : "off", division); + } + currentCharacterIndex = division; + } +} + +- (void)_addLinesFromRanges:(const NSRange *)ranges count:(NSUInteger)count toIndexSet:(NSMutableIndexSet *)set { + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger i; + for (i=0; i < count; i++) { + NSUInteger firstLine = ranges[i].location / bytesPerLine; + NSUInteger lastLine = HFDivideULRoundingUp(NSMaxRange(ranges[i]), bytesPerLine); + [set addIndexesInRange:NSMakeRange(firstLine, lastLine - firstLine)]; + } +} + +- (NSIndexSet *)_indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:(NSArray *)oldSelectedRangeArray toRanges:(NSArray *)newSelectedRangeArray { + NSUInteger oldRangeCount = 0, newRangeCount = 0; + + NEW_ARRAY(NSRange, oldRanges, [oldSelectedRangeArray count]); + NEW_ARRAY(NSRange, newRanges, [newSelectedRangeArray count]); + + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + + /* Extract all the ranges into a local array */ + FOREACH(NSValue *, rangeValue1, oldSelectedRangeArray) { + NSRange range = [rangeValue1 rangeValue]; + if (range.length > 0) { + oldRanges[oldRangeCount++] = range; + } + } + FOREACH(NSValue *, rangeValue2, newSelectedRangeArray) { + NSRange range = [rangeValue2 rangeValue]; + if (range.length > 0) { + newRanges[newRangeCount++] = range; + } + } + +#if ! NDEBUG + /* Assert that ranges of arrays do not have any self-intersection; this is supposed to be enforced by our HFController. Also assert that they aren't "just touching"; if they are they should be merged into a single range. */ + for (NSUInteger i=0; i < oldRangeCount; i++) { + for (NSUInteger j=i+1; j < oldRangeCount; j++) { + HFASSERT(NSIntersectionRange(oldRanges[i], oldRanges[j]).length == 0); + HFASSERT(NSMaxRange(oldRanges[i]) != oldRanges[j].location && NSMaxRange(oldRanges[j]) != oldRanges[i].location); + } + } + for (NSUInteger i=0; i < newRangeCount; i++) { + for (NSUInteger j=i+1; j < newRangeCount; j++) { + HFASSERT(NSIntersectionRange(newRanges[i], newRanges[j]).length == 0); + HFASSERT(NSMaxRange(newRanges[i]) != newRanges[j].location && NSMaxRange(newRanges[j]) != newRanges[i].location); + } + } +#endif + + if (newRangeCount == 0) { + [self _addLinesFromRanges:oldRanges count:oldRangeCount toIndexSet:result]; + } + else if (oldRangeCount == 0) { + [self _addLinesFromRanges:newRanges count:newRangeCount toIndexSet:result]; + } + else { + /* Sort the arrays, since _linesWithParityChangesFromRanges needs it */ + qsort(oldRanges, oldRangeCount, sizeof *oldRanges, range_compare); + qsort(newRanges, newRangeCount, sizeof *newRanges, range_compare); + + [self _linesWithParityChangesFromRanges:oldRanges count:oldRangeCount toRanges:newRanges count:newRangeCount intoIndexSet:result]; + } + + FREE_ARRAY(oldRanges); + FREE_ARRAY(newRanges); + + return result; +} + +- (void)updateSelectedRanges { + NSArray *oldSelectedRanges = cachedSelectedRanges; + cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy]; + NSIndexSet *indexSet = [self _indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:oldSelectedRanges toRanges:cachedSelectedRanges]; + BOOL lastCaretRectNeedsRedraw = ! NSIsEmptyRect(lastDrawnCaretRect); + NSRange lineRangeToInvalidate = NSMakeRange(NSUIntegerMax, 0); + for (NSUInteger lineIndex = [indexSet firstIndex]; ; lineIndex = [indexSet indexGreaterThanIndex:lineIndex]) { + if (lineIndex != NSNotFound && NSMaxRange(lineRangeToInvalidate) == lineIndex) { + lineRangeToInvalidate.length++; + } + else { + if (lineRangeToInvalidate.length > 0) { + NSRect rectToInvalidate = [self _rectForLineRange:lineRangeToInvalidate]; + [self setNeedsDisplayInRect:rectToInvalidate]; + lastCaretRectNeedsRedraw = lastCaretRectNeedsRedraw && ! NSContainsRect(rectToInvalidate, lastDrawnCaretRect); + } + lineRangeToInvalidate = NSMakeRange(lineIndex, 1); + } + if (lineIndex == NSNotFound) break; + } + + if (lastCaretRectNeedsRedraw) [self setNeedsDisplayInRect:lastDrawnCaretRect]; + [oldSelectedRanges release]; //balance the retain we borrowed from the ivar + [self _updateCaretTimer]; + [self _forceCaretOnIfHasCaretTimer]; + + // A new pulse window will be created at the new selected range if necessary. + [self terminateSelectionPulse]; +} + +- (void)drawPulseBackgroundInRect:(NSRect)pulseRect { + [[NSColor yellowColor] set]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(ctx); + [[NSBezierPath bezierPathWithRoundedRect:pulseRect xRadius:25 yRadius:25] addClip]; + NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[NSColor yellowColor] endingColor:[NSColor colorWithCalibratedRed:(CGFloat)1. green:(CGFloat).75 blue:0 alpha:1]]; + [gradient drawInRect:pulseRect angle:90]; + [gradient release]; + CGContextRestoreGState(ctx); +} + +- (void)fadePulseWindowTimer:(NSTimer *)timer { + // TODO: close & invalidate immediatley if view scrolls. + NSWindow *window = [timer userInfo]; + CGFloat alpha = [window alphaValue]; + alpha -= (CGFloat)(3. / 30.); + if (alpha < 0) { + [window close]; + [timer invalidate]; + } + else { + [window setAlphaValue:alpha]; + } +} + +- (void)terminateSelectionPulse { + if (pulseWindow) { + [[self window] removeChildWindow:pulseWindow]; + [pulseWindow setFrame:pulseWindowBaseFrameInScreenCoordinates display:YES animate:NO]; + [NSTimer scheduledTimerWithTimeInterval:1. / 30. target:self selector:@selector(fadePulseWindowTimer:) userInfo:pulseWindow repeats:YES]; + //release is not necessary, since it relases when closed by default + pulseWindow = nil; + pulseWindowBaseFrameInScreenCoordinates = NSZeroRect; + } +} + +- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect { + NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect); + if (! NSIsEmptyRect(caretRect)) { + [[NSColor blackColor] set]; + NSRectFill(caretRect); + lastDrawnCaretRect = caretRect; + } + if (NSIsEmptyRect(caretRectToDraw)) lastDrawnCaretRect = NSZeroRect; +} + + +/* This is the color when we are the first responder in the key window */ +- (NSColor *)primaryTextSelectionColor { + return [NSColor selectedTextBackgroundColor]; +} + +/* This is the color when we are not in the key window */ +- (NSColor *)inactiveTextSelectionColor { + return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1]; +} + +/* This is the color when we are not the first responder, but we are in the key window */ +- (NSColor *)secondaryTextSelectionColor { + return [[self primaryTextSelectionColor] blendedColorWithFraction:.66 ofColor:[NSColor colorWithCalibratedWhite:.8f alpha:1]]; +} + +- (NSColor *)textSelectionColor { + NSWindow *window = [self window]; + if (window == nil) return [self primaryTextSelectionColor]; + else if (! [window isKeyWindow]) return [self inactiveTextSelectionColor]; + else if (self != [window firstResponder]) return [self secondaryTextSelectionColor]; + else return [self primaryTextSelectionColor]; +} + +- (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect { + NSArray *ranges = [self displayedSelectedContentsRanges]; + NSUInteger bytesPerLine = [self bytesPerLine]; + [[self textSelectionColor] set]; + CGFloat lineHeight = [self lineHeight]; + FOREACH(NSValue *, rangeValue, ranges) { + NSRange range = [rangeValue rangeValue]; + if (range.length > 0) { + NSUInteger startByteIndex = range.location; + NSUInteger endByteIndexForThisRange = range.location + range.length - 1; + NSUInteger byteIndex = startByteIndex; + while (byteIndex <= endByteIndexForThisRange) { + NSUInteger endByteIndexForLine = ((byteIndex / bytesPerLine) + 1) * bytesPerLine - 1; + NSUInteger endByteForThisLineOfRange = MIN(endByteIndexForThisRange, endByteIndexForLine); + NSPoint startPoint = [self originForCharacterAtByteIndex:byteIndex]; + NSPoint endPoint = [self originForCharacterAtByteIndex:endByteForThisLineOfRange]; + NSRect selectionRect = NSMakeRect(startPoint.x, startPoint.y, endPoint.x + [self advancePerCharacter] - startPoint.x, lineHeight); + NSRect clippedSelectionRect = NSIntersectionRect(selectionRect, clipRect); + if (! NSIsEmptyRect(clippedSelectionRect)) { + NSRectFill(clippedSelectionRect); + } + byteIndex = endByteForThisLineOfRange + 1; + } + } + } +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (BOOL)hasVisibleDisplayedSelectedContentsRange { + FOREACH(NSValue *, rangeValue, [self displayedSelectedContentsRanges]) { + NSRange range = [rangeValue rangeValue]; + if (range.length > 0) { + return YES; + } + } + return NO; +} + +- (BOOL)becomeFirstResponder { + BOOL result = [super becomeFirstResponder]; + [self _updateCaretTimerWithFirstResponderStatus:YES]; + if ([self showsFocusRing] || [self hasVisibleDisplayedSelectedContentsRange]) { + [self setNeedsDisplay:YES]; + } + return result; +} + +- (BOOL)resignFirstResponder { + BOOL result = [super resignFirstResponder]; + [self _updateCaretTimerWithFirstResponderStatus:NO]; + BOOL needsRedisplay = NO; + if ([self showsFocusRing]) needsRedisplay = YES; + else if (! NSIsEmptyRect(lastDrawnCaretRect)) needsRedisplay = YES; + else if ([self hasVisibleDisplayedSelectedContentsRange]) needsRedisplay = YES; + if (needsRedisplay) [self setNeedsDisplay:YES]; + return result; +} + +- (instancetype)initWithRepresenter:(HFTextRepresenter *)rep { + self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)]; + horizontalContainerInset = 4; + representer = rep; + _hftvflags.editable = YES; + + return self; +} + +- (void)clearRepresenter { + representer = nil; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeObject:representer forKey:@"HFRepresenter"]; + [coder encodeObject:_font forKey:@"HFFont"]; + [coder encodeObject:_data forKey:@"HFData"]; + [coder encodeDouble:verticalOffset forKey:@"HFVerticalOffset"]; + [coder encodeDouble:horizontalContainerInset forKey:@"HFHorizontalContainerOffset"]; + [coder encodeDouble:defaultLineHeight forKey:@"HFDefaultLineHeight"]; + [coder encodeInt64:bytesBetweenVerticalGuides forKey:@"HFBytesBetweenVerticalGuides"]; + [coder encodeInt64:startingLineBackgroundColorIndex forKey:@"HFStartingLineBackgroundColorIndex"]; + [coder encodeObject:rowBackgroundColors forKey:@"HFRowBackgroundColors"]; + [coder encodeBool:_hftvflags.antialias forKey:@"HFAntialias"]; + [coder encodeBool:_hftvflags.drawCallouts forKey:@"HFDrawCallouts"]; + [coder encodeBool:_hftvflags.editable forKey:@"HFEditable"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + representer = [coder decodeObjectForKey:@"HFRepresenter"]; + _font = [[coder decodeObjectForKey:@"HFFont"] retain]; + _data = [[coder decodeObjectForKey:@"HFData"] retain]; + verticalOffset = (CGFloat)[coder decodeDoubleForKey:@"HFVerticalOffset"]; + horizontalContainerInset = (CGFloat)[coder decodeDoubleForKey:@"HFHorizontalContainerOffset"]; + defaultLineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFDefaultLineHeight"]; + bytesBetweenVerticalGuides = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesBetweenVerticalGuides"]; + startingLineBackgroundColorIndex = (NSUInteger)[coder decodeInt64ForKey:@"HFStartingLineBackgroundColorIndex"]; + rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain]; + _hftvflags.antialias = [coder decodeBoolForKey:@"HFAntialias"]; + _hftvflags.drawCallouts = [coder decodeBoolForKey:@"HFDrawCallouts"]; + _hftvflags.editable = [coder decodeBoolForKey:@"HFEditable"]; + return self; +} + +- (CGFloat)horizontalContainerInset { + return horizontalContainerInset; +} + +- (void)setHorizontalContainerInset:(CGFloat)inset { + horizontalContainerInset = inset; +} + +- (void)setBytesBetweenVerticalGuides:(NSUInteger)val { + bytesBetweenVerticalGuides = val; +} + +- (NSUInteger)bytesBetweenVerticalGuides { + return bytesBetweenVerticalGuides; +} + + +- (void)setFont:(NSFont *)val { + if (val != _font) { + [_font release]; + _font = [val retain]; + NSLayoutManager *manager = [[NSLayoutManager alloc] init]; + defaultLineHeight = [manager defaultLineHeightForFont:_font]; + [manager release]; + [self setNeedsDisplay:YES]; + } +} + +- (CGFloat)lineHeight { + return defaultLineHeight; +} + +/* The base implementation does not support font substitution, so we require that it be the base font. */ +- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx { + HFASSERT(idx == 0); + USE(idx); + return _font; +} + +- (NSRange)roundPartialByteRange:(NSRange)byteRange { + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + /* Get the left and right edges of the range */ + NSUInteger left = byteRange.location, right = NSMaxRange(byteRange); + + /* Round both to the left. This may make the range bigger or smaller, or empty! */ + left -= left % bytesPerCharacter; + right -= right % bytesPerCharacter; + + /* Done */ + HFASSERT(right >= left); + return NSMakeRange(left, right - left); + +} + +- (void)setNeedsDisplayForLinesInRange:(NSRange)lineRange { + // redisplay the lines in the given range + if (lineRange.length == 0) return; + NSUInteger firstLine = lineRange.location, lastLine = NSMaxRange(lineRange); + CGFloat lineHeight = [self lineHeight]; + CGFloat vertOffset = [self verticalOffset]; + CGFloat yOrigin = (firstLine - vertOffset) * lineHeight; + CGFloat lastLineBottom = (lastLine - vertOffset) * lineHeight; + NSRect bounds = [self bounds]; + NSRect dirtyRect = NSMakeRect(bounds.origin.x, bounds.origin.y + yOrigin, NSWidth(bounds), lastLineBottom - yOrigin); + [self setNeedsDisplayInRect:dirtyRect]; +} + +- (void)setData:(NSData *)val { + if (val != _data) { + NSUInteger oldLength = [_data length]; + NSUInteger newLength = [val length]; + const unsigned char *oldBytes = (const unsigned char *)[_data bytes]; + const unsigned char *newBytes = (const unsigned char *)[val bytes]; + NSUInteger firstDifferingIndex = HFIndexOfFirstByteThatDiffers(oldBytes, oldLength, newBytes, newLength); + if (firstDifferingIndex == NSUIntegerMax) { + /* Nothing to do! Data is identical! */ + } + else { + NSUInteger lastDifferingIndex = HFIndexOfLastByteThatDiffers(oldBytes, oldLength, newBytes, newLength); + HFASSERT(lastDifferingIndex != NSUIntegerMax); //if we have a first different byte, we must have a last different byte + /* Expand to encompass characters that they touch */ + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + firstDifferingIndex -= firstDifferingIndex % bytesPerCharacter; + lastDifferingIndex = HFRoundUpToMultipleInt(lastDifferingIndex, bytesPerCharacter); + + /* Now figure out the line range they touch */ + const NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger firstLine = firstDifferingIndex / bytesPerLine; + NSUInteger lastLine = HFDivideULRoundingUp(MAX(oldLength, newLength), bytesPerLine); + /* The +1 is for the following case - if we change the last character, then it may push the caret into the next line (even though there's no text there). This last line may have a background color, so we need to make it draw if it did not draw before (or vice versa - when deleting the last character which pulls the caret from the last line). */ + NSUInteger lastDifferingLine = (lastDifferingIndex == NSNotFound ? lastLine : HFDivideULRoundingUp(lastDifferingIndex + 1, bytesPerLine)); + if (lastDifferingLine > firstLine) { + [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastDifferingLine - firstLine)]; + } + } + [_data release]; + _data = [val copy]; + [self _updateCaretTimer]; + } +} + +- (void)setStyles:(NSArray *)newStyles { + if (! [_styles isEqual:newStyles]) { + + /* Figure out which styles changed - that is, we want to compute those objects that are not in oldStyles or newStyles, but not both. */ + NSMutableSet *changedStyles = _styles ? [[NSMutableSet alloc] initWithArray:_styles] : [[NSMutableSet alloc] init]; + FOREACH(HFTextVisualStyleRun *, run, newStyles) { + if ([changedStyles containsObject:run]) { + [changedStyles removeObject:run]; + } + else { + [changedStyles addObject:run]; + } + } + + /* Now figure out the first and last indexes of changed ranges. */ + NSUInteger firstChangedIndex = NSUIntegerMax, lastChangedIndex = 0; + FOREACH(HFTextVisualStyleRun *, changedRun, changedStyles) { + NSRange range = [changedRun range]; + if (range.length > 0) { + firstChangedIndex = MIN(firstChangedIndex, range.location); + lastChangedIndex = MAX(lastChangedIndex, NSMaxRange(range) - 1); + } + } + + /* Don't need this any more */ + [changedStyles release]; + + /* Expand to cover all touched characters */ + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + firstChangedIndex -= firstChangedIndex % bytesPerCharacter; + lastChangedIndex = HFRoundUpToMultipleInt(lastChangedIndex, bytesPerCharacter); + + /* Figure out the changed lines, and trigger redisplay */ + if (firstChangedIndex <= lastChangedIndex) { + const NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger firstLine = firstChangedIndex / bytesPerLine; + NSUInteger lastLine = HFDivideULRoundingUp(lastChangedIndex, bytesPerLine); + [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastLine - firstLine + 1)]; + } + + /* Do the usual Cocoa thing */ + [_styles release]; + _styles = [newStyles copy]; + } +} + +- (void)setVerticalOffset:(CGFloat)val { + if (val != verticalOffset) { + verticalOffset = val; + [self setNeedsDisplay:YES]; + } +} + +- (CGFloat)verticalOffset { + return verticalOffset; +} + +- (NSUInteger)startingLineBackgroundColorIndex { + return startingLineBackgroundColorIndex; +} + +- (void)setStartingLineBackgroundColorIndex:(NSUInteger)val { + startingLineBackgroundColorIndex = val; +} + +- (BOOL)isFlipped { + return YES; +} + +- (HFTextRepresenter *)representer { + return representer; +} + +- (void)dealloc { + HFUnregisterViewForWindowAppearanceChanges(self, _hftvflags.registeredForAppNotifications /* appToo */); + [caretTimer invalidate]; + [caretTimer release]; + [_font release]; + [_data release]; + [_styles release]; + [cachedSelectedRanges release]; + [callouts release]; + if(byteColoring) Block_release(byteColoring); + [super dealloc]; +} + +- (NSColor *)backgroundColorForEmptySpace { + NSArray *colors = [[self representer] rowBackgroundColors]; + if (! [colors count]) return [NSColor clearColor]; + else return colors[0]; +} + +- (NSColor *)backgroundColorForLine:(NSUInteger)line { + NSArray *colors = [[self representer] rowBackgroundColors]; + NSUInteger colorCount = [colors count]; + if (colorCount == 0) return [NSColor clearColor]; + NSUInteger colorIndex = (line + startingLineBackgroundColorIndex) % colorCount; + if (colorIndex == 0) return nil; //will be drawn by empty space + else return colors[colorIndex]; +} + +- (NSUInteger)bytesPerLine { + HFASSERT([self representer] != nil); + return [[self representer] bytesPerLine]; +} + +- (NSUInteger)bytesPerColumn { + HFASSERT([self representer] != nil); + return [[self representer] bytesPerColumn]; +} + +- (void)_drawDefaultLineBackgrounds:(NSRect)clip withLineHeight:(CGFloat)lineHeight maxLines:(NSUInteger)maxLines { + NSRect bounds = [self bounds]; + NSUInteger lineIndex; + NSRect lineRect = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight); + if ([self showsFocusRing]) lineRect = NSInsetRect(lineRect, 2, 0); + lineRect.origin.y -= [self verticalOffset] * [self lineHeight]; + NSUInteger drawableLineIndex = 0; + NEW_ARRAY(NSRect, lineRects, maxLines); + NEW_ARRAY(NSColor*, lineColors, maxLines); + for (lineIndex = 0; lineIndex < maxLines; lineIndex++) { + NSRect clippedLineRect = NSIntersectionRect(lineRect, clip); + if (! NSIsEmptyRect(clippedLineRect)) { + NSColor *lineColor = [self backgroundColorForLine:lineIndex]; + if (lineColor) { + lineColors[drawableLineIndex] = lineColor; + lineRects[drawableLineIndex] = clippedLineRect; + drawableLineIndex++; + } + } + lineRect.origin.y += lineHeight; + } + + if (drawableLineIndex > 0) { + NSRectFillListWithColorsUsingOperation(lineRects, lineColors, drawableLineIndex, NSCompositeSourceOver); + } + + FREE_ARRAY(lineRects); + FREE_ARRAY(lineColors); +} + +- (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex { + HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init]; + [run setRange:NSMakeRange(0, NSUIntegerMax)]; + [run setForegroundColor:[NSColor blackColor]]; + return [run autorelease]; +} + +/* Given a list of rects and a parallel list of values, find cases of equal adjacent values, and union together their corresponding rects, deleting the second element from the list. Next, delete all nil values. Returns the new count of the list. */ +static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count) { + size_t trailing = 0, leading = 0; + while (leading < count) { + /* Copy our value left */ + valueList[trailing] = valueList[leading]; + rectList[trailing] = rectList[leading]; + + /* Skip one - no point unioning with ourselves */ + leading += 1; + + /* Sweep right, unioning until we reach a different value or the end */ + id targetValue = valueList[trailing]; + for (; leading < count; leading++) { + id testValue = valueList[leading]; + if (targetValue == testValue || (testValue && [targetValue isEqual:testValue])) { + /* Values match, so union the two rects */ + rectList[trailing] = NSUnionRect(rectList[trailing], rectList[leading]); + } + else { + /* Values don't match, we're done sweeping */ + break; + } + } + + /* We're done with this index */ + trailing += 1; + } + + /* trailing keeps track of how many values we have */ + count = trailing; + + /* Now do the same thing, except delete nil values */ + for (trailing = leading = 0; leading < count; leading++) { + if (valueList[leading] != nil) { + valueList[trailing] = valueList[leading]; + rectList[trailing] = rectList[leading]; + trailing += 1; + } + } + count = trailing; + + /* All done */ + return count; +} + +/* Draw vertical guidelines every four bytes */ +- (void)drawVerticalGuideLines:(NSRect)clip { + if (bytesBetweenVerticalGuides == 0) return; + + NSUInteger bytesPerLine = [self bytesPerLine]; + NSRect bounds = [self bounds]; + CGFloat advancePerCharacter = [self advancePerCharacter]; + CGFloat spaceAdvancement = advancePerCharacter / 2; + CGFloat advanceAmount = (advancePerCharacter + spaceAdvancement) * bytesBetweenVerticalGuides; + CGFloat lineOffset = (CGFloat)(NSMinX(bounds) + [self horizontalContainerInset] + advanceAmount - spaceAdvancement / 2.); + CGFloat endOffset = NSMaxX(bounds) - [self horizontalContainerInset]; + + NSUInteger numGuides = (bytesPerLine - 1) / bytesBetweenVerticalGuides; // -1 is a trick to avoid drawing the last line + NSUInteger guideIndex = 0, rectIndex = 0; + NEW_ARRAY(NSRect, lineRects, numGuides); + + while (lineOffset < endOffset && guideIndex < numGuides) { + NSRect lineRect = NSMakeRect(lineOffset - 1, NSMinY(bounds), 1, NSHeight(bounds)); + NSRect clippedLineRect = NSIntersectionRect(lineRect, clip); + if (! NSIsEmptyRect(clippedLineRect)) { + lineRects[rectIndex++] = clippedLineRect; + } + lineOffset += advanceAmount; + guideIndex++; + } + if (rectIndex > 0) { + [[NSColor colorWithCalibratedWhite:(CGFloat).8 alpha:1] set]; + NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositePlusDarker); + } + FREE_ARRAY(lineRects); +} + +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount { + USE(byteCount); + UNIMPLEMENTED(); +} + +- (void)setByteColoring:(void (^)(uint8_t, uint8_t*, uint8_t*, uint8_t*, uint8_t*))coloring { + Block_release(byteColoring); + byteColoring = coloring ? Block_copy(coloring) : NULL; + [self setNeedsDisplay:YES]; +} + +- (void)drawByteColoringBackground:(NSRange)range inRect:(NSRect)rect { + if(!byteColoring) return; + + size_t width = (size_t)rect.size.width; + + // A rgba, 8-bit, single row image. + // +1 in case messing around with floats makes us overshoot a bit. + uint32_t *buffer = calloc(width+1, 4); + + const uint8_t *bytes = [_data bytes]; + bytes += range.location; + + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + CGFloat advancePerCharacter = [self advancePerCharacter]; + CGFloat advanceBetweenColumns = [self advanceBetweenColumns]; + + // For each character, draw the corresponding part of the image + CGFloat offset = [self horizontalContainerInset]; + for(NSUInteger i = 0; i < range.length; i++) { + uint8_t r, g, b, a; + byteColoring(bytes[i], &r, &g, &b, &a); + uint32_t c = ((uint32_t)r<<0) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); + memset_pattern4(&buffer[(size_t)offset], &c, 4*(size_t)(advancePerCharacter+1)); + offset += advancePerCharacter; + if(bytesPerColumn && (i+1) % bytesPerColumn == 0) + offset += advanceBetweenColumns; + } + + // Do a CGImage dance to draw the buffer + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, 4 * width, NULL); + CGColorSpaceRef cgcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGImageRef image = CGImageCreate(width, 1, 8, 32, 4 * width, cgcolorspace, + (CGBitmapInfo)kCGImageAlphaLast, provider, NULL, false, kCGRenderingIntentDefault); + CGContextDrawImage([[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(rect), image); + CGColorSpaceRelease(cgcolorspace); + CGImageRelease(image); + CGDataProviderRelease(provider); + free(buffer); +} + +- (void)drawStyledBackgroundsForByteRange:(NSRange)range inRect:(NSRect)rect { + NSRect remainingRunRect = rect; + NSRange remainingRange = range; + + /* Our caller lies to us a little */ + remainingRunRect.origin.x += [self horizontalContainerInset]; + + const NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + + /* Here are the properties we care about */ + struct PropertyInfo_t { + SEL stylePropertyAccessor; // the selector we use to get the property + NSRect *rectList; // the list of rects corresponding to the property values + id *propertyValueList; // the list of the property values + size_t count; //list count, only gets set after cleaning up our lists + } propertyInfos[] = { + {.stylePropertyAccessor = @selector(backgroundColor)}, + {.stylePropertyAccessor = @selector(bookmarkStarts)}, + {.stylePropertyAccessor = @selector(bookmarkExtents)}, + {.stylePropertyAccessor = @selector(bookmarkEnds)} + }; + + /* Each list has the same capacity, and (initially) the same count */ + size_t listCount = 0, listCapacity = 0; + + /* The function pointer we use to get our property values */ + id (* const funcPtr)(id, SEL) = (id (*)(id, SEL))objc_msgSend; + + size_t propertyIndex; + const size_t propertyInfoCount = sizeof propertyInfos / sizeof *propertyInfos; + + while (remainingRange.length > 0) { + /* Get the next run for the remaining range. */ + HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:remainingRange.location]; + + /* The length of the run is the end of the style run or the end of the range we're given (whichever is smaller), minus the beginning of the range we care about. */ + NSUInteger runStart = remainingRange.location; + NSUInteger runLength = MIN(NSMaxRange(range), NSMaxRange([styleRun range])) - runStart; + + /* Get the width of this run and use it to compute the rect */ + CGFloat runRectWidth = [self totalAdvanceForBytesInRange:NSMakeRange(remainingRange.location, runLength)]; + NSRect runRect = remainingRunRect; + runRect.size.width = runRectWidth; + + /* Update runRect and remainingRunRect based on what we just learned */ + remainingRunRect.origin.x += runRectWidth; + remainingRunRect.size.width -= runRectWidth; + + /* Do a hack - if we end at a column boundary, subtract the advance between columns. If the next run has the same value for this property, then we'll end up unioning the rects together and the column gap will be filled. This is the primary purpose of this function. */ + if (bytesPerColumn > 0 && (runStart + runLength) % bytesPerColumn == 0) { + runRect.size.width -= MIN([self advanceBetweenColumns], runRect.size.width); + } + + /* Extend our lists if necessary */ + if (listCount == listCapacity) { + /* Our list is too small, extend it */ + listCapacity = listCapacity + 16; + + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + struct PropertyInfo_t *p = propertyInfos + propertyIndex; + p->rectList = check_realloc(p->rectList, listCapacity * sizeof *p->rectList); + p->propertyValueList = check_realloc(p->propertyValueList, listCapacity * sizeof *p->propertyValueList); + } + } + + /* Now append our values to our lists, even if it's nil */ + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + struct PropertyInfo_t *p = propertyInfos + propertyIndex; + id value = funcPtr(styleRun, p->stylePropertyAccessor); + p->rectList[listCount] = runRect; + p->propertyValueList[listCount] = value; + } + + listCount++; + + /* Update remainingRange */ + remainingRange.location += runLength; + remainingRange.length -= runLength; + + } + + /* Now clean up our lists, to delete the gaps we may have introduced */ + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + struct PropertyInfo_t *p = propertyInfos + propertyIndex; + p->count = unionAndCleanLists(p->rectList, p->propertyValueList, listCount); + } + + /* Finally we can draw them! First, draw byte backgrounds. */ + [self drawByteColoringBackground:range inRect:rect]; + + const struct PropertyInfo_t *p; + + /* Draw backgrounds */ + p = propertyInfos + 0; + if (p->count > 0) NSRectFillListWithColorsUsingOperation(p->rectList, p->propertyValueList, p->count, NSCompositeSourceOver); + + /* Clean up */ + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + p = propertyInfos + propertyIndex; + free(p->rectList); + free(p->propertyValueList); + } +} + +- (void)drawGlyphs:(const struct HFGlyph_t *)glyphs atPoint:(NSPoint)point withAdvances:(const CGSize *)advances withStyleRun:(HFTextVisualStyleRun *)styleRun count:(NSUInteger)glyphCount { + HFASSERT(glyphs != NULL); + HFASSERT(advances != NULL); + HFASSERT(glyphCount > 0); + if ([styleRun shouldDraw]) { + [styleRun set]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + + /* Get all the CGGlyphs together */ + NEW_ARRAY(CGGlyph, cgglyphs, glyphCount); + for (NSUInteger j=0; j < glyphCount; j++) { + cgglyphs[j] = glyphs[j].glyph; + } + + NSUInteger runStart = 0; + HFGlyphFontIndex runFontIndex = glyphs[0].fontIndex; + CGFloat runAdvance = 0; + for (NSUInteger i=1; i <= glyphCount; i++) { + /* Check if this run is finished, or if we are using a substitution font */ + if (i == glyphCount || glyphs[i].fontIndex != runFontIndex || runFontIndex > 0) { + /* Draw this run */ + NSFont *fontToUse = [self fontAtSubstitutionIndex:runFontIndex]; + [[fontToUse screenFont] set]; + CGContextSetTextPosition(ctx, point.x + runAdvance, point.y); + + if (runFontIndex > 0) { + /* A substitution font. Here we should only have one glyph */ + HFASSERT(i - runStart == 1); + /* Get the advance for this glyph. */ + NSSize nativeAdvance; + NSGlyph nativeGlyph = cgglyphs[runStart]; + [fontToUse getAdvancements:&nativeAdvance forGlyphs:&nativeGlyph count:1]; + if (nativeAdvance.width > advances[runStart].width) { + /* This glyph is too wide! We'll have to scale it. Here we only scale horizontally. */ + CGFloat horizontalScale = advances[runStart].width / nativeAdvance.width; + CGAffineTransform textCTM = CGContextGetTextMatrix(ctx); + textCTM.a *= horizontalScale; + CGContextSetTextMatrix(ctx, textCTM); + /* Note that we don't have to restore the text matrix, because the next call to set the font will overwrite it. */ + } + } + + /* Draw the glyphs */ + CGContextShowGlyphsWithAdvances(ctx, cgglyphs + runStart, advances + runStart, i - runStart); + + /* Record the new run */ + if (i < glyphCount) { + /* Sum the advances */ + for (NSUInteger j = runStart; j < i; j++) { + runAdvance += advances[j].width; + } + + /* Record the new run start and index */ + runStart = i; + runFontIndex = glyphs[i].fontIndex; + HFASSERT(runFontIndex != kHFGlyphFontIndexInvalid); + } + } + } + } +} + + +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount { + USE(bytes); + USE(numBytes); + USE(offsetIntoLine); + USE(glyphs); + USE(advances); + USE(resultGlyphCount); + UNIMPLEMENTED_VOID(); +} + +- (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount { + NSParameterAssert(glyphs != NULL && advances != NULL && restrictingToRanges != nil && bytePtr != NULL); + NSRange priorIntersectionRange = {NSUIntegerMax, NSUIntegerMax}; + NSUInteger glyphBufferIndex = 0; + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger restrictionRangeCount = [restrictingToRanges count]; + for (NSUInteger rangeIndex = 0; rangeIndex < restrictionRangeCount; rangeIndex++) { + NSRange inclusionRange = [restrictingToRanges[rangeIndex] rangeValue]; + NSRange intersectionRange = NSIntersectionRange(inclusionRange, byteRange); + if (intersectionRange.length == 0) continue; + + NSUInteger offsetIntoLine = intersectionRange.location % bytesPerLine; + + NSRange byteRangeToSkip; + if (priorIntersectionRange.location == NSUIntegerMax) { + byteRangeToSkip = NSMakeRange(byteRange.location, intersectionRange.location - byteRange.location); + } + else { + HFASSERT(intersectionRange.location >= NSMaxRange(priorIntersectionRange)); + byteRangeToSkip.location = NSMaxRange(priorIntersectionRange); + byteRangeToSkip.length = intersectionRange.location - byteRangeToSkip.location; + } + + if (byteRangeToSkip.length > 0) { + CGFloat additionalAdvance = [self totalAdvanceForBytesInRange:byteRangeToSkip]; + if (glyphBufferIndex == 0) { + *initialTextOffset = *initialTextOffset + additionalAdvance; + } + else { + advances[glyphBufferIndex - 1].width += additionalAdvance; + } + } + + NSUInteger glyphCountForRange = NSUIntegerMax; + [self extractGlyphsForBytes:bytePtr + intersectionRange.location count:intersectionRange.length offsetIntoLine:offsetIntoLine intoArray:glyphs + glyphBufferIndex advances:advances + glyphBufferIndex resultingGlyphCount:&glyphCountForRange]; + HFASSERT(glyphCountForRange != NSUIntegerMax); + glyphBufferIndex += glyphCountForRange; + priorIntersectionRange = intersectionRange; + } + if (resultingGlyphCount) *resultingGlyphCount = glyphBufferIndex; +} + +- (void)drawTextWithClip:(NSRect)clip restrictingToTextInRanges:(NSArray *)restrictingToRanges { + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + NSRect bounds = [self bounds]; + CGFloat lineHeight = [self lineHeight]; + + CGAffineTransform textTransform = CGContextGetTextMatrix(ctx); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + NSUInteger lineStartIndex, bytesPerLine = [self bytesPerLine]; + NSData *dataObject = [self data]; + NSFont *fontObject = [[self font] screenFont]; + //const NSUInteger bytesPerChar = [self bytesPerCharacter]; + const NSUInteger byteCount = [dataObject length]; + + const unsigned char * const bytePtr = [dataObject bytes]; + + NSRect lineRectInBoundsSpace = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight); + lineRectInBoundsSpace.origin.y -= [self verticalOffset] * lineHeight; + + /* Start us off with the horizontal inset and move the baseline down by the ascender so our glyphs just graze the top of our view */ + textTransform.tx += [self horizontalContainerInset]; + textTransform.ty += [fontObject ascender] - lineHeight * [self verticalOffset]; + NSUInteger lineIndex = 0; + const NSUInteger maxGlyphCount = [self maximumGlyphCountForByteCount:bytesPerLine]; + NEW_ARRAY(struct HFGlyph_t, glyphs, maxGlyphCount); + NEW_ARRAY(CGSize, advances, maxGlyphCount); + for (lineStartIndex = 0; lineStartIndex < byteCount; lineStartIndex += bytesPerLine) { + if (lineStartIndex > 0) { + textTransform.ty += lineHeight; + lineRectInBoundsSpace.origin.y += lineHeight; + } + if (NSIntersectsRect(lineRectInBoundsSpace, clip)) { + const NSUInteger bytesInThisLine = MIN(bytesPerLine, byteCount - lineStartIndex); + + /* Draw the backgrounds of any styles. */ + [self drawStyledBackgroundsForByteRange:NSMakeRange(lineStartIndex, bytesInThisLine) inRect:lineRectInBoundsSpace]; + + NSUInteger byteIndexInLine = 0; + CGFloat advanceIntoLine = 0; + while (byteIndexInLine < bytesInThisLine) { + const NSUInteger byteIndex = lineStartIndex + byteIndexInLine; + HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:byteIndex]; + HFASSERT(styleRun != nil); + HFASSERT(byteIndex >= [styleRun range].location); + const NSUInteger bytesInThisRun = MIN(NSMaxRange([styleRun range]) - byteIndex, bytesInThisLine - byteIndexInLine); + const NSRange characterRange = [self roundPartialByteRange:NSMakeRange(byteIndex, bytesInThisRun)]; + if (characterRange.length > 0) { + NSUInteger resultGlyphCount = 0; + CGFloat initialTextOffset = 0; + if (restrictingToRanges == nil) { + [self extractGlyphsForBytes:bytePtr + characterRange.location count:characterRange.length offsetIntoLine:byteIndexInLine intoArray:glyphs advances:advances resultingGlyphCount:&resultGlyphCount]; + } + else { + [self extractGlyphsForBytes:bytePtr range:NSMakeRange(byteIndex, bytesInThisRun) intoArray:glyphs advances:advances withInclusionRanges:restrictingToRanges initialTextOffset:&initialTextOffset resultingGlyphCount:&resultGlyphCount]; + } + HFASSERT(resultGlyphCount <= maxGlyphCount); + +#if ! NDEBUG + for (NSUInteger q=0; q < resultGlyphCount; q++) { + HFASSERT(glyphs[q].fontIndex != kHFGlyphFontIndexInvalid); + } +#endif + + if (resultGlyphCount > 0) { + textTransform.tx += initialTextOffset + advanceIntoLine; + CGContextSetTextMatrix(ctx, textTransform); + /* Draw them */ + [self drawGlyphs:glyphs atPoint:NSMakePoint(textTransform.tx, textTransform.ty) withAdvances:advances withStyleRun:styleRun count:resultGlyphCount]; + + /* Undo the work we did before so as not to screw up the next run */ + textTransform.tx -= initialTextOffset + advanceIntoLine; + + /* Record how far into our line this made us move */ + NSUInteger glyphIndex; + for (glyphIndex = 0; glyphIndex < resultGlyphCount; glyphIndex++) { + advanceIntoLine += advances[glyphIndex].width; + } + } + } + byteIndexInLine += bytesInThisRun; + } + } + else if (NSMinY(lineRectInBoundsSpace) > NSMaxY(clip)) { + break; + } + lineIndex++; + } + FREE_ARRAY(glyphs); + FREE_ARRAY(advances); +} + + +- (void)drawFocusRingWithClip:(NSRect)clip { + USE(clip); + [NSGraphicsContext saveGraphicsState]; + NSSetFocusRingStyle(NSFocusRingOnly); + [[NSColor clearColor] set]; + NSRectFill([self bounds]); + [NSGraphicsContext restoreGraphicsState]; +} + +- (BOOL)shouldDrawCallouts { + return _hftvflags.drawCallouts; +} + +- (void)setShouldDrawCallouts:(BOOL)val { + _hftvflags.drawCallouts = val; + [self setNeedsDisplay:YES]; +} + +- (void)drawBookmarksWithClip:(NSRect)clip { + if([self shouldDrawCallouts]) { + /* Figure out which callouts we're going to draw */ + NSRect allCalloutsRect = NSZeroRect; + NSMutableArray *localCallouts = [[NSMutableArray alloc] initWithCapacity:[callouts count]]; + FOREACH(HFRepresenterTextViewCallout *, callout, [callouts objectEnumerator]) { + NSRect calloutRect = [callout rect]; + if (NSIntersectsRect(clip, calloutRect)) { + [localCallouts addObject:callout]; + allCalloutsRect = NSUnionRect(allCalloutsRect, calloutRect); + } + } + allCalloutsRect = NSIntersectionRect(allCalloutsRect, clip); + + if ([localCallouts count]) { + /* Draw shadows first */ + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect(allCalloutsRect), NULL); + FOREACH(HFRepresenterTextViewCallout *, callout, localCallouts) { + [callout drawShadowWithClip:clip]; + } + CGContextEndTransparencyLayer(ctx); + + FOREACH(HFRepresenterTextViewCallout *, newCallout, localCallouts) { + // NSRect rect = [callout rect]; + // [[NSColor greenColor] set]; + // NSFrameRect(rect); + [newCallout drawWithClip:clip]; + } + } + [localCallouts release]; + } +} + +- (void)drawRect:(NSRect)clip { + [[self backgroundColorForEmptySpace] set]; + NSRectFillUsingOperation(clip, NSCompositeSourceOver); + BOOL antialias = [self shouldAntialias]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + + [[self.font screenFont] set]; + + if ([self showsFocusRing]) { + NSWindow *window = [self window]; + if (self == [window firstResponder] && [window isKeyWindow]) { + [self drawFocusRingWithClip:clip]; + } + } + + NSUInteger bytesPerLine = [self bytesPerLine]; + if (bytesPerLine == 0) return; + NSUInteger byteCount = [_data length]; + + [self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)]; + [self drawSelectionIfNecessaryWithClip:clip]; + + NSColor *textColor = [NSColor blackColor]; + [textColor set]; + + if (! antialias) { + CGContextSaveGState(ctx); + CGContextSetShouldAntialias(ctx, NO); + } + [self drawTextWithClip:clip restrictingToTextInRanges:nil]; + if (! antialias) { + CGContextRestoreGState(ctx); + } + + // Vertical dividers only make sense in single byte mode. + if ([self _effectiveBytesPerColumn] == 1) { + [self drawVerticalGuideLines:clip]; + } + + [self drawCaretIfNecessaryWithClip:clip]; + + [self drawBookmarksWithClip:clip]; +} + +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)byteRange { + HFASSERT(edge == NSMinXEdge || edge == NSMaxXEdge || edge == NSMinYEdge || edge == NSMaxYEdge); + const NSUInteger bytesPerLine = [self bytesPerLine]; + CGFloat lineHeight = [self lineHeight]; + CGFloat vertOffset = [self verticalOffset]; + NSUInteger firstLine = byteRange.location / bytesPerLine, lastLine = (NSMaxRange(byteRange) - 1) / bytesPerLine; + NSRect result = NSZeroRect; + + if (edge == NSMinYEdge || edge == NSMaxYEdge) { + /* This is the top (MinY) or bottom (MaxY). We only have to look at one line. */ + NSUInteger lineIndex = (edge == NSMinYEdge ? firstLine : lastLine); + NSRange lineRange = NSMakeRange(lineIndex * bytesPerLine, bytesPerLine); + NSRange intersection = NSIntersectionRange(lineRange, byteRange); + HFASSERT(intersection.length > 0); + CGFloat yOrigin = (lineIndex - vertOffset) * lineHeight; + CGFloat xStart = [self originForCharacterAtByteIndex:intersection.location].x; + CGFloat xEnd = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter]; + result = NSMakeRect(xStart, yOrigin, xEnd - xStart, 0); + } + else { + if (firstLine == lastLine) { + /* We only need to consider this one line */ + NSRange lineRange = NSMakeRange(firstLine * bytesPerLine, bytesPerLine); + NSRange intersection = NSIntersectionRange(lineRange, byteRange); + HFASSERT(intersection.length > 0); + CGFloat yOrigin = (firstLine - vertOffset) * lineHeight; + CGFloat xCoord; + if (edge == NSMinXEdge) { + xCoord = [self originForCharacterAtByteIndex:intersection.location].x; + } + else { + xCoord = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter]; + } + result = NSMakeRect(xCoord, yOrigin, 0, lineHeight); + } + else { + /* We have more than one line. If we are asking for the left edge, sum up the left edge of every line but the first, and handle the first specially. Likewise for the right edge (except handle the last specially) */ + BOOL includeFirstLine, includeLastLine; + CGFloat xCoord; + if (edge == NSMinXEdge) { + /* Left edge, include the first line only if it starts at the beginning of the line or there's only one line */ + includeFirstLine = (byteRange.location % bytesPerLine == 0); + includeLastLine = YES; + xCoord = [self horizontalContainerInset]; + } + else { + /* Right edge, include the last line only if it starts at the beginning of the line or there's only one line */ + includeFirstLine = YES; + includeLastLine = (NSMaxRange(byteRange) % bytesPerLine == 0); + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + /* Don't add in space for the advance after the last column, hence subtract 1. */ + NSUInteger numColumns = (bytesPerColumn ? (bytesPerLine / bytesPerColumn - 1) : 0); + xCoord = [self horizontalContainerInset] + ([self advancePerCharacter] * bytesPerLine / [self bytesPerCharacter]) + [self advanceBetweenColumns] * numColumns; + } + NSUInteger firstLineToInclude = (includeFirstLine ? firstLine : firstLine + 1), lastLineToInclude = (includeLastLine ? lastLine : lastLine - 1); + result = NSMakeRect(xCoord, (firstLineToInclude - [self verticalOffset]) * lineHeight, 0, (lastLineToInclude - firstLineToInclude + 1) * lineHeight); + } + } + return result; +} + +- (NSUInteger)availableLineCount { + CGFloat result = (CGFloat)ceil(NSHeight([self bounds]) / [self lineHeight]); + HFASSERT(result >= 0.); + HFASSERT(result <= NSUIntegerMax); + return (NSUInteger)result; +} + +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight { + return viewHeight / [self lineHeight]; +} + +- (void)setFrameSize:(NSSize)size { + NSUInteger currentBytesPerLine = [self bytesPerLine]; + double currentLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])]; + [super setFrameSize:size]; + NSUInteger newBytesPerLine = [self maximumBytesPerLineForViewWidth:size.width]; + double newLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])]; + HFControllerPropertyBits bits = 0; + if (newBytesPerLine != currentBytesPerLine) bits |= (HFControllerBytesPerLine | HFControllerDisplayedLineRange); + if (newLineCount != currentLineCount) bits |= HFControllerDisplayedLineRange; + if (bits) [[self representer] representerChangedProperties:bits]; +} + +- (CGFloat)advanceBetweenColumns { + UNIMPLEMENTED(); +} + +- (CGFloat)advancePerCharacter { + UNIMPLEMENTED(); +} + +- (CGFloat)advancePerColumn { + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + if (bytesPerColumn == 0) { + return 0; + } + else { + return [self advancePerCharacter] * (bytesPerColumn / [self bytesPerCharacter]) + [self advanceBetweenColumns]; + } +} + +- (CGFloat)totalAdvanceForBytesInRange:(NSRange)range { + if (range.length == 0) return 0; + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + HFASSERT(bytesPerColumn == 0 || [self bytesPerLine] % bytesPerColumn == 0); + CGFloat result = (range.length * [self advancePerCharacter] / [self bytesPerCharacter]) ; + if (bytesPerColumn > 0) { + NSUInteger numColumnSpaces = NSMaxRange(range) / bytesPerColumn - range.location / bytesPerColumn; //note that integer division does not distribute + result += numColumnSpaces * [self advanceBetweenColumns]; + } + return result; +} + +/* Returns the number of bytes in a character, e.g. if we are UTF-16 this would be 2. */ +- (NSUInteger)bytesPerCharacter { + return 1; +} + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth { + CGFloat availableSpace = (CGFloat)(viewWidth - 2. * [self horizontalContainerInset]); + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn], bytesPerCharacter = [self bytesPerCharacter]; + if (bytesPerColumn == 0) { + /* No columns */ + NSUInteger numChars = (NSUInteger)(availableSpace / [self advancePerCharacter]); + /* Return it, except it's at least one character */ + return MAX(numChars, 1u) * bytesPerCharacter; + } + else { + /* We have some columns */ + CGFloat advancePerColumn = [self advancePerColumn]; + //spaceRequiredForNColumns = N * (advancePerColumn) - spaceBetweenColumns + CGFloat fractionalColumns = (availableSpace + [self advanceBetweenColumns]) / advancePerColumn; + NSUInteger columnCount = (NSUInteger)fmax(1., HFFloor(fractionalColumns)); + return columnCount * bytesPerColumn; + } +} + + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + HFASSERT(bytesPerLine > 0); + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + CGFloat result; + if (bytesPerColumn == 0) { + result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerCharacter] * (bytesPerLine / [self bytesPerCharacter])); + } + else { + HFASSERT(bytesPerLine % bytesPerColumn == 0); + result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerColumn] * (bytesPerLine / bytesPerColumn) - [self advanceBetweenColumns]); + } + return result; +} + +- (BOOL)isEditable { + return _hftvflags.editable; +} + +- (void)setEditable:(BOOL)val { + if (val != _hftvflags.editable) { + _hftvflags.editable = val; + [self _updateCaretTimer]; + } +} + +- (BOOL)shouldAntialias { + return _hftvflags.antialias; +} + +- (void)setShouldAntialias:(BOOL)val { + _hftvflags.antialias = !!val; + [self setNeedsDisplay:YES]; +} + +- (BOOL)behavesAsTextField { + return [[self representer] behavesAsTextField]; +} + +- (BOOL)showsFocusRing { + return [[self representer] behavesAsTextField]; +} + +- (BOOL)isWithinMouseDown { + return _hftvflags.withinMouseDown; +} + +- (void)_windowDidChangeKeyStatus:(NSNotification *)note { + USE(note); + [self _updateCaretTimer]; + if ([[note name] isEqualToString:NSWindowDidBecomeKeyNotification]) { + [self _forceCaretOnIfHasCaretTimer]; + } + if ([self showsFocusRing] && self == [[self window] firstResponder]) { + [[self superview] setNeedsDisplayInRect:NSInsetRect([self frame], -6, -6)]; + } + [self setNeedsDisplay:YES]; +} + +- (void)viewDidMoveToWindow { + [self _updateCaretTimer]; + HFRegisterViewForWindowAppearanceChanges(self, @selector(_windowDidChangeKeyStatus:), ! _hftvflags.registeredForAppNotifications); + _hftvflags.registeredForAppNotifications = YES; + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow { + HFUnregisterViewForWindowAppearanceChanges(self, NO /* appToo */); + [super viewWillMoveToWindow:newWindow]; +} + +/* Computes the character at the given index for selection, properly handling the case where the point is outside the bounds */ +- (NSUInteger)characterAtPointForSelection:(NSPoint)point { + NSPoint mungedPoint = point; + // shift us right by half an advance so that we trigger at the midpoint of each character, rather than at the x origin + mungedPoint.x += [self advancePerCharacter] / (CGFloat)2.; + // make sure we're inside the bounds + const NSRect bounds = [self bounds]; + mungedPoint.x = HFMax(NSMinX(bounds), mungedPoint.x); + mungedPoint.x = HFMin(NSMaxX(bounds), mungedPoint.x); + mungedPoint.y = HFMax(NSMinY(bounds), mungedPoint.y); + mungedPoint.y = HFMin(NSMaxY(bounds), mungedPoint.y); + return [self indexOfCharacterAtPoint:mungedPoint]; +} + +- (NSUInteger)maximumCharacterIndex { + //returns the maximum character index that the selection may lie on. It is one beyond the last byte index, to represent the cursor at the end of the document. + return [[self data] length] / [self bytesPerCharacter]; +} + +- (void)mouseDown:(NSEvent *)event { + HFASSERT(_hftvflags.withinMouseDown == 0); + _hftvflags.withinMouseDown = 1; + [self _forceCaretOnIfHasCaretTimer]; + NSPoint mouseDownLocation = [self convertPoint:[event locationInWindow] fromView:nil]; + NSUInteger characterIndex = [self characterAtPointForSelection:mouseDownLocation]; + + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); //characterIndex may be one beyond the last index, to represent the cursor at the end of the document + [[self representer] beginSelectionWithEvent:event forCharacterIndex:characterIndex]; + + /* Drive the event loop in event tracking mode until we're done */ + HFASSERT(_hftvflags.receivedMouseUp == NO); //paranoia - detect any weird recursive invocations + NSDate *endDate = [NSDate distantFuture]; + + /* Start periodic events for autoscroll */ + [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05]; + + NSPoint autoscrollLocation = mouseDownLocation; + while (! _hftvflags.receivedMouseUp) { + @autoreleasepool { + NSEvent *ev = [NSApp nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask untilDate:endDate inMode:NSEventTrackingRunLoopMode dequeue:YES]; + + if ([ev type] == NSPeriodic) { + // autoscroll if drag is out of view bounds + CGFloat amountToScroll = 0; + NSRect bounds = [self bounds]; + if (autoscrollLocation.y < NSMinY(bounds)) { + amountToScroll = (autoscrollLocation.y - NSMinY(bounds)) / [self lineHeight]; + } + else if (autoscrollLocation.y > NSMaxY(bounds)) { + amountToScroll = (autoscrollLocation.y - NSMaxY(bounds)) / [self lineHeight]; + } + if (amountToScroll != 0.) { + [[[self representer] controller] scrollByLines:amountToScroll]; + characterIndex = [self characterAtPointForSelection:autoscrollLocation]; + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); + [[self representer] continueSelectionWithEvent:ev forCharacterIndex:characterIndex]; + } + } + else if ([ev type] == NSLeftMouseDragged) { + autoscrollLocation = [self convertPoint:[ev locationInWindow] fromView:nil]; + } + + [NSApp sendEvent:ev]; + } // @autoreleasepool + } + + [NSEvent stopPeriodicEvents]; + + _hftvflags.receivedMouseUp = NO; + _hftvflags.withinMouseDown = 0; +} + +- (void)mouseDragged:(NSEvent *)event { + if (! _hftvflags.withinMouseDown) return; + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + NSUInteger characterIndex = [self characterAtPointForSelection:location]; + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); + [[self representer] continueSelectionWithEvent:event forCharacterIndex:characterIndex]; +} + +- (void)mouseUp:(NSEvent *)event { + if (! _hftvflags.withinMouseDown) return; + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + NSUInteger characterIndex = [self characterAtPointForSelection:location]; + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); + [[self representer] endSelectionWithEvent:event forCharacterIndex:characterIndex]; + _hftvflags.receivedMouseUp = YES; +} + +- (void)keyDown:(NSEvent *)event { + HFASSERT(event != NULL); + [self interpretKeyEvents:@[event]]; +} + +- (void)scrollWheel:(NSEvent *)event { + [[self representer] scrollWheel:event]; +} + +- (void)insertText:(id)string { + if (! [self isEditable]) { + NSBeep(); + } + else { + if ([string isKindOfClass:[NSAttributedString class]]) string = [string string]; + [NSCursor setHiddenUntilMouseMoves:YES]; + [[self representer] insertText:string]; + } +} + +- (BOOL)handleCommand:(SEL)sel { + if (sel == @selector(insertTabIgnoringFieldEditor:)) { + [self insertText:@"\t"]; + } + else if ([self respondsToSelector:sel]) { + [self performSelector:sel withObject:nil]; + } + else { + return NO; + } + return YES; +} + +- (void)doCommandBySelector:(SEL)sel { + HFRepresenter *rep = [self representer]; + // NSLog(@"%s%s", _cmd, sel); + if ([self handleCommand:sel]) { + /* Nothing to do */ + } + else if ([rep respondsToSelector:sel]) { + [rep performSelector:sel withObject:self]; + } + else { + [super doCommandBySelector:sel]; + } +} + +- (IBAction)selectAll:sender { + [[self representer] selectAll:sender]; +} + +/* Indicates whether at least one byte is selected */ +- (BOOL)_selectionIsNonEmpty { + NSArray *selection = [[[self representer] controller] selectedContentsRanges]; + FOREACH(HFRangeWrapper *, rangeWrapper, selection) { + if ([rangeWrapper HFRange].length > 0) return YES; + } + return NO; +} + +- (SEL)_pasteboardOwnerStringTypeWritingSelector { + UNIMPLEMENTED(); +} + +- (void)paste:sender { + if (! [self isEditable]) { + NSBeep(); + } + else { + USE(sender); + [[self representer] pasteBytesFromPasteboard:[NSPasteboard generalPasteboard]]; + } +} + +- (void)copy:sender { + USE(sender); + [[self representer] copySelectedBytesToPasteboard:[NSPasteboard generalPasteboard]]; +} + +- (void)cut:sender { + USE(sender); + [[self representer] cutSelectedBytesToPasteboard:[NSPasteboard generalPasteboard]]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)item { + SEL action = [item action]; + if (action == @selector(selectAll:)) return YES; + else if (action == @selector(cut:)) return [[self representer] canCut]; + else if (action == @selector(copy:)) return [self _selectionIsNonEmpty]; + else if (action == @selector(paste:)) return [[self representer] canPasteFromPasteboard:[NSPasteboard generalPasteboard]]; + else return YES; +} + +@end diff --git a/HexFiend/HFRepresenterTextViewCallout.h b/HexFiend/HFRepresenterTextViewCallout.h new file mode 100644 index 00000000..42ae7e3a --- /dev/null +++ b/HexFiend/HFRepresenterTextViewCallout.h @@ -0,0 +1,31 @@ +// +// HFRepresenterTextViewCallout.h +// HexFiend_2 +// +// Copyright 2011 ridiculous_fish. All rights reserved. +// + +#import + +@class HFRepresenterTextView; + +#define kHFRepresenterTextViewCalloutMaxGlyphCount 2u + +@interface HFRepresenterTextViewCallout : NSObject { + CGFloat rotation; + NSPoint tipOrigin; + NSPoint pinStart, pinEnd; +} + +@property(nonatomic) NSInteger byteOffset; +@property(nonatomic, copy) NSColor *color; +@property(nonatomic, copy) NSString *label; +@property(nonatomic, retain) id representedObject; +@property(readonly) NSRect rect; + ++ (void)layoutCallouts:(NSArray *)callouts inView:(HFRepresenterTextView *)textView; + +- (void)drawShadowWithClip:(NSRect)clip; +- (void)drawWithClip:(NSRect)clip; + +@end diff --git a/HexFiend/HFRepresenterTextViewCallout.m b/HexFiend/HFRepresenterTextViewCallout.m new file mode 100644 index 00000000..ae46bd88 --- /dev/null +++ b/HexFiend/HFRepresenterTextViewCallout.m @@ -0,0 +1,477 @@ +// +// HFRepresenterTextViewCallout.m +// HexFiend_2 +// +// Copyright 2011 ridiculous_fish. All rights reserved. +// + +#import "HFRepresenterTextViewCallout.h" +#import "HFRepresenterTextView.h" + +static const CGFloat HFTeardropRadius = 12; +static const CGFloat HFTeadropTipScale = 2.5; + +static const CGFloat HFShadowXOffset = -6; +static const CGFloat HFShadowYOffset = 0; +static const CGFloat HFShadowOffscreenHack = 3100; + +static NSPoint rotatePoint(NSPoint center, NSPoint point, CGFloat percent) { + CGFloat radians = percent * M_PI * 2; + CGFloat x = point.x - center.x; + CGFloat y = point.y - center.y; + CGFloat newX = x * cos(radians) + y * sin(radians); + CGFloat newY = x * -sin(radians) + y * cos(radians); + return NSMakePoint(center.x + newX, center.y + newY); +} + +static NSPoint scalePoint(NSPoint center, NSPoint point, CGFloat percent) { + CGFloat x = point.x - center.x; + CGFloat y = point.y - center.y; + CGFloat newX = x * percent; + CGFloat newY = y * percent; + return NSMakePoint(center.x + newX, center.y + newY); +} + +static NSBezierPath *copyTeardropPath(void) { + static NSBezierPath *sPath = nil; + if (! sPath) { + + CGFloat radius = HFTeardropRadius; + CGFloat rotation = 0; + CGFloat droppiness = .15; + CGFloat tipScale = HFTeadropTipScale; + CGFloat tipLengthFromCenter = radius * tipScale; + NSPoint bulbCenter = NSMakePoint(-tipLengthFromCenter, 0); + + NSPoint triangleCenter = rotatePoint(bulbCenter, NSMakePoint(bulbCenter.x + radius, bulbCenter.y), rotation); + NSPoint dropCorner1 = rotatePoint(bulbCenter, triangleCenter, droppiness / 2); + NSPoint dropCorner2 = rotatePoint(bulbCenter, triangleCenter, -droppiness / 2); + NSPoint dropTip = scalePoint(bulbCenter, triangleCenter, tipScale); + + NSBezierPath *path = [[NSBezierPath alloc] init]; + [path appendBezierPathWithArcWithCenter:bulbCenter radius:radius startAngle:-rotation * 360 + droppiness * 180. endAngle:-rotation * 360 - droppiness * 180. clockwise:NO]; + + [path moveToPoint:dropCorner1]; + [path lineToPoint:dropTip]; + [path lineToPoint:dropCorner2]; + [path closePath]; + + sPath = path; + } + return [sPath retain]; +} + + +@implementation HFRepresenterTextViewCallout + +/* A helpful struct for representing a wedge (portion of a circle). Wedges are counterclockwise. */ +typedef struct { + double offset; // 0 <= offset < 1 + double length; // 0 <= length <= 1 +} Wedge_t; + + +static inline double normalizeAngle(double x) { + /* Convert an angle to the range [0, 1). We typically only generate angles that are off by a full rotation, so a loop isn't too bad. */ + while (x >= 1.) x -= 1.; + while (x < 0.) x += 1.; + return x; +} + +static inline double distanceCCW(double a, double b) { return normalizeAngle(b-a); } + +static inline double wedgeMax(Wedge_t wedge) { + return normalizeAngle(wedge.offset + wedge.length); +} + +/* Computes the smallest wedge containing the two given wedges. Compute the wedge from the min of one to the furthest part of the other, and pick the smaller. */ +static Wedge_t wedgeUnion(Wedge_t wedge1, Wedge_t wedge2) { + // empty wedges don't participate + if (wedge1.length <= 0) return wedge2; + if (wedge2.length <= 0) return wedge1; + + Wedge_t union1 = wedge1; + union1.length = fmin(1., fmax(union1.length, distanceCCW(union1.offset, wedge2.offset) + wedge2.length)); + + Wedge_t union2 = wedge2; + union2.length = fmin(1., fmax(union2.length, distanceCCW(union2.offset, wedge1.offset) + wedge1.length)); + + Wedge_t result = (union1.length <= union2.length ? union1 : union2); + HFASSERT(result.length <= 1); + return result; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // Initialization code here. + } + + return self; +} + +- (void)dealloc { + [_representedObject release]; + [_color release]; + [_label release]; + [super dealloc]; +} + +- (NSComparisonResult)compare:(HFRepresenterTextViewCallout *)callout { + return [_representedObject compare:callout.representedObject]; +} + +static Wedge_t computeForbiddenAngle(double distanceFromEdge, double angleToEdge) { + Wedge_t newForbiddenAngle; + + /* This is how far it is to the center of our teardrop */ + const double teardropLength = HFTeardropRadius * HFTeadropTipScale; + + if (distanceFromEdge <= 0) { + /* We're above or below. */ + if (-distanceFromEdge >= (teardropLength + HFTeardropRadius)) { + /* We're so far above or below we won't be visible at all. No hope. */ + newForbiddenAngle = (Wedge_t){.offset = 0, .length = 1}; + } else { + /* We're either above or below the bounds, but there's a hope we can be visible */ + + double invertedAngleToEdge = normalizeAngle(angleToEdge + .5); + double requiredAngle; + if (-distanceFromEdge >= teardropLength) { + // We're too far north or south that all we can do is point in the right direction + requiredAngle = 0; + } else { + // By confining ourselves to required angles, we can make ourselves visible + requiredAngle = acos(-distanceFromEdge / teardropLength) / (2 * M_PI); + } + // Require at least a small spread + requiredAngle = fmax(requiredAngle, .04); + + double requiredMin = invertedAngleToEdge - requiredAngle; + double requiredMax = invertedAngleToEdge + requiredAngle; + + newForbiddenAngle = (Wedge_t){.offset = requiredMax, .length = distanceCCW(requiredMax, requiredMin) }; + } + } else if (distanceFromEdge < teardropLength) { + // We're onscreen, but some angle will be forbidden + double forbiddenAngle = acos(distanceFromEdge / teardropLength) / (2 * M_PI); + + // This is a wedge out of the top (or bottom) + newForbiddenAngle = (Wedge_t){.offset = angleToEdge - forbiddenAngle, .length = 2 * forbiddenAngle}; + } else { + /* Nothing prohibited at all */ + newForbiddenAngle = (Wedge_t){0, 0}; + } + return newForbiddenAngle; +} + + +static double distanceMod1(double a, double b) { + /* Assuming 0 <= a, b < 1, returns the distance between a and b, mod 1 */ + if (a > b) { + return fmin(a-b, b-a+1); + } else { + return fmin(b-a, a-b+1); + } +} + ++ (void)layoutCallouts:(NSArray *)callouts inView:(HFRepresenterTextView *)textView { + + // Keep track of how many drops are at a given location + NSCountedSet *dropsPerByteLoc = [[NSCountedSet alloc] init]; + + const CGFloat lineHeight = [textView lineHeight]; + const NSRect bounds = [textView bounds]; + + NSMutableArray *remainingCallouts = [[callouts mutableCopy] autorelease]; + [remainingCallouts sortUsingSelector:@selector(compare:)]; + + while ([remainingCallouts count] > 0) { + /* Get the next callout to lay out */ + const NSInteger byteLoc = [remainingCallouts[0] byteOffset]; + + /* Get all the callouts that share that byteLoc */ + NSMutableArray *sharedCallouts = [NSMutableArray array]; + FOREACH(HFRepresenterTextViewCallout *, testCallout, remainingCallouts) { + if ([testCallout byteOffset] == byteLoc) { + [sharedCallouts addObject:testCallout]; + } + } + + /* We expect to get at least one */ + const NSUInteger calloutCount = [sharedCallouts count]; + HFASSERT(calloutCount > 0); + + /* Get the character origin */ + const NSPoint characterOrigin = [textView originForCharacterAtByteIndex:byteLoc]; + + Wedge_t forbiddenAngle = {0, 0}; + + // Compute how far we are from the top (or bottom) + BOOL isNearerTop = (characterOrigin.y < NSMidY(bounds)); + double verticalDistance = (isNearerTop ? characterOrigin.y - NSMinY(bounds) : NSMaxY(bounds) - characterOrigin.y); + forbiddenAngle = wedgeUnion(forbiddenAngle, computeForbiddenAngle(verticalDistance, (isNearerTop ? .25 : .75))); + + // Compute how far we are from the left (or right) + BOOL isNearerLeft = (characterOrigin.x < NSMidX(bounds)); + double horizontalDistance = (isNearerLeft ? characterOrigin.x - NSMinX(bounds) : NSMaxX(bounds) - characterOrigin.x); + forbiddenAngle = wedgeUnion(forbiddenAngle, computeForbiddenAngle(horizontalDistance, (isNearerLeft ? .5 : 0.))); + + + /* How much will each callout rotate? No more than 1/8th. */ + HFASSERT(forbiddenAngle.length <= 1); + double changeInRotationPerCallout = fmin(.125, (1. - forbiddenAngle.length) / calloutCount); + double totalConsumedAmount = changeInRotationPerCallout * calloutCount; + + /* We would like to center around .375. */ + const double goalCenter = .375; + + /* We're going to pretend to work on a line segment that extends from the max prohibited angle all the way back to min */ + double segmentLength = 1. - forbiddenAngle.length; + double goalSegmentCenter = normalizeAngle(goalCenter - wedgeMax(forbiddenAngle)); //may exceed segmentLength! + + /* Now center us on the goal, or as close as we can get. */ + double consumedSegmentCenter; + + /* We only need to worry about wrapping around if we have some prohibited angle */ + if (forbiddenAngle.length <= 0) { //never expect < 0, but be paranoid + consumedSegmentCenter = goalSegmentCenter; + } else { + + /* The consumed segment center is confined to the segment range [amount/2, length - amount/2] */ + double consumedSegmentCenterMin = totalConsumedAmount/2; + double consumedSegmentCenterMax = segmentLength - totalConsumedAmount/2; + if (goalSegmentCenter >= consumedSegmentCenterMin && goalSegmentCenter < consumedSegmentCenterMax) { + /* We can hit our goal */ + consumedSegmentCenter = goalSegmentCenter; + } else { + /* Pick either the min or max location, depending on which one gets us closer to the goal segment center mod 1. */ + if (distanceMod1(goalSegmentCenter, consumedSegmentCenterMin) <= distanceMod1(goalSegmentCenter, consumedSegmentCenterMax)) { + consumedSegmentCenter = consumedSegmentCenterMin; + } else { + consumedSegmentCenter = consumedSegmentCenterMax; + } + + } + } + + /* Now convert this back to an angle */ + double consumedAngleCenter = normalizeAngle(wedgeMax(forbiddenAngle) + consumedSegmentCenter); + + // move us slightly towards the character + NSPoint teardropTipOrigin = NSMakePoint(characterOrigin.x + 1, characterOrigin.y + floor(lineHeight / 8.)); + + // make the pin + NSPoint pinStart, pinEnd; + pinStart = NSMakePoint(characterOrigin.x + .25, characterOrigin.y); + pinEnd = NSMakePoint(pinStart.x, pinStart.y + lineHeight); + + // store it all, invalidating as necessary + NSInteger i = 0; + FOREACH(HFRepresenterTextViewCallout *, callout, sharedCallouts) { + + /* Compute the rotation */ + double seq = (i+1)/2; //0, 1, -1, 2, -2... + if ((i & 1) == 0) seq = -seq; + //if we've got an even number of callouts, we want -.5, .5, -1.5, 1.5... + if (! (calloutCount & 1)) seq -= .5; + // compute the angle of rotation + double angle = consumedAngleCenter + seq * changeInRotationPerCallout; + // our notion of rotation has 0 meaning pointing right and going counterclockwise, but callouts with 0 pointing left and going clockwise, so convert + angle = normalizeAngle(.5 - angle); + + + NSRect beforeRect = [callout rect]; + + callout->rotation = angle; + callout->tipOrigin = teardropTipOrigin; + callout->pinStart = pinStart; + callout->pinEnd = pinEnd; + + // Only the first gets a pin + pinStart = pinEnd = NSZeroPoint; + + NSRect afterRect = [callout rect]; + + if (! NSEqualRects(beforeRect, afterRect)) { + [textView setNeedsDisplayInRect:beforeRect]; + [textView setNeedsDisplayInRect:afterRect]; + } + + i++; + } + + + /* We're done laying out these callouts */ + [remainingCallouts removeObjectsInArray:sharedCallouts]; + } + + [dropsPerByteLoc release]; +} + +- (CGAffineTransform)teardropTransform { + CGAffineTransform trans = CGAffineTransformMakeTranslation(tipOrigin.x, tipOrigin.y); + trans = CGAffineTransformRotate(trans, rotation * M_PI * 2); + return trans; +} + +- (NSRect)teardropBaseRect { + NSSize teardropSize = NSMakeSize(HFTeardropRadius * (1 + HFTeadropTipScale), HFTeardropRadius*2); + NSRect result = NSMakeRect(-teardropSize.width, -teardropSize.height/2, teardropSize.width, teardropSize.height); + return result; +} + +- (CGAffineTransform)shadowTransform { + CGFloat shadowXOffset = HFShadowXOffset; + CGFloat shadowYOffset = HFShadowYOffset; + CGFloat offscreenOffset = HFShadowOffscreenHack; + + // Figure out how much movement the shadow offset produces + CGFloat shadowTranslationDistance = hypot(shadowXOffset, shadowYOffset); + + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformTranslate(transform, tipOrigin.x + offscreenOffset - shadowXOffset, tipOrigin.y - shadowYOffset); + transform = CGAffineTransformRotate(transform, rotation * M_PI * 2 - atan2(shadowTranslationDistance, 2*HFTeardropRadius /* bulbHeight */)); + return transform; +} + +- (void)drawShadowWithClip:(NSRect)clip { + USE(clip); + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + + // Set the shadow. Note that these shadows are pretty unphysical for high rotations. + NSShadow *shadow = [[NSShadow alloc] init]; + [shadow setShadowBlurRadius:5.]; + [shadow setShadowOffset:NSMakeSize(HFShadowXOffset - HFShadowOffscreenHack, HFShadowYOffset)]; + [shadow setShadowColor:[NSColor colorWithDeviceWhite:0. alpha:.5]]; + [shadow set]; + [shadow release]; + + // Draw the shadow first and separately + CGAffineTransform transform = [self shadowTransform]; + CGContextConcatCTM(ctx, transform); + + NSBezierPath *teardrop = copyTeardropPath(); + [teardrop fill]; + [teardrop release]; + + // Clear the shadow + CGContextSetShadowWithColor(ctx, CGSizeZero, 0, NULL); + + // Undo the transform + CGContextConcatCTM(ctx, CGAffineTransformInvert(transform)); +} + +- (void)drawWithClip:(NSRect)clip { + USE(clip); + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + // Here's the font we'll use + CTFontRef ctfont = CTFontCreateWithName(CFSTR("Helvetica-Bold"), 1., NULL); + if (ctfont) { + // Set the font + [(NSFont *)ctfont set]; + + // Get characters + NSUInteger labelLength = MIN([_label length], kHFRepresenterTextViewCalloutMaxGlyphCount); + UniChar calloutUniLabel[kHFRepresenterTextViewCalloutMaxGlyphCount]; + [_label getCharacters:calloutUniLabel range:NSMakeRange(0, labelLength)]; + + // Get our glyphs and advances + CGGlyph glyphs[kHFRepresenterTextViewCalloutMaxGlyphCount]; + CGSize advances[kHFRepresenterTextViewCalloutMaxGlyphCount]; + CTFontGetGlyphsForCharacters(ctfont, calloutUniLabel, glyphs, labelLength); + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, glyphs, advances, labelLength); + + // Count our glyphs. Note: this won't work with any label containing spaces, etc. + NSUInteger glyphCount; + for (glyphCount = 0; glyphCount < labelLength; glyphCount++) { + if (glyphs[glyphCount] == 0) break; + } + + // Set our color. + [_color set]; + + // Draw the pin first + if (! NSEqualPoints(pinStart, pinEnd)) { + [NSBezierPath setDefaultLineWidth:1.25]; + [NSBezierPath strokeLineFromPoint:pinStart toPoint:pinEnd]; + } + + CGContextSaveGState(ctx); + CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect([self rect]), NULL); + + // Rotate and translate in preparation for drawing the teardrop + CGContextConcatCTM(ctx, [self teardropTransform]); + + // Draw the teardrop + NSBezierPath *teardrop = copyTeardropPath(); + [teardrop fill]; + [teardrop release]; + + // Draw the text with white and alpha. Use blend mode copy so that we clip out the shadow, and when the transparency layer is ended we'll composite over the text. + CGFloat textScale = (glyphCount == 1 ? 24 : 20); + + // we are flipped by default, so invert the rotation's sign to get the text direction. Use a little slop so we don't get jitter. + const CGFloat textDirection = (rotation <= .27 || rotation >= .73) ? -1 : 1; + + CGPoint positions[kHFRepresenterTextViewCalloutMaxGlyphCount]; + CGFloat totalAdvance = 0; + for (NSUInteger i=0; i < glyphCount; i++) { + // make sure to provide negative advances if necessary + positions[i].x = copysign(totalAdvance, -textDirection); + positions[i].y = 0; + CGFloat advance = advances[i].width; + // Workaround 5834794 + advance *= textScale; + // Tighten up the advances a little + advance *= .85; + totalAdvance += advance; + } + + + // Compute the vertical offset + CGFloat textYOffset = (glyphCount == 1 ? 4 : 5); + // LOL + if ([_label isEqualToString:@"6"] || [_label isEqualToString:@"7"] == 7) textYOffset -= 1; + + + // Apply this text matrix + NSRect bulbRect = [self teardropBaseRect]; + CGAffineTransform textMatrix = CGAffineTransformMakeScale(-copysign(textScale, textDirection), copysign(textScale, textDirection)); //roughly the font size we want + textMatrix.tx = NSMinX(bulbRect) + HFTeardropRadius + copysign(totalAdvance/2, textDirection); + + + if (textDirection < 0) { + textMatrix.ty = NSMaxY(bulbRect) - textYOffset; + } else { + textMatrix.ty = NSMinY(bulbRect) + textYOffset; + } + + // Draw + CGContextSetTextMatrix(ctx, textMatrix); + CGContextSetTextDrawingMode(ctx, kCGTextClip); + CGContextShowGlyphsAtPositions(ctx, glyphs, positions, glyphCount); + + CGContextSetBlendMode(ctx, kCGBlendModeCopy); + CGContextSetGrayFillColor(ctx, 1., .66); //faint white fill + CGContextFillRect(ctx, NSRectToCGRect(NSInsetRect(bulbRect, -20, -20))); + + // Done drawing, so composite + CGContextEndTransparencyLayer(ctx); + CGContextRestoreGState(ctx); // this also restores the clip, which is important + + // Done with the font + CFRelease(ctfont); + } +} + +- (NSRect)rect { + // get the transformed teardrop rect + NSRect result = NSRectFromCGRect(CGRectApplyAffineTransform(NSRectToCGRect([self teardropBaseRect]), [self teardropTransform])); + + // outset a bit for the shadow + result = NSInsetRect(result, -8, -8); + return result; +} + +@end diff --git a/HexFiend/HFRepresenterTextView_Internal.h b/HexFiend/HFRepresenterTextView_Internal.h new file mode 100644 index 00000000..70eed233 --- /dev/null +++ b/HexFiend/HFRepresenterTextView_Internal.h @@ -0,0 +1,11 @@ +#import + +#define GLYPH_BUFFER_SIZE 16u + +@interface HFRepresenterTextView (HFInternal) + +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)textView glyphs:(CGGlyph *)glyphs; +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs; +- (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)font; //uses CoreText. Here glyphs must have space for [string length] glyphs. + +@end diff --git a/HexFiend/HFRepresenter_Internal.h b/HexFiend/HFRepresenter_Internal.h new file mode 100644 index 00000000..9a0b704a --- /dev/null +++ b/HexFiend/HFRepresenter_Internal.h @@ -0,0 +1,7 @@ +#import + +@interface HFRepresenter (HFInternalStuff) + +- (void)_setController:(HFController *)controller; + +@end diff --git a/HexFiend/HFSharedMemoryByteSlice.h b/HexFiend/HFSharedMemoryByteSlice.h new file mode 100644 index 00000000..204492df --- /dev/null +++ b/HexFiend/HFSharedMemoryByteSlice.h @@ -0,0 +1,32 @@ +// +// HFSharedMemoryByteSlice.h +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFSharedMemoryByteSlice + @brief A subclass of HFByteSlice for working with data stored in memory. + + HFSharedMemoryByteSlice is a subclass of HFByteSlice that represents a portion of data from memory, e.g. typed or pasted in by the user. The term "shared" refers to the ability for mutiple HFSharedMemoryByteSlices to reference the same NSData; it does not mean that the data is in shared memory or shared between processes. + + Instances of HFSharedMemoryByteSlice are immutable (like all instances of HFByteSlice). However, to support efficient typing, the backing data is an instance of NSMutableData that may be grown. A referenced range of the NSMutableData will never have its contents changed, but it may be allowed to grow larger, so that the data does not have to be copied merely to append a single byte. This is implemented by overriding the -byteSliceByAppendingSlice: method of HFByteSlice. +*/ +@interface HFSharedMemoryByteSlice : HFByteSlice { + NSMutableData *data; + NSUInteger offset; + NSUInteger length; + unsigned char inlineTailLength; + unsigned char inlineTail[15]; //size chosen to exhaust padding of 32-byte allocator +} + +// copies the data +- (instancetype)initWithUnsharedData:(NSData *)data; + +// retains, does not copy +- (instancetype)initWithData:(NSMutableData *)data; +- (instancetype)initWithData:(NSMutableData *)data offset:(NSUInteger)offset length:(NSUInteger)length; + +@end diff --git a/HexFiend/HFSharedMemoryByteSlice.m b/HexFiend/HFSharedMemoryByteSlice.m new file mode 100644 index 00000000..fe7f43cc --- /dev/null +++ b/HexFiend/HFSharedMemoryByteSlice.m @@ -0,0 +1,209 @@ +// +// HFSharedMemoryByteSlice.m +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import +#import + +#define MAX_FAST_PATH_SIZE (1 << 13) + +#define MAX_TAIL_LENGTH (sizeof ((HFSharedMemoryByteSlice *)NULL)->inlineTail / sizeof *((HFSharedMemoryByteSlice *)NULL)->inlineTail) + +@implementation HFSharedMemoryByteSlice + +- (instancetype)initWithUnsharedData:(NSData *)unsharedData { + self = [super init]; + REQUIRE_NOT_NULL(unsharedData); + NSUInteger dataLength = [unsharedData length]; + NSUInteger inlineAmount = MIN(dataLength, MAX_TAIL_LENGTH); + NSUInteger sharedAmount = dataLength - inlineAmount; + HFASSERT(inlineAmount <= UCHAR_MAX); + inlineTailLength = (unsigned char)inlineAmount; + length = sharedAmount; + if (inlineAmount > 0) { + [unsharedData getBytes:inlineTail range:NSMakeRange(dataLength - inlineAmount, inlineAmount)]; + } + if (sharedAmount > 0) { + data = [[NSMutableData alloc] initWithBytes:[unsharedData bytes] length:sharedAmount]; + } + return self; +} + +// retains, does not copy +- (instancetype)initWithData:(NSMutableData *)dat { + REQUIRE_NOT_NULL(dat); + return [self initWithData:dat offset:0 length:[dat length]]; +} + +- (instancetype)initWithData:(NSMutableData *)dat offset:(NSUInteger)off length:(NSUInteger)len { + self = [super init]; + REQUIRE_NOT_NULL(dat); + HFASSERT(off + len >= off); //check for overflow + HFASSERT(off + len <= [dat length]); + offset = off; + length = len; + data = [dat retain]; + return self; +} + +- (instancetype)initWithSharedData:(NSMutableData *)dat offset:(NSUInteger)off length:(NSUInteger)len tail:(const void *)tail tailLength:(NSUInteger)tailLen { + self = [super init]; + if (off || len) REQUIRE_NOT_NULL(dat); + if (tailLen) REQUIRE_NOT_NULL(tail); + HFASSERT(tailLen <= MAX_TAIL_LENGTH); + HFASSERT(off + len >= off); + HFASSERT(off + len <= [dat length]); + offset = off; + length = len; + data = [dat retain]; + HFASSERT(tailLen <= UCHAR_MAX); + inlineTailLength = (unsigned char)tailLen; + memcpy(inlineTail, tail, tailLen); + HFASSERT([self length] == tailLen + len); + return self; +} + +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (unsigned long long)length { + return length + inlineTailLength; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)lrange { + HFASSERT(HFSum(length, inlineTailLength) >= HFMaxRange(lrange)); + NSRange requestedRange = NSMakeRange(ll2l(lrange.location), ll2l(lrange.length)); + NSRange dataRange = NSMakeRange(0, length); + NSRange tailRange = NSMakeRange(length, inlineTailLength); + NSRange dataRangeToCopy = NSIntersectionRange(requestedRange, dataRange); + NSRange tailRangeToCopy = NSIntersectionRange(requestedRange, tailRange); + HFASSERT(HFSum(dataRangeToCopy.length, tailRangeToCopy.length) == lrange.length); + + if (dataRangeToCopy.length > 0) { + HFASSERT(HFSum(NSMaxRange(dataRangeToCopy), offset) <= [data length]); + const void *bytes = [data bytes]; + memcpy(dst, bytes + dataRangeToCopy.location + offset, dataRangeToCopy.length); + } + if (tailRangeToCopy.length > 0) { + HFASSERT(tailRangeToCopy.location >= length); + HFASSERT(NSMaxRange(tailRangeToCopy) - length <= inlineTailLength); + memcpy(dst + dataRangeToCopy.length, inlineTail + tailRangeToCopy.location - length, tailRangeToCopy.length); + } +} + +- (HFByteSlice *)subsliceWithRange:(HFRange)lrange { + if (HFRangeEqualsRange(lrange, HFRangeMake(0, HFSum(length, inlineTailLength)))) return [[self retain] autorelease]; + + HFByteSlice *result; + HFASSERT(lrange.length > 0); + HFASSERT(HFSum(length, inlineTailLength) >= HFMaxRange(lrange)); + NSRange requestedRange = NSMakeRange(ll2l(lrange.location), ll2l(lrange.length)); + NSRange dataRange = NSMakeRange(0, length); + NSRange tailRange = NSMakeRange(length, inlineTailLength); + NSRange dataRangeToCopy = NSIntersectionRange(requestedRange, dataRange); + NSRange tailRangeToCopy = NSIntersectionRange(requestedRange, tailRange); + HFASSERT(HFSum(dataRangeToCopy.length, tailRangeToCopy.length) == lrange.length); + + NSMutableData *resultData = NULL; + NSUInteger resultOffset = 0; + NSUInteger resultLength = 0; + const unsigned char *tail = NULL; + NSUInteger tailLength = 0; + if (dataRangeToCopy.length > 0) { + resultData = data; + HFASSERT(resultData != NULL); + resultOffset = offset + dataRangeToCopy.location; + resultLength = dataRangeToCopy.length; + HFASSERT(HFSum(resultOffset, resultLength) <= [data length]); + } + if (tailRangeToCopy.length > 0) { + tail = inlineTail + tailRangeToCopy.location - length; + tailLength = tailRangeToCopy.length; + HFASSERT(tail >= inlineTail && tail + tailLength <= inlineTail + inlineTailLength); + } + HFASSERT(resultLength + tailLength == lrange.length); + result = [[[[self class] alloc] initWithSharedData:resultData offset:resultOffset length:resultLength tail:tail tailLength:tailLength] autorelease]; + HFASSERT([result length] == lrange.length); + return result; +} + +- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice { + REQUIRE_NOT_NULL(slice); + const unsigned long long sliceLength = [slice length]; + if (sliceLength == 0) return self; + + const unsigned long long thisLength = [self length]; + + HFASSERT(inlineTailLength <= MAX_TAIL_LENGTH); + NSUInteger spaceRemainingInTail = MAX_TAIL_LENGTH - inlineTailLength; + + if (sliceLength <= spaceRemainingInTail) { + /* We can do our work entirely within the tail */ + NSUInteger newTailLength = (NSUInteger)sliceLength + inlineTailLength; + unsigned char newTail[MAX_TAIL_LENGTH]; + memcpy(newTail, inlineTail, inlineTailLength); + [slice copyBytes:newTail + inlineTailLength range:HFRangeMake(0, sliceLength)]; + HFByteSlice *result = [[[[self class] alloc] initWithSharedData:data offset:offset length:length tail:newTail tailLength:newTailLength] autorelease]; + HFASSERT([result length] == HFSum(sliceLength, thisLength)); + return result; + } + else { + /* We can't do our work entirely in the tail; see if we can append some shared data. */ + HFASSERT(offset + length >= offset); + if (offset + length == [data length]) { + /* We can append some shared data. But impose some reasonable limit on how big our slice can get; this is 16 MB */ + if (HFSum(thisLength, sliceLength) < (1ULL << 24)) { + NSUInteger newDataOffset = offset; + NSUInteger newDataLength = length; + unsigned char newDataTail[MAX_TAIL_LENGTH]; + unsigned char newDataTailLength = MAX_TAIL_LENGTH; + NSMutableData *newData = (data ? data : [[[NSMutableData alloc] init] autorelease]); + + NSUInteger sliceLengthInt = ll2l(sliceLength); + NSUInteger newTotalTailLength = sliceLengthInt + inlineTailLength; + HFASSERT(newTotalTailLength >= MAX_TAIL_LENGTH); + NSUInteger amountToShiftIntoSharedData = newTotalTailLength - MAX_TAIL_LENGTH; + NSUInteger amountToShiftIntoSharedDataFromTail = MIN(amountToShiftIntoSharedData, inlineTailLength); + NSUInteger amountToShiftIntoSharedDataFromNewSlice = amountToShiftIntoSharedData - amountToShiftIntoSharedDataFromTail; + + if (amountToShiftIntoSharedDataFromTail > 0) { + HFASSERT(amountToShiftIntoSharedDataFromTail <= inlineTailLength); + [newData appendBytes:inlineTail length:amountToShiftIntoSharedDataFromTail]; + newDataLength += amountToShiftIntoSharedDataFromTail; + } + if (amountToShiftIntoSharedDataFromNewSlice > 0) { + HFASSERT(amountToShiftIntoSharedDataFromNewSlice <= [slice length]); + NSUInteger dataLength = offset + length + amountToShiftIntoSharedDataFromTail; + HFASSERT([newData length] == dataLength); + [newData setLength:dataLength + amountToShiftIntoSharedDataFromNewSlice]; + [slice copyBytes:[newData mutableBytes] + dataLength range:HFRangeMake(0, amountToShiftIntoSharedDataFromNewSlice)]; + newDataLength += amountToShiftIntoSharedDataFromNewSlice; + } + + /* We've updated our data; now figure out the tail */ + NSUInteger amountOfTailFromNewSlice = sliceLengthInt - amountToShiftIntoSharedDataFromNewSlice; + HFASSERT(amountOfTailFromNewSlice <= MAX_TAIL_LENGTH); + [slice copyBytes:newDataTail + MAX_TAIL_LENGTH - amountOfTailFromNewSlice range:HFRangeMake(sliceLengthInt - amountOfTailFromNewSlice, amountOfTailFromNewSlice)]; + + /* Copy the rest, if any, from the end of self */ + NSUInteger amountOfTailFromSelf = MAX_TAIL_LENGTH - amountOfTailFromNewSlice; + HFASSERT(amountOfTailFromSelf <= inlineTailLength); + if (amountOfTailFromSelf > 0) { + memcpy(newDataTail, inlineTail + inlineTailLength - amountOfTailFromSelf, amountOfTailFromSelf); + } + + HFByteSlice *result = [[[[self class] alloc] initWithSharedData:newData offset:newDataOffset length:newDataLength tail:newDataTail tailLength:newDataTailLength] autorelease]; + HFASSERT([result length] == HFSum([slice length], [self length])); + return result; + } + } + } + return nil; +} + +@end diff --git a/HexFiend/HFStatusBarRepresenter.h b/HexFiend/HFStatusBarRepresenter.h new file mode 100644 index 00000000..e70b893f --- /dev/null +++ b/HexFiend/HFStatusBarRepresenter.h @@ -0,0 +1,31 @@ +// +// HFStatusBarRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @enum HFStatusBarMode + The HFStatusBarMode enum is used to describe the format of the byte counts displayed by the status bar. +*/ +typedef NS_ENUM(NSUInteger, HFStatusBarMode) { + HFStatusModeDecimal, ///< The status bar should display byte counts in decimal + HFStatusModeHexadecimal, ///< The status bar should display byte counts in hexadecimal + HFStatusModeApproximate, ///< The text should display byte counts approximately (e.g. "56.3 KB") + HFSTATUSMODECOUNT ///< The number of modes, to allow easy cycling +}; + +/*! @class HFStatusBarRepresenter + @brief The HFRepresenter for the status bar. + + HFStatusBarRepresenter is a subclass of HFRepresenter responsible for showing the status bar, which displays information like the total length of the document, or the number of selected bytes. +*/ +@interface HFStatusBarRepresenter : HFRepresenter { + HFStatusBarMode statusMode; +} + +@property (nonatomic) HFStatusBarMode statusMode; + +@end diff --git a/HexFiend/HFStatusBarRepresenter.m b/HexFiend/HFStatusBarRepresenter.m new file mode 100644 index 00000000..702dfea5 --- /dev/null +++ b/HexFiend/HFStatusBarRepresenter.m @@ -0,0 +1,266 @@ +// +// HFStatusBarRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +#define kHFStatusBarDefaultModeUserDefaultsKey @"HFStatusBarDefaultMode" + +@interface HFStatusBarView : NSView { + NSCell *cell; + NSSize cellSize; + HFStatusBarRepresenter *representer; + NSDictionary *cellAttributes; + BOOL registeredForAppNotifications; +} + +- (void)setRepresenter:(HFStatusBarRepresenter *)rep; +- (void)setString:(NSString *)string; + +@end + + +@implementation HFStatusBarView + +- (void)_sharedInitStatusBarView { + NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + [style setAlignment:NSCenterTextAlignment]; + cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor colorWithCalibratedWhite:(CGFloat).15 alpha:1], NSForegroundColorAttributeName, [NSFont labelFontOfSize:10], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil]; + cell = [[NSCell alloc] initTextCell:@""]; + [cell setAlignment:NSCenterTextAlignment]; + [cell setBackgroundStyle:NSBackgroundStyleRaised]; +} + +- (instancetype)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + [self _sharedInitStatusBarView]; + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + [self _sharedInitStatusBarView]; + return self; +} + +// nothing to do in encodeWithCoder + +- (BOOL)isFlipped { return YES; } + +- (void)setRepresenter:(HFStatusBarRepresenter *)rep { + representer = rep; +} + +- (void)setString:(NSString *)string { + [cell setAttributedStringValue:[[[NSAttributedString alloc] initWithString:string attributes:cellAttributes] autorelease]]; + cellSize = [cell cellSize]; + [self setNeedsDisplay:YES]; +} + +- (void)drawDividerWithClip:(NSRect)clipRect { + [[NSColor lightGrayColor] set]; + NSRect bounds = [self bounds]; + NSRect lineRect = bounds; + lineRect.size.height = 1; + NSRectFill(NSIntersectionRect(lineRect, clipRect)); +} + + +- (NSGradient *)getGradient:(BOOL)active { + static NSGradient *sActiveGradient; + static NSGradient *sInactiveGradient; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sActiveGradient = [[NSGradient alloc] initWithColorsAndLocations: + [NSColor colorWithCalibratedWhite:.89 alpha:1.], 0.00, + [NSColor colorWithCalibratedWhite:.77 alpha:1.], 0.9, + [NSColor colorWithCalibratedWhite:.82 alpha:1.], 1.0, + nil]; + + sInactiveGradient = [[NSGradient alloc] initWithColorsAndLocations: + [NSColor colorWithCalibratedWhite:.93 alpha:1.], 0.00, + [NSColor colorWithCalibratedWhite:.87 alpha:1.], 0.9, + [NSColor colorWithCalibratedWhite:.90 alpha:1.], 1.0, + nil]; + }); + return active ? sActiveGradient : sInactiveGradient; +} + + +- (void)drawRect:(NSRect)clip { + USE(clip); + NSRect bounds = [self bounds]; + // [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set]; + // NSRectFill(clip); + + NSWindow *window = [self window]; + BOOL drawActive = (window == nil || [window isMainWindow] || [window isKeyWindow]); + [[self getGradient:drawActive] drawInRect:bounds angle:90.]; + + [self drawDividerWithClip:clip]; + NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height); + [cell drawWithFrame:cellRect inView:self]; +} + +- (void)mouseDown:(NSEvent *)event { + USE(event); + HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT; + [representer setStatusMode:newMode]; + [[NSUserDefaults standardUserDefaults] setInteger:newMode forKey:kHFStatusBarDefaultModeUserDefaultsKey]; +} + +- (void)windowDidChangeKeyStatus:(NSNotification *)note { + USE(note); + [self setNeedsDisplay:YES]; +} + +- (void)viewDidMoveToWindow { + HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications); + registeredForAppNotifications = YES; + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow { + HFUnregisterViewForWindowAppearanceChanges(self, NO); + [super viewWillMoveToWindow:newWindow]; +} + +- (void)dealloc { + HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications); + [cell release]; + [cellAttributes release]; + [super dealloc]; +} + +@end + +@implementation HFStatusBarRepresenter + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeInt64:statusMode forKey:@"HFStatusMode"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + statusMode = (NSUInteger)[coder decodeInt64ForKey:@"HFStatusMode"]; + return self; +} + +- (instancetype)init { + self = [super init]; + statusMode = [[NSUserDefaults standardUserDefaults] integerForKey:kHFStatusBarDefaultModeUserDefaultsKey]; + return self; +} + +- (NSView *)createView { + HFStatusBarView *view = [[HFStatusBarView alloc] initWithFrame:NSMakeRect(0, 0, 100, 18)]; + [view setRepresenter:self]; + [view setAutoresizingMask:NSViewWidthSizable]; + return view; +} + +- (NSString *)describeLength:(unsigned long long)length { + switch (statusMode) { + case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu byte%s", length, length == 1 ? "" : "s"]; + case HFStatusModeHexadecimal: return [NSString stringWithFormat:@"0x%llX byte%s", length, length == 1 ? "" : "s"]; + case HFStatusModeApproximate: return [NSString stringWithFormat:@"%@", HFDescribeByteCount(length)]; + default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @""; + } +} + +- (NSString *)describeOffset:(unsigned long long)offset { + switch (statusMode) { + case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu", offset]; + case HFStatusModeHexadecimal: return [NSString stringWithFormat:@"0x%llX", offset]; + case HFStatusModeApproximate: return [NSString stringWithFormat:@"%@", HFDescribeByteCount(offset)]; + default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @""; + } +} + +/* same as describeOffset, except we treat Approximate like Hexadecimal */ +- (NSString *)describeOffsetExcludingApproximate:(unsigned long long)offset { + switch (statusMode) { + case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu", offset]; + case HFStatusModeHexadecimal: + case HFStatusModeApproximate: return [NSString stringWithFormat:@"0x%llX", offset]; + default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @""; + } +} + +- (NSString *)stringForEmptySelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length { + return [NSString stringWithFormat:@"%@ out of %@", [self describeOffset:offset], [self describeLength:length]]; +} + +- (NSString *)stringForSingleByteSelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length { + return [NSString stringWithFormat:@"Byte %@ selected out of %@", [self describeOffset:offset], [self describeLength:length]]; +} + +- (NSString *)stringForSingleRangeSelection:(HFRange)range length:(unsigned long long)length { + return [NSString stringWithFormat:@"%@ selected at offset %@ out of %@", [self describeLength:range.length], [self describeOffsetExcludingApproximate:range.location], [self describeLength:length]]; +} + +- (NSString *)stringForMultipleSelectionsWithLength:(unsigned long long)multipleSelectionLength length:(unsigned long long)length { + return [NSString stringWithFormat:@"%@ selected at multiple offsets out of %@", [self describeLength:multipleSelectionLength], [self describeLength:length]]; +} + + +- (void)updateString { + NSString *string = nil; + HFController *controller = [self controller]; + if (controller) { + unsigned long long length = [controller contentsLength]; + NSArray *ranges = [controller selectedContentsRanges]; + NSUInteger rangeCount = [ranges count]; + if (rangeCount == 1) { + HFRange range = [ranges[0] HFRange]; + if (range.length == 0) { + string = [self stringForEmptySelectionAtOffset:range.location length:length]; + } + else if (range.length == 1) { + string = [self stringForSingleByteSelectionAtOffset:range.location length:length]; + } + else { + string = [self stringForSingleRangeSelection:range length:length]; + } + } + else { + unsigned long long totalSelectionLength = 0; + FOREACH(HFRangeWrapper *, wrapper, ranges) { + HFRange range = [wrapper HFRange]; + totalSelectionLength = HFSum(totalSelectionLength, range.length); + } + string = [self stringForMultipleSelectionsWithLength:totalSelectionLength length:length]; + } + } + if (! string) string = @""; + [[self view] setString:string]; +} + +- (HFStatusBarMode)statusMode { + return statusMode; +} + +- (void)setStatusMode:(HFStatusBarMode)mode { + statusMode = mode; + [self updateString]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & (HFControllerContentLength | HFControllerSelectedRanges)) { + [self updateString]; + } +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(0, -1); +} + +@end diff --git a/HexFiend/HFStringEncodingTextRepresenter.h b/HexFiend/HFStringEncodingTextRepresenter.h new file mode 100644 index 00000000..2c5da7b5 --- /dev/null +++ b/HexFiend/HFStringEncodingTextRepresenter.h @@ -0,0 +1,26 @@ +// +// HFASCIITextRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFStringEncodingTextRepresenter + + @brief An HFRepresenter responsible for showing data interpreted via an NSStringEncoding. + + HFHexTextRepresenter is an HFRepresenter responsible for showing and editing data interpreted via an NSStringEncoding. Currently only supersets of ASCII are supported. +*/ +@interface HFStringEncodingTextRepresenter : HFTextRepresenter { + NSStringEncoding stringEncoding; + +} + +/*! Get the string encoding for this representer. The default encoding is [NSString defaultCStringEncoding]. */ +@property (nonatomic) NSStringEncoding encoding; + +/*! Set the string encoding for this representer. */ + +@end diff --git a/HexFiend/HFStringEncodingTextRepresenter.m b/HexFiend/HFStringEncodingTextRepresenter.m new file mode 100644 index 00000000..27ea995c --- /dev/null +++ b/HexFiend/HFStringEncodingTextRepresenter.m @@ -0,0 +1,121 @@ +// +// HFASCIITextRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +@interface HFStringEncodingPasteboardOwner : HFPasteboardOwner { + NSStringEncoding encoding; +} +@property (nonatomic) NSStringEncoding encoding; +@end + +@implementation HFStringEncodingPasteboardOwner +- (void)setEncoding:(NSStringEncoding)val { encoding = val; } +- (NSStringEncoding)encoding { return encoding; } + +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker { + HFASSERT([type isEqual:NSStringPboardType]); + HFByteArray *byteArray = [self byteArray]; + HFASSERT(length <= NSUIntegerMax); + NSUInteger dataLength = ll2l(length); + NSUInteger stringLength = dataLength; + NSUInteger offset = 0, remaining = dataLength; + unsigned char * restrict const stringBuffer = check_malloc(stringLength); + while (remaining > 0) { + NSUInteger amountToCopy = MIN(32u * 1024u, remaining); + [byteArray copyBytes:stringBuffer + offset range:HFRangeMake(offset, amountToCopy)]; + offset += amountToCopy; + remaining -= amountToCopy; + } + NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:encoding freeWhenDone:YES]; + [pboard setString:string forType:type]; + [string release]; +} + +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { + return dataLength; +} + +@end + +@implementation HFStringEncodingTextRepresenter + +- (instancetype)init { + self = [super init]; + stringEncoding = [NSString defaultCStringEncoding]; + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + stringEncoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeInt64:stringEncoding forKey:@"HFStringEncoding"]; +} + +- (Class)_textViewClass { + return [HFRepresenterStringEncodingTextView class]; +} + +- (NSStringEncoding)encoding { + return stringEncoding; +} + +- (void)setEncoding:(NSStringEncoding)encoding { + stringEncoding = encoding; + [[self view] setEncoding:encoding]; + [[self controller] representer:self changedProperties:HFControllerViewSizeRatios]; +} + +- (void)initializeView { + [[self view] setEncoding:stringEncoding]; + [super initializeView]; +} + +- (void)insertText:(NSString *)text { + REQUIRE_NOT_NULL(text); + NSData *data = [text dataUsingEncoding:[self encoding] allowLossyConversion:NO]; + if (! data) { + NSBeep(); + } + else if ([data length]) { // a 0 length text can come about via e.g. option-e + [[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:YES]; + } +} + +- (NSData *)dataFromPasteboardString:(NSString *)string { + REQUIRE_NOT_NULL(string); + return [string dataUsingEncoding:[self encoding] allowLossyConversion:NO]; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(1, 0); +} + +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges]; + HFASSERT(selection != NULL); + if ([selection length] == 0) { + NSBeep(); + } + else { + HFStringEncodingPasteboardOwner *owner = [HFStringEncodingPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]]; + [owner setEncoding:[self encoding]]; + [owner setBytesPerLine:[self bytesPerLine]]; + } +} + +@end diff --git a/HexFiend/HFTextRepresenter.h b/HexFiend/HFTextRepresenter.h new file mode 100644 index 00000000..306a197f --- /dev/null +++ b/HexFiend/HFTextRepresenter.h @@ -0,0 +1,39 @@ +// +// HFTextRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +/*! @class HFTextRepresenter + @brief An HFRepresenter that draws text (e.g. the hex or ASCII view). + + HFTextRepresenter is an abstract subclass of HFRepresenter that is responsible for displaying text. There are two concrete subclass, HFHexTextRepresenter and HFStringEncodingTextRepresenter. + + Most of the functionality of HFTextRepresenter is private, and there is not yet enough exposed to allow creating new representers based on it. However, there is a small amount of configurability. +*/ +@interface HFTextRepresenter : HFRepresenter {} +/*! Given a rect edge, return an NSRect representing the maximum edge in that direction, in the coordinate system of the receiver's view. The dimension in the direction of the edge is 0 (so if edge is NSMaxXEdge, the resulting width is 0). The returned rect is in the coordinate space of the receiver's view. If the byte range is not displayed, returns NSZeroRect. + + If range is entirely above the visible region, returns an NSRect whose width and height are 0, and whose origin is -CGFLOAT_MAX (the most negative CGFloat). If range is entirely below the visible region, returns the same except with CGFLOAT_MAX (positive). + + This raises an exception if range is empty. +*/ +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forByteRange:(HFRange)range; + +/*! Returns the origin of the character at the given byte index. The returned point is in the coordinate space of the receiver's view. If the character is not displayed because it would be above the displayed range, returns {0, -CGFLOAT_MAX}. If it is not displayed because it is below the displayed range, returns {0, CGFLOAT_MAX}. As a special affordance, you may pass a byte index one greater than the contents length of the controller, and it will return the result as if the byte existed. + */ +- (NSPoint)locationOfCharacterAtByteIndex:(unsigned long long)byteIndex; + +/*! The per-row background colors. Each row is drawn with the next color in turn, cycling back to the beginning when the array is exhausted. Any empty space is filled with the first color in the array. If the array is empty, then the background is drawn with \c clearColor. + */ +@property (nonatomic, copy) NSArray *rowBackgroundColors; + +/*! Whether the text view behaves like a text field (YES) or a text view (NO). Currently this determines whether it draws a focus ring when it is the first responder. +*/ +@property (nonatomic) BOOL behavesAsTextField; + +@end diff --git a/HexFiend/HFTextRepresenter.m b/HexFiend/HFTextRepresenter.m new file mode 100644 index 00000000..edc35154 --- /dev/null +++ b/HexFiend/HFTextRepresenter.m @@ -0,0 +1,373 @@ +// +// HFTextRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import +#import + +@implementation HFTextRepresenter + +- (Class)_textViewClass { + UNIMPLEMENTED(); +} + +- (instancetype)init { + self = [super init]; + + NSColor *color1 = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; + NSColor *color2 = [NSColor colorWithCalibratedRed:.87 green:.89 blue:1. alpha:1.]; + _rowBackgroundColors = [@[color1, color2] retain]; + + return self; +} + +- (void)dealloc { + if ([self isViewLoaded]) { + [[self view] clearRepresenter]; + } + [_rowBackgroundColors release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeBool:_behavesAsTextField forKey:@"HFBehavesAsTextField"]; + [coder encodeObject:_rowBackgroundColors forKey:@"HFRowBackgroundColors"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + _behavesAsTextField = [coder decodeBoolForKey:@"HFBehavesAsTextField"]; + _rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain]; + return self; +} + +- (NSView *)createView { + HFRepresenterTextView *view = [[[self _textViewClass] alloc] initWithRepresenter:self]; + [view setAutoresizingMask:NSViewHeightSizable]; + return view; +} + +- (HFByteArrayDataStringType)byteArrayDataStringType { + UNIMPLEMENTED(); +} + +- (HFRange)entireDisplayedRange { + HFController *controller = [self controller]; + unsigned long long contentsLength = [controller contentsLength]; + HFASSERT(controller != NULL); + HFFPRange displayedLineRange = [controller displayedLineRange]; + NSUInteger bytesPerLine = [controller bytesPerLine]; + unsigned long long lineStart = HFFPToUL(floorl(displayedLineRange.location)); + unsigned long long lineEnd = HFFPToUL(ceill(displayedLineRange.location + displayedLineRange.length)); + HFASSERT(lineEnd >= lineStart); + HFRange byteRange = HFRangeMake(HFProductULL(bytesPerLine, lineStart), HFProductULL(lineEnd - lineStart, bytesPerLine)); + if (byteRange.length == 0) { + /* This can happen if we are too small to even show one line */ + return HFRangeMake(0, 0); + } + else { + HFASSERT(byteRange.location <= contentsLength); + byteRange.length = MIN(byteRange.length, contentsLength - byteRange.location); + HFASSERT(HFRangeIsSubrangeOfRange(byteRange, HFRangeMake(0, [controller contentsLength]))); + return byteRange; + } +} + +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forByteRange:(HFRange)byteRange { + HFASSERT(byteRange.length > 0); + HFRange displayedRange = [self entireDisplayedRange]; + HFRange intersection = HFIntersectionRange(displayedRange, byteRange); + NSRect result; + if (intersection.length > 0) { + NSRange intersectionNSRange = NSMakeRange(ll2l(intersection.location - displayedRange.location), ll2l(intersection.length)); + if (intersectionNSRange.length > 0) { + result = [[self view] furthestRectOnEdge:edge forRange:intersectionNSRange]; + } + } + else if (byteRange.location < displayedRange.location) { + /* We're below it. */ + return NSMakeRect(-CGFLOAT_MAX, -CGFLOAT_MAX, 0, 0); + } + else if (byteRange.location >= HFMaxRange(displayedRange)) { + /* We're above it */ + return NSMakeRect(CGFLOAT_MAX, CGFLOAT_MAX, 0, 0); + } + else { + /* Shouldn't be possible to get here */ + [NSException raise:NSInternalInconsistencyException format:@"furthestRectOnEdge: expected an intersection, or a range below or above the byte range, but nothin'"]; + } + return result; +} + +- (NSPoint)locationOfCharacterAtByteIndex:(unsigned long long)index { + NSPoint result; + HFRange displayedRange = [self entireDisplayedRange]; + if (HFLocationInRange(index, displayedRange) || index == HFMaxRange(displayedRange)) { + NSUInteger location = ll2l(index - displayedRange.location); + result = [[self view] originForCharacterAtByteIndex:location]; + } + else if (index < displayedRange.location) { + result = NSMakePoint(-CGFLOAT_MAX, -CGFLOAT_MAX); + } + else { + result = NSMakePoint(CGFLOAT_MAX, CGFLOAT_MAX); + } + return result; +} + +- (HFTextVisualStyleRun *)styleForAttributes:(NSSet *)attributes range:(NSRange)range { + HFTextVisualStyleRun *run = [[[HFTextVisualStyleRun alloc] init] autorelease]; + [run setRange:range]; + [run setForegroundColor:[NSColor blackColor]]; + + return run; +} + +- (NSArray *)stylesForRange:(HFRange)range { + return nil; +} + +- (void)updateText { + HFController *controller = [self controller]; + HFRepresenterTextView *view = [self view]; + HFRange entireDisplayedRange = [self entireDisplayedRange]; + [view setData:[controller dataForRange:entireDisplayedRange]]; + [view setStyles:[self stylesForRange:entireDisplayedRange]]; + HFFPRange lineRange = [controller displayedLineRange]; + long double offsetLongDouble = lineRange.location - floorl(lineRange.location); + CGFloat offset = ld2f(offsetLongDouble); + [view setVerticalOffset:offset]; + [view setStartingLineBackgroundColorIndex:ll2l(HFFPToUL(floorl(lineRange.location)) % NSUIntegerMax)]; +} + +- (void)initializeView { + [super initializeView]; + HFRepresenterTextView *view = [self view]; + HFController *controller = [self controller]; + if (controller) { + [view setFont:[controller font]]; + [view setEditable:[controller editable]]; + [self updateText]; + } + else { + [view setFont:[NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]]; + } +} + +- (void)scrollWheel:(NSEvent *)event { + [[self controller] scrollWithScrollEvent:event]; +} + +- (void)selectAll:(id)sender { + [[self controller] selectAll:sender]; +} + +- (double)selectionPulseAmount { + return [[self controller] selectionPulseAmount]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & (HFControllerFont | HFControllerLineHeight)) { + [[self view] setFont:[[self controller] font]]; + } + if (bits & (HFControllerContentValue | HFControllerDisplayedLineRange | HFControllerByteRangeAttributes)) { + [self updateText]; + } + if (bits & (HFControllerSelectedRanges | HFControllerDisplayedLineRange)) { + [[self view] updateSelectedRanges]; + } + if (bits & (HFControllerEditable)) { + [[self view] setEditable:[[self controller] editable]]; + } + if (bits & (HFControllerAntialias)) { + [[self view] setShouldAntialias:[[self controller] shouldAntialias]]; + } + if (bits & (HFControllerShowCallouts)) { + [[self view] setShouldDrawCallouts:[[self controller] shouldShowCallouts]]; + } + if (bits & (HFControllerColorBytes)) { + if([[self controller] shouldColorBytes]) { + [[self view] setByteColoring: ^(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a){ + *r = *g = *b = (uint8_t)(255 * ((255-byte)/255.0*0.6+0.4)); + *a = (uint8_t)(255 * 0.7); + }]; + } else { + [[self view] setByteColoring:NULL]; + } + } + [super controllerDidChange:bits]; +} + +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight { + return [[self view] maximumAvailableLinesForViewHeight:viewHeight]; +} + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth { + return [[self view] maximumBytesPerLineForViewWidth:viewWidth]; +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + return [[self view] minimumViewWidthForBytesPerLine:bytesPerLine]; +} + +- (NSUInteger)byteGranularity { + HFRepresenterTextView *view = [self view]; + NSUInteger bytesPerColumn = MAX([view bytesPerColumn], 1u), bytesPerCharacter = [view bytesPerCharacter]; + return HFLeastCommonMultiple(bytesPerColumn, bytesPerCharacter); +} + +- (NSArray *)displayedSelectedContentsRanges { + HFController *controller = [self controller]; + NSArray *result; + NSArray *selectedRanges = [controller selectedContentsRanges]; + HFRange displayedRange = [self entireDisplayedRange]; + + HFASSERT(displayedRange.length <= NSUIntegerMax); + NEW_ARRAY(NSValue *, clippedSelectedRanges, [selectedRanges count]); + NSUInteger clippedRangeIndex = 0; + FOREACH(HFRangeWrapper *, wrapper, selectedRanges) { + HFRange selectedRange = [wrapper HFRange]; + BOOL clippedRangeIsVisible; + NSRange clippedSelectedRange; + /* Necessary because zero length ranges do not intersect anything */ + if (selectedRange.length == 0) { + /* Remember that {6, 0} is considered a subrange of {3, 3} */ + clippedRangeIsVisible = HFRangeIsSubrangeOfRange(selectedRange, displayedRange); + if (clippedRangeIsVisible) { + HFASSERT(selectedRange.location >= displayedRange.location); + clippedSelectedRange.location = ll2l(selectedRange.location - displayedRange.location); + clippedSelectedRange.length = 0; + } + } + else { + // selectedRange.length > 0 + clippedRangeIsVisible = HFIntersectsRange(selectedRange, displayedRange); + if (clippedRangeIsVisible) { + HFRange intersectionRange = HFIntersectionRange(selectedRange, displayedRange); + HFASSERT(intersectionRange.location >= displayedRange.location); + clippedSelectedRange.location = ll2l(intersectionRange.location - displayedRange.location); + clippedSelectedRange.length = ll2l(intersectionRange.length); + } + } + if (clippedRangeIsVisible) clippedSelectedRanges[clippedRangeIndex++] = [NSValue valueWithRange:clippedSelectedRange]; + } + result = [NSArray arrayWithObjects:clippedSelectedRanges count:clippedRangeIndex]; + FREE_ARRAY(clippedSelectedRanges); + return result; +} + +//maps bookmark keys as NSNumber to byte locations as NSNumbers. Because bookmark callouts may extend beyond the lines containing them, allow a larger range by 10 lines. +- (NSDictionary *)displayedBookmarkLocations { + NSMutableDictionary *result = nil; + HFController *controller = [self controller]; + NSUInteger rangeExtension = 10 * [controller bytesPerLine]; + HFRange displayedRange = [self entireDisplayedRange]; + + HFRange includedRange = displayedRange; + + /* Extend the bottom */ + unsigned long long bottomExtension = MIN(includedRange.location, rangeExtension); + includedRange.location -= bottomExtension; + includedRange.length += bottomExtension; + + /* Extend the top */ + unsigned long long topExtension = MIN([controller contentsLength] - HFMaxRange(includedRange), rangeExtension); + includedRange.length = HFSum(includedRange.length, topExtension); + + return result; +} + +- (unsigned long long)byteIndexForCharacterIndex:(NSUInteger)characterIndex { + HFController *controller = [self controller]; + HFFPRange lineRange = [controller displayedLineRange]; + unsigned long long scrollAmount = HFFPToUL(floorl(lineRange.location)); + unsigned long long byteIndex = HFProductULL(scrollAmount, [controller bytesPerLine]) + characterIndex * [[self view] bytesPerCharacter]; + return byteIndex; +} + +- (void)beginSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex { + [[self controller] beginSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]]; +} + +- (void)continueSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex { + [[self controller] continueSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]]; +} + +- (void)endSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex { + [[self controller] endSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]]; +} + +- (void)insertText:(NSString *)text { + USE(text); + UNIMPLEMENTED_VOID(); +} + +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb { + USE(pb); + UNIMPLEMENTED_VOID(); +} + +- (void)cutSelectedBytesToPasteboard:(NSPasteboard *)pb { + [self copySelectedBytesToPasteboard:pb]; + [[self controller] deleteSelection]; +} + +- (NSData *)dataFromPasteboardString:(NSString *)string { + USE(string); + UNIMPLEMENTED(); +} + +- (BOOL)canPasteFromPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + if ([[self controller] editable]) { + // we can paste if the pboard contains text or contains an HFByteArray + return [HFPasteboardOwner unpackByteArrayFromPasteboard:pb] || [pb availableTypeFromArray:@[NSStringPboardType]]; + } + return NO; +} + +- (BOOL)canCut { + /* We can cut if we are editable, we have at least one byte selected, and we are not in overwrite mode */ + HFController *controller = [self controller]; + if ([controller editMode] != HFInsertMode) return NO; + if (! [controller editable]) return NO; + + FOREACH(HFRangeWrapper *, rangeWrapper, [controller selectedContentsRanges]) { + if ([rangeWrapper HFRange].length > 0) return YES; //we have something selected + } + return NO; // we did not find anything selected +} + +- (BOOL)pasteBytesFromPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + BOOL result = NO; + HFByteArray *byteArray = [HFPasteboardOwner unpackByteArrayFromPasteboard:pb]; + if (byteArray) { + [[self controller] insertByteArray:byteArray replacingPreviousBytes:0 allowUndoCoalescing:NO]; + result = YES; + } + else { + NSString *stringType = [pb availableTypeFromArray:@[NSStringPboardType]]; + if (stringType) { + NSString *stringValue = [pb stringForType:stringType]; + if (stringValue) { + NSData *data = [self dataFromPasteboardString:stringValue]; + if (data) { + [[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:NO]; + } + } + } + } + return result; +} + +@end diff --git a/HexFiend/HFTextRepresenter_Internal.h b/HexFiend/HFTextRepresenter_Internal.h new file mode 100644 index 00000000..c1e9f018 --- /dev/null +++ b/HexFiend/HFTextRepresenter_Internal.h @@ -0,0 +1,33 @@ +#import + +@interface HFTextRepresenter (HFInternal) + +- (NSArray *)displayedSelectedContentsRanges; //returns an array of NSValues representing the selected ranges (as NSRanges) clipped to the displayed range. + +- (NSDictionary *)displayedBookmarkLocations; //returns an dictionary mapping bookmark names to bookmark locations. Bookmark locations may be negative. + +- (void)beginSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex; +- (void)continueSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex; +- (void)endSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex; + +// Copy/Paste methods +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb; +- (void)cutSelectedBytesToPasteboard:(NSPasteboard *)pb; +- (BOOL)canPasteFromPasteboard:(NSPasteboard *)pb; +- (BOOL)canCut; +- (BOOL)pasteBytesFromPasteboard:(NSPasteboard *)pb; + +// Must be implemented by subclasses +- (void)insertText:(NSString *)text; + +// Must be implemented by subclasses. Return NSData representing the string value. +- (NSData *)dataFromPasteboardString:(NSString *)string; + +// Value between [0, 1] +- (double)selectionPulseAmount; + +- (void)scrollWheel:(NSEvent *)event; + +- (void)selectAll:(id)sender; + +@end diff --git a/HexFiend/HFTextRepresenter_KeyBinding.m b/HexFiend/HFTextRepresenter_KeyBinding.m new file mode 100644 index 00000000..b6e16d69 --- /dev/null +++ b/HexFiend/HFTextRepresenter_KeyBinding.m @@ -0,0 +1,128 @@ +// +// HFTextRepresenter_KeyBinding.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +#define FORWARD(x) - (void)x : sender { USE(sender); UNIMPLEMENTED_VOID(); } + +@implementation HFTextRepresenter (HFKeyBinding) + +- (void)moveRight:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementByte andModifySelection:NO]; } +- (void)moveLeft:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementByte andModifySelection:NO]; } +- (void)moveUp:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementLine andModifySelection:NO]; } +- (void)moveDown:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementLine andModifySelection:NO]; } +- (void)moveWordRight:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementColumn andModifySelection:NO]; } +- (void)moveWordLeft:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementColumn andModifySelection:NO]; } + +- (void)moveRightAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementByte andModifySelection:YES]; } +- (void)moveLeftAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementByte andModifySelection:YES]; } +- (void)moveUpAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementLine andModifySelection:YES]; } +- (void)moveDownAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementLine andModifySelection:YES]; } +- (void)moveWordRightAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementColumn andModifySelection:YES]; } +- (void)moveWordLeftAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementColumn andModifySelection:YES]; } + +- (void)moveForward:unused { USE(unused); [self moveRight:unused]; } +- (void)moveBackward:unused { USE(unused); [self moveLeft:unused]; } + +- (void)moveWordForward:unused { USE(unused); [self moveWordRight:unused]; } +- (void)moveWordBackward:unused { USE(unused); [self moveWordLeft:unused]; } +- (void)moveForwardAndModifySelection:unused { USE(unused); [self moveRightAndModifySelection:unused]; } +- (void)moveBackwardAndModifySelection:unused { USE(unused); [self moveLeftAndModifySelection:unused]; } +- (void)moveWordForwardAndModifySelection:unused { USE(unused); [self moveForwardAndModifySelection:unused]; } +- (void)moveWordBackwardAndModifySelection:unused { USE(unused); [self moveBackwardAndModifySelection:unused]; } + +- (void)deleteBackward:unused { USE(unused); [[self controller] deleteDirection:HFControllerDirectionLeft]; } +- (void)deleteForward:unused { USE(unused); [[self controller] deleteDirection:HFControllerDirectionRight]; } +- (void)deleteWordForward:unused { USE(unused); [self deleteForward:unused]; } +- (void)deleteWordBackward:unused { USE(unused); [self deleteBackward:unused]; } + +- (void)delete:unused { USE(unused); [self deleteForward:unused]; } + + //todo: implement these + +- (void)deleteToBeginningOfLine:(id)sender { USE(sender); } +- (void)deleteToEndOfLine:(id)sender { USE(sender); } +- (void)deleteToBeginningOfParagraph:(id)sender { USE(sender); } +- (void)deleteToEndOfParagraph:(id)sender { USE(sender); } + +- (void)moveToBeginningOfLine:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionLeft andModifySelection:NO]; } +- (void)moveToEndOfLine:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionRight andModifySelection:NO]; } +- (void)moveToBeginningOfDocument:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementDocument andModifySelection:NO]; } +- (void)moveToEndOfDocument:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementDocument andModifySelection:NO]; } + +- (void)moveToBeginningOfLineAndModifySelection:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionLeft andModifySelection:YES]; } +- (void)moveToEndOfLineAndModifySelection:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionRight andModifySelection:YES]; } +- (void)moveToBeginningOfDocumentAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementDocument andModifySelection:YES]; } +- (void)moveToEndOfDocumentAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementDocument andModifySelection:YES]; } + +- (void)moveToBeginningOfParagraph:unused { USE(unused); [self moveToBeginningOfLine:unused]; } +- (void)moveToEndOfParagraph:unused { USE(unused); [self moveToEndOfLine:unused]; } +- (void)moveToBeginningOfParagraphAndModifySelection:unused { USE(unused); [self moveToBeginningOfLineAndModifySelection:unused]; } +- (void)moveToEndOfParagraphAndModifySelection:unused { USE(unused); [self moveToEndOfLineAndModifySelection:unused]; } + +- (void)scrollPageDown:unused { USE(unused); [[self controller] scrollByLines:[[self controller] displayedLineRange].length]; } +- (void)scrollPageUp:unused { USE(unused); [[self controller] scrollByLines: - [[self controller] displayedLineRange].length]; } +- (void)pageDown:unused { USE(unused); [self scrollPageDown:unused]; } +- (void)pageUp:unused { USE(unused); [self scrollPageUp:unused]; } + +- (void)centerSelectionInVisibleArea:unused { + USE(unused); + HFController *controller = [self controller]; + NSArray *selection = [controller selectedContentsRanges]; + unsigned long long min = ULLONG_MAX, max = 0; + HFASSERT([selection count] >= 1); + FOREACH(HFRangeWrapper *, wrapper, selection) { + HFRange range = [wrapper HFRange]; + min = MIN(min, range.location); + max = MAX(max, HFMaxRange(range)); + } + HFASSERT(max >= min); + [controller maximizeVisibilityOfContentsRange:HFRangeMake(min, max - min)]; +} + +- (void)insertTab:unused { + USE(unused); + [[[self view] window] selectNextKeyView:nil]; +} + +- (void)insertBacktab:unused { + USE(unused); + [[[self view] window] selectPreviousKeyView:nil]; +} + +FORWARD(scrollLineUp) +FORWARD(scrollLineDown) +FORWARD(transpose) +FORWARD(transposeWords) + +FORWARD(selectParagraph) +FORWARD(selectLine) +FORWARD(selectWord) +FORWARD(indent) +//FORWARD(insertNewline) +FORWARD(insertParagraphSeparator) +FORWARD(insertNewlineIgnoringFieldEditor) +FORWARD(insertTabIgnoringFieldEditor) +FORWARD(insertLineBreak) +FORWARD(insertContainerBreak) +FORWARD(changeCaseOfLetter) +FORWARD(uppercaseWord) +FORWARD(lowercaseWord) +FORWARD(capitalizeWord) +FORWARD(deleteBackwardByDecomposingPreviousCharacter) +FORWARD(yank) +FORWARD(complete) +FORWARD(setMark) +FORWARD(deleteToMark) +FORWARD(selectToMark) +FORWARD(swapWithMark) +//FORWARD(cancelOperation) + +@end + diff --git a/HexFiend/HFTextVisualStyleRun.h b/HexFiend/HFTextVisualStyleRun.h new file mode 100644 index 00000000..0fa2ea74 --- /dev/null +++ b/HexFiend/HFTextVisualStyleRun.h @@ -0,0 +1,23 @@ +// +// HFTextVisualStyle.h +// HexFiend_2 +// +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import + +@interface HFTextVisualStyleRun : NSObject {} + +@property (nonatomic, copy) NSColor *foregroundColor; +@property (nonatomic, copy) NSColor *backgroundColor; +@property (nonatomic) NSRange range; +@property (nonatomic) BOOL shouldDraw; +@property (nonatomic) CGFloat scale; +@property (nonatomic, copy) NSIndexSet *bookmarkStarts; +@property (nonatomic, copy) NSIndexSet *bookmarkExtents; +@property (nonatomic, copy) NSIndexSet *bookmarkEnds; + +- (void)set; + +@end diff --git a/HexFiend/HFTextVisualStyleRun.m b/HexFiend/HFTextVisualStyleRun.m new file mode 100644 index 00000000..e9675cd6 --- /dev/null +++ b/HexFiend/HFTextVisualStyleRun.m @@ -0,0 +1,79 @@ +// +// HFTextVisualStyleRun.m +// HexFiend_2 +// +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import "HFTextVisualStyleRun.h" + + +@implementation HFTextVisualStyleRun + +- (instancetype)init { + self = [super init]; + _scale = 1.; + _shouldDraw = YES; + return self; +} + +- (void)dealloc { + [_foregroundColor release]; + [_backgroundColor release]; + [_bookmarkStarts release]; + [_bookmarkExtents release]; + [super dealloc]; +} + +- (void)set { + [_foregroundColor set]; + if (_scale != (CGFloat)1.0) { + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + CGAffineTransform tm = CGContextGetTextMatrix(ctx); + /* Huge hack - adjust downward a little bit if we are scaling */ + tm = CGAffineTransformTranslate(tm, 0, -.25 * (_scale - 1)); + tm = CGAffineTransformScale(tm, _scale, _scale); + CGContextSetTextMatrix(ctx, tm); + } +} + +static inline NSUInteger flip(NSUInteger x) { + return _Generic(x, unsigned: NSSwapInt, unsigned long: NSSwapLong, unsigned long long: NSSwapLongLong)(x); +} +static inline NSUInteger rol(NSUInteger x, unsigned char r) { + r %= sizeof(NSUInteger)*8; + return (x << r) | (x << (sizeof(NSUInteger)*8 - r)); +} +- (NSUInteger)hash { + NSUInteger A = 0; + // All these hashes tend to have only low bits, except the double which has only high bits. +#define Q(x, r) rol(x, sizeof(NSUInteger)*r/6) + A ^= flip([_foregroundColor hash] ^ Q([_backgroundColor hash], 2)); // skew high + A ^= Q(_range.length ^ flip(_range.location), 2); // skew low + A ^= flip([_bookmarkStarts hash]) ^ Q([_bookmarkEnds hash], 3) ^ Q([_bookmarkExtents hash], 4); // skew high + A ^= _shouldDraw ? 0 : (NSUInteger)-1; + A ^= *(NSUInteger*)&_scale; // skew high + return A; +#undef Q +} + +- (BOOL)isEqual:(HFTextVisualStyleRun *)run { + if(![run isKindOfClass:[self class]]) return NO; + /* Check each field for equality. */ + if(!NSEqualRanges(_range, run->_range)) return NO; + if(_scale != run->_scale) return NO; + if(_shouldDraw != run->_shouldDraw) return NO; + if(!!_foregroundColor != !!run->_foregroundColor) return NO; + if(!!_backgroundColor != !!run->_backgroundColor) return NO; + if(!!_bookmarkStarts != !!run->_bookmarkStarts) return NO; + if(!!_bookmarkExtents != !!run->_bookmarkExtents) return NO; + if(!!_bookmarkEnds != !!run->_bookmarkEnds) return NO; + if(![_foregroundColor isEqual: run->_foregroundColor]) return NO; + if(![_backgroundColor isEqual: run->_backgroundColor]) return NO; + if(![_bookmarkStarts isEqual: run->_bookmarkStarts]) return NO; + if(![_bookmarkExtents isEqual: run->_bookmarkExtents]) return NO; + if(![_bookmarkEnds isEqual: run->_bookmarkEnds]) return NO; + return YES; +} + +@end diff --git a/HexFiend/HFTypes.h b/HexFiend/HFTypes.h new file mode 100644 index 00000000..b6ae6818 --- /dev/null +++ b/HexFiend/HFTypes.h @@ -0,0 +1,13 @@ +/*! @brief HFRange is the 64 bit analog of NSRange, containing a 64 bit location and length. */ +typedef struct { + unsigned long long location; + unsigned long long length; +} HFRange; + +/*! @brief HFFPRange is a struct used for representing floating point ranges, similar to NSRange. It contains two long doubles. + + This is useful for (for example) showing the range of visible lines. A double-precision value has 53 significant bits in the mantissa - so we would start to have precision problems at the high end of the range we can represent. Long double has a 64 bit mantissa on Intel, which means that we would start to run into trouble at the very very end of our range - barely acceptable. */ +typedef struct { + long double location; + long double length; +} HFFPRange; diff --git a/HexFiend/HFVerticalScrollerRepresenter.h b/HexFiend/HFVerticalScrollerRepresenter.h new file mode 100644 index 00000000..a14881ea --- /dev/null +++ b/HexFiend/HFVerticalScrollerRepresenter.h @@ -0,0 +1,21 @@ +// +// HFRepresenterVerticalScroller.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFVerticalScrollerRepresenter + @brief An HFRepresenter responsible for showing a vertical scroll bar. + + HFVerticalScrollerRepresenter is an HFRepresenter whose view is a vertical NSScroller, that represents the current position within an HFController "document." It has no methods beyond those of HFRepresenter. + + As HFVerticalScrollerRepresenter is an especially simple representer, it makes for good sample code. +*/ +@interface HFVerticalScrollerRepresenter : HFRepresenter { + +} + +@end diff --git a/HexFiend/HFVerticalScrollerRepresenter.m b/HexFiend/HFVerticalScrollerRepresenter.m new file mode 100644 index 00000000..371b5687 --- /dev/null +++ b/HexFiend/HFVerticalScrollerRepresenter.m @@ -0,0 +1,133 @@ +// +// HFRepresenterVerticalScroller.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +/* Note that on Tiger, NSScroller did not support double in any meaningful way; [scroller doubleValue] always returns 0, and setDoubleValue: doesn't look like it works either. */ + +#import + + +@implementation HFVerticalScrollerRepresenter + +/* No special NSCoding support needed */ + +- (NSView *)createView { + NSScroller *scroller = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy], 64)]; + [scroller setTarget:self]; + [scroller setContinuous:YES]; + [scroller setEnabled:YES]; + [scroller setTarget:self]; + [scroller setAction:@selector(scrollerDidChangeValue:)]; + [scroller setAutoresizingMask:NSViewHeightSizable]; + return scroller; +} + +- (NSUInteger)visibleLines { + HFController *controller = [self controller]; + HFASSERT(controller != NULL); + return ll2l(HFFPToUL(ceill([controller displayedLineRange].length))); +} + +- (void)scrollByKnobToValue:(double)newValue { + HFASSERT(newValue >= 0. && newValue <= 1.); + HFController *controller = [self controller]; + unsigned long long contentsLength = [controller contentsLength]; + NSUInteger bytesPerLine = [controller bytesPerLine]; + HFASSERT(bytesPerLine > 0); + unsigned long long totalLineCountTimesBytesPerLine = HFRoundUpToNextMultipleSaturate(contentsLength - 1, bytesPerLine); + HFASSERT(totalLineCountTimesBytesPerLine == ULLONG_MAX || totalLineCountTimesBytesPerLine % bytesPerLine == 0); + unsigned long long totalLineCount = HFDivideULLRoundingUp(totalLineCountTimesBytesPerLine, bytesPerLine); + HFFPRange currentLineRange = [controller displayedLineRange]; + HFASSERT(currentLineRange.length < HFULToFP(totalLineCount)); + long double maxScroll = totalLineCount - currentLineRange.length; + long double newScroll = maxScroll * (long double)newValue; + [controller setDisplayedLineRange:(HFFPRange){newScroll, currentLineRange.length}]; +} + +- (void)scrollByLines:(long long)linesInt { + if (linesInt == 0) return; + + //note - this properly computes the absolute value even for LLONG_MIN + long double lines = HFULToFP((unsigned long long)llabs(linesInt)); + + HFController *controller = [self controller]; + HFASSERT(controller != NULL); + HFFPRange displayedRange = [[self controller] displayedLineRange]; + if (linesInt < 0) { + displayedRange.location -= MIN(lines, displayedRange.location); + } + else { + long double availableLines = HFULToFP([controller totalLineCount]); + displayedRange.location = MIN(availableLines - displayedRange.length, displayedRange.location + lines); + } + [controller setDisplayedLineRange:displayedRange]; +} + +- (void)scrollerDidChangeValue:(NSScroller *)scroller { + assert(scroller == [self view]); + switch ([scroller hitPart]) { + case NSScrollerDecrementPage: [self scrollByLines: -(long long)[self visibleLines]]; break; + case NSScrollerIncrementPage: [self scrollByLines: (long long)[self visibleLines]]; break; + case NSScrollerDecrementLine: [self scrollByLines: -1LL]; break; + case NSScrollerIncrementLine: [self scrollByLines: 1LL]; break; + case NSScrollerKnob: [self scrollByKnobToValue:[scroller doubleValue]]; break; + default: break; + } +} + +- (void)updateScrollerValue { + HFController *controller = [self controller]; + CGFloat value, proportion; + NSScroller *scroller = [self view]; + BOOL enable = YES; + if (controller == nil) { + value = 0; + proportion = 0; + } + else { + unsigned long long length = [controller contentsLength]; + HFFPRange lineRange = [controller displayedLineRange]; + HFASSERT(lineRange.location >= 0 && lineRange.length >= 0); + if (length == 0) { + value = 0; + proportion = 1; + enable = NO; + } + else { + long double availableLines = HFULToFP([controller totalLineCount]); + long double consumedLines = MAX(1., lineRange.length); + proportion = ld2f(lineRange.length / availableLines); + + long double maxScroll = availableLines - consumedLines; + HFASSERT(maxScroll >= lineRange.location); + if (maxScroll == 0.) { + enable = NO; + value = 0; + } + else { + value = ld2f(lineRange.location / maxScroll); + } + } + } + [scroller setDoubleValue:value]; + [scroller setKnobProportion:proportion]; + [scroller setEnabled:enable]; +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + USE(bytesPerLine); + return [NSScroller scrollerWidthForControlSize:[[self view] controlSize] scrollerStyle:NSScrollerStyleLegacy]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & (HFControllerContentLength | HFControllerDisplayedLineRange)) [self updateScrollerValue]; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(2, 0); +} + +@end diff --git a/HexFiend/HexFiend.h b/HexFiend/HexFiend.h new file mode 100644 index 00000000..60d69a7e --- /dev/null +++ b/HexFiend/HexFiend.h @@ -0,0 +1,78 @@ +/*! @mainpage HexFiend.framework + * + * @section intro Introduction + * HexFiend.framework (hereafter "Hex Fiend" when there is no risk of confusion with the app by the same name) is a framework designed to enable applications to support viewing and editing of binary data. The emphasis is on editing data in a natural way, following Mac OS X text editing conventions. + * + * Hex Fiend is designed to work efficiently with large amounts (64 bits worth) of data. As such, it can work with arbitrarily large files without reading the entire file into memory. This includes insertions, deletions, and in-place editing. Hex Fiend can also efficiently save such changes back to the file, without requiring any additional temporary disk space. + * + * Hex Fiend has a clean separation between the model, view, and controller layers. The model layer allows for efficient manipulation of raw data of mixed sources, making it useful for tools that need to work with large files. + * + * Both the framework and the app are open source under a BSD-style license. In summary, you may use Hex Fiend in any project as long as you include the copyright notice somewhere in the documentation. + * + * @section requirements Requirements + * Hex Fiend is only available on Mac OS X, and supported on Mountain Lion and later. + * + * @section getting_started Getting Started + * + * The Hex Fiend source code is available at http://ridiculousfish.com/hexfiend/ and on GitHub at https://github.com/ridiculousfish/HexFiend + * + * Hex Fiend comes with some sample code ("HexFiendling"), distributed as part of the project. And of course the Hex Fiend application itself is open source, acting as a more sophisticated sample code. +*/ + + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +/* The following is all for Doxygen */ + + +/*! @defgroup model Model + * Hex Fiend's model classes + */ +///@{ +///@class HFByteArray +///@class HFBTreeByteArray +///@class HFFullMemoryByteArray +///@class HFByteSlice +///@class HFFileByteSlice +///@class HFSharedMemoryByteSlice +///@class HFFullMemoryByteSlice + +///@} + + +/*! @defgroup view View + * Hex Fiend's view classes + */ +///@{ +///@class HFRepresenter +///@class HFHexTextRepresenter +///@class HFStringEncodingTextRepresenter +///@class HFLayoutRepresenter +///@class HFLineCountingRepresenter +///@class HFStatusBarRepresenter +///@class HFVerticalScrollerRepresenter +///@class HFLineCountingRepresenter + +///@} + +/*! @defgroup controller Controller + * Hex Fiend's controller classes + */ +///@{ +///@class HFController + +///@} diff --git a/HexFiend/HexFiend_2_Framework_Prefix.pch b/HexFiend/HexFiend_2_Framework_Prefix.pch new file mode 100644 index 00000000..96d0fb76 --- /dev/null +++ b/HexFiend/HexFiend_2_Framework_Prefix.pch @@ -0,0 +1,99 @@ +// +// Prefix header for all source files of the 'HexFiend_2' target in the 'HexFiend_2' project +// + +#ifdef __OBJC__ + #import + #import +#endif + +#define PRIVATE_EXTERN __private_extern__ + +#include + +#if ! NDEBUG +#define HFASSERT(a) assert(a) +#else +#define HFASSERT(a) if (0 && ! (a)) abort() +#endif + + +#define UNIMPLEMENTED_VOID() [NSException raise:NSGenericException \ + format:@"Message %@ sent to instance of class %@, "\ + @"which does not implement that method",\ + NSStringFromSelector(_cmd), [[self class] description]] + +#define UNIMPLEMENTED() UNIMPLEMENTED_VOID(); return 0 + +/* Macro to "use" a variable to prevent unused variable warnings. */ +#define USE(x) ((void)(x)) + +#define check_malloc(x) ({ size_t _count = (x); void *_result = malloc(_count); if(!_result) { fprintf(stderr, "Out of memory allocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; }) +#define check_calloc(x) ({ size_t _count = (x); void *_result = calloc(_count, 1); if(!_result) { fprintf(stderr, "Out of memory allocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; }) +#define check_realloc(p, x) ({ size_t _count = (x); void *_result = realloc((p), x); if(!_result) { fprintf(stderr, "Out of memory reallocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; }) + +#if ! NDEBUG +#define REQUIRE_NOT_NULL(a) do { \ + if ((a)==NULL) {\ + fprintf(stderr, "REQUIRE_NOT_NULL failed: NULL value for parameter " #a " on line %d in file %s\n", __LINE__, __FILE__);\ + abort();\ + }\ +} while (0) + +#define EXPECT_CLASS(e, c) do { \ + if (! [(e) isKindOfClass:[c class]]) {\ + fprintf(stderr, "EXPECT_CLASS failed: Expression " #e " is %s on line %d in file %s\n", (e) ? "(nil)" : [[e description] UTF8String], __LINE__, __FILE__);\ + abort();\ + }\ +} while (0) + +#else +#define REQUIRE_NOT_NULL(a) USE(a) +#define EXPECT_CLASS(e, c) USE(e) +#endif + +#define FOREACH(type, var, exp) for (type var in (exp)) + +#define NEW_ARRAY(type, name, number) \ + type name ## static_ [256];\ + type * name = ((number) <= 256 ? name ## static_ : check_malloc((number) * sizeof(type))) + +#define FREE_ARRAY(name) \ + if (name != name ## static_) free(name) + +#if !defined(MIN) + #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#endif + +#if !defined(MAX) + #define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) +#endif + +//How many bytes should we read at a time when doing a find/replace? +#define SEARCH_CHUNK_SIZE 32768 + +//What's the smallest clipboard data size we should offer to avoid copying when quitting? This is 5 MB +#define MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT (5UL << 20) + +//What's the largest clipboard data size we should support exporting (at all?) This is 500 MB. Note that we can still copy more data than this internally, we just can't put it in, say, TextEdit. +#define MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT (500UL << 20) + +// When we save a file, and other byte arrays need to break their dependencies on the file by copying some of its data into memory, what's the max amount we should copy (per byte array)? We currently don't show any progress for this, so this should be a smaller value +#define MAX_MEMORY_TO_USE_FOR_BREAKING_FILE_DEPENDENCIES_ON_SAVE (16 * 1024 * 1024) + +#ifdef __OBJC__ + #import + #import "HFFunctions_Private.h" +#endif + +#ifndef __has_feature // Optional. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + +#ifndef NS_RETURNS_RETAINED +#if __has_feature(attribute_ns_returns_retained) +#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) +#else +#define NS_RETURNS_RETAINED +#endif +#endif diff --git a/HexFiend/License.txt b/HexFiend/License.txt new file mode 100644 index 00000000..7760edbd --- /dev/null +++ b/HexFiend/License.txt @@ -0,0 +1,21 @@ +Copyright (c) 2005-2009, Peter Ammon +* All rights reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile index 033a62c0..7ad7c87d 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ OBJ := build/obj CC := clang -CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" +CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. SDL_LDFLAGS := -lSDL LDFLAGS += -lc -lm CONF ?= debug @@ -32,7 +32,7 @@ endif ifeq ($(CONF),debug) CFLAGS += -g else ifeq ($(CONF), release) -CFLAGS += -O3 -flto +CFLAGS += -O3 -flto -DNDEBUG LDFLAGS += -flto else $(error Invalid value for CONF: $(CONF). Use "debug" or "release") @@ -46,7 +46,7 @@ CORE_SOURCES := $(shell echo Core/*.c) SDL_SOURCES := $(shell echo SDL/*.c) ifeq ($(shell uname -s),Darwin) -COCOA_SOURCES := $(shell echo Cocoa/*.m) +COCOA_SOURCES := $(shell echo Cocoa/*.m) $(shell echo HexFiend/*.m) SDL_SOURCES += $(shell echo SDL/*.m) endif @@ -75,6 +75,11 @@ $(OBJ)/%.c.o: %.c -@mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ +# HexFiend requires more flags +$(OBJ)/HexFiend/%.m.o: HexFiend/%.m + -@mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch + $(OBJ)/%.m.o: %.m -@mkdir -p $(dir $@) $(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ From f9236d12bf411262b75b7caaf4293a209e09b94e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 13 Aug 2016 22:52:41 +0300 Subject: [PATCH 0129/1216] Improvements to the help command and general debugger usability. --- Cocoa/Document.xib | 26 +++--- Core/debugger.c | 204 +++++++++++++++++++++++++++------------------ 2 files changed, 137 insertions(+), 93 deletions(-) diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 33357b61..e8f62bfa 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -49,30 +49,30 @@ - + - + - + - + - + - + - - + + - - + + NSAllRomanInputSourcesLocaleIdentifier @@ -85,12 +85,12 @@ - + - + @@ -105,7 +105,7 @@ - + diff --git a/Core/debugger.c b/Core/debugger.c index 1f7efce1..3d3fc438 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -588,13 +588,15 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, return VALUE_16(literal); } -typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments); +struct debugger_command_s; +typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, const struct debugger_command_s *command); -typedef struct { +typedef struct debugger_command_s { const char *command; uint8_t min_length; debugger_command_imp_t *implementation; const char *help_string; // Null if should not appear in help + const char *arguments_format; // For usage message } debugger_command_t; static const char *lstrip(const char *str) @@ -611,12 +613,22 @@ GB_log(gb, "Program is running. \n"); \ return false; \ } -static bool cont(GB_gameboy_t *gb, char *arguments) +static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) +{ + if (command->arguments_format) { + GB_log(gb, "Usage: %s %s\n", command->command, command->arguments_format); + } + else { + GB_log(gb, "Usage: %s\n", command->command); + } +} + +static bool cont(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { STOPPED_ONLY if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: continue\n"); + print_usage(gb, command); return true; } @@ -624,12 +636,12 @@ static bool cont(GB_gameboy_t *gb, char *arguments) return false; } -static bool next(GB_gameboy_t *gb, char *arguments) +static bool next(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { STOPPED_ONLY if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: next\n"); + print_usage(gb, command); return true; } @@ -639,24 +651,24 @@ static bool next(GB_gameboy_t *gb, char *arguments) return false; } -static bool step(GB_gameboy_t *gb, char *arguments) +static bool step(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { STOPPED_ONLY if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: step\n"); + print_usage(gb, command); return true; } return false; } -static bool finish(GB_gameboy_t *gb, char *arguments) +static bool finish(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { STOPPED_ONLY if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: finish\n"); + print_usage(gb, command); return true; } @@ -666,12 +678,12 @@ static bool finish(GB_gameboy_t *gb, char *arguments) return false; } -static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments) +static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { STOPPED_ONLY if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: sld\n"); + print_usage(gb, command); return true; } @@ -681,10 +693,10 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments) return false; } -static bool registers(GB_gameboy_t *gb, char *arguments) +static bool registers(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: registers\n"); + print_usage(gb, command); return true; } @@ -723,10 +735,10 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } -static bool breakpoint(GB_gameboy_t *gb, char *arguments) +static bool breakpoint(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { - GB_log(gb, "Usage: breakpoint [ if ]\n"); + print_usage(gb, command); return true; } @@ -788,21 +800,17 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) return true; } -static bool delete(GB_gameboy_t *gb, char *arguments) +static bool delete(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { - GB_log(gb, "Delete all breakpoints? "); - char *answer = gb->input_callback(gb); - if (answer[0] == 'Y' || answer[0] == 'y') { - for (unsigned i = gb->n_breakpoints; i--;) { - if (gb->breakpoints[i].condition) { - free(gb->breakpoints[i].condition); - } + for (unsigned i = gb->n_breakpoints; i--;) { + if (gb->breakpoints[i].condition) { + free(gb->breakpoints[i].condition); } - free(gb->breakpoints); - gb->breakpoints = NULL; - gb->n_breakpoints = 0; } + free(gb->breakpoints); + gb->breakpoints = NULL; + gb->n_breakpoints = 0; return true; } @@ -852,11 +860,11 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } -static bool watch(GB_gameboy_t *gb, char *arguments) +static bool watch(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { print_usage: - GB_log(gb, "Usage: watch (r|w|rw) [ if ]\n"); + print_usage(gb, command); return true; } @@ -944,21 +952,17 @@ print_usage: return true; } -static bool unwatch(GB_gameboy_t *gb, char *arguments) +static bool unwatch(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { - GB_log(gb, "Delete all watchpoints? "); - char *answer = gb->input_callback(gb); - if (answer[0] == 'Y' || answer[0] == 'y') { - for (unsigned i = gb->n_watchpoints; i--;) { - if (gb->watchpoints[i].condition) { - free(gb->watchpoints[i].condition); - } + for (unsigned i = gb->n_watchpoints; i--;) { + if (gb->watchpoints[i].condition) { + free(gb->watchpoints[i].condition); } - free(gb->watchpoints); - gb->watchpoints = NULL; - gb->n_watchpoints = 0; } + free(gb->watchpoints); + gb->watchpoints = NULL; + gb->n_watchpoints = 0; return true; } @@ -986,10 +990,10 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments) return true; } -static bool list(GB_gameboy_t *gb, char *arguments) +static bool list(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: list\n"); + print_usage(gb, command); return true; } @@ -1069,10 +1073,10 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr) return _should_break(gb, full_addr); } -static bool print(GB_gameboy_t *gb, char *arguments) +static bool print(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { - GB_log(gb, "Usage: print \n"); + print_usage(gb, command); return true; } @@ -1084,10 +1088,10 @@ static bool print(GB_gameboy_t *gb, char *arguments) return true; } -static bool examine(GB_gameboy_t *gb, char *arguments) +static bool examine(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { - GB_log(gb, "Usage: examine \n"); + print_usage(gb, command); return true; } @@ -1118,17 +1122,17 @@ static bool examine(GB_gameboy_t *gb, char *arguments) return true; } -static bool mbc(GB_gameboy_t *gb, char *arguments) +static bool mbc(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: mbc\n"); + print_usage(gb, command); return true; } const GB_cartridge_t *cartridge = gb->cartridge_type; if (cartridge->has_ram) { - GB_log(gb, "Cartrdige includes%s RAM: %x\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); + GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); } else { GB_log(gb, "No cartridge RAM\n"); @@ -1166,10 +1170,10 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) return true; } -static bool backtrace(GB_gameboy_t *gb, char *arguments) +static bool backtrace(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) { if (strlen(lstrip(arguments))) { - GB_log(gb, "Usage: backtrace\n"); + print_usage(gb, command); return true; } @@ -1181,51 +1185,50 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments) return true; } -static bool help(GB_gameboy_t *gb, char *arguments); +static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command); + +#define HELP_NEWLINE "\n " + +/* Commands without implementations are aliases of the previous non-alias commands */ static const debugger_command_t commands[] = { {"continue", 1, cont, "Continue running until next stop"}, {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, {"backtrace", 2, backtrace, "Display the current call stack"}, - {"bt", 2, backtrace, NULL}, - {"sld", 3, stack_leak_detection, "Run until the current function returns, or a stack leak is detected (Experimental)"}, + {"bt", 2, }, /* Alias */ + {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected. (Experimental)"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, - {"mbc", 3, mbc, NULL}, - {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression. Can also modify the condition of existing breakpoints."}, - {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, - {"watch", 1, watch, "Add a new watchpoint at the specified address/expression. Can also modify the condition and type of existing watchpoints."}, - {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints"}, + {"mbc", 3, }, /* Alias */ + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression." HELP_NEWLINE + "Can also modify the condition of existing breakpoints.", + "[ if ]"}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, + {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE + "Can also modify the condition and type of existing watchpoints.", + " (r|w|rw) [ if ]"}, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"}, {"list", 1, list, "List all set breakpoints and watchpoints"}, - {"print", 1, print, "Evaluate and print an expression"}, - {"eval", 2, print, NULL}, - {"examine", 2, examine, "Examine values at address"}, - {"x", 1, examine, NULL}, + {"print", 1, print, "Evaluate and print an expression", ""}, + {"eval", 2, }, /* Alias */ + {"examine", 2, examine, "Examine values at address", ""}, + {"x", 1, }, /* Alias */ - {"help", 1, help, "List available commands"}, + {"help", 1, help, "List available commands or show help for the specified command", "[]"}, + {NULL,}, /* Null terminator */ }; -static bool help(GB_gameboy_t *gb, char *arguments) -{ - /* Todo: command specific help */ - const debugger_command_t *command = commands; - for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) { - if (command->help_string) { - GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command); - GB_log(gb, ": %s\n", command->help_string); - } - } - return true; -} - static const debugger_command_t *find_command(const char *string) { - const debugger_command_t *command = commands; size_t length = strlen(string); - for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) { + for (const debugger_command_t *command = commands; command->command; command++) { if (command->min_length > length) continue; if (memcmp(command->command, string, length) == 0) { /* Is a substring? */ + /* Aliases */ + while (!command->implementation) { + command--; + } return command; } } @@ -1233,6 +1236,47 @@ static const debugger_command_t *find_command(const char *string) return NULL; } +static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command); + GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length); +} + +static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command) +{ + print_command_shortcut(gb, command); + GB_log(gb, ": "); + GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); +} + +static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *ignored) +{ + const debugger_command_t *command = find_command(arguments); + if (command) { + print_command_description(gb, command); + GB_log(gb, "\n"); + print_usage(gb, command); + + command++; + if (command->command && !command->implementation) { /* Command has aliases*/ + GB_log(gb, "\nAliases: "); + do { + print_command_shortcut(gb, command); + GB_log(gb, " "); + command++; + } while (command->command && !command->implementation); + GB_log(gb, "\n"); + } + return true; + } + for (const debugger_command_t *command = commands; command->command; command++) { + if (command->help_string) { + print_command_description(gb, command); + } + } + return true; +} + void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) { /* Called just after the CPU calls a function/enters an interrupt/etc... */ @@ -1409,7 +1453,7 @@ bool GB_debugger_do_command(GB_gameboy_t *gb, char *input) const debugger_command_t *command = find_command(command_string); if (command) { - return command->implementation(gb, arguments); + return command->implementation(gb, arguments, command); } else { GB_log(gb, "%s: no such command.\n", command_string); From 0734e990b3cb6b117d6bac64b0af006c29a6abec Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Aug 2016 14:54:54 +0300 Subject: [PATCH 0130/1216] Memory viewer now has a Goto command, different memory space modes, and allows viewing/editing specific banks --- Cocoa/Document.h | 3 + Cocoa/Document.m | 107 ++++++++++++++++++++++++++++- Cocoa/Document.xib | 82 ++++++++++++++++++++-- Cocoa/GBMemoryByteArray.h | 10 +++ Cocoa/GBMemoryByteArray.m | 126 ++++++++++++++++++++++++++++++++-- Core/debugger.c | 28 ++++++-- Core/debugger.h | 1 + HexFiend/HFController.h | 2 +- HexFiend/HFController.m | 2 +- HexFiend/HFLineCountingView.m | 2 +- 10 files changed, 340 insertions(+), 23 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 9e1e73dc..32cfb6b7 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -9,6 +9,9 @@ @property (strong) IBOutlet NSWindow *mainWindow; @property (strong) IBOutlet NSView *memoryView; @property (strong) IBOutlet NSPanel *memoryWindow; +@property (readonly) GB_gameboy_t *gameboy; +@property (strong) IBOutlet NSTextField *memoryBankInput; +@property (strong) IBOutlet NSToolbarItem *memoryBankItem; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 2a7a89d7..4dad62dd 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -8,6 +8,8 @@ #include "HexFiend/HexFiend.h" #include "GBMemoryByteArray.h" +/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ + @interface Document () { /* NSTextViews freeze the entire app if they're modified too often and too quickly. @@ -20,6 +22,7 @@ HFController *hex_controller; NSString *lastConsoleInput; + HFLineCountingRepresenter *lineRep; } @property GBAudioClient *audioClient; @@ -175,6 +178,12 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) } [self readFromFile:self.fileName ofType:@"gb"]; [self start]; + + if (hex_controller) { + /* Verify bank sanity, especially when switching models. */ + [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:0]; + [self hexUpdateBank:self.memoryBankInput]; + } } - (IBAction)togglePause:(id)sender @@ -221,7 +230,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) HFHexTextRepresenter *hexRep = [[HFHexTextRepresenter alloc] init]; HFStringEncodingTextRepresenter *asciiRep = [[HFStringEncodingTextRepresenter alloc] init]; HFVerticalScrollerRepresenter *scrollRep = [[HFVerticalScrollerRepresenter alloc] init]; - HFLineCountingRepresenter *lineRep = [[HFLineCountingRepresenter alloc] init]; + lineRep = [[HFLineCountingRepresenter alloc] init]; HFStatusBarRepresenter *statusRep = [[HFStatusBarRepresenter alloc] init]; lineRep.lineNumberFormat = HFLineNumberFormatHexadecimal; @@ -250,6 +259,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) [layoutView setFrame:layoutViewFrame]; [layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin]; [self.memoryView addSubview:layoutView]; + + self.memoryBankItem.enabled = false; } + (BOOL)autosavesInPlace { @@ -521,7 +532,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void) performAtomicBlock: (void (^)())block { while (!is_inited); - bool was_running = running; + bool was_running = running && !gb.debug_stopped; if (was_running) { [self stop]; } @@ -546,4 +557,96 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) [self.memoryWindow makeKeyAndOrderFront:sender]; } +- (IBAction)hexGoTo:(id)sender +{ + [self performAtomicBlock:^{ + uint16_t addr; + if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, NULL)) { + NSBeep(); + return; + } + addr -= lineRep.valueOffset; + if (addr >= hex_controller.byteArray.length) { + NSBeep(); + return; + } + [hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]]; + [hex_controller _ensureVisibilityOfLocation:addr]; + [self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]]; + }]; +} + +- (IBAction)hexUpdateBank:(NSControl *)sender +{ + [self performAtomicBlock:^{ + uint16_t addr, bank; + if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, &bank)) { + NSBeep(); + return; + } + + if (bank == (uint16_t) -1) { + bank = addr; + } + + uint16_t n_banks = 1; + switch ([(GBMemoryByteArray *)(hex_controller.byteArray) mode]) { + case GBMemoryROM: + n_banks = gb.rom_size / 0x4000; + break; + case GBMemoryVRAM: + n_banks = gb.is_cgb ? 2 : 1; + break; + case GBMemoryExternalRAM: + n_banks = gb.mbc_ram_size / 0x2000; + break; + case GBMemoryRAM: + n_banks = gb.is_cgb ? 8 : 1; + break; + case GBMemoryEntireSpace: + break; + } + + bank %= n_banks; + + [sender setStringValue:[NSString stringWithFormat:@"$%x", bank]]; + [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank]; + [hex_controller reloadData]; + }]; +} + +- (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender +{ + self.memoryBankItem.enabled = [sender indexOfSelectedItem] != GBMemoryEntireSpace; + GBMemoryByteArray *byteArray = (GBMemoryByteArray *)(hex_controller.byteArray); + [byteArray setMode:(GB_memory_mode_t)[sender indexOfSelectedItem]]; + switch ((GB_memory_mode_t)[sender indexOfSelectedItem]) { + case GBMemoryEntireSpace: + case GBMemoryROM: + lineRep.valueOffset = 0; + byteArray.selectedBank = gb.mbc_rom_bank; + break; + case GBMemoryVRAM: + lineRep.valueOffset = 0x8000; + byteArray.selectedBank = gb.cgb_vram_bank; + break; + case GBMemoryExternalRAM: + lineRep.valueOffset = 0xA000; + byteArray.selectedBank = gb.mbc_ram_bank; + break; + case GBMemoryRAM: + lineRep.valueOffset = 0xC000; + byteArray.selectedBank = gb.cgb_ram_bank; + break; + } + [self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; + [hex_controller reloadData]; + [self.memoryView setNeedsDisplay:YES]; +} + +- (GB_gameboy_t *) gameboy +{ + return &gb; +} + @end \ No newline at end of file diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index e8f62bfa..75827e97 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -10,6 +10,8 @@ + + @@ -69,10 +71,10 @@ - + - + NSAllRomanInputSourcesLocaleIdentifier @@ -84,7 +86,7 @@ - + @@ -125,7 +127,79 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/GBMemoryByteArray.h b/Cocoa/GBMemoryByteArray.h index 95991751..e3ed71f8 100644 --- a/Cocoa/GBMemoryByteArray.h +++ b/Cocoa/GBMemoryByteArray.h @@ -2,6 +2,16 @@ #import "HexFiend/HexFiend.h" #import "HexFiend/HFByteArray.h" +typedef enum { + GBMemoryEntireSpace, + GBMemoryROM, + GBMemoryVRAM, + GBMemoryExternalRAM, + GBMemoryRAM +} GB_memory_mode_t; + @interface GBMemoryByteArray : HFByteArray - (instancetype) initWithDocument:(Document *)document; +@property uint16_t selectedBank; +@property GB_memory_mode_t mode; @end diff --git a/Cocoa/GBMemoryByteArray.m b/Cocoa/GBMemoryByteArray.m index 8a8c1e8f..a81fb118 100644 --- a/Cocoa/GBMemoryByteArray.m +++ b/Cocoa/GBMemoryByteArray.m @@ -17,16 +17,83 @@ - (unsigned long long)length { - return 0x10000; + switch (_mode) { + case GBMemoryEntireSpace: + return 0x10000; + case GBMemoryROM: + return 0x8000; + case GBMemoryVRAM: + return 0x2000; + case GBMemoryExternalRAM: + return 0x2000; + case GBMemoryRAM: + return 0x2000; + } } - (void)copyBytes:(unsigned char *)dst range:(HFRange)range { - uint16_t addr = (uint16_t) range.location; - unsigned long long length = range.length; - while (length) { - *(dst++) = [_document readMemory:addr++]; - length--; + __block uint16_t addr = (uint16_t) range.location; + __block unsigned long long length = range.length; + if (_mode == GBMemoryEntireSpace) { + while (length) { + *(dst++) = [_document readMemory:addr++]; + length--; + } + } + else { + [_document performAtomicBlock:^{ + unsigned char *_dst = dst; + uint16_t bank_backup = 0; + GB_gameboy_t *gb = _document.gameboy; + switch (_mode) { + case GBMemoryROM: + bank_backup = gb->mbc_rom_bank; + gb->mbc_rom_bank = self.selectedBank; + break; + case GBMemoryVRAM: + bank_backup = gb->cgb_vram_bank; + if (gb->is_cgb) { + gb->cgb_vram_bank = self.selectedBank; + } + addr += 0x8000; + break; + case GBMemoryExternalRAM: + bank_backup = gb->mbc_ram_bank; + gb->mbc_ram_bank = self.selectedBank; + addr += 0xA000; + break; + case GBMemoryRAM: + bank_backup = gb->cgb_ram_bank; + if (gb->is_cgb) { + gb->cgb_ram_bank = self.selectedBank; + } + addr += 0xC000; + break; + default: + assert(false); + } + while (length) { + *(_dst++) = [_document readMemory:addr++]; + length--; + } + switch (_mode) { + case GBMemoryROM: + gb->mbc_rom_bank = bank_backup; + break; + case GBMemoryVRAM: + gb->cgb_vram_bank = bank_backup; + break; + case GBMemoryExternalRAM: + gb->mbc_ram_bank = bank_backup; + break; + case GBMemoryRAM: + gb->cgb_ram_bank = bank_backup; + break; + default: + assert(false); + } + }]; } } @@ -49,15 +116,60 @@ { if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */ [_document performAtomicBlock:^{ + uint16_t addr = (uint16_t) lrange.location; + uint16_t bank_backup = 0; + GB_gameboy_t *gb = _document.gameboy; + switch (_mode) { + case GBMemoryROM: + bank_backup = gb->mbc_rom_bank; + gb->mbc_rom_bank = self.selectedBank; + break; + case GBMemoryVRAM: + bank_backup = gb->cgb_vram_bank; + if (gb->is_cgb) { + gb->cgb_vram_bank = self.selectedBank; + } + addr += 0x8000; + break; + case GBMemoryExternalRAM: + bank_backup = gb->mbc_ram_bank; + gb->mbc_ram_bank = self.selectedBank; + addr += 0xA000; + break; + case GBMemoryRAM: + bank_backup = gb->cgb_ram_bank; + if (gb->is_cgb) { + gb->cgb_ram_bank = self.selectedBank; + } + addr += 0xC000; + break; + default: + break; + } uint8_t values[lrange.length]; [slice copyBytes:values range:HFRangeMake(0, lrange.length)]; - uint16_t addr = (uint16_t) lrange.location; uint8_t *src = values; unsigned long long length = lrange.length; while (length) { [_document writeMemory:addr++ value:*(src++)]; length--; } + switch (_mode) { + case GBMemoryROM: + gb->mbc_rom_bank = bank_backup; + break; + case GBMemoryVRAM: + gb->cgb_vram_bank = bank_backup; + break; + case GBMemoryExternalRAM: + gb->mbc_ram_bank = bank_backup; + break; + case GBMemoryRAM: + gb->cgb_ram_bank = bank_backup; + break; + default: + break; + } }]; } diff --git a/Core/debugger.c b/Core/debugger.c index 3d3fc438..51e015d4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -333,11 +333,11 @@ static struct { }; value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, + size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, + size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; @@ -410,19 +410,19 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; } } - GB_log(gb, "Unknown register: %.*s\n", length, string); + GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string); *error = true; return (lvalue_t){0,}; } - GB_log(gb, "Expression is not an lvalue: %.*s\n", length, string); + GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string); *error = true; return (lvalue_t){0,}; } #define ERROR ((value_t){0,}) value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, + size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; @@ -567,7 +567,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, return (value_t){true, symbol->bank, symbol->addr}; } - GB_log(gb, "Unknown register or symbol: %.*s\n", length, string); + GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); *error = true; return ERROR; } @@ -581,7 +581,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } uint16_t literal = (uint16_t) (strtol(string, &end, base)); if (end != string + length) { - GB_log(gb, "Failed to parse: %.*s\n", length, string); + GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); *error = true; return ERROR; } @@ -1558,4 +1558,18 @@ const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); if (symbol && symbol->addr == addr) return symbol->name; return NULL; +} + +/* The public version of debugger_evaluate */ +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank) +{ + bool error = false; + value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); + if (result) { + *result = value.value; + } + if (result_bank) { + *result_bank = value.has_bank? value.value : -1; + } + return error; } \ No newline at end of file diff --git a/Core/debugger.h b/Core/debugger.h index 4e7808f8..882f8439 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -11,4 +11,5 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank); /* result_bank is -1 if unused. */ #endif /* debugger_h */ diff --git a/HexFiend/HFController.h b/HexFiend/HFController.h index 7bdc0700..4a59a4d6 100644 --- a/HexFiend/HFController.h +++ b/HexFiend/HFController.h @@ -367,7 +367,7 @@ You create an HFController via [[HFController alloc] init]. After that - (unsigned long long)contentsLength; - (void) reloadData; - +- (void)_ensureVisibilityOfLocation:(unsigned long long)location; @end /*! A notification posted whenever any of the HFController's properties change. The object is the HFController. The userInfo contains one key, HFControllerChangedPropertiesKey, which contains an NSNumber with the changed properties as a HFControllerPropertyBits bitmask. This is useful for external objects to be notified of changes. HFRepresenters added to the HFController are notified via the controllerDidChange: message. diff --git a/HexFiend/HFController.m b/HexFiend/HFController.m index aaae78ee..73a31b28 100644 --- a/HexFiend/HFController.m +++ b/HexFiend/HFController.m @@ -1796,7 +1796,7 @@ static BOOL rangesAreInAscendingOrder(NSEnumerator *rangeEnumerator) { [cachedData release]; cachedData = nil; [self _updateDisplayedRange]; - [self _addPropertyChangeBits: HFControllerContentValue]; + [self _addPropertyChangeBits: HFControllerContentValue | HFControllerContentLength]; END_TRANSACTION(); } diff --git a/HexFiend/HFLineCountingView.m b/HexFiend/HFLineCountingView.m index 6c9578d9..d111bcd8 100644 --- a/HexFiend/HFLineCountingView.m +++ b/HexFiend/HFLineCountingView.m @@ -368,7 +368,7 @@ static inline int common_prefix_length(const char *a, const char *b) { [self getLineNumberFormatString:formatString length:sizeof formatString]; while (lineCount--) { - int charCount = sprintf(buffer + bufferIndex, formatString, lineValue); + int charCount = sprintf(buffer + bufferIndex, formatString, lineValue + self.representer.valueOffset); HFASSERT(charCount > 0); bufferIndex += charCount; buffer[bufferIndex++] = '\n'; From e7626535a88e710c9389e3bcf35804e173442a83 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Aug 2016 17:51:17 +0300 Subject: [PATCH 0131/1216] Initial Windows support --- Core/apu.c | 2 + Core/display.c | 48 ++++++++++++---- Core/gb.c | 14 ++--- Core/symbol_hash.c | 3 + Makefile | 128 ++++++++++++++++++++++++++++++++----------- SDL/SDLMain.m | 16 +++--- SDL/main.c | 28 ++++++++-- Windows/stdio.h | 69 +++++++++++++++++++++++ Windows/sys/select.h | 0 Windows/sys/time.h | 0 Windows/unistd.h | 0 11 files changed, 242 insertions(+), 66 deletions(-) mode change 100644 => 100755 Core/apu.c mode change 100644 => 100755 Core/display.c mode change 100644 => 100755 Core/gb.c mode change 100644 => 100755 Core/symbol_hash.c mode change 100644 => 100755 Makefile mode change 100644 => 100755 SDL/main.c create mode 100755 Windows/stdio.h create mode 100755 Windows/sys/select.h create mode 100755 Windows/sys/time.h create mode 100755 Windows/unistd.h diff --git a/Core/apu.c b/Core/apu.c old mode 100644 new mode 100755 index 2db8c36e..c4085eed --- a/Core/apu.c +++ b/Core/apu.c @@ -4,11 +4,13 @@ #include "apu.h" #include "gb.h" +#undef max #define max(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) +#undef min #define min(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ diff --git a/Core/display.c b/Core/display.c old mode 100644 new mode 100755 index 06ff945f..5355ca33 --- a/Core/display.c +++ b/Core/display.c @@ -6,6 +6,10 @@ #include #include "gb.h" #include "display.h" +#ifdef _WIN32 +#define _WIN32_WINNT 0x0500 +#include +#endif #pragma pack(push, 1) typedef struct { @@ -161,17 +165,42 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return gb->background_palletes_rgb[(attributes & 7) * 4 + background_pixel]; } +static int64_t get_nanoseconds(void) +{ +#ifndef _WIN32 + struct timeval now; + gettimeofday(&now, NULL); + return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; +#else + FILETIME time; + GetSystemTimeAsFileTime(&time); + return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L; +#endif +} + +static void nsleep(uint64_t nanoseconds) +{ +#ifndef _WIN32 + struct timespec sleep = {0, nanoseconds}; + nanosleep(&sleep, NULL); +#else + HANDLE timer; + LARGE_INTEGER time; + timer = CreateWaitableTimer(NULL, true, NULL); + time.QuadPart = -(nanoseconds / 100L); + SetWaitableTimer(timer, &time, 0, NULL, NULL, false); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#endif +} + // 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); - int64_t nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; + int64_t nanoseconds = get_nanoseconds(); if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) { return; } @@ -205,14 +234,9 @@ void display_vblank(GB_gameboy_t *gb) 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; + int64_t nanoseconds = get_nanoseconds(); if (labs((signed long)(nanoseconds - gb->last_vblank)) < FRAME_LENGTH ) { - sleep.tv_nsec = (FRAME_LENGTH + gb->last_vblank - nanoseconds); - nanosleep(&sleep, NULL); - + nsleep(FRAME_LENGTH + gb->last_vblank - nanoseconds); gb->last_vblank += FRAME_LENGTH; } else { diff --git a/Core/gb.c b/Core/gb.c old mode 100644 new mode 100755 index 1ec611b7..8e835328 --- a/Core/gb.c +++ b/Core/gb.c @@ -73,6 +73,7 @@ static char *default_input_callback(GB_gameboy_t *gb) static char *default_async_input_callback(GB_gameboy_t *gb) { +#ifndef _WIN32 fd_set set; FD_ZERO(&set); FD_SET(STDIN_FILENO, &set); @@ -84,6 +85,7 @@ static char *default_async_input_callback(GB_gameboy_t *gb) } return default_input_callback(gb); } +#endif return NULL; } @@ -95,10 +97,6 @@ void GB_init(GB_gameboy_t *gb) 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(); @@ -127,10 +125,6 @@ void GB_init_cgb(GB_gameboy_t *gb) 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(); @@ -180,7 +174,7 @@ void GB_free(GB_gameboy_t *gb) int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) { - FILE *f = fopen(path, "r"); + FILE *f = fopen(path, "rb"); if (!f) return errno; fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); fclose(f); @@ -189,7 +183,7 @@ int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) int GB_load_rom(GB_gameboy_t *gb, const char *path) { - FILE *f = fopen(path, "r"); + FILE *f = fopen(path, "rb"); if (!f) return errno; fseek(f, 0, SEEK_END); gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */ diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c old mode 100644 new mode 100755 index 5af04f09..37664e10 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -1,4 +1,7 @@ #include "gb.h" +#ifdef _WIN32 +typedef intptr_t ssize_t; +#endif static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) { diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index 7ad7c87d..6f774215 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ -ifeq ($(shell uname -s),Darwin) +# Set target, configuration, version and destination folders + +PLATFORM := $(shell uname -s) + +ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa else DEFAULT := sdl @@ -11,43 +15,79 @@ MAKECMDGOALS := $(DEFAULT) endif VERSION := 0.6 +CONF ?= debug BIN := build/bin OBJ := build/obj +# Set tools + CC := clang +ifeq ($(PLATFORM),windows32) +# To force use of the Unix version instead of the Windows version +MKDIR := $(shell which mkdir) +else +MKDIR := mkdir +endif -CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. +# Set compilation and linkage flags based on target, platform and configuration + +CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES SDL_LDFLAGS := -lSDL +ifeq ($(PLATFORM),windows32) +CFLAGS += -IWindows +LDFLAGS += -lmsvcrt -lSDLmain +else LDFLAGS += -lc -lm -CONF ?= debug +endif -ifeq ($(shell uname -s),Darwin) +ifeq ($(PLATFORM),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 -framework Carbon SDL_LDFLAGS := -framework SDL endif +ifeq ($(PLATFORM),windows32) +CFLAGS += -Wno-deprecated-declarations # Seems like Microsoft deprecated every single LIBC function +LDFLAGS += -Wl,/NODEFAULTLIB:libcmt +endif + ifeq ($(CONF),debug) CFLAGS += -g +ifeq ($(PLATFORM),windows32) +LDFLAGS += -Wl,/debug +endif else ifeq ($(CONF), release) -CFLAGS += -O3 -flto -DNDEBUG +CFLAGS += -O3 -DNDEBUG +ifneq ($(PLATFORM),windows32) LDFLAGS += -flto +CFLAGS += -flto +endif else $(error Invalid value for CONF: $(CONF). Use "debug" or "release") endif +# Define our targets + +ifeq ($(PLATFORM),windows32) +SDL_TARGET := $(BIN)/sdl/sameboy.exe $(BIN)/sdl/sameboy_debugger.exe $(BIN)/sdl/SDL.dll +else +SDL_TARGET := $(BIN)/sdl/sameboy +endif + cocoa: $(BIN)/Sameboy.app -sdl: $(BIN)/sdl/sameboy $(BIN)/sdl/dmg_boot.bin $(BIN)/sdl/cgb_boot.bin $(BIN)/sdl/LICENSE +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 -CORE_SOURCES := $(shell echo Core/*.c) -SDL_SOURCES := $(shell echo SDL/*.c) +# Get a list of our source files and their respective object file targets -ifeq ($(shell uname -s),Darwin) -COCOA_SOURCES := $(shell echo Cocoa/*.m) $(shell echo HexFiend/*.m) -SDL_SOURCES += $(shell echo SDL/*.m) +CORE_SOURCES := $(shell ls Core/*.c) +SDL_SOURCES := $(shell ls SDL/*.c) + +ifeq ($(PLATFORM),Darwin) +COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) +SDL_SOURCES += $(shell ls SDL/*.m) endif CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES)) @@ -57,6 +97,7 @@ SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES)) ALL_OBJECTS := $(CORE_OBJECTS) $(COCOA_OBJECTS) $(SDL_OBJECTS) # Automatic dependency generation + ifneq ($(MAKECMDGOALS),clean) -include $(CORE_OBJECTS:.o=.dep) ifneq ($(filter $(MAKECMDGOALS),sdl),) @@ -68,28 +109,30 @@ endif endif $(OBJ)/%.dep: % - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ +# Compilation rules + $(OBJ)/%.c.o: %.c - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ # HexFiend requires more flags $(OBJ)/HexFiend/%.m.o: HexFiend/%.m - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch $(OBJ)/%.m.o: %.m - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ # Cocoa Port -Shaders:$(shell echo Shaders/*.fsh) +Shaders:$(shell ls Shaders/*.fsh) $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ - $(shell echo Cocoa/*.icns) \ + $(shell ls Cocoa/*.icns) \ Cocoa/License.html \ Cocoa/info.plist \ $(BIN)/BootROMs/dmg_boot.bin \ @@ -98,15 +141,15 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(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 + $(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/ 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 + $(MKDIR) -p $(BIN)/Sameboy.app/Contents/Resources/Shaders cp Shaders/*.fsh $(BIN)/Sameboy.app/Contents/Resources/Shaders $(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit ifeq ($(CONF), release) strip $@ @@ -115,27 +158,50 @@ endif $(BIN)/Sameboy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib ibtool --compile $@ $^ +# SDL Port + +# Unix versions build only one binary $(BIN)/sdl/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) ifeq ($(CONF), release) strip $@ endif - -$(BIN)/BootROMs/%.bin: BootROMs/%.asm - -@mkdir -p $(dir $@) - cd BootROMs && rgbasm -o ../$@.tmp ../$< - rgblink -o $@.tmp2 $@.tmp - head -c $(if $(findstring dmg,$@), 256, 2304) $@.tmp2 > $@ - @rm $@.tmp $@.tmp2 + +# Windows version builds two, one with a conole and one without it +$(BIN)/sdl/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:windows + +$(BIN)/sdl/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:console + +# We must provide SDL.dll with the Windows port. This is an AWFUL HACK to find it. +SPACE := +SPACE += +$(BIN)/sdl/SDL.dll: + @$(eval POTENTIAL_MATCHES := $(subst @@@," ",$(patsubst %,%/SDL.dll,$(subst ;,$(SPACE),$(subst $(SPACE),@@@,$(lib)))))) + @$(eval MATCH := $(shell ls $(POTENTIAL_MATCHES) 2> NUL | head -n 1)) + cp "$(MATCH)" $@ $(BIN)/sdl/%.bin: $(BIN)/BootROMs/%.bin - -@mkdir -p $(dir $@) + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ $(BIN)/sdl/LICENSE: LICENSE cp -f $^ $@ +# Boot ROMs + +$(BIN)/BootROMs/%.bin: BootROMs/%.asm + -@$(MKDIR) -p $(dir $@) + cd BootROMs && rgbasm -o ../$@.tmp ../$< + rgblink -o $@.tmp2 $@.tmp + head -c $(if $(findstring dmg,$@), 256, 2304) $@.tmp2 > $@ + @rm $@.tmp $@.tmp2 + +# Clean + clean: - rm -rf build - \ No newline at end of file + rm -rf build \ No newline at end of file diff --git a/SDL/SDLMain.m b/SDL/SDLMain.m index 535e4c7e..d8aadf51 100644 --- a/SDL/SDLMain.m +++ b/SDL/SDLMain.m @@ -19,21 +19,21 @@ @end /* Use this flag to determine whether we use SDLMain.nib or not */ -#define SDL_USE_NIB_FILE 0 +#define SDL_USE_NIB_FILE 0 /* Use this flag to determine whether we use CPS (docking) or not */ -#define SDL_USE_CPS 1 +#define SDL_USE_CPS 1 #ifdef SDL_USE_CPS /* Portions of CPS.h */ typedef struct CPSProcessSerNum { - UInt32 lo; - UInt32 hi; + 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); +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 */ @@ -200,7 +200,7 @@ static void setupWindowMenu(void) /* Replacement for NSApplicationMain */ static void CustomApplicationMain (int argc, char **argv) { - SDLMain *sdlMain; + SDLMain *sdlMain; /* Ensure the application object is initialised */ [NSApplication sharedApplication]; diff --git a/SDL/main.c b/SDL/main.c old mode 100644 new mode 100755 index da00977c..1d159720 --- a/SDL/main.c +++ b/SDL/main.c @@ -5,6 +5,15 @@ #include #include #include +#ifndef _WIN32 +#define AUDIO_FREQUENCY 96000 +#else +/* Windows (well, at least my VM) can't handle 96KHz sound well :( */ +#define AUDIO_FREQUENCY 44100 +#include +#include +#define snprintf _snprintf +#endif #include "gb.h" #include "debugger.h" @@ -14,7 +23,7 @@ static char *filename; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); GB_gameboy_t gb; -void GB_update_keys_status(GB_gameboy_t *gb) +static void GB_update_keys_status(GB_gameboy_t *gb) { static bool ctrl = false; static bool shift = false; @@ -111,7 +120,7 @@ void GB_update_keys_status(GB_gameboy_t *gb) } } -void vblank(GB_gameboy_t *gb) +static void vblank(GB_gameboy_t *gb) { SDL_Surface *screen = gb->user_data; SDL_Flip(screen); @@ -139,16 +148,24 @@ static const char *executable_folder(void) 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 +#ifdef _WIN32 + HMODULE hModule = GetModuleHandle(NULL); + GetModuleFileName(hModule, path, sizeof(path) - 1); +#else /* No OS-specific way, assume running from CWD */ getcwd(&path[0], sizeof(path) - 1); return path; #endif +#endif #endif size_t pos = strlen(path); while (pos) { pos--; +#ifdef _WIN32 + if (path[pos] == '\\') { +#else if (path[pos] == '/') { +#endif path[pos] = 0; break; } @@ -256,6 +273,7 @@ usage: SDL_Init( SDL_INIT_EVERYTHING ); screen = SDL_SetVideoMode(160, 144, 32, SDL_SWSURFACE ); + SDL_WM_SetCaption("SameBoy v" xstr(VERSION), "SameBoy v" xstr(VERSION)); #ifdef __APPLE__ cocoa_disable_filtering(); #endif @@ -281,14 +299,14 @@ usage: /* Configure Audio */ SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); - want.freq = 96000; + want.freq = AUDIO_FREQUENCY; want.format = AUDIO_S16SYS; want.channels = 2; want.samples = 512; want.callback = audio_callback; want.userdata = &gb; SDL_OpenAudio(&want, &have); - GB_set_sample_rate(&gb, 96000); + GB_set_sample_rate(&gb, AUDIO_FREQUENCY); /* Start Audio */ SDL_PauseAudio(0); diff --git a/Windows/stdio.h b/Windows/stdio.h new file mode 100755 index 00000000..8ff90e49 --- /dev/null +++ b/Windows/stdio.h @@ -0,0 +1,69 @@ +#pragma once +#include_next +#include + +static inline int vasprintf(char **str, const char *fmt, va_list args) +{ + size_t size = _vscprintf(fmt, args) + 1; + *str = malloc(size); + int ret = vsprintf(*str, fmt, args); + if (ret != size - 1) { + free(*str); + *str = NULL; + return -1; + } + return ret; +} + +/* This code is public domain -- Will Hartung 4/9/09 */ +static inline size_t getline(char **lineptr, size_t *n, FILE *stream) { + char *bufptr = NULL; + char *p = bufptr; + size_t size; + int c; + + if (lineptr == NULL) { + return -1; + } + if (stream == NULL) { + return -1; + } + if (n == NULL) { + return -1; + } + bufptr = *lineptr; + size = *n; + + c = fgetc(stream); + if (c == EOF) { + return -1; + } + if (bufptr == NULL) { + bufptr = malloc(128); + if (bufptr == NULL) { + return -1; + } + size = 128; + } + p = bufptr; + while (c != EOF) { + if ((p - bufptr) > (size - 1)) { + size = size + 128; + bufptr = realloc(bufptr, size); + if (bufptr == NULL) { + return -1; + } + } + *p++ = c; + if (c == '\n') { + break; + } + c = fgetc(stream); + } + + *p++ = '\0'; + *lineptr = bufptr; + *n = size; + + return p - bufptr - 1; +} \ No newline at end of file diff --git a/Windows/sys/select.h b/Windows/sys/select.h new file mode 100755 index 00000000..e69de29b diff --git a/Windows/sys/time.h b/Windows/sys/time.h new file mode 100755 index 00000000..e69de29b diff --git a/Windows/unistd.h b/Windows/unistd.h new file mode 100755 index 00000000..e69de29b From 44dfb60c9c5defc1ff30c35e91a4689117aba1fe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Aug 2016 18:15:15 +0300 Subject: [PATCH 0132/1216] Updated README --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34283888..d64c15bf 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Features currently supported only with the Cocoa version: * Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x) ## Compatibility -While SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), some games fail to run correctly. SameBoy is still relatively early in its development and accuracy and compatibility will be improved. +SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. ## Compilation SameBoy requires the following tools and libraries to build: @@ -38,4 +38,11 @@ SameBoy requires the following tools and libraries to build: * SDL port: SDL.framework (OS X) or libsdl (Other platforms) * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation -SameBoy was compiled and tested on OS X and Ubuntu. \ No newline at end of file +On Windows, SameBoy also requires: + * Visual Studio (For headers, etc.) + * [GnuWin](http://gnuwin32.sourceforge.net/) + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively. + +To compile, simply run: make [CONF=(release|debug)] (cocoa|sdl|bootroms) + +SameBoy was compiled and tested on OS X, Ubuntu and Windows 7 32-bit. \ No newline at end of file From 276fe5338566a38218f1e8be1bf17d68540e6b6b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Aug 2016 22:59:03 +0300 Subject: [PATCH 0133/1216] Added icon and version information to Windows port --- Makefile | 13 ++++++++++--- Windows/resources.rc | 24 ++++++++++++++++++++++++ Windows/sameboy.ico | Bin 0 -> 114427 bytes 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 Windows/resources.rc create mode 100644 Windows/sameboy.ico diff --git a/Makefile b/Makefile index 6f774215..2f3e1f54 100755 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" - SDL_LDFLAGS := -lSDL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -LDFLAGS += -lmsvcrt -lSDLmain +LDFLAGS += -lmsvcrt -lSDLmain -Wl,/MANIFESTFILE:NUL else LDFLAGS += -lc -lm endif @@ -169,14 +169,21 @@ ifeq ($(CONF), release) endif # Windows version builds two, one with a conole and one without it -$(BIN)/sdl/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) +$(BIN)/sdl/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:windows -$(BIN)/sdl/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) +$(BIN)/sdl/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:console +$(OBJ)/%.res: %.rc + -@$(MKDIR) -p $(dir $@) + rc /fo $@ /dVERSION=\"$(VERSION)\" $^ + +%.o: %.res + cvtres /OUT:"$@" $^ + # We must provide SDL.dll with the Windows port. This is an AWFUL HACK to find it. SPACE := SPACE += diff --git a/Windows/resources.rc b/Windows/resources.rc new file mode 100644 index 00000000..74e3839d --- /dev/null +++ b/Windows/resources.rc @@ -0,0 +1,24 @@ +1 VERSIONINFO +FILEOS 0x4 +FILETYPE 0x1 +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Lior Halphon" + VALUE "FileDescription", "SameBoy" + VALUE "FileVersion", VERSION + VALUE "LegalCopyright", "Copyright © 2015-2016 Lior Halphon" + VALUE "ProductName", "SameBoy" + VALUE "ProductVersion", VERSION + VALUE "WWW", "https://github.com/LIJI32/SameBoy" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409 0x04E4 + } +} + +IDI_ICON1 ICON DISCARDABLE "SameBoy.ico" \ No newline at end of file diff --git a/Windows/sameboy.ico b/Windows/sameboy.ico new file mode 100644 index 0000000000000000000000000000000000000000..1d677693f7d4f39a08b3d7d63c03eb819a039a6d GIT binary patch literal 114427 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 literal 0 HcmV?d00001 From 04205380340c027a72a85617fd8c11b2a0873ce5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Aug 2016 23:03:28 +0300 Subject: [PATCH 0134/1216] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d64c15bf..ea0b756e 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,6 @@ On Windows, SameBoy also requires: * [GnuWin](http://gnuwin32.sourceforge.net/) * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively. -To compile, simply run: make [CONF=(release|debug)] (cocoa|sdl|bootroms) +To compile, simply run: make (cocoa|sdl|bootroms) -SameBoy was compiled and tested on OS X, Ubuntu and Windows 7 32-bit. \ No newline at end of file +SameBoy was compiled and tested on OS X, Ubuntu and Windows 7 32-bit. From 9479fce7d037ddbc2429633ee9290e7f3fb8b8c5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Aug 2016 23:05:13 +0300 Subject: [PATCH 0135/1216] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea0b756e..8a667973 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,6 @@ On Windows, SameBoy also requires: * [GnuWin](http://gnuwin32.sourceforge.net/) * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively. -To compile, simply run: make (cocoa|sdl|bootroms) +To compile, simply run: `make [CONF=(release|debug)] (cocoa|sdl|bootroms)` SameBoy was compiled and tested on OS X, Ubuntu and Windows 7 32-bit. From 33da8734fe6eaecd2f24824e8bfb0046267dd623 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 21 Aug 2016 00:38:26 +0300 Subject: [PATCH 0136/1216] Mouse hiding is now only enabled during full screen mode --- Cocoa/Document.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 4dad62dd..25fa66db 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -113,7 +113,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void) vblank { - self.view.mouseHidingEnabled = YES; + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [self.view flip]; GB_set_pixels_output(&gb, self.view.pixels); } @@ -127,7 +127,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { GB_apu_copy_buffer(&gb, buffer, nFrames); } andSampleRate:96000]; - self.view.mouseHidingEnabled = YES; + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [self.audioClient start]; NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; @@ -347,11 +347,13 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void) windowWillEnterFullScreen:(NSNotification *)notification { fullScreen = true; + self.view.mouseHidingEnabled = running; } - (void) windowWillExitFullScreen:(NSNotification *)notification { fullScreen = false; + self.view.mouseHidingEnabled = NO; } - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame From 92c2b22735c38df26e196758de4a247f2e78267b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 21 Aug 2016 21:58:33 +0300 Subject: [PATCH 0137/1216] Cocoa port now remembers the mute switch --- Cocoa/Document.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 25fa66db..7f5a8876 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -128,7 +128,9 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) GB_apu_copy_buffer(&gb, buffer, nFrames); } andSampleRate:96000]; self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; - [self.audioClient start]; + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { + [self.audioClient start]; + } NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; while (running) { @@ -312,6 +314,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) else { [self.audioClient start]; } + [[NSUserDefaults standardUserDefaults] setBool:!self.audioClient.isPlaying forKey:@"Mute"]; } - (IBAction)toggleBlend:(id)sender From ee4907949b3ab3581214fa9f43ec7ec4155fc3ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 21 Aug 2016 22:33:57 +0300 Subject: [PATCH 0138/1216] Support for RTC latching. Fixes #4. --- Core/gb.c | 6 +++--- Core/gb.h | 15 ++++++++------- Core/memory.c | 14 +++++++++----- Core/timing.c | 22 +++++++++++----------- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 8e835328..76ccf87e 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -369,7 +369,7 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return EIO; } if (gb->cartridge_type->has_rtc) { - if (fwrite(gb->rtc_data, 1, sizeof(gb->rtc_data), f) != sizeof(gb->rtc_data)) { + if (fwrite(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) { fclose(f); return EIO; } @@ -397,7 +397,7 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) goto reset_rtc; } - if (fread(gb->rtc_data, 1, sizeof(gb->rtc_data), f) != sizeof(gb->rtc_data)) { + if (fread(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) { goto reset_rtc; } @@ -418,7 +418,7 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) 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. */ + gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ exit: fclose(f); return; diff --git a/Core/gb.h b/Core/gb.h index 4c3fe93c..18a78ab8 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -306,15 +306,16 @@ typedef struct GB_gameboy_s { GB_SECTION(rtc, union { struct { - uint8_t rtc_seconds; - uint8_t rtc_minutes; - uint8_t rtc_hours; - uint8_t rtc_days; - uint8_t rtc_high; + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t days; + uint8_t high; }; - uint8_t rtc_data[5]; - }; + uint8_t data[5]; + } rtc_real, rtc_latched; time_t last_rtc_second; + bool rtc_latch; ); /* Video Display */ diff --git a/Core/memory.c b/Core/memory.c index 77dbfb4c..d8719c88 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -78,8 +78,8 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t 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]; + gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ + return gb->rtc_latched.data[gb->mbc_ram_bank - 8]; } if (!gb->mbc_ram) { @@ -284,7 +284,12 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break; - case 0x6000: case 0x7000: /* Todo: Clock latching support */ break; + case 0x6000: case 0x7000: + if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct*/ + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + } + gb->rtc_latch = value & 1; + break; } break; case GB_MBC5: @@ -314,8 +319,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ - gb->rtc_data[gb->mbc_ram_bank - 8] = value; - gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */ + gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; /* Todo: does it really write both? */ } if (gb->cartridge_type->mbc_type == GB_MBC2) { diff --git a/Core/timing.c b/Core/timing.c index 48f28dcf..24f7da70 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -114,26 +114,26 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) void GB_rtc_run(GB_gameboy_t *gb) { - if ((gb->rtc_high & 0x40) == 0) { /* is timer running? */ + if ((gb->rtc_real.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) + if (++gb->rtc_real.seconds == 60) { - gb->rtc_seconds = 0; - if (++gb->rtc_minutes == 60) + gb->rtc_real.seconds = 0; + if (++gb->rtc_real.minutes == 60) { - gb->rtc_minutes = 0; - if (++gb->rtc_hours == 24) + gb->rtc_real.minutes = 0; + if (++gb->rtc_real.hours == 24) { - gb->rtc_hours = 0; - if (++gb->rtc_days == 0) + gb->rtc_real.hours = 0; + if (++gb->rtc_real.days == 0) { - if (gb->rtc_high & 1) /* Bit 8 of days*/ + if (gb->rtc_real.high & 1) /* Bit 8 of days*/ { - gb->rtc_high |= 0x80; /* Overflow bit */ + gb->rtc_real.high |= 0x80; /* Overflow bit */ } - gb->rtc_high ^= 1; + gb->rtc_real.high ^= 1; } } } From 833cd88aeae7aeddb1a0101bf1b3f1aa1791fabd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Aug 2016 01:30:11 +0300 Subject: [PATCH 0139/1216] Allow compilation with precompiled boot ROMs. --- Makefile | 16 ++++++++++++---- README.md | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 2f3e1f54..c6a22919 100755 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +# Make hacks +.INTERMEDIATE: + # Set target, configuration, version and destination folders PLATFORM := $(shell uname -s) @@ -19,6 +22,7 @@ CONF ?= debug BIN := build/bin OBJ := build/obj +BOOTROMS_DIR ?= $(BIN)/BootROMs # Set tools @@ -135,14 +139,14 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(shell ls Cocoa/*.icns) \ Cocoa/License.html \ Cocoa/info.plist \ - $(BIN)/BootROMs/dmg_boot.bin \ - $(BIN)/BootROMs/cgb_boot.bin \ + $(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)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(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 @@ -192,7 +196,11 @@ $(BIN)/sdl/SDL.dll: @$(eval MATCH := $(shell ls $(POTENTIAL_MATCHES) 2> NUL | head -n 1)) cp "$(MATCH)" $@ -$(BIN)/sdl/%.bin: $(BIN)/BootROMs/%.bin +$(BIN)/sdl/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/Sameboy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ diff --git a/README.md b/README.md index 8a667973..bc4970fb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,6 @@ On Windows, SameBoy also requires: * [GnuWin](http://gnuwin32.sourceforge.net/) * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively. -To compile, simply run: `make [CONF=(release|debug)] (cocoa|sdl|bootroms)` +To compile, simply run `make`. The targets are cocoa (Default for OS X), sdl (Default for everything else) and bootroms. You may also specify CONF=debug (default) or CONF=release to control optimization and symbols, and specify BOOTROMS_DIR=... to a directory containing precomiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs. SameBoy was compiled and tested on OS X, Ubuntu and Windows 7 32-bit. From a746c726eee4de5263c505251d73a4df18e62400 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Sep 2016 03:39:32 +0300 Subject: [PATCH 0140/1216] Added basic automatic ROM tester --- Core/display.c | 22 ++-- Core/gb.h | 2 + Core/memory.c | 5 +- Core/z80_cpu.c | 2 +- Makefile | 26 ++++- Tester/main.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 322 insertions(+), 18 deletions(-) create mode 100755 Tester/main.c diff --git a/Core/display.c b/Core/display.c index 5355ca33..4c9874fb 100755 --- a/Core/display.c +++ b/Core/display.c @@ -201,7 +201,7 @@ void display_vblank(GB_gameboy_t *gb) /* Called every Gameboy vblank. Does FPS-capping and calls user's vblank callback if Turbo Mode allows. */ if (gb->turbo) { int64_t nanoseconds = get_nanoseconds(); - if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) { + if (!gb->turbo_dont_skip && nanoseconds <= gb->last_vblank + FRAME_LENGTH) { return; } gb->last_vblank = nanoseconds; @@ -386,16 +386,18 @@ void GB_display_run(GB_gameboy_t *gb) /* Render. This chunk is outside the Mode 3 if, because otherwise we might not render some pixels, since this function only runs between atomic CPU changes, and not every clock. */ - int16_t current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); - for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { - if (gb->previous_lcdc_x >= 160) { - continue; + if (!gb->disable_rendering) { + int16_t current_lcdc_x = ((gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) & ~7) - (gb->effective_scx & 0x7); + 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->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 % LINE_LENGTH < MODE2_LENGTH + MODE3_LENGTH) { /* Mode 3 */ diff --git a/Core/gb.h b/Core/gb.h index 18a78ab8..76006066 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -410,6 +410,8 @@ typedef struct GB_gameboy_s { /* Misc */ bool turbo; + bool turbo_dont_skip; + bool disable_rendering; uint32_t ram_size; // Different between CGB and DMG uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank diff --git a/Core/memory.c b/Core/memory.c index d8719c88..9fd2a54b 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -547,10 +547,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_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; + GB_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr); return; } } diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 21b08381..07eb153d 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -11,7 +11,7 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); static void ill(GB_gameboy_t *gb, uint8_t opcode) { - GB_log(gb, "Illegal Opcode. Halting."); + GB_log(gb, "Illegal Opcode. Halting.\n"); gb->interrupt_enable = 0; gb->halted = true; } diff --git a/Makefile b/Makefile index c6a22919..1bfd5344 100755 --- a/Makefile +++ b/Makefile @@ -76,18 +76,23 @@ endif ifeq ($(PLATFORM),windows32) SDL_TARGET := $(BIN)/sdl/sameboy.exe $(BIN)/sdl/sameboy_debugger.exe $(BIN)/sdl/SDL.dll +TESTER_TARGET := $(BIN)/tester/sameboy_tester.exe else SDL_TARGET := $(BIN)/sdl/sameboy +TESTER_TARGET := $(BIN)/tester/sameboy_tester endif cocoa: $(BIN)/Sameboy.app 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 + # Get a list of our source files and their respective object file targets CORE_SOURCES := $(shell ls Core/*.c) SDL_SOURCES := $(shell ls SDL/*.c) +TESTER_SOURCES := $(shell ls Tester/*.c) ifeq ($(PLATFORM),Darwin) COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) @@ -97,8 +102,7 @@ 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) +TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES)) # Automatic dependency generation @@ -107,6 +111,9 @@ ifneq ($(MAKECMDGOALS),clean) ifneq ($(filter $(MAKECMDGOALS),sdl),) -include $(SDL_OBJECTS:.o=.dep) endif +ifneq ($(filter $(MAKECMDGOALS),tester),) +-include $(TESTER_OBJECTS:.o=.dep) +endif ifneq ($(filter $(MAKECMDGOALS),cocoa),) -include $(COCOA_OBJECTS:.o=.dep) endif @@ -196,7 +203,20 @@ $(BIN)/sdl/SDL.dll: @$(eval MATCH := $(shell ls $(POTENTIAL_MATCHES) 2> NUL | head -n 1)) cp "$(MATCH)" $@ -$(BIN)/sdl/%.bin: $(BOOTROMS_DIR)/%.bin +# Tester + +$(BIN)/tester/sameboy_tester: $(CORE_OBJECTS) $(TESTER_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) +ifeq ($(CONF), release) + strip $@ +endif + +$(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) -Wl,/subsystem:console + +$(BIN)/sdl/%.bin $(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ diff --git a/Tester/main.c b/Tester/main.c new file mode 100755 index 00000000..c4a9bca3 --- /dev/null +++ b/Tester/main.c @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#define snprintf _snprintf +#endif + +#include "gb.h" +#include "debugger.h" + +static bool running = false; +static char *filename; +static char *bmp_filename; +static char *log_filename; +static FILE *log_file; +static void replace_extension(const char *src, size_t length, char *dest, const char *ext); +static bool push_start_a; +static unsigned int test_length = 60 * 40; +GB_gameboy_t gb; + +static unsigned int frames = 0; +const char bmp_header[] = { +0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00, +0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00, +0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF, +0xFF, 0xFF, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00, +0x00, 0x00, 0x02, 0x68, 0x01, 0x00, 0x12, 0x0B, +0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +uint32_t bitmap[160*144]; + +static void vblank(GB_gameboy_t *gb) +{ + /* Do not press any buttons during the last two seconds, this might cause a + screenshot to be taken while the LCD is off if the press makes the game + load graphics. */ + if (push_start_a && frames < test_length - 120) { + switch (frames % 40) { + case 0: + gb->keys[7] = true; // Start down + break; + case 10: + gb->keys[7] = false; // Start up + break; + case 20: + gb->keys[4] = true; // A down + break; + case 30: + gb->keys[4] = false; // A up + break; + } + } + + if (frames == test_length) { + FILE *f = fopen(bmp_filename, "wb"); + fwrite(&bmp_header, 1, sizeof(bmp_header), f); + fwrite(&bitmap, 1, sizeof(bitmap), f); + fclose(f); + running = false; + } + else if (frames == test_length - 1) { + gb->disable_rendering = false; + } + + frames++; +} + +static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + if (!log_file) log_file = fopen(log_filename, "w"); + fprintf(log_file, "%s", string); +} + +#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 +#ifdef _WIN32 + HMODULE hModule = GetModuleHandle(NULL); + GetModuleFileName(hModule, path, sizeof(path) - 1); +#else + /* No OS-specific way, assume running from CWD */ + getcwd(&path[0], sizeof(path) - 1); + return path; +#endif +#endif +#endif + size_t pos = strlen(path); + while (pos) { + pos--; +#ifdef _WIN32 + if (path[pos] == '\\') { +#else + if (path[pos] == '/') { +#endif + 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 uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return (r << 24) | (g << 16) | (b << 8); +} + +static void replace_extension(const char *src, size_t length, char *dest, const char *ext) +{ + memcpy(dest, src, length); + dest[length] = 0; + + /* Remove extension */ + for (size_t i = length; i--;) { + if (dest[i] == '/') break; + if (dest[i] == '.') { + dest[i] = 0; + break; + } + } + + /* Add new extension */ + strcat(dest, ext); +} + +int main(int argc, char **argv) +{ +#define str(x) #x +#define xstr(x) str(x) + fprintf(stderr, "SameBoy Tester v" xstr(VERSION) "\n"); + + if (argc == 1) { + fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds]" +#ifndef _WIN32 + " [--jobs number of tests to run simultaneously]" +#endif + " rom ...\n", argv[0]); + exit(1); + } + +#ifndef _WIN32 + unsigned int max_forks = 1; + unsigned int current_forks = 0; +#endif + + bool dmg = false; + for (unsigned i = 1; i < argc; i++) { + if (strcmp(argv[i], "--dmg") == 0) { + fprintf(stderr, "Using DMG mode\n"); + dmg = true; + continue; + } + + if (strcmp(argv[i], "--start") == 0) { + fprintf(stderr, "Pushing Start and A\n"); + push_start_a = true; + continue; + } + + if (strcmp(argv[i], "--length") == 0 && i != argc - 1) { + test_length = atoi(argv[++i]) * 60; + fprintf(stderr, "Test length is %d seconds\n", test_length / 60); + continue; + } + +#ifndef _WIN32 + if (strcmp(argv[i], "--jobs") == 0 && i != argc - 1) { + max_forks = atoi(argv[++i]); + /* Make sure wrong input doesn't blow anything up. */ + if (max_forks < 1) max_forks = 1; + if (max_forks > 16) max_forks = 16; + fprintf(stderr, "Running up to %d tests simultaneously\n", max_forks); + continue; + } + + if (max_forks > 1) { + while (current_forks >= max_forks) { + int wait_out; + while(wait(&wait_out) == -1); + current_forks--; + } + + current_forks++; + if (fork() != 0) continue; + } +#endif + + if (dmg) { + GB_init(&gb); + if (GB_load_boot_rom(&gb, executable_relative_path("dmg_boot.bin"))) { + perror("Failed to load boot ROM"); + exit(1); + } + } + else { + GB_init_cgb(&gb); + if (GB_load_boot_rom(&gb, executable_relative_path("cgb_boot.bin"))) { + perror("Failed to load boot ROM"); + exit(1); + } + } + + filename = argv[i]; + fprintf(stderr, "Testing ROM %s\n", filename); + + if (GB_load_rom(&gb, filename)) { + perror("Failed to load ROM"); + exit(1); + } + + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_pixels_output(&gb, &bitmap[0]); + GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_log_callback(&gb, log_callback); + + size_t path_length = strlen(filename); + + char bitmap_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .bmp + NULL */ + replace_extension(filename, path_length, bitmap_path, ".bmp"); + bmp_filename = &bitmap_path[0]; + + char log_path[path_length + 5]; + replace_extension(filename, path_length, log_path, ".log"); + log_filename = &log_path[0]; + + /* Run emulation */ + running = true; + gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; + frames = 0; + while (running) { + GB_run(&gb); + } + + if (log_file) { + fclose(log_file); + log_file = NULL; + } + + GB_free(&gb); +#ifndef _WIN32 + if (max_forks > 1) { + exit(0); + } +#endif + } +#ifndef _WIN32 + int wait_out; + while(wait(&wait_out) != -1); +#endif + return 0; +} + From a2d77b175449450278a6350a8978bd291957110e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Sep 2016 04:00:37 +0300 Subject: [PATCH 0141/1216] Warn about unsupported cartridges/MBCs --- Core/mbc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/mbc.c b/Core/mbc.c index fddccb47..5ba6c3f6 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -101,6 +101,10 @@ void GB_configure_cart(GB_gameboy_t *gb) { gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; + if (gb->rom[0x147] == 0xFF || (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0)) { + GB_log(gb, "Cartridge type %02x is not yet supported.\n", 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]]; From a6c5a8fdaae0db7a383b9551246a4d675c0b3b41 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Sep 2016 13:34:49 +0300 Subject: [PATCH 0142/1216] Fixed a bug that caused the Cocoa port to freeze sometimes if the emulator was reset while debugging. --- Core/gb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/gb.h b/Core/gb.h index 76006066..bd8d0eb8 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -379,7 +379,7 @@ typedef struct GB_gameboy_s { size_t ir_queue_length; /*** Debugger ***/ - bool debug_stopped; + volatile bool debug_stopped; bool debug_fin_command, debug_next_command; /* Breakpoints */ From 84a4701733d4c9cf6942e4c243ae2c96a9e2d00d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Sep 2016 22:59:23 +0300 Subject: [PATCH 0143/1216] Auto-detect common crashes, fixed logs about unsupported carts not being written. --- Tester/main.c | 55 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index c4a9bca3..b83b428e 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -12,6 +12,7 @@ #include "gb.h" #include "debugger.h" +#include "memory.h" static bool running = false; static char *filename; @@ -59,12 +60,31 @@ static void vblank(GB_gameboy_t *gb) break; } } + + /* Detect common crashes and stop the test early */ + if (frames < test_length - 1) { + if (gb->backtrace_size >= 0x80) { + GB_log(gb, "A stack overflow has probably occurred.\n"); + frames = test_length - 1; + } + if (gb->pc == 0x38 && GB_read_memory(gb, 0x38) == 0xFF) { + GB_log(gb, "The game is probably stuck in an FF loop.\n"); + frames = test_length - 1; + } + if (gb->halted && (!gb->ime || !gb->interrupt_enable)) { + GB_log(gb, "The game is deadlocked.\n"); + frames = test_length - 1; + } + } if (frames == test_length) { FILE *f = fopen(bmp_filename, "wb"); fwrite(&bmp_header, 1, sizeof(bmp_header), f); fwrite(&bitmap, 1, sizeof(bitmap), f); fclose(f); + if (!gb->boot_rom_finished) { + GB_log(gb, "Boot ROM did not finish.\n"); + } running = false; } else if (frames == test_length - 1) { @@ -215,7 +235,19 @@ int main(int argc, char **argv) if (fork() != 0) continue; } #endif + filename = argv[i]; + size_t path_length = strlen(filename); + char bitmap_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .bmp + NULL */ + replace_extension(filename, path_length, bitmap_path, ".bmp"); + bmp_filename = &bitmap_path[0]; + + char log_path[path_length + 5]; + replace_extension(filename, path_length, log_path, ".log"); + log_filename = &log_path[0]; + + fprintf(stderr, "Testing ROM %s\n", filename); + if (dmg) { GB_init(&gb); if (GB_load_boot_rom(&gb, executable_relative_path("dmg_boot.bin"))) { @@ -230,29 +262,16 @@ int main(int argc, char **argv) exit(1); } } - - filename = argv[i]; - fprintf(stderr, "Testing ROM %s\n", filename); - - if (GB_load_rom(&gb, filename)) { - perror("Failed to load ROM"); - exit(1); - } - + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_pixels_output(&gb, &bitmap[0]); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_log_callback(&gb, log_callback); - - size_t path_length = strlen(filename); - - char bitmap_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .bmp + NULL */ - replace_extension(filename, path_length, bitmap_path, ".bmp"); - bmp_filename = &bitmap_path[0]; - char log_path[path_length + 5]; - replace_extension(filename, path_length, log_path, ".log"); - log_filename = &log_path[0]; + if (GB_load_rom(&gb, filename)) { + perror("Failed to load ROM"); + exit(1); + } /* Run emulation */ running = true; From ab2e532cc33fe02412d82f3f6680144ee521f290 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Sep 2016 23:24:47 +0300 Subject: [PATCH 0144/1216] Added link to automation results --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc4970fb..0f632ab2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Features currently supported only with the Cocoa version: * Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x) ## Compatibility -SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. +SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. The latest results for SameBoy's automatic tester are available [here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html). ## Compilation SameBoy requires the following tools and libraries to build: From 603b8969ab3cf179f6457d98ca2fb4996ff37b57 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 5 Sep 2016 22:13:28 +0300 Subject: [PATCH 0145/1216] Correct (disconnected) serial emulation. --- Core/gb.c | 4 ++-- Core/gb.h | 1 + Core/memory.c | 16 ++++++++++++---- Core/timing.c | 12 ++++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 76ccf87e..dd591d03 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -111,8 +111,8 @@ void GB_init(GB_gameboy_t *gb) gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; - gb->io_registers[GB_IO_JOYP] = 0xF; + gb->io_registers[GB_IO_SC] = 0x7E; } void GB_init_cgb(GB_gameboy_t *gb) @@ -134,8 +134,8 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; - gb->io_registers[GB_IO_JOYP] = 0xF; + gb->io_registers[GB_IO_SC] = 0x7C; } void GB_free(GB_gameboy_t *gb) diff --git a/Core/gb.h b/Core/gb.h index bd8d0eb8..dd6c7c01 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -295,6 +295,7 @@ typedef struct GB_gameboy_s { GB_PADDING(uint32_t, dma_cycles); GB_aligned_double apu_cycles; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ + uint16_t serial_cycles; ); /* APU */ diff --git a/Core/memory.c b/Core/memory.c index 9fd2a54b..8faa0dff 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -162,6 +162,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_OBP1: case GB_IO_WY: case GB_IO_WX: + case GB_IO_SC: case GB_IO_SB: return gb->io_registers[addr & 0xFF]; case GB_IO_TIMA: @@ -220,8 +221,6 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return ret; } - case GB_IO_SC: /* Serial not supported yet */ - return 0x7E; default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { return GB_apu_read(gb, addr & 0xFF); @@ -521,10 +520,19 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->hdma_cycles = 0; return; + /* Todo: what happens when starting a transfer during a transfer? + What happens when starting a transfer during external clock? + */ case GB_IO_SC: + if (!gb->cgb_mode) { + value |= 2; + } + gb->io_registers[GB_IO_SC] = value | (~0x83); if ((value & 0x80) && (value & 0x1) ) { - gb->io_registers[GB_IO_SB] = 0xFF; - gb->io_registers[GB_IO_IF] |= 0x8; + gb->serial_cycles = gb->cgb_mode && (value & 2)? 128 : 4096; + } + else { + gb->serial_cycles = 0; } return; diff --git a/Core/timing.c b/Core/timing.c index 24f7da70..726fb36d 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -41,6 +41,18 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) } } + if (gb->serial_cycles) { + if (gb->serial_cycles <= cycles) { + gb->serial_cycles = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->io_registers[GB_IO_SB] = 0xFF; + gb->io_registers[GB_IO_IF] |= 8; + } + else { + gb->serial_cycles -= cycles; + } + } + if (gb->cgb_double_speed) { cycles >>=1; } From ae003ee020bb2f14d5cc6c6117035b2b225d9f6c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 6 Sep 2016 18:00:05 +0300 Subject: [PATCH 0146/1216] Fixed several automation false negatives. --- Tester/main.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index b83b428e..e61b6133 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -20,7 +20,7 @@ static char *bmp_filename; static char *log_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); -static bool push_start_a; +static bool push_start_a, nekojara_fix; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -45,7 +45,7 @@ static void vblank(GB_gameboy_t *gb) screenshot to be taken while the LCD is off if the press makes the game load graphics. */ if (push_start_a && frames < test_length - 120) { - switch (frames % 40) { + switch (frames % (nekojara_fix? 60 : 40)) { /* Nekojara's first menu item is continue, so the normal hueristic won't work. */ case 0: gb->keys[7] = true; // Start down break; @@ -58,6 +58,12 @@ static void vblank(GB_gameboy_t *gb) case 30: gb->keys[4] = false; // A up break; + case 40: + gb->keys[3] = true; // D-Pad Down down + break; + case 50: + gb->keys[3] = false; // D-Pad Down up + break; } } @@ -71,7 +77,7 @@ static void vblank(GB_gameboy_t *gb) GB_log(gb, "The game is probably stuck in an FF loop.\n"); frames = test_length - 1; } - if (gb->halted && (!gb->ime || !gb->interrupt_enable)) { + if (gb->halted && !gb->interrupt_enable) { GB_log(gb, "The game is deadlocked.\n"); frames = test_length - 1; } @@ -272,7 +278,7 @@ int main(int argc, char **argv) perror("Failed to load ROM"); exit(1); } - + nekojara_fix = strcmp((const char *)(gb.rom + 0x134), "NEKOJARA") == 0; /* It's OK. No overflow is possilbe here. */ /* Run emulation */ running = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; From 01fc137256fcc54f3b7baf58831726913a2267bd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 6 Sep 2016 22:36:16 +0300 Subject: [PATCH 0147/1216] Fixed an APU issue that might occur when having more than one GB_gameboy_t object --- Core/apu.c | 9 ++++----- Core/apu.h | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index c4085eed..1234a151 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -271,7 +271,6 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { static const double duties[] = {0.125, 0.25, 0.5, 0.75}; - static uint16_t NRX3_X4_temp[3] = {0}; uint8_t channel = 0; if (!gb->apu.global_enable && reg != GB_IO_NR52) { @@ -336,8 +335,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) 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]); + gb->apu.NRX3_X4_temp[channel] = (gb->apu.NRX3_X4_temp[channel] & 0xFF00) | value; + gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - gb->apu.NRX3_X4_temp[channel]); if (channel == 2) { gb->apu.wave_channels[channel].frequency /= 2; } @@ -353,8 +352,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) 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]); + gb->apu.NRX3_X4_temp[channel] = (gb->apu.NRX3_X4_temp[channel] & 0xFF) | ((value & 0x7) << 8); + gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - gb->apu.NRX3_X4_temp[channel]); if (channel == 2) { gb->apu.wave_channels[channel].frequency /= 2; } diff --git a/Core/apu.h b/Core/apu.h index 835c569f..4e2f1308 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -53,6 +53,7 @@ typedef struct bool left_on[4]; bool right_on[4]; bool global_enable; + uint16_t NRX3_X4_temp[3]; } GB_apu_t; void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_samples, GB_sample_t *samples); From 62ecadeb57a9123d0916f9f10d1ef177522d5f1c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 7 Sep 2016 00:37:11 +0300 Subject: [PATCH 0148/1216] Fixed another reset-while-debugging deadlock in Cocoa --- Cocoa/Document.m | 2 ++ Core/debugger.c | 4 +++- Core/gb.h | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 7f5a8876..e5442ee4 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -153,6 +153,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void) stop { if (!running) return; + gb.debug_disable = true; if (gb.debug_stopped) { gb.debug_stopped = false; [self consoleInput:nil]; @@ -160,6 +161,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) stopping = true; running = false; while (stopping); + gb.debug_disable = false; } - (IBAction)reset:(id)sender diff --git a/Core/debugger.c b/Core/debugger.c index 51e015d4..ca0c3675 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1463,6 +1463,8 @@ bool GB_debugger_do_command(GB_gameboy_t *gb, char *input) void GB_debugger_run(GB_gameboy_t *gb) { + if (gb->debug_disable) return; + char *input = NULL; if (gb->debug_next_command && gb->debug_call_depth <= 0) { gb->debug_stopped = true; @@ -1482,7 +1484,7 @@ next_command: GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_cpu_disassemble(gb, gb->pc, 5); } - if (gb->debug_stopped) { + if (gb->debug_stopped && !gb->debug_disable) { gb->debug_next_command = false; gb->debug_fin_command = false; gb->stack_leak_detection = false; diff --git a/Core/gb.h b/Core/gb.h index dd6c7c01..a2a7d8b2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -380,7 +380,7 @@ typedef struct GB_gameboy_s { size_t ir_queue_length; /*** Debugger ***/ - volatile bool debug_stopped; + volatile bool debug_stopped, debug_disable; bool debug_fin_command, debug_next_command; /* Breakpoints */ From 7bafb6a843c5bbdb747914db484b4042d293a3b3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 7 Sep 2016 00:44:00 +0300 Subject: [PATCH 0149/1216] Cocoa port: remember breakpoints and watchpoints after reset --- Cocoa/Document.m | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e5442ee4..aca44d27 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -169,6 +169,18 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) bool was_cgb = gb.is_cgb; [self stop]; is_inited = false; + + /* Back up user's breakpoints/watchpoints */ + typeof(gb.breakpoints) breakpoints = gb.breakpoints; + typeof(gb.n_breakpoints) n_breakpoints = gb.n_breakpoints; + typeof(gb.watchpoints) watchpoints = gb.watchpoints; + typeof(gb.n_watchpoints) n_watchpoints = gb.n_watchpoints; + + /* Reset them so they're not freed*/ + gb.watchpoints = NULL; + gb.breakpoints = NULL; + gb.n_watchpoints = gb.n_breakpoints = 0; + GB_free(&gb); if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) { [self initCGB]; @@ -176,6 +188,13 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) else { [self initDMG]; } + + /* Restore backpoints/watchpoints */ + gb.breakpoints = breakpoints; + gb.n_breakpoints = n_breakpoints; + gb.watchpoints = watchpoints; + gb.n_watchpoints = n_watchpoints; + if ([sender tag] != 0) { /* User explictly selected a model, save the preference */ [[NSUserDefaults standardUserDefaults] setBool:!gb.is_cgb forKey:@"EmulateDMG"]; From ad604036a5653c4c899de322b8a38de733075e69 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Sep 2016 19:29:14 +0300 Subject: [PATCH 0150/1216] More false-negative fixes --- Tester/main.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index e61b6133..52d797cb 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -20,7 +20,7 @@ static char *bmp_filename; static char *log_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); -static bool push_start_a, nekojara_fix; +static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -45,7 +45,11 @@ static void vblank(GB_gameboy_t *gb) screenshot to be taken while the LCD is off if the press makes the game load graphics. */ if (push_start_a && frames < test_length - 120) { - switch (frames % (nekojara_fix? 60 : 40)) { /* Nekojara's first menu item is continue, so the normal hueristic won't work. */ + unsigned combo_length = 40; + if (start_is_not_first) combo_length = 60; /* The start item in the menu is not the first, so also push down */ + else if (a_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ + + switch ((push_faster? frames * 2 : frames) % combo_length) { case 0: gb->keys[7] = true; // Start down break; @@ -53,13 +57,15 @@ static void vblank(GB_gameboy_t *gb) gb->keys[7] = false; // Start up break; case 20: - gb->keys[4] = true; // A down + gb->keys[b_is_confirm? 5: 4] = true; // A down (or B) break; case 30: - gb->keys[4] = false; // A up + gb->keys[b_is_confirm? 5: 4] = false; // A up (or B) break; case 40: - gb->keys[3] = true; // D-Pad Down down + if (gb->boot_rom_finished) { + gb->keys[3] = true; // D-Pad Down down + } break; case 50: gb->keys[3] = false; // D-Pad Down up @@ -278,7 +284,15 @@ int main(int argc, char **argv) perror("Failed to load ROM"); exit(1); } - nekojara_fix = strcmp((const char *)(gb.rom + 0x134), "NEKOJARA") == 0; /* It's OK. No overflow is possilbe here. */ + + /* Game specific hacks for start attempt automations */ + /* It's OK. No overflow is possible here. */ + start_is_not_first = strcmp((const char *)(gb.rom + 0x134), "NEKOJARA") == 0 || + strcmp((const char *)(gb.rom + 0x134), "GINGA") == 0; + a_is_bad = strcmp((const char *)(gb.rom + 0x134), "DESERT STRIKE") == 0; + b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0; + push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0; + /* Run emulation */ running = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; From 6d836b9f48217b116be1a3f62e027368a1bb2b41 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Sep 2016 02:20:44 +0300 Subject: [PATCH 0151/1216] Fixed APU bug that prevented some games from working --- Core/apu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 1234a151..b0634a96 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -345,7 +345,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR24: case GB_IO_NR34: gb->apu.wave_channels[channel].stop_on_length = value & 0x40; - if (value & 0x80) { + if ((value & 0x80) && (channel != 2 || gb->apu.wave_enable)) { 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; @@ -360,6 +360,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) break; case GB_IO_NR30: gb->apu.wave_enable = value & 0x80; + gb->apu.wave_channels[2].is_playing &= gb->apu.wave_enable; break; case GB_IO_NR31: gb->apu.wave_channels[2].sound_length = (256 - value) / 256.0; From 95cfb114a255b5ad6d5adc9d3a9a12017a56357e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Sep 2016 13:25:18 +0300 Subject: [PATCH 0152/1216] Properly setting MBC RAM --- Core/mbc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/mbc.c b/Core/mbc.c index 5ba6c3f6..27192080 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -109,6 +109,7 @@ void GB_configure_cart(GB_gameboy_t *gb) 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); + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); } /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these). From 6e86dbcebb52df5e00baf20b3de423ce416d1c59 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Sep 2016 19:46:42 +0300 Subject: [PATCH 0153/1216] Cocoa port now shows the open dialog if no ROM is open. --- Cocoa/AppDelegate.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 3da9ed8a..a03b062f 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -52,4 +52,12 @@ } [_preferencesWindow makeKeyAndOrderFront:self]; } + +- (void)applicationDidBecomeActive:(NSNotification *)notification +{ + NSDocumentController *controller = [NSDocumentController sharedDocumentController]; + if (![[controller documents] count]) { + [[NSDocumentController sharedDocumentController] openDocument:self]; + } +} @end From 71a9b7eb7700a455ff91d49a008d9d656d1470a4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Sep 2016 20:59:02 +0300 Subject: [PATCH 0154/1216] Updated change log and incremented version to 0.7 --- CHANGES.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 10c9df11..3f0e1bec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,55 @@ # Change Log +## Version 0.7 + +### New/Improved Features + * The debugger now includes a backtrace command to show the stacktrace + * Cocoa port now includes a Hex Editor/Viewer + * The debugger help command was improved + * General improvements to debugger usability + * The SDL port now compiles on Windows (Binaries included) + * Mouse hiding in Cocoa is now only enabled during full screen + * The Cocoa port now remembers the mute setting + * SameBoy now issues a warning when loading a game that uses an unsupported cartridge + * Cocoa port now remembers previous breakpoints/watchpoints after reset + * Cocoa port now automatically shows the open if needed + +### Accuracy Improvements/Fixes +As of this version, SameBoy is regularly tested against 1405 DMG ROMs to make sure no accuracy regressions are made. [The latest results are available here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html). + + * OBP0/1 are now initialized to the correct value (Fixes Mooneye's DMG hardware registers test). + * A disconnected serial cable is now emulated. Fixes: + * Baseball + * Faceball 2000 + * Fighting Simulator + * Godzilla + * Hiryuu Gaiden + * In Your Face + * Lunar Lander + * Pinball Party + * Sneaky Snakes + * Super R.C. Pro-Am + * WWF Stars + * Yoshi's Egg + * Correctly emulating unused OAM RAM in DMG mode + * DMG boot ROM now finishes with the original register values (Fixes Mooneye's DMG boot registers test) + * RTC clock latching is now emulated. + * Fixed APU issues where simultaneously running games could affect eachother + * Fixed APU issue that could break some games. Fixes: + * Chiki Chiki Tengoku + * Moguranya/Mole Mania + * Fixed MBC RAM not being properly reset. Old save data must be deleted for this fix to apply. Fixes: + * Purikura Pocket 3 + * Probably affects many other games + +### Bug Fixes + * Boot ROMs were not trimmed correctly + * Fixes several bugs that caused the Cocoa port to freeze when using the reset command during debugging + +### Misc Internal Changes + * SameBoy can now be compiled with precompiled (non-SameBoy) boot ROMs + * SameBoy includes an automated game ROM tester + ## Version 0.6 ### New/Improved Features diff --git a/Makefile b/Makefile index 1bfd5344..f2a5be33 100755 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.6 +VERSION := 0.7 CONF ?= debug BIN := build/bin From 43be91f0324bb8fedf3e3e4d5cdf3967e7e05da5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Sep 2016 01:20:18 +0300 Subject: [PATCH 0155/1216] Slightly more readable code. --- Core/z80_cpu.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 07eb153d..e6733b86 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1418,11 +1418,13 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } - if (gb->ime && interrupt) { - if (gb->ime_toggle) { - gb->ime = !gb->ime; - gb->ime_toggle = false; - } + bool effecitve_ime = gb->ime; + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } + + if (effecitve_ime && interrupt) { uint8_t interrupt_bit = 0; uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; while (!(interrupt_queue & 1)) { @@ -1431,17 +1433,12 @@ void GB_cpu_run(GB_gameboy_t *gb) } gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); gb->ime = false; - gb->ime_toggle = false; nop(gb, 0); gb->pc -= 2; /* Run pseudo instructions rst 40-60*/ rst(gb, 0x87 + interrupt_bit * 8); } else if(!gb->halted && !gb->stopped) { - if (gb->ime_toggle) { - gb->ime = !gb->ime; - gb->ime_toggle = false; - } uint8_t opcode = GB_read_memory(gb, gb->pc); opcodes[opcode](gb, opcode); } From b95860c034a58fa5cef64f8b85b8ed4f60fc0d10 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Sep 2016 01:21:47 +0300 Subject: [PATCH 0156/1216] Making the APU independent of sample rate --- Core/apu.c | 181 ++++++++++++++++++++++++++------------------------ Core/apu.h | 16 ++--- Core/gb.c | 2 +- Core/gb.h | 2 +- Core/timing.c | 3 +- 5 files changed, 108 insertions(+), 96 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index b0634a96..ca59ba9d 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -16,26 +16,25 @@ _a > _b ? _a : _b; }) __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); -} +#define APU_FREQUENCY 0x80000 -static int16_t generate_square(double phase, int16_t amplitude, double duty) +static int16_t generate_square(uint64_t phase, uint32_t wave_length, int16_t amplitude, double duty) { - if (fmod(phase, 2 * M_PI) > duty * 2 * M_PI) { + if (!wave_length) return 0; + if (phase % wave_length > wave_length * duty) { return amplitude; } return 0; } -static int16_t generate_wave(double phase, int16_t amplitude, int8_t *wave, uint8_t shift) +static int16_t generate_wave(uint64_t phase, uint32_t wave_length, int16_t amplitude, int8_t *wave, uint8_t shift) { - phase = fmod(phase, 2 * M_PI); - return ((wave[(int)(phase / (2 * M_PI) * 32)]) >> shift) * (int)amplitude / 0xF; + if (!wave_length) wave_length = 1; + phase = phase % wave_length; + return ((wave[(int)(phase * 32 / wave_length)]) >> shift) * (int)amplitude / 0xF; } -static int16_t generate_noise(double phase, int16_t amplitude, uint16_t lfsr) +static int16_t generate_noise(int16_t amplitude, uint16_t lfsr) { if (lfsr & 1) { return amplitude; @@ -62,71 +61,76 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) /* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with these tests in mind. */ -void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_samples, GB_sample_t *samples) +void GB_apu_run_internal(GB_gameboy_t *gb, unsigned int n_cycles, GB_sample_t *samples) { - for (; n_samples--; samples++) { - samples->left = samples->right = 0; - if (!gb->apu.global_enable) { - continue; + while (n_cycles--) { + if (n_cycles == 0) { + samples->left = samples->right = 0; + if (!gb->apu.global_enable) { + continue; + } + + gb->io_registers[GB_IO_PCM_12] = 0; + gb->io_registers[GB_IO_PCM_34] = 0; + + { + int16_t sample = generate_square(gb->apu.wave_channels[0].phase, + gb->apu.wave_channels[0].wave_length, + gb->apu.wave_channels[0].amplitude, + gb->apu.wave_channels[0].duty); + if (gb->apu.wave_channels[0].left_on ) samples->left += sample; + if (gb->apu.wave_channels[0].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_square(gb->apu.wave_channels[1].phase, + gb->apu.wave_channels[1].wave_length, + gb->apu.wave_channels[1].amplitude, + gb->apu.wave_channels[1].duty); + if (gb->apu.wave_channels[1].left_on ) samples->left += sample; + if (gb->apu.wave_channels[1].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + if (gb->apu.wave_enable) + { + int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, + gb->apu.wave_channels[2].wave_length, + MAX_CH_AMP, + gb->apu.wave_form, + gb->apu.wave_shift); + if (gb->apu.wave_channels[2].left_on ) samples->left += sample; + if (gb->apu.wave_channels[2].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude, + gb->apu.lfsr); + if (gb->apu.wave_channels[3].left_on ) samples->left += sample; + if (gb->apu.wave_channels[3].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + samples->left *= gb->apu.left_volume; + samples->right *= gb->apu.right_volume; } - gb->io_registers[GB_IO_PCM_12] = 0; - gb->io_registers[GB_IO_PCM_34] = 0; - - { - int16_t sample = generate_square(gb->apu.wave_channels[0].phase, - gb->apu.wave_channels[0].amplitude, - gb->apu.wave_channels[0].duty); - if (gb->apu.left_on [0]) samples->left += sample; - if (gb->apu.right_on[0]) samples->right += sample; - gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; - } - - { - int16_t sample = generate_square(gb->apu.wave_channels[1].phase, - gb->apu.wave_channels[1].amplitude, - gb->apu.wave_channels[1].duty); - if (gb->apu.left_on [1]) samples->left += sample; - if (gb->apu.right_on[1]) samples->right += sample; - gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; - } - - if (gb->apu.wave_enable) - { - int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, - MAX_CH_AMP, - gb->apu.wave_form, - gb->apu.wave_shift); - if (gb->apu.left_on [2]) samples->left += sample; - if (gb->apu.right_on[2]) samples->right += sample; - gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; - } - - { - int16_t sample = generate_noise(gb->apu.wave_channels[3].phase, - gb->apu.wave_channels[3].amplitude, - gb->apu.lfsr); - if (gb->apu.left_on [3]) samples->left += sample; - if (gb->apu.right_on[3]) samples->right += sample; - gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; - } - - samples->left *= gb->apu.left_volume; - samples->right *= gb->apu.right_volume; - for (uint8_t 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) { + gb->apu.wave_channels[i].phase++; + if (gb->apu.wave_channels[i].wave_length && gb->apu.wave_channels[i].phase >= gb->apu.wave_channels[i].wave_length) { 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; + + gb->apu.wave_channels[i].phase %= gb->apu.wave_channels[i].wave_length; } /* 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; + gb->apu.wave_channels[i].sound_length -= 1.0 / APU_FREQUENCY; } if (gb->apu.wave_channels[i].sound_length <= 0) { gb->apu.wave_channels[i].amplitude = 0; @@ -136,7 +140,7 @@ void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_sa } } - gb->apu.envelope_step_timer += 1.0 / sample_rate; + gb->apu.envelope_step_timer += 1.0 / APU_FREQUENCY; if (gb->apu.envelope_step_timer >= 1.0 / 64) { gb->apu.envelope_step_timer -= 1.0 / 64; for (uint8_t i = 0; i < 4; i++) { @@ -147,13 +151,13 @@ void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_sa } } - gb->apu.sweep_step_timer += 1.0 / sample_rate; + gb->apu.sweep_step_timer += 1.0 / APU_FREQUENCY; 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 - uint16_t temp = (uint16_t) (2048 - 131072 / gb->apu.wave_channels[0].frequency); + uint16_t temp = 2048 - gb->apu.wave_channels[0].wave_length / (APU_FREQUENCY / 131072); // Apply sweep temp = temp + gb->apu.wave_channels[0].sweep_direction * @@ -163,7 +167,8 @@ void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_sa } // Back to frequency - gb->apu.wave_channels[0].frequency = 131072.0 / (2048 - temp); + gb->apu.wave_channels[0].wave_length = (2048 - temp) * (APU_FREQUENCY / 131072); + gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps; } @@ -176,20 +181,26 @@ void GB_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) { - GB_sample_t sample = {0, }; - GB_apu_render(gb, gb->sample_rate, 1, &sample); - gb->apu_cycles -= ticks_per_sample; + GB_sample_t sample = {0, }; + + if (gb->apu.apu_cycles >= CPU_FREQUENCY / APU_FREQUENCY) { + GB_apu_run_internal(gb, gb->apu.apu_cycles / (CPU_FREQUENCY / APU_FREQUENCY), &sample); + gb->apu.apu_cycles %= (CPU_FREQUENCY / APU_FREQUENCY); + gb->audio_buffer[gb->audio_position] = sample; + } + + if (gb->apu_sample_cycles > ticks_per_sample) { + gb->apu_sample_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; - } + GB_log(gb, "Audio overflow\n"); + should_log_overflow = false; + } */ } else { - gb->audio_buffer[gb->audio_position++] = sample; + gb->audio_position++; should_log_overflow = true; } } @@ -225,7 +236,7 @@ void GB_apu_init(GB_gameboy_t *gb) 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; + gb->apu.wave_channels[i].left_on = gb->apu.wave_channels[i].right_on = 1; } } @@ -335,10 +346,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR13: case GB_IO_NR23: case GB_IO_NR33: - gb->apu.NRX3_X4_temp[channel] = (gb->apu.NRX3_X4_temp[channel] & 0xFF00) | value; - gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - gb->apu.NRX3_X4_temp[channel]); + gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF00) | value; + gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072); if (channel == 2) { - gb->apu.wave_channels[channel].frequency /= 2; + gb->apu.wave_channels[channel].wave_length *= 2; } break; case GB_IO_NR14: @@ -352,10 +363,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channels[channel].cur_envelope_steps = gb->apu.wave_channels[channel].envelope_steps; } - gb->apu.NRX3_X4_temp[channel] = (gb->apu.NRX3_X4_temp[channel] & 0xFF) | ((value & 0x7) << 8); - gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - gb->apu.NRX3_X4_temp[channel]); + gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF) | ((value & 0x7) << 8); + gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072); if (channel == 2) { - gb->apu.wave_channels[channel].frequency /= 2; + gb->apu.wave_channels[channel].wave_length *= 2; } break; case GB_IO_NR30: @@ -379,7 +390,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) double r = value & 0x7; if (r == 0) r = 0.5; uint8_t s = value >> 4; - gb->apu.wave_channels[3].frequency = 524288.0 / r / (1 << (s + 1)); + gb->apu.wave_channels[3].wave_length = r * (1 << s) * (APU_FREQUENCY / 262144) ; gb->apu.lfsr_7_bit = value & 0x8; break; } @@ -400,8 +411,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) 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; + gb->apu.wave_channels[i].left_on = value & 1; + gb->apu.wave_channels[i].right_on = value & 0x10; value >>= 1; } break; diff --git a/Core/apu.h b/Core/apu.h index 4e2f1308..db717116 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -21,8 +21,8 @@ typedef struct /* Not all used on all channels */ typedef struct { - GB_aligned_double phase; - GB_aligned_double frequency; + uint64_t phase; + uint32_t wave_length; GB_aligned_double duty; GB_aligned_double sound_length; /* In seconds */ int16_t amplitude; @@ -36,11 +36,15 @@ typedef struct signed int sweep_direction; uint8_t sweep_shift; bool is_playing; + uint16_t NRX3_X4_temp; + bool left_on; + bool right_on; } GB_apu_channel_t; typedef struct { - GB_apu_channel_t wave_channels[4]; + uint8_t apu_cycles; + bool global_enable; GB_aligned_double envelope_step_timer; /* In seconds */ GB_aligned_double sweep_step_timer; /* In seconds */ int8_t wave_form[32]; @@ -50,13 +54,9 @@ typedef struct bool lfsr_7_bit; double left_volume; double right_volume; - bool left_on[4]; - bool right_on[4]; - bool global_enable; - uint16_t NRX3_X4_temp[3]; + GB_apu_channel_t wave_channels[4]; } GB_apu_t; -void GB_apu_render(GB_gameboy_t *gb, unsigned int sample_rate, unsigned int n_samples, GB_sample_t *samples); void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count); void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); diff --git a/Core/gb.c b/Core/gb.c index dd591d03..c504545a 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -491,7 +491,7 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) free(gb->audio_buffer); } gb->buffer_size = sample_rate / 25; // 40ms delay - gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer)); + gb->audio_buffer = malloc((gb->buffer_size + 1) * sizeof(*gb->audio_buffer)); gb->sample_rate = sample_rate; gb->audio_position = 0; } diff --git a/Core/gb.h b/Core/gb.h index a2a7d8b2..6be2c0ca 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -293,7 +293,7 @@ typedef struct GB_gameboy_s { uint32_t div_cycles; GB_PADDING(uint32_t, tima_cycles); GB_PADDING(uint32_t, dma_cycles); - GB_aligned_double apu_cycles; + GB_aligned_double apu_sample_cycles; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; ); diff --git a/Core/timing.c b/Core/timing.c index 726fb36d..277d0116 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -60,7 +60,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Not affected by speed boost gb->hdma_cycles += cycles; gb->display_cycles += cycles; - gb->apu_cycles += cycles; + gb->apu_sample_cycles += cycles; + gb->apu.apu_cycles += cycles; gb->cycles_since_ir_change += cycles; gb->cycles_since_input_ir_change += cycles; GB_dma_run(gb); From ff7b8a685404f81f88b9e400840cb4e1d432e62d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Sep 2016 16:55:56 +0300 Subject: [PATCH 0157/1216] Removed doubles, reorganized code a bit --- Core/apu.c | 147 +++++++++++++++++++++++++------------------------- Core/apu.h | 18 ++++--- Core/memory.c | 6 ++- 3 files changed, 86 insertions(+), 85 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index ca59ba9d..d8db53d2 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -18,10 +18,10 @@ _a < _b ? _a : _b; }) #define APU_FREQUENCY 0x80000 -static int16_t generate_square(uint64_t phase, uint32_t wave_length, int16_t amplitude, double duty) +static int16_t generate_square(uint64_t phase, uint32_t wave_length, int16_t amplitude, uint8_t duty) { if (!wave_length) return 0; - if (phase % wave_length > wave_length * duty) { + if (phase % wave_length > wave_length * duty / 8) { return amplitude; } return 0; @@ -58,64 +58,66 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) return lfsr; } +void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples) +{ + samples->left = samples->right = 0; + if (!gb->apu.global_enable) { + return; + } + + gb->io_registers[GB_IO_PCM_12] = 0; + gb->io_registers[GB_IO_PCM_34] = 0; + + { + int16_t sample = generate_square(gb->apu.wave_channels[0].phase, + gb->apu.wave_channels[0].wave_length, + gb->apu.wave_channels[0].amplitude, + gb->apu.wave_channels[0].duty); + if (gb->apu.wave_channels[0].left_on ) samples->left += sample; + if (gb->apu.wave_channels[0].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_square(gb->apu.wave_channels[1].phase, + gb->apu.wave_channels[1].wave_length, + gb->apu.wave_channels[1].amplitude, + gb->apu.wave_channels[1].duty); + if (gb->apu.wave_channels[1].left_on ) samples->left += sample; + if (gb->apu.wave_channels[1].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + if (gb->apu.wave_enable) + { + int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, + gb->apu.wave_channels[2].wave_length, + MAX_CH_AMP, + gb->apu.wave_form, + gb->apu.wave_shift); + if (gb->apu.wave_channels[2].left_on ) samples->left += sample; + if (gb->apu.wave_channels[2].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude, + gb->apu.lfsr); + if (gb->apu.wave_channels[3].left_on ) samples->left += sample; + if (gb->apu.wave_channels[3].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + samples->left *= gb->apu.left_volume; + samples->right *= gb->apu.right_volume; +} + /* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with these tests in mind. */ -void GB_apu_run_internal(GB_gameboy_t *gb, unsigned int n_cycles, GB_sample_t *samples) +static void GB_apu_run_internal(GB_gameboy_t *gb) { - while (n_cycles--) { - if (n_cycles == 0) { - samples->left = samples->right = 0; - if (!gb->apu.global_enable) { - continue; - } - - gb->io_registers[GB_IO_PCM_12] = 0; - gb->io_registers[GB_IO_PCM_34] = 0; - - { - int16_t sample = generate_square(gb->apu.wave_channels[0].phase, - gb->apu.wave_channels[0].wave_length, - gb->apu.wave_channels[0].amplitude, - gb->apu.wave_channels[0].duty); - if (gb->apu.wave_channels[0].left_on ) samples->left += sample; - if (gb->apu.wave_channels[0].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; - } - - { - int16_t sample = generate_square(gb->apu.wave_channels[1].phase, - gb->apu.wave_channels[1].wave_length, - gb->apu.wave_channels[1].amplitude, - gb->apu.wave_channels[1].duty); - if (gb->apu.wave_channels[1].left_on ) samples->left += sample; - if (gb->apu.wave_channels[1].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; - } - - if (gb->apu.wave_enable) - { - int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, - gb->apu.wave_channels[2].wave_length, - MAX_CH_AMP, - gb->apu.wave_form, - gb->apu.wave_shift); - if (gb->apu.wave_channels[2].left_on ) samples->left += sample; - if (gb->apu.wave_channels[2].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; - } - - { - int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude, - gb->apu.lfsr); - if (gb->apu.wave_channels[3].left_on ) samples->left += sample; - if (gb->apu.wave_channels[3].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; - } - - samples->left *= gb->apu.left_volume; - samples->right *= gb->apu.right_volume; - } + for (;gb->apu.apu_cycles >= CPU_FREQUENCY/APU_FREQUENCY; gb->apu.apu_cycles -= CPU_FREQUENCY/APU_FREQUENCY) { for (uint8_t i = 0; i < 4; i++) { /* Phase */ @@ -130,19 +132,19 @@ void GB_apu_run_internal(GB_gameboy_t *gb, unsigned int n_cycles, GB_sample_t *s /* 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 / APU_FREQUENCY; + gb->apu.wave_channels[i].sound_length -= 1; } 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.wave_channels[i].sound_length = i == 2? APU_FREQUENCY : APU_FREQUENCY / 4; } } } - gb->apu.envelope_step_timer += 1.0 / APU_FREQUENCY; - if (gb->apu.envelope_step_timer >= 1.0 / 64) { - gb->apu.envelope_step_timer -= 1.0 / 64; + gb->apu.envelope_step_timer += 1; + if (gb->apu.envelope_step_timer >= APU_FREQUENCY / 64) { + gb->apu.envelope_step_timer -= APU_FREQUENCY / 64; for (uint8_t 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); @@ -151,9 +153,9 @@ void GB_apu_run_internal(GB_gameboy_t *gb, unsigned int n_cycles, GB_sample_t *s } } - gb->apu.sweep_step_timer += 1.0 / APU_FREQUENCY; - if (gb->apu.sweep_step_timer >= 1.0 / 128) { - gb->apu.sweep_step_timer -= 1.0 / 128; + gb->apu.sweep_step_timer += 1; + if (gb->apu.sweep_step_timer >= APU_FREQUENCY / 128) { + gb->apu.sweep_step_timer -= APU_FREQUENCY / 128; if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) { // Convert back to GB format @@ -181,13 +183,8 @@ void GB_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; - GB_sample_t sample = {0, }; - if (gb->apu.apu_cycles >= CPU_FREQUENCY / APU_FREQUENCY) { - GB_apu_run_internal(gb, gb->apu.apu_cycles / (CPU_FREQUENCY / APU_FREQUENCY), &sample); - gb->apu.apu_cycles %= (CPU_FREQUENCY / APU_FREQUENCY); - gb->audio_buffer[gb->audio_position] = sample; - } + GB_apu_run_internal(gb); if (gb->apu_sample_cycles > ticks_per_sample) { gb->apu_sample_cycles -= ticks_per_sample; @@ -200,7 +197,7 @@ void GB_apu_run(GB_gameboy_t *gb) */ } else { - gb->audio_position++; + GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position++]); should_log_overflow = true; } } @@ -231,7 +228,7 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) void GB_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.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4; gb->apu.lfsr = 0x7FFF; gb->apu.left_volume = 1.0; gb->apu.right_volume = 1.0; @@ -281,7 +278,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { - static const double duties[] = {0.125, 0.25, 0.5, 0.75}; + static const uint8_t duties[] = {1, 2, 4, 6}; /* Values are in 1/8 */ uint8_t channel = 0; if (!gb->apu.global_enable && reg != GB_IO_NR52) { @@ -326,7 +323,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) 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; + gb->apu.wave_channels[channel].sound_length = (64 - (value & 0x3F)) * (APU_FREQUENCY / 256); if (gb->apu.wave_channels[channel].sound_length == 0) { gb->apu.wave_channels[channel].is_playing = false; } @@ -374,7 +371,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channels[2].is_playing &= gb->apu.wave_enable; break; case GB_IO_NR31: - gb->apu.wave_channels[2].sound_length = (256 - value) / 256.0; + gb->apu.wave_channels[2].sound_length = (256 - value) * (APU_FREQUENCY / 256); if (gb->apu.wave_channels[2].sound_length == 0) { gb->apu.wave_channels[2].is_playing = false; } diff --git a/Core/apu.h b/Core/apu.h index db717116..c02e1919 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -19,21 +19,22 @@ typedef struct } GB_sample_t; /* Not all used on all channels */ +/* All lengths are in APU ticks */ typedef struct { - uint64_t phase; + uint32_t phase; uint32_t wave_length; - GB_aligned_double duty; - GB_aligned_double sound_length; /* In seconds */ + int32_t sound_length; + bool stop_on_length; + uint8_t duty; int16_t amplitude; int16_t start_amplitude; - bool stop_on_length; uint8_t envelope_steps; uint8_t cur_envelope_steps; - signed int envelope_direction; + int8_t envelope_direction; uint8_t sweep_steps; uint8_t cur_sweep_steps; - signed int sweep_direction; + int8_t sweep_direction; uint8_t sweep_shift; bool is_playing; uint16_t NRX3_X4_temp; @@ -45,8 +46,8 @@ typedef struct { uint8_t apu_cycles; bool global_enable; - GB_aligned_double envelope_step_timer; /* In seconds */ - GB_aligned_double sweep_step_timer; /* In seconds */ + uint32_t envelope_step_timer; + uint32_t sweep_step_timer; int8_t wave_form[32]; uint8_t wave_shift; bool wave_enable; @@ -60,6 +61,7 @@ typedef struct void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count); void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); +void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples); void GB_apu_init(GB_gameboy_t *gb); void GB_apu_run(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index 8faa0dff..442f7c73 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -139,12 +139,14 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE; + case GB_IO_PCM_12: + case GB_IO_PCM_34: + GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position]); 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: + if (!gb->is_cgb) { return 0xFF; } From eefc998e43bdf4d7bc301b48a58b777d89c14917 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Sep 2016 17:06:35 +0300 Subject: [PATCH 0158/1216] Removed for loop in APU --- Core/apu.c | 95 +++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index d8db53d2..b6bf9f9b 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -117,63 +117,62 @@ void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *sampl static void GB_apu_run_internal(GB_gameboy_t *gb) { - for (;gb->apu.apu_cycles >= CPU_FREQUENCY/APU_FREQUENCY; gb->apu.apu_cycles -= CPU_FREQUENCY/APU_FREQUENCY) { + uint32_t steps = gb->apu.apu_cycles / (CPU_FREQUENCY/APU_FREQUENCY); + gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY); + for (uint8_t i = 0; i < 4; i++) { + /* Phase */ + gb->apu.wave_channels[i].phase += steps; + while (gb->apu.wave_channels[i].wave_length && gb->apu.wave_channels[i].phase >= gb->apu.wave_channels[i].wave_length) { + if (i == 3) { + gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit); + } + gb->apu.wave_channels[i].phase -= gb->apu.wave_channels[i].wave_length; + } + /* 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 -= steps; + } + 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? APU_FREQUENCY : APU_FREQUENCY / 4; + } + } + } + + gb->apu.envelope_step_timer += steps; + while (gb->apu.envelope_step_timer >= APU_FREQUENCY / 64) { + gb->apu.envelope_step_timer -= APU_FREQUENCY / 64; for (uint8_t i = 0; i < 4; i++) { - /* Phase */ - gb->apu.wave_channels[i].phase++; - if (gb->apu.wave_channels[i].wave_length && gb->apu.wave_channels[i].phase >= gb->apu.wave_channels[i].wave_length) { - if (i == 3) { - gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit); - } - - gb->apu.wave_channels[i].phase %= gb->apu.wave_channels[i].wave_length; - } - /* 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; - } - 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? APU_FREQUENCY : APU_FREQUENCY / 4; - } + 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.envelope_step_timer += 1; - if (gb->apu.envelope_step_timer >= APU_FREQUENCY / 64) { - gb->apu.envelope_step_timer -= APU_FREQUENCY / 64; - for (uint8_t 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 += steps; + while (gb->apu.sweep_step_timer >= APU_FREQUENCY / 128) { + gb->apu.sweep_step_timer -= APU_FREQUENCY / 128; + if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) { + + // Convert back to GB format + uint16_t temp = 2048 - gb->apu.wave_channels[0].wave_length / (APU_FREQUENCY / 131072); + + // 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; } - } - gb->apu.sweep_step_timer += 1; - if (gb->apu.sweep_step_timer >= APU_FREQUENCY / 128) { - gb->apu.sweep_step_timer -= APU_FREQUENCY / 128; - if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) { - - // Convert back to GB format - uint16_t temp = 2048 - gb->apu.wave_channels[0].wave_length / (APU_FREQUENCY / 131072); - - // 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].wave_length = (2048 - temp) * (APU_FREQUENCY / 131072); + // Back to frequency + gb->apu.wave_channels[0].wave_length = (2048 - temp) * (APU_FREQUENCY / 131072); - gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps; - } + gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps; } } } From 594aea2d5a48fe621327b9a9ba4d9ab68250fdf1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Sep 2016 17:33:48 +0300 Subject: [PATCH 0159/1216] APU is now being run lazily --- Core/apu.c | 116 ++++++++++++++++++++++++++------------------------ Core/gb.c | 2 +- Core/memory.c | 5 ++- 3 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index b6bf9f9b..ae15c56a 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -58,60 +58,6 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) return lfsr; } -void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples) -{ - samples->left = samples->right = 0; - if (!gb->apu.global_enable) { - return; - } - - gb->io_registers[GB_IO_PCM_12] = 0; - gb->io_registers[GB_IO_PCM_34] = 0; - - { - int16_t sample = generate_square(gb->apu.wave_channels[0].phase, - gb->apu.wave_channels[0].wave_length, - gb->apu.wave_channels[0].amplitude, - gb->apu.wave_channels[0].duty); - if (gb->apu.wave_channels[0].left_on ) samples->left += sample; - if (gb->apu.wave_channels[0].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; - } - - { - int16_t sample = generate_square(gb->apu.wave_channels[1].phase, - gb->apu.wave_channels[1].wave_length, - gb->apu.wave_channels[1].amplitude, - gb->apu.wave_channels[1].duty); - if (gb->apu.wave_channels[1].left_on ) samples->left += sample; - if (gb->apu.wave_channels[1].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; - } - - if (gb->apu.wave_enable) - { - int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, - gb->apu.wave_channels[2].wave_length, - MAX_CH_AMP, - gb->apu.wave_form, - gb->apu.wave_shift); - if (gb->apu.wave_channels[2].left_on ) samples->left += sample; - if (gb->apu.wave_channels[2].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; - } - - { - int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude, - gb->apu.lfsr); - if (gb->apu.wave_channels[3].left_on ) samples->left += sample; - if (gb->apu.wave_channels[3].right_on) samples->right += sample; - gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; - } - - samples->left *= gb->apu.left_volume; - samples->right *= gb->apu.right_volume; -} - /* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with these tests in mind. */ @@ -177,14 +123,69 @@ static void GB_apu_run_internal(GB_gameboy_t *gb) } } +void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples) +{ + GB_apu_run_internal(gb); + + samples->left = samples->right = 0; + if (!gb->apu.global_enable) { + return; + } + + gb->io_registers[GB_IO_PCM_12] = 0; + gb->io_registers[GB_IO_PCM_34] = 0; + + { + int16_t sample = generate_square(gb->apu.wave_channels[0].phase, + gb->apu.wave_channels[0].wave_length, + gb->apu.wave_channels[0].amplitude, + gb->apu.wave_channels[0].duty); + if (gb->apu.wave_channels[0].left_on ) samples->left += sample; + if (gb->apu.wave_channels[0].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_square(gb->apu.wave_channels[1].phase, + gb->apu.wave_channels[1].wave_length, + gb->apu.wave_channels[1].amplitude, + gb->apu.wave_channels[1].duty); + if (gb->apu.wave_channels[1].left_on ) samples->left += sample; + if (gb->apu.wave_channels[1].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + if (gb->apu.wave_enable) + { + int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, + gb->apu.wave_channels[2].wave_length, + MAX_CH_AMP, + gb->apu.wave_form, + gb->apu.wave_shift); + if (gb->apu.wave_channels[2].left_on ) samples->left += sample; + if (gb->apu.wave_channels[2].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude, + gb->apu.lfsr); + if (gb->apu.wave_channels[3].left_on ) samples->left += sample; + if (gb->apu.wave_channels[3].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + samples->left *= gb->apu.left_volume; + samples->right *= gb->apu.right_volume; +} + void GB_apu_run(GB_gameboy_t *gb) { + if (gb->sample_rate == 0) return; static bool should_log_overflow = true; while (gb->audio_copy_in_progress); double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate; - GB_apu_run_internal(gb); - if (gb->apu_sample_cycles > ticks_per_sample) { gb->apu_sample_cycles -= ticks_per_sample; if (gb->audio_position == gb->buffer_size) { @@ -239,6 +240,7 @@ void GB_apu_init(GB_gameboy_t *gb) uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) { /* Todo: what happens when reading from the wave from while it's playing? */ + GB_apu_run_internal(gb); if (reg == GB_IO_NR52) { uint8_t value = 0; @@ -277,6 +279,8 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { + GB_apu_run_internal(gb); + static const uint8_t duties[] = {1, 2, 4, 6}; /* Values are in 1/8 */ uint8_t channel = 0; diff --git a/Core/gb.c b/Core/gb.c index c504545a..dd591d03 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -491,7 +491,7 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) free(gb->audio_buffer); } gb->buffer_size = sample_rate / 25; // 40ms delay - gb->audio_buffer = malloc((gb->buffer_size + 1) * sizeof(*gb->audio_buffer)); + gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer)); gb->sample_rate = sample_rate; gb->audio_position = 0; } diff --git a/Core/memory.c b/Core/memory.c index 442f7c73..03d043fe 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -141,7 +141,10 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_PCM_12: case GB_IO_PCM_34: - GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position]); + { + GB_sample_t dummy; + GB_apu_get_samples_and_update_pcm_regs(gb, &dummy); + } case GB_IO_HDMA1: case GB_IO_HDMA2: case GB_IO_HDMA3: From f274cbc2ec638a4e488c8411fe93fd1d446be30f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Sep 2016 17:40:10 +0300 Subject: [PATCH 0160/1216] Read wave form while playing --- Core/apu.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index ae15c56a..a3598536 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -239,7 +239,6 @@ void GB_apu_init(GB_gameboy_t *gb) uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) { - /* Todo: what happens when reading from the wave from while it's playing? */ GB_apu_run_internal(gb); if (reg == GB_IO_NR52) { @@ -271,7 +270,11 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) }; if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.wave_channels[2].is_playing) { - return (uint8_t)((gb->display_cycles * 22695477 * reg) >> 8); // Semi-random but deterministic + if (gb->apu.wave_channels[2].wave_length == 0) { + return gb->apu.wave_form[0]; + } + gb->apu.wave_channels[2].phase %= gb->apu.wave_channels[2].wave_length; + return gb->apu.wave_form[(int)(gb->apu.wave_channels[2].phase * 32 / gb->apu.wave_channels[2].wave_length)]; } return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; From 4beb9464683814d5990da794721bb5af65d41712 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Sep 2016 22:49:35 +0300 Subject: [PATCH 0161/1216] Slight APU optimization --- Core/apu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index a3598536..c1d62d3f 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -64,6 +64,8 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) static void GB_apu_run_internal(GB_gameboy_t *gb) { uint32_t steps = gb->apu.apu_cycles / (CPU_FREQUENCY/APU_FREQUENCY); + if (!steps) return; + gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY); for (uint8_t i = 0; i < 4; i++) { /* Phase */ From 71d4ba21f2eed560dda65e1623648d7143a516d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Sep 2016 11:58:31 +0300 Subject: [PATCH 0162/1216] Added a tick-counting debugger command --- Core/debugger.c | 16 ++++++++++++++++ Core/gb.h | 3 +++ Core/timing.c | 2 ++ 3 files changed, 21 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index ca0c3675..a689ea7a 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1185,6 +1185,21 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, const debugger_command_ return true; } +static bool ticks(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +{ + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, "Ticks: %lu. (Resetting)\n", gb->debugger_ticks); + gb->debugger_ticks = 0; + + return true; +} + static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command); #define HELP_NEWLINE "\n " @@ -1198,6 +1213,7 @@ static const debugger_command_t commands[] = { {"backtrace", 2, backtrace, "Display the current call stack"}, {"bt", 2, }, /* Alias */ {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected. (Experimental)"}, + {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used. "}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ diff --git a/Core/gb.h b/Core/gb.h index a2a7d8b2..b1326f8e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -409,6 +409,9 @@ typedef struct GB_gameboy_s { GB_symbol_map_t *bank_symbols[0x200]; GB_reversed_symbol_map_t reversed_symbol_map; + /* Ticks command */ + unsigned long debugger_ticks; + /* Misc */ bool turbo; bool turbo_dont_skip; diff --git a/Core/timing.c b/Core/timing.c index 726fb36d..b31826b1 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -53,6 +53,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) } } + gb->debugger_ticks += cycles; + if (gb->cgb_double_speed) { cycles >>=1; } From f4c5cf20bc2b0643548cc918333738bac2b9c5de Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Sep 2016 13:27:32 +0300 Subject: [PATCH 0163/1216] Cleanup of ret_cc --- Core/z80_cpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index e6733b86..cf72e9bf 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -731,7 +731,8 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify timing */ - if (condition_code(gb, GB_read_memory(gb, gb->pc++))) { + gb->pc++; + if (condition_code(gb, opcode)) { GB_debugger_ret_hook(gb); GB_advance_cycles(gb, 8); gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); From fe51805ed75d9d290cff6ff09aae0dcd0589a89e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Sep 2016 18:24:38 +0300 Subject: [PATCH 0164/1216] Incorrect constant name --- Core/display.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 4c9874fb..b18bd1a6 100755 --- a/Core/display.c +++ b/Core/display.c @@ -278,10 +278,10 @@ void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt */ -#define MODE2_LENGTH 80 -#define MODE3_LENGTH 172 -#define MODE1_LENGTH 204 -#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE1_LENGTH) // = 456 +#define MODE2_LENGTH (80) +#define MODE3_LENGTH (172) +#define MODE0_LENGTH (204) +#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 void GB_display_run(GB_gameboy_t *gb) { From ed3135893428b17cd44cf6e3b8cc6529798fd703 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 18 Sep 2016 21:00:05 +0300 Subject: [PATCH 0165/1216] Forgot to emulate LCDC bit 0! --- Core/display.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index b18bd1a6..8f12f231 100755 --- a/Core/display.c +++ b/Core/display.c @@ -45,7 +45,22 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) uint8_t 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 */ + bool window_enabled = (gb->io_registers[GB_IO_LCDC] & 0x20); + bool bg_enabled = true; + bool bg_behind = false; + if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if (gb->cgb_mode) { + bg_behind = true; + } + else if (gb->is_cgb) { /* CGB in DMG mode*/ + bg_enabled = window_enabled = false; + } + else { + /* DMG */ + bg_enabled = false; + } + } + if (gb->effective_window_enabled && window_enabled) { /* Window Enabled */ if (y >= gb->effective_window_y && x + 7 >= gb->io_registers[GB_IO_WX]) { in_window = true; } @@ -118,7 +133,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) } if (attributes & 0x80) { - priority = true; + priority = !bg_behind && bg_enabled; } if (!priority && sprite_pixel) { @@ -129,6 +144,10 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return gb->sprite_palletes_rgb[sprite_palette * 4 + sprite_pixel]; } + if (!bg_enabled) { + return gb->background_palletes_rgb[0]; + } + if (gb->io_registers[GB_IO_LCDC] & 0x10) { tile_address = tile * 0x10; } From 1a3a96762b4e226fee0a0eff1f410fa92fb599c1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 18 Sep 2016 23:50:04 +0300 Subject: [PATCH 0166/1216] CPU cleanup --- Core/z80_cpu.c | 108 +++++++------------------------------------------ 1 file changed, 15 insertions(+), 93 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index cf72e9bf..2ea16d37 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -19,7 +19,6 @@ static void ill(GB_gameboy_t *gb, uint8_t opcode) static void nop(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; } static void stop(GB_gameboy_t *gb, uint8_t opcode) @@ -33,7 +32,6 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) else { gb->stopped = true; } - gb->pc++; } /* Operand naming conventions for functions: @@ -53,7 +51,6 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) uint16_t value; GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); value |= GB_read_memory(gb, gb->pc++) << 8; @@ -66,7 +63,6 @@ static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; - gb->pc++; GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 4); } @@ -76,7 +72,6 @@ static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 8); register_id = (opcode >> 4) + 1; - gb->pc++; gb->registers[register_id]++; } @@ -84,7 +79,6 @@ static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; GB_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); @@ -101,7 +95,6 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; GB_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); @@ -120,7 +113,6 @@ static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; GB_advance_cycles(gb, 4); - gb->pc++; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] &= 0xFF; gb->registers[register_id] |= GB_read_memory(gb, gb->pc++) << 8; @@ -132,7 +124,6 @@ static void rlca(GB_gameboy_t *gb, uint8_t opcode) bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; GB_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; @@ -145,7 +136,6 @@ static void rla(GB_gameboy_t *gb, uint8_t opcode) bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; GB_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; @@ -160,7 +150,6 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) /* Todo: Verify order is correct */ uint16_t addr; GB_advance_cycles(gb, 4); - gb->pc++; addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8; @@ -177,7 +166,6 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) uint16_t rr; uint8_t register_id; GB_advance_cycles(gb, 8); - gb->pc++; register_id = (opcode >> 4) + 1; rr = gb->registers[register_id]; gb->registers[GB_REGISTER_HL] = hl + rr; @@ -198,7 +186,6 @@ static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = (opcode >> 4) + 1; GB_advance_cycles(gb, 4); - gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[register_id]) << 8; GB_advance_cycles(gb, 4); @@ -209,7 +196,6 @@ static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 8); register_id = (opcode >> 4) + 1; - gb->pc++; gb->registers[register_id]--; } @@ -219,7 +205,6 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; - gb->pc++; value = (gb->registers[register_id] & 0xFF) + 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; @@ -240,7 +225,6 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; - gb->pc++; value = (gb->registers[register_id] & 0xFF) - 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; @@ -262,7 +246,6 @@ static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; - gb->pc++; gb->registers[register_id] &= 0xFF00; gb->registers[register_id] |= GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); @@ -273,7 +256,6 @@ static void rrca(GB_gameboy_t *gb, uint8_t opcode) bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; GB_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; @@ -286,7 +268,6 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode) bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; GB_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; @@ -300,7 +281,6 @@ static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify cycles are not 8 and 4 instead */ GB_advance_cycles(gb, 4); - gb->pc++; gb->pc += (int8_t) GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 8); } @@ -323,7 +303,6 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { - gb->pc++; if (condition_code(gb, opcode)) { GB_advance_cycles(gb, 4); gb->pc += (int8_t)GB_read_memory(gb, gb->pc++); @@ -340,7 +319,6 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ GB_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; @@ -392,7 +370,6 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) static void cpl(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; gb->registers[GB_REGISTER_AF] ^= 0xFF00; gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG; } @@ -400,7 +377,6 @@ static void cpl(GB_gameboy_t *gb, uint8_t opcode) static void scf(GB_gameboy_t *gb, uint8_t opcode) { GB_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); } @@ -408,7 +384,6 @@ static void scf(GB_gameboy_t *gb, uint8_t opcode) static void ccf(GB_gameboy_t *gb, uint8_t opcode) { GB_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); } @@ -416,7 +391,6 @@ static void ccf(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 4); } @@ -424,7 +398,6 @@ static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 4); } @@ -432,7 +405,6 @@ static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; GB_advance_cycles(gb, 4); @@ -441,7 +413,6 @@ static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; GB_advance_cycles(gb, 4); @@ -451,7 +422,6 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); @@ -471,7 +441,6 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); @@ -491,7 +460,6 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; uint8_t data = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], data); @@ -553,7 +521,6 @@ static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t dst_low; uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; dst_register_id = ((opcode >> 4) + 1) & 3; dst_low = opcode & 8; @@ -586,7 +553,6 @@ static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_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; @@ -605,7 +571,6 @@ static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; GB_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; @@ -626,7 +591,6 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_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; @@ -645,7 +609,6 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; GB_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; @@ -666,7 +629,6 @@ static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_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; @@ -679,7 +641,6 @@ static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_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; @@ -692,7 +653,6 @@ static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_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; @@ -705,7 +665,6 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_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; @@ -725,13 +684,11 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); gb->halted = true; - gb->pc++; } static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify timing */ - gb->pc++; if (condition_code(gb, opcode)) { GB_debugger_ret_hook(gb); GB_advance_cycles(gb, 8); @@ -751,7 +708,6 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 3; - gb->pc++; gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); GB_advance_cycles(gb, 4); gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; @@ -762,7 +718,6 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { - gb->pc++; if (condition_code(gb, opcode)) { GB_advance_cycles(gb, 4); uint16_t addr = GB_read_memory(gb, gb->pc); @@ -780,7 +735,6 @@ static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) { - gb->pc++; GB_advance_cycles(gb, 4); uint16_t addr = GB_read_memory(gb, gb->pc); GB_advance_cycles(gb, 4); @@ -790,18 +744,17 @@ static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t call_addr = gb->pc; - gb->pc++; + uint16_t call_addr = gb->pc - 1; if (condition_code(gb, opcode)) { GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_SP] -= 2; - uint16_t addr = GB_read_memory(gb, gb->pc); + uint16_t addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); - addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + addr |= (GB_read_memory(gb, gb->pc++) << 8); GB_advance_cycles(gb, 8); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = addr; @@ -817,7 +770,6 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; GB_advance_cycles(gb, 8); - gb->pc++; register_id = ((opcode >> 4) + 1) & 3; gb->registers[GB_REGISTER_SP] -= 2; GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); @@ -830,7 +782,6 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -850,7 +801,6 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -872,7 +822,6 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -892,7 +841,6 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -914,7 +862,6 @@ static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -928,7 +875,6 @@ static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -942,7 +888,6 @@ static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -956,7 +901,6 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - gb->pc++; value = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); a = gb->registers[GB_REGISTER_AF] >> 8; @@ -975,12 +919,12 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t call_addr = gb->pc; + uint16_t call_addr = gb->pc - 1; GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 1) >> 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 1) & 0xFF); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = opcode ^ 0xC7; GB_debugger_call_hook(gb, call_addr); @@ -1005,17 +949,16 @@ static void reti(GB_gameboy_t *gb, uint8_t opcode) static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t call_addr = gb->pc; - gb->pc++; + uint16_t call_addr = gb->pc - 1; GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_SP] -= 2; - uint16_t addr = GB_read_memory(gb, gb->pc); + uint16_t addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); - addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + addr |= (GB_read_memory(gb, gb->pc++) << 8); GB_advance_cycles(gb, 8); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc + 2) >> 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc + 2) & 0xFF); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); @@ -1024,7 +967,6 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; uint8_t temp = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); @@ -1035,7 +977,6 @@ static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->pc++; uint8_t temp = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + temp) << 8; @@ -1045,7 +986,6 @@ static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; GB_write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 4); } @@ -1054,7 +994,6 @@ static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->pc++; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; GB_advance_cycles(gb, 4); } @@ -1064,7 +1003,6 @@ static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) int16_t offset; uint16_t sp = gb->registers[GB_REGISTER_SP]; GB_advance_cycles(gb, 4); - gb->pc++; offset = (int8_t) GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 12); gb->registers[GB_REGISTER_SP] += offset; @@ -1091,7 +1029,6 @@ static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; GB_advance_cycles(gb, 4); - gb->pc++; addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8; @@ -1105,7 +1042,6 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) uint16_t addr; GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->pc++; addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8 ; @@ -1117,7 +1053,6 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) static void di(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - gb->pc++; /* di is delayed in CGB */ if (!gb->is_cgb) { @@ -1132,7 +1067,6 @@ static void ei(GB_gameboy_t *gb, uint8_t opcode) { /* ei is actually "disable interrupts for one instruction, then enable them". */ GB_advance_cycles(gb, 4); - gb->pc++; gb->ime = false; gb->ime_toggle = true; } @@ -1141,7 +1075,6 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; GB_advance_cycles(gb, 4); - gb->pc++; gb->registers[GB_REGISTER_AF] &= 0xFF00; offset = (int8_t) GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 8); @@ -1159,7 +1092,6 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 8); - gb->pc++; gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; } @@ -1168,7 +1100,6 @@ static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1186,7 +1117,6 @@ static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x01) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1206,7 +1136,6 @@ static void rl_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool bit7; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; bit7 = (value & 0x80) != 0; @@ -1229,7 +1158,6 @@ static void rr_r(GB_gameboy_t *gb, uint8_t opcode) bool bit1; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; bit1 = (value & 0x1) != 0; @@ -1250,7 +1178,6 @@ static void sla_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool carry; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1268,7 +1195,6 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t bit7; uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); bit7 = value & 0x80; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1286,7 +1212,6 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 1)); @@ -1302,7 +1227,6 @@ static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 4) | (value << 4)); @@ -1316,7 +1240,6 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; uint8_t bit; GB_advance_cycles(gb, 4); - gb->pc++; value = get_src_value(gb, opcode); bit = 1 << ((opcode >> 3) & 7); if ((opcode & 0xC0) == 0x40) { /* Bit */ @@ -1337,7 +1260,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - opcode = GB_read_memory(gb, ++gb->pc); + opcode = GB_read_memory(gb, gb->pc++); switch (opcode >> 3) { case 0: rlc_r(gb, opcode); @@ -1435,12 +1358,11 @@ void GB_cpu_run(GB_gameboy_t *gb) 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 && !gb->stopped) { - uint8_t opcode = GB_read_memory(gb, gb->pc); + uint8_t opcode = GB_read_memory(gb, gb->pc++); opcodes[opcode](gb, opcode); } else { From f04928432487bfdea7085e21861f624e531143d3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Sep 2016 01:22:21 +0300 Subject: [PATCH 0167/1216] Emulate the HALT bug on a DMG --- Core/z80_cpu.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 2ea16d37..6db90075 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1333,7 +1333,10 @@ void GB_cpu_run(GB_gameboy_t *gb) { gb->vblank_just_occured = false; bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; + bool halt_bug = false; + if (interrupt) { + halt_bug = gb->halted && !gb->is_cgb; /* Todo: Does this bug happen on a CGB? */ gb->halted = false; } @@ -1363,6 +1366,9 @@ void GB_cpu_run(GB_gameboy_t *gb) } else if(!gb->halted && !gb->stopped) { uint8_t opcode = GB_read_memory(gb, gb->pc++); + if (halt_bug) { + gb->pc--; + } opcodes[opcode](gb, opcode); } else { From a026f8b26da517b605c85fef96adf02a16d0f2cd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Sep 2016 01:45:02 +0300 Subject: [PATCH 0168/1216] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0f632ab2..bee6392a 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ Features common to both Cocoa and SDL versions: * Emulates LCD timing effects, supporting the Demotronic trick, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos * Accurate instruction and memory timings * Real time clock emulation + * High quality 96KHz audio + * Battery save support + * Save states Features currently supported only with the Cocoa version: * Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars * Retina display support, allowing a wider range of scaling factors without artifacts - * High quality 96KHz audio - * Battery save support - * Save states * Optional frame blending * Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x) From 37e895352f12af9b80cf7e2acd475bd8ac16c01d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Sep 2016 19:58:30 +0300 Subject: [PATCH 0169/1216] Volumes no longer doubles --- Core/apu.c | 12 ++++++------ Core/apu.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index c1d62d3f..9bedc5e9 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -177,8 +177,8 @@ void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *sampl gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; } - samples->left *= gb->apu.left_volume; - samples->right *= gb->apu.right_volume; + samples->left = (int) samples->left * gb->apu.left_volume / 7; + samples->right = (int) samples->right * gb->apu.right_volume / 7; } void GB_apu_run(GB_gameboy_t *gb) @@ -232,8 +232,8 @@ void GB_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 = 4; gb->apu.lfsr = 0x7FFF; - gb->apu.left_volume = 1.0; - gb->apu.right_volume = 1.0; + gb->apu.left_volume = 7; + gb->apu.right_volume = 7; for (int i = 0; i < 4; i++) { gb->apu.wave_channels[i].left_on = gb->apu.wave_channels[i].right_on = 1; } @@ -410,8 +410,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) break; case GB_IO_NR50: - gb->apu.left_volume = (value & 7) / 7.0; - gb->apu.right_volume = ((value >> 4) & 7) / 7.0; + gb->apu.left_volume = (value & 7); + gb->apu.right_volume = ((value >> 4) & 7); break; case GB_IO_NR51: diff --git a/Core/apu.h b/Core/apu.h index c02e1919..ffd10e9a 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -53,8 +53,8 @@ typedef struct bool wave_enable; uint16_t lfsr; bool lfsr_7_bit; - double left_volume; - double right_volume; + uint8_t left_volume; + uint8_t right_volume; GB_apu_channel_t wave_channels[4]; } GB_apu_t; From f46414b7b1a1e562e5202fbdbf73ce0db52c025e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Sep 2016 20:04:38 +0300 Subject: [PATCH 0170/1216] Updated struct version :( --- Core/gb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/gb.h b/Core/gb.h index 6be2c0ca..bc451444 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -9,7 +9,7 @@ #include "symbol_hash.h" -#define GB_STRUCT_VERSION 9 +#define GB_STRUCT_VERSION 10 enum { GB_REGISTER_AF, From 97eb3fe209ac2539b8a8ea2547018615801c01d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Sep 2016 22:59:00 +0300 Subject: [PATCH 0171/1216] Detect games stuck on blank screens --- Tester/main.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 52d797cb..94450a4d 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -89,15 +89,29 @@ static void vblank(GB_gameboy_t *gb) } } - if (frames == test_length) { - FILE *f = fopen(bmp_filename, "wb"); - fwrite(&bmp_header, 1, sizeof(bmp_header), f); - fwrite(&bitmap, 1, sizeof(bitmap), f); - fclose(f); - if (!gb->boot_rom_finished) { - GB_log(gb, "Boot ROM did not finish.\n"); + if (frames >= test_length ) { + bool is_screen_blank = false; + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { /* LCD is off */ + is_screen_blank = true; + } + else if(!gb->cgb_mode) { /* BG can't be disabled in CGB mode */ + is_screen_blank =!(gb->io_registers[GB_IO_LCDC] & (gb->is_cgb? 3 : 0x23)); + } + + /* Let the test run for an extra 8 frames if the screen is off/disabled */ + if (!is_screen_blank || frames >= test_length + 8) { + FILE *f = fopen(bmp_filename, "wb"); + fwrite(&bmp_header, 1, sizeof(bmp_header), f); + fwrite(&bitmap, 1, sizeof(bitmap), f); + fclose(f); + if (!gb->boot_rom_finished) { + GB_log(gb, "Boot ROM did not finish.\n"); + } + if (is_screen_blank) { + GB_log(gb, "Game probably stuck with blank screen. \n"); + } + running = false; } - running = false; } else if (frames == test_length - 1) { gb->disable_rendering = false; From 6f2b36cacb90a9cc0ec4acb1fa8c8c4c3257b9e7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Sep 2016 22:59:25 +0300 Subject: [PATCH 0172/1216] The HALT bug also happens on CGBs, regardless of DMG mode. --- Core/z80_cpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 6db90075..b664aced 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1336,7 +1336,8 @@ void GB_cpu_run(GB_gameboy_t *gb) bool halt_bug = false; if (interrupt) { - halt_bug = gb->halted && !gb->is_cgb; /* Todo: Does this bug happen on a CGB? */ + /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ + halt_bug = gb->halted; gb->halted = false; } From 252439c1af49807826ab58402f84f0cfbcbc9431 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 21 Sep 2016 01:59:43 +0300 Subject: [PATCH 0173/1216] Fixed a deadlocking race condition that might happen when reading APU memory in the hex viewer --- Core/apu.c | 5 ++++- Core/gb.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 9bedc5e9..4743fcb7 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -63,8 +63,9 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) static void GB_apu_run_internal(GB_gameboy_t *gb) { + while (!__sync_bool_compare_and_swap(&gb->apu_lock, false, true)); uint32_t steps = gb->apu.apu_cycles / (CPU_FREQUENCY/APU_FREQUENCY); - if (!steps) return; + if (!steps) goto exit; gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY); for (uint8_t i = 0; i < 4; i++) { @@ -123,6 +124,8 @@ static void GB_apu_run_internal(GB_gameboy_t *gb) gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps; } } +exit: + gb->apu_lock = false; } void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples) diff --git a/Core/gb.h b/Core/gb.h index 22ee9fdd..9123dc31 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -363,6 +363,7 @@ typedef struct GB_gameboy_s { unsigned int audio_position; bool audio_stream_started; // detects first copy request to minimize lag volatile bool audio_copy_in_progress; + volatile bool apu_lock; /* Callbacks */ void *user_data; From bc3cab7dfac247c9dff9f460ade2afeeee4b274d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 21 Sep 2016 02:15:02 +0300 Subject: [PATCH 0174/1216] Forbid pressing two opposing direction keys. Fixes Pocket Bomberman (U). --- Core/joypad.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Core/joypad.c b/Core/joypad.c index 89bcba84..bf3e120f 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -22,6 +22,13 @@ void GB_update_joyp(GB_gameboy_t *gb) for (uint8_t i = 0; i < 4; i++) { gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i; } + /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ + if (!(gb->io_registers[GB_IO_JOYP] & 1)) { + gb->io_registers[GB_IO_JOYP] |= 2; + } + if (!(gb->io_registers[GB_IO_JOYP] & 4)) { + gb->io_registers[GB_IO_JOYP] |= 8; + } break; case 1: From 09917053798839a251acbd91a4fb097d74aa2a30 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 22 Sep 2016 01:51:09 +0300 Subject: [PATCH 0175/1216] Refined HALT bug behavior, fixed Robocop --- Core/gb.h | 1 + Core/z80_cpu.c | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index b1326f8e..e8d929f6 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -226,6 +226,7 @@ typedef struct GB_gameboy_s { bool stopped; bool boot_rom_finished; bool ime_toggle; /* ei (and di in CGB) have delayed effects.*/ + bool halt_bug; /* Misc state*/ /* IR */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index b664aced..c5a35280 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -684,6 +684,11 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); gb->halted = true; + /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ + if (!gb->ime && (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0) { + gb->halted = false; + gb->halt_bug = true; + } } static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) @@ -1332,12 +1337,9 @@ static GB_opcode_t *opcodes[256] = { void GB_cpu_run(GB_gameboy_t *gb) { gb->vblank_just_occured = false; - bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; - bool halt_bug = false; + bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; if (interrupt) { - /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ - halt_bug = gb->halted; gb->halted = false; } @@ -1354,7 +1356,7 @@ void GB_cpu_run(GB_gameboy_t *gb) if (effecitve_ime && interrupt) { uint8_t interrupt_bit = 0; - uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF]; + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; while (!(interrupt_queue & 1)) { interrupt_queue >>= 1; interrupt_bit++; @@ -1367,8 +1369,9 @@ void GB_cpu_run(GB_gameboy_t *gb) } else if(!gb->halted && !gb->stopped) { uint8_t opcode = GB_read_memory(gb, gb->pc++); - if (halt_bug) { + if (gb->halt_bug) { gb->pc--; + gb->halt_bug = false; } opcodes[opcode](gb, opcode); } From 42c01a21b21a4c8984e2cfe7922348739e7f33bf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 22 Sep 2016 01:52:40 +0300 Subject: [PATCH 0176/1216] Fixed crash when accessing MBC RAM on a cartridge that "has RAM", but it's 0-sized. --- Core/memory.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 8faa0dff..cef9559f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -74,7 +74,7 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { - if (!gb->mbc_ram_enable) return 0xFF; + if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return 0xFF; if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ @@ -314,7 +314,7 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (!gb->mbc_ram_enable) return; + if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return; if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ From 52ed2ca55e02cf3e7386d849d900e6ab0a3c5307 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Sep 2016 18:30:07 +0300 Subject: [PATCH 0177/1216] Corrected BG enable's behavior (Fixes visual glitch with Krusty's Funhouse) --- Core/display.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Core/display.c b/Core/display.c index 8f12f231..580ea902 100755 --- a/Core/display.c +++ b/Core/display.c @@ -144,31 +144,29 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return gb->sprite_palletes_rgb[sprite_palette * 4 + sprite_pixel]; } - if (!bg_enabled) { - return gb->background_palletes_rgb[0]; - } + if (bg_enabled) { + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + if (attributes & 0x8) { + tile_address += 0x2000; + } - if (gb->io_registers[GB_IO_LCDC] & 0x10) { - tile_address = tile * 0x10; - } - else { - tile_address = (int8_t) tile * 0x10 + 0x1000; - } - if (attributes & 0x8) { - tile_address += 0x2000; - } + if (attributes & 0x20) { + x = ~x; + } - if (attributes & 0x20) { - x = ~x; - } + if (attributes & 0x40) { + y = ~y; + } - 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 ); } - 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; From 3ac3eccebb613f9ce5e222e1aadf96234e552861 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 01:09:17 +0300 Subject: [PATCH 0178/1216] Bugfix: Multiple watchpoints did not work correctly and conflicted with breakpoints --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index a689ea7a..c4b5ef55 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1406,7 +1406,7 @@ void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) { - uint16_t index = find_breakpoint(gb, addr); + uint16_t index = find_watchpoint(gb, addr); uint32_t key = WP_KEY(addr); if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { From 5565c096c7f0028b2f6680a7576e9ec88df38e7a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 01:09:44 +0300 Subject: [PATCH 0179/1216] Misc optimizations, especially for the tester --- Core/debugger.c | 2 ++ Core/display.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index c4b5ef55..418a2544 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1393,6 +1393,7 @@ static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, u void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (gb->debug_stopped) return; + if (!gb->n_watchpoints) return; /* Try any-bank breakpoint */ value_t full_addr = (VALUE_16(addr)); @@ -1438,6 +1439,7 @@ static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) { if (gb->debug_stopped) return; + if (!gb->n_watchpoints) return; /* Try any-bank breakpoint */ value_t full_addr = (VALUE_16(addr)); diff --git a/Core/display.c b/Core/display.c index 580ea902..b5ca8474 100755 --- a/Core/display.c +++ b/Core/display.c @@ -216,9 +216,9 @@ static void nsleep(uint64_t nanoseconds) void display_vblank(GB_gameboy_t *gb) { /* Called every Gameboy vblank. Does FPS-capping and calls user's vblank callback if Turbo Mode allows. */ - if (gb->turbo) { + if (gb->turbo && !gb->turbo_dont_skip) { int64_t nanoseconds = get_nanoseconds(); - if (!gb->turbo_dont_skip && nanoseconds <= gb->last_vblank + FRAME_LENGTH) { + if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) { return; } gb->last_vblank = nanoseconds; From 7bf9cc8f1ad65fd2aef74c7e260dee7b90fe08dd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 01:10:50 +0300 Subject: [PATCH 0180/1216] Detect blank screens by actual screen content, as some games modify LCDC between vblanks. --- Tester/main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 94450a4d..2f861fbc 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -90,16 +90,16 @@ static void vblank(GB_gameboy_t *gb) } if (frames >= test_length ) { - bool is_screen_blank = false; - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { /* LCD is off */ - is_screen_blank = true; - } - else if(!gb->cgb_mode) { /* BG can't be disabled in CGB mode */ - is_screen_blank =!(gb->io_registers[GB_IO_LCDC] & (gb->is_cgb? 3 : 0x23)); + bool is_screen_blank = true; + for (unsigned i = 160*144; i--;) { + if (bitmap[i] != bitmap[0]) { + is_screen_blank = false; + break; + } } - /* Let the test run for an extra 8 frames if the screen is off/disabled */ - if (!is_screen_blank || frames >= test_length + 8) { + /* Let the test run for an extra second if the screen is off/disabled */ + if (!is_screen_blank || frames >= test_length + 60) { FILE *f = fopen(bmp_filename, "wb"); fwrite(&bmp_header, 1, sizeof(bmp_header), f); fwrite(&bitmap, 1, sizeof(bitmap), f); From 4f9b86c9000dd9f49440123b0997e5823ce9d246 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 13:51:59 +0300 Subject: [PATCH 0181/1216] Make Github not count HexFiend into the language stats. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..43c8926b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +HexFiend/* linguist-vendored From 5c5b1cd3ae8e5fcf0427400ccf835a63743779c9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 14:12:41 +0300 Subject: [PATCH 0182/1216] Seems like even 1 second isn't enough for some games. --- Tester/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 2f861fbc..84cd2e87 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -98,8 +98,8 @@ static void vblank(GB_gameboy_t *gb) } } - /* Let the test run for an extra second if the screen is off/disabled */ - if (!is_screen_blank || frames >= test_length + 60) { + /* Let the test run for extra four seconds if the screen is off/disabled */ + if (!is_screen_blank || frames >= test_length + 60 * 4) { FILE *f = fopen(bmp_filename, "wb"); fwrite(&bmp_header, 1, sizeof(bmp_header), f); fwrite(&bitmap, 1, sizeof(bitmap), f); From 17748b980a24a2fb291e2d9a1fe5ebabfab471e2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 18:24:01 +0300 Subject: [PATCH 0183/1216] Fixed a false positive with the FF-loop detection --- Tester/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 84cd2e87..75e8cc19 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -79,10 +79,6 @@ static void vblank(GB_gameboy_t *gb) GB_log(gb, "A stack overflow has probably occurred.\n"); frames = test_length - 1; } - if (gb->pc == 0x38 && GB_read_memory(gb, 0x38) == 0xFF) { - GB_log(gb, "The game is probably stuck in an FF loop.\n"); - frames = test_length - 1; - } if (gb->halted && !gb->interrupt_enable) { GB_log(gb, "The game is deadlocked.\n"); frames = test_length - 1; @@ -313,6 +309,11 @@ int main(int argc, char **argv) frames = 0; while (running) { GB_run(&gb); + /* This early crash test must not run in vblank because PC might not point to the next instruction. */ + if (gb.pc == 0x38 && frames < test_length - 1 && GB_read_memory(&gb, 0x38) == 0xFF) { + GB_log(&gb, "The game is probably stuck in an FF loop.\n"); + frames = test_length - 1; + } } if (log_file) { From 4a2bec239a4c661dff81c45f23a9be0144aed491 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Sep 2016 23:34:06 +0300 Subject: [PATCH 0184/1216] MBC2 RAM support was completely broken. --- Cocoa/Document.m | 2 +- Core/mbc.c | 11 +++++++++-- Core/memory.c | 13 ++++++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index aca44d27..15fc1aa1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -624,7 +624,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) n_banks = gb.is_cgb ? 2 : 1; break; case GBMemoryExternalRAM: - n_banks = gb.mbc_ram_size / 0x2000; + n_banks = (gb.mbc_ram_size + 0x1FFF) / 0x2000; break; case GBMemoryRAM: n_banks = gb.is_cgb ? 8 : 1; diff --git a/Core/mbc.c b/Core/mbc.c index 27192080..80fde6e6 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -106,9 +106,16 @@ void GB_configure_cart(GB_gameboy_t *gb) } 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]]; + if (gb->cartridge_type->mbc_type == GB_MBC2) { + gb->mbc_ram_size = 0x200; + } + else { + 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); + + /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types?*/ memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); } diff --git a/Core/memory.c b/Core/memory.c index 95170812..819e4d04 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -86,7 +86,11 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) return 0xFF; } - return gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; + uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; + if (gb->cartridge_type->mbc_type == GB_MBC2) { + ret |= 0xF0; + } + return ret; } static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) @@ -278,8 +282,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_MBC2: switch (addr & 0xF000) { - /* Todo: is this correct? */ - case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value & 0x1; break; + case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value == 10; break; case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; } break; @@ -326,10 +329,6 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; /* Todo: does it really write both? */ } - if (gb->cartridge_type->mbc_type == GB_MBC2) { - value &= 0xF; - } - if (!gb->mbc_ram) { return; } From 3dd1580256c27433bd640b49b6efd4d76ff74e8a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Oct 2016 14:31:34 +0300 Subject: [PATCH 0185/1216] Automation triggered a bug in DX Bakenou, fixed false positive. --- Tester/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 75e8cc19..eb60a6ab 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -20,7 +20,7 @@ static char *bmp_filename; static char *log_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); -static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster; +static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -49,7 +49,7 @@ static void vblank(GB_gameboy_t *gb) if (start_is_not_first) combo_length = 60; /* The start item in the menu is not the first, so also push down */ else if (a_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ - switch ((push_faster? frames * 2 : frames) % combo_length) { + switch ((push_faster? frames * 2 : push_slower? frames / 2 : frames) % combo_length) { case 0: gb->keys[7] = true; // Start down break; @@ -302,6 +302,8 @@ int main(int argc, char **argv) a_is_bad = strcmp((const char *)(gb.rom + 0x134), "DESERT STRIKE") == 0; b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0; push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0; + push_slower = strcmp((const char *)(gb.rom + 0x134), "BAKENOU") == 0; + /* Run emulation */ running = true; From 80a1b12ae7da80f56ede7ab48a5401f78590ac47 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Oct 2016 22:08:34 +0300 Subject: [PATCH 0186/1216] Added palette command to the debugger --- Core/debugger.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index 418a2544..13fd399f 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1200,6 +1200,37 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, const debugger_command_t *c return true; } + +static bool palettes(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!gb->is_cgb) { + GB_log(gb, "Not available on a DMG.\n"); + return true; + } + + GB_log(gb, "Background palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->background_palletes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + GB_log(gb, "Sprites palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palletes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + return true; +} static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command); #define HELP_NEWLINE "\n " @@ -1217,6 +1248,7 @@ static const debugger_command_t commands[] = { {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ + {"palettes", 3, palettes, "Displays the current CGB palettes"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression." HELP_NEWLINE "Can also modify the condition of existing breakpoints.", "[ if ]"}, From 4904277f0db816aadcd534899580f2fe6a530662 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Oct 2016 22:10:44 +0300 Subject: [PATCH 0187/1216] Corrected behavior of palette registers in DMG mode. This affected a broken Game & Watch Gallery 2 ROM that was previously used in the automation test. --- Core/memory.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 819e4d04..9bc8809c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -197,7 +197,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_BGPI: case GB_IO_OBPI: - if (!gb->is_cgb) { + if (!gb->cgb_mode && gb->boot_rom_finished) { return 0xFF; } return gb->io_registers[addr & 0xFF] | 0x40; @@ -205,7 +205,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_BGPD: case GB_IO_OBPD: { - if (!gb->is_cgb) { + if (!gb->cgb_mode && gb->boot_rom_finished) { return 0xFF; } uint8_t index_reg = (addr & 0xFF) - 1; @@ -479,14 +479,16 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_BGPI: case GB_IO_OBPI: - if (!gb->is_cgb) { + if (!gb->cgb_mode && gb->boot_rom_finished) { return; } gb->io_registers[addr & 0xFF] = value; return; case GB_IO_BGPD: case GB_IO_OBPD: - if (!gb->is_cgb) { + if (!gb->cgb_mode && gb->boot_rom_finished) { + /* Todo: Due to the behavior of a broken Game & Watch Gallery 2 ROM on a real CGB. A proper test ROM + is required. */ return; } uint8_t index_reg = (addr & 0xFF) - 1; From 21b91adf6a060d0f6b4f76bce616dac7bd42469b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 00:10:09 +0300 Subject: [PATCH 0188/1216] Improved open-dialog behavior in Cocoa --- Cocoa/AppDelegate.m | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index a03b062f..ff98f3f6 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -53,11 +53,10 @@ [_preferencesWindow makeKeyAndOrderFront:self]; } -- (void)applicationDidBecomeActive:(NSNotification *)notification +- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender { - NSDocumentController *controller = [NSDocumentController sharedDocumentController]; - if (![[controller documents] count]) { - [[NSDocumentController sharedDocumentController] openDocument:self]; - } + [[NSDocumentController sharedDocumentController] openDocument:self]; + return YES; } + @end From 58a4081b48b3072b008a322a5e9aef09f6cd186e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 00:10:31 +0300 Subject: [PATCH 0189/1216] Improved open performance in Cocoa --- Cocoa/Document.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 15fc1aa1..1cfef34b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -675,4 +675,9 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return &gb; } ++ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName +{ + return YES; +} + @end \ No newline at end of file From 9b71454f075740e3499664dc9597f63a99232831 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 02:15:03 +0300 Subject: [PATCH 0190/1216] Basic HUC1 support (Emulated as MBC1) --- Core/gb.h | 4 ++++ Core/mbc.c | 62 +++++++++++++++++++++++++++--------------------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index e67ff3df..7847f830 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -169,6 +169,10 @@ typedef struct { GB_MBC4, // Does this exist??? GB_MBC5, } mbc_type; + enum { + GB_STANDARD_MBC, + GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */ + } mbc_subtype; bool has_ram; bool has_battery; bool has_rtc; diff --git a/Core/mbc.c b/Core/mbc.c index 80fde6e6..ef095751 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -5,46 +5,46 @@ const GB_cartridge_t GB_cart_defs[256] = { // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type - /* MBC RAM BAT. RTC RUMB. */ - { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY - { GB_MBC1 , false, false, false, false}, // 01h MBC1 - { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM - { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY + /* MBC SUBTYPE RAM BAT. RTC RUMB. EXTRA */ + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false, }, // 00h ROM ONLY + { GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1 + { GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY [5] = - { GB_MBC2 , true , false, false, false}, // 05h MBC2 - { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY + { GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2 + { GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY [8] = - { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM - { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + { GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY [0xB] = // Todo: What are these? - { GB_NO_MBC, false, false, false, false}, // 0Bh MMM01 - { GB_NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM - { GB_NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01 + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY [0xF] = - { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY - { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY - { GB_MBC3 , false, false, false, false}, // 11h MBC3 - { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM - { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3 + { GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY [0x15] = // Todo: Do these exist? - { GB_MBC4 , false, false, false, false}, // 15h MBC4 - { GB_MBC4 , true , false, false, false}, // 16h MBC4+RAM - { GB_MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY + { GB_MBC4 , GB_STANDARD_MBC, false, false, false, false}, // 15h MBC4 + { GB_MBC4 , GB_STANDARD_MBC, true , false, false, false}, // 16h MBC4+RAM + { GB_MBC4 , GB_STANDARD_MBC, true , true , false, false}, // 17h MBC4+RAM+BATTERY [0x19] = - { GB_MBC5 , false, false, false, false}, // 19h MBC5 - { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM - { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY - { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE - { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM - { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + { GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5 + { GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY [0xFC] = // Todo: What are these? - { GB_NO_MBC, false, false, false, false}, // FCh POCKET CAMERA - { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 - { GB_NO_MBC, false, false, false, false}, // FEh HuC3 - { GB_NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FEh HuC3 + { GB_MBC1 , GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY }; void GB_update_mbc_mappings(GB_gameboy_t *gb) @@ -101,7 +101,7 @@ void GB_configure_cart(GB_gameboy_t *gb) { gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; - if (gb->rom[0x147] == 0xFF || (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0)) { + if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) { GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]); } From b3b041a151dc08f094a14eb00401a22396474cc8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 03:40:11 +0300 Subject: [PATCH 0191/1216] Basic GB Camera support (Emulate only MBC, enough for the ROM to boot). --- Core/gb.h | 2 ++ Core/mbc.c | 2 +- Core/memory.c | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 7847f830..75066c05 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -172,6 +172,7 @@ typedef struct { enum { GB_STANDARD_MBC, GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */ + GB_CAMERA, /* Not emulated as well */ } mbc_subtype; bool has_ram; bool has_battery; @@ -282,6 +283,7 @@ typedef struct GB_gameboy_s { } mbc5; }; uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ + bool camera_registers_mapped; ); diff --git a/Core/mbc.c b/Core/mbc.c index ef095751..ddc5a31f 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -41,7 +41,7 @@ const GB_cartridge_t GB_cart_defs[256] = { { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY [0xFC] = // Todo: What are these? - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FCh POCKET CAMERA + { GB_MBC5 , GB_CAMERA , true, true, false, false}, // FCh POCKET CAMERA { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FEh HuC3 { GB_MBC1 , GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY diff --git a/Core/memory.c b/Core/memory.c index 9bc8809c..f003565b 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -74,7 +74,7 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { - if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return 0xFF; + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_subtype != GB_CAMERA) return 0xFF; if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ @@ -82,6 +82,10 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) return gb->rtc_latched.data[gb->mbc_ram_bank - 8]; } + if (gb->camera_registers_mapped) { + return 0; + } + if (!gb->mbc_ram) { return 0xFF; } @@ -282,7 +286,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_MBC2: switch (addr & 0xF000) { - case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value == 10; break; + case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value == 0xA; break; case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; } break; @@ -292,7 +296,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break; case 0x6000: case 0x7000: - if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct*/ + if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */ memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); } gb->rtc_latch = value & 1; @@ -304,7 +308,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; case 0x2000: gb->mbc5.rom_bank_low = value; break; case 0x3000: gb->mbc5.rom_bank_high = value; break; - case 0x4000: case 0x5000: gb->mbc5.ram_bank = value; break; + case 0x4000: case 0x5000: + gb->mbc5.ram_bank = value; + gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA; + break; } break; } @@ -333,6 +340,10 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } + if (gb->camera_registers_mapped) { + return; + } + gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; } From ab5f66795ab48b0fddb6ec71c3a0d7b66bbb3ee1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 17:14:58 +0300 Subject: [PATCH 0192/1216] Gameboy Camera API --- Core/camera.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ Core/camera.h | 15 +++++++++ Core/gb.h | 5 +++ Core/memory.c | 16 ++++++--- 4 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 Core/camera.c create mode 100644 Core/camera.h diff --git a/Core/camera.c b/Core/camera.c new file mode 100644 index 00000000..cfbf9054 --- /dev/null +++ b/Core/camera.c @@ -0,0 +1,89 @@ +#include "camera.h" + +static int8_t dither_random(uint8_t x, uint8_t y) +{ + static bool once = false; + static int8_t random[128*112]; + if (!once) { + unsigned int r = 0x1337c0de; + for (int i = 0; i < sizeof(random); i++) { + random[i] = r % 85 - 42; + r += 11; + r *= 25214903917; + } + once = true; + } + + return random[x + y * 128]; +} + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) +{ + uint8_t tile_x = addr / 0x10 % 0x10; + uint8_t tile_y = addr / 0x10 / 0x10; + + uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8; + uint8_t bit = addr & 1; + + uint8_t ret = 0; + + for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { + + int color = gb->camera_get_pixel_callback? ~gb->camera_get_pixel_callback(gb, x,y) : (rand() & 0xFF); + + /* Dither using a deterministic random */ + color += dither_random(x, y); + if (color > 255) { + color = 255; + } + else if (color < 0) { + color = 0; + } + + ret <<= 1; + ret |= (color >> (6 + bit)) & 1; + } + + return ret; +} + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback) +{ + gb->camera_get_pixel_callback = callback; +} + +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback) +{ + gb->camera_update_request_callback = callback; +} + +void GB_camera_updated(GB_gameboy_t *gb) +{ + gb->camera_registers[0] = 0; +} + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + addr &= 0x7F; + if (addr == 0) { + if ((value & 1) && gb->camera_update_request_callback) { + /* If no callback is set, ignore the write as if the camera is instantly done */ + gb->camera_update_request_callback(gb); + gb->camera_registers[0] = 1; + } + } + else { + if (addr >= 0x36) { + GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value); + } + /* Todo: find out what these registers do */ + gb->camera_registers[addr] = value; + } +} +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr) +{ + if ((addr & 0x7F) == 0) { + return gb->camera_registers[0]; + } + return 0; +} \ No newline at end of file diff --git a/Core/camera.h b/Core/camera.h new file mode 100644 index 00000000..d0d7a9db --- /dev/null +++ b/Core/camera.h @@ -0,0 +1,15 @@ +#ifndef camera_h +#define camera_h +#include "gb.h" + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr); + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback); +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback); + +void GB_camera_updated(GB_gameboy_t *gb); + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr); + +#endif diff --git a/Core/gb.h b/Core/gb.h index 75066c05..0836dbbc 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -159,6 +159,8 @@ typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_a typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); +typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); +typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); typedef struct { enum { @@ -284,6 +286,7 @@ typedef struct GB_gameboy_s { }; uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ bool camera_registers_mapped; + uint8_t camera_registers[0x36]; ); @@ -380,6 +383,8 @@ typedef struct GB_gameboy_s { GB_rgb_encode_callback_t rgb_encode_callback; GB_vblank_callback_t vblank_callback; GB_infrared_callback_t infrared_callback; + GB_camera_get_pixel_callback_t camera_get_pixel_callback; + GB_camera_update_request_callback_t camera_update_request_callback; /* IR */ long cycles_since_ir_change; diff --git a/Core/memory.c b/Core/memory.c index f003565b..28d2a7d9 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -7,6 +7,7 @@ #include "debugger.h" #include "mbc.h" #include "timing.h" +#include "camera.h" typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); @@ -83,13 +84,17 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } if (gb->camera_registers_mapped) { - return 0; + return GB_camera_read_register(gb, addr); } if (!gb->mbc_ram) { return 0xFF; } + if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) { + return GB_camera_read_image(gb, addr - 0xa100); + } + uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; if (gb->cartridge_type->mbc_type == GB_MBC2) { ret |= 0xF0; @@ -329,6 +334,11 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { + if (gb->camera_registers_mapped) { + GB_camera_write_register(gb, addr, value); + return; + } + if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return; if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { @@ -340,10 +350,6 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - if (gb->camera_registers_mapped) { - return; - } - gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; } From 9c68ac14190a024d42eefd9d7f113524d4cfe44c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 18:14:05 +0300 Subject: [PATCH 0193/1216] Camera API bugfix --- Core/camera.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/camera.c b/Core/camera.c index cfbf9054..d5f65750 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -68,8 +68,8 @@ void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (addr == 0) { if ((value & 1) && gb->camera_update_request_callback) { /* If no callback is set, ignore the write as if the camera is instantly done */ - gb->camera_update_request_callback(gb); gb->camera_registers[0] = 1; + gb->camera_update_request_callback(gb); } } else { From de7c15fc68ef8821e62ba8fc90764427feb2ac4f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 18:33:33 +0300 Subject: [PATCH 0194/1216] Another camera API bugfix --- Core/camera.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/camera.c b/Core/camera.c index d5f65750..4812c891 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -29,7 +29,7 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { - int color = gb->camera_get_pixel_callback? ~gb->camera_get_pixel_callback(gb, x,y) : (rand() & 0xFF); + int color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x,y) ^ 0xFF : (rand() & 0xFF); /* Dither using a deterministic random */ color += dither_random(x, y); From 479a64dca67fae62583b9bf06d1cb2a33d60bc95 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 2 Oct 2016 23:36:20 +0300 Subject: [PATCH 0195/1216] Dither using a pattern, closer to actual GameBoy Camera --- Core/camera.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/Core/camera.c b/Core/camera.c index 4812c891..db8eb2f5 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -1,22 +1,5 @@ #include "camera.h" -static int8_t dither_random(uint8_t x, uint8_t y) -{ - static bool once = false; - static int8_t random[128*112]; - if (!once) { - unsigned int r = 0x1337c0de; - for (int i = 0; i < sizeof(random); i++) { - random[i] = r % 85 - 42; - r += 11; - r *= 25214903917; - } - once = true; - } - - return random[x + y * 128]; -} - uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) { uint8_t tile_x = addr / 0x10 % 0x10; @@ -31,8 +14,8 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) int color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x,y) ^ 0xFF : (rand() & 0xFF); - /* Dither using a deterministic random */ - color += dither_random(x, y); + /* Dither using a pattern */ + color += ((x + y) & 1? 21 : -21) >> (y & 1); if (color > 255) { color = 255; } From b50b38c78adcdff178e143559a2aedbedcd1c2b1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Oct 2016 00:26:12 +0300 Subject: [PATCH 0196/1216] GameBoy Camera support in Cocoa --- Cocoa/Document.m | 109 ++++++++++++++++++++++++++++++++++++++++++++--- Makefile | 2 +- README.md | 1 + 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 1cfef34b..fdd8eeaf 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1,3 +1,4 @@ +#include #include #include "GBAudioClient.h" #include "Document.h" @@ -5,6 +6,7 @@ #include "gb.h" #include "debugger.h" #include "memory.h" +#include "camera.h" #include "HexFiend/HexFiend.h" #include "GBMemoryByteArray.h" @@ -23,6 +25,11 @@ NSString *lastConsoleInput; HFLineCountingRepresenter *lineRep; + + CVImageBufferRef cameraImage; + AVCaptureSession *cameraSession; + AVCaptureConnection *cameraConnection; + AVCaptureStillImageOutput *cameraOutput; } @property GBAudioClient *audioClient; @@ -30,6 +37,8 @@ - (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes; - (const char *) getDebuggerInput; - (const char *) getAsyncDebuggerInput; +- (void) cameraRequestUpdate; +- (uint8_t) cameraGetPixelAtX:(uint8_t)x andY:(uint8_t)y; @end static void vblank(GB_gameboy_t *gb) @@ -62,6 +71,18 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return (r << 0) | (g << 8) | (b << 16); } +static void cameraRequestUpdate(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)(gb->user_data); + [self cameraRequestUpdate]; +} + +static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + Document *self = (__bridge Document *)(gb->user_data); + return [self cameraGetPixelAtX:x andY:y]; +} + @implementation Document { GB_gameboy_t gb; @@ -91,23 +112,26 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { GB_init(&gb); GB_load_boot_rom(&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_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); - GB_set_rgb_encode_callback(&gb, rgbEncode); - gb.user_data = (__bridge void *)(self); + [self initCommon]; } - (void) initCGB { GB_init_cgb(&gb); GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]); + [self initCommon]; + +} + +- (void) initCommon +{ 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_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); GB_set_rgb_encode_callback(&gb, rgbEncode); + GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); + GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); gb.user_data = (__bridge void *)(self); } @@ -222,6 +246,9 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void)dealloc { GB_free(&gb); + if (cameraImage) { + CVBufferRelease(cameraImage); + } } - (void)windowControllerDidLoadNib:(NSWindowController *)aController { @@ -680,4 +707,74 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return YES; } +- (void)cameraRequestUpdate +{ + dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + @try { + if (!cameraSession) { + NSError *error; + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; + AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error]; + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions([[[device formats] firstObject] formatDescription]); + + if (!input) { + GB_camera_updated(&gb); + return; + } + + cameraOutput = [[AVCaptureStillImageOutput alloc] init]; + /* Greyscale is not widely supported, so we use YUV, whose first element is the brightness. */ + [cameraOutput setOutputSettings: @{(id)kCVPixelBufferPixelFormatTypeKey: @(kYUVSPixelFormat), + (id)kCVPixelBufferWidthKey: @(MAX(128, 112 * dimensions.width / dimensions.height)), + (id)kCVPixelBufferHeightKey: @(MAX(112, 128 * dimensions.height / dimensions.width)),}]; + + + cameraSession = [AVCaptureSession new]; + cameraSession.sessionPreset = AVCaptureSessionPresetPhoto; + + [cameraSession addInput: input]; + [cameraSession addOutput: cameraOutput]; + /* ARC will stop the session when the window is closed. */ + [cameraSession startRunning]; + cameraConnection = [cameraOutput connectionWithMediaType: AVMediaTypeVideo]; + } + + [cameraOutput captureStillImageAsynchronouslyFromConnection: cameraConnection completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError *error) { + if (error) { + GB_camera_updated(&gb); + } + else { + if (cameraImage) { + CVBufferRelease(cameraImage); + cameraImage = NULL; + } + cameraImage = CVBufferRetain(CMSampleBufferGetImageBuffer(sampleBuffer)); + /* We only need the actual buffer, no need to ever unlock it. */ + CVPixelBufferLockBaseAddress(cameraImage, 0); + } + + GB_camera_updated(&gb); + }]; + } + @catch (NSException *exception) { + /* I have not tested camera support on many devices, so we catch exceptions just in case. */ + GB_camera_updated(&gb); + } + }); +} + +- (uint8_t)cameraGetPixelAtX:(uint8_t)x andY:(uint8_t) y +{ + if (!cameraImage) { + return rand(); + } + + uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(cameraImage); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(cameraImage); + uint8_t offsetX = (CVPixelBufferGetWidth(cameraImage) - 128) / 2; + uint8_t offsetY = (CVPixelBufferGetHeight(cameraImage) - 112) / 2; + uint8_t ret = baseAddress[(x + offsetX) * 2 + (y + offsetY) * bytesPerRow]; + + return ret; +} @end \ No newline at end of file diff --git a/Makefile b/Makefile index f2a5be33..8fe77050 100755 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \ $(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit + $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia ifeq ($(CONF), release) strip $@ endif diff --git a/README.md b/README.md index bee6392a..8457e8ac 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Features currently supported only with the Cocoa version: * Retina display support, allowing a wider range of scaling factors without artifacts * Optional frame blending * Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x) + * GameBoy Camera support ## Compatibility SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. The latest results for SameBoy's automatic tester are available [here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html). From 2a84d62187e75caba8517c32b76656fc9eb2597e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Oct 2016 01:29:54 +0300 Subject: [PATCH 0197/1216] Forbid reading the image while the camera is busy --- Core/camera.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/camera.c b/Core/camera.c index db8eb2f5..af30bada 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -2,6 +2,10 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) { + if (gb->camera_registers[0]) { + /* Forbid reading the image while the camera is busy. */ + return 0xFF; + } uint8_t tile_x = addr / 0x10 % 0x10; uint8_t tile_y = addr / 0x10 / 0x10; From dd23fffcc0d5940d4f2801c07d8bdb51526abf6a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Oct 2016 14:22:54 +0300 Subject: [PATCH 0198/1216] Proper (I believe) emulation of most GameBoy Camera registers --- Core/camera.c | 41 ++++++++++++++++++++++++++++------------- Core/camera.h | 9 +++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Core/camera.c b/Core/camera.c index af30bada..61eda89e 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -2,7 +2,7 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) { - if (gb->camera_registers[0]) { + if (gb->camera_registers[GB_CAMERA_FLAGS] & 1) { /* Forbid reading the image while the camera is busy. */ return 0xFF; } @@ -16,19 +16,33 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { - int color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x,y) ^ 0xFF : (rand() & 0xFF); + long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x,y) : (rand() & 0xFF); - /* Dither using a pattern */ - color += ((x + y) & 1? 21 : -21) >> (y & 1); - if (color > 255) { - color = 255; + /* Color is multiplied by the multiplier register. */ + /* It is unknown what register 1 does, but changing bits 2-3 from 0x4 to 0x8 seems equivalent to adding 0x2000 + to the multiplier. Is it related to actual exposure time? */ + unsigned long multiplier_bias = (gb->camera_registers[GB_CAMERA_UNKNOWN_FLAGS] & 0xF) * 0x800; + color = color * ((gb->camera_registers[GB_CAMERA_MULTIPLIER_HIGH] << 8) + gb->camera_registers[GB_CAMERA_MULTIPLIER_LOW] + multiplier_bias) / 0x3000; + + /* The camera's registers are used as a threshold pattern, which defines the dithering */ + uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START; + + /* Todo: I have absolutely no reason to assume that this does not go backwards! */ + if (color < gb->camera_registers[pattern_base]) { + color = 3; } - else if (color < 0) { + else if (color < gb->camera_registers[pattern_base + 1]) { + color = 2; + } + else if (color < gb->camera_registers[pattern_base + 2]) { + color = 1; + } + else { color = 0; } ret <<= 1; - ret |= (color >> (6 + bit)) & 1; + ret |= (color >> bit) & 1; } return ret; @@ -46,22 +60,23 @@ void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_re void GB_camera_updated(GB_gameboy_t *gb) { - gb->camera_registers[0] = 0; + gb->camera_registers[GB_CAMERA_FLAGS] &= ~1; } void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { addr &= 0x7F; - if (addr == 0) { - if ((value & 1) && gb->camera_update_request_callback) { + if (addr == GB_CAMERA_FLAGS) { + if ((value & 1) && !(gb->camera_registers[GB_CAMERA_FLAGS] & 1) && gb->camera_update_request_callback) { /* If no callback is set, ignore the write as if the camera is instantly done */ - gb->camera_registers[0] = 1; + gb->camera_registers[GB_CAMERA_FLAGS] |= 1; gb->camera_update_request_callback(gb); } } else { if (addr >= 0x36) { GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value); + return; } /* Todo: find out what these registers do */ gb->camera_registers[addr] = value; @@ -70,7 +85,7 @@ void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr) { if ((addr & 0x7F) == 0) { - return gb->camera_registers[0]; + return gb->camera_registers[GB_CAMERA_FLAGS]; } return 0; } \ No newline at end of file diff --git a/Core/camera.h b/Core/camera.h index d0d7a9db..3bb86975 100644 --- a/Core/camera.h +++ b/Core/camera.h @@ -2,6 +2,15 @@ #define camera_h #include "gb.h" +enum { + GB_CAMERA_FLAGS = 0, + GB_CAMERA_UNKNOWN_FLAGS = 1, + GB_CAMERA_MULTIPLIER_HIGH = 2, + GB_CAMERA_MULTIPLIER_LOW = 3, + GB_CAMERA_DITHERING_PATTERN_START = 6, + GB_CAMERA_DITHERING_PATTERN_END = 0x35, +}; + uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr); void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback); From 8941504863e641d2b701a5153ff2ffa05f368c42 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Oct 2016 18:24:15 +0300 Subject: [PATCH 0199/1216] Refinements to camera support according to AntonioND's docs --- Core/camera.c | 33 +++++++++++++++++++++------------ Core/camera.h | 8 ++++---- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Core/camera.c b/Core/camera.c index 61eda89e..0c6539fd 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -2,7 +2,7 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) { - if (gb->camera_registers[GB_CAMERA_FLAGS] & 1) { + if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { /* Forbid reading the image while the camera is busy. */ return 0xFF; } @@ -18,16 +18,24 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x,y) : (rand() & 0xFF); - /* Color is multiplied by the multiplier register. */ - /* It is unknown what register 1 does, but changing bits 2-3 from 0x4 to 0x8 seems equivalent to adding 0x2000 - to the multiplier. Is it related to actual exposure time? */ - unsigned long multiplier_bias = (gb->camera_registers[GB_CAMERA_UNKNOWN_FLAGS] & 0xF) * 0x800; - color = color * ((gb->camera_registers[GB_CAMERA_MULTIPLIER_HIGH] << 8) + gb->camera_registers[GB_CAMERA_MULTIPLIER_LOW] + multiplier_bias) / 0x3000; + static const double gain_values[] = {0.8809390, 0.9149149, 0.9457498, 0.9739758, + 1.0000000, 1.0241412, 1.0466537, 1.0677433, + 1.0875793, 1.1240310, 1.1568911, 1.1868043, + 1.2142561, 1.2396208, 1.2743837, 1.3157323, + 1.3525190, 1.3856512, 1.4157897, 1.4434309, + 1.4689574, 1.4926697, 1.5148087, 1.5355703, + 1.5551159, 1.5735801, 1.5910762, 1.6077008, + 1.6235366, 1.6386550, 1.6531183, 1.6669808}; + /* Multiply color by gain value */ + color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENCHANCEMENT_FLAGS] & 0x1F]; + + + /* Color is multiplied by the exposure register to simulate exposure. */ + color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000; /* The camera's registers are used as a threshold pattern, which defines the dithering */ uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START; - /* Todo: I have absolutely no reason to assume that this does not go backwards! */ if (color < gb->camera_registers[pattern_base]) { color = 3; } @@ -60,16 +68,17 @@ void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_re void GB_camera_updated(GB_gameboy_t *gb) { - gb->camera_registers[GB_CAMERA_FLAGS] &= ~1; + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1; } void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { addr &= 0x7F; - if (addr == GB_CAMERA_FLAGS) { - if ((value & 1) && !(gb->camera_registers[GB_CAMERA_FLAGS] & 1) && gb->camera_update_request_callback) { + if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) { + value &= 0x7; + if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) { /* If no callback is set, ignore the write as if the camera is instantly done */ - gb->camera_registers[GB_CAMERA_FLAGS] |= 1; + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1; gb->camera_update_request_callback(gb); } } @@ -85,7 +94,7 @@ void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr) { if ((addr & 0x7F) == 0) { - return gb->camera_registers[GB_CAMERA_FLAGS]; + return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS]; } return 0; } \ No newline at end of file diff --git a/Core/camera.h b/Core/camera.h index 3bb86975..9c009f6b 100644 --- a/Core/camera.h +++ b/Core/camera.h @@ -3,10 +3,10 @@ #include "gb.h" enum { - GB_CAMERA_FLAGS = 0, - GB_CAMERA_UNKNOWN_FLAGS = 1, - GB_CAMERA_MULTIPLIER_HIGH = 2, - GB_CAMERA_MULTIPLIER_LOW = 3, + GB_CAMERA_SHOOT_AND_1D_FLAGS = 0, + GB_CAMERA_GAIN_AND_EDGE_ENCHANCEMENT_FLAGS = 1, + GB_CAMERA_EXPOSURE_HIGH = 2, + GB_CAMERA_EXPOSURE_LOW = 3, GB_CAMERA_DITHERING_PATTERN_START = 6, GB_CAMERA_DITHERING_PATTERN_END = 0x35, }; From 2d06599a85b06eba12b8b39b8a32bd0b16381b72 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Oct 2016 19:39:20 +0300 Subject: [PATCH 0200/1216] Edge enhancement support --- Core/camera.c | 59 +++++++++++++++++++++++++++++++++++++++------------ Core/camera.h | 3 ++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/Core/camera.c b/Core/camera.c index 0c6539fd..f7921e90 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -1,5 +1,37 @@ #include "camera.h" +/* This is not a completely emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. */ + + +static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + if (x >= 128) { + x = 0; + } + if (y >= 112) { + y = 0; + } + + long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (rand() & 0xFF); + + static const double gain_values[] = {0.8809390, 0.9149149, 0.9457498, 0.9739758, + 1.0000000, 1.0241412, 1.0466537, 1.0677433, + 1.0875793, 1.1240310, 1.1568911, 1.1868043, + 1.2142561, 1.2396208, 1.2743837, 1.3157323, + 1.3525190, 1.3856512, 1.4157897, 1.4434309, + 1.4689574, 1.4926697, 1.5148087, 1.5355703, + 1.5551159, 1.5735801, 1.5910762, 1.6077008, + 1.6235366, 1.6386550, 1.6531183, 1.6669808}; + /* Multiply color by gain value */ + color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F]; + + + /* Color is multiplied by the exposure register to simulate exposure. */ + color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000; + + return color; +} + uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) { if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { @@ -14,25 +46,24 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) uint8_t ret = 0; + /* This is not a complete emulation of the chip's image proccessing algorithm, it only emulates the features used by + the actual GameBoy Camera ROM. */ + for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { - long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x,y) : (rand() & 0xFF); + long color = get_processed_color(gb, x, y); - static const double gain_values[] = {0.8809390, 0.9149149, 0.9457498, 0.9739758, - 1.0000000, 1.0241412, 1.0466537, 1.0677433, - 1.0875793, 1.1240310, 1.1568911, 1.1868043, - 1.2142561, 1.2396208, 1.2743837, 1.3157323, - 1.3525190, 1.3856512, 1.4157897, 1.4434309, - 1.4689574, 1.4926697, 1.5148087, 1.5355703, - 1.5551159, 1.5735801, 1.5910762, 1.6077008, - 1.6235366, 1.6386550, 1.6531183, 1.6669808}; - /* Multiply color by gain value */ - color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENCHANCEMENT_FLAGS] & 0x1F]; + static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5}; + double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7]; + if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) { + color += (color * 4) * edge_enhancement_ratio; + color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio; + } - /* Color is multiplied by the exposure register to simulate exposure. */ - color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000; - /* The camera's registers are used as a threshold pattern, which defines the dithering */ uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START; diff --git a/Core/camera.h b/Core/camera.h index 9c009f6b..5adda5d1 100644 --- a/Core/camera.h +++ b/Core/camera.h @@ -4,9 +4,10 @@ enum { GB_CAMERA_SHOOT_AND_1D_FLAGS = 0, - GB_CAMERA_GAIN_AND_EDGE_ENCHANCEMENT_FLAGS = 1, + GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1, GB_CAMERA_EXPOSURE_HIGH = 2, GB_CAMERA_EXPOSURE_LOW = 3, + GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4, GB_CAMERA_DITHERING_PATTERN_START = 6, GB_CAMERA_DITHERING_PATTERN_END = 0x35, }; From 90b7383df7594afe0849bf150a6ca0a66676dc06 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Oct 2016 23:05:47 +0300 Subject: [PATCH 0201/1216] The camera's get pixel callback must return the same value for every coordinate until a new photo is taken. --- Cocoa/Document.m | 2 +- Core/camera.c | 45 ++++++++++++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index fdd8eeaf..1cf25450 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -766,7 +766,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) - (uint8_t)cameraGetPixelAtX:(uint8_t)x andY:(uint8_t) y { if (!cameraImage) { - return rand(); + return 0; } uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(cameraImage); diff --git a/Core/camera.c b/Core/camera.c index f7921e90..4e1d3b6e 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -1,7 +1,27 @@ #include "camera.h" -/* This is not a completely emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. */ +static int noise_seed = 0; +/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. + We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */ + +static uint8_t generate_noise(uint8_t x, uint8_t y) +{ + int value = (x + y * 128 + noise_seed); + uint8_t *data = (uint8_t *) &value; + unsigned hash; + + while ((int *) data != &value + 1) { + hash ^= (*data << 8); + if (hash & 0x8000) { + hash ^= 0x8a00; + hash ^= *data; + } + data++; + hash <<= 1; + } + return (hash >> 8); +} static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) { @@ -12,16 +32,17 @@ static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) y = 0; } - long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (rand() & 0xFF); + long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y)); - static const double gain_values[] = {0.8809390, 0.9149149, 0.9457498, 0.9739758, - 1.0000000, 1.0241412, 1.0466537, 1.0677433, - 1.0875793, 1.1240310, 1.1568911, 1.1868043, - 1.2142561, 1.2396208, 1.2743837, 1.3157323, - 1.3525190, 1.3856512, 1.4157897, 1.4434309, - 1.4689574, 1.4926697, 1.5148087, 1.5355703, - 1.5551159, 1.5735801, 1.5910762, 1.6077008, - 1.6235366, 1.6386550, 1.6531183, 1.6669808}; + static const double gain_values[] = + {0.8809390, 0.9149149, 0.9457498, 0.9739758, + 1.0000000, 1.0241412, 1.0466537, 1.0677433, + 1.0875793, 1.1240310, 1.1568911, 1.1868043, + 1.2142561, 1.2396208, 1.2743837, 1.3157323, + 1.3525190, 1.3856512, 1.4157897, 1.4434309, + 1.4689574, 1.4926697, 1.5148087, 1.5355703, + 1.5551159, 1.5735801, 1.5910762, 1.6077008, + 1.6235366, 1.6386550, 1.6531183, 1.6669808}; /* Multiply color by gain value */ color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F]; @@ -46,9 +67,6 @@ uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) uint8_t ret = 0; - /* This is not a complete emulation of the chip's image proccessing algorithm, it only emulates the features used by - the actual GameBoy Camera ROM. */ - for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { long color = get_processed_color(gb, x, y); @@ -107,6 +125,7 @@ void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) addr &= 0x7F; if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) { value &= 0x7; + noise_seed = rand(); if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) { /* If no callback is set, ignore the write as if the camera is instantly done */ gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1; From 76c795a966c50453646841471aad0e1ed345fc23 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 4 Oct 2016 04:01:06 +0300 Subject: [PATCH 0202/1216] Whoops. --- Core/camera.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/camera.c b/Core/camera.c index 4e1d3b6e..5a087d25 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -9,7 +9,7 @@ static uint8_t generate_noise(uint8_t x, uint8_t y) { int value = (x + y * 128 + noise_seed); uint8_t *data = (uint8_t *) &value; - unsigned hash; + unsigned hash = 0; while ((int *) data != &value + 1) { hash ^= (*data << 8); From 11cbe58eb177d861f9b53f1102468b1d47e3b890 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 5 Oct 2016 23:56:44 +0300 Subject: [PATCH 0203/1216] False positive corrections --- Tester/main.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index eb60a6ab..950f214f 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -20,7 +20,7 @@ static char *bmp_filename; static char *log_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); -static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower; +static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, do_not_stop; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -44,7 +44,7 @@ static void vblank(GB_gameboy_t *gb) /* Do not press any buttons during the last two seconds, this might cause a screenshot to be taken while the LCD is off if the press makes the game load graphics. */ - if (push_start_a && frames < test_length - 120) { + if (push_start_a && (frames < test_length - 120 || do_not_stop)) { unsigned combo_length = 40; if (start_is_not_first) combo_length = 60; /* The start item in the menu is not the first, so also push down */ else if (a_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ @@ -299,11 +299,14 @@ int main(int argc, char **argv) /* It's OK. No overflow is possible here. */ start_is_not_first = strcmp((const char *)(gb.rom + 0x134), "NEKOJARA") == 0 || strcmp((const char *)(gb.rom + 0x134), "GINGA") == 0; - a_is_bad = strcmp((const char *)(gb.rom + 0x134), "DESERT STRIKE") == 0; + a_is_bad = strcmp((const char *)(gb.rom + 0x134), "DESERT STRIKE") == 0 || + /* Restarting in Puzzle Boy/Kwirk (Start followed by A) leaks stack. */ + strcmp((const char *)(gb.rom + 0x134), "KWIRK") == 0 || + strcmp((const char *)(gb.rom + 0x134), "PUZZLE BOY") == 0; b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0; push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0; push_slower = strcmp((const char *)(gb.rom + 0x134), "BAKENOU") == 0; - + do_not_stop = strcmp((const char *)(gb.rom + 0x134), "SPACE INVADERS") == 0; /* Run emulation */ running = true; From fa35869bc4ee6dd91cff8c5635798127c6c3a17d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 11 Oct 2016 13:37:43 +0300 Subject: [PATCH 0204/1216] Implemented DMG STAT-write interrupt bug, fixed Road Rash and Zero no Densetsu (These game do not work on CGBs) --- Core/memory.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index 28d2a7d9..beb9a098 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -433,6 +433,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_STAT: + /* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */ + if (!gb->is_cgb && (gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) { + gb->io_registers[GB_IO_IF] |= 2; + } /* Delete previous R/W bits */ gb->io_registers[GB_IO_STAT] &= 7; /* Set them by value */ From 5cca2a41688b8bee9e4ee5ed9bdb2207bc0a53ae Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 11 Oct 2016 14:53:54 +0300 Subject: [PATCH 0205/1216] Be more forgiving about stack overflows. Some games commercially leak stack every once in a while when being stress-tested. --- Tester/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tester/main.c b/Tester/main.c index 950f214f..7cb1bb8f 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -75,7 +75,7 @@ static void vblank(GB_gameboy_t *gb) /* Detect common crashes and stop the test early */ if (frames < test_length - 1) { - if (gb->backtrace_size >= 0x80) { + if (gb->backtrace_size >= 0x300) { GB_log(gb, "A stack overflow has probably occurred.\n"); frames = test_length - 1; } From 9ca7540c69e72d4c8d2dede8bd4da1f81fa4c655 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 14 Oct 2016 01:19:39 +0300 Subject: [PATCH 0206/1216] Fixed dereferencing a non-banked address in the debugger --- Core/debugger.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index 13fd399f..ad0219ab 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -470,10 +470,14 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (depth == 0) { value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); banking_state_t state; + if (addr.bank) { save_banking_state(gb, &state); switch_banking_state(gb, addr.bank); + } value_t r = VALUE_16(GB_read_memory(gb, addr.value)); + if (addr.bank) { restore_banking_state(gb, &state); + } return r; } From 3fc4fcc5384125c73e56e3169c5e45106ec5aebd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 14 Oct 2016 01:30:54 +0300 Subject: [PATCH 0207/1216] Added a custom automation combo for Tsuri Sensei, to avoid an in-game buffer-overflow --- Tester/main.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 7cb1bb8f..ad5633ad 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -20,7 +20,7 @@ static char *bmp_filename; static char *log_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); -static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, do_not_stop; +static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, do_not_stop, push_a_twice; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -46,10 +46,13 @@ static void vblank(GB_gameboy_t *gb) load graphics. */ if (push_start_a && (frames < test_length - 120 || do_not_stop)) { unsigned combo_length = 40; - if (start_is_not_first) combo_length = 60; /* The start item in the menu is not the first, so also push down */ + if (start_is_not_first || push_a_twice) combo_length = 60; /* The start item in the menu is not the first, so also push down */ else if (a_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ - switch ((push_faster? frames * 2 : push_slower? frames / 2 : frames) % combo_length) { + switch ((push_faster ? frames * 2 : + push_slower ? frames / 2 : + push_a_twice? frames / 4: + frames) % combo_length) { case 0: gb->keys[7] = true; // Start down break; @@ -63,11 +66,15 @@ static void vblank(GB_gameboy_t *gb) gb->keys[b_is_confirm? 5: 4] = false; // A up (or B) break; case 40: - if (gb->boot_rom_finished) { + if (push_a_twice) { + gb->keys[b_is_confirm? 5: 4] = true; // A down (or B) + } + else if (gb->boot_rom_finished) { gb->keys[3] = true; // D-Pad Down down } break; case 50: + gb->keys[b_is_confirm? 5: 4] = false; // A down (or B) gb->keys[3] = false; // D-Pad Down up break; } @@ -307,6 +314,12 @@ int main(int argc, char **argv) push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0; push_slower = strcmp((const char *)(gb.rom + 0x134), "BAKENOU") == 0; do_not_stop = strcmp((const char *)(gb.rom + 0x134), "SPACE INVADERS") == 0; + + /* Pressing start while in the map in Tsuri Sensi will leak an internal screen-stack which + will eventually overflow, override an array of jump-table indexes, jump to a random + address, execute an invalid opcode, and crash. Pressing A twice while slowing down + will prevent this scenario. */ + push_a_twice = strcmp((const char *)(gb.rom + 0x134), "TURI SENSEI V1") == 0; /* Run emulation */ running = true; From 11f8c41305e54d44b6be90169d4c9615d2a462b3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 17 Oct 2016 18:51:43 +0300 Subject: [PATCH 0208/1216] Basic HUC3 support --- Core/debugger.c | 9 ++++++++- Core/gb.h | 9 +++++++-- Core/mbc.c | 27 +++++++++++++-------------- Core/memory.c | 11 +++++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index ad0219ab..f1a9c883 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1143,7 +1143,14 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, const debugger_command_t *com } if (cartridge->mbc_type) { - GB_log(gb, "MBC%d\n", cartridge->mbc_type); + static const char * const mapper_names[] = { + [GB_MBC1] = "MBC1", + [GB_MBC2] = "MBC2", + [GB_MBC3] = "MBC3", + [GB_MBC5] = "MBC5", + [GB_HUC3] = "HUC3", + }; + GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); if (cartridge->has_ram) { GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); diff --git a/Core/gb.h b/Core/gb.h index 0836dbbc..8173f3d9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -168,13 +168,13 @@ typedef struct { GB_MBC1, GB_MBC2, GB_MBC3, - GB_MBC4, // Does this exist??? GB_MBC5, + GB_HUC3, } mbc_type; enum { GB_STANDARD_MBC, GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */ - GB_CAMERA, /* Not emulated as well */ + GB_CAMERA, } mbc_subtype; bool has_ram; bool has_battery; @@ -283,6 +283,11 @@ typedef struct GB_gameboy_s { uint8_t rom_bank_high:1; uint8_t ram_bank:4; } mbc5; + + struct { + uint8_t rom_bank; + uint8_t ram_bank; + } huc3; }; uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ bool camera_registers_mapped; diff --git a/Core/mbc.c b/Core/mbc.c index ddc5a31f..b537a2cd 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -6,7 +6,7 @@ const GB_cartridge_t GB_cart_defs[256] = { // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type /* MBC SUBTYPE RAM BAT. RTC RUMB. EXTRA */ - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false, }, // 00h ROM ONLY + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY { GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1 { GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM { GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY @@ -17,7 +17,7 @@ const GB_cartridge_t GB_cart_defs[256] = { { GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM { GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY [0xB] = - // Todo: What are these? + /* Todo: Not supported yet */ { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01 { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY @@ -27,30 +27,25 @@ const GB_cartridge_t GB_cart_defs[256] = { { GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3 { GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM { GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY - [0x15] = - // Todo: Do these exist? - { GB_MBC4 , GB_STANDARD_MBC, false, false, false, false}, // 15h MBC4 - { GB_MBC4 , GB_STANDARD_MBC, true , false, false, false}, // 16h MBC4+RAM - { GB_MBC4 , GB_STANDARD_MBC, true , true , false, false}, // 17h MBC4+RAM+BATTERY [0x19] = { GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5 { GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM { GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + /* Todo: Rumble supported yet */ { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY [0xFC] = - // Todo: What are these? - { GB_MBC5 , GB_CAMERA , true, true, false, false}, // FCh POCKET CAMERA - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FEh HuC3 - { GB_MBC1 , GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY + { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) + { GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only) + { GB_MBC1 , GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings) }; void GB_update_mbc_mappings(GB_gameboy_t *gb) { switch (gb->cartridge_type->mbc_type) { - case GB_NO_MBC: case GB_MBC4: return; + case GB_NO_MBC: return; case GB_MBC1: /* Todo: some obscure behaviors of MBC1 are not supported. See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */ if (gb->mbc1.mode == 0) { @@ -91,8 +86,12 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); gb->mbc_ram_bank = gb->mbc5.ram_bank; break; + case GB_HUC3: + gb->mbc_rom_bank = gb->huc3.rom_bank; + gb->mbc_ram_bank = gb->huc3.ram_bank; + break; } - if (gb->mbc_rom_bank == 0 && gb->cartridge_type->mbc_type != GB_MBC5) { + if (gb->mbc_rom_bank == 0 && gb->cartridge_type->mbc_type != GB_MBC5 && gb->cartridge_type->mbc_type != GB_HUC3) { gb->mbc_rom_bank = 1; } } diff --git a/Core/memory.c b/Core/memory.c index beb9a098..0c54d012 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -280,7 +280,7 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { switch (gb->cartridge_type->mbc_type) { - case GB_NO_MBC: case GB_MBC4: return; + case GB_NO_MBC: return; case GB_MBC1: switch (addr & 0xF000) { case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; @@ -291,7 +291,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_MBC2: switch (addr & 0xF000) { - case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value == 0xA; break; + case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = (value & 0xF) == 0xA; break; case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; } break; @@ -319,6 +319,13 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; } break; + case GB_HUC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break; + case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; + } + break; } GB_update_mbc_mappings(gb); } From edf93abff176f8ab444532ca45f84a0ce3bc0c1e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 18 Oct 2016 00:31:07 +0300 Subject: [PATCH 0209/1216] According to Mooneye's test ROMs, this behavior does not happen on a CGB --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index b5ca8474..8dde5a96 100755 --- a/Core/display.c +++ b/Core/display.c @@ -368,7 +368,7 @@ void GB_display_run(GB_gameboy_t *gb) } // LY = 144 interrupt bug - if (gb->io_registers[GB_IO_LY] == 144) { + if (gb->io_registers[GB_IO_LY] == 144 && !gb->is_cgb) { /* User requests an interrupt on Mode 2 */ if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH && gb->io_registers[GB_IO_STAT] & 0x20) { // Mode 2 gb->stat_interrupt_line = true; From 18ec502cfe3e1aa544c8f1e12530a9c951f80321 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 18 Oct 2016 02:35:21 +0300 Subject: [PATCH 0210/1216] Fine tuning the stack-overflow tester detection --- Tester/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tester/main.c b/Tester/main.c index ad5633ad..65a12b6d 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -82,7 +82,7 @@ static void vblank(GB_gameboy_t *gb) /* Detect common crashes and stop the test early */ if (frames < test_length - 1) { - if (gb->backtrace_size >= 0x300) { + if (gb->backtrace_size >= 0x200 || (gb->registers[GB_REGISTER_SP] >= 0xfe00 && gb->registers[GB_REGISTER_SP] < 0xff80)) { GB_log(gb, "A stack overflow has probably occurred.\n"); frames = test_length - 1; } From ee51dec20e2d9838a6a7e5331e9fad5c4631289d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 19 Oct 2016 23:48:46 +0300 Subject: [PATCH 0211/1216] Added modifier syntax to debugger: Changed watch's syntax, added format modifier to print/eval, added count option to examine command. --- Core/debugger.c | 197 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 149 insertions(+), 48 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index f1a9c883..7857b59b 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -593,7 +593,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } struct debugger_command_s; -typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, const struct debugger_command_s *command); +typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); typedef struct debugger_command_s { const char *command; @@ -601,6 +601,7 @@ typedef struct debugger_command_s { debugger_command_imp_t *implementation; const char *help_string; // Null if should not appear in help const char *arguments_format; // For usage message + const char *modifiers_format; // For usage message } debugger_command_t; static const char *lstrip(const char *str) @@ -617,18 +618,30 @@ GB_log(gb, "Program is running. \n"); \ return false; \ } -static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) -{ - if (command->arguments_format) { - GB_log(gb, "Usage: %s %s\n", command->command, command->arguments_format); - } - else { - GB_log(gb, "Usage: %s\n", command->command); - } +#define NO_MODIFIERS \ +if (modifiers) { \ +print_usage(gb, command); \ +return true; \ } -static bool cont(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) { + GB_log(gb, "Usage: %s", command->command); + + if (command->arguments_format) { + GB_log(gb, "[/%s]", command->modifiers_format); + } + + if (command->arguments_format) { + GB_log(gb, " %s", command->arguments_format); + } + + GB_log(gb, "\n"); +} + +static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS STOPPED_ONLY if (strlen(lstrip(arguments))) { @@ -640,8 +653,9 @@ static bool cont(GB_gameboy_t *gb, char *arguments, const debugger_command_t *co return false; } -static bool next(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS STOPPED_ONLY if (strlen(lstrip(arguments))) { @@ -655,8 +669,9 @@ static bool next(GB_gameboy_t *gb, char *arguments, const debugger_command_t *co return false; } -static bool step(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS STOPPED_ONLY if (strlen(lstrip(arguments))) { @@ -667,8 +682,9 @@ static bool step(GB_gameboy_t *gb, char *arguments, const debugger_command_t *co return false; } -static bool finish(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS STOPPED_ONLY if (strlen(lstrip(arguments))) { @@ -682,8 +698,9 @@ static bool finish(GB_gameboy_t *gb, char *arguments, const debugger_command_t * return false; } -static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS STOPPED_ONLY if (strlen(lstrip(arguments))) { @@ -697,8 +714,9 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, const debugg return false; } -static bool registers(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -739,8 +757,9 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } -static bool breakpoint(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS if (strlen(lstrip(arguments)) == 0) { print_usage(gb, command); return true; @@ -804,8 +823,9 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, const debugger_command return true; } -static bool delete(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS if (strlen(lstrip(arguments)) == 0) { for (unsigned i = gb->n_breakpoints; i--;) { if (gb->breakpoints[i].condition) { @@ -864,7 +884,7 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } -static bool watch(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { print_usage: @@ -877,9 +897,13 @@ print_usage: return true; } + if (!modifiers) { + modifiers = "w"; + } + uint8_t flags = 0; - while (*arguments != ' ' && *arguments) { - switch (*arguments) { + while (*modifiers) { + switch (*modifiers) { case 'r': flags |= GB_WATCHPOINT_R; break; @@ -889,7 +913,7 @@ print_usage: default: goto print_usage; } - arguments++; + modifiers++; } if (!flags) { @@ -956,8 +980,9 @@ print_usage: return true; } -static bool unwatch(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS if (strlen(lstrip(arguments)) == 0) { for (unsigned i = gb->n_watchpoints; i--;) { if (gb->watchpoints[i].condition) { @@ -994,8 +1019,9 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, const debugger_command_t return true; } -static bool list(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -1077,22 +1103,61 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr) return _should_break(gb, full_addr); } -static bool print(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { print_usage(gb, command); return true; } + if (!modifiers || !modifiers[0]) { + modifiers = "a"; + } + else if (modifiers[1]) { + print_usage(gb, command); + return true; + } + bool error; value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); if (!error) { - GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); + switch (modifiers[0]) { + case 'a': + GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); + break; + case 'd': + GB_log(gb, "=%d\n", result.value); + break; + case 'x': + GB_log(gb, "=$%x\n", result.value); + break; + case 'o': + GB_log(gb, "=0%o\n", result.value); + break; + case 'b': + { + if (!result.value) { + GB_log(gb, "=%%0\n"); + break; + } + char binary[17]; + binary[16] = 0; + char *ptr = &binary[16]; + while (result.value) { + *(--ptr) = (result.value & 1)? '1' : '0'; + result.value >>= 1; + } + GB_log(gb, "=%%%s\n", ptr); + break; + } + default: + break; + } } return true; } -static bool examine(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { print_usage(gb, command); @@ -1101,33 +1166,54 @@ static bool examine(GB_gameboy_t *gb, char *arguments, const debugger_command_t bool error; value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t count = 32; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + if (!error) { if (addr.has_bank) { banking_state_t old_state; save_banking_state(gb, &old_state); switch_banking_state(gb, addr.bank); - GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); - for (int i = 0; i < 16; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + while (count) { + GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); + for (int i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); } - GB_log(gb, "\n"); restore_banking_state(gb, &old_state); } else { - GB_log(gb, "%04x: ", addr.value); - for (int i = 0; i < 16; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + while (count) { + GB_log(gb, "%04x: ", addr.value); + for (int i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); } - GB_log(gb, "\n"); } } return true; } -static bool mbc(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS + if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -1181,8 +1267,10 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, const debugger_command_t *com return true; } -static bool backtrace(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS + if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -1196,8 +1284,9 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, const debugger_command_ return true; } -static bool ticks(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS STOPPED_ONLY if (strlen(lstrip(arguments))) { @@ -1212,8 +1301,9 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, const debugger_command_t *c } -static bool palettes(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command) +static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { + NO_MODIFIERS if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -1242,7 +1332,7 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, const debugger_command_t return true; } -static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command); +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); #define HELP_NEWLINE "\n " @@ -1254,24 +1344,28 @@ static const debugger_command_t commands[] = { {"finish", 1, finish, "Run until the current function returns"}, {"backtrace", 2, backtrace, "Display the current call stack"}, {"bt", 2, }, /* Alias */ - {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected. (Experimental)"}, - {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used. "}, + {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, + {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ {"palettes", 3, palettes, "Displays the current CGB palettes"}, - {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression." HELP_NEWLINE + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE "Can also modify the condition of existing breakpoints.", "[ if ]"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE - "Can also modify the condition and type of existing watchpoints.", - " (r|w|rw) [ if ]"}, + "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE + "Default watchpoint type is write-only.", + "[ if ]", "(r|w|rw)"}, {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"}, {"list", 1, list, "List all set breakpoints and watchpoints"}, - {"print", 1, print, "Evaluate and print an expression", ""}, + {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE + "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE + "decimal (d), hexadecimal (x), octal (o) or binary (b).", + "", "format"}, {"eval", 2, }, /* Alias */ - {"examine", 2, examine, "Examine values at address", ""}, + {"examine", 2, examine, "Examine values at address", "", "count"}, {"x", 1, }, /* Alias */ {"help", 1, help, "List available commands or show help for the specified command", "[]"}, @@ -1308,7 +1402,7 @@ static void print_command_description(GB_gameboy_t *gb, const debugger_command_t GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); } -static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *ignored) +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored) { const debugger_command_t *command = find_command(arguments); if (command) { @@ -1512,9 +1606,16 @@ bool GB_debugger_do_command(GB_gameboy_t *gb, char *input) arguments = ""; } + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + const debugger_command_t *command = find_command(command_string); if (command) { - return command->implementation(gb, arguments, command); + return command->implementation(gb, arguments, modifiers, command); } else { GB_log(gb, "%s: no such command.\n", command_string); From 1b8832a7ff4bad523d7eb99ba9edb41119848789 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 19 Oct 2016 23:55:23 +0300 Subject: [PATCH 0212/1216] Added disassemble command --- Core/debugger.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Core/debugger.c b/Core/debugger.c index 7857b59b..01750e6f 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1210,6 +1210,42 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de return true; } +static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + arguments = "pc"; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t count = 5; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + GB_cpu_disassemble(gb, addr.value, count); + + restore_banking_state(gb, &old_state); + } + else { + GB_cpu_disassemble(gb, addr.value, count); + } + } + return true; +} + static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS @@ -1367,6 +1403,8 @@ static const debugger_command_t commands[] = { {"eval", 2, }, /* Alias */ {"examine", 2, examine, "Examine values at address", "", "count"}, {"x", 1, }, /* Alias */ + {"disassemble", 1, disassemble, "disassemble instructions at address", "", "count"}, + {"help", 1, help, "List available commands or show help for the specified command", "[]"}, {NULL,}, /* Null terminator */ From 2d51d134795ea81d0ca49cbdcd316bd151b7ad92 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 22 Oct 2016 00:49:32 +0300 Subject: [PATCH 0213/1216] Various optimizations --- Core/debugger.c | 4 +-- Core/gb.c | 6 +++- Core/gb.h | 29 +++++++++++++++-- Core/memory.c | 13 +++++--- Core/timing.c | 5 +-- Core/z80_cpu.c | 86 ++++++++++++++++++++++++------------------------- Tester/main.c | 6 ++++ 7 files changed, 91 insertions(+), 58 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 01750e6f..e606441e 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1568,7 +1568,6 @@ static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, u void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (gb->debug_stopped) return; - if (!gb->n_watchpoints) return; /* Try any-bank breakpoint */ value_t full_addr = (VALUE_16(addr)); @@ -1614,7 +1613,6 @@ static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) { if (gb->debug_stopped) return; - if (!gb->n_watchpoints) return; /* Try any-bank breakpoint */ value_t full_addr = (VALUE_16(addr)); @@ -1679,7 +1677,7 @@ next_command: if (input) { free(input); } - if (!gb->debug_stopped && should_break(gb, gb->pc)) { + if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc)) { gb->debug_stopped = true; GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_cpu_disassemble(gb, gb->pc, 5); diff --git a/Core/gb.c b/Core/gb.c index dd591d03..7a607f2e 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -426,9 +426,13 @@ exit: void GB_run(GB_gameboy_t *gb) { - GB_update_joyp(gb); GB_debugger_run(gb); GB_cpu_run(gb); + if (gb->vblank_just_occured) { + GB_update_joyp(gb); + GB_rtc_run(gb); + GB_debugger_handle_async_commands(gb); + } } void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) diff --git a/Core/gb.h b/Core/gb.h index 8173f3d9..6cf4c630 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -142,7 +142,7 @@ enum { #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 #define DIV_CYCLES (0x100) -#define INTERNAL_DIV_CYCLES (0x400) +#define INTERNAL_DIV_CYCLES (0x40000) #define FRAME_LENGTH 16742706 // in nanoseconds typedef enum { @@ -220,7 +220,32 @@ typedef struct GB_gameboy_s { GB_SECTION(core_state, /* Registers */ uint16_t pc; - uint16_t registers[GB_REGISTERS_16_BIT]; + union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp; + }; + struct { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t a, f, + b, c, + d, e, + h, l; +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t f, a, + c, b, + e, d, + l, h; +#else +#error Unable to detect endianess +#endif + }; + + }; uint8_t ime; uint8_t interrupt_enable; uint8_t cgb_ram_bank; diff --git a/Core/memory.c b/Core/memory.c index 0c54d012..3c04264f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -168,7 +168,6 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } /* Fall through */ case GB_IO_JOYP: - case GB_IO_DIV: case GB_IO_TMA: case GB_IO_LCDC: case GB_IO_SCY: @@ -188,6 +187,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return 0; } return gb->io_registers[GB_IO_TIMA]; + case GB_IO_DIV: + return gb->div_cycles >> 8; case GB_IO_HDMA5: if (!gb->is_cgb) { return 0xFF; @@ -270,7 +271,9 @@ static GB_read_function_t * const read_map[] = uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { - GB_debugger_test_read_watchpoint(gb, addr); + if (gb->n_watchpoints) { + GB_debugger_test_read_watchpoint(gb, addr); + } if (is_addr_in_dma_use(gb, addr)) { addr = gb->dma_current_src; } @@ -454,12 +457,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_DIV: GB_set_internal_div_counter(gb, 0); - 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; + GB_update_joyp(gb); return; case GB_IO_BIOS: @@ -618,7 +621,9 @@ static GB_write_function_t * const write_map[] = void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - GB_debugger_test_write_watchpoint(gb, addr, value); + if (gb->n_watchpoints) { + GB_debugger_test_write_watchpoint(gb, addr, value); + } if (is_addr_in_dma_use(gb, addr)) { /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ return; diff --git a/Core/timing.c b/Core/timing.c index 027df7b5..0e6a2a29 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -93,11 +93,8 @@ static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) { - /* DIV and TIMA increase when a specific high-bit becomes a low-bit. */ + /* TIMA increases when a specific high-bit becomes a low-bit. */ value &= INTERNAL_DIV_CYCLES - 1; - if (counter_overflow_check(gb->div_cycles, value, DIV_CYCLES)) { - gb->io_registers[GB_IO_DIV]++; - } if ((gb->io_registers[GB_IO_TAC] & 4) && counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index c5a35280..4f1fab91 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -515,40 +515,44 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) } } -static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode) -{ - uint8_t dst_register_id; - uint8_t dst_low; - uint8_t value; - GB_advance_cycles(gb, 4); +/* The LD r,r instruction is extremely common and extremely simple. Decoding this opcode at runtime is a significent + performance hit, so we generate functions for every ld x,y couple (including [hl]) at compile time using macros. */ - 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 { - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 4); - } - } - 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; - } - } +/* Todo: It's probably wise to do the same to all opcodes. */ +#define LD_X_Y(x, y) \ +static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ + GB_advance_cycles(gb, 4); \ + gb->x = gb->y;\ } +#define LD_X_DHL(x) \ +static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +GB_advance_cycles(gb, 4); \ +gb->x = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); \ +GB_advance_cycles(gb, 4);\ +} + +#define LD_DHL_Y(y) \ +static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +GB_advance_cycles(gb, 4); \ +GB_write_memory(gb, gb->registers[GB_REGISTER_HL], gb->y); \ +GB_advance_cycles(gb, 4);\ +} + +LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) +LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) +LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) +LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a) +LD_X_Y(h,b) LD_X_Y(h,c) LD_X_Y(h,d) LD_X_Y(h,e) LD_X_Y(h,l) LD_X_DHL(h) LD_X_Y(h,a) +LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL(l) LD_X_Y(l,a) +LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) +LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) + + static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; @@ -1308,14 +1312,14 @@ static GB_opcode_t *opcodes[256] = { 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, + nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, + ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ + ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, + ld_h_b, ld_h_c, ld_h_d, ld_h_e, nop, ld_h_l, ld_h_dhl, ld_h_a, /* 6X */ + ld_l_b, ld_l_c, ld_l_d, ld_l_e, ld_l_h, nop, ld_l_dhl, ld_l_a, + ld_dhl_b, ld_dhl_c, ld_dhl_d, ld_dhl_e, ld_dhl_h, ld_dhl_l, halt, ld_dhl_a, /* 7X */ + ld_a_b, ld_a_c, ld_a_d, ld_a_e, ld_a_h, ld_a_l, ld_a_dhl, nop, 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 */ @@ -1333,7 +1337,6 @@ static GB_opcode_t *opcodes[256] = { 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 GB_cpu_run(GB_gameboy_t *gb) { gb->vblank_just_occured = false; @@ -1378,9 +1381,4 @@ void GB_cpu_run(GB_gameboy_t *gb) else { GB_advance_cycles(gb, 4); } - - if (gb->vblank_just_occured) { - GB_rtc_run(gb); - GB_debugger_handle_async_commands(gb); - } } diff --git a/Tester/main.c b/Tester/main.c index 65a12b6d..cd1bb088 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -39,6 +39,11 @@ const char bmp_header[] = { uint32_t bitmap[160*144]; +static char *async_input_callback(GB_gameboy_t *gb) +{ + return NULL; +} + static void vblank(GB_gameboy_t *gb) { /* Do not press any buttons during the last two seconds, this might cause a @@ -296,6 +301,7 @@ int main(int argc, char **argv) GB_set_pixels_output(&gb, &bitmap[0]); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_log_callback(&gb, log_callback); + GB_set_async_input_callback(&gb, async_input_callback); if (GB_load_rom(&gb, filename)) { perror("Failed to load ROM"); From 47aaf44017768020f44458c0b139ec45a54bbd19 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 22 Oct 2016 15:37:03 +0300 Subject: [PATCH 0214/1216] Rumble API --- Core/gb.c | 10 ++++++++++ Core/gb.h | 4 ++++ Core/memory.c | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 7a607f2e..6e82c349 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -350,6 +350,11 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) memcpy(gb, &save, sizeof(save)); errno = 0; + + if (gb->cartridge_type->has_rumble && gb->rumble_callback) { + gb->rumble_callback(gb, gb->rumble_state); + } + error: fclose(f); return errno; @@ -489,6 +494,11 @@ void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_pre gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change}; } +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) +{ + gb->rumble_callback = callback; +} + void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) { if (gb->audio_buffer) { diff --git a/Core/gb.h b/Core/gb.h index 6cf4c630..9b1a64a9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -161,6 +161,7 @@ typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_ typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); typedef struct { enum { @@ -317,6 +318,7 @@ typedef struct GB_gameboy_s { uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ bool camera_registers_mapped; uint8_t camera_registers[0x36]; + bool rumble_state; ); @@ -415,6 +417,7 @@ typedef struct GB_gameboy_s { GB_infrared_callback_t infrared_callback; GB_camera_get_pixel_callback_t camera_get_pixel_callback; GB_camera_update_request_callback_t camera_update_request_callback; + GB_rumble_callback_t rumble_callback; /* IR */ long cycles_since_ir_change; @@ -493,4 +496,5 @@ void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callb void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); void GB_set_infrared_input(GB_gameboy_t *gb, bool state); void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); #endif /* GB_h */ diff --git a/Core/memory.c b/Core/memory.c index 3c04264f..0ea1330b 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -317,6 +317,15 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x2000: gb->mbc5.rom_bank_low = value; break; case 0x3000: gb->mbc5.rom_bank_high = value; break; case 0x4000: case 0x5000: + if (gb->cartridge_type->has_rumble) { + if (!!(value & 8) != gb->rumble_state) { + gb->rumble_state = !gb->rumble_state; + if (gb->rumble_callback) { + gb->rumble_callback(gb, gb->rumble_state); + } + } + value &= 7; + } gb->mbc5.ram_bank = value; gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA; break; From 388fb600dee3e2e60deb3a0821450cbab688e68d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 27 Oct 2016 00:14:02 +0300 Subject: [PATCH 0215/1216] Added VRAM-debugging APIs, added VRAM viewer to Cocoa Port, fixed uninitialized VRAM, fixed memory viewer crash --- Cocoa/Document.h | 15 +- Cocoa/Document.m | 296 +++++++++++++++++++++++++ Cocoa/Document.xib | 478 ++++++++++++++++++++++++++++++++++++++++ Cocoa/GBColorCell.h | 5 + Cocoa/GBColorCell.m | 42 ++++ Cocoa/GBImageCell.h | 5 + Cocoa/GBImageCell.m | 10 + Cocoa/GBImageView.h | 21 ++ Cocoa/GBImageView.m | 92 ++++++++ Cocoa/MainMenu.xib | 6 + Core/display.c | 179 ++++++++++++++- Core/display.h | 31 +++ Core/gb.c | 4 + HexFiend/HFController.m | 11 +- Makefile | 2 +- 15 files changed, 1185 insertions(+), 12 deletions(-) create mode 100644 Cocoa/GBColorCell.h create mode 100644 Cocoa/GBColorCell.m create mode 100644 Cocoa/GBImageCell.h create mode 100644 Cocoa/GBImageCell.m create mode 100644 Cocoa/GBImageView.h create mode 100644 Cocoa/GBImageView.m diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 32cfb6b7..1a722e9e 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -1,7 +1,8 @@ #import #include "GBView.h" +#include "GBImageView.h" -@interface Document : NSDocument +@interface Document : NSDocument @property (strong) IBOutlet GBView *view; @property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSPanel *consoleWindow; @@ -12,6 +13,18 @@ @property (readonly) GB_gameboy_t *gameboy; @property (strong) IBOutlet NSTextField *memoryBankInput; @property (strong) IBOutlet NSToolbarItem *memoryBankItem; +@property (strong) IBOutlet GBImageView *tilesetImageView; +@property (strong) IBOutlet NSPopUpButton *tilesetPaletteButton; +@property (strong) IBOutlet GBImageView *tilemapImageView; +@property (strong) IBOutlet NSPopUpButton *tilemapPaletteButton; +@property (strong) IBOutlet NSPopUpButton *tilemapMapButton; +@property (strong) IBOutlet NSPopUpButton *TilemapSetButton; +@property (strong) IBOutlet NSButton *gridButton; +@property (strong) IBOutlet NSTabView *vramTabView; +@property (strong) IBOutlet NSPanel *vramWindow; +@property (strong) IBOutlet NSTextField *vramStatusLabel; +@property (strong) IBOutlet NSTableView *paletteTableView; +@property (strong) IBOutlet NSTableView *spritesTableView; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 1cf25450..9c388596 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -7,10 +7,12 @@ #include "debugger.h" #include "memory.h" #include "camera.h" +#include "display.h" #include "HexFiend/HexFiend.h" #include "GBMemoryByteArray.h" /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ +/* Todo: Split into category files! This is so messy!!! */ @interface Document () { @@ -30,6 +32,11 @@ AVCaptureSession *cameraSession; AVCaptureConnection *cameraConnection; AVCaptureStillImageOutput *cameraOutput; + + GB_oam_info_t oamInfo[40]; + uint16_t oamCount; + uint8_t oamHeight; + bool oamUpdating; } @property GBAudioClient *audioClient; @@ -140,6 +147,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [self.view flip]; GB_set_pixels_output(&gb, self.view.pixels); + [self reloadVRAMData: nil]; } - (void) run @@ -262,6 +270,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"], window_frame.size.height); [self.mainWindow setFrame:window_frame display:YES]; + self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; [self start]; } @@ -450,6 +459,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) NSString *nsstring = @(string); // For ref-counting dispatch_async(dispatch_get_main_queue(), ^{ [hex_controller reloadData]; + [self reloadVRAMData: nil]; NSFont *font = [NSFont userFixedPitchFontOfSize:12]; NSUnderlineStyle underline = NSUnderlineStyleNone; @@ -595,6 +605,28 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) } } ++ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale +{ + CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data); + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; + CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; + + CGImageRef iref = CGImageCreate(width, + height, + 8, + 32, + 4 * width, + colorSpaceRef, + bitmapInfo, + provider, + NULL, + YES, + renderingIntent); + + return [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(width * scale, height * scale)]; +} + - (void) reloadMemoryView { if (self.memoryWindow.isVisible) { @@ -602,6 +634,79 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) } } +- (IBAction) reloadVRAMData: (id) sender +{ + if (self.vramWindow.isVisible) { + switch ([self.vramTabView.tabViewItems indexOfObject:self.vramTabView.selectedTabViewItem]) { + case 0: + /* Tileset */ + { + GB_palette_type_t palette_type = GB_PALETTE_NONE; + NSUInteger palette_menu_index = self.tilesetPaletteButton.indexOfSelectedItem; + if (palette_menu_index) { + palette_type = palette_menu_index > 8? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND; + } + size_t bufferLength = 256 * 192 * 4; + NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength]; + data.length = bufferLength; + GB_draw_tileset(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 1) & 7); + + self.tilesetImageView.image = [Document imageFromData:data width:256 height:192 scale:1.0]; + self.tilesetImageView.layer.magnificationFilter = kCAFilterNearest; + } + break; + + case 1: + /* Tilemap */ + { + GB_palette_type_t palette_type = GB_PALETTE_NONE; + NSUInteger palette_menu_index = self.tilemapPaletteButton.indexOfSelectedItem; + if (palette_menu_index > 1) { + palette_type = palette_menu_index > 9? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND; + } + else if (palette_menu_index == 1) { + palette_type = GB_PALETTE_AUTO; + } + + size_t bufferLength = 256 * 256 * 4; + NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength]; + data.length = bufferLength; + GB_draw_tilemap(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 2) & 7, + (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem, + (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem); + + self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0]; + self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest; + } + break; + + case 2: + /* OAM */ + { + oamCount = GB_get_oam_info(&gb, oamInfo); + oamHeight = (gb.io_registers[GB_IO_LCDC] & 4) ? 16:8; + dispatch_async(dispatch_get_main_queue(), ^{ + if (!oamUpdating) { + oamUpdating = true; + [self.spritesTableView reloadData]; + oamUpdating = false; + } + }); + } + break; + + case 3: + /* Palettes */ + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.paletteTableView reloadData]; + }); + } + break; + } + } +} + - (IBAction) showMemory:(id)sender { if (!hex_controller) { @@ -777,4 +882,195 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return ret; } + +- (IBAction)toggleTilesetGrid:(NSButton *)sender +{ + if (sender.state) { + self.tilesetImageView.horizontalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:128], + + ]; + self.tilesetImageView.verticalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:64], + ]; + self.tilemapImageView.horizontalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + ]; + self.tilemapImageView.verticalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + ]; + } + else { + self.tilesetImageView.horizontalGrids = nil; + self.tilesetImageView.verticalGrids = nil; + self.tilemapImageView.horizontalGrids = nil; + self.tilemapImageView.verticalGrids = nil; + } +} + +- (IBAction)vramTabChanged:(NSSegmentedControl *)sender +{ + [self.vramTabView selectTabViewItemAtIndex:[sender selectedSegment]]; + [self reloadVRAMData:sender]; + [self.vramTabView.selectedTabViewItem.view addSubview:self.gridButton]; + self.gridButton.hidden = [sender selectedSegment] >= 2; + + NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height; + CGRect window_rect = self.vramWindow.frame; + window_rect.origin.y += window_rect.size.height; + switch ([sender selectedSegment]) { + case 0: + window_rect.size.height = 384 + height_diff + 48; + break; + case 1: + case 2: + window_rect.size.height = 512 + height_diff + 48; + break; + case 3: + window_rect.size.height = 20 * 16 + height_diff + 24; + break; + + default: + break; + } + window_rect.origin.y -= window_rect.size.height; + [self.vramWindow setFrame:window_rect display:YES animate:YES]; +} + +- (void)mouseDidLeaveImageView:(GBImageView *)view +{ + self.vramStatusLabel.stringValue = @""; +} + +- (void)imageView:(GBImageView *)view mouseMovedToX:(NSUInteger)x Y:(NSUInteger)y +{ + if (view == self.tilesetImageView) { + uint8_t bank = x >= 128? 1 : 0; + x &= 127; + uint16_t tile = x / 8 + y / 8 * 16; + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x at %d:$%04x", tile & 0xFF, bank, 0x8000 + tile * 0x10]; + } + else if (view == self.tilemapImageView) { + uint16_t map_offset = x / 8 + y / 8 * 32; + uint16_t map_base = 0x1800; + GB_map_type_t map_type = (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem; + GB_tileset_type_t tileset_type = (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem; + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb.io_registers[GB_IO_LCDC] & 0x08)) { + map_base = 0x1c00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb.io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + uint8_t tile = gb.vram[map_base + map_offset]; + uint16_t tile_address = 0; + if (tileset_type == GB_TILESET_8000) { + tile_address = 0x8000 + tile * 0x10; + } + else { + tile_address = 0x9000 + (int8_t)tile * 0x10; + } + + if (gb.is_cgb) { + uint8_t attributes = gb.vram[map_base + map_offset + 0x2000]; + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d)", + tile, + attributes & 0x8? 1 : 0, + tile_address, + 0x8000 + map_base + map_offset, + (attributes & 0x80) ? 'P' : '-', + (attributes & 0x40) ? 'V' : '-', + (attributes & 0x20) ? 'H' : '-', + attributes & 0x8? 1 : 0, + attributes & 0x7 + ]; + } + else { + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x ($%04x) at map address $%04x", + tile, + tile_address, + 0x8000 + map_base + map_offset + ]; + } + + } +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + if (tableView == self.paletteTableView) { + return 16; /* 8 BG palettes, 8 OBJ palettes*/ + } + else if (tableView == self.spritesTableView) { + return oamCount; + } + return 0; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (tableView == self.paletteTableView) { + if (columnIndex == 0) { + return [NSString stringWithFormat:@"%s %d", row >=8? "Object" : "Background", (int)(row & 7)]; + } + + uint16_t index = columnIndex - 1 + (row & 7) * 4; + return @(((row >= 8? gb.sprite_palletes_data : gb.background_palletes_data)[(index << 1) + 1] << 8) | + (row >= 8? gb.sprite_palletes_data : gb.background_palletes_data)[(index << 1)]); + } + else if (tableView == self.spritesTableView) { + switch (columnIndex) { + case 0: + return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image length:64 * 4] width:8 height:oamHeight scale:16.0/oamHeight]; + case 1: + return @((int)oamInfo[row].x - 8); + case 2: + return @((int)oamInfo[row].y - 16); + case 3: + return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile]; + case 4: + return [NSString stringWithFormat:@"$%04x", 0x8000 + oamInfo[row].tile * 0x10]; + case 5: + return [NSString stringWithFormat:@"$%04x", oamInfo[row].oam_addr]; + case 6: + if (gb.cgb_mode) { + return [NSString stringWithFormat:@"%c%c%c%d%d", + oamInfo[row].flags & 0x80? 'P' : '-', + oamInfo[row].flags & 0x40? 'Y' : '-', + oamInfo[row].flags & 0x20? 'X' : '-', + oamInfo[row].flags & 0x08? 1 : 0, + oamInfo[row].flags & 0x07]; + } + return [NSString stringWithFormat:@"%c%c%c%d", + oamInfo[row].flags & 0x80? 'P' : '-', + oamInfo[row].flags & 0x40? 'Y' : '-', + oamInfo[row].flags & 0x20? 'X' : '-', + oamInfo[row].flags & 0x10? 1 : 0]; + case 7: + return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many sprites in line": @""; + + } + } + return nil; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row +{ + return tableView == self.spritesTableView; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row +{ + return NO; +} + +- (IBAction)showVRAMViewer:(id)sender +{ + [self.vramWindow makeKeyAndOrderFront:sender]; +} @end \ No newline at end of file diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 75827e97..c9cf907b 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -6,15 +6,27 @@ + + + + + + + + + + + + @@ -50,6 +62,7 @@ + @@ -201,5 +214,470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/GBColorCell.h b/Cocoa/GBColorCell.h new file mode 100644 index 00000000..a622c788 --- /dev/null +++ b/Cocoa/GBColorCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBColorCell : NSTextFieldCell + +@end diff --git a/Cocoa/GBColorCell.m b/Cocoa/GBColorCell.m new file mode 100644 index 00000000..afbbc8d1 --- /dev/null +++ b/Cocoa/GBColorCell.m @@ -0,0 +1,42 @@ +#import "GBColorCell.h" + +static inline double scale_channel(uint8_t x) +{ + x &= 0x1f; + return x / 31.0; +} + +@implementation GBColorCell +{ + NSInteger _integerValue; +} + +- (void)setObjectValue:(id)objectValue +{ + _integerValue = [objectValue integerValue]; + super.objectValue = [NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)]; +} + +- (NSInteger)integerValue +{ + return _integerValue; +} + +- (int)intValue +{ + return (int)_integerValue; +} + + +- (NSColor *) backgroundColor +{ + uint16_t color = self.integerValue; + return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0]; +} + +- (BOOL)drawsBackground +{ + return YES; +} + +@end diff --git a/Cocoa/GBImageCell.h b/Cocoa/GBImageCell.h new file mode 100644 index 00000000..0323b41d --- /dev/null +++ b/Cocoa/GBImageCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBImageCell : NSImageCell + +@end diff --git a/Cocoa/GBImageCell.m b/Cocoa/GBImageCell.m new file mode 100644 index 00000000..6f54ec83 --- /dev/null +++ b/Cocoa/GBImageCell.m @@ -0,0 +1,10 @@ +#import "GBImageCell.h" + +@implementation GBImageCell +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + CGContextSetInterpolationQuality(context, kCGInterpolationNone); + [super drawWithFrame:cellFrame inView:controlView]; +} +@end diff --git a/Cocoa/GBImageView.h b/Cocoa/GBImageView.h new file mode 100644 index 00000000..22a8829e --- /dev/null +++ b/Cocoa/GBImageView.h @@ -0,0 +1,21 @@ +#import + +@protocol GBImageViewDelegate; + +@interface GBImageViewGridConfiguration : NSObject +@property NSColor *color; +@property NSUInteger size; +- (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size; +@end + +@interface GBImageView : NSImageView +@property (nonatomic) NSArray *horizontalGrids; +@property (nonatomic) NSArray *verticalGrids; +@property (weak) IBOutlet id delegate; +@end + +@protocol GBImageViewDelegate +@optional +- (void) mouseDidLeaveImageView: (GBImageView *)view; +- (void) imageView: (GBImageView *)view mouseMovedToX:(NSUInteger) x Y:(NSUInteger) y; +@end diff --git a/Cocoa/GBImageView.m b/Cocoa/GBImageView.m new file mode 100644 index 00000000..674d4b2c --- /dev/null +++ b/Cocoa/GBImageView.m @@ -0,0 +1,92 @@ +#import "GBImageView.h" + +@implementation GBImageViewGridConfiguration +- (instancetype)initWithColor:(NSColor *)color size:(NSUInteger)size +{ + self = [super init]; + self.color = color; + self.size = size; + return self; +} +@end + +@implementation GBImageView +{ + NSTrackingArea *trackingArea; +} +- (void)drawRect:(NSRect)dirtyRect +{ + CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + CGContextSetInterpolationQuality(context, kCGInterpolationNone); + [super drawRect:dirtyRect]; + CGFloat y_ratio = self.frame.size.height / self.image.size.height; + CGFloat x_ratio = self.frame.size.width / self.image.size.width; + for (GBImageViewGridConfiguration *conf in self.verticalGrids) { + [conf.color set]; + for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) { + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(0, y + 0.5)]; + [line lineToPoint:NSMakePoint(self.frame.size.width, y + 0.5)]; + [line setLineWidth:1.0]; + [line stroke]; + } + } + + for (GBImageViewGridConfiguration *conf in self.horizontalGrids) { + [conf.color set]; + for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) { + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(x + 0.5, 0)]; + [line lineToPoint:NSMakePoint(x + 0.5, self.frame.size.height)]; + [line setLineWidth:1.0]; + [line stroke]; + } + } +} + +- (void)setHorizontalGrids:(NSArray *)horizontalGrids +{ + self->_horizontalGrids = horizontalGrids; + [self setNeedsDisplay]; +} + +- (void)setVerticalGrids:(NSArray *)verticalGrids +{ + self->_verticalGrids = verticalGrids; + [self setNeedsDisplay]; +} + +- (void)updateTrackingAreas +{ + if(trackingArea != nil) { + [self removeTrackingArea:trackingArea]; + } + + trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + if ([self.delegate respondsToSelector:@selector(mouseDidLeaveImageView:)]) { + [self.delegate mouseDidLeaveImageView:self]; + } +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + if ([self.delegate respondsToSelector:@selector(imageView:mouseMovedToX:Y:)]) { + NSPoint location = [self convertPoint:theEvent.locationInWindow fromView:nil]; + location.x /= self.bounds.size.width; + location.y /= self.bounds.size.height; + location.y = 1 - location.y; + location.x *= self.image.size.width; + location.y *= self.image.size.height; + [self.delegate imageView:self mouseMovedToX:(NSUInteger)location.x Y:(NSUInteger)location.y]; + } +} + +@end diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index ae3a5008..85376a2f 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -375,6 +375,12 @@ + + + + + +
    diff --git a/Core/display.c b/Core/display.c index 8dde5a96..66d7f2ba 100755 --- a/Core/display.c +++ b/Core/display.c @@ -11,14 +11,12 @@ #include #endif -#pragma pack(push, 1) -typedef struct { +typedef struct __attribute__((packed)) { uint8_t y; uint8_t x; uint8_t tile; uint8_t flags; } GB_sprite_t; -#pragma pack(pop) static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) { @@ -439,3 +437,178 @@ updateSTAT: gb->io_registers[GB_IO_IF] |= 2; } } + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + + switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + default: + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palletes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palletes_rgb + (4 * (palette_index & 7)); + break; + } + + for (unsigned y = 0; y < 192; y++) { + for (unsigned x = 0; x < 256; x++) { + if (x >= 128 && !gb->is_cgb) { + *(dest++) = gb->background_palletes_rgb[0]; + continue; + } + uint16_t tile = (x % 128) / 8 + y / 8 * 16; + uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0); + uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1); + + if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_BACKGROUND) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + else if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_OAM) { + pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3); + } + } + } + + + *(dest++) = palette[pixel]; + } + } +} + +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + uint16_t map = 0x1800; + + switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palletes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palletes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_AUTO: + break; + } + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) { + map = 0x1c00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + for (unsigned y = 0; y < 256; y++) { + for (unsigned x = 0; x < 256; x++) { + uint8_t tile = gb->vram[map + x/8 + y/8 * 32]; + uint16_t tile_address; + uint8_t attributes = 0; + + if (tileset_type == GB_TILESET_8800) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x8) { + tile_address += 0x2000; + } + + uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) | + ((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1); + + if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + + if (palette) { + *(dest++) = palette[pixel]; + } + else { + *(dest++) = gb->background_palletes_rgb[(attributes & 7) * 4 + pixel]; + } + } + } +} + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest) +{ + uint8_t count = 0; + unsigned sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; + uint8_t oam_to_dest_index[40] = {0,}; + for (unsigned y = 0; y < 144; y++) { + GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + uint8_t sprites_in_line = 0; + for (uint8_t i = 0; i < 40; i++, sprite++) { + int sprite_y = sprite->y - 16; + bool obscured = false; + // Is sprite not in this line? + if (sprite_y > y || sprite_y + sprite_height <= y) continue; + if (++sprites_in_line == 11) obscured = true; + + GB_oam_info_t *info = NULL; + if (!oam_to_dest_index[i]) { + info = dest + count; + oam_to_dest_index[i] = ++count; + info->x = sprite->x; + info->y = sprite->y; + info->tile = sprite_height == 16? sprite->tile & 0xFE : sprite->tile; + info->flags = sprite->flags; + info->obscured_by_line_limit = false; + info->oam_addr = 0xFE00 + i * sizeof(*sprite); + } + else { + info = dest + oam_to_dest_index[i] - 1; + } + info->obscured_by_line_limit |= obscured; + } + } + + + for (unsigned i = 0; i < count; i++) { + uint16_t vram_address = dest[i].tile * 0x10; + uint8_t flags = dest[i].flags; + uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0); + + for (unsigned y = 0; y < sprite_height; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | + ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); + + if (!gb->cgb_mode) { + color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3; + } + dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?sprite_height - 1 -y:y) * 8] = gb->sprite_palletes_rgb[palette * 4 + color]; + } + vram_address += 2; + } + } + return count; +} \ No newline at end of file diff --git a/Core/display.h b/Core/display.h index c1542841..8cc6bf7f 100644 --- a/Core/display.h +++ b/Core/display.h @@ -4,4 +4,35 @@ #include "gb.h" void GB_display_run(GB_gameboy_t *gb); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); + +typedef enum { + GB_PALETTE_NONE, + GB_PALETTE_BACKGROUND, + GB_PALETTE_OAM, + GB_PALETTE_AUTO, +} GB_palette_type_t; + +typedef enum { + GB_MAP_AUTO, + GB_MAP_9800, + GB_MAP_9C00, +} GB_map_type_t; + +typedef enum { + GB_TILESET_AUTO, + GB_TILESET_8800, + GB_TILESET_8000, +} GB_tileset_type_t; + +typedef struct { + uint32_t image[128]; + uint8_t x, y, tile, flags; + uint16_t oam_addr; + bool obscured_by_line_limit; +} GB_oam_info_t; + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest); #endif /* display_h */ diff --git a/Core/gb.c b/Core/gb.c index 6e82c349..2d5fff45 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -95,7 +95,9 @@ void GB_init(GB_gameboy_t *gb) gb->magic = (uintptr_t)'SAME'; gb->version = GB_STRUCT_VERSION; gb->ram = malloc(gb->ram_size = 0x2000); + memset(gb->ram, 0, gb->ram_size); gb->vram = malloc(gb->vram_size = 0x2000); + memset(gb->vram, 0, gb->vram_size); gb->mbc_rom_bank = 1; gb->last_rtc_second = time(NULL); @@ -121,7 +123,9 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->magic = (uintptr_t)'SAME'; gb->version = GB_STRUCT_VERSION; gb->ram = malloc(gb->ram_size = 0x2000 * 8); + memset(gb->ram, 0, gb->ram_size); gb->vram = malloc(gb->vram_size = 0x2000 * 2); + memset(gb->vram, 0, gb->vram_size); gb->is_cgb = true; gb->cgb_mode = true; diff --git a/HexFiend/HFController.m b/HexFiend/HFController.m index 73a31b28..74e033c0 100644 --- a/HexFiend/HFController.m +++ b/HexFiend/HFController.m @@ -19,11 +19,7 @@ /* Used for the anchor range and location */ #define NO_SELECTION ULLONG_MAX -#if ! NDEBUG #define VALIDATE_SELECTION() [self _ensureSelectionIsValid] -#else -#define VALIDATE_SELECTION() do { } while (0) -#endif #define BENCHMARK_BYTEARRAYS 0 @@ -456,7 +452,6 @@ static inline Class preferredByteArrayClass(void) { return [HFRangeWrapper organizeAndMergeRanges:result]; } -#if ! NDEBUG - (void)_ensureSelectionIsValid { HFASSERT(selectedContentsRanges != nil); HFASSERT([selectedContentsRanges count] > 0); @@ -464,11 +459,13 @@ static inline Class preferredByteArrayClass(void) { FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { EXPECT_CLASS(wrapper, HFRangeWrapper); HFRange range = [wrapper HFRange]; - HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + if (!HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))){ + [self setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(0, 0)]]]; + return; + } if (onlyOneWrapper == NO) HFASSERT(range.length > 0); /* If we have more than one wrapper, then none of them should be zero length */ } } -#endif - (void)_setSingleSelectedContentsRange:(HFRange)newSelection { HFASSERT(HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))); diff --git a/Makefile b/Makefile index 8fe77050..f91fc8dc 100755 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ endif ifeq ($(PLATFORM),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 -framework Carbon +LDFLAGS += -framework AppKit -framework Carbon -framework QuartzCore SDL_LDFLAGS := -framework SDL endif From 15f641258129fe10a26afe19b2acd77eec5abc44 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 29 Oct 2016 22:54:32 +0300 Subject: [PATCH 0216/1216] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8457e8ac..a3a1fc27 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ SameBoy is an open source Gameboy (DMG) and Gameboy Color (CGB) emulator, writte Features common to both Cocoa and SDL versions: * Supports Gameboy (DMG) and Gameboy Color (CGB) emulation * Lets you choose the model you want to emulate regardless of ROM + * High quality 96KHz audio + * Battery save support + * Save states * Includes open source DMG and CGB boot ROMs: * Complete support for (and documentation of) *all* game-specific palettes in the CGB boot ROM, for accurate emulation of Gameboy games on a Gameboy Color * Supports manual palette selection with key combinations, with 4 additional new palettes (A + B + direction) * Supports palette selection in a CGB game, forcing it to run in 'paletted' DMG mode, if ROM allows doing so. * Support for games with a non-Nintendo logo in the header * No long animation in the DMG boot - * Has a text-based debugger with an expression evaluator + * Advanced text-based debugger with an expression evaluator, disassembler, conditional breakpoints, conditional watchpoints, backtracing and other features * Emulates [PCM_12 and PCM_34 registers](https://github.com/LIJI32/GBVisualizer) * Emulates LCD timing effects, supporting the Demotronic trick, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos - * Accurate instruction and memory timings + * Extermely high accuracy * Real time clock emulation - * High quality 96KHz audio - * Battery save support - * Save states Features currently supported only with the Cocoa version: * Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars @@ -29,7 +29,7 @@ Features currently supported only with the Cocoa version: * GameBoy Camera support ## Compatibility -SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. The latest results for SameBoy's automatic tester are available [here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html). +SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html). ## Compilation SameBoy requires the following tools and libraries to build: @@ -44,6 +44,6 @@ On Windows, SameBoy also requires: * [GnuWin](http://gnuwin32.sourceforge.net/) * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively. -To compile, simply run `make`. The targets are cocoa (Default for OS X), sdl (Default for everything else) and bootroms. You may also specify CONF=debug (default) or CONF=release to control optimization and symbols, and specify BOOTROMS_DIR=... to a directory containing precomiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs. +To compile, simply run `make`. The targets are cocoa (Default for OS X), sdl (Default for everything else), bootroms and tester. You may also specify CONF=debug (default) or CONF=release to control optimization and symbols, and specify BOOTROMS_DIR=... to a directory containing precompiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs. SameBoy was compiled and tested on OS X, Ubuntu and Windows 7 32-bit. From 22c34e109553de641da4a09bb9c1cc157770bd80 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Nov 2016 01:58:53 +0200 Subject: [PATCH 0217/1216] Serial API --- Core/gb.c | 29 +++++++++++++++++++++++++++++ Core/gb.h | 15 ++++++++++++++- Core/memory.c | 3 +++ Core/timing.c | 9 ++++++++- 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 2d5fff45..00495909 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -503,6 +503,35 @@ void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) gb->rumble_callback = callback; } +void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback) +{ + gb->serial_transfer_start_callback = callback; +} + +void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback) +{ + gb->serial_transfer_end_callback = callback; +} + +uint8_t GB_serial_get_data(GB_gameboy_t *gb) +{ + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial read request while using internal clock. \n"); + return 0xFF; + } + return gb->io_registers[GB_IO_SB]; +} +void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data) +{ + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial write request while using internal clock. \n"); + return; + } + gb->io_registers[GB_IO_SB] = data; +} + void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) { if (gb->audio_buffer) { diff --git a/Core/gb.h b/Core/gb.h index 9b1a64a9..63ea824e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -162,6 +162,9 @@ typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_si typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); +typedef void (*GB_serial_transfer_start_callback_t)(GB_gameboy_t *gb, uint8_t byte_to_send); +typedef uint8_t (*GB_serial_transfer_end_callback_t)(GB_gameboy_t *gb); + typedef struct { enum { @@ -418,7 +421,8 @@ typedef struct GB_gameboy_s { GB_camera_get_pixel_callback_t camera_get_pixel_callback; GB_camera_update_request_callback_t camera_update_request_callback; GB_rumble_callback_t rumble_callback; - + GB_serial_transfer_start_callback_t serial_transfer_start_callback; + GB_serial_transfer_end_callback_t serial_transfer_end_callback; /* IR */ long cycles_since_ir_change; long cycles_since_input_ir_change; @@ -497,4 +501,13 @@ void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) void GB_set_infrared_input(GB_gameboy_t *gb, bool state); void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); + +/* These APIs are used when using internal clock */ +void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback); +void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback); + +/* These APIs are used when using external clock */ +uint8_t GB_serial_get_data(GB_gameboy_t *gb); +void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); + #endif /* GB_h */ diff --git a/Core/memory.c b/Core/memory.c index 0ea1330b..ecc9e541 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -576,6 +576,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_SC] = value | (~0x83); if ((value & 0x80) && (value & 0x1) ) { gb->serial_cycles = gb->cgb_mode && (value & 2)? 128 : 4096; + if (gb->serial_transfer_start_callback) { + gb->serial_transfer_start_callback(gb, gb->io_registers[GB_IO_SB]); + } } else { gb->serial_cycles = 0; diff --git a/Core/timing.c b/Core/timing.c index 0e6a2a29..c1f8ebcf 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -45,7 +45,14 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) if (gb->serial_cycles <= cycles) { gb->serial_cycles = 0; gb->io_registers[GB_IO_SC] &= ~0x80; - gb->io_registers[GB_IO_SB] = 0xFF; + /* TODO: Does SB "update" bit by bit? */ + if (gb->serial_transfer_end_callback) { + gb->io_registers[GB_IO_SB] = gb->serial_transfer_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] = 0xFF; + } + gb->io_registers[GB_IO_IF] |= 8; } else { From 8c14ec32688acecd2edfbfe9fe4bc11dc14cbfb4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 13 Nov 2016 00:42:05 +0200 Subject: [PATCH 0218/1216] An interrupt should also occur when using external clock. --- Core/gb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/gb.c b/Core/gb.c index 00495909..babdb6f4 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -530,6 +530,7 @@ void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data) return; } gb->io_registers[GB_IO_SB] = data; + gb->io_registers[GB_IO_IF] |= 8; } void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) From cd382ef23692cb27f675aecfca1e86ab36a0b00d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 12 Jan 2017 23:11:26 +0200 Subject: [PATCH 0219/1216] Fixed: Conditional read watchpoints crashed if the expression referred to the 'new' variable. Breakpoint and watchpoint conditions no longer trigger watchpoints. --- Core/debugger.c | 86 ++++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index e606441e..6e54e1e6 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -425,6 +425,12 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { + /* Disable watchpoints while evaulating expressions */ + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + value_t ret = ERROR; + *error = false; // Strip whitespace while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { @@ -438,7 +444,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, { GB_log(gb, "Expected expression.\n"); *error = true; - return ERROR; + goto exit; } if (string[0] == '(' && string[length - 1] == ')') { // Attempt to strip parentheses @@ -452,7 +458,10 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (string[i] == ')') depth--; } - if (depth == 0) return debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + if (depth == 0) { + ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + goto exit; + } } else if (string[0] == '[' && string[length - 1] == ']') { // Attempt to strip square parentheses (memory dereference) @@ -471,14 +480,14 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); banking_state_t state; if (addr.bank) { - save_banking_state(gb, &state); - switch_banking_state(gb, addr.bank); + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); } - value_t r = VALUE_16(GB_read_memory(gb, addr.value)); + ret = VALUE_16(GB_read_memory(gb, addr.value)); if (addr.bank) { - restore_banking_state(gb, &state); + restore_banking_state(gb, &state); } - return r; + goto exit; } } @@ -511,15 +520,17 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (operator_index != -1) { unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); - if (*error) return ERROR; + if (*error) goto exit; if (operators[operator_index].lvalue_operator) { lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); - if (*error) return ERROR; - return operators[operator_index].lvalue_operator(gb, left, right.value); + if (*error) goto exit; + ret = operators[operator_index].lvalue_operator(gb, left, right.value); + goto exit; } value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); - if (*error) return ERROR; - return operators[operator_index].operator(left, right); + if (*error) goto exit; + ret = operators[operator_index].operator(left, right); + goto exit; } // Not an expression - must be a register or a literal @@ -528,38 +539,41 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (length == 1) { switch (string[0]) { - case 'a': return VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); - case 'f': return VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); - case 'b': return VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); - case 'c': return VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); - case 'd': return VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); - case 'e': return VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); - case 'h': return VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); - case 'l': return VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); + case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; + case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; + case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; + case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; + case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; + case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; + case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; + case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; } } else if (length == 2) { switch (string[0]) { - case 'a': if (string[1] == 'f') return VALUE_16(gb->registers[GB_REGISTER_AF]); - case 'b': if (string[1] == 'c') return VALUE_16(gb->registers[GB_REGISTER_BC]); - case 'd': if (string[1] == 'e') return VALUE_16(gb->registers[GB_REGISTER_DE]); - case 'h': if (string[1] == 'l') return VALUE_16(gb->registers[GB_REGISTER_HL]); - case 's': if (string[1] == 'p') return VALUE_16(gb->registers[GB_REGISTER_SP]); - case 'p': if (string[1] == 'c') return (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; + case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} + case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} + case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} + case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} + case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} + case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} } } else if (length == 3) { if (watchpoint_address && memcmp(string, "old", 3) == 0) { - return VALUE_16(GB_read_memory(gb, *watchpoint_address)); + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; } if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { - return VALUE_16(*watchpoint_new_value); + ret = VALUE_16(*watchpoint_new_value); + goto exit; } /* $new is identical to $old in read conditions */ if (watchpoint_address && memcmp(string, "new", 3) == 0) { - return VALUE_16(GB_read_memory(gb, *watchpoint_address)); + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; } } @@ -568,12 +582,13 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, symbol_name[length] = 0; const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name); if (symbol) { - return (value_t){true, symbol->bank, symbol->addr}; + ret = (value_t){true, symbol->bank, symbol->addr}; + goto exit; } GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); *error = true; - return ERROR; + goto exit; } char *end; @@ -587,9 +602,12 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (end != string + length) { GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); *error = true; - return ERROR; + goto exit; } - return VALUE_16(literal); + ret = VALUE_16(literal); +exit: + gb->n_watchpoints = n_watchpoints; + return ret; } struct debugger_command_s; @@ -1460,7 +1478,7 @@ static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug } return true; } - for (const debugger_command_t *command = commands; command->command; command++) { + for (command = commands; command->command; command++) { if (command->help_string) { print_command_description(gb, command); } From 527ae01e0e76fc50641d1b4e763e9391c28663e7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Jan 2017 21:27:37 +0200 Subject: [PATCH 0220/1216] Printer API, compression not supported yet --- Core/gb.c | 9 +++ Core/gb.h | 6 +- Core/printer.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++ Core/printer.h | 60 +++++++++++++++++ 4 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 Core/printer.c create mode 100644 Core/printer.h diff --git a/Core/gb.c b/Core/gb.c index babdb6f4..0232cf4e 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -543,3 +543,12 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) gb->sample_rate = sample_rate; gb->audio_position = 0; } + +void GB_disconnect_serial(GB_gameboy_t *gb) +{ + gb->serial_transfer_start_callback = NULL; + gb->serial_transfer_end_callback = NULL; + + /* Reset any internally-emulated device. Currently, only the printer. */ + memset(&gb->printer, 0, sizeof(gb->printer)); +} \ No newline at end of file diff --git a/Core/gb.h b/Core/gb.h index 63ea824e..f64041d4 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -4,6 +4,7 @@ #include #include #include +#include "printer.h" #include "apu.h" #include "save_struct.h" #include "symbol_hash.h" @@ -264,9 +265,9 @@ typedef struct GB_gameboy_s { bool ime_toggle; /* ei (and di in CGB) have delayed effects.*/ bool halt_bug; - /* Misc state*/ - /* IR */ + /* Misc state */ bool infrared_input; + GB_printer_t printer; ); /* DMA and HDMA */ @@ -510,4 +511,5 @@ void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_en uint8_t GB_serial_get_data(GB_gameboy_t *gb); void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); +void GB_disconnect_serial(GB_gameboy_t *gb); #endif /* GB_h */ diff --git a/Core/printer.c b/Core/printer.c new file mode 100644 index 00000000..da57c8fc --- /dev/null +++ b/Core/printer.c @@ -0,0 +1,179 @@ +#include "gb.h" + +/* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface. + Incorrect usage is not correctly emulated, as it's not well documented, nor do I + have my own GB Printer to figure it out myself. + + It also does not currently emulate communication timeout, which means that a bug + might prevent the printer operation until the GameBoy is restarted. + + Also, field mask values are assumed. */ + +static void handle_command(GB_gameboy_t *gb) +{ + + switch (gb->printer.command_id) { + case GB_PRINTER_INIT_COMMAND: + gb->printer.status = 0; + gb->printer.image_offset = 0; + break; + + case GB_PRINTER_START_COMMAND: + if (gb->printer.command_length == 4) { + gb->printer.status = 6; /* Printing */ + uint32_t image[gb->printer.image_offset]; + uint8_t palette = gb->printer.command_data[2]; + uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff), + gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa), + gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55), + gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)}; + for (unsigned i = 0; i < gb->printer.image_offset; i++) { + image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3]; + } + + if (gb->printer.callback) { + gb->printer.callback(gb, image, gb->printer.image_offset / 160, + gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7, + gb->printer.command_data[3] & 0x7F); + } + + gb->printer.image_offset = 0; + } + break; + + case GB_PRINTER_DATA_COMMAND: + if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) { + gb->printer.image_offset %= sizeof(gb->printer.image); + gb->printer.status = 8; /* Received 0x280 bytes */ + + uint8_t *byte = gb->printer.command_data; + + for (unsigned row = 2; row--; ) { + for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) { + for (unsigned y = 0; y < 8; y++, byte += 2) { + for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) { + gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] = + ((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1); + (*byte) <<= 1; + (*(byte + 1)) <<= 1; + } + } + } + + gb->printer.image_offset += 8 * 160; + } + } + + case GB_PRINTER_NOP_COMMAND: + default: + break; + } +} + +static void serial_start(GB_gameboy_t *gb, uint8_t byte_received) +{ + gb->printer.byte_to_send = 0; + switch (gb->printer.command_state) { + case GB_PRINTER_COMMAND_MAGIC1: + if (byte_received != 0x88) { + return; + } + gb->printer.status &= ~1; + gb->printer.command_length = 0; + gb->printer.checksum = 0; + break; + + case GB_PRINTER_COMMAND_MAGIC2: + if (byte_received != 0x33) { + if (byte_received != 0x88) { + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + } + return; + } + break; + + case GB_PRINTER_COMMAND_ID: + gb->printer.command_id = byte_received & 0xF; + break; + + case GB_PRINTER_COMMAND_COMPRESSION: + gb->printer.compression = byte_received & 1; + break; + + case GB_PRINTER_COMMAND_LENGTH_LOW: + gb->printer.length_left = byte_received; + break; + + case GB_PRINTER_COMMAND_LENGTH_HIGH: + gb->printer.length_left |= (byte_received & 3) << 8; + break; + + case GB_PRINTER_COMMAND_DATA: + if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + } + gb->printer.length_left--; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_LOW: + gb->printer.checksum ^= byte_received; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_HIGH: + gb->printer.checksum ^= byte_received << 8; + if (gb->printer.checksum) { + gb->printer.status |= 1; /* Checksum error*/ + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + return; + } + break; + case GB_PRINTER_COMMAND_ACTIVE: + gb->printer.byte_to_send = 0x81; + break; + case GB_PRINTER_COMMAND_STATUS: + + if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) { + /* Games expect INIT commands to return 0? */ + gb->printer.byte_to_send = 0; + } + else { + gb->printer.byte_to_send = gb->printer.status; + } + + /* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */ + if (gb->printer.status == 6) { + gb->printer.status = 4; /* Done */ + } + + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + handle_command(gb); + return; + } + + if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) { + gb->printer.checksum += byte_received; + } + + if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) { + gb->printer.command_state++; + } + + if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) { + if (gb->printer.length_left == 0) { + gb->printer.command_state++; + } + } + +} + +static uint8_t serial_end(GB_gameboy_t *gb) +{ + return gb->printer.byte_to_send; +} + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback) +{ + GB_set_serial_transfer_start_callback(gb, serial_start); + GB_set_serial_transfer_end_callback(gb, serial_end); + gb->printer.callback = callback; +} \ No newline at end of file diff --git a/Core/printer.h b/Core/printer.h new file mode 100644 index 00000000..0e1d56c6 --- /dev/null +++ b/Core/printer.h @@ -0,0 +1,60 @@ +#ifndef printer_h +#define printer_h +#include +#include + +#define GB_PRINTER_MAX_COMMAND_LENGTH 0x280 +#define GB_PRINTER_DATA_SIZE 0x280 + +typedef struct GB_gameboy_s GB_gameboy_t; + +typedef void (*GB_print_image_callback_t)(GB_gameboy_t *gb, + uint32_t *image, + uint8_t height, + uint8_t top_margin, + uint8_t bottom_margin, + uint8_t exposure); + + +typedef struct +{ + /* Communication state machine */ + + enum { + GB_PRINTER_COMMAND_MAGIC1, + GB_PRINTER_COMMAND_MAGIC2, + GB_PRINTER_COMMAND_ID, + GB_PRINTER_COMMAND_COMPRESSION, + GB_PRINTER_COMMAND_LENGTH_LOW, + GB_PRINTER_COMMAND_LENGTH_HIGH, + GB_PRINTER_COMMAND_DATA, + GB_PRINTER_COMMAND_CHECKSUM_LOW, + GB_PRINTER_COMMAND_CHECKSUM_HIGH, + GB_PRINTER_COMMAND_ACTIVE, + GB_PRINTER_COMMAND_STATUS, + } command_state : 8; + enum { + GB_PRINTER_INIT_COMMAND = 1, + GB_PRINTER_START_COMMAND = 2, + GB_PRINTER_DATA_COMMAND = 4, + GB_PRINTER_NOP_COMMAND = 0xF, + } command_id : 8; + bool compression; + uint16_t length_left; + uint8_t command_data[GB_PRINTER_MAX_COMMAND_LENGTH]; + uint16_t command_length; + uint16_t checksum; + uint8_t status; + uint8_t byte_to_send; + + uint8_t image[160 * 200]; + uint16_t image_offset; + + GB_print_image_callback_t callback; +} GB_printer_t; + + +typedef struct GB_gameboy_s GB_gameboy_t; + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback); +#endif \ No newline at end of file From 613d3b2e82721a42f0be0de511d932b289b679bc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Jan 2017 22:26:44 +0200 Subject: [PATCH 0221/1216] Printer support in Cocoa --- Cocoa/Document.h | 3 ++ Cocoa/Document.m | 98 +++++++++++++++++++++++++++++++++++++++++++++- Cocoa/Document.xib | 35 ++++++++++++++++- Cocoa/MainMenu.xib | 19 +++++++++ 4 files changed, 152 insertions(+), 3 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 1a722e9e..48a2cbaa 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -25,6 +25,9 @@ @property (strong) IBOutlet NSTextField *vramStatusLabel; @property (strong) IBOutlet NSTableView *paletteTableView; @property (strong) IBOutlet NSTableView *spritesTableView; +@property (strong) IBOutlet NSPanel *printerFeedWindow; +@property (strong) IBOutlet NSImageView *feedImageView; +@property (strong) IBOutlet NSButton *feedSaveButton; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 9c388596..19b826d6 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -37,6 +37,9 @@ uint16_t oamCount; uint8_t oamHeight; bool oamUpdating; + + NSMutableData *currentPrinterImageData; + enum {GBAccessoryNone, GBAccessoryPrinter} accessory; } @property GBAudioClient *audioClient; @@ -46,6 +49,9 @@ - (const char *) getAsyncDebuggerInput; - (void) cameraRequestUpdate; - (uint8_t) cameraGetPixelAtX:(uint8_t)x andY:(uint8_t)y; +- (void) printImage:(uint32_t *)image height:(unsigned) height + topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin + exposure:(unsigned) exposure; @end static void vblank(GB_gameboy_t *gb) @@ -75,7 +81,7 @@ static char *asyncConsoleInput(GB_gameboy_t *gb) static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { - return (r << 0) | (g << 8) | (b << 16); + return (r << 0) | (g << 8) | (b << 16) | 0xFF000000; } static void cameraRequestUpdate(GB_gameboy_t *gb) @@ -90,6 +96,13 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return [self cameraGetPixelAtX:x andY:y]; } +static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, + uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure) +{ + Document *self = (__bridge Document *)(gb->user_data); + [self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure]; +} + @implementation Document { GB_gameboy_t gb; @@ -132,6 +145,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) - (void) initCommon { + gb.user_data = (__bridge void *)(self); 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); @@ -139,7 +153,6 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) GB_set_rgb_encode_callback(&gb, rgbEncode); GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); - gb.user_data = (__bridge void *)(self); } - (void) vblank @@ -271,6 +284,14 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) window_frame.size.height); [self.mainWindow setFrame:window_frame display:YES]; self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; + + + [self.feedSaveButton removeFromSuperview]; + /* contentView.superview.subviews.lastObject is the titlebar view */ + NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject; + [titleView addSubview: self.feedSaveButton]; + self.feedSaveButton.frame = (NSRect){{268, 2}, {48, 17}}; + [self start]; } @@ -400,6 +421,12 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return false; } } + else if ([anItem action] == @selector(disconnectAllAccessories:)) { + [(NSMenuItem*)anItem setState:accessory == GBAccessoryNone]; + } + else if ([anItem action] == @selector(connectPrinter:)) { + [(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter]; + } return [super validateUserInterfaceItem:anItem]; } @@ -1073,4 +1100,71 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) { [self.vramWindow makeKeyAndOrderFront:sender]; } + +- (void) printImage:(uint32_t *)imageBytes height:(unsigned) height + topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin + exposure:(unsigned) exposure +{ + uint32_t paddedImage[160 * (topMargin + height + bottomMargin)]; + memset(paddedImage, 0xFF, sizeof(paddedImage)); + memcpy(paddedImage + (160 * topMargin), imageBytes, 160 * height * sizeof(imageBytes[0])); + if (!self.printerFeedWindow.isVisible) { + currentPrinterImageData = [[NSMutableData alloc] init]; + } + [currentPrinterImageData appendBytes:paddedImage length:sizeof(paddedImage)]; + self.feedImageView.image = [Document imageFromData:currentPrinterImageData + width:160 + height:currentPrinterImageData.length / 160 / sizeof(imageBytes[0]) + scale:2.0]; + /* UI related code must run on main thread. */ + dispatch_async(dispatch_get_main_queue(), ^{ + NSRect frame = self.printerFeedWindow.frame; + frame.size = self.feedImageView.image.size; + frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height; + [self.printerFeedWindow setMaxSize:frame.size]; + [self.printerFeedWindow setFrame:frame display:NO animate: self.printerFeedWindow.isVisible]; + [self.printerFeedWindow orderFront:NULL]; + }); + +} +- (IBAction)savePrinterFeed:(id)sender +{ + bool shouldResume = running; + [self stop]; + NSSavePanel * savePanel = [NSSavePanel savePanel]; + [savePanel setAllowedFileTypes:@[@"png"]]; + [savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result){ + if (result == NSFileHandlingPanelOKButton) { + [savePanel orderOut:self]; + CGImageRef cgRef = [self.feedImageView.image CGImageForProposedRect:NULL + context:nil + hints:nil]; + NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; + [imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}]; + NSData *data = [imageRep representationUsingType:NSPNGFileType properties:@{}]; + [data writeToURL:savePanel.URL atomically:NO]; + [self.printerFeedWindow setIsVisible:NO]; + } + if (shouldResume) { + [self start]; + } + }]; +} + +- (IBAction)disconnectAllAccessories:(id)sender +{ + [self performAtomicBlock:^{ + accessory = GBAccessoryNone; + GB_disconnect_serial(&gb); + }]; +} + +- (IBAction)connectPrinter:(id)sender +{ + [self performAtomicBlock:^{ + accessory = GBAccessoryPrinter; + GB_connect_printer(&gb, printImage); + }]; +} + @end \ No newline at end of file diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index c9cf907b..726f0dce 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -10,6 +10,8 @@ + + @@ -17,6 +19,7 @@ + @@ -32,7 +35,7 @@ - + @@ -679,5 +682,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 85376a2f..d43f1071 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -339,6 +339,25 @@
    + + + + + + + + + + + + + + + + + + +
    From 5dcc8e744e4be65930240b9414db0eaec15596d2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Jan 2017 19:45:07 +0200 Subject: [PATCH 0222/1216] Fixed a crash in the Cocoa port that might happen after closing GameBoy Camera --- Cocoa/Document.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 19b826d6..d0c23d7c 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -266,6 +266,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void)dealloc { + [cameraSession stopRunning]; GB_free(&gb); if (cameraImage) { CVBufferRelease(cameraImage); @@ -866,7 +867,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [cameraSession addInput: input]; [cameraSession addOutput: cameraOutput]; - /* ARC will stop the session when the window is closed. */ [cameraSession startRunning]; cameraConnection = [cameraOutput connectionWithMediaType: AVMediaTypeVideo]; } From 8d5bacf6aed65b13bc72fa6a5c2019c83197935b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Jan 2017 18:16:45 +0200 Subject: [PATCH 0223/1216] Updated Cocoa cartridge icons --- Cocoa/Cartridge.icns | Bin 278314 -> 275675 bytes Cocoa/ColorCartridge.icns | Bin 366311 -> 266723 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Cocoa/Cartridge.icns b/Cocoa/Cartridge.icns index 36a9adbb2230c73752325a41a116cf9fae8f62db..cf8ade76b6964128bf22c6f04f0e2ec2c276e9d8 100644 GIT binary patch literal 275675 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�J8dfCdtSD(-br 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

    e)s5p!>909YpI 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

    t>p}Xxk=Y`-EOSHpzGNq6 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%0A;Jc5i)T* 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 IzvlA)0W3P3ssI20 literal 278314 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

    uJFEs+J|4x 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

    blf`%}%kJ-h%k83MB4MtnVuI0kZYe0!IK9kIO9 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 D-^az{ diff --git a/Cocoa/ColorCartridge.icns b/Cocoa/ColorCartridge.icns index 95dada16575547e9aba2b34b64d738ebdf64cad0..0cd91c157a117d21ad4937bb2780ab66f5291336 100644 GIT binary patch literal 266723 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 a-$NljXzs6#|B==I-@6a}fL=a76#6GEc&NSr literal 366311 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

    {}7blzVkBiEIv-`=)kqBH=cmb$~2=4 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℘1m-Xd-H%paLt^rAO6Si`n_1%Hsf*(5meJ2a7;- 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� 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

      }jyaqVLsV=ix#*zt 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 Date: Fri, 20 Jan 2017 18:35:20 +0200 Subject: [PATCH 0224/1216] 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 00000000..60d82c11 --- /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 149f6789..72375fa9 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 f91fc8dc..ffe52cc7 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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

    - + - + + + + + + + From 5c16d0e6562e1854beeebfeb85ed2255ec9d7072 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 13 Jan 2018 13:49:20 +0200 Subject: [PATCH 0459/1216] Added model selection GUI in the SDL port. Closes #24 --- SDL/gui.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++-------- SDL/gui.h | 7 ++++++- SDL/main.c | 46 ++++++++--------------------------------- 3 files changed, 66 insertions(+), 47 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 626dcea5..5b040170 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -60,7 +60,8 @@ configuration_t configuration = .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, .highpass_mode = GB_HIGHPASS_ACCURATE, .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, - .blend_frames = true + .blend_frames = true, + .model = MODEL_CGB }; @@ -72,7 +73,6 @@ static const char *help[] ={ " Open Menu: Escape\n" " Reset: " MODIFIER_NAME "+R\n" " Pause: " MODIFIER_NAME "+P\n" -" Toggle DMG/CGB: " MODIFIER_NAME "+T\n" " Save state: " MODIFIER_NAME "+(0-9)\n" " Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n" #ifdef __APPLE__ @@ -225,6 +225,7 @@ static void item_help(unsigned index) gui_state = SHOWING_HELP; } +static void enter_emulation_menu(unsigned index); static void enter_graphics_menu(unsigned index); static void enter_controls_menu(unsigned index); static void enter_joypad_menu(unsigned index); @@ -232,6 +233,7 @@ static void enter_audio_menu(unsigned index); static const struct menu_item paused_menu[] = { {"Resume", NULL}, + {"Enumlation Options", enter_emulation_menu}, {"Graphic Options", enter_graphics_menu}, {"Audio Options", enter_audio_menu}, {"Keyboard", enter_controls_menu}, @@ -243,6 +245,49 @@ static const struct menu_item paused_menu[] = { static const struct menu_item *const nonpaused_menu = &paused_menu[1]; +static void return_to_root_menu(unsigned index) +{ + current_menu = root_menu; + current_selection = 0; +} + +static void cycle_model(unsigned index) +{ + + configuration.model++; + if (configuration.model == MODEL_MAX) { + configuration.model = 0; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static void cycle_model_backwards(unsigned index) +{ + if (configuration.model == 0) { + configuration.model = MODEL_MAX; + } + configuration.model--; + pending_command = GB_SDL_RESET_COMMAND; +} + +const char *current_model_string(unsigned index) +{ + return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance"} + [configuration.model]; +} + +static const struct menu_item emulation_menu[] = { + {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_emulation_menu(unsigned index) +{ + current_menu = emulation_menu; + current_selection = 0; +} + const char *current_scaling_mode(unsigned index) { return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"} @@ -374,12 +419,6 @@ const char *current_filter_name(unsigned index) return shaders[i].display_name; } -static void return_to_root_menu(unsigned index) -{ - current_menu = root_menu; - current_selection = 0; -} - static void toggle_blend_frames(unsigned index) { configuration.blend_frames ^= true; @@ -808,9 +847,12 @@ void run_gui(bool is_running) current_selection--; should_render = true; } - else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN) { + else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && !current_menu[current_selection].backwards_handler) { if (current_menu[current_selection].handler) { current_menu[current_selection].handler(current_selection); + if (pending_command == GB_SDL_RESET_COMMAND && !is_running) { + pending_command = GB_SDL_NO_COMMAND; + } if (pending_command) { if (!is_running && pending_command == GB_SDL_QUIT_COMMAND) { exit(0); diff --git a/SDL/gui.h b/SDL/gui.h index b68d0d1b..2c99b9fd 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -25,7 +25,6 @@ enum pending_command { GB_SDL_LOAD_STATE_COMMAND, GB_SDL_RESET_COMMAND, GB_SDL_NEW_FILE_COMMAND, - GB_SDL_TOGGLE_MODEL_COMMAND, GB_SDL_QUIT_COMMAND, }; @@ -44,6 +43,12 @@ typedef struct { bool swap_joysticks_bits_1_and_2; char filter[32]; + enum { + MODEL_DMG, + MODEL_CGB, + MODEL_AGB, + MODEL_MAX, + } model; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 7db2df6c..b819c9d4 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -14,7 +14,6 @@ #endif GB_gameboy_t gb; -static bool dmg = false; static bool paused = false; static uint32_t pixel_buffer_1[160*144], pixel_buffer_2[160*144]; static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; @@ -184,12 +183,6 @@ static void handle_events(GB_gameboy_t *gb) pending_command = GB_SDL_RESET_COMMAND; } break; - - case SDL_SCANCODE_T: - if (event.key.keysym.mod & MODIFIER) { - pending_command = GB_SDL_TOGGLE_MODEL_COMMAND; - } - break; case SDL_SCANCODE_P: if (event.key.keysym.mod & MODIFIER) { @@ -308,8 +301,7 @@ static bool handle_pending_command(void) } case GB_SDL_RESET_COMMAND: - GB_reset(&gb); - return false; + return true; case GB_SDL_NO_COMMAND: return false; @@ -317,10 +309,6 @@ static bool handle_pending_command(void) case GB_SDL_NEW_FILE_COMMAND: return true; - case GB_SDL_TOGGLE_MODEL_COMMAND: - dmg = !dmg; - return true; - case GB_SDL_QUIT_COMMAND: GB_save_battery(&gb, battery_save_path_ptr); exit(0); @@ -333,10 +321,10 @@ static void run(void) pending_command = GB_SDL_NO_COMMAND; restart: if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, !dmg); + GB_switch_model_and_reset(&gb, configuration.model != MODEL_DMG); } else { - if (dmg) { + if (configuration.model == MODEL_DMG) { GB_init(&gb); } else { @@ -353,12 +341,8 @@ restart: bool error = false; start_capturing_logs(); - if (dmg) { - error = GB_load_boot_rom(&gb, executable_relative_path("dmg_boot.bin")); - } - else { - error = GB_load_boot_rom(&gb, executable_relative_path("cgb_boot.bin")); - } + const char * const boot_roms[] = {"dmg_boot.bin", "cgb_boot.bin", "agb_boot.bin"}; + error = GB_load_boot_rom(&gb, executable_relative_path(boot_roms[configuration.model])); end_capturing_logs(true, error); start_capturing_logs(); @@ -416,25 +400,13 @@ int main(int argc, char **argv) #define xstr(x) str(x) fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); - if (argc > 3) { -usage: - fprintf(stderr, "Usage: %s [--dmg] [rom]\n", argv[0]); + if (argc > 2) { + fprintf(stderr, "Usage: %s [rom]\n", argv[0]); exit(1); } - for (unsigned i = 1; i < argc; i++) { - if (strcmp(argv[i], "--dmg") == 0) { - if (dmg) { - goto usage; - } - dmg = true; - } - else if (!filename) { - filename = argv[i]; - } - else { - goto usage; - } + if (argc == 2) { + filename = argv[2]; } signal(SIGINT, debugger_interrupt); From 130c7c28c2325ca2bb01ef90c02ef38be9774f31 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 15 Jan 2018 22:23:20 +0200 Subject: [PATCH 0460/1216] Re-do the way the libretro port does audio. Audio is now sent to libretro at 384KHz, which is then resampled to whatever rate the user configured. --- libretro/libretro.c | 644 ++++++++++++++++++++++---------------------- 1 file changed, 322 insertions(+), 322 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index b21010e5..63799095 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -6,7 +6,8 @@ #include #include -#define AUDIO_FREQUENCY 44100 +#define AUDIO_FREQUENCY 384000 +#define FRAME_RATE (0x400000 / 70224.0) #ifdef _WIN32 #include @@ -18,9 +19,9 @@ #include "libretro.h" #ifdef _WIN32 - char slash = '\\'; +char slash = '\\'; #else - char slash = '/'; +char slash = '/'; #endif #define VIDEO_WIDTH 160 @@ -39,7 +40,7 @@ static retro_audio_sample_batch_t audio_batch_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; -signed short soundbuf[1024*2]; +signed short soundbuf[1024 * 2]; char retro_system_directory[4096]; char retro_save_directory[4096]; @@ -52,18 +53,18 @@ extern const unsigned dmg_boot_length, cgb_boot_length; static void fallback_log(enum retro_log_level level, const char *fmt, ...) { - (void)level; - va_list va; - va_start(va, fmt); - vfprintf(stderr, fmt, va); - va_end(va); + (void)level; + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); } static void replace_extension(const char *src, size_t length, char *dest, const char *ext) { memcpy(dest, src, length); dest[length] = 0; - + /* Remove extension */ for (size_t i = length; i--;) { if (dest[i] == '/') break; @@ -72,7 +73,7 @@ static void replace_extension(const char *src, size_t length, char *dest, const break; } } - + /* Add new extension */ strcat(dest, ext); } @@ -81,38 +82,47 @@ static struct retro_rumble_interface rumble; static void GB_update_keys_status(GB_gameboy_t *gb) { - - input_poll_cb(); - - GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); - GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); - GB_set_key_state(gb, GB_KEY_UP,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); - GB_set_key_state(gb, GB_KEY_DOWN,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); - GB_set_key_state(gb, GB_KEY_A,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) ); - GB_set_key_state(gb, GB_KEY_B,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); - GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); - GB_set_key_state(gb, GB_KEY_START,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); - - if (gb->rumble_state) - rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535); - else - rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 0); - + + input_poll_cb(); + + GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); + GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); + GB_set_key_state(gb, GB_KEY_UP,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); + GB_set_key_state(gb, GB_KEY_DOWN,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); + GB_set_key_state(gb, GB_KEY_A,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) ); + GB_set_key_state(gb, GB_KEY_B,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); + GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); + GB_set_key_state(gb, GB_KEY_START,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); + + if (gb->rumble_state) + rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535); + else + rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 0); + } static void audio_callback(void *gb) { - GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, (float)AUDIO_FREQUENCY / 59.72); - audio_batch_cb(soundbuf, (float)AUDIO_FREQUENCY / 59.72); + size_t length = GB_apu_get_current_buffer_length(gb); + + while (length > sizeof(soundbuf) / 4) + { + GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, 1024); + audio_batch_cb(soundbuf, 1024); + length -= 1024; + } + if (length) { + GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, length); + audio_batch_cb(soundbuf, length); + } } static void vblank(GB_gameboy_t *gb) { - GB_update_keys_status(gb); - GB_set_pixels_output(gb, frame_buf); - audio_callback(gb); + GB_update_keys_status(gb); + audio_callback(gb); } static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) @@ -134,77 +144,77 @@ static retro_environment_t environ_cb; void retro_init(void) { - frame_buf = (uint32_t*)malloc(VIDEO_PIXELS * sizeof(uint32_t)); - const char *dir = NULL; - - if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) - snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); - else - snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); - - if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) - snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); - else - snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); + frame_buf = (uint32_t*)malloc(VIDEO_PIXELS * sizeof(uint32_t)); + const char *dir = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) + snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); + else + snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); + + if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) + snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); + else + snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); } void retro_deinit(void) { - free(frame_buf); - frame_buf = NULL; + free(frame_buf); + frame_buf = NULL; } unsigned retro_api_version(void) { - return RETRO_API_VERSION; + return RETRO_API_VERSION; } void retro_set_controller_port_device(unsigned port, unsigned device) { - log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port); + log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port); } void retro_get_system_info(struct retro_system_info *info) { - memset(info, 0, sizeof(*info)); - info->library_name = "SameBoy"; + memset(info, 0, sizeof(*info)); + info->library_name = "SameBoy"; #ifdef GIT_VERSION - info->library_version = SAMEBOY_CORE_VERSION GIT_VERSION; + info->library_version = SAMEBOY_CORE_VERSION GIT_VERSION; #else - info->library_version = SAMEBOY_CORE_VERSION; + info->library_version = SAMEBOY_CORE_VERSION; #endif - info->need_fullpath = true; - info->valid_extensions = "gb|gbc"; + info->need_fullpath = true; + info->valid_extensions = "gb|gbc"; } void retro_get_system_av_info(struct retro_system_av_info *info) { - struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT,VIDEO_WIDTH, VIDEO_HEIGHT ,160.0 / 144.0 }; - struct retro_system_timing timing = { 59.72, 44100.0 }; - - info->geometry = geom; - info->timing = timing; - + struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT,VIDEO_WIDTH, VIDEO_HEIGHT ,160.0 / 144.0 }; + struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; + + info->geometry = geom; + info->timing = timing; + } void retro_set_environment(retro_environment_t cb) { - environ_cb = cb; - - if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) - log_cb = logging.log; - else - log_cb = fallback_log; - - static const struct retro_controller_description controllers[] = { - { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, - }; - - static const struct retro_controller_info ports[] = { - { controllers, 1 }, - { NULL, 0 }, - }; - cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb = cb; + + if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) + log_cb = logging.log; + else + log_cb = fallback_log; + + static const struct retro_controller_description controllers[] = { + { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, + }; + + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { NULL, 0 }, + }; + cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); } void retro_set_audio_sample(retro_audio_sample_t cb) @@ -213,316 +223,306 @@ void retro_set_audio_sample(retro_audio_sample_t cb) void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { - audio_batch_cb = cb; + audio_batch_cb = cb; } void retro_set_input_poll(retro_input_poll_t cb) { - input_poll_cb = cb; + input_poll_cb = cb; } void retro_set_input_state(retro_input_state_t cb) { - input_state_cb = cb; + input_state_cb = cb; } void retro_set_video_refresh(retro_video_refresh_t cb) { - video_cb = cb; + video_cb = cb; } void retro_reset(void) { - GB_reset(&gb); + GB_reset(&gb); } static void check_variables(void) { - struct retro_variable var = {0}; - - var.key = "sameboy_color_correction_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb)) - { - if (strcmp(var.value, "off") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_DISABLED); - else if (strcmp(var.value, "correct curves") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_CORRECT_CURVES); - else if (strcmp(var.value, "emulate hardware") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); - else if (strcmp(var.value, "preserve brightness") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - } - - var.key = "sameboy_high_pass_filter_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - if (strcmp(var.value, "off") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_OFF); - else if (strcmp(var.value, "accurate") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_ACCURATE); - else if (strcmp(var.value, "remove dc offset") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_REMOVE_DC_OFFSET); - } + struct retro_variable var = {0}; + + var.key = "sameboy_color_correction_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb)) + { + if (strcmp(var.value, "off") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_DISABLED); + else if (strcmp(var.value, "correct curves") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_CORRECT_CURVES); + else if (strcmp(var.value, "emulate hardware") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + else if (strcmp(var.value, "preserve brightness") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + } + + var.key = "sameboy_high_pass_filter_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "off") == 0) + GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_OFF); + else if (strcmp(var.value, "accurate") == 0) + GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_ACCURATE); + else if (strcmp(var.value, "remove dc offset") == 0) + GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_REMOVE_DC_OFFSET); + } } void retro_run(void) { - static int frames; - size_t samples; - - bool updated = false; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(); - samples = GB_apu_get_current_buffer_length(&gb); - if (!(frames < (samples / 35112))) - { - GB_run_frame(&gb); - frames ++; - } - else - frames = 0; - - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); + bool updated = false; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) + check_variables(); + GB_run_frame(&gb); + + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); } bool retro_load_game(const struct retro_game_info *info) { - struct retro_input_descriptor desc[] = { - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - - enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; - if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) - { - log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); - return false; - } - - snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - - char buf[256]; - int err = 0; - if (!strstr(info->path, "gbc")) - { - GB_init(&gb); - snprintf(buf, sizeof(buf), "%s%cdmg_boot.bin", retro_system_directory, slash); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - err = GB_load_boot_rom(&gb, buf); - - if (err) { - GB_load_boot_rom_from_buffer(&gb, dmg_boot, dmg_boot_length); - err = 0; - } - } - else - { - GB_init_cgb(&gb); - snprintf(buf, sizeof(buf), "%s%ccgb_boot.bin", retro_system_directory, slash); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - err = GB_load_boot_rom(&gb, buf); - - if (err) { - GB_load_boot_rom_from_buffer(&gb, cgb_boot, cgb_boot_length); - err = 0; - } - } - if (err) - log_cb(RETRO_LOG_INFO, "Failed to load boot ROM %s %d\n", buf, err); - (void)info; - - if (GB_load_rom(&gb,info->path)) { + struct retro_input_descriptor desc[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, + }; + + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); + + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) + { + log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); + return false; + } + + snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + + char buf[256]; + int err = 0; + if (!strstr(info->path, "gbc")) + { + GB_init(&gb); + snprintf(buf, sizeof(buf), "%s%cdmg_boot.bin", retro_system_directory, slash); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + err = GB_load_boot_rom(&gb, buf); + + if (err) { + GB_load_boot_rom_from_buffer(&gb, dmg_boot, dmg_boot_length); + err = 0; + } + } + else + { + GB_init_cgb(&gb); + snprintf(buf, sizeof(buf), "%s%ccgb_boot.bin", retro_system_directory, slash); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + err = GB_load_boot_rom(&gb, buf); + + if (err) { + GB_load_boot_rom_from_buffer(&gb, cgb_boot, cgb_boot_length); + err = 0; + } + } + if (err) + log_cb(RETRO_LOG_INFO, "Failed to load boot ROM %s %d\n", buf, err); + (void)info; + + if (GB_load_rom(&gb,info->path)) { perror("Failed to load ROM"); exit(1); - } - - GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); - GB_set_user_data(&gb, (void*)NULL); - GB_set_pixels_output(&gb,(unsigned int*)frame_buf); - GB_set_rgb_encode_callback(&gb, rgb_encode); - - size_t path_length = strlen(retro_game_path); - + } + + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_user_data(&gb, (void*)NULL); + GB_set_pixels_output(&gb,(unsigned int*)frame_buf); + GB_set_rgb_encode_callback(&gb, rgb_encode); + + size_t path_length = strlen(retro_game_path); + #ifndef DISABLE_DEBUGGER - { - char TMPC[512]; - sprintf(TMPC,"%s/registers.sym",retro_system_directory); - GB_debugger_load_symbol_file(&gb, TMPC); - } + { + char TMPC[512]; + sprintf(TMPC,"%s/registers.sym",retro_system_directory); + GB_debugger_load_symbol_file(&gb, TMPC); + } #endif - - replace_extension(retro_game_path, path_length, symbols_path, ".sym"); - + + replace_extension(retro_game_path, path_length, symbols_path, ".sym"); + #ifndef DISABLE_DEBUGGER - GB_debugger_load_symbol_file(&gb, symbols_path); + GB_debugger_load_symbol_file(&gb, symbols_path); #endif - - GB_set_sample_rate(&gb, AUDIO_FREQUENCY); - - struct retro_memory_descriptor descs[7]; - size_t size; - uint16_t bank; - - memset(descs, 0, sizeof(descs)); - - descs[0].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IE, &size, &bank); - descs[0].start = 0xFFFF; - descs[0].len = 1; - - descs[1].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_HRAM, &size, &bank); - descs[1].start = 0xFF80; - descs[1].len = 0x0080; - - descs[2].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_RAM, &size, &bank); - descs[2].start = 0xC000; - descs[2].len = 0x2000; - - descs[3].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); - descs[3].start = 0xA000; - descs[3].len = 0x2000; - - descs[4].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); - descs[4].start = 0x8000; - descs[4].len = 0x2000; - - descs[5].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, &size, &bank); - descs[5].start = 0x0000; - descs[5].len = 0x4000; - descs[5].flags = RETRO_MEMDESC_CONST; - - descs[6].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_OAM, &size, &bank); - descs[6].start = 0xFE00; - descs[6].len = 0x00A0; - - struct retro_memory_map mmaps; - mmaps.descriptors = descs; - mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); - environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); - - bool yes = true; - environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); - - if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) - log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); - else - log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - - static struct retro_variable vars_cgb[] = { - { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { NULL } - }; - - static struct retro_variable vars_dmg[] = { - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { NULL } - }; - - if (GB_is_cgb(&gb)) - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, vars_cgb); - else - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, vars_dmg); - check_variables(); - - return true; + + GB_set_sample_rate(&gb, AUDIO_FREQUENCY); + + struct retro_memory_descriptor descs[7]; + size_t size; + uint16_t bank; + + memset(descs, 0, sizeof(descs)); + + descs[0].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IE, &size, &bank); + descs[0].start = 0xFFFF; + descs[0].len = 1; + + descs[1].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_HRAM, &size, &bank); + descs[1].start = 0xFF80; + descs[1].len = 0x0080; + + descs[2].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_RAM, &size, &bank); + descs[2].start = 0xC000; + descs[2].len = 0x2000; + + descs[3].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + descs[3].start = 0xA000; + descs[3].len = 0x2000; + + descs[4].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); + descs[4].start = 0x8000; + descs[4].len = 0x2000; + + descs[5].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, &size, &bank); + descs[5].start = 0x0000; + descs[5].len = 0x4000; + descs[5].flags = RETRO_MEMDESC_CONST; + + descs[6].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_OAM, &size, &bank); + descs[6].start = 0xFE00; + descs[6].len = 0x00A0; + + struct retro_memory_map mmaps; + mmaps.descriptors = descs; + mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); + environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); + + bool yes = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); + + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) + log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); + else + log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); + + static struct retro_variable vars_cgb[] = { + { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, + { NULL } + }; + + static struct retro_variable vars_dmg[] = { + { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, + { NULL } + }; + + if (GB_is_cgb(&gb)) + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, vars_cgb); + else + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, vars_dmg); + check_variables(); + + return true; } void retro_unload_game(void) { - GB_free(&gb); + GB_free(&gb); } unsigned retro_get_region(void) { - return RETRO_REGION_NTSC; + return RETRO_REGION_NTSC; } bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num) { - return false; + return false; } size_t retro_serialize_size(void) { - return GB_get_save_state_size(&gb); + return GB_get_save_state_size(&gb); } bool retro_serialize(void *data, size_t size) { - GB_save_state_to_buffer(&gb, (uint8_t*) data); - if (data) - return true; - else - return false; + GB_save_state_to_buffer(&gb, (uint8_t*) data); + if (data) + return true; + else + return false; } bool retro_unserialize(const void *data, size_t size) { - if (GB_load_state_from_buffer(&gb, (uint8_t*) data, size) == 0) - return true; - else - return false; + if (GB_load_state_from_buffer(&gb, (uint8_t*) data, size) == 0) + return true; + else + return false; } void *retro_get_memory_data(unsigned type) { - void* data; - switch(type) - { - case RETRO_MEMORY_SAVE_RAM: - if (gb.cartridge_type->has_battery && gb.mbc_ram_size != 0) - data = gb.mbc_ram; - else + void* data; + switch(type) + { + case RETRO_MEMORY_SAVE_RAM: + if (gb.cartridge_type->has_battery && gb.mbc_ram_size != 0) + data = gb.mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_RTC: + if(gb.cartridge_type->has_battery) + data = &gb.rtc_real; + else + data = NULL; + break; + default: data = NULL; - break; - case RETRO_MEMORY_RTC: - if(gb.cartridge_type->has_battery) - data = &gb.rtc_real; - else - data = NULL; - break; - default: - data = NULL; - break; - } - - return data; + break; + } + + return data; } size_t retro_get_memory_size(unsigned type) { - size_t size; - switch(type) - { - case RETRO_MEMORY_SAVE_RAM: - if (gb.cartridge_type->has_battery && gb.mbc_ram_size != 0) - size = gb.mbc_ram_size; - else + size_t size; + switch(type) + { + case RETRO_MEMORY_SAVE_RAM: + if (gb.cartridge_type->has_battery && gb.mbc_ram_size != 0) + size = gb.mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_RTC: + if(gb.cartridge_type->has_battery) + size = sizeof (gb.rtc_real); + else + size = 0; + break; + default: size = 0; - break; - case RETRO_MEMORY_RTC: - if(gb.cartridge_type->has_battery) - size = sizeof (gb.rtc_real); - else - size = 0; - break; - default: - size = 0; - break; - } - - return size; + break; + } + + return size; } void retro_cheat_reset(void) @@ -530,8 +530,8 @@ void retro_cheat_reset(void) void retro_cheat_set(unsigned index, bool enabled, const char *code) { - (void)index; - (void)enabled; - (void)code; + (void)index; + (void)enabled; + (void)code; } From 37906bcd1f12d8b5199a9bb3fe11be3004e1036c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jan 2018 00:47:46 +0200 Subject: [PATCH 0461/1216] Fixed sound pops in Super Mario Land 2. --- Core/apu.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 8d5b3e73..363fb9b8 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -173,7 +173,9 @@ void GB_apu_div_event(GB_gameboy_t *gb) gb->apu.square_channels[i].volume_countdown = nrx2 & 7; - update_square_sample(gb, i); + if (gb->apu.is_active[i]) { + update_square_sample(gb, i); + } } } } @@ -192,10 +194,12 @@ void GB_apu_div_event(GB_gameboy_t *gb) gb->apu.noise_channel.volume_countdown = nr42 & 7; - update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? - gb->apu.noise_channel.current_volume : 0, - 0); + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.noise_channel.current_volume : 0, + 0); + } } } } @@ -748,8 +752,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR44: { if (value & 0x80) { - gb->apu.noise_channel.lfsr = 0; - gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div; /* I'm COMPLETELY unsure about this logic, but it passes all relevant tests. @@ -777,7 +779,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.current_volume : 0, 0); } - + gb->apu.noise_channel.lfsr = 0; gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; if ((gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { @@ -821,7 +823,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb) { - return gb->apu_output.buffer_position; + return gb->apu_output.buffer_position; } void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) From 09dd47c6de9ee9d00728d06eb4cf8468300904bc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jan 2018 19:56:39 +0200 Subject: [PATCH 0462/1216] =?UTF-8?q?Fixed=20unintentional=20delay=20in=20?= =?UTF-8?q?NR50=20and=20NR51=E2=80=99s=20effects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/apu.c b/Core/apu.c index 363fb9b8..69bc5ea6 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -469,6 +469,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) /* Globals */ case GB_IO_NR50: case GB_IO_NR51: + gb->io_registers[reg] = value; /* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/ /* We call update_samples with the current value so the APU output is updated with the new outputs */ for (unsigned i = GB_N_CHANNELS; i--;) { From af143b0420ab6ccc34f907cc3b8120c678149c09 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Jan 2018 00:06:33 +0200 Subject: [PATCH 0463/1216] Added model selection to libretro, including GBA. Closes #23. --- libretro/Makefile.common | 3 +- libretro/libretro.c | 275 +++++++++++++++++---------------------- 2 files changed, 125 insertions(+), 153 deletions(-) diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 4ba74827..8e5af51f 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -11,8 +11,9 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/Core/z80_cpu.c \ $(CORE_DIR)/Core/joypad.c \ $(CORE_DIR)/Core/save_state.c \ + $(CORE_DIR)/libretro/agb_boot.c \ $(CORE_DIR)/libretro/cgb_boot.c \ - $(CORE_DIR)/libretro/dmg_boot.c \ + $(CORE_DIR)/libretro/dmg_boot.c \ $(CORE_DIR)/libretro/libretro.c ifeq ($(HAVE_DEBUGGER), 1) diff --git a/libretro/libretro.c b/libretro/libretro.c index 63799095..5889b197 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -19,9 +19,9 @@ #include "libretro.h" #ifdef _WIN32 -char slash = '\\'; +static const char slash = '\\'; #else -char slash = '/'; +static const char slash = '/'; #endif #define VIDEO_WIDTH 160 @@ -31,6 +31,14 @@ char slash = '/'; char battery_save_path[512]; char symbols_path[512]; +enum model { + MODEL_DMG, + MODEL_CGB, + MODEL_AGB +}; + +static enum model model = MODEL_CGB; + static uint32_t *frame_buf; static struct retro_log_callback logging; static retro_log_printf_t log_cb; @@ -48,8 +56,8 @@ char retro_game_path[4096]; int RLOOP=1; GB_gameboy_t gb; -extern const unsigned char dmg_boot[], cgb_boot[]; -extern const unsigned dmg_boot_length, cgb_boot_length; +extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; +extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length; static void fallback_log(enum retro_log_level level, const char *fmt, ...) { @@ -60,24 +68,6 @@ static void fallback_log(enum retro_log_level level, const char *fmt, ...) va_end(va); } -static void replace_extension(const char *src, size_t length, char *dest, const char *ext) -{ - memcpy(dest, src, length); - dest[length] = 0; - - /* Remove extension */ - for (size_t i = length; i--;) { - if (dest[i] == '/') break; - if (dest[i] == '.') { - dest[i] = 0; - break; - } - } - - /* Add new extension */ - strcat(dest, ext); -} - static struct retro_rumble_interface rumble; static void GB_update_keys_status(GB_gameboy_t *gb) @@ -130,16 +120,6 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return r<<16|g<<8|b; } -#ifndef DISABLE_DEBUGGER -static void debugger_interrupt(int ignore) -{ - /* ^C twice to exit */ - if (GB_debugger_is_stopped(&gb)) - exit(0); - GB_debugger_break(&gb); -} -#endif - static retro_environment_t environ_cb; void retro_init(void) @@ -246,128 +226,35 @@ void retro_reset(void) GB_reset(&gb); } -static void check_variables(void) +static void init_for_current_model(void) { - struct retro_variable var = {0}; - - var.key = "sameboy_color_correction_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb)) - { - if (strcmp(var.value, "off") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_DISABLED); - else if (strcmp(var.value, "correct curves") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_CORRECT_CURVES); - else if (strcmp(var.value, "emulate hardware") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); - else if (strcmp(var.value, "preserve brightness") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + if (GB_is_inited(&gb)) { + GB_switch_model_and_reset(&gb, model != MODEL_DMG); } - - var.key = "sameboy_high_pass_filter_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - if (strcmp(var.value, "off") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_OFF); - else if (strcmp(var.value, "accurate") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_ACCURATE); - else if (strcmp(var.value, "remove dc offset") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_REMOVE_DC_OFFSET); + else { + if (model == MODEL_DMG) { + GB_init(&gb); + } + else { + GB_init_cgb(&gb); + } } -} - -void retro_run(void) -{ - bool updated = false; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(); - GB_run_frame(&gb); - - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); -} - -bool retro_load_game(const struct retro_game_info *info) -{ - struct retro_input_descriptor desc[] = { - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - - enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; - if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) - { - log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); - return false; - } - - snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model]; char buf[256]; - int err = 0; - if (!strstr(info->path, "gbc")) - { - GB_init(&gb); - snprintf(buf, sizeof(buf), "%s%cdmg_boot.bin", retro_system_directory, slash); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - err = GB_load_boot_rom(&gb, buf); - - if (err) { - GB_load_boot_rom_from_buffer(&gb, dmg_boot, dmg_boot_length); - err = 0; - } - } - else - { - GB_init_cgb(&gb); - snprintf(buf, sizeof(buf), "%s%ccgb_boot.bin", retro_system_directory, slash); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - err = GB_load_boot_rom(&gb, buf); - - if (err) { - GB_load_boot_rom_from_buffer(&gb, cgb_boot, cgb_boot_length); - err = 0; - } - } - if (err) - log_cb(RETRO_LOG_INFO, "Failed to load boot ROM %s %d\n", buf, err); - (void)info; + snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - if (GB_load_rom(&gb,info->path)) { - perror("Failed to load ROM"); - exit(1); + if (GB_load_boot_rom(&gb, buf)) { + GB_load_boot_rom_from_buffer(&gb, boot_code, boot_length); } GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_user_data(&gb, (void*)NULL); GB_set_pixels_output(&gb,(unsigned int*)frame_buf); GB_set_rgb_encode_callback(&gb, rgb_encode); - - size_t path_length = strlen(retro_game_path); - -#ifndef DISABLE_DEBUGGER - { - char TMPC[512]; - sprintf(TMPC,"%s/registers.sym",retro_system_directory); - GB_debugger_load_symbol_file(&gb, TMPC); - } -#endif - - replace_extension(retro_game_path, path_length, symbols_path, ".sym"); - -#ifndef DISABLE_DEBUGGER - GB_debugger_load_symbol_file(&gb, symbols_path); -#endif - GB_set_sample_rate(&gb, AUDIO_FREQUENCY); struct retro_memory_descriptor descs[7]; @@ -409,6 +296,97 @@ bool retro_load_game(const struct retro_game_info *info) mmaps.descriptors = descs; mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); +} + +static void check_variables(void) +{ + struct retro_variable var = {0}; + + var.key = "sameboy_color_correction_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb)) + { + if (strcmp(var.value, "off") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_DISABLED); + else if (strcmp(var.value, "correct curves") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_CORRECT_CURVES); + else if (strcmp(var.value, "emulate hardware") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + else if (strcmp(var.value, "preserve brightness") == 0) + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + } + + var.key = "sameboy_high_pass_filter_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "off") == 0) + GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_OFF); + else if (strcmp(var.value, "accurate") == 0) + GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_ACCURATE); + else if (strcmp(var.value, "remove dc offset") == 0) + GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_REMOVE_DC_OFFSET); + } + + var.key = "sameboy_model"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + enum model new_model = model; + if (strcmp(var.value, "Game Boy") == 0) + new_model = MODEL_DMG; + else if (strcmp(var.value, "Game Boy Color") == 0) + new_model = MODEL_CGB; + else if (strcmp(var.value, "Game Boy Advance") == 0) + new_model = MODEL_AGB; + if (GB_is_inited(&gb) && new_model != model) { + model = new_model; + init_for_current_model(); + } + } +} + +void retro_run(void) +{ + bool updated = false; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) + check_variables(); + GB_run_frame(&gb); + + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); +} + +bool retro_load_game(const struct retro_game_info *info) +{ + struct retro_input_descriptor desc[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, + }; + + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); + + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) + { + log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); + return false; + } + + snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + + init_for_current_model(); + + if (GB_load_rom(&gb,info->path)) { + log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); + return false; + } bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); @@ -418,21 +396,14 @@ bool retro_load_game(const struct retro_game_info *info) else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - static struct retro_variable vars_cgb[] = { + static const struct retro_variable vars[] = { { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, + { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, { NULL } }; - static struct retro_variable vars_dmg[] = { - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { NULL } - }; - - if (GB_is_cgb(&gb)) - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, vars_cgb); - else - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, vars_dmg); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); check_variables(); return true; From 59fe551f26fb4718abe3a17b86d4ad40c9510650 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Mon, 22 Jan 2018 16:58:06 +0100 Subject: [PATCH 0464/1216] Makefile: Add Switch target. --- libretro/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libretro/Makefile b/libretro/Makefile index 41b77e4d..17a5d840 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -74,6 +74,12 @@ else ifeq ($(platform), linux-portable) fpic := -fPIC -nostdlib SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T LIBM := +# Nintendo Switch (libtransistor) +else ifeq ($(platform), switch) + TARGET := $(TARGET_NAME)_libretro_$(platform).a + include $(LIBTRANSISTOR_HOME)/libtransistor.mk + CFLAGS += -Wl,-q -Wall -O3 -fno-short-enums -fno-optimize-sibling-calls + STATIC_LINKING=1 else ifneq (,$(findstring osx,$(platform))) TARGET := $(TARGET_NAME)_libretro.dylib fpic := -fPIC From ce31de47cb4ee5e0a461edea4499cab43d42e87b Mon Sep 17 00:00:00 2001 From: connor rigby Date: Mon, 22 Jan 2018 12:25:27 -0800 Subject: [PATCH 0465/1216] Fix loading game from command line. --- SDL/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index b819c9d4..4387b2d5 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -406,7 +406,7 @@ int main(int argc, char **argv) } if (argc == 2) { - filename = argv[2]; + filename = argv[1]; } signal(SIGINT, debugger_interrupt); From b047c7a9e10d87744c8bddbf822622328f73db19 Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 26 Jan 2018 19:01:20 -0500 Subject: [PATCH 0466/1216] readd bootroms --- BootROMs/prebuilt/agb_boot.bin | Bin 0 -> 2304 bytes libretro/Makefile | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 BootROMs/prebuilt/agb_boot.bin diff --git a/BootROMs/prebuilt/agb_boot.bin b/BootROMs/prebuilt/agb_boot.bin new file mode 100644 index 0000000000000000000000000000000000000000..917bce7d542347ca73fc7b7c1ee4b0a80a484bc4 GIT binary patch literal 2304 zcmd^Bjc?o55q}gVita2*vSWu=G2&fBmSTjpc~+AL|8 z6j)&=mP7>sY6B}4t^266kJuRyv>S%nXi>BQvWD5_)P%ss-7v#jvGuu~oy@};IjQQ8 zc;t?9TWtFmb|Bx~y}SGI?)bptcFP`Hi0cx{nunMzQ?N+L;zq}0A6`?lWD5zZcSr@< ztoD(5bCFfoNJr33Nr7yr!`#NeRXOm%4t(3Zz^D{Sj;-Saf8>_3Sxyhh;Y#QB;UWZa z%!K(IbJd5J!{9FcW|*N>mJ9S1ok*4K8zad@S@Gcyf&obl<9*$FRsW@aW5i5xm~XliOI8;@f`a3H{AaJ)${ z2@D0Y**M0n80Rn^9UkuF`A3F^0vN-^6M}diFxcF9ZghN9AJ?Wn0qn>(PA0~y z@@`w({;mD(2-(`LO~~bNF>$-r;Xmlv4`th$O*c^`J3U?EuO=q~f!(C`pR}#@Zbs-RExK%wZS7sE0^-DQFMS)80saCl zfrErn-;rw>tUJt7%^ns-K1(y++U@u5*m<9lxODN-#e{+=uU`~Z1ua{)1@$9D zQ8exKB50n|KIAQt9}z;F9pnf1SWH9L9s28eWCJ;2XU~E~5YQUveuzit^qIl4XORdx z(X(jq{W~lFN~`NQiz_Imruvr)M2(4lZ>bKwu)LO191vAWY0Ld7MNw4C?~o`8Cr!`K_!0ghy$Z2U|%Oy00Q8|!kHrL`)+y831Z*XyX_|xK zWB_UkRLu+Rw5_$m8*hw2VKi8&Gthyx)G`zcZ75|0&L2M;-cz#gO?iY)Q+4&NPr9D^ zFQo1_+2;0!skBh!z%`N zly`AQ)a)zol}F#J!gmcxALi(c<&UsR-@MnNB6OH9q(2gmhkcX$4QH@ahZq-30rLt| zg^Pwsv*s2SCJn5sd#SvJX*2942Ug?y3z(x&aANT=Lv;!4wvQI5iC1+StKF1!Z-lWZoZz|D2Pvx=-@n>US=(`ow1eUT z42otue0WttuC(>FNL}=Gyp*19YvtzB*G?VJ%qCZNQJ3)F($@|i&lqDyM>Z?{$;F^# znSi2mQigWKYSw&-nazI%y9Jj2d-T)Y(f~A~y}Q3mN$!Dp(GsyMe|lh^SOk}ynx`!q z^k1BsPgyAta&uolR`$h%vjg%e*x&VvMi+ML(S--#cI%l1kN#ijl_vY!S7#TE vAd5J*uf_+*q6jW(%|hUu)O3z3-xFb5;Ph $@ echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ hexdump -v -e '/1 "0x%02x, "' $< >> $@ From ab49034205261e8e50708d097ec22151f153a801 Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 26 Jan 2018 19:08:31 -0500 Subject: [PATCH 0467/1216] update Bootroms --- BootROMs/prebuilt/cgb_boot.bin | Bin 2304 -> 2304 bytes BootROMs/prebuilt/dmg_boot.bin | Bin 256 -> 256 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/BootROMs/prebuilt/cgb_boot.bin b/BootROMs/prebuilt/cgb_boot.bin index 42d88fcc4b09c81f9b5469520d0f77179761a183..0bbc6776060a42003dd1065ff04e8b177e1f318e 100644 GIT binary patch delta 155 zcmZn=Y7m;B%lK)c-hM{Qjqf^`IV#*I7`4kLa%`?+xx~n5IXRGRek2RWS#^%}5B!1T zFK5Qn^$N^qRoEZ+{Bk(3T!EjlLGb8DK88d`PdN^~nv-A|JI0fCj1O|o`m&#{KXRSp zS-m?*tp=B3EsH-J181ECW5ro#wzG34+p-^FJU^LWFEH`| diff --git a/BootROMs/prebuilt/dmg_boot.bin b/BootROMs/prebuilt/dmg_boot.bin index a7b51d4c85d282227bd2b245c78a7d26c973a3c5..5f4283418c5dd50a72162bb37ee3af366a473ba6 100644 GIT binary patch delta 54 zcmZo*YG9gR!nkpwsha3w2D|37s~Ff=&jMKoK`ghkizgPSb3aw&|9`sew427ChZE<< IFg*wW03`1gZU6uP delta 52 zcmZo*YG9gR!nkIlsha3K2D|37OBvYs&jMKoL9A(K=S?h7XZzstZ-T Date: Sat, 27 Jan 2018 21:46:13 +0200 Subject: [PATCH 0468/1216] Added automatic model selection for the libretro port. --- libretro/libretro.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 542991bf..ba57b4a7 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -34,11 +34,12 @@ char symbols_path[512]; enum model { MODEL_DMG, MODEL_CGB, - MODEL_AGB + MODEL_AGB, + MODEL_AUTO }; -static enum model model = MODEL_CGB; - +static enum model model = MODEL_AUTO; +static enum model auto_model = MODEL_CGB; static uint32_t *frame_buf; static struct retro_log_callback logging; static retro_log_printf_t log_cb; @@ -228,20 +229,24 @@ void retro_reset(void) static void init_for_current_model(void) { + enum model effective_model = model; + if (effective_model == MODEL_AUTO) { + effective_model = auto_model; + } if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, model != MODEL_DMG); + GB_switch_model_and_reset(&gb, effective_model != MODEL_DMG); } else { - if (model == MODEL_DMG) { + if (effective_model == MODEL_DMG) { GB_init(&gb); } else { GB_init_cgb(&gb); } } - const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model]; + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model]; char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); @@ -339,10 +344,15 @@ static void check_variables(void) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; + else if (strcmp(var.value, "Auto") == 0) + new_model = MODEL_AUTO; if (GB_is_inited(&gb) && new_model != model) { model = new_model; init_for_current_model(); } + else { + model = new_model; + } } } @@ -388,6 +398,8 @@ bool retro_load_game(const struct retro_game_info *info) return false; } + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'c' ? MODEL_CGB : MODEL_DMG; + bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); @@ -399,7 +411,7 @@ bool retro_load_game(const struct retro_game_info *info) static const struct retro_variable vars[] = { { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { NULL } }; From 95234036bbd973832b5601a3ac61a4852f8b89c5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 31 Jan 2018 15:18:04 +0200 Subject: [PATCH 0469/1216] Added return value to GB_run API. --- Core/gb.c | 4 +++- Core/gb.h | 5 ++++- Core/timing.c | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index bf41891a..cc86fef8 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -271,15 +271,17 @@ exit: return; } -void GB_run(GB_gameboy_t *gb) +uint8_t GB_run(GB_gameboy_t *gb) { GB_debugger_run(gb); + gb->cycles_since_run = 0; GB_cpu_run(gb); if (gb->vblank_just_occured) { GB_update_joyp(gb); GB_rtc_run(gb); GB_debugger_handle_async_commands(gb); } + return gb->cycles_since_run; } uint64_t GB_run_frame(GB_gameboy_t *gb) diff --git a/Core/gb.h b/Core/gb.h index dbe81edf..ca373f5e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -483,6 +483,7 @@ struct GB_gameboy_internal_s { uint32_t ram_size; // Different between CGB and DMG uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank + uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run() ); }; @@ -506,7 +507,9 @@ bool GB_is_cgb(GB_gameboy_t *gb); void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb); -void GB_run(GB_gameboy_t *gb); + +/* Returns the time passed, in 4MHz ticks. */ +uint8_t GB_run(GB_gameboy_t *gb); /* Returns the time passed since the last frame, in nanoseconds */ uint64_t GB_run_frame(GB_gameboy_t *gb); diff --git a/Core/timing.c b/Core/timing.c index 1818a7cf..cbcfb821 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -153,6 +153,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_ir_change += cycles; gb->cycles_since_input_ir_change += cycles; gb->cycles_since_last_sync += cycles; + gb->cycles_since_run += cycles; GB_dma_run(gb); GB_hdma_run(gb); GB_apu_run(gb); From f3c07f1f99a773551002d979edf40eb4ec109ef0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 31 Jan 2018 23:58:17 +0200 Subject: [PATCH 0470/1216] Fixed a silly bug that prevented libretro automatic model selection from functioning correctly. --- libretro/libretro.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index ba57b4a7..4aec97c4 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -391,6 +391,7 @@ bool retro_load_game(const struct retro_game_info *info) snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; init_for_current_model(); if (GB_load_rom(&gb,info->path)) { @@ -398,8 +399,6 @@ bool retro_load_game(const struct retro_game_info *info) return false; } - auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'c' ? MODEL_CGB : MODEL_DMG; - bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); From aeb9f0eda430b298f6ba1901d723eff1be7def42 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 2 Feb 2018 19:22:08 +0200 Subject: [PATCH 0471/1216] Prevent screensaver when using a joypad --- Cocoa/GBView.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index dfcc2f20..3afc2477 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -216,6 +216,7 @@ - (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state { + UpdateSystemActivity(UsrActivity); NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; for (GBButton i = 0; i < GBButtonCount; i++) { @@ -239,6 +240,7 @@ - (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value { + UpdateSystemActivity(UsrActivity); NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; NSNumber *x_axis = [mapping objectForKey:@"XAxis"]; NSNumber *y_axis = [mapping objectForKey:@"YAxis"]; From 51eacd3174500a2f4d05d7cfe9636a8fcced325a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Feb 2018 00:50:38 +0200 Subject: [PATCH 0472/1216] Update version to 0.10.1, update copyright to 2018 --- Cocoa/License.html | 2 +- LICENSE | 2 +- Makefile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cocoa/License.html b/Cocoa/License.html index c6647b56..26731434 100644 --- a/Cocoa/License.html +++ b/Cocoa/License.html @@ -30,7 +30,7 @@

    SameBoy

    MIT License

    -

    Copyright © 2015-2017 Lior Halphon

    +

    Copyright © 2015-2018 Lior Halphon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index 008851f5..63c6787e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2017 Lior Halphon +Copyright (c) 2015-2018 Lior Halphon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index f4163588..140a2c2b 100755 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.10 +VERSION := 0.10.1 export VERSION CONF ?= debug From 8f56da465a38cae9ad30a8adf197c8c1ce2c6976 Mon Sep 17 00:00:00 2001 From: rootfather Date: Sat, 3 Feb 2018 16:35:50 +0100 Subject: [PATCH 0473/1216] SDL: Fix typo in the GUI --- SDL/gui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index 5b040170..c84895b3 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -233,7 +233,7 @@ static void enter_audio_menu(unsigned index); static const struct menu_item paused_menu[] = { {"Resume", NULL}, - {"Enumlation Options", enter_emulation_menu}, + {"Emulation Options", enter_emulation_menu}, {"Graphic Options", enter_graphics_menu}, {"Audio Options", enter_audio_menu}, {"Keyboard", enter_controls_menu}, From d04789746203922777db27784cade09829a938a0 Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 26 Jan 2018 21:01:18 -0500 Subject: [PATCH 0474/1216] use the prebuilt roms --- libretro/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 2fbb69ea..e97df1be 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -147,14 +147,14 @@ CFLAGS += -Wall -D__LIBRETRO__ $(fpic) $(INCFLAGS) -std=gnu11 -D_GNU_SOURCE -D all: $(TARGET) -$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/build/bin/BootROMs/%_boot.bin +$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/BootROMs/prebuilt/%_boot.bin echo "/* AUTO-GENERATED */" > $@ echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ hexdump -v -e '/1 "0x%02x, "' $< >> $@ echo "};" >> $@ echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ -$(CORE_DIR)/build/bin/BootROMs/%_boot.bin: +$(CORE_DIR)/BootROMs/prebuilt/%_boot.bin: $(MAKE) -C $(CORE_DIR) $(patsubst $(CORE_DIR)/%,%,$@) $(TARGET): $(OBJECTS) From e97624ba7da79d9bcd8f0969b082e63f9e7c4fcc Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 26 Jan 2018 22:27:58 -0500 Subject: [PATCH 0475/1216] fix warning --- libretro/libretro.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 4aec97c4..519591a9 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -480,8 +480,10 @@ void *retro_get_memory_data(unsigned type) else data = NULL; break; + default: + data = NULL; + break; } - return data; } @@ -512,7 +514,6 @@ size_t retro_get_memory_size(unsigned type) size = 0; break; } - return size; } From 5660b762c0daf0c2717b9c8234496cf4922dcbbb Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 2 Feb 2018 21:28:56 -0500 Subject: [PATCH 0476/1216] add prebuilt roms again --- BootROMs/prebuilt/agb_boot.bin | Bin 0 -> 2304 bytes BootROMs/prebuilt/cgb_boot.bin | Bin 0 -> 2304 bytes BootROMs/prebuilt/dmg_boot.bin | Bin 0 -> 256 bytes libretro/Makefile | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 BootROMs/prebuilt/agb_boot.bin create mode 100644 BootROMs/prebuilt/cgb_boot.bin create mode 100644 BootROMs/prebuilt/dmg_boot.bin diff --git a/BootROMs/prebuilt/agb_boot.bin b/BootROMs/prebuilt/agb_boot.bin new file mode 100644 index 0000000000000000000000000000000000000000..917bce7d542347ca73fc7b7c1ee4b0a80a484bc4 GIT binary patch literal 2304 zcmd^Bjc?o55q}gVita2*vSWu=G2&fBmSTjpc~+AL|8 z6j)&=mP7>sY6B}4t^266kJuRyv>S%nXi>BQvWD5_)P%ss-7v#jvGuu~oy@};IjQQ8 zc;t?9TWtFmb|Bx~y}SGI?)bptcFP`Hi0cx{nunMzQ?N+L;zq}0A6`?lWD5zZcSr@< ztoD(5bCFfoNJr33Nr7yr!`#NeRXOm%4t(3Zz^D{Sj;-Saf8>_3Sxyhh;Y#QB;UWZa z%!K(IbJd5J!{9FcW|*N>mJ9S1ok*4K8zad@S@Gcyf&obl<9*$FRsW@aW5i5xm~XliOI8;@f`a3H{AaJ)${ z2@D0Y**M0n80Rn^9UkuF`A3F^0vN-^6M}diFxcF9ZghN9AJ?Wn0qn>(PA0~y z@@`w({;mD(2-(`LO~~bNF>$-r;Xmlv4`th$O*c^`J3U?EuO=q~f!(C`pR}#@Zbs-RExK%wZS7sE0^-DQFMS)80saCl zfrErn-;rw>tUJt7%^ns-K1(y++U@u5*m<9lxODN-#e{+=uU`~Z1ua{)1@$9D zQ8exKB50n|KIAQt9}z;F9pnf1SWH9L9s28eWCJ;2XU~E~5YQUveuzit^qIl4XORdx z(X(jq{W~lFN~`NQiz_Imruvr)M2(4lZ>bKwu)LO191vAWY0Ld7MNw4C?~o`8Cr!`K_!0ghy$Z2U|%Oy00Q8|!kHrL`)+y831Z*XyX_|xK zWB_UkRLu+Rw5_$m8*hw2VKi8&Gthyx)G`zcZ75|0&L2M;-cz#gO?iY)Q+4&NPr9D^ zFQo1_+2;0!skBh!z%`N zly`AQ)a)zol}F#J!gmcxALi(c<&UsR-@MnNB6OH9q(2gmhkcX$4QH@ahZq-30rLt| zg^Pwsv*s2SCJn5sd#SvJX*2942Ug?y3z(x&aANT=Lv;!4wvQI5iC1+StKF1!Z-lWZoZz|D2Pvx=-@n>US=(`ow1eUT z42otue0WttuC(>FNL}=Gyp*19YvtzB*G?VJ%qCZNQJ3)F($@|i&lqDyM>Z?{$;F^# znSi2mQigWKYSw&-nazI%y9Jj2d-T)Y(f~A~y}Q3mN$!Dp(GsyMe|lh^SOk}ynx`!q z^k1BsPgyAta&uolR`$h%vjg%e*x&VvMi+ML(S--#cI%l1kN#ijl_vY!S7#TE vAd5J*uf_+*q6jW(%|hUu)O3z3-xFb5;Phm}VSKN@VF zFWUtC3q0B1_ulut_wjr0Yx#Mb?TpR07!uO0!_1Z`SR`a~qhoRaucpA9H053MOXBD%&?kk_)mD!0(1bsLF?D?OnJ;dg(kvaen^K;Yx;M zC1i{>%5tRKy~bpq0KNm8l`R6Ye#)rdA}!Wm7^cBWGU{3DQHJrk(lwA&VZPO{cC*$# zMrt*7;PuK?rR%@1JHkJ9>}ue> z|Iv`w7nNx-c%QGlc5t)si3%)k@JRvASK})yp*@kRs`_}OA>L5CB@!3vW!V>Jd{R|B z9`l4?^`W}j$|?qz)_$z6td19(!&{k6sIk5tp>la+SxsB}{?^u>w)XD!Zrs!Gxt`V@ z-1Sgv*TJrR?YIZy+u3Y(bad1(jLyzZIOOT+=~ygw`0(MWsVTjs1rvfpAs&O{ZGuT? zD5UEx81KS3i}C32a3{|{GBgyz7%rX=#Pfi`X2-Lm&jJfO5)zI6Fgn}R5RP!v@KE!&O_^&>=4 zH0}2zXr9|XHALh)3wm*}-$?kO(?4 zbTs(R-Iaf()peYO6_ij@{mXfxCd8n>Scj2cUP~!1h^nNt<^GhSD5@QFNfd>XC;R(P zoq90Qx3qNY*6TNJT(~d-bm0m@HV{IB09hpz#UHexlAwRYg;5l+uahbO0q{LdOC(f5 zfRywPOige&qN1c$R_?wts5wra5W}!-M|2+zQG%dcxdPycgdkMAWxAVytt2!}b1~cu zKuv+F`JtVTy_SFNwGk+c1}k+IIjgJ;7zCHr2NN9Z(FQ@iU)&r=`1 zr>d7jIb6tv7jogfxNu62%29Jx3Oc!Ga{FUzGv@jy)`yo&Qmzh?4W^uXB6iP=T zj?~m#%s~>@wY4RJySAFcLYWM-19JE_a~Z|UoYPGrn6vqRUVKY19G zED=zAR?5(>Sjk#HVPSaKc<-LiQ<8UJPP9er$(kC%M*(9D2*S~eNXVVI&5)(yWqo!xR^Onx+0eUtoK(x)AD3P!Fv zEUTlkS|*z+CMb^@=Yo?H94Zx?*3?jOW8=oT(lc(4lKt>sarxS0VRqsVA2!Ra=*?n9 z8A&Uav&Yu3CmJn4eaNtW!_Lfm43MdHnKl+;V2#i3G2-(N!tFIO^FHIh(kqVkH?GYr wm|+%iY+qFX-irdbs8#c!^HSq^u5@pVZHCupYmhH2m{OB7#)afY*A1b61C_k-%m4rY literal 0 HcmV?d00001 diff --git a/BootROMs/prebuilt/dmg_boot.bin b/BootROMs/prebuilt/dmg_boot.bin new file mode 100644 index 0000000000000000000000000000000000000000..5f4283418c5dd50a72162bb37ee3af366a473ba6 GIT binary patch literal 256 zcmXr~_g|5rLFsgk#&5fZ2Wk%l?LI#cdZ21o{y@d<&jWWs7Dh#Z1}XQmO$Z{O z;gjG-20jicVI^f=g`akkuLNc)>d)lkvDM%(0t)EyOMJDP_`rcp_v~T@yXLd27}!`r ztb-ty8<3^Af$`}_MPEinVFtk)42S Date: Fri, 2 Feb 2018 22:12:23 -0500 Subject: [PATCH 0477/1216] add address sanitizer support --- libretro/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libretro/Makefile b/libretro/Makefile index 45728504..535c6313 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -53,6 +53,12 @@ else endif endif +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) $(CFLAGS) + CXXFLAGS := -fsanitize=$(SANITIZER) $(CXXFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) -lasan +endif + ifeq ($(platform), osx) ifndef ($(NOUNIVERSAL)) CFLAGS += $(ARCHFLAGS) From 9c23fe25639c3b3b33f13cd59f6bc06c32424621 Mon Sep 17 00:00:00 2001 From: Tatsuya79 Date: Sat, 3 Feb 2018 15:29:18 +0100 Subject: [PATCH 0478/1216] Reduce input lag by 1 frame --- libretro/libretro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 519591a9..2c270de0 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -112,7 +112,6 @@ static void audio_callback(void *gb) static void vblank(GB_gameboy_t *gb) { - GB_update_keys_status(gb); audio_callback(gb); } @@ -361,6 +360,7 @@ void retro_run(void) bool updated = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(); + GB_update_keys_status(&gb); GB_run_frame(&gb); video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); From ede16f1d3ba989540e0d15a90c56bf8191d52917 Mon Sep 17 00:00:00 2001 From: kwyxz Date: Sun, 4 Feb 2018 00:23:09 +0000 Subject: [PATCH 0479/1216] Allow build on Haiku target --- libretro/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libretro/Makefile b/libretro/Makefile index 535c6313..f421aae1 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -71,7 +71,7 @@ EXT := a endif ifeq ($(platform), unix) - EXT ?= so + EXT ?= so TARGET := $(TARGET_NAME)_libretro.$(EXT) fpic := -fPIC SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined @@ -156,7 +156,13 @@ all: $(TARGET) $(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/BootROMs/prebuilt/%_boot.bin echo "/* AUTO-GENERATED */" > $@ echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ +ifneq ($(findstring Haiku,$(shell uname -s)),) + # turns out od is posix, hexdump is not hence is less portable + # this is still rather ugly and could be done better I guess + od -A none -t x1 -v $< | sed -e 's/^\ /0x/' -e 's/\ /,\ 0x/g' -e 's/$$/,/g' | tr '\n' ' ' >> $@ +else hexdump -v -e '/1 "0x%02x, "' $< >> $@ +endif echo "};" >> $@ echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ From 3bd1c122c369866d629a68ea81e481c86c9deb7c Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 20:23:51 -0500 Subject: [PATCH 0480/1216] try to fix jni build --- libretro/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 1dc2b3e2..66789768 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -25,7 +25,7 @@ endif CORE_DIR := ../.. -include ../../Makefile.common +include ../Makefile.common LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) LOCAL_CXXFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL From e1bd85caa625c3ff38ae425ff41ff81aae04b796 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 20:53:41 -0500 Subject: [PATCH 0481/1216] add WiiU target --- libretro/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libretro/Makefile b/libretro/Makefile index f421aae1..ec3bb12b 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -86,6 +86,14 @@ else ifeq ($(platform), switch) include $(LIBTRANSISTOR_HOME)/libtransistor.mk CFLAGS += -Wl,-q -Wall -O3 -fno-short-enums -fno-optimize-sibling-calls STATIC_LINKING=1 +# Nintendo WiiU +else ifeq ($(platform), wiiu) + TARGET := $(TARGET_NAME)_libretro_$(platform).a + CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) + AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) + CFLAGS += -DGEKKO -DHW_RVL -DWIIU -mwup -mcpu=750 -meabi -mhard-float -D__ppc__ -DMSB_FIRST -I$(DEVKITPRO)/libogc/include + CFLAGS += -U__INT32_TYPE__ -U __UINT32_TYPE__ -D__INT32_TYPE__=int + STATIC_LINKING = 1 else ifneq (,$(findstring osx,$(platform))) TARGET := $(TARGET_NAME)_libretro.dylib fpic := -fPIC From a04646ab5b0962551dedf24f3c6860f41c6ce160 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 21:14:31 -0500 Subject: [PATCH 0482/1216] update Android.mk --- libretro/jni/Android.mk | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 66789768..b275e354 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -27,9 +27,17 @@ CORE_DIR := ../.. include ../Makefile.common +$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/BootROMs/prebuilt/%_boot.bin + echo "/* AUTO-GENERATED */" > $@ + echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ + hexdump -v -e '/1 "0x%02x, "' $< >> $@ + echo "};" >> $@ + echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ + LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) -LOCAL_CXXFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL +LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL LOCAL_C_INCLUDES = $(INCFLAGS) include $(BUILD_SHARED_LIBRARY) + From 9a09f920320afb191a7b3b1c21854be95bdd70ff Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 21:20:59 -0500 Subject: [PATCH 0483/1216] update Android.mk --- libretro/jni/Android.mk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index b275e354..7deea1c9 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -28,14 +28,14 @@ CORE_DIR := ../.. include ../Makefile.common $(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/BootROMs/prebuilt/%_boot.bin - echo "/* AUTO-GENERATED */" > $@ - echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ - hexdump -v -e '/1 "0x%02x, "' $< >> $@ - echo "};" >> $@ - echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ + echo "/* AUTO-GENERATED */" > $@ + echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ + hexdump -v -e '/1 "0x%02x, "' $< >> $@ + echo "};" >> $@ + echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) -LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL +LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" LOCAL_C_INCLUDES = $(INCFLAGS) From a939fed87247d109301adbb295ee6b5389cfa81b Mon Sep 17 00:00:00 2001 From: radius Date: Mon, 5 Feb 2018 18:17:55 -0500 Subject: [PATCH 0484/1216] cleanup whitespaces --- libretro/libretro.c | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 2c270de0..0b28a40a 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -26,9 +26,9 @@ static const char slash = '/'; #define VIDEO_WIDTH 160 #define VIDEO_HEIGHT 144 -#define VIDEO_PIXELS VIDEO_WIDTH * VIDEO_HEIGHT +#define VIDEO_PIXELS VIDEO_WIDTH * VIDEO_HEIGHT -char battery_save_path[512]; +char battery_save_path[512]; char symbols_path[512]; enum model { @@ -73,9 +73,9 @@ static struct retro_rumble_interface rumble; static void GB_update_keys_status(GB_gameboy_t *gb) { - + input_poll_cb(); - + GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); GB_set_key_state(gb, GB_KEY_UP,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); @@ -84,19 +84,19 @@ static void GB_update_keys_status(GB_gameboy_t *gb) GB_set_key_state(gb, GB_KEY_B,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); GB_set_key_state(gb, GB_KEY_START,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); - + if (gb->rumble_state) rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535); else rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 0); - + } static void audio_callback(void *gb) { size_t length = GB_apu_get_current_buffer_length(gb); - + while (length > sizeof(soundbuf) / 4) { GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, 1024); @@ -126,12 +126,12 @@ void retro_init(void) { frame_buf = (uint32_t*)malloc(VIDEO_PIXELS * sizeof(uint32_t)); const char *dir = NULL; - + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); else snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); - + if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); else @@ -171,25 +171,25 @@ void retro_get_system_av_info(struct retro_system_av_info *info) { struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT,VIDEO_WIDTH, VIDEO_HEIGHT ,160.0 / 144.0 }; struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; - + info->geometry = geom; info->timing = timing; - + } void retro_set_environment(retro_environment_t cb) { environ_cb = cb; - + if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) log_cb = logging.log; else log_cb = fallback_log; - + static const struct retro_controller_description controllers[] = { { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, }; - + static const struct retro_controller_info ports[] = { { controllers, 1 }, { NULL, 0 }, @@ -246,56 +246,56 @@ static void init_for_current_model(void) const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model]; const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model]; unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model]; - + char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - + if (GB_load_boot_rom(&gb, buf)) { GB_load_boot_rom_from_buffer(&gb, boot_code, boot_length); } - + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_user_data(&gb, (void*)NULL); GB_set_pixels_output(&gb,(unsigned int*)frame_buf); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, AUDIO_FREQUENCY); - + struct retro_memory_descriptor descs[7]; size_t size; uint16_t bank; - + memset(descs, 0, sizeof(descs)); - + descs[0].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IE, &size, &bank); descs[0].start = 0xFFFF; descs[0].len = 1; - + descs[1].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_HRAM, &size, &bank); descs[1].start = 0xFF80; descs[1].len = 0x0080; - + descs[2].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_RAM, &size, &bank); descs[2].start = 0xC000; descs[2].len = 0x2000; - + descs[3].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); descs[3].start = 0xA000; descs[3].len = 0x2000; - + descs[4].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); descs[4].start = 0x8000; descs[4].len = 0x2000; - + descs[5].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, &size, &bank); descs[5].start = 0x0000; descs[5].len = 0x4000; descs[5].flags = RETRO_MEMDESC_CONST; - + descs[6].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_OAM, &size, &bank); descs[6].start = 0xFE00; descs[6].len = 0x00A0; - + struct retro_memory_map mmaps; mmaps.descriptors = descs; mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); @@ -305,7 +305,7 @@ static void init_for_current_model(void) static void check_variables(void) { struct retro_variable var = {0}; - + var.key = "sameboy_color_correction_mode"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb)) @@ -319,7 +319,7 @@ static void check_variables(void) else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } - + var.key = "sameboy_high_pass_filter_mode"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -331,7 +331,7 @@ static void check_variables(void) else if (strcmp(var.value, "remove dc offset") == 0) GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_REMOVE_DC_OFFSET); } - + var.key = "sameboy_model"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -362,7 +362,7 @@ void retro_run(void) check_variables(); GB_update_keys_status(&gb); GB_run_frame(&gb); - + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); } @@ -379,44 +379,44 @@ bool retro_load_game(const struct retro_game_info *info) { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 0 }, }; - + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); return false; } - + snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; init_for_current_model(); - + if (GB_load_rom(&gb,info->path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); return false; } - + bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); - + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - + static const struct retro_variable vars[] = { { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { NULL } }; - + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); check_variables(); - + return true; } From dd7704e572e86e616bb275614bd3124a1a71a0cb Mon Sep 17 00:00:00 2001 From: radius Date: Mon, 5 Feb 2018 18:31:31 -0500 Subject: [PATCH 0485/1216] set sample rate to 44100 on WIIU --- libretro/libretro.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 0b28a40a..3ab408c8 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -6,7 +6,12 @@ #include #include +#ifndef WIIU #define AUDIO_FREQUENCY 384000 +#else +#define AUDIO_FREQUENCY 44100 +#endif + #define FRAME_RATE (0x400000 / 70224.0) #ifdef _WIN32 @@ -54,7 +59,6 @@ signed short soundbuf[1024 * 2]; char retro_system_directory[4096]; char retro_save_directory[4096]; char retro_game_path[4096]; -int RLOOP=1; GB_gameboy_t gb; extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; From db0269cd3fe29ce9f066fba68e46bbb9ec4be71c Mon Sep 17 00:00:00 2001 From: radius Date: Mon, 5 Feb 2018 18:39:25 -0500 Subject: [PATCH 0486/1216] clean whitespace --- libretro/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/Makefile b/libretro/Makefile index ec3bb12b..ff79f180 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -110,7 +110,7 @@ endif DEFINES := -DIOS ifeq ($(platform),ios-arm64) CC = cc -arch armv64 -isysroot $(IOSSDK) -else +else CC = cc -arch armv7 -isysroot $(IOSSDK) endif ifeq ($(platform),$(filter $(platform),ios9 ios-arm64)) From ff4168b8f6af714a98fab3b1f52c73e8b032cb54 Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 26 Jan 2018 22:27:58 -0500 Subject: [PATCH 0487/1216] fix warning --- libretro/libretro.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 3ab408c8..ac014347 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -486,7 +486,9 @@ void *retro_get_memory_data(unsigned type) break; default: data = NULL; - break; + break; + default: + data = NULL; } return data; } From 3b858fb6afe4992efc168110f87805f653c04833 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 27 Jan 2018 14:59:18 -0500 Subject: [PATCH 0488/1216] initial link cable implementation --- libretro/libretro.c | 322 ++++++++++++++++++++++++++++---------------- 1 file changed, 206 insertions(+), 116 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index ac014347..2c53e5a3 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -31,7 +31,7 @@ static const char slash = '/'; #define VIDEO_WIDTH 160 #define VIDEO_HEIGHT 144 -#define VIDEO_PIXELS VIDEO_WIDTH * VIDEO_HEIGHT +#define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT) char battery_save_path[512]; char symbols_path[512]; @@ -46,6 +46,8 @@ enum model { static enum model model = MODEL_AUTO; static enum model auto_model = MODEL_CGB; static uint32_t *frame_buf; + +static uint32_t *frame_buf = NULL; static struct retro_log_callback logging; static retro_log_printf_t log_cb; @@ -60,7 +62,8 @@ char retro_system_directory[4096]; char retro_save_directory[4096]; char retro_game_path[4096]; -GB_gameboy_t gb; +GB_gameboy_t gb1; +GB_gameboy_t gb2; extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length; @@ -75,25 +78,25 @@ static void fallback_log(enum retro_log_level level, const char *fmt, ...) static struct retro_rumble_interface rumble; -static void GB_update_keys_status(GB_gameboy_t *gb) +static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { input_poll_cb(); - - GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); - GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); - GB_set_key_state(gb, GB_KEY_UP,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); - GB_set_key_state(gb, GB_KEY_DOWN,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); - GB_set_key_state(gb, GB_KEY_A,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) ); - GB_set_key_state(gb, GB_KEY_B,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); - GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); - GB_set_key_state(gb, GB_KEY_START,input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); - + + GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); + GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); + GB_set_key_state(gb, GB_KEY_UP,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); + GB_set_key_state(gb, GB_KEY_DOWN,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); + GB_set_key_state(gb, GB_KEY_A,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) ); + GB_set_key_state(gb, GB_KEY_B,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); + GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); + GB_set_key_state(gb, GB_KEY_START,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); + if (gb->rumble_state) - rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535); + rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535); else - rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 0); - + rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0); + } @@ -114,11 +117,18 @@ static void audio_callback(void *gb) } -static void vblank(GB_gameboy_t *gb) +static void vblank1(GB_gameboy_t *gb) { + GB_update_keys_status(gb, 0); audio_callback(gb); } +static void vblank2(GB_gameboy_t *gb) +{ + GB_update_keys_status(gb, 1); + //audio_callback(gb); +} + static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return r<<16|g<<8|b; @@ -128,7 +138,7 @@ static retro_environment_t environ_cb; void retro_init(void) { - frame_buf = (uint32_t*)malloc(VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf = (uint32_t*)malloc(2 * VIDEO_PIXELS * sizeof(uint32_t)); const char *dir = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) @@ -173,7 +183,7 @@ void retro_get_system_info(struct retro_system_info *info) void retro_get_system_av_info(struct retro_system_av_info *info) { - struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT,VIDEO_WIDTH, VIDEO_HEIGHT ,160.0 / 144.0 }; + struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH, VIDEO_HEIGHT * 2 ,160.0 / 288.0 }; struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; info->geometry = geom; @@ -227,7 +237,28 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { - GB_reset(&gb); + GB_reset(&gb1); + GB_reset(&gb2); +} + +static void serial_start1(GB_gameboy_t *gb, uint8_t byte_received) +{ + GB_serial_set_data(&gb2, byte_received); +} + +static uint8_t serial_end1(GB_gameboy_t *gb) +{ + return GB_serial_get_data(&gb2); +} + +static void serial_start2(GB_gameboy_t *gb, uint8_t byte_received) +{ + GB_serial_set_data(&gb1, byte_received); +} + +static uint8_t serial_end2(GB_gameboy_t *gb) +{ + return GB_serial_get_data(&gb1); } static void init_for_current_model(void) @@ -236,17 +267,29 @@ static void init_for_current_model(void) if (effective_model == MODEL_AUTO) { effective_model = auto_model; } - if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, effective_model != MODEL_DMG); + if (GB_is_inited(&gb1)) { + GB_switch_model_and_reset(&gb1, effective_model != MODEL_DMG); } else { if (effective_model == MODEL_DMG) { - GB_init(&gb); + GB_init(&gb1); } else { - GB_init_cgb(&gb); + GB_init_cgb(&gb1); } } + if (GB_is_inited(&gb2)) { + GB_switch_model_and_reset(&gb2, effective_model != MODEL_DMG); + } + else { + if (effective_model == MODEL_DMG) { + GB_init(&gb2); + } + else { + GB_init_cgb(&gb2); + } + } + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model]; const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model]; unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model]; @@ -254,49 +297,61 @@ static void init_for_current_model(void) char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - - if (GB_load_boot_rom(&gb, buf)) { - GB_load_boot_rom_from_buffer(&gb, boot_code, boot_length); + + if (GB_load_boot_rom(&gb1, buf)) { + GB_load_boot_rom_from_buffer(&gb1, boot_code, boot_length); } - - GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); - GB_set_user_data(&gb, (void*)NULL); - GB_set_pixels_output(&gb,(unsigned int*)frame_buf); - GB_set_rgb_encode_callback(&gb, rgb_encode); - GB_set_sample_rate(&gb, AUDIO_FREQUENCY); + if (GB_load_boot_rom(&gb2, buf)) { + GB_load_boot_rom_from_buffer(&gb2, boot_code, boot_length); + } + + GB_set_vblank_callback(&gb1, (GB_vblank_callback_t) vblank1); + GB_set_vblank_callback(&gb2, (GB_vblank_callback_t) vblank2); + GB_set_user_data(&gb1, (void*)NULL); + GB_set_user_data(&gb2, (void*)NULL); + GB_set_pixels_output(&gb1,(unsigned int*)frame_buf); + GB_set_rgb_encode_callback(&gb1, rgb_encode); + GB_set_pixels_output(&gb2,(unsigned int*)(frame_buf + VIDEO_PIXELS)); + GB_set_rgb_encode_callback(&gb2, rgb_encode); + GB_set_sample_rate(&gb1, AUDIO_FREQUENCY); + GB_set_sample_rate(&gb2, AUDIO_FREQUENCY); + GB_set_serial_transfer_start_callback(&gb1, serial_start1); + GB_set_serial_transfer_end_callback(&gb1, serial_end1); + GB_set_serial_transfer_start_callback(&gb2, serial_start2); + GB_set_serial_transfer_end_callback(&gb2, serial_end2); struct retro_memory_descriptor descs[7]; size_t size; uint16_t bank; memset(descs, 0, sizeof(descs)); - - descs[0].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IE, &size, &bank); + + descs[0].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_IE, &size, &bank); descs[0].start = 0xFFFF; descs[0].len = 1; - - descs[1].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_HRAM, &size, &bank); + + descs[1].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_HRAM, &size, &bank); descs[1].start = 0xFF80; descs[1].len = 0x0080; - - descs[2].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_RAM, &size, &bank); + + descs[2].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_RAM, &size, &bank); descs[2].start = 0xC000; descs[2].len = 0x2000; - - descs[3].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + + descs[3].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); descs[3].start = 0xA000; descs[3].len = 0x2000; - - descs[4].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); + + descs[4].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_VRAM, &size, &bank); descs[4].start = 0x8000; descs[4].len = 0x2000; - - descs[5].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, &size, &bank); + + descs[5].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_ROM, &size, &bank); descs[5].start = 0x0000; descs[5].len = 0x4000; descs[5].flags = RETRO_MEMDESC_CONST; - - descs[6].ptr = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_OAM, &size, &bank); + + descs[6].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_OAM, &size, &bank); descs[6].start = 0xFE00; descs[6].len = 0x00A0; @@ -312,16 +367,28 @@ static void check_variables(void) var.key = "sameboy_color_correction_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb)) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb1)) { if (strcmp(var.value, "off") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_DISABLED); + { + GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_DISABLED); + } else if (strcmp(var.value, "correct curves") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_CORRECT_CURVES); + { + GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_CORRECT_CURVES); + } else if (strcmp(var.value, "emulate hardware") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + { + GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + } else if (strcmp(var.value, "preserve brightness") == 0) - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + { + GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + } } var.key = "sameboy_high_pass_filter_mode"; @@ -329,11 +396,20 @@ static void check_variables(void) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_OFF); + { + GB_set_highpass_filter_mode(&gb1, GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gb2, GB_HIGHPASS_OFF); + } else if (strcmp(var.value, "accurate") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_ACCURATE); + { + GB_set_highpass_filter_mode(&gb1, GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gb2, GB_HIGHPASS_ACCURATE); + } else if (strcmp(var.value, "remove dc offset") == 0) - GB_set_highpass_filter_mode(&gb, GB_HIGHPASS_REMOVE_DC_OFFSET); + { + GB_set_highpass_filter_mode(&gb1, GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gb2, GB_HIGHPASS_REMOVE_DC_OFFSET); + } } var.key = "sameboy_model"; @@ -349,7 +425,11 @@ static void check_variables(void) new_model = MODEL_AGB; else if (strcmp(var.value, "Auto") == 0) new_model = MODEL_AUTO; - if (GB_is_inited(&gb) && new_model != model) { + if (GB_is_inited(&gb1) && new_model != model) { + model = new_model; + init_for_current_model(); + } + if (GB_is_inited(&gb2) && new_model != model) { model = new_model; init_for_current_model(); } @@ -359,15 +439,23 @@ static void check_variables(void) } } +int frames = 0; void retro_run(void) { - bool updated = false; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(); - GB_update_keys_status(&gb); - GB_run_frame(&gb); + bool updated = false; - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); + if (!frame_buf) + return; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) + check_variables(); + + GB_run_frame(&gb1); + GB_run_frame(&gb2); + + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); + + frames++; } bool retro_load_game(const struct retro_game_info *info) @@ -397,12 +485,15 @@ bool retro_load_game(const struct retro_game_info *info) auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; init_for_current_model(); - - if (GB_load_rom(&gb,info->path)) { + + if (GB_load_rom(&gb1,info->path)) { + log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); + return false; + } + if (GB_load_rom(&gb2,info->path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); return false; } - bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); @@ -426,7 +517,8 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { - GB_free(&gb); + GB_free(&gb1); + GB_free(&gb2); } unsigned retro_get_region(void) @@ -441,12 +533,12 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - return GB_get_save_state_size(&gb); + return GB_get_save_state_size(&gb1); } bool retro_serialize(void *data, size_t size) { - GB_save_state_to_buffer(&gb, (uint8_t*) data); + GB_save_state_to_buffer(&gb1, (uint8_t*) data); if (data) return true; else @@ -455,7 +547,7 @@ bool retro_serialize(void *data, size_t size) bool retro_unserialize(const void *data, size_t size) { - if (GB_load_state_from_buffer(&gb, (uint8_t*) data, size) == 0) + if (GB_load_state_from_buffer(&gb1, (uint8_t*) data, size) == 0) return true; else return false; @@ -463,28 +555,25 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { - void* data; - switch(type) - { - case RETRO_MEMORY_SYSTEM_RAM: - data = gb.ram; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gb.cartridge_type->has_battery && gb.mbc_ram_size != 0) - data = gb.mbc_ram; - else - data = NULL; - break; - case RETRO_MEMORY_VIDEO_RAM: - data = gb.vram; - break; - case RETRO_MEMORY_RTC: - if(gb.cartridge_type->has_battery) - data = &gb.rtc_real; - else - data = NULL; - break; - default: + void* data; + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + data = gb1.ram; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) + data = gb1.mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_VIDEO_RAM: + data = gb1.vram; + break; + case RETRO_MEMORY_RTC: + if(gb1.cartridge_type->has_battery) + data = &gb1.rtc_real; + else data = NULL; break; default: @@ -495,32 +584,33 @@ void *retro_get_memory_data(unsigned type) size_t retro_get_memory_size(unsigned type) { - size_t size; - switch(type) - { - case RETRO_MEMORY_SYSTEM_RAM: - size = gb.ram_size; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gb.cartridge_type->has_battery && gb.mbc_ram_size != 0) - size = gb.mbc_ram_size; - else - size = 0; - break; - case RETRO_MEMORY_VIDEO_RAM: - size = gb.vram_size; - break; - case RETRO_MEMORY_RTC: - if(gb.cartridge_type->has_battery) - size = sizeof (gb.rtc_real); - else - size = 0; - break; - default: + size_t size; + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + size = gb1.ram_size; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) + size = gb1.mbc_ram_size; + else size = 0; - break; - } - return size; + break; + case RETRO_MEMORY_VIDEO_RAM: + size = gb1.vram_size; + break; + case RETRO_MEMORY_RTC: + if(gb1.cartridge_type->has_battery) + size = sizeof (gb1.rtc_real); + else + size = 0; + break; + default: + size = 0; + break; + } + + return size; } void retro_cheat_reset(void) From 45e0a754915f3d52a253cdd77d3b89500c7d1fc9 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 27 Jan 2018 15:24:27 -0500 Subject: [PATCH 0489/1216] savefile hack for slot 2, both load from the same SRM file but gb2 doesn't save --- libretro/libretro.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 2c53e5a3..e909f110 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -563,7 +563,11 @@ void *retro_get_memory_data(unsigned type) break; case RETRO_MEMORY_SAVE_RAM: if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) + { data = gb1.mbc_ram; + /* let's copy the save to gb2 so it can save independently */ + memcpy(gb2.mbc_ram, gb1.mbc_ram, gb1.mbc_ram_size); + } else data = NULL; break; From 91816c30b56a719a00d0e1be51d2ce35d94e6cef Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 15:33:46 -0500 Subject: [PATCH 0490/1216] fix gameboy synchronization thanks to @LIJI32 --- libretro/libretro.c | 157 ++++++++++++++++++++++++-------------------- 1 file changed, 87 insertions(+), 70 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index e909f110..1573cd0f 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -66,6 +66,7 @@ GB_gameboy_t gb1; GB_gameboy_t gb2; extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length; +bool vblank1_occurred = false, vblank2_occurred = false; static void fallback_log(enum retro_log_level level, const char *fmt, ...) { @@ -119,12 +120,14 @@ static void audio_callback(void *gb) static void vblank1(GB_gameboy_t *gb) { + vblank1_occurred = true; GB_update_keys_status(gb, 0); audio_callback(gb); } static void vblank2(GB_gameboy_t *gb) { + vblank2_occurred = true; GB_update_keys_status(gb, 1); //audio_callback(gb); } @@ -241,24 +244,30 @@ void retro_reset(void) GB_reset(&gb2); } +static uint8_t byte_to_send1 = 0xFF, byte_to_send2 = 0xFF; + static void serial_start1(GB_gameboy_t *gb, uint8_t byte_received) { - GB_serial_set_data(&gb2, byte_received); + byte_to_send1 = byte_received; } static uint8_t serial_end1(GB_gameboy_t *gb) { - return GB_serial_get_data(&gb2); + uint8_t ret = GB_serial_get_data(&gb2); + GB_serial_set_data(&gb2, byte_to_send1); + return ret; } static void serial_start2(GB_gameboy_t *gb, uint8_t byte_received) { - GB_serial_set_data(&gb1, byte_received); + byte_to_send2 = byte_received; } static uint8_t serial_end2(GB_gameboy_t *gb) { - return GB_serial_get_data(&gb1); + uint8_t ret = GB_serial_get_data(&gb1); + GB_serial_set_data(&gb1, byte_to_send2); + return ret; } static void init_for_current_model(void) @@ -442,20 +451,28 @@ static void check_variables(void) int frames = 0; void retro_run(void) { - bool updated = false; - - if (!frame_buf) - return; - - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(); - - GB_run_frame(&gb1); - GB_run_frame(&gb2); - - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); - - frames++; + bool updated = false; + + if (!frame_buf) + return; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) + check_variables(); + + vblank1_occurred = vblank2_occurred = false; + signed delta = 0; + while (!vblank1_occurred || !vblank2_occurred) { + if (delta >= 0) { + delta -= GB_run(&gb1); + } + else { + delta += GB_run(&gb2); + } + } + + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); + + frames++; } bool retro_load_game(const struct retro_game_info *info) @@ -555,66 +572,66 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { - void* data; - switch(type) - { - case RETRO_MEMORY_SYSTEM_RAM: - data = gb1.ram; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) - { - data = gb1.mbc_ram; - /* let's copy the save to gb2 so it can save independently */ - memcpy(gb2.mbc_ram, gb1.mbc_ram, gb1.mbc_ram_size); - } - else + void* data; + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + data = gb1.ram; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) + { + data = gb1.mbc_ram; + /* let's copy the save to gb2 so it can save independently */ + memcpy(gb2.mbc_ram, gb1.mbc_ram, gb1.mbc_ram_size); + } + else + data = NULL; + break; + case RETRO_MEMORY_VIDEO_RAM: + data = gb1.vram; + break; + case RETRO_MEMORY_RTC: + if(gb1.cartridge_type->has_battery) + data = &gb1.rtc_real; + else + data = NULL; + break; + default: data = NULL; - break; - case RETRO_MEMORY_VIDEO_RAM: - data = gb1.vram; - break; - case RETRO_MEMORY_RTC: - if(gb1.cartridge_type->has_battery) - data = &gb1.rtc_real; - else - data = NULL; - break; - default: - data = NULL; } return data; } size_t retro_get_memory_size(unsigned type) { - size_t size; - switch(type) - { - case RETRO_MEMORY_SYSTEM_RAM: - size = gb1.ram_size; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) - size = gb1.mbc_ram_size; - else + size_t size; + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + size = gb1.ram_size; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) + size = gb1.mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_VIDEO_RAM: + size = gb1.vram_size; + break; + case RETRO_MEMORY_RTC: + if(gb1.cartridge_type->has_battery) + size = sizeof (gb1.rtc_real); + else + size = 0; + break; + default: size = 0; - break; - case RETRO_MEMORY_VIDEO_RAM: - size = gb1.vram_size; - break; - case RETRO_MEMORY_RTC: - if(gb1.cartridge_type->has_battery) - size = sizeof (gb1.rtc_real); - else - size = 0; - break; - default: - size = 0; - break; - } - - return size; + break; + } + + return size; } void retro_cheat_reset(void) From 49a8cd0bb5663b4223862de4289223e6ed98d526 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 16:27:56 -0500 Subject: [PATCH 0491/1216] hook up savestates for GB#2 --- libretro/libretro.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 1573cd0f..dd986b17 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -550,12 +550,20 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - return GB_get_save_state_size(&gb1); + return 2 * GB_get_save_state_size(&gb1); } bool retro_serialize(void *data, size_t size) { - GB_save_state_to_buffer(&gb1, (uint8_t*) data); + void* gb1_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); + void* gb2_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); + + GB_save_state_to_buffer(&gb1, (uint8_t*) gb1_data); + GB_save_state_to_buffer(&gb2, (uint8_t*) gb2_data); + + memcpy(data, gb1_data, size / 2); + memcpy(data + (size / 2), gb2_data, size / 2); + if (data) return true; else @@ -564,7 +572,16 @@ bool retro_serialize(void *data, size_t size) bool retro_unserialize(const void *data, size_t size) { - if (GB_load_state_from_buffer(&gb1, (uint8_t*) data, size) == 0) + void* gb1_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); + void* gb2_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); + + memcpy (gb1_data, data, size / 2); + memcpy (gb2_data, data + (size / 2), size / 2); + + int ret1 = GB_load_state_from_buffer(&gb1, gb1_data, size / 2); + int ret2 = GB_load_state_from_buffer(&gb2, gb2_data, size / 2); + + if (ret1 == 0 && ret2 ==0) return true; else return false; From b573fbcdc4545906fe25027f9e6e7e75c7234e7a Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 21:09:47 -0500 Subject: [PATCH 0492/1216] reduce code duplication --- libretro/libretro.c | 246 ++++++++++++++++++++++---------------------- 1 file changed, 125 insertions(+), 121 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index dd986b17..38d8a9ff 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -56,14 +56,16 @@ static retro_audio_sample_batch_t audio_batch_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; +static unsigned emulated_gbs = 2; + signed short soundbuf[1024 * 2]; char retro_system_directory[4096]; char retro_save_directory[4096]; char retro_game_path[4096]; -GB_gameboy_t gb1; -GB_gameboy_t gb2; +GB_gameboy_t gb[2]; + extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length; bool vblank1_occurred = false, vblank2_occurred = false; @@ -240,8 +242,9 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { - GB_reset(&gb1); - GB_reset(&gb2); + for (int i = 0; i < emulated_gbs; i++) + GB_reset(&gb[i]); + } static uint8_t byte_to_send1 = 0xFF, byte_to_send2 = 0xFF; @@ -253,8 +256,8 @@ static void serial_start1(GB_gameboy_t *gb, uint8_t byte_received) static uint8_t serial_end1(GB_gameboy_t *gb) { - uint8_t ret = GB_serial_get_data(&gb2); - GB_serial_set_data(&gb2, byte_to_send1); + uint8_t ret = GB_serial_get_data(&gb[1]); + GB_serial_set_data(&gb[1], byte_to_send1); return ret; } @@ -265,8 +268,8 @@ static void serial_start2(GB_gameboy_t *gb, uint8_t byte_received) static uint8_t serial_end2(GB_gameboy_t *gb) { - uint8_t ret = GB_serial_get_data(&gb1); - GB_serial_set_data(&gb1, byte_to_send2); + uint8_t ret = GB_serial_get_data(&gb[0]); + GB_serial_set_data(&gb[0], byte_to_send2); return ret; } @@ -276,91 +279,82 @@ static void init_for_current_model(void) if (effective_model == MODEL_AUTO) { effective_model = auto_model; } - if (GB_is_inited(&gb1)) { - GB_switch_model_and_reset(&gb1, effective_model != MODEL_DMG); - } - else { - if (effective_model == MODEL_DMG) { - GB_init(&gb1); - } - else { - GB_init_cgb(&gb1); - } - } - if (GB_is_inited(&gb2)) { - GB_switch_model_and_reset(&gb2, effective_model != MODEL_DMG); - } - else { - if (effective_model == MODEL_DMG) { - GB_init(&gb2); - } - else { - GB_init_cgb(&gb2); + + for (i = 0; i < emulated_gbs; i++) + { + if (GB_is_inited(&gb[i])) + GB_switch_model_and_reset(&gb1, effective_model != MODEL_DMG); + + else + { + if (effective_model == MODEL_DMG) + GB_init(&gb[i]); + else + GB_init_cgb(&gb[i]); } } - const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model]; - + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model]; + char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - - if (GB_load_boot_rom(&gb1, buf)) { - GB_load_boot_rom_from_buffer(&gb1, boot_code, boot_length); + + for (i = 0; i < emulated_gbs; i++) + { + if (GB_load_boot_rom(&gb[i], buf)) + GB_load_boot_rom_from_buffer(&gb[i], boot_code, boot_length); + GB_set_user_data(&gb[i], (void*)NULL); + GB_set_pixels_output(&gb[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); + GB_set_rgb_encode_callback(&gb[i], rgb_encode); + GB_set_sample_rate(&gb[i], AUDIO_FREQUENCY); } - if (GB_load_boot_rom(&gb2, buf)) { - GB_load_boot_rom_from_buffer(&gb2, boot_code, boot_length); - } - - GB_set_vblank_callback(&gb1, (GB_vblank_callback_t) vblank1); - GB_set_vblank_callback(&gb2, (GB_vblank_callback_t) vblank2); - GB_set_user_data(&gb1, (void*)NULL); - GB_set_user_data(&gb2, (void*)NULL); - GB_set_pixels_output(&gb1,(unsigned int*)frame_buf); - GB_set_rgb_encode_callback(&gb1, rgb_encode); - GB_set_pixels_output(&gb2,(unsigned int*)(frame_buf + VIDEO_PIXELS)); - GB_set_rgb_encode_callback(&gb2, rgb_encode); - GB_set_sample_rate(&gb1, AUDIO_FREQUENCY); - GB_set_sample_rate(&gb2, AUDIO_FREQUENCY); - GB_set_serial_transfer_start_callback(&gb1, serial_start1); - GB_set_serial_transfer_end_callback(&gb1, serial_end1); - GB_set_serial_transfer_start_callback(&gb2, serial_start2); - GB_set_serial_transfer_end_callback(&gb2, serial_end2); + + /* todo: attempt to make these more generic */ + GB_set_vblank_callback(&gb[0], (GB_vblank_callback_t) vblank1); + GB_set_vblank_callback(&gb[1], (GB_vblank_callback_t) vblank2); + GB_set_serial_transfer_start_callback(&gb[0], serial_start1); + GB_set_serial_transfer_end_callback(&gb[0], serial_end1); + GB_set_serial_transfer_start_callback(&gb[1], serial_start2); + GB_set_serial_transfer_end_callback(&gb[1], serial_end2); struct retro_memory_descriptor descs[7]; size_t size; uint16_t bank; + + /* todo: add netplay awareness for this so achievements can be granted on the respective client */ + i = 0; memset(descs, 0, sizeof(descs)); - - descs[0].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_IE, &size, &bank); + + descs[0].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_IE, &size, &bank); descs[0].start = 0xFFFF; descs[0].len = 1; - - descs[1].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_HRAM, &size, &bank); + + descs[1].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_HRAM, &size, &bank); descs[1].start = 0xFF80; descs[1].len = 0x0080; - - descs[2].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_RAM, &size, &bank); + + descs[2].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_RAM, &size, &bank); descs[2].start = 0xC000; descs[2].len = 0x2000; - - descs[3].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + + descs[3].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); descs[3].start = 0xA000; descs[3].len = 0x2000; - - descs[4].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_VRAM, &size, &bank); + + descs[4].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); descs[4].start = 0x8000; descs[4].len = 0x2000; - - descs[5].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_ROM, &size, &bank); + + descs[5].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_ROM, &size, &bank); descs[5].start = 0x0000; descs[5].len = 0x4000; descs[5].flags = RETRO_MEMDESC_CONST; - - descs[6].ptr = GB_get_direct_access(&gb1, GB_DIRECT_ACCESS_OAM, &size, &bank); + + descs[6].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_OAM, &size, &bank); descs[6].start = 0xFE00; descs[6].len = 0x00A0; @@ -376,27 +370,27 @@ static void check_variables(void) var.key = "sameboy_color_correction_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb1)) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) { if (strcmp(var.value, "off") == 0) { - GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_DISABLED); - GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_DISABLED); } else if (strcmp(var.value, "correct curves") == 0) { - GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_CORRECT_CURVES); - GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_CORRECT_CURVES); } else if (strcmp(var.value, "emulate hardware") == 0) { - GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_EMULATE_HARDWARE); - GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); } else if (strcmp(var.value, "preserve brightness") == 0) { - GB_set_color_correction_mode(&gb1, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - GB_set_color_correction_mode(&gb2, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } } @@ -406,18 +400,18 @@ static void check_variables(void) { if (strcmp(var.value, "off") == 0) { - GB_set_highpass_filter_mode(&gb1, GB_HIGHPASS_OFF); - GB_set_highpass_filter_mode(&gb2, GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_OFF); } else if (strcmp(var.value, "accurate") == 0) { - GB_set_highpass_filter_mode(&gb1, GB_HIGHPASS_ACCURATE); - GB_set_highpass_filter_mode(&gb2, GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_ACCURATE); } else if (strcmp(var.value, "remove dc offset") == 0) { - GB_set_highpass_filter_mode(&gb1, GB_HIGHPASS_REMOVE_DC_OFFSET); - GB_set_highpass_filter_mode(&gb2, GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_REMOVE_DC_OFFSET); } } @@ -434,11 +428,11 @@ static void check_variables(void) new_model = MODEL_AGB; else if (strcmp(var.value, "Auto") == 0) new_model = MODEL_AUTO; - if (GB_is_inited(&gb1) && new_model != model) { + if (GB_is_inited(&gb[0]) && new_model != model) { model = new_model; init_for_current_model(); } - if (GB_is_inited(&gb2) && new_model != model) { + if (GB_is_inited(&gb[1]) && new_model != model) { model = new_model; init_for_current_model(); } @@ -446,33 +440,40 @@ static void check_variables(void) model = new_model; } } + + var.key = "sameboy_link"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "enabled") == 0) + emulated_gbs = 2; + else + emulated_gbs = 2; + } } -int frames = 0; void retro_run(void) { bool updated = false; if (!frame_buf) return; - + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(); - + vblank1_occurred = vblank2_occurred = false; signed delta = 0; while (!vblank1_occurred || !vblank2_occurred) { if (delta >= 0) { - delta -= GB_run(&gb1); + delta -= GB_run(&gb[0]); } else { - delta += GB_run(&gb2); + delta += GB_run(&gb[1]); } } - + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); - - frames++; } bool retro_load_game(const struct retro_game_info *info) @@ -502,15 +503,17 @@ bool retro_load_game(const struct retro_game_info *info) auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; init_for_current_model(); - - if (GB_load_rom(&gb1,info->path)) { - log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); - return false; - } - if (GB_load_rom(&gb2,info->path)) { - log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); - return false; + + for (int i = 0; i < emulated_gbs; i++) + { + if (GB_load_rom(&gb[i],info->path)) + { + log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); + return false; + } } + + bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); @@ -523,6 +526,7 @@ bool retro_load_game(const struct retro_game_info *info) { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_link", "Link Cable; disabled|enabled" }, { NULL } }; @@ -534,8 +538,8 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { - GB_free(&gb1); - GB_free(&gb2); + for (int i = 0; i < emulated_gbs; i++) + GB_free(&gb[i]); } unsigned retro_get_region(void) @@ -550,7 +554,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - return 2 * GB_get_save_state_size(&gb1); + return 2 * GB_get_save_state_size(&gb[0]); } bool retro_serialize(void *data, size_t size) @@ -558,8 +562,8 @@ bool retro_serialize(void *data, size_t size) void* gb1_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); void* gb2_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); - GB_save_state_to_buffer(&gb1, (uint8_t*) gb1_data); - GB_save_state_to_buffer(&gb2, (uint8_t*) gb2_data); + GB_save_state_to_buffer(&gb[0], (uint8_t*) gb1_data); + GB_save_state_to_buffer(&gb[1], (uint8_t*) gb2_data); memcpy(data, gb1_data, size / 2); memcpy(data + (size / 2), gb2_data, size / 2); @@ -578,8 +582,8 @@ bool retro_unserialize(const void *data, size_t size) memcpy (gb1_data, data, size / 2); memcpy (gb2_data, data + (size / 2), size / 2); - int ret1 = GB_load_state_from_buffer(&gb1, gb1_data, size / 2); - int ret2 = GB_load_state_from_buffer(&gb2, gb2_data, size / 2); + int ret1 = GB_load_state_from_buffer(&gb[0], gb1_data, size / 2); + int ret2 = GB_load_state_from_buffer(&gb[1], gb2_data, size / 2); if (ret1 == 0 && ret2 ==0) return true; @@ -593,24 +597,24 @@ void *retro_get_memory_data(unsigned type) switch(type) { case RETRO_MEMORY_SYSTEM_RAM: - data = gb1.ram; + data = gb[0].ram; break; case RETRO_MEMORY_SAVE_RAM: - if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) + if (gb[0].cartridge_type->has_battery && gb[0].mbc_ram_size != 0) { - data = gb1.mbc_ram; - /* let's copy the save to gb2 so it can save independently */ - memcpy(gb2.mbc_ram, gb1.mbc_ram, gb1.mbc_ram_size); + data = gb[0].mbc_ram; + /* let's copy the save to gb[1] so it can save independently */ + memcpy(gb[1].mbc_ram, gb[0].mbc_ram, gb[0].mbc_ram_size); } else data = NULL; break; case RETRO_MEMORY_VIDEO_RAM: - data = gb1.vram; + data = gb[0].vram; break; case RETRO_MEMORY_RTC: - if(gb1.cartridge_type->has_battery) - data = &gb1.rtc_real; + if(gb[0].cartridge_type->has_battery) + data = &gb[0].rtc_real; else data = NULL; break; @@ -626,20 +630,20 @@ size_t retro_get_memory_size(unsigned type) switch(type) { case RETRO_MEMORY_SYSTEM_RAM: - size = gb1.ram_size; + size = gb[0].ram_size; break; case RETRO_MEMORY_SAVE_RAM: - if (gb1.cartridge_type->has_battery && gb1.mbc_ram_size != 0) - size = gb1.mbc_ram_size; + if (gb[0].cartridge_type->has_battery && gb[0].mbc_ram_size != 0) + size = gb[0].mbc_ram_size; else size = 0; break; case RETRO_MEMORY_VIDEO_RAM: - size = gb1.vram_size; + size = gb[0].vram_size; break; case RETRO_MEMORY_RTC: - if(gb1.cartridge_type->has_battery) - size = sizeof (gb1.rtc_real); + if(gb[0].cartridge_type->has_battery) + size = sizeof (gb[0].rtc_real); else size = 0; break; From 52634b80230226c5a9fdbc60bf4796106595dcb4 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 21:13:44 -0500 Subject: [PATCH 0493/1216] add layout core option --- libretro/libretro.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 38d8a9ff..fad47e6a 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -45,7 +45,11 @@ enum model { static enum model model = MODEL_AUTO; static enum model auto_model = MODEL_CGB; -static uint32_t *frame_buf; + +enum layout { + LAYOUT_TOP_DOWN, + LAYOUT_LEFT_RIGHT +}; static uint32_t *frame_buf = NULL; static struct retro_log_callback logging; @@ -56,7 +60,8 @@ static retro_audio_sample_batch_t audio_batch_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; -static unsigned emulated_gbs = 2; +static unsigned emulated_devices = 2; +static unsigned layout = 0; signed short soundbuf[1024 * 2]; @@ -242,7 +247,7 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { - for (int i = 0; i < emulated_gbs; i++) + for (int i = 0; i < emulated_devices; i++) GB_reset(&gb[i]); } @@ -280,7 +285,7 @@ static void init_for_current_model(void) effective_model = auto_model; } - for (i = 0; i < emulated_gbs; i++) + for (i = 0; i < emulated_devices; i++) { if (GB_is_inited(&gb[i])) GB_switch_model_and_reset(&gb1, effective_model != MODEL_DMG); @@ -302,7 +307,7 @@ static void init_for_current_model(void) snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - for (i = 0; i < emulated_gbs; i++) + for (i = 0; i < emulated_devices; i++) { if (GB_load_boot_rom(&gb[i], buf)) GB_load_boot_rom_from_buffer(&gb[i], boot_code, boot_length); @@ -446,9 +451,19 @@ static void check_variables(void) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "enabled") == 0) - emulated_gbs = 2; + emulated_devices = 2; else - emulated_gbs = 2; + emulated_devices = 2; + } + + var.key = "sameboy_link_layout"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "top-down") == 0) + layout = LAYOUT_TOP_DOWN; + else + layout = LAYOUT_LEFT_RIGHT; } } @@ -504,7 +519,7 @@ bool retro_load_game(const struct retro_game_info *info) auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; init_for_current_model(); - for (int i = 0; i < emulated_gbs; i++) + for (int i = 0; i < emulated_devices; i++) { if (GB_load_rom(&gb[i],info->path)) { @@ -527,6 +542,7 @@ bool retro_load_game(const struct retro_game_info *info) { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, { NULL } }; @@ -538,7 +554,7 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { - for (int i = 0; i < emulated_gbs; i++) + for (int i = 0; i < emulated_devices; i++) GB_free(&gb[i]); } From f7d129bd2434c110d7da2c3a5c25e3861ad77ee7 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 22:06:41 -0500 Subject: [PATCH 0494/1216] separate core options for single and linked mode --- libretro/libretro.c | 261 +++++++++++++++++++++++++++++++------------- 1 file changed, 186 insertions(+), 75 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index fad47e6a..2eaef490 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -51,6 +51,7 @@ enum layout { LAYOUT_LEFT_RIGHT }; +static enum model model[2]; static uint32_t *frame_buf = NULL; static struct retro_log_callback logging; static retro_log_printf_t log_cb; @@ -288,29 +289,30 @@ static void init_for_current_model(void) for (i = 0; i < emulated_devices; i++) { if (GB_is_inited(&gb[i])) - GB_switch_model_and_reset(&gb1, effective_model != MODEL_DMG); + GB_switch_model_and_reset(&gb[i], effective_model[i] != MODEL_DMG); else { - if (effective_model == MODEL_DMG) + if (effective_model[i] == MODEL_DMG) GB_init(&gb[i]); else GB_init_cgb(&gb[i]); } + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model[i]]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model[i]]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model[i]]; + + char buf[256]; + snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + if (GB_load_boot_rom(&gb[i], buf)) + GB_load_boot_rom_from_buffer(&gb[i], boot_code, boot_length); } - const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model]; - - char buf[256]; - snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + for (i = 0; i < emulated_devices; i++) { - if (GB_load_boot_rom(&gb[i], buf)) - GB_load_boot_rom_from_buffer(&gb[i], boot_code, boot_length); GB_set_user_data(&gb[i], (void*)NULL); GB_set_pixels_output(&gb[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); GB_set_rgb_encode_callback(&gb[i], rgb_encode); @@ -369,83 +371,172 @@ static void init_for_current_model(void) environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); } -static void check_variables(void) +static void check_variables(bool link) { struct retro_variable var = {0}; - var.key = "sameboy_color_correction_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) + if (!link) { - if (strcmp(var.value, "off") == 0) + var.key = "sameboy_color_correction_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) { - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_DISABLED); + if (strcmp(var.value, "off") == 0) + { + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_DISABLED); + } + else if (strcmp(var.value, "correct curves") == 0) + { + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_CORRECT_CURVES); + } + else if (strcmp(var.value, "emulate hardware") == 0) + { + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + } + else if (strcmp(var.value, "preserve brightness") == 0) + { + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + } } - else if (strcmp(var.value, "correct curves") == 0) + + var.key = "sameboy_high_pass_filter_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_CORRECT_CURVES); + if (strcmp(var.value, "off") == 0) + { + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_OFF); + } + else if (strcmp(var.value, "accurate") == 0) + { + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_ACCURATE); + } + else if (strcmp(var.value, "remove dc offset") == 0) + { + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_REMOVE_DC_OFFSET); + } } - else if (strcmp(var.value, "emulate hardware") == 0) + + var.key = "sameboy_model"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - } - else if (strcmp(var.value, "preserve brightness") == 0) - { - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + enum model new_model = model[0]; + if (strcmp(var.value, "Game Boy") == 0) + new_model = MODEL_DMG; + else if (strcmp(var.value, "Game Boy Color") == 0) + new_model = MODEL_CGB; + else if (strcmp(var.value, "Game Boy Advance") == 0) + new_model = MODEL_AGB; + else if (strcmp(var.value, "Auto") == 0) + new_model = MODEL_AUTO; + if (GB_is_inited(&gb[0]) && new_model != model[0]) { + model[0] = new_model; + init_for_current_model(); + } + if (GB_is_inited(&gb[1]) && new_model != model[0]) { + model[0] = new_model; + init_for_current_model(); + } } } - - var.key = "sameboy_high_pass_filter_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + else { - if (strcmp(var.value, "off") == 0) + var.key = "sameboy_color_correction_mode_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) { - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_OFF); + if (strcmp(var.value, "off") == 0) + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); + else if (strcmp(var.value, "correct curves") == 0) + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + else if (strcmp(var.value, "emulate hardware") == 0) + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + else if (strcmp(var.value, "preserve brightness") == 0) + GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } - else if (strcmp(var.value, "accurate") == 0) - { - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_ACCURATE); - } - else if (strcmp(var.value, "remove dc offset") == 0) - { - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_REMOVE_DC_OFFSET); - } - } - var.key = "sameboy_model"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - enum model new_model = model; - if (strcmp(var.value, "Game Boy") == 0) - new_model = MODEL_DMG; - else if (strcmp(var.value, "Game Boy Color") == 0) - new_model = MODEL_CGB; - else if (strcmp(var.value, "Game Boy Advance") == 0) - new_model = MODEL_AGB; - else if (strcmp(var.value, "Auto") == 0) - new_model = MODEL_AUTO; - if (GB_is_inited(&gb[0]) && new_model != model) { - model = new_model; - init_for_current_model(); + var.key = "sameboy_color_correction_mode_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[1])) + { + if (strcmp(var.value, "off") == 0) + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_DISABLED); + else if (strcmp(var.value, "correct curves") == 0) + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_CORRECT_CURVES); + else if (strcmp(var.value, "emulate hardware") == 0) + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + else if (strcmp(var.value, "preserve brightness") == 0) + GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } - if (GB_is_inited(&gb[1]) && new_model != model) { - model = new_model; - init_for_current_model(); - } - else { - model = new_model; - } - } + var.key = "sameboy_high_pass_filter_mode_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "off") == 0) + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); + else if (strcmp(var.value, "accurate") == 0) + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); + else if (strcmp(var.value, "remove dc offset") == 0) + GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + } + + var.key = "sameboy_high_pass_filter_mode_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "off") == 0) + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_OFF); + else if (strcmp(var.value, "accurate") == 0) + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_ACCURATE); + else if (strcmp(var.value, "remove dc offset") == 0) + GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_REMOVE_DC_OFFSET); + } + + var.key = "sameboy_model_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + enum model new_model = model[0]; + if (strcmp(var.value, "Game Boy") == 0) + new_model = MODEL_DMG; + else if (strcmp(var.value, "Game Boy Color") == 0) + new_model = MODEL_CGB; + else if (strcmp(var.value, "Game Boy Advance") == 0) + new_model = MODEL_AGB; + if (GB_is_inited(&gb[0]) && new_model != model[0]) { + model[0] = new_model; + init_for_current_model(); + } + } + + var.key = "sameboy_model_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + enum model new_model = model[0]; + if (strcmp(var.value, "Game Boy") == 0) + new_model = MODEL_DMG; + else if (strcmp(var.value, "Game Boy Color") == 0) + new_model = MODEL_CGB; + else if (strcmp(var.value, "Game Boy Advance") == 0) + new_model = MODEL_AGB; + if (GB_is_inited(&gb[1]) && new_model != model[0]) { + model[0] = new_model; + init_for_current_model(); + } + } + + } + var.key = "sameboy_link"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -475,7 +566,7 @@ void retro_run(void) return; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(); + check_variables(emulated_devices == 2 ? true : false); vblank1_occurred = vblank2_occurred = false; signed delta = 0; @@ -491,6 +582,27 @@ void retro_run(void) video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); } +static const struct retro_variable vars[] = { + { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, + { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, + { NULL } +}; +static const struct retro_variable vars_link[] = { + { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_model_1", "Emulated Model for GB #1; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model_2", "Emulated Model for GB #2; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_color_correction_mode_1", "Color Correction for GB #1; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode_2", "Color Correction for GB #2; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode_1", "High Pass Filter for GB #1; off|accurate|remove dc offset" }, + { "sameboy_high_pass_filter_mode_2", "High Pass Filter for GB #2; off|accurate|remove dc offset" }, + + { NULL } +}; + bool retro_load_game(const struct retro_game_info *info) { struct retro_input_descriptor desc[] = { @@ -528,7 +640,6 @@ bool retro_load_game(const struct retro_game_info *info) } } - bool yes = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); @@ -546,8 +657,8 @@ bool retro_load_game(const struct retro_game_info *info) { NULL } }; - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); - check_variables(); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + check_variables(emulated_devices == 2 ? true : false); return true; } From 7ee063da28802eaa2539ec0b15f1bf43cedd4c4b Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 22:42:26 -0500 Subject: [PATCH 0495/1216] single mode works --- libretro/libretro.c | 382 +++++++++++++++++++++----------------------- 1 file changed, 183 insertions(+), 199 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 2eaef490..2ca3ecb1 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -61,7 +61,7 @@ static retro_audio_sample_batch_t audio_batch_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; -static unsigned emulated_devices = 2; +static unsigned emulated_devices = 1; static unsigned layout = 0; signed short soundbuf[1024 * 2]; @@ -125,7 +125,6 @@ static void audio_callback(void *gb) } } - static void vblank1(GB_gameboy_t *gb) { vblank1_occurred = true; @@ -140,119 +139,6 @@ static void vblank2(GB_gameboy_t *gb) //audio_callback(gb); } -static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) -{ - return r<<16|g<<8|b; -} - -static retro_environment_t environ_cb; - -void retro_init(void) -{ - frame_buf = (uint32_t*)malloc(2 * VIDEO_PIXELS * sizeof(uint32_t)); - const char *dir = NULL; - - if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) - snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); - else - snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); - - if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) - snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); - else - snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); -} - -void retro_deinit(void) -{ - free(frame_buf); - frame_buf = NULL; -} - -unsigned retro_api_version(void) -{ - return RETRO_API_VERSION; -} - -void retro_set_controller_port_device(unsigned port, unsigned device) -{ - log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port); -} - -void retro_get_system_info(struct retro_system_info *info) -{ - memset(info, 0, sizeof(*info)); - info->library_name = "SameBoy"; -#ifdef GIT_VERSION - info->library_version = SAMEBOY_CORE_VERSION GIT_VERSION; -#else - info->library_version = SAMEBOY_CORE_VERSION; -#endif - info->need_fullpath = true; - info->valid_extensions = "gb|gbc"; -} - -void retro_get_system_av_info(struct retro_system_av_info *info) -{ - struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH, VIDEO_HEIGHT * 2 ,160.0 / 288.0 }; - struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; - - info->geometry = geom; - info->timing = timing; - -} - -void retro_set_environment(retro_environment_t cb) -{ - environ_cb = cb; - - if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) - log_cb = logging.log; - else - log_cb = fallback_log; - - static const struct retro_controller_description controllers[] = { - { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, - }; - - static const struct retro_controller_info ports[] = { - { controllers, 1 }, - { NULL, 0 }, - }; - cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); -} - -void retro_set_audio_sample(retro_audio_sample_t cb) -{ -} - -void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) -{ - audio_batch_cb = cb; -} - -void retro_set_input_poll(retro_input_poll_t cb) -{ - input_poll_cb = cb; -} - -void retro_set_input_state(retro_input_state_t cb) -{ - input_state_cb = cb; -} - -void retro_set_video_refresh(retro_video_refresh_t cb) -{ - video_cb = cb; -} - -void retro_reset(void) -{ - for (int i = 0; i < emulated_devices; i++) - GB_reset(&gb[i]); - -} - static uint8_t byte_to_send1 = 0xFF, byte_to_send2 = 0xFF; static void serial_start1(GB_gameboy_t *gb, uint8_t byte_received) @@ -279,6 +165,33 @@ static uint8_t serial_end2(GB_gameboy_t *gb) return ret; } +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return r<<16|g<<8|b; +} + +static retro_environment_t environ_cb; + +static const struct retro_variable vars[] = { + { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, + { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, + { NULL } +}; +static const struct retro_variable vars_link[] = { + { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_model_1", "Emulated Model for GB #1; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model_2", "Emulated Model for GB #2; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_color_correction_mode_1", "Color Correction for GB #1; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode_2", "Color Correction for GB #2; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode_1", "High Pass Filter for GB #1; off|accurate|remove dc offset" }, + { "sameboy_high_pass_filter_mode_2", "High Pass Filter for GB #2; off|accurate|remove dc offset" }, + { NULL } +}; + static void init_for_current_model(void) { enum model effective_model = model; @@ -307,12 +220,6 @@ static void init_for_current_model(void) log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); if (GB_load_boot_rom(&gb[i], buf)) GB_load_boot_rom_from_buffer(&gb[i], boot_code, boot_length); - } - - - - for (i = 0; i < emulated_devices; i++) - { GB_set_user_data(&gb[i], (void*)NULL); GB_set_pixels_output(&gb[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); GB_set_rgb_encode_callback(&gb[i], rgb_encode); @@ -321,11 +228,14 @@ static void init_for_current_model(void) /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gb[0], (GB_vblank_callback_t) vblank1); - GB_set_vblank_callback(&gb[1], (GB_vblank_callback_t) vblank2); - GB_set_serial_transfer_start_callback(&gb[0], serial_start1); - GB_set_serial_transfer_end_callback(&gb[0], serial_end1); - GB_set_serial_transfer_start_callback(&gb[1], serial_start2); - GB_set_serial_transfer_end_callback(&gb[1], serial_end2); + if (emulated_devices == 2) + { + GB_set_vblank_callback(&gb[1], (GB_vblank_callback_t) vblank2); + GB_set_serial_transfer_start_callback(&gb[0], serial_start1); + GB_set_serial_transfer_end_callback(&gb[0], serial_end1); + GB_set_serial_transfer_start_callback(&gb[1], serial_start2); + GB_set_serial_transfer_end_callback(&gb[1], serial_end2); + } struct retro_memory_descriptor descs[7]; size_t size; @@ -382,25 +292,13 @@ static void check_variables(bool link) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) { if (strcmp(var.value, "off") == 0) - { GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_DISABLED); - } else if (strcmp(var.value, "correct curves") == 0) - { GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_CORRECT_CURVES); - } else if (strcmp(var.value, "emulate hardware") == 0) - { GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - } else if (strcmp(var.value, "preserve brightness") == 0) - { GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - } } var.key = "sameboy_high_pass_filter_mode"; @@ -408,20 +306,11 @@ static void check_variables(bool link) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) - { GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_OFF); - } else if (strcmp(var.value, "accurate") == 0) - { GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_ACCURATE); - } else if (strcmp(var.value, "remove dc offset") == 0) - { GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_REMOVE_DC_OFFSET); - } } var.key = "sameboy_model"; @@ -441,10 +330,6 @@ static void check_variables(bool link) model[0] = new_model; init_for_current_model(); } - if (GB_is_inited(&gb[1]) && new_model != model[0]) { - model[0] = new_model; - init_for_current_model(); - } } } else @@ -544,7 +429,7 @@ static void check_variables(bool link) if (strcmp(var.value, "enabled") == 0) emulated_devices = 2; else - emulated_devices = 2; + emulated_devices = 1; } var.key = "sameboy_link_layout"; @@ -558,6 +443,115 @@ static void check_variables(bool link) } } +void retro_init(void) +{ + frame_buf = (uint32_t*)malloc(2 * VIDEO_PIXELS * sizeof(uint32_t)); + const char *dir = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) + snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); + else + snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); + + if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) + snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); + else + snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); + + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + check_variables(emulated_devices == 2 ? true : false); +} + +void retro_deinit(void) +{ + free(frame_buf); + frame_buf = NULL; +} + +unsigned retro_api_version(void) +{ + return RETRO_API_VERSION; +} + +void retro_set_controller_port_device(unsigned port, unsigned device) +{ + log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port); +} + +void retro_get_system_info(struct retro_system_info *info) +{ + memset(info, 0, sizeof(*info)); + info->library_name = "SameBoy"; +#ifdef GIT_VERSION + info->library_version = SAMEBOY_CORE_VERSION GIT_VERSION; +#else + info->library_version = SAMEBOY_CORE_VERSION; +#endif + info->need_fullpath = true; + info->valid_extensions = "gb|gbc"; +} + +void retro_get_system_av_info(struct retro_system_av_info *info) +{ + struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH, VIDEO_HEIGHT * 2 ,160.0 / 288.0 }; + struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; + + info->geometry = geom; + info->timing = timing; + +} + +void retro_set_environment(retro_environment_t cb) +{ + environ_cb = cb; + + if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) + log_cb = logging.log; + else + log_cb = fallback_log; + + static const struct retro_controller_description controllers[] = { + { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, + }; + + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { NULL, 0 }, + }; + cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); +} + +void retro_set_audio_sample(retro_audio_sample_t cb) +{ +} + +void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) +{ + audio_batch_cb = cb; +} + +void retro_set_input_poll(retro_input_poll_t cb) +{ + input_poll_cb = cb; +} + +void retro_set_input_state(retro_input_state_t cb) +{ + input_state_cb = cb; +} + +void retro_set_video_refresh(retro_video_refresh_t cb) +{ + video_cb = cb; +} + +void retro_reset(void) +{ + for (int i = 0; i < emulated_devices; i++) + GB_reset(&gb[i]); + +} + void retro_run(void) { bool updated = false; @@ -570,39 +564,23 @@ void retro_run(void) vblank1_occurred = vblank2_occurred = false; signed delta = 0; + if (emulated_devices == 2) + { while (!vblank1_occurred || !vblank2_occurred) { - if (delta >= 0) { - delta -= GB_run(&gb[0]); - } - else { - delta += GB_run(&gb[1]); + if (delta >= 0) { + delta -= GB_run(&gb[0]); + } + else { + delta += GB_run(&gb[1]); + } } } + else + GB_run_frame(&gb[0]); video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); } -static const struct retro_variable vars[] = { - { "sameboy_link", "Link Cable; disabled|enabled" }, - { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, - { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, - { NULL } -}; -static const struct retro_variable vars_link[] = { - { "sameboy_link", "Link Cable; disabled|enabled" }, - { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, - { "sameboy_model_1", "Emulated Model for GB #1; Game Boy Color|Game Boy Advance|Game Boy" }, - { "sameboy_model_2", "Emulated Model for GB #2; Game Boy Color|Game Boy Advance|Game Boy" }, - { "sameboy_color_correction_mode_1", "Color Correction for GB #1; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_color_correction_mode_2", "Color Correction for GB #2; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode_1", "High Pass Filter for GB #1; off|accurate|remove dc offset" }, - { "sameboy_high_pass_filter_mode_2", "High Pass Filter for GB #2; off|accurate|remove dc offset" }, - - { NULL } -}; - bool retro_load_game(const struct retro_game_info *info) { struct retro_input_descriptor desc[] = { @@ -659,7 +637,6 @@ bool retro_load_game(const struct retro_game_info *info) environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); check_variables(emulated_devices == 2 ? true : false); - return true; } @@ -681,19 +658,23 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - return 2 * GB_get_save_state_size(&gb[0]); + size_t size = 0; + for (int i = 0; i < emulated_devices; i++) + size += GB_get_save_state_size(&gb[i]); + + return size; } bool retro_serialize(void *data, size_t size) { - void* gb1_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); - void* gb2_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); - - GB_save_state_to_buffer(&gb[0], (uint8_t*) gb1_data); - GB_save_state_to_buffer(&gb[1], (uint8_t*) gb2_data); - - memcpy(data, gb1_data, size / 2); - memcpy(data + (size / 2), gb2_data, size / 2); + void* save_data[2]; + for (int i = 0; i < emulated_devices; i++) + { + save_data[i] = (uint8_t*)malloc(size * sizeof(uint8_t) / emulated_devices); + GB_save_state_to_buffer(&gb[i], (uint8_t*) save_data[i]); + memcpy(data + (size * i / emulated_devices), save_data[i], size / emulated_devices); + free(save_data[i]); + } if (data) return true; @@ -703,19 +684,22 @@ bool retro_serialize(void *data, size_t size) bool retro_unserialize(const void *data, size_t size) { - void* gb1_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); - void* gb2_data = (uint8_t*)malloc(0.5 * size * sizeof(uint8_t)); + void* save_data[2]; + int ret; - memcpy (gb1_data, data, size / 2); - memcpy (gb2_data, data + (size / 2), size / 2); + for (int i = 0; i < emulated_devices; i++) + { + save_data[i] = (uint8_t*)malloc(size * sizeof(uint8_t)/ emulated_devices); + memcpy (save_data[i], data + (size * i / emulated_devices), size / emulated_devices); + ret = GB_load_state_from_buffer(&gb[i], save_data[i], size / emulated_devices); + free(save_data[i]); - int ret1 = GB_load_state_from_buffer(&gb[0], gb1_data, size / 2); - int ret2 = GB_load_state_from_buffer(&gb[1], gb2_data, size / 2); + if (ret != 0) + return false; + } + + return true; - if (ret1 == 0 && ret2 ==0) - return true; - else - return false; } void *retro_get_memory_data(unsigned type) @@ -731,7 +715,7 @@ void *retro_get_memory_data(unsigned type) { data = gb[0].mbc_ram; /* let's copy the save to gb[1] so it can save independently */ - memcpy(gb[1].mbc_ram, gb[0].mbc_ram, gb[0].mbc_ram_size); + //memcpy(gb[1].mbc_ram, gb[0].mbc_ram, gb[0].mbc_ram_size); } else data = NULL; From 8b506e84fdb76b3f1517305934342819d1262371 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 23:06:14 -0500 Subject: [PATCH 0496/1216] fix changing mode between link enabled and disabled --- libretro/libretro.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 2ca3ecb1..741ee79c 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -62,6 +62,7 @@ static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; static unsigned emulated_devices = 1; +static unsigned pre_init = 1; static unsigned layout = 0; signed short soundbuf[1024 * 2]; @@ -173,7 +174,7 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; static const struct retro_variable vars[] = { - { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link", "Link Cable (restart); disabled|enabled" }, { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, @@ -421,15 +422,18 @@ static void check_variables(bool link) } } - + var.key = "sameboy_link"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + int old_emulated_devices = emulated_devices; if (strcmp(var.value, "enabled") == 0) emulated_devices = 2; else emulated_devices = 1; + if (pre_init == 0 && emulated_devices != old_emulated_devices) + emulated_devices = old_emulated_devices; } var.key = "sameboy_link_layout"; @@ -460,6 +464,7 @@ void retro_init(void) environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); check_variables(emulated_devices == 2 ? true : false); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); } void retro_deinit(void) @@ -555,6 +560,7 @@ void retro_reset(void) void retro_run(void) { bool updated = false; + pre_init = 0; if (!frame_buf) return; @@ -658,15 +664,14 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - size_t size = 0; - for (int i = 0; i < emulated_devices; i++) - size += GB_get_save_state_size(&gb[i]); - - return size; + return 2 * GB_get_save_state_size(&gb[0]); } bool retro_serialize(void *data, size_t size) { + if (pre_init == 1) + return false; + void* save_data[2]; for (int i = 0; i < emulated_devices; i++) { From 457b3b3f065a8eebbd91a9558424865684f39dd4 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 23:09:40 -0500 Subject: [PATCH 0497/1216] change viewport size --- libretro/libretro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 741ee79c..fe6235cb 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -498,7 +498,7 @@ void retro_get_system_info(struct retro_system_info *info) void retro_get_system_av_info(struct retro_system_av_info *info) { - struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH, VIDEO_HEIGHT * 2 ,160.0 / 288.0 }; + struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices , VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT) }; struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; info->geometry = geom; @@ -584,7 +584,7 @@ void retro_run(void) else GB_run_frame(&gb[0]); - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * 2, VIDEO_WIDTH * sizeof(uint32_t)); + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); } bool retro_load_game(const struct retro_game_info *info) From 88dfb9a1594f2541c3c05266c463d45ad89915e5 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 31 Jan 2018 23:21:29 -0500 Subject: [PATCH 0498/1216] fix emulated model for slot 2 --- libretro/libretro.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index fe6235cb..7e2c60a8 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -408,15 +408,15 @@ static void check_variables(bool link) var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - enum model new_model = model[0]; + enum model new_model = model[1]; if (strcmp(var.value, "Game Boy") == 0) new_model = MODEL_DMG; else if (strcmp(var.value, "Game Boy Color") == 0) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; - if (GB_is_inited(&gb[1]) && new_model != model[0]) { - model[0] = new_model; + if (GB_is_inited(&gb[1]) && new_model != model[1]) { + model[1] = new_model; init_for_current_model(); } } From 945ee0bdb39e0ef2c6f096e12274d64197cbd576 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 1 Feb 2018 09:15:22 -0500 Subject: [PATCH 0499/1216] rename the array so it doesn't conflict with the function signature --- libretro/libretro.c | 156 ++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 7e2c60a8..9f937bca 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -71,7 +71,7 @@ char retro_system_directory[4096]; char retro_save_directory[4096]; char retro_game_path[4096]; -GB_gameboy_t gb[2]; +GB_gameboy_t gameboy[2]; extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length; @@ -149,8 +149,8 @@ static void serial_start1(GB_gameboy_t *gb, uint8_t byte_received) static uint8_t serial_end1(GB_gameboy_t *gb) { - uint8_t ret = GB_serial_get_data(&gb[1]); - GB_serial_set_data(&gb[1], byte_to_send1); + uint8_t ret = GB_serial_get_data(&gameboy[1]); + GB_serial_set_data(&gameboy[1], byte_to_send1); return ret; } @@ -161,8 +161,8 @@ static void serial_start2(GB_gameboy_t *gb, uint8_t byte_received) static uint8_t serial_end2(GB_gameboy_t *gb) { - uint8_t ret = GB_serial_get_data(&gb[0]); - GB_serial_set_data(&gb[0], byte_to_send2); + uint8_t ret = GB_serial_get_data(&gameboy[0]); + GB_serial_set_data(&gameboy[0], byte_to_send2); return ret; } @@ -202,15 +202,15 @@ static void init_for_current_model(void) for (i = 0; i < emulated_devices; i++) { - if (GB_is_inited(&gb[i])) - GB_switch_model_and_reset(&gb[i], effective_model[i] != MODEL_DMG); + if (GB_is_inited(&gameboy[i])) + GB_switch_model_and_reset(&gameboy[i], effective_model[i] != MODEL_DMG); else { if (effective_model[i] == MODEL_DMG) - GB_init(&gb[i]); + GB_init(&gameboy[i]); else - GB_init_cgb(&gb[i]); + GB_init_cgb(&gameboy[i]); } const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model[i]]; const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model[i]]; @@ -219,23 +219,23 @@ static void init_for_current_model(void) char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - if (GB_load_boot_rom(&gb[i], buf)) - GB_load_boot_rom_from_buffer(&gb[i], boot_code, boot_length); - GB_set_user_data(&gb[i], (void*)NULL); - GB_set_pixels_output(&gb[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); - GB_set_rgb_encode_callback(&gb[i], rgb_encode); - GB_set_sample_rate(&gb[i], AUDIO_FREQUENCY); + if (GB_load_boot_rom(&gameboy[i], buf)) + GB_load_boot_rom_from_buffer(&gameboy[i], boot_code, boot_length); + GB_set_user_data(&gameboy[i], (void*)NULL); + GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); + GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); + GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); } /* todo: attempt to make these more generic */ - GB_set_vblank_callback(&gb[0], (GB_vblank_callback_t) vblank1); + GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); if (emulated_devices == 2) { - GB_set_vblank_callback(&gb[1], (GB_vblank_callback_t) vblank2); - GB_set_serial_transfer_start_callback(&gb[0], serial_start1); - GB_set_serial_transfer_end_callback(&gb[0], serial_end1); - GB_set_serial_transfer_start_callback(&gb[1], serial_start2); - GB_set_serial_transfer_end_callback(&gb[1], serial_end2); + GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); + GB_set_serial_transfer_start_callback(&gameboy[0], serial_start1); + GB_set_serial_transfer_end_callback(&gameboy[0], serial_end1); + GB_set_serial_transfer_start_callback(&gameboy[1], serial_start2); + GB_set_serial_transfer_end_callback(&gameboy[1], serial_end2); } struct retro_memory_descriptor descs[7]; @@ -247,32 +247,32 @@ static void init_for_current_model(void) i = 0; memset(descs, 0, sizeof(descs)); - descs[0].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_IE, &size, &bank); + descs[0].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IE, &size, &bank); descs[0].start = 0xFFFF; descs[0].len = 1; - descs[1].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_HRAM, &size, &bank); + descs[1].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_HRAM, &size, &bank); descs[1].start = 0xFF80; descs[1].len = 0x0080; - descs[2].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_RAM, &size, &bank); + descs[2].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank); descs[2].start = 0xC000; descs[2].len = 0x2000; - descs[3].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + descs[3].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); descs[3].start = 0xA000; descs[3].len = 0x2000; - descs[4].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); + descs[4].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); descs[4].start = 0x8000; descs[4].len = 0x2000; - descs[5].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_ROM, &size, &bank); + descs[5].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_ROM, &size, &bank); descs[5].start = 0x0000; descs[5].len = 0x4000; descs[5].flags = RETRO_MEMDESC_CONST; - descs[6].ptr = GB_get_direct_access(&gb[i], GB_DIRECT_ACCESS_OAM, &size, &bank); + descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); descs[6].start = 0xFE00; descs[6].len = 0x00A0; @@ -290,16 +290,16 @@ static void check_variables(bool link) { var.key = "sameboy_color_correction_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gameboy[0])) { if (strcmp(var.value, "off") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); else if (strcmp(var.value, "correct curves") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); else if (strcmp(var.value, "emulate hardware") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } var.key = "sameboy_high_pass_filter_mode"; @@ -307,11 +307,11 @@ static void check_variables(bool link) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); else if (strcmp(var.value, "accurate") == 0) - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); else if (strcmp(var.value, "remove dc offset") == 0) - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); } var.key = "sameboy_model"; @@ -327,7 +327,7 @@ static void check_variables(bool link) new_model = MODEL_AGB; else if (strcmp(var.value, "Auto") == 0) new_model = MODEL_AUTO; - if (GB_is_inited(&gb[0]) && new_model != model[0]) { + if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { model[0] = new_model; init_for_current_model(); } @@ -337,30 +337,30 @@ static void check_variables(bool link) { var.key = "sameboy_color_correction_mode_1"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[0])) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gameboy[0])) { if (strcmp(var.value, "off") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); else if (strcmp(var.value, "correct curves") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); else if (strcmp(var.value, "emulate hardware") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) - GB_set_color_correction_mode(&gb[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } var.key = "sameboy_color_correction_mode_2"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gb[1])) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gameboy[1])) { if (strcmp(var.value, "off") == 0) - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_DISABLED); + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_DISABLED); else if (strcmp(var.value, "correct curves") == 0) - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_CORRECT_CURVES); + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_CORRECT_CURVES); else if (strcmp(var.value, "emulate hardware") == 0) - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) - GB_set_color_correction_mode(&gb[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } var.key = "sameboy_high_pass_filter_mode_1"; @@ -368,11 +368,11 @@ static void check_variables(bool link) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); else if (strcmp(var.value, "accurate") == 0) - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); else if (strcmp(var.value, "remove dc offset") == 0) - GB_set_highpass_filter_mode(&gb[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); } var.key = "sameboy_high_pass_filter_mode_2"; @@ -380,11 +380,11 @@ static void check_variables(bool link) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_OFF); + GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_OFF); else if (strcmp(var.value, "accurate") == 0) - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_ACCURATE); + GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_ACCURATE); else if (strcmp(var.value, "remove dc offset") == 0) - GB_set_highpass_filter_mode(&gb[1], GB_HIGHPASS_REMOVE_DC_OFFSET); + GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_REMOVE_DC_OFFSET); } var.key = "sameboy_model_1"; @@ -398,7 +398,7 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; - if (GB_is_inited(&gb[0]) && new_model != model[0]) { + if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { model[0] = new_model; init_for_current_model(); } @@ -415,7 +415,7 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; - if (GB_is_inited(&gb[1]) && new_model != model[1]) { + if (GB_is_inited(&gameboy[1]) && new_model != model[1]) { model[1] = new_model; init_for_current_model(); } @@ -553,7 +553,7 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { for (int i = 0; i < emulated_devices; i++) - GB_reset(&gb[i]); + GB_reset(&gameboy[i]); } @@ -574,15 +574,15 @@ void retro_run(void) { while (!vblank1_occurred || !vblank2_occurred) { if (delta >= 0) { - delta -= GB_run(&gb[0]); + delta -= GB_run(&gameboy[0]); } else { - delta += GB_run(&gb[1]); + delta += GB_run(&gameboy[1]); } } } else - GB_run_frame(&gb[0]); + GB_run_frame(&gameboy[0]); video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); } @@ -617,7 +617,7 @@ bool retro_load_game(const struct retro_game_info *info) for (int i = 0; i < emulated_devices; i++) { - if (GB_load_rom(&gb[i],info->path)) + if (GB_load_rom(&gameboy[i],info->path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); return false; @@ -649,7 +649,7 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { for (int i = 0; i < emulated_devices; i++) - GB_free(&gb[i]); + GB_free(&gameboy[i]); } unsigned retro_get_region(void) @@ -664,7 +664,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - return 2 * GB_get_save_state_size(&gb[0]); + return 2 * GB_get_save_state_size(&gameboy[0]); } bool retro_serialize(void *data, size_t size) @@ -676,7 +676,7 @@ bool retro_serialize(void *data, size_t size) for (int i = 0; i < emulated_devices; i++) { save_data[i] = (uint8_t*)malloc(size * sizeof(uint8_t) / emulated_devices); - GB_save_state_to_buffer(&gb[i], (uint8_t*) save_data[i]); + GB_save_state_to_buffer(&gameboy[i], (uint8_t*) save_data[i]); memcpy(data + (size * i / emulated_devices), save_data[i], size / emulated_devices); free(save_data[i]); } @@ -696,7 +696,7 @@ bool retro_unserialize(const void *data, size_t size) { save_data[i] = (uint8_t*)malloc(size * sizeof(uint8_t)/ emulated_devices); memcpy (save_data[i], data + (size * i / emulated_devices), size / emulated_devices); - ret = GB_load_state_from_buffer(&gb[i], save_data[i], size / emulated_devices); + ret = GB_load_state_from_buffer(&gameboy[i], save_data[i], size / emulated_devices); free(save_data[i]); if (ret != 0) @@ -713,24 +713,24 @@ void *retro_get_memory_data(unsigned type) switch(type) { case RETRO_MEMORY_SYSTEM_RAM: - data = gb[0].ram; + data = gameboy[0].ram; break; case RETRO_MEMORY_SAVE_RAM: - if (gb[0].cartridge_type->has_battery && gb[0].mbc_ram_size != 0) + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { - data = gb[0].mbc_ram; - /* let's copy the save to gb[1] so it can save independently */ - //memcpy(gb[1].mbc_ram, gb[0].mbc_ram, gb[0].mbc_ram_size); + data = gameboy[0].mbc_ram; + /* let's copy the save to gameboy[1] so it can save independently */ + //memcpy(gameboy[1].mbc_ram, gameboy[0].mbc_ram, gameboy[0].mbc_ram_size); } else data = NULL; break; case RETRO_MEMORY_VIDEO_RAM: - data = gb[0].vram; + data = gameboy[0].vram; break; case RETRO_MEMORY_RTC: - if(gb[0].cartridge_type->has_battery) - data = &gb[0].rtc_real; + if(gameboy[0].cartridge_type->has_battery) + data = &gameboy[0].rtc_real; else data = NULL; break; @@ -746,20 +746,20 @@ size_t retro_get_memory_size(unsigned type) switch(type) { case RETRO_MEMORY_SYSTEM_RAM: - size = gb[0].ram_size; + size = gameboy[0].ram_size; break; case RETRO_MEMORY_SAVE_RAM: - if (gb[0].cartridge_type->has_battery && gb[0].mbc_ram_size != 0) - size = gb[0].mbc_ram_size; + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + size = gameboy[0].mbc_ram_size; else size = 0; break; case RETRO_MEMORY_VIDEO_RAM: - size = gb[0].vram_size; + size = gameboy[0].vram_size; break; case RETRO_MEMORY_RTC: - if(gb[0].cartridge_type->has_battery) - size = sizeof (gb[0].rtc_real); + if(gameboy[0].cartridge_type->has_battery) + size = sizeof (gameboy[0].rtc_real); else size = 0; break; From 408bf5baab5d5a00b7698d766becb04460b03c39 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 1 Feb 2018 10:03:40 -0500 Subject: [PATCH 0500/1216] add audio output selection --- libretro/libretro.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 9f937bca..92c4cc7c 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -46,11 +46,16 @@ enum model { static enum model model = MODEL_AUTO; static enum model auto_model = MODEL_CGB; -enum layout { +enum screen_layout { LAYOUT_TOP_DOWN, LAYOUT_LEFT_RIGHT }; +enum audio_out { + GB_1, + GB_2 +}; + static enum model model[2]; static uint32_t *frame_buf = NULL; static struct retro_log_callback logging; @@ -63,7 +68,8 @@ static retro_input_state_t input_state_cb; static unsigned emulated_devices = 1; static unsigned pre_init = 1; -static unsigned layout = 0; +static unsigned screen_layout = 0; +static unsigned audio_out = 0; signed short soundbuf[1024 * 2]; @@ -130,14 +136,16 @@ static void vblank1(GB_gameboy_t *gb) { vblank1_occurred = true; GB_update_keys_status(gb, 0); - audio_callback(gb); + if (audio_out == GB_1) + audio_callback(gb); } static void vblank2(GB_gameboy_t *gb) { vblank2_occurred = true; GB_update_keys_status(gb, 1); - //audio_callback(gb); + if (audio_out == GB_2) + audio_callback(gb); } static uint8_t byte_to_send1 = 0xFF, byte_to_send2 = 0xFF; @@ -175,7 +183,6 @@ static retro_environment_t environ_cb; static const struct retro_variable vars[] = { { "sameboy_link", "Link Cable (restart); disabled|enabled" }, - { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -184,6 +191,7 @@ static const struct retro_variable vars[] = { static const struct retro_variable vars_link[] = { { "sameboy_link", "Link Cable; disabled|enabled" }, { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_audio_output", "Audio output; GB #1|GB #2" }, { "sameboy_model_1", "Emulated Model for GB #1; Game Boy Color|Game Boy Advance|Game Boy" }, { "sameboy_model_2", "Emulated Model for GB #2; Game Boy Color|Game Boy Advance|Game Boy" }, { "sameboy_color_correction_mode_1", "Color Correction for GB #1; off|correct curves|emulate hardware|preserve brightness" }, @@ -441,9 +449,19 @@ static void check_variables(bool link) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "top-down") == 0) - layout = LAYOUT_TOP_DOWN; + screen_layout = LAYOUT_TOP_DOWN; else - layout = LAYOUT_LEFT_RIGHT; + screen_layout = LAYOUT_LEFT_RIGHT; + } + + var.key = "sameboy_audio_output"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "GB #1") == 0) + audio_out = GB_1; + else + audio_out = GB_2; } } From 5e04600ab15d7b759fca147983be21d0faf3579a Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 1 Feb 2018 10:08:00 -0500 Subject: [PATCH 0501/1216] add descriptors to P2 --- libretro/libretro.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 92c4cc7c..cfb87f61 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -539,6 +539,7 @@ void retro_set_environment(retro_environment_t cb) static const struct retro_controller_info ports[] = { { controllers, 1 }, + { controllers, 2 }, { NULL, 0 }, }; cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); From ef143261e560a7a9ef2de9ea15916266627b58e0 Mon Sep 17 00:00:00 2001 From: Brad Parker Date: Thu, 1 Feb 2018 13:26:58 -0500 Subject: [PATCH 0502/1216] implement left-right screen layout option, add ASAN to Makefile --- libretro/libretro.c | 60 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index cfb87f61..20bb3d5d 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -58,6 +58,7 @@ enum audio_out { static enum model model[2]; static uint32_t *frame_buf = NULL; +static uint32_t *frame_buf_copy = NULL; static struct retro_log_callback logging; static retro_log_printf_t log_cb; @@ -71,6 +72,8 @@ static unsigned pre_init = 1; static unsigned screen_layout = 0; static unsigned audio_out = 0; +static bool geometry_updated = false; + signed short soundbuf[1024 * 2]; char retro_system_directory[4096]; @@ -452,6 +455,8 @@ static void check_variables(bool link) screen_layout = LAYOUT_TOP_DOWN; else screen_layout = LAYOUT_LEFT_RIGHT; + + geometry_updated = true; } var.key = "sameboy_audio_output"; @@ -467,7 +472,6 @@ static void check_variables(bool link) void retro_init(void) { - frame_buf = (uint32_t*)malloc(2 * VIDEO_PIXELS * sizeof(uint32_t)); const char *dir = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) @@ -483,12 +487,20 @@ void retro_init(void) environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); check_variables(emulated_devices == 2 ? true : false); environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + + frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + + memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); } void retro_deinit(void) { free(frame_buf); + free(frame_buf_copy); frame_buf = NULL; + frame_buf_copy = NULL; } unsigned retro_api_version(void) @@ -516,12 +528,24 @@ void retro_get_system_info(struct retro_system_info *info) void retro_get_system_av_info(struct retro_system_av_info *info) { - struct retro_game_geometry geom = { VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices , VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT) }; + struct retro_game_geometry geom; struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; - + + if (screen_layout == LAYOUT_TOP_DOWN) { + geom.base_width = VIDEO_WIDTH; + geom.base_height = VIDEO_HEIGHT * emulated_devices; + geom.aspect_ratio = VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); + }else if (screen_layout == LAYOUT_LEFT_RIGHT) { + geom.base_width = VIDEO_WIDTH * emulated_devices; + geom.base_height = VIDEO_HEIGHT; + geom.aspect_ratio = (VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; + } + + geom.max_width = VIDEO_WIDTH * emulated_devices; + geom.max_height = VIDEO_HEIGHT * emulated_devices; + info->geometry = geom; info->timing = timing; - } void retro_set_environment(retro_environment_t cb) @@ -539,7 +563,7 @@ void retro_set_environment(retro_environment_t cb) static const struct retro_controller_info ports[] = { { controllers, 1 }, - { controllers, 2 }, + { controllers, 1 }, { NULL, 0 }, }; cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); @@ -579,6 +603,17 @@ void retro_reset(void) void retro_run(void) { bool updated = false; + + if (pre_init) + geometry_updated = false; + + if (geometry_updated) { + struct retro_system_av_info info; + retro_get_system_av_info(&info); + geometry_updated = false; + environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &info.geometry); + } + pre_init = 0; if (!frame_buf) @@ -603,7 +638,20 @@ void retro_run(void) else GB_run_frame(&gameboy[0]); - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); + if (screen_layout == LAYOUT_TOP_DOWN) { + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); + }else if (screen_layout == LAYOUT_LEFT_RIGHT) { + /* use slow memcpy method for now */ + for (int index = 0; index < emulated_devices; index++) { + for (int y = 0; y < VIDEO_HEIGHT; y++) { + for (int x = 0; x < VIDEO_WIDTH; x++) { + frame_buf_copy[VIDEO_WIDTH * emulated_devices * y + (x + VIDEO_WIDTH * index)] = frame_buf[VIDEO_WIDTH * (y + VIDEO_HEIGHT * index) + x]; + } + } + } + + video_cb(frame_buf_copy, VIDEO_WIDTH * emulated_devices, VIDEO_HEIGHT, VIDEO_WIDTH * emulated_devices * sizeof(uint32_t)); + } } bool retro_load_game(const struct retro_game_info *info) From 120edb6f8cc8d36f8f5391c75efd5ef6b418174b Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 2 Feb 2018 22:01:27 -0500 Subject: [PATCH 0503/1216] rebase from master --- libretro/libretro.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 20bb3d5d..1c8c35f7 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -43,9 +43,6 @@ enum model { MODEL_AUTO }; -static enum model model = MODEL_AUTO; -static enum model auto_model = MODEL_CGB; - enum screen_layout { LAYOUT_TOP_DOWN, LAYOUT_LEFT_RIGHT @@ -57,6 +54,8 @@ enum audio_out { }; static enum model model[2]; +static enum model auto_model = MODEL_CGB; + static uint32_t *frame_buf = NULL; static uint32_t *frame_buf_copy = NULL; static struct retro_log_callback logging; @@ -206,9 +205,14 @@ static const struct retro_variable vars_link[] = { static void init_for_current_model(void) { - enum model effective_model = model; - if (effective_model == MODEL_AUTO) { - effective_model = auto_model; + unsigned i = 0; + enum model effective_model[2]; + for (i=0; i < emulated_devices; i++) + { + effective_model[i] = model[i]; + if (effective_model[i] == MODEL_AUTO) { + effective_model[i] = auto_model; + } } for (i = 0; i < emulated_devices; i++) From 50fd7f1140876a105d560cb6c29914c26d2f20ac Mon Sep 17 00:00:00 2001 From: radius Date: Fri, 2 Feb 2018 22:07:30 -0500 Subject: [PATCH 0504/1216] use set geometry instead --- libretro/libretro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 1c8c35f7..d40d3c09 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -615,7 +615,7 @@ void retro_run(void) struct retro_system_av_info info; retro_get_system_av_info(&info); geometry_updated = false; - environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &info.geometry); + environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &info.geometry); } pre_init = 0; From a8741674ce32152454d7a57a2ec1ab94af2a8fcd Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 11:28:35 -0500 Subject: [PATCH 0505/1216] better savestate code --- libretro/libretro.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index d40d3c09..50c44061 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -735,7 +735,10 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - return 2 * GB_get_save_state_size(&gameboy[0]); + if (emulated_devices == 2) + return GB_get_save_state_size(&gameboy[0]) + GB_get_save_state_size(&gameboy[1]); + else + return GB_get_save_state_size(&gameboy[0]); } bool retro_serialize(void *data, size_t size) @@ -744,11 +747,14 @@ bool retro_serialize(void *data, size_t size) return false; void* save_data[2]; + size_t state_size[2]; + for (int i = 0; i < emulated_devices; i++) { - save_data[i] = (uint8_t*)malloc(size * sizeof(uint8_t) / emulated_devices); + state_size[i] = GB_get_save_state_size(&gameboy[i]); + save_data[i] = (uint8_t*)malloc(state_size[i]); GB_save_state_to_buffer(&gameboy[i], (uint8_t*) save_data[i]); - memcpy(data + (size * i / emulated_devices), save_data[i], size / emulated_devices); + memcpy(data + (state_size[i] * i), save_data[i], state_size[i]); free(save_data[i]); } @@ -761,13 +767,15 @@ bool retro_serialize(void *data, size_t size) bool retro_unserialize(const void *data, size_t size) { void* save_data[2]; + size_t state_size[2]; int ret; for (int i = 0; i < emulated_devices; i++) { - save_data[i] = (uint8_t*)malloc(size * sizeof(uint8_t)/ emulated_devices); - memcpy (save_data[i], data + (size * i / emulated_devices), size / emulated_devices); - ret = GB_load_state_from_buffer(&gameboy[i], save_data[i], size / emulated_devices); + state_size[i] = GB_get_save_state_size(&gameboy[i]); + save_data[i] = (uint8_t*)malloc(state_size[i]); + memcpy (save_data[i], data + (state_size[i] * i), state_size[i]); + ret = GB_load_state_from_buffer(&gameboy[i], save_data[i], state_size[i]); free(save_data[i]); if (ret != 0) From 7459b9610c523c455c017426df4c746b06997e5c Mon Sep 17 00:00:00 2001 From: Tatsuya79 Date: Sat, 3 Feb 2018 12:32:31 +0100 Subject: [PATCH 0506/1216] Reduce input lag by 1 frame --- libretro/libretro.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 50c44061..7469f5a0 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -137,7 +137,6 @@ static void audio_callback(void *gb) static void vblank1(GB_gameboy_t *gb) { vblank1_occurred = true; - GB_update_keys_status(gb, 0); if (audio_out == GB_1) audio_callback(gb); } @@ -145,7 +144,6 @@ static void vblank1(GB_gameboy_t *gb) static void vblank2(GB_gameboy_t *gb) { vblank2_occurred = true; - GB_update_keys_status(gb, 1); if (audio_out == GB_2) audio_callback(gb); } @@ -626,6 +624,10 @@ void retro_run(void) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(emulated_devices == 2 ? true : false); + GB_update_keys_status(&gameboy[0], 0); + if (emulated_devices == 2) + GB_update_keys_status(&gameboy[1], 1); + vblank1_occurred = vblank2_occurred = false; signed delta = 0; if (emulated_devices == 2) From b169d86bf8f1501f217db260cd79cd330a56e4e4 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 12:38:11 -0500 Subject: [PATCH 0507/1216] subsystem support --- libretro/libretro.c | 101 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 6 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 7469f5a0..1b64b4aa 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -33,7 +33,14 @@ static const char slash = '/'; #define VIDEO_HEIGHT 144 #define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT) -char battery_save_path[512]; +#define RETRO_MEMORY_GAMEBOY_1_RAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_GAMEBOY_1_RTC ((2 << 8) | RETRO_MEMORY_RTC) +#define RETRO_MEMORY_GAMEBOY_2_RAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_GAMEBOY_2_RTC ((3 << 8) | RETRO_MEMORY_RTC) + +#define RETRO_GAME_TYPE_GAMEBOY_LINK_2P 0x101 + +char battery_save_path[512]; char symbols_path[512]; enum model { @@ -100,7 +107,7 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { input_poll_cb(); - + GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); GB_set_key_state(gb, GB_KEY_UP,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); @@ -109,12 +116,12 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) GB_set_key_state(gb, GB_KEY_B,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); GB_set_key_state(gb, GB_KEY_START,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); - + if (gb->rumble_state) rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535); else rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0); - + } @@ -201,6 +208,26 @@ static const struct retro_variable vars_link[] = { { NULL } }; +static const struct retro_subsystem_memory_info gb1_memory[] = { + { "srm", RETRO_MEMORY_GAMEBOY_1_RAM }, + { "rtc", RETRO_MEMORY_GAMEBOY_1_RTC }, +}; + +static const struct retro_subsystem_memory_info gb2_memory[] = { + { "srm", RETRO_MEMORY_GAMEBOY_2_RAM }, + { "rtc", RETRO_MEMORY_GAMEBOY_2_RTC }, +}; + +static const struct retro_subsystem_rom_info gb_roms[] = { + { "GameBoy #1", "gb|gbc", true, false, true, gb1_memory, 1 }, + { "GameBoy #2", "gb|gbc", true, false, true, gb2_memory, 1 }, +}; + + static const struct retro_subsystem_info subsystems[] = { + { "2 Player Gameboy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P | 0x1000 }, + { NULL }, +}; + static void init_for_current_model(void) { unsigned i = 0; @@ -569,6 +596,7 @@ void retro_set_environment(retro_environment_t cb) { NULL, 0 }, }; cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); } void retro_set_audio_sample(retro_audio_sample_t cb) @@ -730,9 +758,70 @@ unsigned retro_get_region(void) return RETRO_REGION_NTSC; } -bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num) +bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num_info) { - return false; + struct retro_input_descriptor desc[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, + }; + + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); + + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) + { + log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); + return false; + } + + snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + init_for_current_model(); + for (int i = 0; i < emulated_devices; i++) + { + auto_model = (info[i].path[strlen(info[i].path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; + if (GB_load_rom(&gameboy[i], info[i].path)) + { + log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); + return false; + } + } + + + bool yes = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); + + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) + log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); + else + log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); + + static const struct retro_variable vars[] = { + { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, + { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, + { NULL } + }; + + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + check_variables(emulated_devices == 2 ? true : false); + return true; } size_t retro_serialize_size(void) From b74095a2cd2b0afbcc2ce212f531c15046f3687d Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 13:05:05 -0500 Subject: [PATCH 0508/1216] set emulated devices automatically --- libretro/libretro.c | 55 ++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 1b64b4aa..70a2d650 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -189,14 +189,12 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; static const struct retro_variable vars[] = { - { "sameboy_link", "Link Cable (restart); disabled|enabled" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, { NULL } }; static const struct retro_variable vars_link[] = { - { "sameboy_link", "Link Cable; disabled|enabled" }, { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; GB #1|GB #2" }, { "sameboy_model_1", "Emulated Model for GB #1; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -463,19 +461,6 @@ static void check_variables(bool link) } - var.key = "sameboy_link"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - int old_emulated_devices = emulated_devices; - if (strcmp(var.value, "enabled") == 0) - emulated_devices = 2; - else - emulated_devices = 1; - if (pre_init == 0 && emulated_devices != old_emulated_devices) - emulated_devices = old_emulated_devices; - } - var.key = "sameboy_link_layout"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -513,15 +498,6 @@ void retro_init(void) else snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); - check_variables(emulated_devices == 2 ? true : false); - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); - - frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - - memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); } void retro_deinit(void) @@ -690,6 +666,16 @@ void retro_run(void) bool retro_load_game(const struct retro_game_info *info) { + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + check_variables(emulated_devices == 2 ? true : false); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + + frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + + memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + struct retro_input_descriptor desc[] = { { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, @@ -760,6 +746,18 @@ unsigned retro_get_region(void) bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num_info) { + emulated_devices = 2; + + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + check_variables(emulated_devices == 2 ? true : false); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + + frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + + memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + struct retro_input_descriptor desc[] = { { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, @@ -809,15 +807,6 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - - static const struct retro_variable vars[] = { - { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_link", "Link Cable; disabled|enabled" }, - { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, - { NULL } - }; environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); check_variables(emulated_devices == 2 ? true : false); From a5b182f0fa583071ad45fcecc0db1d5ae560acb6 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 13:59:06 -0500 Subject: [PATCH 0509/1216] standarize core options --- libretro/libretro.c | 108 +++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 70a2d650..4497c2e5 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -40,7 +40,7 @@ static const char slash = '/'; #define RETRO_GAME_TYPE_GAMEBOY_LINK_2P 0x101 -char battery_save_path[512]; +char battery_save_path[512]; char symbols_path[512]; enum model { @@ -107,7 +107,7 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { input_poll_cb(); - + GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); GB_set_key_state(gb, GB_KEY_UP,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); @@ -222,10 +222,14 @@ static const struct retro_subsystem_rom_info gb_roms[] = { }; static const struct retro_subsystem_info subsystems[] = { - { "2 Player Gameboy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P | 0x1000 }, + { "2 Player Gameboy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P }, { NULL }, }; +static const struct retro_controller_description controllers[] = { + { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, +}; + static void init_for_current_model(void) { unsigned i = 0; @@ -253,7 +257,7 @@ static void init_for_current_model(void) const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model[i]]; const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model[i]]; unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model[i]]; - + char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); @@ -339,7 +343,7 @@ static void check_variables(bool link) else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); } - + var.key = "sameboy_high_pass_filter_mode"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -351,7 +355,7 @@ static void check_variables(bool link) else if (strcmp(var.value, "remove dc offset") == 0) GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); } - + var.key = "sameboy_model"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -363,12 +367,14 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; - else if (strcmp(var.value, "Auto") == 0) + else new_model = MODEL_AUTO; + if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { model[0] = new_model; init_for_current_model(); } + model[0] = new_model; } } else @@ -436,10 +442,13 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; + else + new_model = MODEL_AUTO; if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { model[0] = new_model; init_for_current_model(); } + model[0] = new_model; } var.key = "sameboy_model_2"; @@ -453,10 +462,13 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; + else + new_model = MODEL_AUTO; if (GB_is_inited(&gameboy[1]) && new_model != model[1]) { model[1] = new_model; init_for_current_model(); } + model[1] = new_model; } } @@ -487,12 +499,12 @@ static void check_variables(bool link) void retro_init(void) { const char *dir = NULL; - + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); else snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); - + if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); else @@ -556,22 +568,12 @@ void retro_get_system_av_info(struct retro_system_av_info *info) void retro_set_environment(retro_environment_t cb) { environ_cb = cb; - + if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) log_cb = logging.log; else log_cb = fallback_log; - - static const struct retro_controller_description controllers[] = { - { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, - }; - - static const struct retro_controller_info ports[] = { - { controllers, 1 }, - { controllers, 1 }, - { NULL, 0 }, - }; - cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); } @@ -621,7 +623,7 @@ void retro_run(void) } pre_init = 0; - + if (!frame_buf) return; @@ -666,9 +668,8 @@ void retro_run(void) bool retro_load_game(const struct retro_game_info *info) { - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); - check_variables(emulated_devices == 2 ? true : false); - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); + check_variables(false); frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); @@ -697,9 +698,9 @@ bool retro_load_game(const struct retro_game_info *info) return false; } + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; init_for_current_model(); for (int i = 0; i < emulated_devices; i++) @@ -711,25 +712,20 @@ bool retro_load_game(const struct retro_game_info *info) } } - bool yes = true; - environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); + bool achievements = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - static const struct retro_variable vars[] = { - { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated Model; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_link", "Link Cable; disabled|enabled" }, - { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, - { NULL } + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { NULL, 0 }, }; - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); - check_variables(emulated_devices == 2 ? true : false); + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); return true; } @@ -746,11 +742,14 @@ unsigned retro_get_region(void) bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num_info) { - emulated_devices = 2; - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); - check_variables(emulated_devices == 2 ? true : false); - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); + if (type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) + emulated_devices = 2; + else + return false; /* all other types are unhandled for now */ + + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_link); + check_variables(true); frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); @@ -777,39 +776,44 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 0 }, }; - + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); return false; } - + + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + init_for_current_model(); for (int i = 0; i < emulated_devices; i++) { - auto_model = (info[i].path[strlen(info[i].path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; if (GB_load_rom(&gameboy[i], info[i].path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); return false; } } - - bool yes = true; - environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); - + bool achievements = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, emulated_devices == 2 ? (void *)vars_link : (void *)vars); - check_variables(emulated_devices == 2 ? true : false); + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { NULL, 0 }, + }; + + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + return true; } @@ -926,7 +930,7 @@ size_t retro_get_memory_size(unsigned type) size = 0; break; } - + return size; } From f98b71a25750931846142cd142c41d363bdf3676 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 14:22:47 -0500 Subject: [PATCH 0510/1216] rename core options --- libretro/libretro.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 4497c2e5..24f70a16 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -189,20 +189,35 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; static const struct retro_variable vars[] = { + { "sameboy_link", "Link Cable; disabled|enabled" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, { NULL } }; -static const struct retro_variable vars_link[] = { - { "sameboy_link_layout", "Screen Layout; top-down|left-right" }, - { "sameboy_audio_output", "Audio output; GB #1|GB #2" }, - { "sameboy_model_1", "Emulated Model for GB #1; Game Boy Color|Game Boy Advance|Game Boy" }, - { "sameboy_model_2", "Emulated Model for GB #2; Game Boy Color|Game Boy Advance|Game Boy" }, - { "sameboy_color_correction_mode_1", "Color Correction for GB #1; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_color_correction_mode_2", "Color Correction for GB #2; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode_1", "High Pass Filter for GB #1; off|accurate|remove dc offset" }, - { "sameboy_high_pass_filter_mode_2", "High Pass Filter for GB #2; off|accurate|remove dc offset" }, + +static const struct retro_variable vars_link_single[] = { + { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, + { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_color_correction_mode_1", "Color Correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode_2", "Color Correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode_1", "High Pass Filter for Game Boy #1; off|accurate|remove dc offset" }, + { "sameboy_high_pass_filter_mode_2", "High Pass Filter for Game Boy #2; off|accurate|remove dc offset" }, + { NULL } +}; + +static const struct retro_variable vars_link_dual[] = { + { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, + { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_color_correction_mode_1", "Color Correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode_2", "Color Correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode_1", "High Pass Filter for Game Boy #1; off|accurate|remove dc offset" }, + { "sameboy_high_pass_filter_mode_2", "High Pass Filter for Game Boy #2; off|accurate|remove dc offset" }, { NULL } }; @@ -473,7 +488,7 @@ static void check_variables(bool link) } - var.key = "sameboy_link_layout"; + var.key = "sameboy_screen_layout"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { @@ -489,7 +504,7 @@ static void check_variables(bool link) var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "GB #1") == 0) + if (strcmp(var.value, "Game Boy #1") == 0) audio_out = GB_1; else audio_out = GB_2; @@ -748,7 +763,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, else return false; /* all other types are unhandled for now */ - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_link); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_link_dual); check_variables(true); frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); From 0b96df1428570eaef5c2e3240b97c8e34f918162 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 15:16:36 -0500 Subject: [PATCH 0511/1216] readd single game link cable --- libretro/libretro.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 24f70a16..8616407a 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -79,6 +79,7 @@ static unsigned screen_layout = 0; static unsigned audio_out = 0; static bool geometry_updated = false; +static bool link_single = false; signed short soundbuf[1024 * 2]; @@ -189,7 +190,7 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; static const struct retro_variable vars[] = { - { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link", "Single Game Link Cable (restart); disabled|enabled" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -197,7 +198,7 @@ static const struct retro_variable vars[] = { }; static const struct retro_variable vars_link_single[] = { - { "sameboy_link", "Link Cable; disabled|enabled" }, + { "sameboy_link", "Single Game Link Cable (restart); disabled|enabled" }, { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -500,6 +501,16 @@ static void check_variables(bool link) geometry_updated = true; } + var.key = "sameboy_link"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "enabled") == 0) + link_single = true; + else + link_single = false; + } + var.key = "sameboy_audio_output"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -685,6 +696,12 @@ bool retro_load_game(const struct retro_game_info *info) { environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); check_variables(false); + if (link_single) + { + emulated_devices = 2; + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_link_single); + check_variables(true); + } frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); From 567936a21d97ee49ac0ecca9214d6a0dbd24f356 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 15:21:26 -0500 Subject: [PATCH 0512/1216] cut code duplication a bit --- libretro/libretro.c | 79 +++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 8616407a..e486cb96 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -536,6 +536,36 @@ void retro_init(void) else snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { controllers, 1 }, + { NULL, 0 }, + }; + + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + + struct retro_input_descriptor desc[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, + }; + + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); + } void retro_deinit(void) @@ -709,20 +739,6 @@ bool retro_load_game(const struct retro_game_info *info) memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - struct retro_input_descriptor desc[] = { - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { @@ -752,12 +768,6 @@ bool retro_load_game(const struct retro_game_info *info) else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - static const struct retro_controller_info ports[] = { - { controllers, 1 }, - { NULL, 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); return true; } @@ -789,28 +799,6 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - struct retro_input_descriptor desc[] = { - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { @@ -839,13 +827,6 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); - static const struct retro_controller_info ports[] = { - { controllers, 1 }, - { NULL, 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); - return true; } From d3a67f9e190a0341f35bedfea5097409789332a0 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 15:59:31 -0500 Subject: [PATCH 0513/1216] make link cable a runtime option --- libretro/libretro.c | 70 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index e486cb96..b5bf5098 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -79,7 +79,10 @@ static unsigned screen_layout = 0; static unsigned audio_out = 0; static bool geometry_updated = false; -static bool link_single = false; +static bool sameboy_dual = false; + +static bool link_cable_emulation = false; +static bool infrared_emulation = false; signed short soundbuf[1024 * 2]; @@ -190,15 +193,17 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; static const struct retro_variable vars[] = { - { "sameboy_link", "Single Game Link Cable (restart); disabled|enabled" }, + { "sameboy_dual", "Dual Game Boy Mode (restart); disabled|enabled" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, { NULL } }; -static const struct retro_variable vars_link_single[] = { - { "sameboy_link", "Single Game Link Cable (restart); disabled|enabled" }, +static const struct retro_variable vars_sameboy_dual[] = { + { "sameboy_dual", "Dual Game Boy Mode (restart); disabled|enabled" }, + { "sameboy_link", "Link Cable Emulation; enabled|disabled" }, + { "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" }, { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -211,6 +216,8 @@ static const struct retro_variable vars_link_single[] = { }; static const struct retro_variable vars_link_dual[] = { + { "sameboy_link", "Link Cable Emulation; enbled|disabled" }, + { "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" }, { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -238,14 +245,32 @@ static const struct retro_subsystem_rom_info gb_roms[] = { }; static const struct retro_subsystem_info subsystems[] = { - { "2 Player Gameboy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P }, + { "2 Player Game Boy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P }, { NULL }, }; static const struct retro_controller_description controllers[] = { - { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, + { "Nintendo Game Boy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, }; +static void set_link_cable_state(bool state) +{ + if (state && emulated_devices == 2) + { + GB_set_serial_transfer_start_callback(&gameboy[0], serial_start1); + GB_set_serial_transfer_end_callback(&gameboy[0], serial_end1); + GB_set_serial_transfer_start_callback(&gameboy[1], serial_start2); + GB_set_serial_transfer_end_callback(&gameboy[1], serial_end2); + } + else if (!state) + { + GB_set_serial_transfer_start_callback(&gameboy[0], NULL); + GB_set_serial_transfer_end_callback(&gameboy[0], NULL); + GB_set_serial_transfer_start_callback(&gameboy[1], NULL); + GB_set_serial_transfer_end_callback(&gameboy[1], NULL); + } +} + static void init_for_current_model(void) { unsigned i = 0; @@ -290,10 +315,8 @@ static void init_for_current_model(void) if (emulated_devices == 2) { GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); - GB_set_serial_transfer_start_callback(&gameboy[0], serial_start1); - GB_set_serial_transfer_end_callback(&gameboy[0], serial_end1); - GB_set_serial_transfer_start_callback(&gameboy[1], serial_start2); - GB_set_serial_transfer_end_callback(&gameboy[1], serial_end2); + if (link_cable_emulation) + set_link_cable_state(true); } struct retro_memory_descriptor descs[7]; @@ -501,14 +524,29 @@ static void check_variables(bool link) geometry_updated = true; } - var.key = "sameboy_link"; + var.key = "sameboy_dual"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "enabled") == 0) - link_single = true; + sameboy_dual = true; else - link_single = false; + sameboy_dual = false; + } + + var.key = "sameboy_link"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + bool tmp = link_cable_emulation; + if (strcmp(var.value, "enabled") == 0) + link_cable_emulation = true; + else + link_cable_emulation = false; + if (link_cable_emulation && link_cable_emulation != tmp) + set_link_cable_state(true); + else if (!link_cable_emulation && link_cable_emulation != tmp) + set_link_cable_state(false); } var.key = "sameboy_audio_output"; @@ -726,10 +764,10 @@ bool retro_load_game(const struct retro_game_info *info) { environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); check_variables(false); - if (link_single) + if (sameboy_dual) { emulated_devices = 2; - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_link_single); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_sameboy_dual); check_variables(true); } @@ -768,6 +806,7 @@ bool retro_load_game(const struct retro_game_info *info) else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); + check_variables(emulated_devices == 2 ? true : false); return true; } @@ -827,6 +866,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, else log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); + check_variables(emulated_devices == 2 ? true : false); return true; } From ff8a20ef044bb304667c97fff47e7876961b2c1c Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 16:06:50 -0500 Subject: [PATCH 0514/1216] disable this for now --- libretro/libretro.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index b5bf5098..fa958bbf 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -82,7 +82,7 @@ static bool geometry_updated = false; static bool sameboy_dual = false; static bool link_cable_emulation = false; -static bool infrared_emulation = false; +/*static bool infrared_emulation = false;*/ signed short soundbuf[1024 * 2]; @@ -203,7 +203,7 @@ static const struct retro_variable vars[] = { static const struct retro_variable vars_sameboy_dual[] = { { "sameboy_dual", "Dual Game Boy Mode (restart); disabled|enabled" }, { "sameboy_link", "Link Cable Emulation; enabled|disabled" }, - { "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" }, + /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, @@ -216,8 +216,8 @@ static const struct retro_variable vars_sameboy_dual[] = { }; static const struct retro_variable vars_link_dual[] = { - { "sameboy_link", "Link Cable Emulation; enbled|disabled" }, - { "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" }, + { "sameboy_link", "Link Cable Emulation; enabled|disabled" }, + /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, From 792087276e8386d9ad72fab7547f93da35d48213 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 16:17:25 -0500 Subject: [PATCH 0515/1216] cleanup logs --- libretro/libretro.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index fa958bbf..3a9bb019 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -621,7 +621,7 @@ unsigned retro_api_version(void) void retro_set_controller_port_device(unsigned port, unsigned device) { - log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port); + log_cb(RETRO_LOG_INFO, "Connecting device %u into port %u\n", device, port); } void retro_get_system_info(struct retro_system_info *info) @@ -780,7 +780,7 @@ bool retro_load_game(const struct retro_game_info *info) enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { - log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); + log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); return false; } @@ -793,7 +793,7 @@ bool retro_load_game(const struct retro_game_info *info) { if (GB_load_rom(&gameboy[i],info->path)) { - log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); + log_cb(RETRO_LOG_INFO, "Failed to load ROM at %s\n", info->path); return false; } } @@ -802,9 +802,9 @@ bool retro_load_game(const struct retro_game_info *info) environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) - log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); + log_cb(RETRO_LOG_INFO, "Rumble environment supported\n"); else - log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); + log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); check_variables(emulated_devices == 2 ? true : false); return true; @@ -841,7 +841,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { - log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n"); + log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); return false; } @@ -862,9 +862,9 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) - log_cb(RETRO_LOG_INFO, "Rumble environment supported.\n"); + log_cb(RETRO_LOG_INFO, "Rumble environment supported\n"); else - log_cb(RETRO_LOG_INFO, "Rumble environment not supported.\n"); + log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); check_variables(emulated_devices == 2 ? true : false); return true; From 8dd94505a87e1b2ba0c768f20254fec2225e73be Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 16:48:40 -0500 Subject: [PATCH 0516/1216] save ram handling for dual mode --- libretro/libretro.c | 159 ++++++++++++++++++++++++++++++++------------ 1 file changed, 115 insertions(+), 44 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 3a9bb019..6f05cf8d 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -33,9 +33,9 @@ static const char slash = '/'; #define VIDEO_HEIGHT 144 #define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT) -#define RETRO_MEMORY_GAMEBOY_1_RAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_GAMEBOY_1_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) #define RETRO_MEMORY_GAMEBOY_1_RTC ((2 << 8) | RETRO_MEMORY_RTC) -#define RETRO_MEMORY_GAMEBOY_2_RAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_GAMEBOY_2_SRAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) #define RETRO_MEMORY_GAMEBOY_2_RTC ((3 << 8) | RETRO_MEMORY_RTC) #define RETRO_GAME_TYPE_GAMEBOY_LINK_2P 0x101 @@ -60,6 +60,12 @@ enum audio_out { GB_2 }; +enum mode{ + MODE_SINGLE_GAME, + MODE_SINGLE_GAME_DUAL, + MODE_DUAL_GAME +}; + static enum model model[2]; static enum model auto_model = MODEL_CGB; @@ -78,6 +84,8 @@ static unsigned pre_init = 1; static unsigned screen_layout = 0; static unsigned audio_out = 0; +static enum mode mode = MODE_SINGLE_GAME; + static bool geometry_updated = false; static bool sameboy_dual = false; @@ -230,12 +238,12 @@ static const struct retro_variable vars_link_dual[] = { }; static const struct retro_subsystem_memory_info gb1_memory[] = { - { "srm", RETRO_MEMORY_GAMEBOY_1_RAM }, + { "srm.slot1", RETRO_MEMORY_GAMEBOY_1_SRAM }, { "rtc", RETRO_MEMORY_GAMEBOY_1_RTC }, }; static const struct retro_subsystem_memory_info gb2_memory[] = { - { "srm", RETRO_MEMORY_GAMEBOY_2_RAM }, + { "srm.slot2", RETRO_MEMORY_GAMEBOY_2_SRAM }, { "rtc", RETRO_MEMORY_GAMEBOY_2_RTC }, }; @@ -767,9 +775,12 @@ bool retro_load_game(const struct retro_game_info *info) if (sameboy_dual) { emulated_devices = 2; + mode = MODE_SINGLE_GAME_DUAL; environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_sameboy_dual); check_variables(true); } + else + mode = MODE_SINGLE_GAME; frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); @@ -825,7 +836,10 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, { if (type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) + { emulated_devices = 2; + mode = MODE_DUAL_GAME; + } else return false; /* all other types are unhandled for now */ @@ -925,62 +939,119 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { - void* data; - switch(type) + void* data = NULL; + switch(mode) { - case RETRO_MEMORY_SYSTEM_RAM: - data = gameboy[0].ram; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + case MODE_SINGLE_GAME: + case MODE_SINGLE_GAME_DUAL: /* todo: hook this properly */ { - data = gameboy[0].mbc_ram; - /* let's copy the save to gameboy[1] so it can save independently */ - //memcpy(gameboy[1].mbc_ram, gameboy[0].mbc_ram, gameboy[0].mbc_ram_size); + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + data = gameboy[0].ram; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + data = gameboy[0].mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_VIDEO_RAM: + data = gameboy[0].vram; + break; + case RETRO_MEMORY_RTC: + if(gameboy[0].cartridge_type->has_battery) + data = &gameboy[0].rtc_real; + else + data = NULL; + break; + default: + break; + } } - else - data = NULL; break; - case RETRO_MEMORY_VIDEO_RAM: - data = gameboy[0].vram; - break; - case RETRO_MEMORY_RTC: - if(gameboy[0].cartridge_type->has_battery) - data = &gameboy[0].rtc_real; - else - data = NULL; + case MODE_DUAL_GAME: /* todo: hook up other memory types */ + { + switch (type) + { + case RETRO_MEMORY_GAMEBOY_1_SRAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + data = gameboy[0].mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_GAMEBOY_2_SRAM: + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) + data = gameboy[1].mbc_ram; + else + data = NULL; + break; + default: + break; + } + } break; default: - data = NULL; + break; } return data; } size_t retro_get_memory_size(unsigned type) { - size_t size; - switch(type) + size_t size = 0; + switch(mode) { - case RETRO_MEMORY_SYSTEM_RAM: - size = gameboy[0].ram_size; + case MODE_SINGLE_GAME: + case MODE_SINGLE_GAME_DUAL: /* todo: hook this properly */ + { + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + size = gameboy[0].ram_size; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + size = gameboy[0].mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_VIDEO_RAM: + size = gameboy[0].vram_size; + break; + case RETRO_MEMORY_RTC: + if(gameboy[0].cartridge_type->has_battery) + size = sizeof (gameboy[0].rtc_real); + else + size = 0; + break; + default: + break; + } + } break; - case RETRO_MEMORY_SAVE_RAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) - size = gameboy[0].mbc_ram_size; - else - size = 0; - break; - case RETRO_MEMORY_VIDEO_RAM: - size = gameboy[0].vram_size; - break; - case RETRO_MEMORY_RTC: - if(gameboy[0].cartridge_type->has_battery) - size = sizeof (gameboy[0].rtc_real); - else - size = 0; + case MODE_DUAL_GAME: /* todo: hook up other memory types */ + { + switch (type) + { + case RETRO_MEMORY_GAMEBOY_1_SRAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + size = gameboy[0].mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_GAMEBOY_2_SRAM: + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) + size = gameboy[1].mbc_ram_size; + else + size = 0; + break; + default: + break;; + } + } break; default: - size = 0; break; } From cc296a31144688d684cee00a8a0634137089c827 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 3 Feb 2018 20:23:08 -0500 Subject: [PATCH 0517/1216] fix savefile names --- libretro/jni/Android.mk | 7 ------- libretro/libretro.c | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 7deea1c9..3401c104 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -27,13 +27,6 @@ CORE_DIR := ../.. include ../Makefile.common -$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/BootROMs/prebuilt/%_boot.bin - echo "/* AUTO-GENERATED */" > $@ - echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ - hexdump -v -e '/1 "0x%02x, "' $< >> $@ - echo "};" >> $@ - echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ - LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" diff --git a/libretro/libretro.c b/libretro/libretro.c index 6f05cf8d..dfddc6af 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -238,12 +238,12 @@ static const struct retro_variable vars_link_dual[] = { }; static const struct retro_subsystem_memory_info gb1_memory[] = { - { "srm.slot1", RETRO_MEMORY_GAMEBOY_1_SRAM }, + { "srm", RETRO_MEMORY_GAMEBOY_1_SRAM }, { "rtc", RETRO_MEMORY_GAMEBOY_1_RTC }, }; static const struct retro_subsystem_memory_info gb2_memory[] = { - { "srm.slot2", RETRO_MEMORY_GAMEBOY_2_SRAM }, + { "srm", RETRO_MEMORY_GAMEBOY_2_SRAM }, { "rtc", RETRO_MEMORY_GAMEBOY_2_RTC }, }; From 217e9787bd640cbd1b8250e31c0064331ddc302f Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 7 Feb 2018 15:27:28 -0500 Subject: [PATCH 0518/1216] change MAX_CH_AMP on WiiU --- Core/apu.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/apu.h b/Core/apu.h index 44b3d45c..c50d5a8f 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -7,7 +7,11 @@ #ifdef GB_INTERNAL /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ +#ifdef WIIU +#define MAX_CH_AMP 0x1FE0 / 4 +#else #define MAX_CH_AMP 0x1FE0 +#endif #define CH_STEP (MAX_CH_AMP/0xF/8) #endif From 1c61b006bac7767bb0082feeeeedd1912fd55860 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Feb 2018 14:42:14 +0200 Subject: [PATCH 0519/1216] Added rewinding support to the core and the Cocoa frontend --- Cocoa/AppDelegate.m | 4 +- Cocoa/Document.m | 29 ++++- Cocoa/GBButtons.h | 1 + Cocoa/GBButtons.m | 2 +- Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 20 ++++ Cocoa/GBView.h | 1 + Cocoa/GBView.m | 20 +++- Cocoa/Preferences.xib | 110 ++++++++++++------- Core/display.c | 4 +- Core/gb.c | 7 ++ Core/gb.h | 13 ++- Core/rewind.c | 205 ++++++++++++++++++++++++++++++++++++ Core/rewind.h | 13 +++ 14 files changed, 384 insertions(+), 46 deletions(-) create mode 100644 Core/rewind.c create mode 100644 Core/rewind.h diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index dce9a700..d9cfdef0 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -28,10 +28,12 @@ @"GBStart": @(kVK_Return), @"GBTurbo": @(kVK_Space), + @"GBRewind": @(kVK_Tab), @"GBFilter": @"NearestNeighbor", @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), - @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET) + @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), + @"GBRewindLength": @(10) }]; } diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 6ec45c89..d4a4288c 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -51,6 +51,8 @@ enum model { bool logToSideView; bool shouldClearSideView; enum model current_model; + + bool rewind; } @property GBAudioClient *audioClient; @@ -166,6 +168,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); + GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); [self loadROM]; } @@ -179,6 +182,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [self reloadVRAMData: nil]; }); } + if (self.view.isRewinding) { + rewind = true; + } } - (void) run @@ -197,7 +203,16 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; while (running) { - GB_run(&gb); + if (rewind) { + rewind = false; + GB_rewind_pop(&gb); + if (!GB_rewind_pop(&gb)) { + rewind = self.view.isRewinding; + } + } + else { + GB_run(&gb); + } } [hex_timer invalidate]; [self.audioClient stop]; @@ -327,6 +342,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, name:@"GBColorCorrectionChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRewindLength) + name:@"GBRewindLengthChanged" + object:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { [self initDMG]; } @@ -1324,4 +1344,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } } +- (void) updateRewindLength +{ + if (GB_is_inited(&gb)) { + GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); + } +} + @end diff --git a/Cocoa/GBButtons.h b/Cocoa/GBButtons.h index e02440e8..0bd9fdcf 100644 --- a/Cocoa/GBButtons.h +++ b/Cocoa/GBButtons.h @@ -11,6 +11,7 @@ typedef enum : NSUInteger { GBSelect, GBStart, GBTurbo, + GBRewind, GBButtonCount } GBButton; diff --git a/Cocoa/GBButtons.m b/Cocoa/GBButtons.m index 9784eeff..db0f3644 100644 --- a/Cocoa/GBButtons.m +++ b/Cocoa/GBButtons.m @@ -1,4 +1,4 @@ #import #import "GBButtons.h" -NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo"}; \ No newline at end of file +NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind"}; diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 88cd9265..1334d489 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -7,6 +7,7 @@ @property (strong) IBOutlet NSButton *aspectRatioCheckbox; @property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; @property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property (strong) IBOutlet NSPopUpButton *rewindPopupButton; @property (strong) IBOutlet NSButton *configureJoypadButton; @property (strong) IBOutlet NSButton *skipButton; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index e85d2e1b..c2dfe2dd 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -14,6 +14,7 @@ NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton; NSPopUpButton *_colorCorrectionPopupButton; + NSPopUpButton *_rewindPopupButton; NSButton *_aspectRatioCheckbox; } @@ -77,6 +78,18 @@ return _colorCorrectionPopupButton; } +- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton +{ + _rewindPopupButton = rewindPopupButton; + NSInteger length = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]; + [_rewindPopupButton selectItemWithTag:length]; +} + +- (NSPopUpButton *)rewindPopupButton +{ + return _rewindPopupButton; +} + - (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton { _highpassFilterPopupButton = highpassFilterPopupButton; @@ -161,6 +174,13 @@ } +- (IBAction)rewindLengthChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBRewindLength"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil]; +} + - (IBAction) configureJoypad:(id)sender { [self.configureJoypadButton setEnabled:NO]; diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index 813f8c03..ffd6dc03 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -10,4 +10,5 @@ @property (nonatomic) BOOL shouldBlendFrameWithPrevious; @property GBShader *shader; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; +@property bool isRewinding; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 3afc2477..71aee65a 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -173,7 +173,12 @@ handled = true; switch (i) { case GBTurbo: - GB_set_turbo_mode(_gb, true, false); + GB_set_turbo_mode(_gb, true, self.isRewinding); + break; + + case GBRewind: + self.isRewinding = true; + GB_set_turbo_mode(_gb, false, false); break; default: @@ -201,6 +206,10 @@ case GBTurbo: GB_set_turbo_mode(_gb, false, false); break; + + case GBRewind: + self.isRewinding = false; + break; default: GB_set_key_state(_gb, (GB_key_t)i, false); @@ -224,7 +233,14 @@ if (mapped_button && [mapped_button integerValue] == button) { switch (i) { case GBTurbo: - GB_set_turbo_mode(_gb, state, false); + GB_set_turbo_mode(_gb, state, state && self.isRewinding); + break; + + case GBRewind: + self.isRewinding = state; + if (state) { + GB_set_turbo_mode(_gb, false, false); + } break; default: diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 57c42dbd..c599225f 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,8 +1,8 @@ - + - + @@ -17,14 +17,14 @@ - + - + - + @@ -33,7 +33,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -76,7 +76,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -116,8 +116,32 @@ + + + + + + +

    + + + + + + + + + + + + + + + + + - + @@ -137,7 +161,7 @@ - + @@ -145,15 +169,46 @@ + + + + + + + + + + + - + - + - + @@ -203,28 +258,6 @@ - - @@ -235,9 +268,10 @@ + - + diff --git a/Core/display.c b/Core/display.c index f9533bab..41f85bf1 100755 --- a/Core/display.c +++ b/Core/display.c @@ -198,6 +198,8 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) static void display_vblank(GB_gameboy_t *gb) { + gb->vblank_just_occured = true; + if (gb->turbo) { if (GB_timing_sync_turbo(gb)) { return; @@ -216,8 +218,6 @@ static void display_vblank(GB_gameboy_t *gb) gb->vblank_callback(gb); GB_timing_sync(gb); - - gb->vblank_just_occured = true; } static inline uint8_t scale_channel(uint8_t x) diff --git a/Core/gb.c b/Core/gb.c index cc86fef8..c2b63907 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -11,6 +11,10 @@ #endif #include "gb.h" +/* The libretro frontend does not link against rewind.c, so we provide empty weak alternatives to its functions */ +void __attribute__((weak)) GB_rewind_free(GB_gameboy_t *gb) { } +void __attribute__((weak)) GB_rewind_push(GB_gameboy_t *gb) { } + void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { char *string = NULL; @@ -149,6 +153,7 @@ void GB_free(GB_gameboy_t *gb) gb->reversed_symbol_map.buckets[i] = next; } } + GB_rewind_free(gb); memset(gb, 0, sizeof(*gb)); } @@ -280,6 +285,7 @@ uint8_t GB_run(GB_gameboy_t *gb) GB_update_joyp(gb); GB_rtc_run(gb); GB_debugger_handle_async_commands(gb); + GB_rewind_push(gb); } return gb->cycles_since_run; } @@ -505,6 +511,7 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb) gb->vram = realloc(gb->vram, gb->vram_size = 0x2000); } gb->is_cgb = is_cgb; + GB_rewind_free(gb); GB_reset(gb); } diff --git a/Core/gb.h b/Core/gb.h index ca373f5e..8f41e786 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -17,6 +17,7 @@ #include "memory.h" #include "printer.h" #include "timing.h" +#include "rewind.h" #include "z80_cpu.h" #include "symbol_hash.h" @@ -161,7 +162,7 @@ typedef enum { #define CPU_FREQUENCY 0x400000 #define DIV_CYCLES (0x100) #define INTERNAL_DIV_CYCLES (0x40000) -#define FRAME_LENGTH 16742706 // in nanoseconds +#define FRAME_LENGTH (1000000000LL * LCDC_PERIOD / CPU_FREQUENCY) // in nanoseconds #if !defined(MIN) #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) @@ -475,6 +476,16 @@ struct GB_gameboy_internal_s { /* Ticks command */ unsigned long debugger_ticks; + + /* Rewind */ +#define GB_REWIND_FRAMES_PER_KEY 255 + size_t rewind_buffer_length; + struct { + uint8_t *key_state; + uint8_t *compressed_states[GB_REWIND_FRAMES_PER_KEY]; + unsigned pos; + } *rewind_sequences; // lasts about 4 seconds + size_t rewind_pos; /* Misc */ bool turbo; diff --git a/Core/rewind.c b/Core/rewind.c new file mode 100644 index 00000000..ae711a2b --- /dev/null +++ b/Core/rewind.c @@ -0,0 +1,205 @@ +#include "rewind.h" +#include + +static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size) +{ + size_t malloc_size = 0x1000; + uint8_t *compressed = malloc(malloc_size); + size_t counter_pos = 0; + size_t data_pos = sizeof(uint16_t); + bool prev_mode = true; + *(uint16_t *)compressed = 0; +#define COUNTER (*(uint16_t *)&compressed[counter_pos]) +#define DATA (compressed[data_pos]) + + while (uncompressed_size) { + if (prev_mode) { + if (*data == *prev && COUNTER != 0xffff) { + COUNTER++; + data++; + prev++; + uncompressed_size--; + } + else { + prev_mode = false; + counter_pos += sizeof(uint16_t); + data_pos = counter_pos + sizeof(uint16_t); + if (data_pos >= malloc_size) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + COUNTER = 0; + } + } + else { + if (*data != *prev && COUNTER != 0xffff) { + COUNTER++; + DATA = *data; + data_pos++; + data++; + prev++; + uncompressed_size--; + if (data_pos >= malloc_size) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + } + else { + prev_mode = true; + counter_pos = data_pos; + data_pos = counter_pos + sizeof(uint16_t); + if (counter_pos >= malloc_size - 1) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + COUNTER = 0; + } + } + } + + return realloc(compressed, data_pos); +#undef DATA +#undef COUNTER +} + + +static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size) +{ + size_t counter_pos = 0; + size_t data_pos = sizeof(uint16_t); + bool prev_mode = true; +#define COUNTER (*(uint16_t *)&data[counter_pos]) +#define DATA (data[data_pos]) + + while (uncompressed_size) { + if (prev_mode) { + if (COUNTER) { + COUNTER--; + *(dest++) = *(prev++); + uncompressed_size--; + } + else { + prev_mode = false; + counter_pos += sizeof(uint16_t); + data_pos = counter_pos + sizeof(uint16_t); + } + } + else { + if (COUNTER) { + COUNTER--; + *(dest++) = DATA; + data_pos++; + prev++; + uncompressed_size--; + } + else { + prev_mode = true; + counter_pos = data_pos; + data_pos += sizeof(uint16_t); + } + } + } +#undef DATA +#undef COUNTER +} + +void GB_rewind_push(GB_gameboy_t *gb) +{ + const size_t save_size = GB_get_save_state_size(gb); + if (!gb->rewind_sequences) { + if (gb->rewind_buffer_length) { + gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); + memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); + gb->rewind_pos = 0; + } + else { + return; + } + } + + if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) { + gb->rewind_pos++; + if (gb->rewind_pos == gb->rewind_buffer_length) { + gb->rewind_pos = 0; + } + if (gb->rewind_sequences[gb->rewind_pos].key_state) { + free(gb->rewind_sequences[gb->rewind_pos].key_state); + gb->rewind_sequences[gb->rewind_pos].key_state = NULL; + } + for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) { + if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) { + free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]); + gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = 0; + } + } + gb->rewind_sequences[gb->rewind_pos].pos = 0; + } + + if (!gb->rewind_sequences[gb->rewind_pos].key_state) { + gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size); + GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state); + } + else { + uint8_t *save_state = malloc(save_size); + GB_save_state_to_buffer(gb, save_state); + gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] = + state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size); + free(save_state); + } + +} + +bool GB_rewind_pop(GB_gameboy_t *gb) +{ + if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) { + return false; + } + + const size_t save_size = GB_get_save_state_size(gb); + if (gb->rewind_sequences[gb->rewind_pos].pos == 0) { + GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size); + free(gb->rewind_sequences[gb->rewind_pos].key_state); + gb->rewind_sequences[gb->rewind_pos].key_state = NULL; + gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1; + return true; + } + + uint8_t *save_state = malloc(save_size); + state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state, + gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos], + save_state, + save_size); + free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]); + gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL; + GB_load_state_from_buffer(gb, save_state, save_size); + free(save_state); + return true; +} + +void GB_rewind_free(GB_gameboy_t *gb) +{ + if (!gb->rewind_sequences) return; + for (unsigned i = 0; i < gb->rewind_buffer_length; i++) { + if (gb->rewind_sequences[i].key_state) { + free(gb->rewind_sequences[i].key_state); + } + for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) { + if (gb->rewind_sequences[i].compressed_states[j]) { + free(gb->rewind_sequences[i].compressed_states[j]); + } + } + } + free(gb->rewind_sequences); + gb->rewind_sequences = NULL; +} + +void GB_set_rewind_length(GB_gameboy_t *gb, double seconds) +{ + GB_rewind_free(gb); + if (seconds == 0) { + gb->rewind_buffer_length = 0; + } + else { + gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY); + } +} diff --git a/Core/rewind.h b/Core/rewind.h new file mode 100644 index 00000000..30d795cc --- /dev/null +++ b/Core/rewind.h @@ -0,0 +1,13 @@ +#ifndef rewind_h +#define rewind_h + +#include "gb.h" + +#ifdef GB_INTERNAL +void GB_rewind_push(GB_gameboy_t *gb); +void GB_rewind_free(GB_gameboy_t *gb); +#endif +bool GB_rewind_pop(GB_gameboy_t *gb); +void GB_set_rewind_length(GB_gameboy_t *gb, double seconds); + +#endif From 81f808e18438f3a52e51a0cc42d1ad70ba1abee3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Feb 2018 15:02:22 +0200 Subject: [PATCH 0520/1216] Refinements for the Wii U port --- Core/apu.h | 3 ++- libretro/libretro.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/apu.h b/Core/apu.h index c50d5a8f..c4d4fc70 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -8,7 +8,8 @@ #ifdef GB_INTERNAL /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ #ifdef WIIU -#define MAX_CH_AMP 0x1FE0 / 4 +/* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/ +#define MAX_CH_AMP (0x1FE0 / 4) #else #define MAX_CH_AMP 0x1FE0 #endif diff --git a/libretro/libretro.c b/libretro/libretro.c index dfddc6af..98b88c2f 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -9,7 +9,8 @@ #ifndef WIIU #define AUDIO_FREQUENCY 384000 #else -#define AUDIO_FREQUENCY 44100 +/* Use the internal sample rate for the Wii U */ +#define AUDIO_FREQUENCY 48000 #endif #define FRAME_RATE (0x400000 / 70224.0) From 220ba9ff38c90ab422cfff85c684d2c37e7369f8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Feb 2018 15:41:00 +0200 Subject: [PATCH 0521/1216] Restored auto model selection in libretro. Fixed incorrect aspect ratio in libretro (Closes #30). --- libretro/libretro.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 98b88c2f..cbeece9d 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -205,7 +205,7 @@ static const struct retro_variable vars[] = { { "sameboy_dual", "Dual Game Boy Mode (restart); disabled|enabled" }, { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { NULL } }; @@ -215,8 +215,8 @@ static const struct retro_variable vars_sameboy_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, - { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color Correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color Correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High Pass Filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -229,8 +229,8 @@ static const struct retro_variable vars_link_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Game Boy" }, - { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Game Boy" }, + { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color Correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color Correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High Pass Filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -304,9 +304,9 @@ static void init_for_current_model(void) else GB_init_cgb(&gameboy[i]); } - const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[model[i]]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[model[i]]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[model[i]]; + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model[i]]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model[i]]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model[i]]; char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); @@ -492,6 +492,7 @@ static void check_variables(bool link) new_model = MODEL_AGB; else new_model = MODEL_AUTO; + if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { model[0] = new_model; init_for_current_model(); @@ -512,6 +513,7 @@ static void check_variables(bool link) new_model = MODEL_AGB; else new_model = MODEL_AUTO; + if (GB_is_inited(&gameboy[1]) && new_model != model[1]) { model[1] = new_model; init_for_current_model(); @@ -654,11 +656,11 @@ void retro_get_system_av_info(struct retro_system_av_info *info) if (screen_layout == LAYOUT_TOP_DOWN) { geom.base_width = VIDEO_WIDTH; geom.base_height = VIDEO_HEIGHT * emulated_devices; - geom.aspect_ratio = VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); + geom.aspect_ratio = (double)VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); }else if (screen_layout == LAYOUT_LEFT_RIGHT) { geom.base_width = VIDEO_WIDTH * emulated_devices; geom.base_height = VIDEO_HEIGHT; - geom.aspect_ratio = (VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; + geom.aspect_ratio = ((double)VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; } geom.max_width = VIDEO_WIDTH * emulated_devices; From 0cbbaac490272447d6bc32a8d1c2eadd173ac83e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Feb 2018 19:50:42 +0200 Subject: [PATCH 0522/1216] Updated incorrect comment after verification --- Core/joypad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/joypad.c b/Core/joypad.c index c5c4f089..c780b2fd 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -50,7 +50,7 @@ void GB_update_joyp(GB_gameboy_t *gb) break; } if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { - /* Todo: disable when emulating CGB */ + /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ gb->io_registers[GB_IO_IF] |= 0x10; } gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support From afcc66fb3cb9b68f9aff436f394dc0297011d71d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Feb 2018 23:30:30 +0200 Subject: [PATCH 0523/1216] Added CPU under/over-clocking support in Core, add under-clocking hotkey in the Cocoa port, allow modifier keys to be configured as input keys in Cocoa. --- Cocoa/AppDelegate.m | 1 + Cocoa/GBButtons.h | 1 + Cocoa/GBButtons.m | 2 +- Cocoa/GBPreferencesWindow.m | 13 +++++++++++++ Cocoa/GBView.m | 36 +++++++++++++++++++++++++++++++++++ Cocoa/NSString+StringForKey.m | 31 ++++++++++++++++++++---------- Cocoa/Preferences.xib | 30 ++++++++++++++--------------- Core/apu.c | 4 ++-- Core/gb.c | 16 ++++++++++++++-- Core/gb.h | 7 ++++++- Core/timing.c | 4 ++-- 11 files changed, 112 insertions(+), 33 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index d9cfdef0..44f56835 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -29,6 +29,7 @@ @"GBTurbo": @(kVK_Space), @"GBRewind": @(kVK_Tab), + @"GBSlow-Motion": @(kVK_Shift), @"GBFilter": @"NearestNeighbor", @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), diff --git a/Cocoa/GBButtons.h b/Cocoa/GBButtons.h index 0bd9fdcf..314a930a 100644 --- a/Cocoa/GBButtons.h +++ b/Cocoa/GBButtons.h @@ -12,6 +12,7 @@ typedef enum : NSUInteger { GBStart, GBTurbo, GBRewind, + GBUnderclock, GBButtonCount } GBButton; diff --git a/Cocoa/GBButtons.m b/Cocoa/GBButtons.m index db0f3644..044e9332 100644 --- a/Cocoa/GBButtons.m +++ b/Cocoa/GBButtons.m @@ -1,4 +1,4 @@ #import #import "GBButtons.h" -NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind"}; +NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"}; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index c2dfe2dd..655ab0a6 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -16,6 +16,7 @@ NSPopUpButton *_colorCorrectionPopupButton; NSPopUpButton *_rewindPopupButton; NSButton *_aspectRatioCheckbox; + NSEventModifierFlags previousModifiers; } + (NSArray *)filterList @@ -145,6 +146,18 @@ [self makeFirstResponder:self.controlsTableView]; } +- (void) flagsChanged:(NSEvent *)event +{ + if (event.modifierFlags > previousModifiers) { + [self keyDown:event]; + } + else { + [self keyUp:event]; + } + + previousModifiers = event.modifierFlags; +} + - (IBAction)graphicFilterChanged:(NSPopUpButton *)sender { [[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]] diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 71aee65a..5e1e31a5 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -12,6 +12,9 @@ NSTrackingArea *tracking_area; BOOL _mouseHidingEnabled; bool enableAnalog; + bool underclockKeyDown; + double clockMultiplier; + NSEventModifierFlags previousModifiers; } - (void) awakeFromNib @@ -51,6 +54,7 @@ owner:self userInfo:nil]; [self addTrackingArea:tracking_area]; + clockMultiplier = 1.0; } - (void) filterChanged @@ -153,6 +157,14 @@ - (void) flip { + if (underclockKeyDown && clockMultiplier > 0.5) { + clockMultiplier -= 0.1; + GB_set_clock_multiplier(_gb, clockMultiplier); + } + if (!underclockKeyDown && clockMultiplier < 1.0) { + clockMultiplier += 0.1; + GB_set_clock_multiplier(_gb, clockMultiplier); + } current_buffer = (current_buffer + 1) % self.numberOfBuffers; [self setNeedsDisplay:YES]; } @@ -180,6 +192,10 @@ self.isRewinding = true; GB_set_turbo_mode(_gb, false, false); break; + + case GBUnderclock: + underclockKeyDown = true; + break; default: GB_set_key_state(_gb, (GB_key_t)i, true); @@ -210,6 +226,10 @@ case GBRewind: self.isRewinding = false; break; + + case GBUnderclock: + underclockKeyDown = false; + break; default: GB_set_key_state(_gb, (GB_key_t)i, false); @@ -242,6 +262,10 @@ GB_set_turbo_mode(_gb, false, false); } break; + + case GBUnderclock: + underclockKeyDown = state; + break; default: if (i < GB_KEY_A) { @@ -324,4 +348,16 @@ return _mouseHidingEnabled; } +- (void) flagsChanged:(NSEvent *)event +{ + if (event.modifierFlags > previousModifiers) { + [self keyDown:event]; + } + else { + [self keyUp:event]; + } + + previousModifiers = event.modifierFlags; +} + @end diff --git a/Cocoa/NSString+StringForKey.m b/Cocoa/NSString+StringForKey.m index 6126c584..f5a9aa32 100644 --- a/Cocoa/NSString+StringForKey.m +++ b/Cocoa/NSString+StringForKey.m @@ -13,21 +13,32 @@ { /* These cases are not handled by stringForVirtualKey */ switch (keyCode) { - case 115: return @"↖"; - case 119: return @"↘"; - case 116: return @"⇞"; - case 121: return @"⇟"; - case 51: return @"⌫"; - case 117: return @"⌦"; - case 76: return @"⌤"; - + + case kVK_Home: return @"↖"; + case kVK_End: return @"↘"; + case kVK_PageUp: return @"⇞"; + case kVK_PageDown: return @"⇟"; + case kVK_Delete: return @"⌫"; + case kVK_ForwardDelete: return @"⌦"; + case kVK_ANSI_KeypadEnter: return @"⌤"; + case kVK_CapsLock: return @"⇪"; + case kVK_Shift: return @"Left ⇧"; + case kVK_Control: return @"Left ⌃"; + case kVK_Option: return @"Left ⌥"; + case kVK_Command: return @"Left ⌘"; + case kVK_RightShift: return @"Right ⇧"; + case kVK_RightControl: return @"Right ⌃"; + case kVK_RightOption: return @"Right ⌥"; + case kVK_RightCommand: return @"Right ⌘"; + case kVK_Function: return @"fn"; + /* Label Keypad buttons accordingly */ default: - if ((keyCode < 82 || keyCode > 92)) { + if ((keyCode < kVK_ANSI_Keypad0 || keyCode > kVK_ANSI_Keypad9)) { return [NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]; } - case 65: case 67: case 69: case 75: case 78: case 81: + case kVK_ANSI_KeypadDecimal: case kVK_ANSI_KeypadMultiply: case kVK_ANSI_KeypadPlus: case kVK_ANSI_KeypadDivide: case kVK_ANSI_KeypadMinus: case kVK_ANSI_KeypadEquals: return [@"Keypad " stringByAppendingString:[NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]]; } } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index c599225f..b8bd246f 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -17,14 +17,14 @@ - + - + - + @@ -33,7 +33,7 @@ - + @@ -67,7 +67,7 @@
    - + @@ -76,7 +76,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -117,7 +117,7 @@ - + @@ -141,7 +141,7 @@ - + @@ -161,7 +161,7 @@ - + @@ -170,7 +170,7 @@ - + @@ -201,14 +201,14 @@ - + - + - + diff --git a/Core/apu.c b/Core/apu.c index 69bc5ea6..a6496da6 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -359,7 +359,7 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - double cycles_per_sample = CPU_FREQUENCY / (double)gb->apu_output.sample_rate; // TODO: this should be cached! + double cycles_per_sample = GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; if (gb->apu_output.sample_cycles > cycles_per_sample) { gb->apu_output.sample_cycles -= cycles_per_sample; @@ -837,7 +837,7 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) gb->apu_output.sample_rate = sample_rate; gb->apu_output.buffer_position = 0; if (sample_rate) { - gb->apu_output.highpass_rate = pow(0.999958, CPU_FREQUENCY / (double)sample_rate); + gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); } } diff --git a/Core/gb.c b/Core/gb.c index c2b63907..43c29806 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -100,6 +100,7 @@ void GB_init(GB_gameboy_t *gb) gb->async_input_callback = default_async_input_callback; #endif gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->clock_multiplier = 1.0; GB_reset(gb); } @@ -116,7 +117,8 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->async_input_callback = default_async_input_callback; #endif gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type - + gb->clock_multiplier = 1.0; + GB_reset(gb); } @@ -307,7 +309,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb) } gb->turbo = old_turbo; gb->turbo_dont_skip = old_dont_skip; - return gb->cycles_since_last_sync * FRAME_LENGTH * LCDC_PERIOD; + return gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb); } void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) @@ -580,3 +582,13 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * return NULL; } } + +void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) +{ + gb->clock_multiplier = multiplier; +} + +uint32_t GB_get_clock_rate(GB_gameboy_t *gb) +{ + return CPU_FREQUENCY * gb->clock_multiplier; +} diff --git a/Core/gb.h b/Core/gb.h index 8f41e786..3dea9961 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -162,7 +162,6 @@ typedef enum { #define CPU_FREQUENCY 0x400000 #define DIV_CYCLES (0x100) #define INTERNAL_DIV_CYCLES (0x40000) -#define FRAME_LENGTH (1000000000LL * LCDC_PERIOD / CPU_FREQUENCY) // in nanoseconds #if !defined(MIN) #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) @@ -495,6 +494,7 @@ struct GB_gameboy_internal_s { uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run() + double clock_multiplier; ); }; @@ -583,4 +583,9 @@ void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); void GB_disconnect_serial(GB_gameboy_t *gb); +#ifdef GB_INTERNAL +uint32_t GB_get_clock_rate(GB_gameboy_t *gb); +#endif +void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); + #endif /* GB_h */ diff --git a/Core/timing.c b/Core/timing.c index cbcfb821..c03803f6 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -40,7 +40,7 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb) { if (!gb->turbo_dont_skip) { int64_t nanoseconds = get_nanoseconds(); - if (nanoseconds <= gb->last_sync + FRAME_LENGTH) { + if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { return true; } gb->last_sync = nanoseconds; @@ -57,7 +57,7 @@ void GB_timing_sync(GB_gameboy_t *gb) /* Prevent syncing if not enough time has passed.*/ if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return; - uint64_t target_nanoseconds = gb->cycles_since_last_sync * FRAME_LENGTH / LCDC_PERIOD; + uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb); int64_t nanoseconds = get_nanoseconds(); if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) { nsleep(target_nanoseconds + gb->last_sync - nanoseconds); From bfb37884e130396a4e215520326354f454651787 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Feb 2018 22:50:15 +0200 Subject: [PATCH 0524/1216] Inactive channels are not equivalent to channels with 0 volume. --- Core/apu.c | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index a6496da6..29b8dba0 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -26,11 +26,11 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value, unsig gb->apu.samples[index] = value; if (gb->apu_output.sample_rate) { unsigned left_volume = 0; - if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + if (gb->apu.is_active[index] && (gb->io_registers[GB_IO_NR51] & (1 << index))) { left_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; } unsigned right_volume = 0; - if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + if (gb->apu.is_active[index] && (gb->io_registers[GB_IO_NR51] & (0x10 << index))) { right_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; } GB_sample_t output = {(0xf - value) * left_volume, (0xf - value) * right_volume}; @@ -82,11 +82,13 @@ static void render(GB_gameboy_t *gb) unsigned left_volume = 0; unsigned right_volume = 0; for (unsigned i = GB_N_CHANNELS; i--;) { - if (mask & 1) { - left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF; - } - if (mask & 0x10) { - right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF; + if (gb->apu.is_active[i]) { + if (mask & 1) { + left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF; + } + if (mask & 0x10) { + right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF; + } } mask >>= 1; } @@ -540,8 +542,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((value & 0xF8) == 0) { /* According to Blargg's test ROM this should disable the channel instantly TODO: verify how "instant" the change is using PCM12 */ - update_sample(gb, index, 0, 0); gb->apu.is_active[index] = false; + update_sample(gb, index, 0, 0); } else if (gb->apu.is_active[index]) { nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); @@ -590,8 +592,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7; - if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0) { + if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { gb->apu.is_active[index] = true; + update_square_sample(gb, index); } if (gb->apu.square_channels[index].pulse_length == 0) { gb->apu.square_channels[index].pulse_length = 0x40; @@ -625,8 +628,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].pulse_length = 0x3F; } else { - update_sample(gb, index, 0, 0); gb->apu.is_active[index] = false; + update_sample(gb, index, 0, 0); } } } @@ -682,7 +685,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) 8); } } - gb->apu.is_active[GB_WAVE] = true; + if (!gb->apu.is_active[GB_WAVE]) { + gb->apu.is_active[GB_WAVE] = true; + update_sample(gb, GB_WAVE, 0, 0); + } gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; gb->apu.wave_channel.current_sample_index = 0; if (gb->apu.wave_channel.pulse_length == 0) { @@ -703,13 +709,16 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.pulse_length = 0xFF; } else { - update_sample(gb, GB_WAVE, 0, 0); gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); } } } gb->apu.wave_channel.length_enabled = value & 0x40; - gb->apu.is_active[GB_WAVE] &= gb->apu.wave_channel.enable; + if (gb->apu.is_active[GB_WAVE] && !gb->apu.wave_channel.enable) { + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } break; @@ -725,8 +734,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((value & 0xF8) == 0) { /* According to Blargg's test ROM this should disable the channel instantly TODO: verify how "instant" the change is using PCM12 */ - update_sample(gb, GB_NOISE, 0, 0); gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); } else if (gb->apu.is_active[GB_NOISE]){ nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); @@ -783,8 +792,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.lfsr = 0; gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; - if ((gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { + if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { gb->apu.is_active[GB_NOISE] = true; + update_sample(gb, GB_NOISE, 0, 0); } if (gb->apu.noise_channel.pulse_length == 0) { @@ -804,8 +814,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.pulse_length = 0x3F; } else { - update_sample(gb, GB_NOISE, 0, 0); gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); } } } From 0c231db9e7b6012cc3ab947ac12e18aa4f057d7b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Feb 2018 23:13:15 +0200 Subject: [PATCH 0525/1216] This is probably not correct (and makes no sense from an hardware design perspective), but this correctly emulates my analog test cases and fixes the pops introduced by the last commit. --- Core/apu.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 29b8dba0..ade49b33 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -24,13 +24,13 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value, unsigned cycles_offset) { gb->apu.samples[index] = value; - if (gb->apu_output.sample_rate) { + if (gb->apu.is_active[index] && gb->apu_output.sample_rate) { unsigned left_volume = 0; - if (gb->apu.is_active[index] && (gb->io_registers[GB_IO_NR51] & (1 << index))) { + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { left_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; } unsigned right_volume = 0; - if (gb->apu.is_active[index] && (gb->io_registers[GB_IO_NR51] & (0x10 << index))) { + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { right_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; } GB_sample_t output = {(0xf - value) * left_volume, (0xf - value) * right_volume}; @@ -90,6 +90,10 @@ static void render(GB_gameboy_t *gb) right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF; } } + else { + left_volume += gb->apu_output.current_sample[i].left * CH_STEP; + right_volume += gb->apu_output.current_sample[i].right * CH_STEP; + } mask >>= 1; } gb->apu_output.highpass_diff = (GB_double_sample_t) From fc35111ae7bf23bb6ef8d854ee84aa80928bb45b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Feb 2018 01:26:37 +0200 Subject: [PATCH 0526/1216] =?UTF-8?q?Corrected=20the=20emulated=20DAC?= =?UTF-8?q?=E2=80=99s=20range?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 8 ++++---- Core/apu.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index ade49b33..0164f316 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -21,7 +21,7 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; } -static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value, unsigned cycles_offset) +static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) { gb->apu.samples[index] = value; if (gb->apu.is_active[index] && gb->apu_output.sample_rate) { @@ -33,7 +33,7 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value, unsig if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { right_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; } - GB_sample_t output = {(0xf - value) * left_volume, (0xf - value) * right_volume}; + GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume}; if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { refresh_channel(gb, index, cycles_offset); gb->apu_output.current_sample[index] = output; @@ -51,9 +51,9 @@ static void render(GB_gameboy_t *gb) } else { refresh_channel(gb, i, 0); - output.left += (unsigned) gb->apu_output.summed_samples[i].left * CH_STEP + output.left += (signed long) gb->apu_output.summed_samples[i].left * CH_STEP / gb->apu_output.cycles_since_render; - output.right += (unsigned) gb->apu_output.summed_samples[i].right * CH_STEP + output.right += (signed long) gb->apu_output.summed_samples[i].right * CH_STEP / gb->apu_output.cycles_since_render; gb->apu_output.summed_samples[i] = (GB_sample_t){0, 0}; } diff --git a/Core/apu.h b/Core/apu.h index c4d4fc70..495bea84 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -9,9 +9,9 @@ /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ #ifdef WIIU /* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/ -#define MAX_CH_AMP (0x1FE0 / 4) +#define MAX_CH_AMP (0xFF0 / 2) #else -#define MAX_CH_AMP 0x1FE0 +#define MAX_CH_AMP 0xFF0 #endif #define CH_STEP (MAX_CH_AMP/0xF/8) #endif From f79af39ea202008a860f6ac07dd0ec19b9613f57 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Feb 2018 18:01:50 +0200 Subject: [PATCH 0527/1216] =?UTF-8?q?More=20accurate=20emulation=20of=20th?= =?UTF-8?q?e=20APU=E2=80=99s=20analog=20characteristics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 50 ++++++++++++++++++++++++++++++++++---------------- Core/apu.h | 6 ++++++ Core/memory.c | 6 ++++-- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 0164f316..59970ec2 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -23,8 +23,15 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) { - gb->apu.samples[index] = value; - if (gb->apu.is_active[index] && gb->apu_output.sample_rate) { + if (!gb->apu.is_active[index]) { + value = gb->apu.samples[index]; + } + else { + gb->apu.samples[index] = value; + gb->apu_output.dac_discharge[index] = 1.0; + } + + if (gb->apu_output.sample_rate) { unsigned left_volume = 0; if (gb->io_registers[GB_IO_NR51] & (1 << index)) { left_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; @@ -41,27 +48,36 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign } } -static void render(GB_gameboy_t *gb) +static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) { GB_sample_t output = {0,0}; for (unsigned i = GB_N_CHANNELS; i--;) { - if (likely(gb->apu_output.last_update[i] == 0)) { - output.left += gb->apu_output.current_sample[i].left * CH_STEP; - output.right += gb->apu_output.current_sample[i].right * CH_STEP; + double multiplier = CH_STEP; + if (!gb->apu.is_active[i]) { + gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] < 0) { + multiplier = 0; + } + else { + multiplier *= pow(0.05, 1 - gb->apu_output.dac_discharge[i]) * (gb->apu_output.dac_discharge[i]); + } + } + + if (likely(gb->apu_output.last_update[i] == 0 || no_downsampling)) { + output.left += gb->apu_output.current_sample[i].left * multiplier; + output.right += gb->apu_output.current_sample[i].right * multiplier; } else { refresh_channel(gb, i, 0); - output.left += (signed long) gb->apu_output.summed_samples[i].left * CH_STEP + output.left += (signed long) gb->apu_output.summed_samples[i].left * multiplier / gb->apu_output.cycles_since_render; - output.right += (signed long) gb->apu_output.summed_samples[i].right * CH_STEP + output.right += (signed long) gb->apu_output.summed_samples[i].right * multiplier / gb->apu_output.cycles_since_render; gb->apu_output.summed_samples[i] = (GB_sample_t){0, 0}; } gb->apu_output.last_update[i] = 0; } gb->apu_output.cycles_since_render = 0; - - GB_sample_t filtered_output = gb->apu_output.highpass_mode? (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left, @@ -104,6 +120,10 @@ static void render(GB_gameboy_t *gb) } } + if (dest) { + *dest = filtered_output; + return; + } while (gb->apu_output.copy_in_progress); while (!__sync_bool_compare_and_swap(&gb->apu_output.lock, false, true)); @@ -369,7 +389,7 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_cycles > cycles_per_sample) { gb->apu_output.sample_cycles -= cycles_per_sample; - render(gb); + render(gb, false, NULL); } } } @@ -386,15 +406,13 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) if (count > gb->apu_output.buffer_position) { // GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position); - GB_sample_t output = {-gb->apu_output.highpass_diff.left, -gb->apu_output.highpass_diff.right}; - for (unsigned i = GB_N_CHANNELS; i--;) { - output.left += gb->apu_output.current_sample[i].left * CH_STEP; - output.right += gb->apu_output.current_sample[i].right * CH_STEP; - } + GB_sample_t output; + render(gb, true, &output); for (unsigned i = 0; i < count - gb->apu_output.buffer_position; i++) { dest[gb->apu_output.buffer_position + i] = output; } + count = gb->apu_output.buffer_position; } memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer)); diff --git a/Core/apu.h b/Core/apu.h index 495bea84..bd69ac4f 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -6,6 +6,9 @@ #ifdef GB_INTERNAL +/* Speed = 1 / Length (in seconds) */ +#define DAC_DECAY_SPEED 500.0 + /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ #ifdef WIIU /* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/ @@ -16,6 +19,8 @@ #define CH_STEP (MAX_CH_AMP/0xF/8) #endif + + /* APU ticks are 2MHz, triggered by an internal APU clock. */ typedef struct @@ -129,6 +134,7 @@ typedef struct { unsigned last_update[GB_N_CHANNELS]; GB_sample_t current_sample[GB_N_CHANNELS]; GB_sample_t summed_samples[GB_N_CHANNELS]; + double dac_discharge[GB_N_CHANNELS]; GB_highpass_mode_t highpass_mode; double highpass_rate; diff --git a/Core/memory.c b/Core/memory.c index b41057fc..ef2f007a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -149,10 +149,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_PCM_12: if (!gb->is_cgb) return 0xFF; - return (gb->apu.samples[GB_SQUARE_2] << 4) | gb->apu.samples[GB_SQUARE_1]; + return (gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | + (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0); case GB_IO_PCM_34: if (!gb->is_cgb) return 0xFF; - return (gb->apu.samples[GB_NOISE] << 4) | gb->apu.samples[GB_WAVE]; + return (gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | + (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0); case GB_IO_JOYP: case GB_IO_TMA: case GB_IO_LCDC: From d0202a3f9aa93cdb468da7fd0a36bec199b61c54 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Feb 2018 20:43:48 +0200 Subject: [PATCH 0528/1216] Added LCD graphics filter; emulates low-resolution LCD artifacts --- Cocoa/GBPreferencesWindow.m | 1 + Cocoa/Preferences.xib | 9 ++--- SDL/gui.c | 1 + Shaders/LCD.fsh | 70 +++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 Shaders/LCD.fsh diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 655ab0a6..66d8e4c7 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -28,6 +28,7 @@ @"NearestNeighbor", @"Bilinear", @"SmoothBilinear", + @"LCD", @"Scale2x", @"Scale4x", @"AAScale2x", diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index b8bd246f..da853241 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -35,7 +35,7 @@ - +
    + + + + + + + + + + + + + + + + + + diff --git a/Core/cheats.c b/Core/cheats.c index 36b4afb7..fa9b6a7a 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -1,6 +1,7 @@ #include "gb.h" #include "cheats.h" #include +#include static inline uint8_t hash_addr(uint16_t addr) { @@ -30,6 +31,8 @@ static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) { + if (!gb->cheat_enabled) return; + if (!gb->boot_rom_finished) return; const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)]; if (hash) { for (unsigned i = 0; i < hash->size; i++) { @@ -44,6 +47,16 @@ void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) } } +bool GB_cheats_enabled(GB_gameboy_t *gb) +{ + return gb->cheat_enabled; +} + +void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled) +{ + gb->cheat_enabled = enabled; +} + void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) { GB_cheat_t *cheat = malloc(sizeof(*cheat)); @@ -66,7 +79,7 @@ void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, u } else { (*hash)->size++; - *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); (*hash)->cheats[(*hash)->size - 1] = cheat; } } @@ -171,3 +184,57 @@ bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *descriptio } return false; } + +void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) +{ + GB_cheat_t *cheat = NULL; + for (unsigned i = 0; i < gb->cheat_count; i++) { + if (gb->cheats[i] == _cheat) { + cheat = gb->cheats[i]; + break; + } + } + + assert(cheat); + + if (cheat->address != address) { + /* Remove from old bucket */ + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; + for (unsigned i = 0; i < (*hash)->size; i++) { + if ((*hash)->cheats[i] == cheat) { + (*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; + if ((*hash)->size == 0) { + free(*hash); + *hash = NULL; + } + else { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + } + break; + } + } + cheat->address = address; + + /* Add to new bucket */ + hash = &gb->cheat_hash[hash_addr(address)]; + if (!*hash) { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat)); + (*hash)->size = 1; + (*hash)->cheats[0] = cheat; + } + else { + (*hash)->size++; + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + (*hash)->cheats[(*hash)->size - 1] = cheat; + } + } + cheat->bank = bank; + cheat->value = value; + cheat->old_value = old_value; + cheat->use_old_value = use_old_value; + cheat->enabled = enabled; + if (description != cheat->description) { + strncpy(cheat->description, description, sizeof(cheat->description)); + cheat->description[sizeof(cheat->description) - 1] = 0; + } +} diff --git a/Core/cheats.h b/Core/cheats.h index c461f22e..be5b39ae 100644 --- a/Core/cheats.h +++ b/Core/cheats.h @@ -7,9 +7,12 @@ typedef struct GB_cheat_s GB_cheat_t; void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); +void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled); const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size); void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat); +bool GB_cheats_enabled(GB_gameboy_t *gb); +void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled); #ifdef GB_INTERNAL void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); diff --git a/Core/gb.h b/Core/gb.h index 8a60770e..03abfa11 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -647,6 +647,7 @@ struct GB_gameboy_internal_s { double sgb_intro_sweep_previous_sample; /* Cheats */ + bool cheat_enabled; size_t cheat_count; GB_cheat_t **cheats; GB_cheat_hash_t *cheat_hash[256]; diff --git a/Makefile b/Makefile index 7c223755..5923b2e4 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ OPEN_DIALOG = OpenDialog/cocoa.m endif -CFLAGS += -Werror -Wall -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -Werror -Wall -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES ifeq (,$(PKG_CONFIG)) SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_LDFLAGS := $(shell sdl2-config --libs) From 2bc75caf9efe0c4f44bd2e6f9bcc154664a7402b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 18:03:21 +0300 Subject: [PATCH 1033/1216] Fix CRT shader on OpenGL --- Shaders/CRT.fsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shaders/CRT.fsh b/Shaders/CRT.fsh index c1ae7ef6..0bc4c656 100644 --- a/Shaders/CRT.fsh +++ b/Shaders/CRT.fsh @@ -159,7 +159,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } // Gamma correction - ret = pow(ret, 0.72); + ret = pow(ret, vec4(0.72)); return ret; } From 0c3db932b22e3ba9e033ce4391c9d8dd5b99f0d6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 18:19:15 +0300 Subject: [PATCH 1034/1216] Fix Mavericks compatibility --- Cocoa/Document.m | 14 ++++++-------- Cocoa/GBSplitView.h | 2 +- Cocoa/GBSplitView.m | 10 ++++++++++ Cocoa/GBViewMetal.m | 2 ++ Cocoa/NSObject+MavericksCompat.m | 7 +++++++ Makefile | 2 +- 6 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 Cocoa/NSObject+MavericksCompat.m diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 71436d33..0114658f 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -817,9 +817,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } if (![console_output_timer isValid]) { - console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 repeats:NO block:^(NSTimer * _Nonnull timer) { - [self appendPendingOutput]; - }]; + console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode]; } @@ -1665,7 +1663,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } -- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview; +- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview; { if ([[splitView arrangedSubviews] lastObject] == subview) { return YES; @@ -1673,16 +1671,16 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) return NO; } -- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex +- (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { return 600; } -- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { +- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { return splitView.frame.size.width - 321; } -- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { +- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { if ([[splitView arrangedSubviews] lastObject] == view) { return NO; } @@ -1691,7 +1689,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) - (void)splitViewDidResizeSubviews:(NSNotification *)notification { - NSSplitView *splitview = notification.object; + GBSplitView *splitview = notification.object; if ([[[splitview arrangedSubviews] firstObject] frame].size.width < 600) { [splitview setPosition:600 ofDividerAtIndex:0]; } diff --git a/Cocoa/GBSplitView.h b/Cocoa/GBSplitView.h index 7b2faa2c..6ab97cf0 100644 --- a/Cocoa/GBSplitView.h +++ b/Cocoa/GBSplitView.h @@ -3,5 +3,5 @@ @interface GBSplitView : NSSplitView -(void) setDividerColor:(NSColor *)color; - +- (NSArray *)arrangedSubviews; @end diff --git a/Cocoa/GBSplitView.m b/Cocoa/GBSplitView.m index 0a30fe0c..0fb3bc4c 100644 --- a/Cocoa/GBSplitView.m +++ b/Cocoa/GBSplitView.m @@ -17,4 +17,14 @@ return [super dividerColor]; } +/* Mavericks comaptibility */ +- (NSArray *)arrangedSubviews +{ + if (@available(macOS 10.11, *)) { + return [super arrangedSubviews]; + } else { + return [self subviews]; + } +} + @end diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 4c8a5d5e..62deadcf 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -1,4 +1,6 @@ #import "GBViewMetal.h" +#pragma clang diagnostic ignored "-Wpartial-availability" + static const vector_float2 rect[] = { diff --git a/Cocoa/NSObject+MavericksCompat.m b/Cocoa/NSObject+MavericksCompat.m new file mode 100644 index 00000000..6c065143 --- /dev/null +++ b/Cocoa/NSObject+MavericksCompat.m @@ -0,0 +1,7 @@ +#import +@implementation NSObject (MavericksCompat) +- (instancetype)initWithCoder:(NSCoder *)coder +{ + return [self init]; +} +@end diff --git a/Makefile b/Makefile index 5923b2e4..36a893dd 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ OPEN_DIALOG = OpenDialog/cocoa.m endif -CFLAGS += -Werror -Wall -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -Werror -Wall -Wpartial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES ifeq (,$(PKG_CONFIG)) SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_LDFLAGS := $(shell sdl2-config --libs) From 5df45417fad1c9467d75dff40b3de7b4369af490 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 18:27:31 +0300 Subject: [PATCH 1035/1216] Console quirks --- Cocoa/Document.m | 3 ++- Cocoa/Document.xib | 36 ++++++++++++++++++------------------ Core/debugger.c | 5 +++-- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 0114658f..3c83fab9 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -470,7 +470,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) - (void)windowControllerDidLoadNib:(NSWindowController *)aController { [super windowControllerDidLoadNib:aController]; - + // Interface Builder bug? + [self.consoleWindow setContentSize:self.consoleWindow.minSize]; /* Close Open Panels, if any */ for (NSWindow *window in [[NSApplication sharedApplication] windows]) { if ([window isKindOfClass:[NSOpenPanel class]]) { diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 338650bc..f096bb34 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -107,22 +107,22 @@ - + - + - + - + - + @@ -137,29 +137,29 @@ - + - + - + - + - + - + @@ -173,27 +173,27 @@ - + - + - + - + - + - + @@ -208,7 +208,7 @@ - + diff --git a/Core/debugger.c b/Core/debugger.c index 4e3bd639..94fae806 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1766,10 +1766,11 @@ static const debugger_command_t commands[] = { {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, - {"backtrace", 2, backtrace, "Display the current call stack"}, + {"backtrace", 2, backtrace, "Displays the current call stack"}, {"bt", 2, }, /* Alias */ {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, - {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used"}, + {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE + "used"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ From 0abd3b2c469dc65eb09120c4a6811af1da83b678 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 19:15:40 +0300 Subject: [PATCH 1036/1216] Dump and load cheats --- Cocoa/Document.h | 3 ++ Cocoa/Document.m | 4 +++ Cocoa/Document.xib | 4 ++- Core/cheats.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++ Core/cheats.h | 2 ++ 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 1117c26f..93537887 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -3,6 +3,8 @@ #include "GBImageView.h" #include "GBSplitView.h" +@class GBCheatWindowController; + @interface Document : NSDocument @property (strong) IBOutlet GBView *view; @property (strong) IBOutlet NSTextView *consoleOutput; @@ -34,6 +36,7 @@ @property (strong) IBOutlet GBSplitView *debuggerSplitView; @property (strong) IBOutlet NSBox *debuggerVerticalLine; @property (strong) IBOutlet NSPanel *cheatsWindow; +@property (strong) IBOutlet GBCheatWindowController *cheatWindowController; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 3c83fab9..823204c2 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -7,6 +7,7 @@ #include "HexFiend/HexFiend.h" #include "GBMemoryByteArray.h" #include "GBWarningPopover.h" +#include "GBCheatWindowController.h" /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: Split into category files! This is so messy!!! */ @@ -359,6 +360,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.audioClient = nil; self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); stopping = false; } @@ -643,6 +645,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) NSString *rom_warnings = [self captureOutputForBlock:^{ GB_load_rom(&gb, [self.fileName UTF8String]); GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + GB_load_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); + [self.cheatWindowController.cheatsTable reloadData]; GB_debugger_clear_symbols(&gb); GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]); diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index f096bb34..f1f2f5a1 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -9,6 +9,8 @@ + + @@ -1056,7 +1058,7 @@ - + diff --git a/Core/cheats.c b/Core/cheats.c index fa9b6a7a..0525816f 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -2,6 +2,7 @@ #include "cheats.h" #include #include +#include static inline uint8_t hash_addr(uint16_t addr) { @@ -238,3 +239,78 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des cheat->description[sizeof(cheat->description) - 1] = 0; } } + +#define CHEAT_MAGIC 'SBCh' + +void GB_load_cheats(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return; + } + + uint32_t magic = 0; + uint32_t struct_size = 0; + fread(&magic, sizeof(magic), 1, f); + fread(&struct_size, sizeof(struct_size), 1, f); + if (magic != CHEAT_MAGIC && magic != __builtin_bswap32(CHEAT_MAGIC)) { + GB_log(gb, "The file is not a SameBoy cheat database"); + return; + } + + if (struct_size != sizeof(GB_cheat_t)) { + GB_log(gb, "This cheat database is not compatible with this version of SameBoy"); + return; + } + + // Remove all cheats first + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } + + GB_cheat_t cheat; + while (fread(&cheat, sizeof(cheat), 1, f)) { + if (magic == __builtin_bswap32(CHEAT_MAGIC)) { + cheat.address = __builtin_bswap16(cheat.address); + cheat.bank = __builtin_bswap16(cheat.bank); + } + cheat.description[sizeof(cheat.description) - 1] = 0; + GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled); + } + + return; +} + +int GB_save_cheats(GB_gameboy_t *gb, const char *path) +{ + if (!gb->cheat_count) return 0; // Nothing to save. + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno)); + return errno; + } + + uint32_t magic = CHEAT_MAGIC; + uint32_t struct_size = sizeof(GB_cheat_t); + + if (fwrite(&magic, sizeof(magic), 1, f) != 1) { + fclose(f); + return EIO; + } + + if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) { + fclose(f); + return EIO; + } + + for (size_t i = 0; i cheat_count; i++) { + if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) { + fclose(f); + return EIO; + } + } + + errno = 0; + fclose(f); + return errno; +} diff --git a/Core/cheats.h b/Core/cheats.h index be5b39ae..13b7d7b4 100644 --- a/Core/cheats.h +++ b/Core/cheats.h @@ -13,6 +13,8 @@ const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size); void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat); bool GB_cheats_enabled(GB_gameboy_t *gb); void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled); +void GB_load_cheats(GB_gameboy_t *gb, const char *path); +int GB_save_cheats(GB_gameboy_t *gb, const char *path); #ifdef GB_INTERNAL void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); From 695c6ee943d661a40db8fbd3914605e7d2b29a46 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 19:21:00 +0300 Subject: [PATCH 1037/1216] Don't crash if a naughty frontend runs the boot ROM without a ROM --- Core/display.c | 2 +- Core/gb.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 356e7425..67a9fc4d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -165,7 +165,7 @@ static void display_vblank(GB_gameboy_t *gb) 0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C, 0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964, }; - unsigned index = gb->rom[0x14e] % 5; + unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0; gb->borrowed_border.palette[0] = colors[index]; gb->borrowed_border.palette[10] = colors[5 + index]; gb->borrowed_border.palette[14] = colors[10 + index]; diff --git a/Core/gb.c b/Core/gb.c index 75538e13..3b834dd8 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -230,7 +230,7 @@ void GB_borrow_sgb_border(GB_gameboy_t *gb) if (gb->border_mode != GB_BORDER_ALWAYS) return; if (gb->tried_loading_sgb_border) return; gb->tried_loading_sgb_border = true; - if (gb->rom[0x146] != 3) return; // Not an SGB game, nothing to borrow + if (gb->rom && gb->rom[0x146] != 3) return; // Not an SGB game, nothing to borrow if (!gb->boot_rom_load_callback) return; // Can't borrow a border without this callback GB_gameboy_t sgb; GB_init(&sgb, GB_MODEL_SGB); From 32a0dc0e435a54bc70d2403d85a2996ced757a52 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 20:44:15 +0300 Subject: [PATCH 1038/1216] Rename the "Developer" menu to "Develop", like first party Mac apps --- Cocoa/MainMenu.xib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index e56b4a05..71add1c1 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -382,9 +382,9 @@ - + - + From db9410caa5f4a518984ba298fcb6688d5e6cef68 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 21:48:47 +0300 Subject: [PATCH 1039/1216] Minor UI fix --- Cocoa/Document.m | 2 +- Cocoa/GBCheatWindowController.h | 2 +- Cocoa/GBCheatWindowController.m | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 823204c2..10ba5102 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -646,7 +646,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_load_rom(&gb, [self.fileName UTF8String]); GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); GB_load_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); - [self.cheatWindowController.cheatsTable reloadData]; + [self.cheatWindowController cheatsUpdated]; GB_debugger_clear_symbols(&gb); GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]); diff --git a/Cocoa/GBCheatWindowController.h b/Cocoa/GBCheatWindowController.h index adb0bf80..f70553e6 100644 --- a/Cocoa/GBCheatWindowController.h +++ b/Cocoa/GBCheatWindowController.h @@ -12,6 +12,6 @@ @property (weak) IBOutlet NSTextField *importCodeField; @property (weak) IBOutlet NSTextField *importDescriptionField; @property (weak) IBOutlet Document *document; - +- (void)cheatsUpdated; @end diff --git a/Cocoa/GBCheatWindowController.m b/Cocoa/GBCheatWindowController.m index 994d5e1b..c10e2a94 100644 --- a/Cocoa/GBCheatWindowController.m +++ b/Cocoa/GBCheatWindowController.m @@ -231,4 +231,10 @@ [self.cheatsTable reloadData]; } +- (void)cheatsUpdated +{ + [self.cheatsTable reloadData]; + [self tableViewSelectionDidChange:nil]; +} + @end From d38fd41b0eaa2a598fd88f6d5d37fcb52f257598 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 24 Apr 2020 20:18:56 +0300 Subject: [PATCH 1040/1216] Reorder flags so -Wpartial-availablility is affected by -Wno-unknown-warning -Wno-unknown-warning-option, fixes #249, fixes #251 --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 36a893dd..ba568ffe 100644 --- a/Makefile +++ b/Makefile @@ -87,8 +87,13 @@ ifeq ($(PLATFORM),Darwin) OPEN_DIALOG = OpenDialog/cocoa.m endif +# These myst come before the -Wno- flags +CFLAGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option + +CFLAGS += -Wpartial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context + +CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES -CFLAGS += -Werror -Wall -Wpartial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES ifeq (,$(PKG_CONFIG)) SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_LDFLAGS := $(shell sdl2-config --libs) From 0cf168f32beb6c98dcfb8d6cd17659f1bbf53c45 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 24 Apr 2020 20:37:57 +0300 Subject: [PATCH 1041/1216] Fixing inconsistent style --- BootROMs/pb8.c | 33 ++++-- Cocoa/Document.m | 27 +++-- Cocoa/GBAudioClient.m | 6 +- Cocoa/GBGLShader.m | 2 +- Cocoa/GBImageView.m | 2 +- Cocoa/GBOpenGLView.m | 3 +- Cocoa/GBSplitView.m | 9 +- Cocoa/GBTerminalTextFieldCell.m | 3 +- Cocoa/GBView.m | 6 +- Cocoa/GBViewMetal.m | 3 +- Cocoa/joypad.m | 12 ++- Cocoa/main.m | 3 +- Core/apu.c | 4 +- Core/debugger.c | 22 ++-- Core/display.c | 2 +- Core/gb.c | 6 +- Core/gb.h | 6 +- Core/memory.c | 2 +- Core/save_state.c | 2 +- Core/sgb.c | 4 +- Core/sm83_cpu.c | 10 +- Core/sm83_disassembler.c | 3 +- Core/timing.c | 15 +-- OpenDialog/gtk.c | 3 +- OpenDialog/windows.c | 3 +- QuickLook/generator.m | 2 +- QuickLook/main.c | 30 +++--- SDL/gui.c | 8 +- SDL/main.c | 5 +- SDL/opengl_compat.h | 2 +- Tester/main.c | 4 +- Windows/stdio.h | 3 +- libretro/libretro.c | 175 ++++++++++++-------------------- 33 files changed, 197 insertions(+), 223 deletions(-) diff --git a/BootROMs/pb8.c b/BootROMs/pb8.c index 03a196e9..4ee4524e 100644 --- a/BootROMs/pb8.c +++ b/BootROMs/pb8.c @@ -97,7 +97,8 @@ LoadTileset: * @param blocklength size of an independent input block in bytes * @return 0 for reaching infp end of file, or EOF for error */ -int pb8(FILE *infp, FILE *outfp, size_t blocklength) { +int pb8(FILE *infp, FILE *outfp, size_t blocklength) +{ blocklength >>= 3; // convert bytes to blocks assert(blocklength > 0); while (1) { @@ -113,7 +114,8 @@ int pb8(FILE *infp, FILE *outfp, size_t blocklength) { control_byte <<= 1; if (c == last_byte) { control_byte |= 0x01; - } else { + } + else { literals[nliterals++] = last_byte = c; } } @@ -143,7 +145,8 @@ int pb8(FILE *infp, FILE *outfp, size_t blocklength) { * @param outfp output stream * @return 0 for reaching infp end of file, or EOF for error */ -int unpb8(FILE *infp, FILE *outfp) { +int unpb8(FILE *infp, FILE *outfp) +{ int last_byte = 0; while (1) { int control_byte = fgetc(infp); @@ -165,7 +168,8 @@ int unpb8(FILE *infp, FILE *outfp) { /* CLI frontend ****************************************************/ -static inline void set_fd_binary(unsigned int fd) { +static inline void set_fd_binary(unsigned int fd) +{ #ifdef _WIN32 _setmode(fd, _O_BINARY); #else @@ -197,7 +201,8 @@ static const char *version_msg = static const char *toomanyfilenames_msg = "pb8: too many filenames; try pb8 --help\n"; -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ const char *infilename = NULL; const char *outfilename = NULL; bool decompress = false; @@ -248,11 +253,14 @@ int main(int argc, char **argv) { fprintf(stderr, "pb8: unknown option -%c\n", argtype); return EXIT_FAILURE; } - } else if (!infilename) { + } + else if (!infilename) { infilename = argv[i]; - } else if (!outfilename) { + } + else if (!outfilename) { outfilename = argv[i]; - } else { + } + else { fputs(toomanyfilenames_msg, stderr); return EXIT_FAILURE; } @@ -282,7 +290,8 @@ int main(int argc, char **argv) { perror("for reading"); return EXIT_FAILURE; } - } else { + } + else { infp = stdin; set_fd_binary(0); } @@ -296,7 +305,8 @@ int main(int argc, char **argv) { fclose(infp); return EXIT_FAILURE; } - } else { + } + else { outfp = stdout; set_fd_binary(1); } @@ -305,7 +315,8 @@ int main(int argc, char **argv) { int has_ferror = 0; if (decompress) { compfailed = unpb8(infp, outfp); - } else { + } + else { compfailed = pb8(infp, outfp, blocklength); } fflush(outfp); diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 10ba5102..ed3eaaf0 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -150,7 +150,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) NSMutableArray *debugger_input_queue; } -- (instancetype)init { +- (instancetype)init +{ self = [super init]; if (self) { has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; @@ -470,7 +471,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } } -- (void)windowControllerDidLoadNib:(NSWindowController *)aController { +- (void)windowControllerDidLoadNib:(NSWindowController *)aController +{ [super windowControllerDidLoadNib:aController]; // Interface Builder bug? [self.consoleWindow setContentSize:self.consoleWindow.minSize]; @@ -625,11 +627,13 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.memoryBankItem.enabled = false; } -+ (BOOL)autosavesInPlace { ++ (BOOL)autosavesInPlace +{ return YES; } -- (NSString *)windowNibName { +- (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"; @@ -690,7 +694,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) - (BOOL)validateUserInterfaceItem:(id)anItem { - if([anItem action] == @selector(mute:)) { + if ([anItem action] == @selector(mute:)) { [(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; } else if ([anItem action] == @selector(togglePause:)) { @@ -837,7 +841,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [self.consoleWindow orderBack:nil]; } -- (IBAction)consoleInput:(NSTextField *)sender { +- (IBAction)consoleInput:(NSTextField *)sender +{ NSString *line = [sender stringValue]; if ([line isEqualToString:@""] && lastConsoleInput) { line = lastConsoleInput; @@ -1475,7 +1480,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; if (tableView == self.paletteTableView) { if (columnIndex == 0) { - return [NSString stringWithFormat:@"%s %u", row >=8 ? "Object" : "Background", (unsigned)(row & 7)]; + return [NSString stringWithFormat:@"%s %u", row >= 8 ? "Object" : "Background", (unsigned)(row & 7)]; } uint8_t *palette_data = GB_get_direct_access(&gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, NULL, NULL); @@ -1572,7 +1577,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [self stop]; NSSavePanel * savePanel = [NSSavePanel savePanel]; [savePanel setAllowedFileTypes:@[@"png"]]; - [savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result){ + [savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result) { if (result == NSFileHandlingPanelOKButton) { [savePanel orderOut:self]; CGImageRef cgRef = [self.feedImageView.image CGImageForProposedRect:NULL @@ -1681,11 +1686,13 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) return 600; } -- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { +- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex +{ return splitView.frame.size.width - 321; } -- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { +- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view +{ if ([[splitView arrangedSubviews] lastObject] == view) { return NO; } diff --git a/Cocoa/GBAudioClient.m b/Cocoa/GBAudioClient.m index 81ddec44..7f2115d7 100644 --- a/Cocoa/GBAudioClient.m +++ b/Cocoa/GBAudioClient.m @@ -26,8 +26,7 @@ static OSStatus render( -(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block andSampleRate:(UInt32) rate { - if(!(self = [super init])) - { + if (!(self = [super init])) { return nil; } @@ -102,7 +101,8 @@ static OSStatus render( _playing = NO; } --(void) dealloc { +-(void) dealloc +{ [self stop]; AudioUnitUninitialize(audioUnit); AudioComponentInstanceDispose(audioUnit); diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index d57f43d1..920226b6 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -169,7 +169,7 @@ void main(void) {\n\ + (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type { - const GLchar* source = [contents UTF8String]; + const GLchar *source = [contents UTF8String]; // Create the shader object GLuint shader = glCreateShader(type); // Load the shader source diff --git a/Cocoa/GBImageView.m b/Cocoa/GBImageView.m index 47efa006..3525e72e 100644 --- a/Cocoa/GBImageView.m +++ b/Cocoa/GBImageView.m @@ -93,7 +93,7 @@ - (void)updateTrackingAreas { - if(trackingArea != nil) { + if (trackingArea != nil) { [self removeTrackingArea:trackingArea]; } diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 8831b625..90ebf8d5 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -4,7 +4,8 @@ @implementation GBOpenGLView -- (void)drawRect:(NSRect)dirtyRect { +- (void)drawRect:(NSRect)dirtyRect +{ if (!self.shader) { self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; } diff --git a/Cocoa/GBSplitView.m b/Cocoa/GBSplitView.m index 0fb3bc4c..a56c24e6 100644 --- a/Cocoa/GBSplitView.m +++ b/Cocoa/GBSplitView.m @@ -5,12 +5,14 @@ NSColor *_dividerColor; } -- (void)setDividerColor:(NSColor *)color { +- (void)setDividerColor:(NSColor *)color +{ _dividerColor = color; [self setNeedsDisplay:YES]; } -- (NSColor *)dividerColor { +- (NSColor *)dividerColor +{ if (_dividerColor) { return _dividerColor; } @@ -22,7 +24,8 @@ { if (@available(macOS 10.11, *)) { return [super arrangedSubviews]; - } else { + } + else { return [self subviews]; } } diff --git a/Cocoa/GBTerminalTextFieldCell.m b/Cocoa/GBTerminalTextFieldCell.m index 47a3a35d..e95e7854 100644 --- a/Cocoa/GBTerminalTextFieldCell.m +++ b/Cocoa/GBTerminalTextFieldCell.m @@ -173,7 +173,8 @@ [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag]; } -- (BOOL)resignFirstResponder { +- (BOOL)resignFirstResponder +{ reverse_search_mode = false; return [super resignFirstResponder]; } diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0e528111..6f26a037 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -114,8 +114,7 @@ } - (instancetype)initWithCoder:(NSCoder *)coder { - if (!(self = [super initWithCoder:coder])) - { + if (!(self = [super initWithCoder:coder])) { return self; } [self _init]; @@ -124,8 +123,7 @@ - (instancetype)initWithFrame:(NSRect)frameRect { - if (!(self = [super initWithFrame:frameRect])) - { + if (!(self = [super initWithFrame:frameRect])) { return self; } [self _init]; diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 62deadcf..9a1c78b6 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -159,8 +159,7 @@ static const vector_float2 rect[] = MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; id command_buffer = [command_queue commandBuffer]; - if (render_pass_descriptor != nil) - { + if (render_pass_descriptor != nil) { *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode]; *(vector_float2 *)[output_resolution_buffer contents] = output_resolution; diff --git a/Cocoa/joypad.m b/Cocoa/joypad.m index 2ffe56b8..fd9fe707 100755 --- a/Cocoa/joypad.m +++ b/Cocoa/joypad.m @@ -295,7 +295,8 @@ AddHIDElements(CFArrayRef array, recDevice *pDevice) } static bool -ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) { +ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) +{ while (listitem) { if (listitem->cookie == cookie) { return true; @@ -431,7 +432,8 @@ AddHIDElement(const void *value, void *parameter) } if (elementPrevious) { elementPrevious->pNext = element; - } else { + } + else { *headElement = element; } @@ -519,7 +521,8 @@ GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) *guid16++ = 0; *guid16++ = version; *guid16++ = 0; - } else { + } + else { *guid16++ = BUS_BLUETOOTH; *guid16++ = 0; strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4); @@ -582,7 +585,8 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick) value = GetHIDElementState(device, element) - element->min; if (range == 4) { /* 4 position hatswitch - scale up value */ value *= 2; - } else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */ + } + else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */ value = -1; } if ((unsigned)value >= 8) { diff --git a/Cocoa/main.m b/Cocoa/main.m index 8a6799b4..662469a7 100644 --- a/Cocoa/main.m +++ b/Cocoa/main.m @@ -1,5 +1,6 @@ #import -int main(int argc, const char * argv[]) { +int main(int argc, const char * argv[]) +{ return NSApplicationMain(argc, argv); } diff --git a/Core/apu.c b/Core/apu.c index feda3c89..e63d89f5 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -139,7 +139,7 @@ static double smooth(double x) static void render(GB_gameboy_t *gb) { - GB_sample_t output = {0,0}; + GB_sample_t output = {0, 0}; UNROLL for (unsigned i = 0; i < GB_N_CHANNELS; i++) { @@ -907,7 +907,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.is_active[GB_NOISE] = false; update_sample(gb, GB_NOISE, 0, 0); } - else if (gb->apu.is_active[GB_NOISE]){ + else if (gb->apu.is_active[GB_NOISE]) { nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); update_sample(gb, GB_NOISE, gb->apu.current_lfsr_sample ? diff --git a/Core/debugger.c b/Core/debugger.c index 94fae806..ee27e888 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -298,13 +298,15 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} -static value_t _div(value_t a, value_t b) { +static value_t _div(value_t a, value_t b) +{ if (b.value == 0) { return FIX_BANK(0); } return FIX_BANK(a.value / b.value); }; -static value_t mod(value_t a, value_t b) { +static value_t mod(value_t a, value_t b) +{ if (b.value == 0) { return FIX_BANK(0); } @@ -380,8 +382,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { length--; } - if (length == 0) - { + if (length == 0) { GB_log(gb, "Expected expression.\n"); *error = true; return (lvalue_t){0,}; @@ -487,8 +488,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { length--; } - if (length == 0) - { + if (length == 0) { GB_log(gb, "Expected expression.\n"); *error = true; goto exit; @@ -1167,7 +1167,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0])); gb->n_watchpoints--; - gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); + gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints *sizeof(gb->watchpoints[0])); GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true)); return true; @@ -1216,7 +1216,7 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug gb->watchpoints[i].condition); } else { - GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb,addr, addr.has_bank), + GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); } @@ -1581,7 +1581,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); - GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); + GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7, gb->io_registers[GB_IO_WY]); GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); return true; @@ -1730,7 +1730,7 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug uint8_t shift_amount = 1, mask; if (modifiers) { - switch(modifiers[0]) { + switch (modifiers[0]) { case 'c': shift_amount = 2; break; @@ -1826,7 +1826,7 @@ static const debugger_command_t *find_command(const char *string) static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command) { GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command); - GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length); + GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command + command->min_length); } static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command) diff --git a/Core/display.c b/Core/display.c index 67a9fc4d..b5c0a0a5 100644 --- a/Core/display.c +++ b/Core/display.c @@ -530,7 +530,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) *dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; } } - + if (gb->model & GB_MODEL_NO_SFC_BIT) { if (gb->icd_pixel_callback) { gb->icd_pixel_callback(gb, icd_pixel); diff --git a/Core/gb.c b/Core/gb.c index 3b834dd8..f6174b91 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -119,7 +119,7 @@ static void load_default_border(GB_gameboy_t *gb) }\ }\ }\ - } while(false); + } while (false); if (gb->model == GB_MODEL_AGB) { #include "graphics/agb_border.inc" @@ -658,8 +658,8 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) #endif } -const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff ,0xff, 0xff}, {0xff ,0xff, 0xff}}}; -const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2 ,0xe6 ,0xa6}}}; +const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff, 0xff, 0xff}, {0xff, 0xff, 0xff}}}; +const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2, 0xe6, 0xa6}}}; const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}}; const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}}; diff --git a/Core/gb.h b/Core/gb.h index 03abfa11..01b79e8b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -53,7 +53,7 @@ typedef struct { struct { - uint8_t r,g,b; + uint8_t r, g, b; } colors[5]; } GB_palette_t; @@ -254,11 +254,11 @@ typedef enum { #define INTERNAL_DIV_CYCLES (0x40000) #if !defined(MIN) -#define MIN(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; }) #endif #if !defined(MAX) -#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) +#define MAX(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) #endif #endif diff --git a/Core/memory.c b/Core/memory.c index 58b96bb4..ca9c1790 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -277,7 +277,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: - ; + break; } } diff --git a/Core/save_state.c b/Core/save_state.c index a53ccba5..11024df2 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -361,7 +361,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return -1; } - if (buffer_read(gb->vram,gb->vram_size, &buffer, &length) != gb->vram_size) { + if (buffer_read(gb->vram, gb->vram_size, &buffer, &length) != gb->vram_size) { return -1; } diff --git a/Core/sgb.c b/Core/sgb.c index 271b6817..c77b0db6 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -151,7 +151,7 @@ static void command_ready(GB_gameboy_t *gb) 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ if ((gb->sgb->command[0] & 0xF1) == 0xF1) { - if(gb->boot_rom_finished) return; + if (gb->boot_rom_finished) return; uint8_t checksum = 0; for (unsigned i = 2; i < 0x10; i++) { @@ -247,7 +247,7 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb->attribute_map[x + 20 * y] = inside_palette; } } - else if(middle) { + else if (middle) { gb->sgb->attribute_map[x + 20 * y] = middle_palette; } } diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 2b7b1dd6..13f05df7 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -452,7 +452,7 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) addr = cycle_read_inc_oam_bug(gb, gb->pc++); addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); - cycle_write(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); + cycle_write(gb, addr + 1, gb->registers[GB_REGISTER_SP] >> 8); } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -1222,7 +1222,7 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) uint16_t addr; gb->registers[GB_REGISTER_AF] &= 0xFF; addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8 ; + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8; } @@ -1410,10 +1410,10 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) } } else if ((opcode & 0xC0) == 0x80) { /* res */ - set_src_value(gb, opcode, value & ~bit) ; + set_src_value(gb, opcode, value & ~bit); } else if ((opcode & 0xC0) == 0xC0) { /* set */ - set_src_value(gb, opcode, value | bit) ; + set_src_value(gb, opcode, value | bit); } } @@ -1567,7 +1567,7 @@ void GB_cpu_run(GB_gameboy_t *gb) GB_debugger_call_hook(gb, call_addr); } /* Run mode */ - else if(!gb->halted) { + else if (!gb->halted) { gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; diff --git a/Core/sm83_disassembler.c b/Core/sm83_disassembler.c index 96aec000..7dacd9eb 100644 --- a/Core/sm83_disassembler.c +++ b/Core/sm83_disassembler.c @@ -97,7 +97,8 @@ static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) GB_log(gb, "RLA\n"); } -static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){ +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ uint16_t addr; (*pc)++; addr = GB_read_memory(gb, (*pc)++); diff --git a/Core/timing.c b/Core/timing.c index 1f3f654e..9eb9c916 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -274,19 +274,14 @@ void GB_rtc_run(GB_gameboy_t *gb) time_t current_time = time(NULL); while (gb->last_rtc_second < current_time) { gb->last_rtc_second++; - if (++gb->rtc_real.seconds == 60) - { + if (++gb->rtc_real.seconds == 60) { gb->rtc_real.seconds = 0; - if (++gb->rtc_real.minutes == 60) - { + if (++gb->rtc_real.minutes == 60) { gb->rtc_real.minutes = 0; - if (++gb->rtc_real.hours == 24) - { + if (++gb->rtc_real.hours == 24) { gb->rtc_real.hours = 0; - if (++gb->rtc_real.days == 0) - { - if (gb->rtc_real.high & 1) /* Bit 8 of days*/ - { + if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ gb->rtc_real.high |= 0x80; /* Overflow bit */ } gb->rtc_real.high ^= 1; diff --git a/OpenDialog/gtk.c b/OpenDialog/gtk.c index 7947ea2e..d9163fc3 100644 --- a/OpenDialog/gtk.c +++ b/OpenDialog/gtk.c @@ -88,8 +88,7 @@ char *do_open_rom_dialog(void) int res = gtk_dialog_run (dialog); char *ret = NULL; - if (res == GTK_RESPONSE_ACCEPT) - { + if (res == GTK_RESPONSE_ACCEPT) { char *filename; filename = gtk_file_chooser_get_filename(dialog); ret = strdup(filename); diff --git a/OpenDialog/windows.c b/OpenDialog/windows.c index 75fe7674..6bf9b894 100644 --- a/OpenDialog/windows.c +++ b/OpenDialog/windows.c @@ -17,8 +17,7 @@ char *do_open_rom_dialog(void) dialog.lpstrInitialDir = NULL; dialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; - if (GetOpenFileNameW(&dialog) == TRUE) - { + if (GetOpenFileNameW(&dialog) == TRUE) { char *ret = malloc(MAX_PATH * 4); WideCharToMultiByte(CP_UTF8, 0, filename, sizeof(filename), ret, MAX_PATH * 4, NULL, NULL); return ret; diff --git a/QuickLook/generator.m b/QuickLook/generator.m index 1aa0087f..c3c13dc9 100644 --- a/QuickLook/generator.m +++ b/QuickLook/generator.m @@ -79,7 +79,7 @@ static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder) } /* Mask it with the template (The middle part of the template image is transparent) */ - [effectiveTemplate drawInRect:(NSRect){{0,0},template.size}]; + [effectiveTemplate drawInRect:(NSRect){{0, 0}, template.size}]; } CGColorSpaceRelease(colorSpaceRef); diff --git a/QuickLook/main.c b/QuickLook/main.c index 8566e32b..1d1676ac 100644 --- a/QuickLook/main.c +++ b/QuickLook/main.c @@ -43,8 +43,8 @@ typedef struct __QuickLookGeneratorPluginType QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); -HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv); -void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID); +HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv); +void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID); ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); ULONG QuickLookGeneratorPluginRelease(void *thisInstance); @@ -77,11 +77,11 @@ QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFact QuickLookGeneratorPluginType *theNewInstance; theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType)); - memset(theNewInstance,0,sizeof(QuickLookGeneratorPluginType)); + memset(theNewInstance, 0, sizeof(QuickLookGeneratorPluginType)); /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */ theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct)); - memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl,sizeof(QLGeneratorInterfaceStruct)); + memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl, sizeof(QLGeneratorInterfaceStruct)); /* Retain and keep an open instance refcount for each factory. */ theNewInstance->factoryID = CFRetain(inFactoryID); @@ -110,7 +110,7 @@ void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInsta /* Free the instance structure */ free(thisInstance); - if (theFactoryID){ + if (theFactoryID) { CFPlugInRemoveInstanceForFactory(theFactoryID); CFRelease(theFactoryID); } @@ -121,13 +121,13 @@ void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInsta // ----------------------------------------------------------------------------- // Implementation of the IUnknown QueryInterface function. // -HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv) +HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv) { CFUUIDRef interfaceID; - interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid); + interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, iid); - if (CFEqual(interfaceID,kQLGeneratorCallbacksInterfaceID)){ + if (CFEqual(interfaceID, kQLGeneratorCallbacksInterfaceID)) { /* If the Right interface was requested, bump the ref count, * set the ppv parameter equal to the instance, and * return good status. @@ -138,7 +138,8 @@ HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *p *ppv = thisInstance; CFRelease(interfaceID); return S_OK; - }else{ + } + else { /* Requested interface unknown, bail with error. */ *ppv = NULL; CFRelease(interfaceID); @@ -168,10 +169,11 @@ ULONG QuickLookGeneratorPluginAddRef(void *thisInstance) ULONG QuickLookGeneratorPluginRelease(void *thisInstance) { ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1; - if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0){ + if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0) { DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance ); return 0; - }else{ + } + else { return ((QuickLookGeneratorPluginType*) thisInstance )->refCount; } } @@ -179,7 +181,7 @@ ULONG QuickLookGeneratorPluginRelease(void *thisInstance) // ----------------------------------------------------------------------------- // QuickLookGeneratorPluginFactory // ----------------------------------------------------------------------------- -void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID) +void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID) { QuickLookGeneratorPluginType *result; CFUUIDRef uuid; @@ -187,8 +189,8 @@ void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID) /* If correct type is being requested, allocate an * instance of kQLGeneratorTypeID and return the IUnknown interface. */ - if (CFEqual(typeID,kQLGeneratorTypeID)){ - uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID)); + if (CFEqual(typeID, kQLGeneratorTypeID)) { + uuid = CFUUIDCreateFromString(kCFAllocatorDefault, CFSTR(PLUGIN_ID)); result = AllocQuickLookGeneratorPluginType(uuid); CFRelease(uuid); return result; diff --git a/SDL/gui.c b/SDL/gui.c index 201452a3..5650d9b1 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -338,7 +338,7 @@ static void cycle_model_backwards(unsigned index) const char *current_model_string(unsigned index) { - return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance" , "Super Game Boy"} + return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance", "Super Game Boy"} [configuration.model]; } @@ -800,7 +800,7 @@ static void cycle_joypads(unsigned index) SDL_JoystickClose(joystick); joystick = NULL; } - if ((controller = SDL_GameControllerOpen(joypad_index))){ + if ((controller = SDL_GameControllerOpen(joypad_index))) { joystick = SDL_GameControllerGetJoystick(controller); } else { @@ -822,7 +822,7 @@ static void cycle_joypads_backwards(unsigned index) SDL_JoystickClose(joystick); joystick = NULL; } - if ((controller = SDL_GameControllerOpen(joypad_index))){ + if ((controller = SDL_GameControllerOpen(joypad_index))) { joystick = SDL_GameControllerGetJoystick(controller); } else { @@ -886,7 +886,7 @@ void connect_joypad(void) } } else if (!joystick && SDL_NumJoysticks()) { - if ((controller = SDL_GameControllerOpen(0))){ + if ((controller = SDL_GameControllerOpen(0))) { joystick = SDL_GameControllerGetJoystick(controller); } else { diff --git a/SDL/main.c b/SDL/main.c index 4b6afeaa..4ce2e597 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -149,8 +149,7 @@ static void open_menu(void) static void handle_events(GB_gameboy_t *gb) { SDL_Event event; - while (SDL_PollEvent(&event)) - { + while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pending_command = GB_SDL_QUIT_COMMAND; @@ -603,7 +602,7 @@ static bool get_arg_flag(const char *flag, int *argc, char **argv) int main(int argc, char **argv) { #ifdef _WIN32 - SetProcessDPIAware(); + SetProcessDPIAware(); #endif #define str(x) #x #define xstr(x) str(x) diff --git a/SDL/opengl_compat.h b/SDL/opengl_compat.h index 15b2a176..4b79b0c7 100644 --- a/SDL/opengl_compat.h +++ b/SDL/opengl_compat.h @@ -10,7 +10,7 @@ #define GL_COMPAT_WRAPPER(func) \ ({ extern typeof(func) *GL_COMPAT_NAME(func); \ -if(!GL_COMPAT_NAME(func)) GL_COMPAT_NAME(func) = SDL_GL_GetProcAddress(#func); \ +if (!GL_COMPAT_NAME(func)) GL_COMPAT_NAME(func) = SDL_GL_GetProcAddress(#func); \ GL_COMPAT_NAME(func); \ }) diff --git a/Tester/main.c b/Tester/main.c index e3b662ca..27250a6f 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -300,7 +300,7 @@ int main(int argc, char **argv) if (max_forks > 1) { while (current_forks >= max_forks) { int wait_out; - while(wait(&wait_out) == -1); + while (wait(&wait_out) == -1); current_forks--; } @@ -433,7 +433,7 @@ int main(int argc, char **argv) } #ifndef _WIN32 int wait_out; - while(wait(&wait_out) != -1); + while (wait(&wait_out) != -1); #endif return 0; } diff --git a/Windows/stdio.h b/Windows/stdio.h index 0595b016..ef21ea48 100755 --- a/Windows/stdio.h +++ b/Windows/stdio.h @@ -24,7 +24,8 @@ static inline int vasprintf(char **str, const char *fmt, va_list args) #endif /* This code is public domain -- Will Hartung 4/9/09 */ -static inline size_t getline(char **lineptr, size_t *n, FILE *stream) { +static inline size_t getline(char **lineptr, size_t *n, FILE *stream) +{ char *bufptr = NULL; char *p = bufptr; size_t size; diff --git a/libretro/libretro.c b/libretro/libretro.c index 8cd13244..2cb3ef7f 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -195,7 +195,7 @@ static bool serial_end2(GB_gameboy_t *gb) static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { - return r<<16|g<<8|b; + return r <<16 | g <<8 | b; } static retro_environment_t environ_cb; @@ -323,15 +323,13 @@ static struct retro_input_descriptor descriptors_4p[] = { static void set_link_cable_state(bool state) { - if (state && emulated_devices == 2) - { + if (state && emulated_devices == 2) { GB_set_serial_transfer_bit_start_callback(&gameboy[0], serial_start1); GB_set_serial_transfer_bit_end_callback(&gameboy[0], serial_end1); GB_set_serial_transfer_bit_start_callback(&gameboy[1], serial_start2); GB_set_serial_transfer_bit_end_callback(&gameboy[1], serial_end2); } - else if (!state) - { + else if (!state) { GB_set_serial_transfer_bit_start_callback(&gameboy[0], NULL); GB_set_serial_transfer_bit_end_callback(&gameboy[0], NULL); GB_set_serial_transfer_bit_start_callback(&gameboy[1], NULL); @@ -375,8 +373,7 @@ static void init_for_current_model(unsigned id) /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); - if (emulated_devices == 2) - { + if (emulated_devices == 2) { GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); if (link_cable_emulation) set_link_cable_state(true); @@ -428,17 +425,17 @@ static void init_for_current_model(unsigned id) descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); descs[8].start = 0xFE00; descs[8].len = 0x00A0; - descs[8].select= 0xFFFFFF00; + descs[8].select = 0xFFFFFF00; descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ descs[9].start = 0x10000; descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ - descs[9].select= 0xFFFF0000; + descs[9].select = 0xFFFF0000; descs[10].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IO, &size, &bank); descs[10].start = 0xFF00; descs[10].len = 0x0080; - descs[10].select= 0xFFFFFF00; + descs[10].select = 0xFFFFFF00; struct retro_memory_map mmaps; mmaps.descriptors = descs; @@ -446,8 +443,7 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); /* Let's be extremely nitpicky about how devices and descriptors are set */ - if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) - { + if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { static const struct retro_controller_info ports[] = { { controllers_sgb, 1 }, { controllers_sgb, 1 }, @@ -458,8 +454,7 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_4p); } - else if (emulated_devices == 1) - { + else if (emulated_devices == 1) { static const struct retro_controller_info ports[] = { { controllers, 1 }, { NULL, 0 }, @@ -467,8 +462,7 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_1p); } - else - { + else { static const struct retro_controller_info ports[] = { { controllers, 1 }, { controllers, 1 }, @@ -483,12 +477,10 @@ static void init_for_current_model(unsigned id) static void check_variables() { struct retro_variable var = {0}; - if (emulated_devices == 1) - { + if (emulated_devices == 1) { var.key = "sameboy_color_correction_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); else if (strcmp(var.value, "correct curves") == 0) @@ -503,8 +495,7 @@ static void check_variables() var.key = "sameboy_high_pass_filter_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); else if (strcmp(var.value, "accurate") == 0) @@ -515,8 +506,7 @@ static void check_variables() var.key = "sameboy_model"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum model new_model = model[0]; if (strcmp(var.value, "Game Boy") == 0) new_model = MODEL_DMG; @@ -531,8 +521,7 @@ static void check_variables() else new_model = MODEL_AUTO; - if (new_model != model[0]) - { + if (new_model != model[0]) { geometry_updated = true; model[0] = new_model; init_for_current_model(0); @@ -541,20 +530,17 @@ static void check_variables() var.key = "sameboy_border"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "enabled") == 0) sgb_border = 1; else if (strcmp(var.value, "disabled") == 0) sgb_border = 0; } } - else - { + else { var.key = "sameboy_color_correction_mode_1"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); else if (strcmp(var.value, "correct curves") == 0) @@ -569,8 +555,7 @@ static void check_variables() var.key = "sameboy_color_correction_mode_2"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_DISABLED); else if (strcmp(var.value, "correct curves") == 0) @@ -586,8 +571,7 @@ static void check_variables() var.key = "sameboy_high_pass_filter_mode_1"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); else if (strcmp(var.value, "accurate") == 0) @@ -598,8 +582,7 @@ static void check_variables() var.key = "sameboy_high_pass_filter_mode_2"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_OFF); else if (strcmp(var.value, "accurate") == 0) @@ -610,8 +593,7 @@ static void check_variables() var.key = "sameboy_model_1"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum model new_model = model[0]; if (strcmp(var.value, "Game Boy") == 0) new_model = MODEL_DMG; @@ -626,8 +608,7 @@ static void check_variables() else new_model = MODEL_AUTO; - if (model[0] != new_model) - { + if (model[0] != new_model) { model[0] = new_model; init_for_current_model(0); } @@ -635,8 +616,7 @@ static void check_variables() var.key = "sameboy_model_2"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum model new_model = model[1]; if (strcmp(var.value, "Game Boy") == 0) new_model = MODEL_DMG; @@ -651,8 +631,7 @@ static void check_variables() else new_model = MODEL_AUTO; - if (model[1] != new_model) - { + if (model[1] != new_model) { model[1] = new_model; init_for_current_model(1); } @@ -660,8 +639,7 @@ static void check_variables() var.key = "sameboy_screen_layout"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "top-down") == 0) screen_layout = LAYOUT_TOP_DOWN; else @@ -672,8 +650,7 @@ static void check_variables() var.key = "sameboy_link"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool tmp = link_cable_emulation; if (strcmp(var.value, "enabled") == 0) link_cable_emulation = true; @@ -687,8 +664,7 @@ static void check_variables() var.key = "sameboy_audio_output"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "Game Boy #1") == 0) audio_out = GB_1; else @@ -753,28 +729,25 @@ void retro_get_system_av_info(struct retro_system_av_info *info) struct retro_game_geometry geom; struct retro_system_timing timing = { GB_get_usual_frame_rate(&gameboy[0]), AUDIO_FREQUENCY }; - if (emulated_devices == 2) - { + if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { geom.base_width = VIDEO_WIDTH; geom.base_height = VIDEO_HEIGHT * emulated_devices; geom.aspect_ratio = (double)VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); - }else if (screen_layout == LAYOUT_LEFT_RIGHT) { + } + else if (screen_layout == LAYOUT_LEFT_RIGHT) { geom.base_width = VIDEO_WIDTH * emulated_devices; geom.base_height = VIDEO_HEIGHT; geom.aspect_ratio = ((double)VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; } } - else - { - if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) - { + else { + if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) { geom.base_width = SGB_VIDEO_WIDTH; geom.base_height = SGB_VIDEO_HEIGHT; geom.aspect_ratio = (double)SGB_VIDEO_WIDTH / SGB_VIDEO_HEIGHT; } - else - { + else { geom.base_width = VIDEO_WIDTH; geom.base_height = VIDEO_HEIGHT; geom.aspect_ratio = (double)VIDEO_WIDTH / VIDEO_HEIGHT; @@ -848,13 +821,11 @@ void retro_run(void) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(); - if (emulated_devices == 2) - { + if (emulated_devices == 2) { GB_update_keys_status(&gameboy[0], 0); GB_update_keys_status(&gameboy[1], 1); } - else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) - { + else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { for (unsigned i = 0; i < 4; i++) GB_update_keys_status(&gameboy[0], i); } @@ -863,8 +834,7 @@ void retro_run(void) vblank1_occurred = vblank2_occurred = false; signed delta = 0; - if (emulated_devices == 2) - { + if (emulated_devices == 2) { while (!vblank1_occurred || !vblank2_occurred) { if (delta >= 0) { delta -= GB_run(&gameboy[0]); @@ -874,16 +844,15 @@ void retro_run(void) } } } - else - { + else { GB_run_frame(&gameboy[0]); } - if (emulated_devices == 2) - { + if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); - }else if (screen_layout == LAYOUT_LEFT_RIGHT) { + } + else if (screen_layout == LAYOUT_LEFT_RIGHT) { /* use slow memcpy method for now */ for (int index = 0; index < emulated_devices; index++) { for (int y = 0; y < VIDEO_HEIGHT; y++) { @@ -896,8 +865,7 @@ void retro_run(void) video_cb(frame_buf_copy, VIDEO_WIDTH * emulated_devices, VIDEO_HEIGHT, VIDEO_WIDTH * emulated_devices * sizeof(uint32_t)); } } - else - { + else { if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) { if (sgb_border == 1) video_cb(frame_buf, SGB_VIDEO_WIDTH, SGB_VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); @@ -920,12 +888,11 @@ bool retro_load_game(const struct retro_game_info *info) environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); check_variables(); - frame_buf = (uint32_t*)malloc(SGB_VIDEO_PIXELS* emulated_devices * sizeof(uint32_t)); + frame_buf = (uint32_t*)malloc(SGB_VIDEO_PIXELS *emulated_devices * sizeof(uint32_t)); memset(frame_buf, 0, SGB_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; - if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) - { + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); return false; } @@ -933,11 +900,9 @@ bool retro_load_game(const struct retro_game_info *info) auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - for (int i = 0; i < emulated_devices; i++) - { + for (int i = 0; i < emulated_devices; i++) { init_for_current_model(i); - if (GB_load_rom(&gameboy[i],info->path)) - { + if (GB_load_rom(&gameboy[i], info->path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM at %s\n", info->path); return false; } @@ -984,8 +949,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, memset(frame_buf_copy, 0, emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; - if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) - { + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); return false; } @@ -993,11 +957,9 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - for (int i = 0; i < emulated_devices; i++) - { + for (int i = 0; i < emulated_devices; i++) { init_for_current_model(i); - if (GB_load_rom(&gameboy[i], info[i].path)) - { + if (GB_load_rom(&gameboy[i], info[i].path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); return false; } @@ -1063,8 +1025,7 @@ bool retro_serialize(void *data, size_t size) bool retro_unserialize(const void *data, size_t size) { - for (int i = 0; i < emulated_devices; i++) - { + for (int i = 0; i < emulated_devices; i++) { size_t state_size = GB_get_save_state_size(&gameboy[i]); if (state_size > size) { return false; @@ -1085,10 +1046,8 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { void *data = NULL; - if (emulated_devices == 1) - { - switch(type) - { + if (emulated_devices == 1) { + switch (type) { case RETRO_MEMORY_SYSTEM_RAM: data = gameboy[0].ram; break; @@ -1102,7 +1061,7 @@ void *retro_get_memory_data(unsigned type) data = gameboy[0].vram; break; case RETRO_MEMORY_RTC: - if(gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) data = GB_GET_SECTION(&gameboy[0], rtc); else data = NULL; @@ -1111,10 +1070,8 @@ void *retro_get_memory_data(unsigned type) break; } } - else - { - switch (type) - { + else { + switch (type) { case RETRO_MEMORY_GAMEBOY_1_SRAM: if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) data = gameboy[0].mbc_ram; @@ -1128,13 +1085,13 @@ void *retro_get_memory_data(unsigned type) data = NULL; break; case RETRO_MEMORY_GAMEBOY_1_RTC: - if(gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) data = GB_GET_SECTION(&gameboy[0], rtc); else data = NULL; break; case RETRO_MEMORY_GAMEBOY_2_RTC: - if(gameboy[1].cartridge_type->has_battery) + if (gameboy[1].cartridge_type->has_battery) data = GB_GET_SECTION(&gameboy[1], rtc); else data = NULL; @@ -1150,10 +1107,8 @@ void *retro_get_memory_data(unsigned type) size_t retro_get_memory_size(unsigned type) { size_t size = 0; - if (emulated_devices == 1) - { - switch(type) - { + if (emulated_devices == 1) { + switch (type) { case RETRO_MEMORY_SYSTEM_RAM: size = gameboy[0].ram_size; break; @@ -1167,7 +1122,7 @@ size_t retro_get_memory_size(unsigned type) size = gameboy[0].vram_size; break; case RETRO_MEMORY_RTC: - if(gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) size = GB_SECTION_SIZE(rtc); else size = 0; @@ -1176,10 +1131,8 @@ size_t retro_get_memory_size(unsigned type) break; } } - else - { - switch (type) - { + else { + switch (type) { case RETRO_MEMORY_GAMEBOY_1_SRAM: if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) size = gameboy[0].mbc_ram_size; @@ -1193,11 +1146,11 @@ size_t retro_get_memory_size(unsigned type) size = 0; break; case RETRO_MEMORY_GAMEBOY_1_RTC: - if(gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) size = GB_SECTION_SIZE(rtc); break; case RETRO_MEMORY_GAMEBOY_2_RTC: - if(gameboy[1].cartridge_type->has_battery) + if (gameboy[1].cartridge_type->has_battery) size = GB_SECTION_SIZE(rtc); break; default: From 634dcefd01f67577830755c5344cc118ee6708b9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 24 Apr 2020 20:44:25 +0300 Subject: [PATCH 1042/1216] Typo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ba568ffe..5a2df1d4 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ ifeq ($(PLATFORM),Darwin) OPEN_DIALOG = OpenDialog/cocoa.m endif -# These myst come before the -Wno- flags +# These must come before the -Wno- flags CFLAGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option CFLAGS += -Wpartial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context From 198942b2734eaa8463fadab376fa19b55e025be3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 24 Apr 2020 21:00:30 +0300 Subject: [PATCH 1043/1216] Truly fix #249, fix #251 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5a2df1d4..8792f789 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ endif # These must come before the -Wno- flags CFLAGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option -CFLAGS += -Wpartial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context +CFLAGS += -Werror=partial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES From 8ac029d3feab5923fed75e8897f30065f6ad7806 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 24 Apr 2020 21:06:44 +0300 Subject: [PATCH 1044/1216] Truly truly fix #249, fix #251 --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8792f789..e30b3f66 100644 --- a/Makefile +++ b/Makefile @@ -87,11 +87,11 @@ ifeq ($(PLATFORM),Darwin) OPEN_DIALOG = OpenDialog/cocoa.m endif +# This must come first because GCC is special +CFLAGS += -Werror=partial-availability # These must come before the -Wno- flags CFLAGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option - -CFLAGS += -Werror=partial-availability -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context - +CFLAGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES ifeq (,$(PKG_CONFIG)) From ddad913e06fb51e80957177d404fef3d1d3b5d6c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 24 Apr 2020 21:59:51 +0300 Subject: [PATCH 1045/1216] OK this time it will work. --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e30b3f66..33b8990c 100644 --- a/Makefile +++ b/Makefile @@ -87,11 +87,15 @@ ifeq ($(PLATFORM),Darwin) OPEN_DIALOG = OpenDialog/cocoa.m endif -# This must come first because GCC is special -CFLAGS += -Werror=partial-availability # These must come before the -Wno- flags CFLAGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option CFLAGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context + +# Only add this flag if the compiler supports it +ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0) +CFLAGS += -Wpartial-availability +endif + CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES ifeq (,$(PKG_CONFIG)) From 2df6d266bd6fc993f540f230c4c0ffa7b04ee364 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 13:50:35 +0300 Subject: [PATCH 1046/1216] Add a GitHub action to avoid breaking builds --- .github/actions/install_deps.sh | 23 +++++++++++++++++++++++ .github/workflows/buildability.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/actions/install_deps.sh create mode 100644 .github/workflows/buildability.yml diff --git a/.github/actions/install_deps.sh b/.github/actions/install_deps.sh new file mode 100644 index 00000000..1c9749ef --- /dev/null +++ b/.github/actions/install_deps.sh @@ -0,0 +1,23 @@ +case `echo $1 | cut -d '-' -f 1` in + ubuntu) + sudo apt-get -qq update + sudo apt-get install -yq bison libpng-dev pkg-config libsdl2-dev + ( + cd `mktemp -d` + curl -L https://github.com/rednex/rgbds/archive/v0.4.0.zip > rgbds.zip + unzip rgbds.zip + cd rgbds-* + make -sj + sudo make install + cd .. + rm -rf * + ) + ;; + macos) + brew install rgbds sdl2 + ;; + *) + echo "Unsupported OS" + exit 1 + ;; +esac \ No newline at end of file diff --git a/.github/workflows/buildability.yml b/.github/workflows/buildability.yml new file mode 100644 index 00000000..e5f4a30e --- /dev/null +++ b/.github/workflows/buildability.yml @@ -0,0 +1,28 @@ +name: "Bulidability" +on: push + +jobs: + buildability: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-16.04, macos-latest] + cc: [gcc, clang] + include: + - os: macos-latest + cc: clang + extra_target: cocoa + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Install deps + shell: bash + run: | + ./.github/actions/install_deps.sh ${{ matrix.os }} + - name: Build + run: | + make sdl ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} + - name: Upload binaries + uses: actions/upload-artifact@v1 + with: + name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }} + path: build/bin \ No newline at end of file From 385cd1b8c722a89c43fdf332aa48d64a412d1e6e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 13:52:18 +0300 Subject: [PATCH 1047/1216] Fix chmod --- .github/actions/install_deps.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/actions/install_deps.sh diff --git a/.github/actions/install_deps.sh b/.github/actions/install_deps.sh old mode 100644 new mode 100755 From 17c97c3c2b140554a50844eeb8b3e5d268608832 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 13:59:31 +0300 Subject: [PATCH 1048/1216] Use brew's SDL2 on macOS --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 33b8990c..d8a1a3ca 100644 --- a/Makefile +++ b/Makefile @@ -125,7 +125,6 @@ SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> $(NULL)) CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 GL_LDFLAGS := -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations From 7e908fef0ee5c3cb1c410a178744d74caa55d458 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 14:04:51 +0300 Subject: [PATCH 1049/1216] The macOS environment doesn't come with GCC, it'll just test Clang again --- .github/workflows/buildability.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/buildability.yml b/.github/workflows/buildability.yml index e5f4a30e..78df953b 100644 --- a/.github/workflows/buildability.yml +++ b/.github/workflows/buildability.yml @@ -11,6 +11,9 @@ jobs: - os: macos-latest cc: clang extra_target: cocoa + exclude: + - os: macos-latest + cc: gcc runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 From 097705456c95e107a906fbbc0ffc5f61dacf5a38 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 14:05:35 +0300 Subject: [PATCH 1050/1216] Show compiler version --- .github/workflows/buildability.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buildability.yml b/.github/workflows/buildability.yml index 78df953b..639a2506 100644 --- a/.github/workflows/buildability.yml +++ b/.github/workflows/buildability.yml @@ -23,7 +23,7 @@ jobs: ./.github/actions/install_deps.sh ${{ matrix.os }} - name: Build run: | - make sdl ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} + ${{ matrix.cc }} -v; make sdl ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} - name: Upload binaries uses: actions/upload-artifact@v1 with: From c2a395006ea2421ee574ba1d63ec21f48411a35e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 14:45:52 +0300 Subject: [PATCH 1051/1216] Update docs --- README.md | 2 +- build-faq.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 91e0bf6c..d626bbee 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ SameBoy requires the following tools and libraries to build: * clang * make * Cocoa port: OS X SDK and Xcode command line tools - * SDL port: SDL2.framework (OS X) or libsdl2 (Other platforms) + * SDL port: libsdl2 * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation On Windows, SameBoy also requires: diff --git a/build-faq.md b/build-faq.md index e2192f57..2b056dd2 100644 --- a/build-faq.md +++ b/build-faq.md @@ -4,4 +4,4 @@ When building on macOS, the build system will make a native Cocoa app by default # Attempting to build the SDL frontend on macOS fails on linking -SameBoy on macOS expects you to have the SDL2 framework installed as a framework. You can find the SDL2 binaries on the [SDL homepage](https://www.libsdl.org/download-2.0.php). Mount the DMG and copy SDL2.framework to `/Library/Frameworks/`. +SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. \ No newline at end of file From 1e7737a23943a2076ee4ab50268055696a28ba86 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 14:46:01 +0300 Subject: [PATCH 1052/1216] Limit unroll to GCC 8 --- Core/gb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/gb.h b/Core/gb.h index 01b79e8b..703a4894 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -35,7 +35,7 @@ #ifdef GB_INTERNAL #if __clang__ #define UNROLL _Pragma("unroll") -#elif __GNUC__ +#elif __GNUC__ >= 8 #define UNROLL _Pragma("GCC unroll 8") #else #define UNROLL From c62704e26b3d34144cf86f2f0eddd7d3404b5655 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 14:51:17 +0300 Subject: [PATCH 1053/1216] Minor fix for GCC's LTO --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index d8a1a3ca..1ec1fd93 100644 --- a/Makefile +++ b/Makefile @@ -145,6 +145,7 @@ endif ifneq ($(PLATFORM),windows32) LDFLAGS += -flto CFLAGS += -flto +LDFLAGS += -DGB_INTERNAL # For GCC's LTO endif else From 66112f493038f87cc6b96bb419cf7891ab96b562 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 14:55:51 +0300 Subject: [PATCH 1054/1216] That wasn't enough to fix it, I'll just disable this warning --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1ec1fd93..8f8ea810 100644 --- a/Makefile +++ b/Makefile @@ -145,7 +145,7 @@ endif ifneq ($(PLATFORM),windows32) LDFLAGS += -flto CFLAGS += -flto -LDFLAGS += -DGB_INTERNAL # For GCC's LTO +LDFLAGS += -Wno-lto-type-mismatch # For GCC's LTO endif else From bb5c9f7fc64bb816f213f0e73a9c6e3ba51ae647 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 15:12:10 +0300 Subject: [PATCH 1055/1216] Fix libretro build --- Core/cheats.h | 4 ++++ Core/debugger.h | 4 ++-- Core/gb.c | 14 ++++++++------ Core/timing.c | 2 +- libretro/Makefile | 4 ++-- libretro/Makefile.common | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Core/cheats.h b/Core/cheats.h index 13b7d7b4..cf8aa20d 100644 --- a/Core/cheats.h +++ b/Core/cheats.h @@ -17,8 +17,12 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path); int GB_save_cheats(GB_gameboy_t *gb, const char *path); #ifdef GB_INTERNAL +#ifdef GB_DISABLE_CHEATS +#define GB_apply_cheat(...) +#else void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); #endif +#endif typedef struct { size_t size; diff --git a/Core/debugger.h b/Core/debugger.h index 2906ad9f..4868df31 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -7,7 +7,7 @@ #ifdef GB_INTERNAL -#ifdef DISABLE_DEBUGGER +#ifdef GB_DISABLE_DEBUGGER #define GB_debugger_run(gb) (void)0 #define GB_debugger_handle_async_commands(gb) (void)0 #define GB_debugger_ret_hook(gb) (void)0 @@ -22,7 +22,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb); void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); -#endif /* DISABLE_DEBUGGER */ +#endif /* GB_DISABLE_DEBUGGER */ #endif #ifdef GB_INTERNAL diff --git a/Core/gb.c b/Core/gb.c index f6174b91..2b22f9d1 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -13,7 +13,7 @@ #include "gb.h" -#ifdef DISABLE_REWIND +#ifdef GB_DISABLE_REWIND #define GB_rewind_free(...) #define GB_rewind_push(...) #endif @@ -57,7 +57,7 @@ void GB_log(GB_gameboy_t *gb, const char *fmt, ...) va_end(args); } -#ifndef DISABLE_DEBUGGER +#ifndef GB_DISABLE_DEBUGGER static char *default_input_callback(GB_gameboy_t *gb) { char *expression = NULL; @@ -148,7 +148,7 @@ void GB_init(GB_gameboy_t *gb, GB_model_t model) gb->vram = malloc(gb->vram_size = 0x2000); } -#ifndef DISABLE_DEBUGGER +#ifndef GB_DISABLE_DEBUGGER gb->input_callback = default_input_callback; gb->async_input_callback = default_async_input_callback; #endif @@ -193,13 +193,15 @@ void GB_free(GB_gameboy_t *gb) if (gb->nontrivial_jump_state) { free(gb->nontrivial_jump_state); } -#ifndef DISABLE_DEBUGGER +#ifndef GB_DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); #endif GB_rewind_free(gb); +#ifndef GB_DISABLE_CHEATS while (gb->cheats) { GB_remove_cheat(gb, gb->cheats[0]); } +#endif memset(gb, 0, sizeof(*gb)); } @@ -643,7 +645,7 @@ void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback) void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) { -#ifndef DISABLE_DEBUGGER +#ifndef GB_DISABLE_DEBUGGER if (gb->input_callback == default_input_callback) { gb->async_input_callback = NULL; } @@ -653,7 +655,7 @@ void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) { -#ifndef DISABLE_DEBUGGER +#ifndef GB_DISABLE_DEBUGGER gb->async_input_callback = callback; #endif } diff --git a/Core/timing.c b/Core/timing.c index 9eb9c916..8fee590a 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -10,7 +10,7 @@ static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; -#ifndef DISABLE_TIMEKEEPING +#ifndef GB_DISABLE_TIMEKEEPING static int64_t get_nanoseconds(void) { #ifndef _WIN32 diff --git a/libretro/Makefile b/libretro/Makefile index 75ddfc6c..7e198936 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -260,7 +260,7 @@ endif include Makefile.common -OBJECTS := $(patsubst %.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) +OBJECTS := $(patsubst $(CORE_DIR)/%.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) OBJOUT = -o LINKOUT = -o @@ -306,7 +306,7 @@ else $(LD) $(fpic) $(SHARED) $(INCFLAGS) $(LINKOUT)$@ $(OBJECTS) $(LDFLAGS) endif -$(CORE_DIR)/build/obj/%_libretro.c.o: %.c +$(CORE_DIR)/build/obj/%_libretro.c.o: $(CORE_DIR)/%.c -@$(MKDIR) -p $(dir $@) $(CC) -c $(OBJOUT)$@ $< $(CFLAGS) $(fpic) -DGB_INTERNAL diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 3bf1783f..947b14ca 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -20,7 +20,7 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/libretro/sgb2_boot.c \ $(CORE_DIR)/libretro/libretro.c -CFLAGS += -DDISABLE_TIMEKEEPING -DDISABLE_REWIND -DDISABLE_DEBUGGER +CFLAGS += -DGB_DISABLE_TIMEKEEPING -DGB_DISABLE_REWIND -DGB_DISABLE_DEBUGGER -DGB_DISABLE_CHEATS SOURCES_CXX := From 8e702f14526cdc392c3ac6f720b3880b35d04747 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 15:13:04 +0300 Subject: [PATCH 1056/1216] Also test libretro's buildability --- .github/workflows/buildability.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buildability.yml b/.github/workflows/buildability.yml index 639a2506..7cd52980 100644 --- a/.github/workflows/buildability.yml +++ b/.github/workflows/buildability.yml @@ -23,7 +23,7 @@ jobs: ./.github/actions/install_deps.sh ${{ matrix.os }} - name: Build run: | - ${{ matrix.cc }} -v; make sdl ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} + ${{ matrix.cc }} -v; make sdl libretro ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} - name: Upload binaries uses: actions/upload-artifact@v1 with: From bf678113923194303320be9297872737a038fb6d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 16:59:47 +0300 Subject: [PATCH 1057/1216] Sanity test against a few test ROMs --- .github/actions/LICENSE | 25 +++++++++++++++ .github/actions/cgb-acid2.gbc | Bin 0 -> 32768 bytes .github/actions/cgb_sound.gb | Bin 0 -> 65536 bytes .github/actions/dmg-acid2.gb | Bin 0 -> 32768 bytes .github/actions/dmg_sound-2.gb | Bin 0 -> 65536 bytes .github/actions/oam_bug-2.gb | Bin 0 -> 65536 bytes .github/actions/sanity_tests.sh | 29 ++++++++++++++++++ .../{buildability.yml => sanity.yml} | 8 +++-- 8 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 .github/actions/LICENSE create mode 100644 .github/actions/cgb-acid2.gbc create mode 100644 .github/actions/cgb_sound.gb create mode 100644 .github/actions/dmg-acid2.gb create mode 100755 .github/actions/dmg_sound-2.gb create mode 100755 .github/actions/oam_bug-2.gb create mode 100755 .github/actions/sanity_tests.sh rename .github/workflows/{buildability.yml => sanity.yml} (70%) diff --git a/.github/actions/LICENSE b/.github/actions/LICENSE new file mode 100644 index 00000000..8c295a2b --- /dev/null +++ b/.github/actions/LICENSE @@ -0,0 +1,25 @@ +Blargg's Test ROMs by Shay Green + +Acid2 tests by Matt Currie under MIT: + +MIT License + +Copyright (c) 2020 Matt Currie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/.github/actions/cgb-acid2.gbc b/.github/actions/cgb-acid2.gbc new file mode 100644 index 0000000000000000000000000000000000000000..5f71bd36060b46eefcf7785f81ce16a5a6c5fc67 GIT binary patch literal 32768 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 awh*uoun@2iun@2iun@2iun_nZ1pYrO(txG_ literal 0 HcmV?d00001 diff --git a/.github/actions/oam_bug-2.gb b/.github/actions/oam_bug-2.gb new file mode 100755 index 0000000000000000000000000000000000000000..a3f55af1049fff765424216fed8c6553c2262ecd GIT binary patch literal 65536 zcmeI54|EjOy~k(sX9-IZ5&<0p%7kQ-6^I0=Az(W4CjzA)AO+8>N7+EK7+iNTe@M_I z1_-HbPy6V3wgs(}@{Z5b_s*-Rt(J#RGjn&NYrIwzdFxYro7(U+PdHiaD2cfHe)rDK z{z*2`!+{>``R!pcb7$`F{`tmbmI!6C+%>MYD$5h zE4;`5wrXN+r~oQ}3ZMe004jhApaQ4>Du4>00;m8gfC``jr~oQ}3ZMe004jhApaQ4> zDu4>00;m8ga5)rMkt=k~ylR^C#pEpM^JXG+$wHvWC$|TR_+Jfyo0!XH3$N&kHTe%PyVy=ggk_ZG(!>Ybwq8 zb7s2q1v|`6SLb3yVHA2 zR%y^(6gCSzJ8datyojPu+N(dE0TSfN7XnkEU)Mv6rb27dq@l9KWkMR%BYn1@xmWRV zVJ_&MXPtyhDbi5(;w)QVBqtbsBEB^w_|N3LGO%Xc8=@t8?EiEitUVP72PVp;0YNqe z*5nAmK-lM{`}3jJ$xvV}*8`4ChrUeZdJM`!PqSpIP<*7ZXfC^5{T*;a`uBwg_EiM- z_1w~POGUpR4eaY#aHjOL#dlmU+Ov$ z$x&77tf`h7YU&&6EkZ9lUF)i@-C0}RSmku?k{s^3$m#psbv06TO#>Lz-w5YuRX%m( zgIc+A5bm82n3SC^{~$0~lT&GDp&Vudf->Q=qz?x=`rG~F6YF`QwO@FlrQh_z+xaOk zn0_VX3VxbfXfg}A?O{0R&CVg~^6wZDUT_bYUf4GHfR@X%0(0}?*R{C!?A8Y+0q4#+yn;p6Dl_Y2;oY$b&QaaPAgJnKF>jBv4Mq7~C-EL!< zjjZHb#i!CsFHA2LUN^n*ionOv|LDW#GoIl6iVi5Pg*Fwovg5o#7Z4jy2DC$ zTH!AHG}>Y{Y~V2H)f9TWRq<)fVxP84?A0C+Piqf~?`SRJ+ghu5Li095wq1(&vgTd- zUGWpmTe(L3m@0)@&jQ+M4W5}8vXrNEkLxZa`-?TNn5lW;#DwhXV<(SVvn}+fwYwA= z1FbgCN&8vph23kLA-T)f+ou&2%!EPE+P~Z1(!a+agq!uERZI!P9r}HtFES$6Rn^zm zR13YbKt3uOYWG*z(!Xu{K%B_#P^H^dV_ezocMt9HZ)M)}U{o{kU}1!0!K$vIh1XlQIVO{!vOL&lS>=B9d7&6Z*@B z*5|%Y=L&%pXZmd^W}&%X@SEDk<+uB3ak1$kfsMrCVi@-0V4DbA7`CGL?@#*PEV`zc zK3Pmp@W;(vb>HSKjoU2j*?F#T#A8CU3V64nN{tQ`(VJj+o}NYCnx#SKf+3S%AJxnG z0}EyF(XOYY0x0sA{Dx1+Op}EfS<;1cX^`jmj;o=j&Q;}S6e5NjogJ_X~%8ecI4o(7MCFt)L8^-Mw({I{dSO zO}#$4vzXjv^O~;zBDMKxn|Yuzv|rK`A6;qV-*cXiiecQnTbwWK$rh+&%NKr>9frL; zx&NRaDtOc{pYMOnPk(9?zn?E0FsC)mmM#>g!{ZDR-eb;z2d7qk2sR=4mM)!!FV3~H z0Chl~wZ7c9Y-YRHoRK%_w=-ryIZ8Tw-)r<2#q!Sriy_A{`7ePLfylBpzm##*WlYeDSFt><;!zq^9yE}zxWz)*tj+=$d%uJpydm7kH%NBN& z!@hinAUG<*6%M#0ifvG!{wXcB!$DglB?xxAkYdh+0v8Jv4%beHqe2jwz1Rk6cA-sZ z%M>%sa6WTdg|iLnYO}YPS-Qe*Z(;e{Shmtq$nRkP*j_egma}@KGy8xY*z5pX=qmfP3Ek&Hr@Fhl1;@^|HZWxEg-->q4>IudPG=cU zLzS%l3RVt?$Udwd`0)B16&0cYWt|G0gYp~|tm*7JYl5x|)OS5MyIxq#dT&k**FdFAoZ2X{HCH*}#MQ$P~ziG^<9| z_t1X<6gbtw#wr&;KM(DJo6N^gwkVb|J6=8LvO^Rzp;?g6{>dSI=)$oez!wbr&J;sF z-{trpE|!T3paQ4>Du4>00;m8gfC``jr~oQ}3ZMe004jhAd~*~a|5xzMY3yZaDVaqE z7GLuQpJy2I$-4uKeKfTT{D+G*c|Dz@$Q~-ew3D3KmoJ2xDrWbz+6H^>vuV9>3eLRP z#ZR4wQ-6I*Kc%&vnJ8KQU`wS$X{8@4LG49bM%$@OI20%J_0b-sqy#1qdxPBa1XxnY z36m-fsyI94dyRFjTGuvd^|DII?UL#ns_Gi*r3SZKa#l6eNVRZkYn97c%O^2wf0VRpsTBq>f%rO;sICz$PzfeWV~06ht0T$yt(Q))MOawq(p&f=f^o0PF;EdwMnYZ)N$)QmoBInF~fJk;!=KY$_Hqs%RFZmr&2zst3iPh2_` zHgQ>@!2CXpD9XkV{UJPt#XKV#yuM~R=fG*W!xat*)d!zM0! zlq3_EM~IJoh;Ykg_=3SAQ2|r{6+i`00aO4LKm||%RN#tHfHZo>;Qw7J@c(x8BKbdy zw9+yX<^M9m`F{!-$^Tizm(dZ1&{ZN{owB-8s=Re};qqHcOQl89cWzq-Nr3$&YOWBP zMDGH8V%bs?vsK<2pKWS1TUs>R^5w%y3&)j~4%wD2OQ>||GG3`k2&G0#GefqOs}r)V zT+OqAFQg!vZ5(9Vur49nhIKsKTp=`npu;_|Z`)urcZQL>k~eYP+I7$rJ$Lv^WmxWO z#v8eHdDd;*urium2ziHP&$A@97*b1S+g7fgb%W9BnZqq6B)2GuS}clLkoO5VVQ6v0 z4`ZU8s(clNQF9F+HSF4=u2(ZXK5~;ci6;d{R?L$ERuKQq&TvmklM3O zq$nzn|0eOCl=a?3Ps&lk;=c)t|5g(`DMRWgo)maG^!RTT;=c*Rc#}9!O1~$;lQM-w zJt=S{-jk9=Km@Rlc~bg4BRnb4b=G~p!IPfclk%KOOFSgg1M%PLSe_J!de-B=ZR{15 z(LE_^y>y+I-sYtzc<1JoY^`dnuc_Aqz*QaquCn(eRQ8?(xOuoArQgH-C|`KMFi3Cr zk_}!604IhYWeSP+qZlbY0G!|PVSbb;B#9rT--CXXE9Ms)HUt$w1yBK002M$5PytjR zsRHC5#^V3G82{gPvHTwdgv*QZf1b|&gX&2B52`xV~R$5y$+Z5&o80Mm29txH#G%Z?K)C*9#{8n&1M7iFo&JD%5KGKoAs24!5 zT8Hor>t@a61%uzSjGe>-D*7#Y{_k)bobIzW8nx@3$GqbJ7rV0DsNMHv517$9tP z8Dt;ZUqZ%zLZu5MjIZx4Jl#t}Drfxn4aT<;x{A>65&C^XVL)os&KRHDz#(V+vaeu# z`$)!rf;0a11jb)DjPV~~jQ`~$%FL3k7f)i}m~Gm-HJ)hOe` zm3YPvsvrW`#~A<3k&F*rXWi!;Jk7}&|526ZddL8oNuL;#@pBBu&l#KX7kFv8mzH_y zAAs=()%hhZw`+48=Re9hA4X!BjYP^ukYV?92aJPsk(b;IoF7yT&L33cIp0We z&VP{K^jFe`gZ@uU6TjN<_4#=wvQ!Z>(>Sw$#8fX*H6gwhj_o-F0=1I~r=; zE&$?xR)Dni*nbl}tpJZgOY>MuC((a};BhEwT56=Is)ny!!c@J4-|HnzO^cP_tom9_ zC`l-wAU(b}RSsA&3f^M)S_R!`^o86Mp>JOVw6~JT(W`Joe(p&!tqwB8kQ;;(mG z1BV>BfBO|LD7-cD1qG2K_vwTe6rLXTg2Fh4+~XK>uTMno^`jv-j46)X69BoZ0lA&= z$h|5Nxt%fOhAZ*NU9Ex$U>`&7RU?rby3V@KH+X=OBX^ccI|$hd$SsbE+z)#q)9Md< z#++7v%0rKO=ua{UUDfdm4|Kxo z+8dcLM&-8jk!3_G=ft1TVokn__+-na^ipND~R7alK3s0`2UnZ{D+1S{~%kEV2~|Iuq=`ImyJgJFsL~3e+f$x zYy#rHJD&I#BohDKG2(|S@x;GL1rfkLM*Is#56!z~rPyti`6+i`00aO4LKn1>$3Xt>a z81MgI=mP#f+jWui|NDVFkHyyiJEpJycOc37fBS*3c#uDRvP)-Ox~9wY-HzOC+4O`; zs#svXG2K6&(J?gaYSyqKi+TSfdZRK`lW%5e`0GSw3;f}(`AT-?q^zmZ7wOWc@RwDr zlHW&OO179E*u=;gN@1>9Hi}|%0 zi}`sJ`jJ9ERp_8XK`)Jl6~hJ&IXS`XZ+rqdze^HdL7&gac|IfO zRu(!C`x9iXqb=sQB(j*_0=TdH!98;zelfp>#Krs$#1`{|EAiyKUjY%oK1R+BBNy|7 zuCwm*4F+3sa$cy=qbm7FSj?|-%*FhkF&6WCX6(iM-XZkA3H8I<0^k-npp+Tp>~crp zc_D{q7_4D7SScGw`b>EP;cuevWA;J%9wDcR2Awn_)DI-A=x3xjJm;zG#t(z%1IbqO zYrqx#zLDR?*l1J$6+i`00aO4LKm||%RN(R|K+cTC|NV^r-@RD=Zv*~+GRFT;>iplC zg#X*%PrnNqJ<=8IHia4_&D6Okckc<9BPMIriWT07Z@?4r4Jfuu`jSH16mSy2PySa6 znxUDRypT1PO_=-0zbB)8xEiB<=-f$`KSKK-0`0S|8jK&?UqafSr%+#n_VvAmr+ev- zb=tqzp#5hQ`dZk`3l-UJCgS2aoYb}iC=KeH;nf48SUpY z+P^oE_U|2y_UA-sAN~M3w6zanGU8rF-;zlCoiW;nEAg}sxAVuWuQA%+GLrV8>#X~H zgQ1n2_KOvINF}X6`}4-6{lfh+Q8%Di#hFo z-UYM|!-}$jq|^Quo%ZXQeUQFNNDt6{Ta@-Y6KLN^aoW#N*^M7Y`<=;Xe+$z7<^7Vx z(oq3a02M$5Pyti`6+i`0fxi<4u9*0LzWzT%TwlkQ0GPwp@vGY*?Wk&im*!m(tOVFp z=XPy_fd4)0)&EKK5Zg_Ko$1q@+@HTge+ePXV0~>WTV`)G%ee!g=z98K`FMTqo?OE+ zFO4syKZa>^ZGABP#p>K#Vwg^6%LeMp>dz^;A$gn8ogvN_s$o=e$?3i zYkADR9t&YJGe-x+k?&D_q~l7B|34gw|JU~x%JyG3{-4`i;`o1vN%xAGnm1%ApO9UB z?Bpem|6e$2{6AcY_iK!d|6h2SKp1d$GJ5| zeFI>j{szD*W*(%!@sJ~z{tbYI_y)k=$uAk~1}cCGpaQ4>Du4>00;m8gfC``jr~oQ} z3ZMe004jhApaQ4>Du4>00;m8gfC``jr~oQ}3ZMe004jhApaQ4>Du4>00;m8gfC``j zr~oQ}3ZMe004jhApaQ4>Du4>00;m8gfC``jr~oQ}3ZMe004jhApaQ4>Du4>00;oV# Gf&T}g$kes~ literal 0 HcmV?d00001 diff --git a/.github/actions/sanity_tests.sh b/.github/actions/sanity_tests.sh new file mode 100755 index 00000000..26fb398f --- /dev/null +++ b/.github/actions/sanity_tests.sh @@ -0,0 +1,29 @@ +./build/bin/tester/sameboy_tester --jobs 5 \ + --length 40 .github/actions/cgb_sound.gb \ + --length 10 .github/actions/cgb-acid2.gbc \ + --length 10 .github/actions/dmg-acid2.gb \ +--dmg --length 40 .github/actions/dmg_sound-2.gb \ +--dmg --length 20 .github/actions/oam_bug-2.gb + +mv .github/actions/dmg{,-mode}-acid2.bmp + +./build/bin/tester/sameboy_tester \ +--dmg --length 10 .github/actions/dmg-acid2.gb + +FAILED_TESTS=` +shasum .github/actions/*.bmp | grep -E -v \(\ +44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\ +dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\ +0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\ +c50daed36c57a8170ff362042694786676350997\ \ .github/actions/dmg-mode-acid2.bmp\|\ +c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\ +f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\ +\)` + +if [ -n "$FAILED_TESTS" ] ; then + echo "Failed the following tests:" + echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort + exit 1 +fi + +echo Passed all tests \ No newline at end of file diff --git a/.github/workflows/buildability.yml b/.github/workflows/sanity.yml similarity index 70% rename from .github/workflows/buildability.yml rename to .github/workflows/sanity.yml index 7cd52980..e0ac132d 100644 --- a/.github/workflows/buildability.yml +++ b/.github/workflows/sanity.yml @@ -1,4 +1,4 @@ -name: "Bulidability" +name: "Bulidability and Sanity" on: push jobs: @@ -23,7 +23,11 @@ jobs: ./.github/actions/install_deps.sh ${{ matrix.os }} - name: Build run: | - ${{ matrix.cc }} -v; make sdl libretro ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} + ${{ matrix.cc }} -v; make sdl tester libretro ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} + - name: Sanity tests + shell: bash + run: | + ./.github/actions/sanity_tests.sh - name: Upload binaries uses: actions/upload-artifact@v1 with: From e819b91a97834e8af12a7ce378cb9db1e0d441e6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 17:03:45 +0300 Subject: [PATCH 1058/1216] Rename job, temporarily disable -j --- .github/workflows/sanity.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index e0ac132d..41404a01 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -2,7 +2,7 @@ name: "Bulidability and Sanity" on: push jobs: - buildability: + sanity: strategy: matrix: os: [ubuntu-latest, ubuntu-16.04, macos-latest] @@ -23,7 +23,7 @@ jobs: ./.github/actions/install_deps.sh ${{ matrix.os }} - name: Build run: | - ${{ matrix.cc }} -v; make sdl tester libretro ${{ matrix.extra_target }} -j CONF=release CC=${{ matrix.cc }} + ${{ matrix.cc }} -v; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} - name: Sanity tests shell: bash run: | From a35164dc0a0ccb4b9dad372fa704abac430ab03b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 17:06:24 +0300 Subject: [PATCH 1059/1216] Fixed unused variable on Linux --- Tester/main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 27250a6f..f399f3fc 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -178,8 +178,7 @@ static const char *executable_folder(void) _NSGetExecutablePath(&path[0], &length); #else #ifdef __linux__ - ssize_t length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1); - assert (length != -1); + assert (readlink("/proc/self/exe", &path[0], sizeof(path) - 1) != -1); #else #ifdef _WIN32 HMODULE hModule = GetModuleHandle(NULL); From 7760e11544627da2970b0bfe84e6304a5bd89736 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 17:12:53 +0300 Subject: [PATCH 1060/1216] Better error handling --- .github/actions/sanity_tests.sh | 2 ++ Tester/main.c | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/actions/sanity_tests.sh b/.github/actions/sanity_tests.sh index 26fb398f..8d37b68c 100755 --- a/.github/actions/sanity_tests.sh +++ b/.github/actions/sanity_tests.sh @@ -1,3 +1,5 @@ +set -e + ./build/bin/tester/sameboy_tester --jobs 5 \ --length 40 .github/actions/cgb_sound.gb \ --length 10 .github/actions/cgb-acid2.gbc \ diff --git a/Tester/main.c b/Tester/main.c index f399f3fc..e2e1aa84 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -322,15 +322,15 @@ int main(int argc, char **argv) if (dmg) { GB_init(&gb, GB_MODEL_DMG_B); - if (GB_load_boot_rom(&gb, boot_rom_path? boot_rom_path : executable_relative_path("dmg_boot.bin"))) { - perror("Failed to load boot ROM"); + if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("dmg_boot.bin"))) { + fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("dmg_boot.bin")); exit(1); } } else { GB_init(&gb, GB_MODEL_CGB_E); - if (GB_load_boot_rom(&gb, boot_rom_path? boot_rom_path : executable_relative_path("cgb_boot.bin"))) { - perror("Failed to load boot ROM"); + if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("cgb_boot.bin"))) { + fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("cgb_boot.bin")); exit(1); } } From aa9ccc724fb9de220b87a96888bb3db02438aec9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 17:20:06 +0300 Subject: [PATCH 1061/1216] Fixing a duh --- Tester/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index e2e1aa84..6c175c60 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -174,11 +174,12 @@ static const char *executable_folder(void) } /* Ugly unportable code! :( */ #ifdef __APPLE__ - unsigned int length = sizeof(path) - 1; + size_t length = sizeof(path) - 1; _NSGetExecutablePath(&path[0], &length); #else #ifdef __linux__ - assert (readlink("/proc/self/exe", &path[0], sizeof(path) - 1) != -1); + size_t __attribute__((unused)) length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1); + assert(length != -1); #else #ifdef _WIN32 HMODULE hModule = GetModuleHandle(NULL); From 09e706865835b85bd34b2462225214e1308aa532 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 17:22:43 +0300 Subject: [PATCH 1062/1216] Fixing another duh --- Tester/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tester/main.c b/Tester/main.c index 6c175c60..16dbf7bb 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -174,7 +174,7 @@ static const char *executable_folder(void) } /* Ugly unportable code! :( */ #ifdef __APPLE__ - size_t length = sizeof(path) - 1; + uint32_t length = sizeof(path) - 1; _NSGetExecutablePath(&path[0], &length); #else #ifdef __linux__ From 65fb6afd60bc2fe5f7b2190ab2bcd8975f2f322a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 17:57:19 +0300 Subject: [PATCH 1063/1216] Make fixes --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8f8ea810..49e24b59 100644 --- a/Makefile +++ b/Makefile @@ -342,7 +342,11 @@ $(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) -Wl,/subsystem:console -$(BIN)/SDL/%.bin $(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin +$(BIN)/SDL/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ @@ -397,4 +401,4 @@ libretro: clean: rm -rf build -.PHONY: libretro +.PHONY: libretro tester From 9fbafab67f9d38726763d5a29bf446aaca00fc87 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 18:04:27 +0300 Subject: [PATCH 1064/1216] Use grep -q, put macOS first, restore -j --- .github/actions/sanity_tests.sh | 4 ++-- .github/workflows/sanity.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/sanity_tests.sh b/.github/actions/sanity_tests.sh index 8d37b68c..38302d7c 100755 --- a/.github/actions/sanity_tests.sh +++ b/.github/actions/sanity_tests.sh @@ -13,7 +13,7 @@ mv .github/actions/dmg{,-mode}-acid2.bmp --dmg --length 10 .github/actions/dmg-acid2.gb FAILED_TESTS=` -shasum .github/actions/*.bmp | grep -E -v \(\ +shasum .github/actions/*.bmp | grep -q -E -v \(\ 44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\ dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\ 0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\ @@ -24,7 +24,7 @@ f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\ if [ -n "$FAILED_TESTS" ] ; then echo "Failed the following tests:" - echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort + echo $FAILED_TESTS | tr " " "\n" | grep -q -o -E "[^/]+\.bmp" | sed s/.bmp// | sort exit 1 fi diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index 41404a01..ade68d0f 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -5,7 +5,7 @@ jobs: sanity: strategy: matrix: - os: [ubuntu-latest, ubuntu-16.04, macos-latest] + os: [macos-latest, ubuntu-latest, ubuntu-16.04] cc: [gcc, clang] include: - os: macos-latest From f65dc736321cb51232ee69d3d0be51917221c59e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 18:09:04 +0300 Subject: [PATCH 1065/1216] -q was not enough --- .github/workflows/sanity.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index ade68d0f..653e05d8 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -23,7 +23,7 @@ jobs: ./.github/actions/install_deps.sh ${{ matrix.os }} - name: Build run: | - ${{ matrix.cc }} -v; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} + ${{ matrix.cc }} -v; make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} - name: Sanity tests shell: bash run: | From 36aa3f31b947ca00d2682df7f8a01aa66ccde562 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 18:11:01 +0300 Subject: [PATCH 1066/1216] -q was not enough --- .github/actions/sanity_tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/sanity_tests.sh b/.github/actions/sanity_tests.sh index 38302d7c..8984b264 100755 --- a/.github/actions/sanity_tests.sh +++ b/.github/actions/sanity_tests.sh @@ -12,6 +12,8 @@ mv .github/actions/dmg{,-mode}-acid2.bmp ./build/bin/tester/sameboy_tester \ --dmg --length 10 .github/actions/dmg-acid2.gb +set +e + FAILED_TESTS=` shasum .github/actions/*.bmp | grep -q -E -v \(\ 44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\ From 152924e13fe851c0824579989b8f59d4dbd5358f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 22:48:48 +0300 Subject: [PATCH 1067/1216] Add support to the ISX format, including symbols --- Cocoa/Document.m | 9 +- Cocoa/Info.plist | 47 +++++++- Core/debugger.c | 22 ++-- Core/debugger.h | 3 + Core/gb.c | 213 ++++++++++++++++++++++++++++++++++ Core/gb.h | 3 +- OpenDialog/cocoa.m | 2 +- OpenDialog/gtk.c | 1 + OpenDialog/windows.c | 2 +- QuickLook/Info.plist | 1 + QuickLook/get_image_for_rom.c | 18 ++- SDL/main.c | 18 ++- 12 files changed, 320 insertions(+), 19 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ed3eaaf0..ed52efbd 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -647,11 +647,16 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) - (void) loadROM { NSString *rom_warnings = [self captureOutputForBlock:^{ - GB_load_rom(&gb, [self.fileName UTF8String]); + GB_debugger_clear_symbols(&gb); + if ([[self.fileType pathExtension] isEqualToString:@"isx"]) { + GB_load_isx(&gb, [self.fileName UTF8String]); + } + else { + GB_load_rom(&gb, [self.fileName UTF8String]); + } GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); GB_load_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); [self.cheatWindowController cheatsUpdated]; - GB_debugger_clear_symbols(&gb); GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]); }]; diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 1c7bdb5b..44a21f0f 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -16,7 +16,7 @@ CFBundleTypeIconFile Cartridge CFBundleTypeName - GameBoy Game + Game Boy Game CFBundleTypeRole Viewer LSItemContentTypes @@ -36,7 +36,7 @@ CFBundleTypeIconFile ColorCartridge CFBundleTypeName - GameBoy Color Game + Game Boy Color Game CFBundleTypeRole Viewer LSItemContentTypes @@ -48,6 +48,26 @@ NSDocumentClass Document + + CFBundleTypeExtensions + + gbc + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy ISX File + CFBundleTypeRole + Viewer + LSItemContentTypes + + com.github.liji32.sameboy.isx + + LSTypeIsPackage + 0 + NSDocumentClass + Document + CFBundleExecutable SameBoy @@ -85,7 +105,7 @@ public.data UTTypeDescription - GameBoy Game + Game Boy Game UTTypeIconFile Cartridge UTTypeIdentifier @@ -104,7 +124,7 @@ public.data UTTypeDescription - GameBoy Color Game + Game Boy Color Game UTTypeIconFile ColorCartridge UTTypeIdentifier @@ -117,6 +137,25 @@ + + UTTypeConformsTo + + public.data + + UTTypeDescription + Game Boy ISX File + UTTypeIconFile + ColorCartridge + UTTypeIdentifier + com.github.liji32.sameboy.isx + UTTypeTagSpecification + + public.filename-extension + + isx + + + NSCameraUsageDescription SameBoy needs to access your camera to emulate the Game Boy Camera diff --git a/Core/debugger.c b/Core/debugger.c index ee27e888..caf0af13 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -2160,6 +2160,19 @@ void GB_debugger_handle_async_commands(GB_gameboy_t *gb) } } +void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol) +{ + bank &= 0x1FF; + + if (!gb->bank_symbols[bank]) { + gb->bank_symbols[bank] = GB_map_alloc(); + } + GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + if (allocated_symbol) { + GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); + } +} + void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "r"); @@ -2182,14 +2195,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) char symbol[length]; if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) { - bank &= 0x1FF; - if (!gb->bank_symbols[bank]) { - gb->bank_symbols[bank] = GB_map_alloc(); - } - GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); - if (allocated_symbol) { - GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); - } + GB_debugger_add_symbol(gb, bank, address, symbol); } } free(line); diff --git a/Core/debugger.h b/Core/debugger.h index 4868df31..b6a12d97 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -14,6 +14,8 @@ #define GB_debugger_call_hook(gb, addr) (void)addr #define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value) #define GB_debugger_test_read_watchpoint(gb, addr) (void)addr +#define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol) + #else void GB_debugger_run(GB_gameboy_t *gb); void GB_debugger_handle_async_commands(GB_gameboy_t *gb); @@ -22,6 +24,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb); void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); +void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); #endif /* GB_DISABLE_DEBUGGER */ #endif diff --git a/Core/gb.c b/Core/gb.c index 2b22f9d1..f1eae906 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -296,6 +296,219 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +int GB_load_isx(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open ISX file: %s.\n", strerror(errno)); + return errno; + } + char magic[4]; +#define READ(x) if (fread(&x, sizeof(x), 1, f) != 1) goto error + fread(magic, 1, sizeof(magic), f); + + bool extended = *(uint32_t *)&magic == htonl('ISX '); + + fseek(f, extended? 0x20 : 0, SEEK_SET); + + + uint8_t *old_rom = gb->rom; + uint32_t old_size = gb->rom_size; + gb->rom = NULL; + gb->rom_size = 0; + + while (true) { + uint8_t record_type = 0; + if (fread(&record_type, sizeof(record_type), 1, f) != 1) break; + switch (record_type) { + case 0x01: { // Binary + uint16_t bank; + uint16_t address; + uint16_t length; + uint8_t byte; + READ(byte); + bank = byte; + if (byte >= 0x80) { + READ(byte); + bank |= byte << 8; + } + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap16(address); +#endif + address &= 0x3FFF; + + READ(length); +#ifdef GB_BIG_ENDIAN + length = __builtin_bswap16(length); +#endif + + size_t needed_size = bank * 0x4000 + address + length; + if (needed_size > 1024 * 1024 * 32) + goto error; + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + if (fread(gb->rom + (bank * 0x4000 + address), length, 1, f) != 1) + goto error; + + break; + } + + case 0x11: { // Extended Binary + uint32_t address; + uint32_t length; + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap32(address); +#endif + + READ(length); +#ifdef GB_BIG_ENDIAN + length = __builtin_bswap32(length); +#endif + size_t needed_size = address + length; + if (needed_size > 1024 * 1024 * 32) + goto error; + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + if (fread(gb->rom + address, length, 1, f) != 1) + goto error; + + break; + } + + case 0x04: { // Symbol + uint16_t count; + uint8_t length; + char name[257]; + uint8_t flag; + uint16_t bank; + uint16_t address; + uint8_t byte; + READ(count); +#ifdef GB_BIG_ENDIAN + count = __builtin_bswap16(count); +#endif + while (count--) { + READ(length); + if (fread(name, length, 1, f) != 1) + goto error; + name[length] = 0; + READ(flag); // unused + + READ(byte); + bank = byte; + if (byte >= 0x80) { + READ(byte); + bank |= byte << 8; + } + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap16(address); +#endif + GB_debugger_add_symbol(gb, bank, address, name); + } + break; + } + + case 0x14: { // Extended Binary + uint16_t count; + uint8_t length; + char name[257]; + uint8_t flag; + uint32_t address; + READ(count); +#ifdef GB_BIG_ENDIAN + count = __builtin_bswap16(count); +#endif + while (count--) { + READ(length); + if (fread(name, length + 1, 1, f) != 1) + goto error; + name[length] = 0; + READ(flag); // unused + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap32(address); +#endif + // TODO: How to convert 32-bit addresses to Bank:Address? Needs to tell RAM and ROM apart + } + break; + } + + default: + goto done; + } + } +done:; +#undef READ + if (gb->rom_size == 0) goto error; + + size_t needed_size = (gb->rom_size + 0x3FFF) & ~0x3FFF; /* Round to bank */ + + /* And then round to a power of two */ + while (needed_size & (needed_size - 1)) { + /* I promise this works. */ + needed_size |= needed_size >> 1; + needed_size++; + } + + if (needed_size < 0x8000) { + needed_size = 0x8000; + } + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + GB_configure_cart(gb); + + // Fix a common wrong MBC error + if (gb->rom[0x147] == 3) { // MBC1 + RAM + Battery + if (gb->rom_size == 64 * 0x4000) { + for (unsigned i = 63 * 0x4000; i < 64 * 0x4000; i++) { + if (gb->rom[i]) { + gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery + GB_configure_cart(gb); + gb->rom[0x147] = 0x3; + GB_log(gb, "ROM uses MBC1 but appears to use all 64 banks, assuming MBC3\n"); + break; + } + } + } + } + + if (old_rom) { + free(old_rom); + } + + return 0; +error: + GB_log(gb, "Invalid or unsupported ISX file.\n"); + if (gb->rom) { + free(gb->rom); + gb->rom = old_rom; + gb->rom_size = old_size; + } + fclose(f); + return -1; +} + void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) { gb->rom_size = (size + 0x3fff) & ~0x3fff; diff --git a/Core/gb.h b/Core/gb.h index 703a4894..a83bb09c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -721,7 +721,8 @@ int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); int GB_load_rom(GB_gameboy_t *gb, const char *path); void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); - +int GB_load_isx(GB_gameboy_t *gb, const char *path); + int GB_save_battery_size(GB_gameboy_t *gb); int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); int GB_save_battery(GB_gameboy_t *gb, const char *path); diff --git a/OpenDialog/cocoa.m b/OpenDialog/cocoa.m index 29a722cb..76b9606b 100644 --- a/OpenDialog/cocoa.m +++ b/OpenDialog/cocoa.m @@ -8,7 +8,7 @@ char *do_open_rom_dialog(void) NSWindow *key = [NSApp keyWindow]; NSOpenPanel *dialog = [NSOpenPanel openPanel]; dialog.title = @"Open ROM"; - dialog.allowedFileTypes = @[@"gb", @"gbc", @"sgb"]; + dialog.allowedFileTypes = @[@"gb", @"gbc", @"sgb", @"isx"]; [dialog runModal]; [key makeKeyAndOrderFront:nil]; NSString *ret = [[[dialog URLs] firstObject] path]; diff --git a/OpenDialog/gtk.c b/OpenDialog/gtk.c index d9163fc3..5b1caa3d 100644 --- a/OpenDialog/gtk.c +++ b/OpenDialog/gtk.c @@ -82,6 +82,7 @@ char *do_open_rom_dialog(void) gtk_file_filter_add_pattern(filter, "*.gb"); gtk_file_filter_add_pattern(filter, "*.gbc"); gtk_file_filter_add_pattern(filter, "*.sgb"); + gtk_file_filter_add_pattern(filter, "*.isx"); gtk_file_filter_set_name(filter, "Game Boy ROMs"); gtk_file_chooser_add_filter(dialog, filter); diff --git a/OpenDialog/windows.c b/OpenDialog/windows.c index 6bf9b894..52e281da 100644 --- a/OpenDialog/windows.c +++ b/OpenDialog/windows.c @@ -10,7 +10,7 @@ char *do_open_rom_dialog(void) dialog.lStructSize = sizeof(dialog); dialog.lpstrFile = filename; dialog.nMaxFile = sizeof(filename); - dialog.lpstrFilter = L"Game Boy ROMs\0*.gb;*.gbc;*.sgb\0All files\0*.*\0\0"; + dialog.lpstrFilter = L"Game Boy ROMs\0*.gb;*.gbc;*.sgb;*.isx\0All files\0*.*\0\0"; dialog.nFilterIndex = 1; dialog.lpstrFileTitle = NULL; dialog.nMaxFileTitle = 0; diff --git a/QuickLook/Info.plist b/QuickLook/Info.plist index 2cff196d..b01aae1c 100644 --- a/QuickLook/Info.plist +++ b/QuickLook/Info.plist @@ -13,6 +13,7 @@ com.github.liji32.sameboy.gb com.github.liji32.sameboy.gbc + com.github.liji32.sameboy.isx diff --git a/QuickLook/get_image_for_rom.c b/QuickLook/get_image_for_rom.c index 3950dac8..b9f87edb 100755 --- a/QuickLook/get_image_for_rom.c +++ b/QuickLook/get_image_for_rom.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "get_image_for_rom.h" @@ -60,7 +61,22 @@ int get_image_for_rom(const char *filename, const char *boot_path, uint32_t *out GB_set_log_callback(&gb, log_callback); GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); - if (GB_load_rom(&gb, filename)) { + size_t length = strlen(filename); + char extension[4] = {0,}; + if (length > 4) { + if (filename[length - 4] == '.') { + extension[0] = tolower(filename[length - 3]); + extension[1] = tolower(filename[length - 2]); + extension[2] = tolower(filename[length - 1]); + } + } + if (strcmp(extension, "isx") == 0) { + if (GB_load_isx(&gb, filename)) { + GB_free(&gb); + return 1; + } + } + else if (GB_load_rom(&gb, filename)) { GB_free(&gb); return 1; } diff --git a/SDL/main.c b/SDL/main.c index 4ce2e597..e09630b0 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -526,8 +527,23 @@ restart: } bool error = false; + GB_debugger_clear_symbols(&gb); start_capturing_logs(); - error = GB_load_rom(&gb, filename); + size_t length = strlen(filename); + char extension[4] = {0,}; + if (length > 4) { + if (filename[length - 4] == '.') { + extension[0] = tolower(filename[length - 3]); + extension[1] = tolower(filename[length - 2]); + extension[2] = tolower(filename[length - 1]); + } + } + if (strcmp(extension, "isx") == 0) { + error = GB_load_isx(&gb, filename); + } + else { + GB_load_rom(&gb, filename); + } end_capturing_logs(true, error); size_t path_length = strlen(filename); From ca567bee7913bb588cadd5b6f41a250e89b0389c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 22:54:50 +0300 Subject: [PATCH 1068/1216] Fix Linux build break --- Core/gb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index f1eae906..701a48a6 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -307,7 +307,11 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) #define READ(x) if (fread(&x, sizeof(x), 1, f) != 1) goto error fread(magic, 1, sizeof(magic), f); - bool extended = *(uint32_t *)&magic == htonl('ISX '); +#ifdef GB_BIG_ENDIAN + bool extended = *(uint32_t *)&magic == 'ISX '; +#else + bool extended = *(uint32_t *)&magic == __builtin_bswap32('ISX '); +#endif fseek(f, extended? 0x20 : 0, SEEK_SET); From 9e99ce434e9903d7c1db0747a344059768505814 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 23:09:08 +0300 Subject: [PATCH 1069/1216] Allow loading .RAM files --- Cocoa/Document.m | 2 ++ SDL/main.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ed52efbd..6eb9c2f5 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -650,6 +650,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_debugger_clear_symbols(&gb); if ([[self.fileType pathExtension] isEqualToString:@"isx"]) { GB_load_isx(&gb, [self.fileName UTF8String]); + GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"ram"] UTF8String]); + } else { GB_load_rom(&gb, [self.fileName UTF8String]); diff --git a/SDL/main.c b/SDL/main.c index e09630b0..9a358d06 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -540,6 +540,11 @@ restart: } if (strcmp(extension, "isx") == 0) { error = GB_load_isx(&gb, filename); + /* Try loading .ram file if available */ + char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ + replace_extension(filename, path_length, battery_save_path, ".ram"); + battery_save_path_ptr = battery_save_path; + GB_load_battery(&gb, battery_save_path); } else { GB_load_rom(&gb, filename); From 0534b091a576312f5d5ea2be4ad48ee4b53d01cb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 23:11:29 +0300 Subject: [PATCH 1070/1216] Fix SDL --- SDL/main.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 9a358d06..e27c0f26 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -529,18 +529,18 @@ restart: bool error = false; GB_debugger_clear_symbols(&gb); start_capturing_logs(); - size_t length = strlen(filename); + size_t path_length = strlen(filename); char extension[4] = {0,}; - if (length > 4) { - if (filename[length - 4] == '.') { - extension[0] = tolower(filename[length - 3]); - extension[1] = tolower(filename[length - 2]); - extension[2] = tolower(filename[length - 1]); + if (path_length > 4) { + if (filename[path_length - 4] == '.') { + extension[0] = tolower(filename[path_length - 3]); + extension[1] = tolower(filename[path_length - 2]); + extension[2] = tolower(filename[path_length - 1]); } } if (strcmp(extension, "isx") == 0) { error = GB_load_isx(&gb, filename); - /* Try loading .ram file if available */ + /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ replace_extension(filename, path_length, battery_save_path, ".ram"); battery_save_path_ptr = battery_save_path; @@ -551,7 +551,6 @@ restart: } end_capturing_logs(true, error); - size_t path_length = strlen(filename); /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ From d1e3ad7790879330845b2bbcb63260cbc5505de4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 23:18:03 +0300 Subject: [PATCH 1071/1216] Better hueristics for wrong MBC type --- Core/gb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 701a48a6..966022bc 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -484,13 +484,13 @@ done:; // Fix a common wrong MBC error if (gb->rom[0x147] == 3) { // MBC1 + RAM + Battery - if (gb->rom_size == 64 * 0x4000) { - for (unsigned i = 63 * 0x4000; i < 64 * 0x4000; i++) { + if (gb->rom_size >= 33 * 0x4000) { + for (unsigned i = 32 * 0x4000; i < 33 * 0x4000; i++) { if (gb->rom[i]) { gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery GB_configure_cart(gb); gb->rom[0x147] = 0x3; - GB_log(gb, "ROM uses MBC1 but appears to use all 64 banks, assuming MBC3\n"); + GB_log(gb, "ROM claims to use MBC1 but appears to require MBC3 or 5, assuming MBC3.\n"); break; } } From 110cedeaac3918170263377792e63f5addf436df Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Apr 2020 23:26:17 +0300 Subject: [PATCH 1072/1216] Even better hueristics --- Core/gb.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 966022bc..a17a5a7f 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -484,17 +484,37 @@ done:; // Fix a common wrong MBC error if (gb->rom[0x147] == 3) { // MBC1 + RAM + Battery - if (gb->rom_size >= 33 * 0x4000) { - for (unsigned i = 32 * 0x4000; i < 33 * 0x4000; i++) { + bool needs_fix = false; + if (gb->rom_size >= 0x21 * 0x4000) { + for (unsigned i = 0x20 * 0x4000; i < 0x21 * 0x4000; i++) { if (gb->rom[i]) { - gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery - GB_configure_cart(gb); - gb->rom[0x147] = 0x3; - GB_log(gb, "ROM claims to use MBC1 but appears to require MBC3 or 5, assuming MBC3.\n"); + needs_fix = true; break; } } } + if (!needs_fix && gb->rom_size >= 0x41 * 0x4000) { + for (unsigned i = 0x40 * 0x4000; i < 0x41 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (!needs_fix && gb->rom_size >= 0x61 * 0x4000) { + for (unsigned i = 0x60 * 0x4000; i < 0x61 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (needs_fix) { + gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery + GB_configure_cart(gb); + gb->rom[0x147] = 0x3; + GB_log(gb, "ROM claims to use MBC1 but appears to require MBC3 or 5, assuming MBC3.\n"); + } } if (old_rom) { From 8d016f19d22c87aa11ead516a0c579a1a040544c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 27 Apr 2020 21:12:30 +0300 Subject: [PATCH 1073/1216] Move the audio code to a different file --- Makefile | 3 +- SDL/audio/audio.h | 16 +++++++++ SDL/audio/sdl.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++ SDL/main.c | 81 ++++++++----------------------------------- 4 files changed, 119 insertions(+), 68 deletions(-) create mode 100644 SDL/audio/audio.h create mode 100644 SDL/audio/sdl.c diff --git a/Makefile b/Makefile index 49e24b59..9df7eb86 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ endif VERSION := 0.12.3 export VERSION CONF ?= debug +SDL_AUDIO_DRIVER ?= sdl BIN := build/bin OBJ := build/obj @@ -172,7 +173,7 @@ all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets CORE_SOURCES := $(shell ls Core/*.c) -SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) +SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) SDL/audio/$(SDL_AUDIO_DRIVER).c TESTER_SOURCES := $(shell ls Tester/*.c) ifeq ($(PLATFORM),Darwin) diff --git a/SDL/audio/audio.h b/SDL/audio/audio.h new file mode 100644 index 00000000..acaa011d --- /dev/null +++ b/SDL/audio/audio.h @@ -0,0 +1,16 @@ +#ifndef sdl_audio_h +#define sdl_audio_h + +#include +#include +#include + +bool GB_audio_is_playing(void); +void GB_audio_set_paused(bool paused); +void GB_audio_clear_queue(void); +unsigned GB_audio_get_frequency(void); +size_t GB_audio_get_queue_length(void); +void GB_audio_queue_sample(GB_sample_t *sample); +void GB_audio_init(void); + +#endif /* sdl_audio_h */ diff --git a/SDL/audio/sdl.c b/SDL/audio/sdl.c new file mode 100644 index 00000000..1f8a529e --- /dev/null +++ b/SDL/audio/sdl.c @@ -0,0 +1,87 @@ +#include "audio.h" +#include + +#ifndef _WIN32 +#define AUDIO_FREQUENCY 96000 +#include +#else +#include +/* Windows (well, at least my VM) can't handle 96KHz sound well :( */ + +/* felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. + we can get is 48000. 96000 also works, but always has some faint crackling in + the audio, no matter how high or low I set the buffer length... + Not quite satisfied with that solution, because acc. to SDL2 docs, + 96k + WASAPI *should* work. */ + +#define AUDIO_FREQUENCY 48000 +#endif + +/* Compatibility with older SDL versions */ +#ifndef SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0 +#endif + +static SDL_AudioDeviceID device_id; +static SDL_AudioSpec want_aspec, have_aspec; + +bool GB_audio_is_playing(void) +{ + return SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; +} + +void GB_audio_set_paused(bool paused) +{ + GB_audio_clear_queue(); + SDL_PauseAudioDevice(device_id, paused); +} + +void GB_audio_clear_queue(void) +{ + SDL_ClearQueuedAudio(device_id); +} + +unsigned GB_audio_get_frequency(void) +{ + return have_aspec.freq; +} + +size_t GB_audio_get_queue_length(void) +{ + return SDL_GetQueuedAudioSize(device_id); +} + +void GB_audio_queue_sample(GB_sample_t *sample) +{ + SDL_QueueAudio(device_id, sample, sizeof(*sample)); +} + +void GB_audio_init(void) +{ + /* Configure Audio */ + memset(&want_aspec, 0, sizeof(want_aspec)); + want_aspec.freq = AUDIO_FREQUENCY; + want_aspec.format = AUDIO_S16SYS; + want_aspec.channels = 2; + want_aspec.samples = 512; + + SDL_version _sdl_version; + SDL_GetVersion(&_sdl_version); + unsigned sdl_version = _sdl_version.major * 1000 + _sdl_version.minor * 100 + _sdl_version.patch; + +#ifndef _WIN32 + /* SDL 2.0.5 on macOS and Linux introduced a bug where certain combinations of buffer lengths and frequencies + fail to produce audio correctly. */ + if (sdl_version >= 2005) { + want_aspec.samples = 2048; + } +#else + if (sdl_version < 2006) { + /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency + to 44100 because otherwise we would get garbled audio output.*/ + want_aspec.freq = 44100; + } +#endif + + device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); +} diff --git a/SDL/main.c b/SDL/main.c index e27c0f26..c4a4d0f6 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -8,27 +8,12 @@ #include "utils.h" #include "gui.h" #include "shader.h" - +#include "audio/audio.h" #ifndef _WIN32 -#define AUDIO_FREQUENCY 96000 #include #else #include -/* Windows (well, at least my VM) can't handle 96KHz sound well :( */ - -/* felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. - we can get is 48000. 96000 also works, but always has some faint crackling in - the audio, no matter how high or low I set the buffer length... - Not quite satisfied with that solution, because acc. to SDL2 docs, - 96k + WASAPI *should* work. */ - -#define AUDIO_FREQUENCY 48000 -#endif - -/* Compatibility with older SDL versions */ -#ifndef SDL_AUDIO_ALLOW_SAMPLES_CHANGE -#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0 #endif GB_gameboy_t gb; @@ -42,7 +27,6 @@ static char *filename = NULL; static typeof(free) *free_function = NULL; static char *battery_save_path_ptr; -SDL_AudioDeviceID device_id; void set_filename(const char *new_filename, typeof(free) *new_free_function) { @@ -53,8 +37,6 @@ void set_filename(const char *new_filename, typeof(free) *new_free_function) free_function = new_free_function; } -static SDL_AudioSpec want_aspec, have_aspec; - static char *captured_log = NULL; static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -127,16 +109,15 @@ static void screen_size_changed(void) static void open_menu(void) { - bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; + bool audio_playing = GB_audio_is_playing(); if (audio_playing) { - SDL_PauseAudioDevice(device_id, 1); + GB_audio_set_paused(true); } size_t previous_width = GB_get_screen_width(&gb); run_gui(true); SDL_ShowCursor(SDL_DISABLE); if (audio_playing) { - SDL_ClearQueuedAudio(device_id); - SDL_PauseAudioDevice(device_id, 0); + GB_audio_set_paused(false); } GB_set_color_correction_mode(&gb, configuration.color_correction_mode); GB_set_border_mode(&gb, configuration.border_mode); @@ -176,7 +157,7 @@ static void handle_events(GB_gameboy_t *gb) GB_set_key_state(gb, (GB_key_t) button, event.type == SDL_JOYBUTTONDOWN); } else if (button == JOYPAD_BUTTON_TURBO) { - SDL_ClearQueuedAudio(device_id); + GB_audio_clear_queue(); turbo_down = event.type == SDL_JOYBUTTONDOWN; GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); } @@ -294,14 +275,7 @@ static void handle_events(GB_gameboy_t *gb) break; } #endif - bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; - if (audio_playing) { - SDL_PauseAudioDevice(device_id, 1); - } - else if (!audio_playing) { - SDL_ClearQueuedAudio(device_id); - SDL_PauseAudioDevice(device_id, 0); - } + GB_audio_set_paused(GB_audio_is_playing()); } break; @@ -336,7 +310,7 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYUP: // Fallthrough if (event.key.keysym.scancode == configuration.keys[8]) { turbo_down = event.type == SDL_KEYDOWN; - SDL_ClearQueuedAudio(device_id); + GB_audio_clear_queue(); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); } else if (event.key.keysym.scancode == configuration.keys_2[0]) { @@ -409,15 +383,15 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) if (turbo_down) { static unsigned skip = 0; skip++; - if (skip == have_aspec.freq / 8) { + if (skip == GB_audio_get_frequency() / 8) { skip = 0; } - if (skip > have_aspec.freq / 16) { + if (skip > GB_audio_get_frequency() / 16) { return; } } - if (SDL_GetQueuedAudioSize(device_id) / sizeof(*sample) > have_aspec.freq / 4) { + if (GB_audio_get_queue_length() / sizeof(*sample) > GB_audio_get_frequency() / 4) { return; } @@ -426,7 +400,7 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) sample->right = sample->right * configuration.volume / 100; } - SDL_QueueAudio(device_id, sample, sizeof(*sample)); + GB_audio_queue_sample(sample); } @@ -514,7 +488,7 @@ restart: GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_pixels_output(&gb, active_pixel_buffer); GB_set_rgb_encode_callback(&gb, rgb_encode); - GB_set_sample_rate(&gb, have_aspec.freq); + GB_set_sample_rate(&gb, GB_audio_get_frequency()); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); update_palette(); if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) { @@ -675,34 +649,7 @@ int main(int argc, char **argv) pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); } - - /* Configure Audio */ - memset(&want_aspec, 0, sizeof(want_aspec)); - want_aspec.freq = AUDIO_FREQUENCY; - want_aspec.format = AUDIO_S16SYS; - want_aspec.channels = 2; - want_aspec.samples = 512; - - SDL_version _sdl_version; - SDL_GetVersion(&_sdl_version); - unsigned sdl_version = _sdl_version.major * 1000 + _sdl_version.minor * 100 + _sdl_version.patch; - -#ifndef _WIN32 - /* SDL 2.0.5 on macOS and Linux introduced a bug where certain combinations of buffer lengths and frequencies - fail to produce audio correctly. */ - if (sdl_version >= 2005) { - want_aspec.samples = 2048; - } -#else - if (sdl_version < 2006) { - /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency - to 44100 because otherwise we would get garbled audio output.*/ - want_aspec.freq = 44100; - } -#endif - - device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); - /* Start Audio */ + GB_audio_init(); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); @@ -735,7 +682,7 @@ int main(int argc, char **argv) else { connect_joypad(); } - SDL_PauseAudioDevice(device_id, 0); + GB_audio_set_paused(false); run(); // Never returns return 0; } From c64d5b58b6a8f09877e8dc115a5ba657fca63895 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 27 Apr 2020 23:29:26 +0300 Subject: [PATCH 1074/1216] Make failed builds easier to read --- .github/workflows/sanity.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index 653e05d8..d25a1807 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -23,7 +23,7 @@ jobs: ./.github/actions/install_deps.sh ${{ matrix.os }} - name: Build run: | - ${{ matrix.cc }} -v; make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} + ${{ matrix.cc }} -v; (make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} || (echo "==== Build Failed ==="; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }})) - name: Sanity tests shell: bash run: | From 1e54c55c117f1141699d53647e2348460e96440a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 28 Apr 2020 21:44:29 +0300 Subject: [PATCH 1075/1216] Making libretro compile without warnings with GCC --- Makefile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 9df7eb86..94e8a830 100644 --- a/Makefile +++ b/Makefile @@ -89,14 +89,16 @@ OPEN_DIALOG = OpenDialog/cocoa.m endif # These must come before the -Wno- flags -CFLAGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option -CFLAGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context +WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option +WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context # Only add this flag if the compiler supports it ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0) -CFLAGS += -Wpartial-availability +WARNINGS += -Wpartial-availability endif +CFLAGS += $(WARNINGS) + CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES ifeq (,$(PKG_CONFIG)) @@ -396,7 +398,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb8 # Libretro Core (uses its own build system) libretro: - $(MAKE) -C libretro + CFLAGS="$(WARNINGS)" $(MAKE) -C libretro # Clean clean: From 8f6047fdca3f909b0b1809f4d76c5b6684f7b679 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 28 Apr 2020 21:53:37 +0300 Subject: [PATCH 1076/1216] Prevent -Wall from overriding -Wno flags --- libretro/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 7e198936..b3276288 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -1,6 +1,8 @@ STATIC_LINKING := 0 AR := ar +CFLAGS := -Wall $(CFLAGS) + GIT_VERSION ?= " $(shell git rev-parse --short HEAD || echo unknown)" ifneq ($(GIT_VERSION)," unknown") CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" @@ -93,7 +95,7 @@ else ifeq ($(platform), linux-portable) else ifeq ($(platform), switch) TARGET := $(TARGET_NAME)_libretro_$(platform).a include $(LIBTRANSISTOR_HOME)/libtransistor.mk - CFLAGS += -Wl,-q -Wall -O3 -fno-short-enums -fno-optimize-sibling-calls + CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls STATIC_LINKING=1 # Nintendo WiiU else ifeq ($(platform), wiiu) @@ -141,7 +143,7 @@ else ifeq ($(platform), vita) TARGET := $(TARGET_NAME)_vita.a CC = arm-vita-eabi-gcc AR = arm-vita-eabi-ar - CFLAGS += -Wl,-q -Wall -O3 -fno-short-enums -fno-optimize-sibling-calls + CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls STATIC_LINKING = 1 # Windows MSVC 2017 all architectures @@ -278,7 +280,7 @@ else LD = $(CC) endif -CFLAGS += -Wall -D__LIBRETRO__ $(fpic) $(INCFLAGS) -std=gnu11 -D_GNU_SOURCE -D_USE_MATH_DEFINES +CFLAGS += -D__LIBRETRO__ $(fpic) $(INCFLAGS) -std=gnu11 -D_GNU_SOURCE -D_USE_MATH_DEFINES all: $(TARGET) From 151d58eb60cc4abffd08a8e4ca0276850f98b8b8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 16:05:31 +0300 Subject: [PATCH 1077/1216] setRumble should be double --- Cocoa/GBView.h | 2 +- Cocoa/GBView.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index c19d340f..80721cd7 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -22,5 +22,5 @@ typedef enum { - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; - (void)screenSizeChanged; -- (void)setRumble: (bool)on; +- (void)setRumble: (double)amp; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 1272c939..e733731d 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -270,9 +270,9 @@ } } -- (void)setRumble:(bool)on +- (void)setRumble:(double)amp { - [lastController setRumbleAmplitude:(double)on]; + [lastController setRumbleAmplitude:amp]; } - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis From 6448a692e297e71fc86b2f07fd142c0515fb6406 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 16:06:11 +0300 Subject: [PATCH 1078/1216] Add smart rumble to games without a rumblepak --- Core/apu.c | 1 - Core/display.c | 8 +------- Core/gb.h | 1 + Core/rumble.c | 42 ++++++++++++++++++++++++++++++++++++++++++ Core/rumble.h | 8 ++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 Core/rumble.c create mode 100644 Core/rumble.h diff --git a/Core/apu.c b/Core/apu.c index e63d89f5..afb970c8 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -493,7 +493,6 @@ void GB_apu_run(GB_gameboy_t *gb) /* Step LFSR */ unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; - /* Todo: is this formula is different on a GBA? */ bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; gb->apu.noise_channel.lfsr >>= 1; diff --git a/Core/display.c b/Core/display.c index b3522f11..be2e1088 100644 --- a/Core/display.c +++ b/Core/display.c @@ -200,14 +200,8 @@ static void display_vblank(GB_gameboy_t *gb) } } } + GB_handle_rumble(gb); - if (gb->rumble_callback) { - if (gb->rumble_on_cycles + gb->rumble_off_cycles) { - gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); - gb->rumble_on_cycles = gb->rumble_off_cycles = 0; - } - } - if (gb->vblank_callback) { gb->vblank_callback(gb); } diff --git a/Core/gb.h b/Core/gb.h index d63d400d..7967b6f5 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -22,6 +22,7 @@ #include "symbol_hash.h" #include "sgb.h" #include "cheats.h" +#include "rumble.h" #define GB_STRUCT_VERSION 13 diff --git a/Core/rumble.c b/Core/rumble.c new file mode 100644 index 00000000..5ac3d0d5 --- /dev/null +++ b/Core/rumble.c @@ -0,0 +1,42 @@ +#include "rumble.h" +#include "gb.h" + +void GB_handle_rumble(GB_gameboy_t *gb) +{ + if (gb->rumble_callback) { + if (gb->cartridge_type->has_rumble) { + if (gb->rumble_on_cycles + gb->rumble_off_cycles) { + gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); + gb->rumble_on_cycles = gb->rumble_off_cycles = 0; + } + } + else { + unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80)); + unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10)); + + double ch4_rumble = (MIN(gb->apu.noise_channel.sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0; + + ch4_rumble = MIN(ch4_rumble, 1.0); + ch4_rumble = MAX(ch4_rumble, 0.0); + + double ch1_rumble = 0; + if (gb->apu.sweep_enabled && ((gb->io_registers[GB_IO_NR10] >> 4) & 7)) { + double sweep_speed = (gb->io_registers[GB_IO_NR10] & 7) / (double)((gb->io_registers[GB_IO_NR10] >> 4) & 7); + ch1_rumble = gb->apu.square_channels[GB_SQUARE_1].current_volume * ch1_volume / 32.0 * sweep_speed / 8.0 - 0.5; + ch1_rumble = MIN(ch1_rumble, 1.0); + ch1_rumble = MAX(ch1_rumble, 0.0); + } + + if (!gb->apu.is_active[GB_NOISE]) { + ch4_rumble = 0; + } + + if (!gb->apu.is_active[GB_SQUARE_1]) { + ch1_rumble = 0; + } + + gb->rumble_callback(gb, MIN(MAX(ch1_rumble / 2 + ch4_rumble, 0.0), 1.0)); + } + } +} diff --git a/Core/rumble.h b/Core/rumble.h new file mode 100644 index 00000000..a378f2d8 --- /dev/null +++ b/Core/rumble.h @@ -0,0 +1,8 @@ +#ifndef rumble_h +#define rumble_h + +#include "gb_struct_def.h" + +void GB_handle_rumble(GB_gameboy_t *gb); + +#endif /* rumble_h */ From 4c443d51ce52431e52dbd9e9b9313746a6248825 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 16:06:38 +0300 Subject: [PATCH 1079/1216] Minor JoyKit improvements --- JoyKit/JOYAxes2D.m | 27 ++++++++++++++++++++------- JoyKit/JOYController.m | 3 +++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/JoyKit/JOYAxes2D.m b/JoyKit/JOYAxes2D.m index 28ce16cb..624ccef4 100644 --- a/JoyKit/JOYAxes2D.m +++ b/JoyKit/JOYAxes2D.m @@ -125,23 +125,21 @@ double old1 = _state1, old2 = _state2; { - double min = [self effectiveMinX]; - double max = [self effectiveMaxX]; - if (min == max) return false; int32_t value = x; - + if (initialX != 0) { minX = MIN(value, minX); maxX = MAX(value, maxX); } + double min = [self effectiveMinX]; + double max = [self effectiveMaxX]; + if (min == max) return false; + _state1 = (value - min) / (max - min) * 2 - 1; } { - double min = [self effectiveMinY]; - double max = [self effectiveMaxY]; - if (min == max) return false; int32_t value = y; if (initialY != 0) { @@ -149,8 +147,23 @@ maxY = MAX(value, maxY); } + double min = [self effectiveMinY]; + double max = [self effectiveMaxY]; + if (min == max) return false; + _state2 = (value - min) / (max - min) * 2 - 1; } + + if (_state1 < -1 || _state1 > 1 || + _state2 < -1 || _state2 > 1) { + // Makes no sense, recalibrate + _state1 = _state2 = 0; + initialX = initialY = 0; + minX = _element1.max; + minY = _element2.max; + maxX = _element1.min; + maxY = _element2.min; + } return old1 != _state1 || old2 != _state2; } diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index d5e1acbd..2b218618 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -382,11 +382,13 @@ typedef struct __attribute__((packed)) { - (NSString *)deviceName { + if (!_device) return nil; return IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey)); } - (NSString *)uniqueID { + if (!_device) return nil; NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey)); if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) { serial = [NSString stringWithFormat:@"%04x%04x%08x", @@ -581,6 +583,7 @@ typedef struct __attribute__((packed)) { - (void)sendReport:(NSData *)report { if (!report.length) return; + if (!_device) return; IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length); } From 4b2417855373761a126b30749c3ae5191a5c8d5d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 16:50:31 +0300 Subject: [PATCH 1080/1216] Rumble mode selection --- Cocoa/AppDelegate.m | 1 + Cocoa/Document.m | 11 +++++++++ Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 20 ++++++++++++++++ Cocoa/Preferences.xib | 48 +++++++++++++++++++++++++++++-------- Core/gb.h | 1 + Core/rumble.c | 13 +++++++++- Core/rumble.h | 9 +++++++ 8 files changed, 93 insertions(+), 11 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index be476cfa..34046202 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -43,6 +43,7 @@ @"GBDMGModel": @(GB_MODEL_DMG_B), @"GBCGBModel": @(GB_MODEL_CGB_E), @"GBSGBModel": @(GB_MODEL_SGB2), + @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), }]; [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 824c8165..90a1dc84 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -228,6 +228,11 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) borderModeChanged = true; } +- (void) updateRumbleMode +{ + GB_set_rumble_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]); +} + - (void) initCommon { GB_init(&gb, [self internalModel]); @@ -247,6 +252,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); GB_apu_set_sample_callback(&gb, audioCallback); GB_set_rumble_callback(&gb, rumbleCallback); + [self updateRumbleMode]; } - (void) updateMinSize @@ -556,6 +562,11 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) name:@"GBBorderModeChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRumbleMode) + name:@"GBRumbleModeChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateRewindLength) name:@"GBRewindLengthChanged" diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 7f6bf061..ee697a8d 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -16,6 +16,7 @@ @property (strong) IBOutlet NSButton *skipButton; @property (strong) IBOutlet NSMenuItem *bootROMsFolderItem; @property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton; +@property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton; @property (weak) IBOutlet NSPopUpButton *dmgPopupButton; @property (weak) IBOutlet NSPopUpButton *sgbPopupButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 4d0848ca..71183e1c 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -24,6 +24,7 @@ NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; NSPopUpButton *_preferredJoypadButton; + NSPopUpButton *_rumbleModePopupButton; } + (NSArray *)filterList @@ -125,6 +126,18 @@ return _displayBorderPopupButton; } +- (void)setRumbleModePopupButton:(NSPopUpButton *)rumbleModePopupButton +{ + _rumbleModePopupButton = rumbleModePopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]; + [_rumbleModePopupButton selectItemWithTag:mode]; +} + +- (NSPopUpButton *)rumbleModePopupButton +{ + return _rumbleModePopupButton; +} + - (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton { _rewindPopupButton = rewindPopupButton; @@ -267,6 +280,13 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil]; } +- (IBAction)rumbleModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) + forKey:@"GBRumbleMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil]; +} + - (IBAction)rewindLengthChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 1107e7de..149f71ee 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -76,6 +76,7 @@ + @@ -463,11 +464,11 @@ - + - + @@ -475,8 +476,17 @@ + + + + + + + + + - + @@ -534,7 +544,7 @@ - + @@ -543,7 +553,7 @@ - + @@ -559,11 +569,11 @@ - + - + @@ -572,7 +582,7 @@ - + @@ -590,8 +600,26 @@ + + + + + + + + + + + + + + + + + + - + diff --git a/Core/gb.h b/Core/gb.h index 7967b6f5..97a8069b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -661,6 +661,7 @@ struct GB_gameboy_internal_s { bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units double clock_multiplier; + GB_rumble_mode_t rumble_mode; uint32_t rumble_on_cycles; uint32_t rumble_off_cycles; diff --git a/Core/rumble.c b/Core/rumble.c index 5ac3d0d5..8cbe20d1 100644 --- a/Core/rumble.c +++ b/Core/rumble.c @@ -1,16 +1,27 @@ #include "rumble.h" #include "gb.h" +void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode) +{ + gb->rumble_mode = mode; + if (gb->rumble_callback) { + gb->rumble_callback(gb, 0); + } +} + void GB_handle_rumble(GB_gameboy_t *gb) { if (gb->rumble_callback) { + if (gb->rumble_mode == GB_RUMBLE_DISABLED) { + return; + } if (gb->cartridge_type->has_rumble) { if (gb->rumble_on_cycles + gb->rumble_off_cycles) { gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); gb->rumble_on_cycles = gb->rumble_off_cycles = 0; } } - else { + else if (gb->rumble_mode == GB_RUMBLE_ALL_GAMES) { unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80)); unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10)); diff --git a/Core/rumble.h b/Core/rumble.h index a378f2d8..eae9f372 100644 --- a/Core/rumble.h +++ b/Core/rumble.h @@ -3,6 +3,15 @@ #include "gb_struct_def.h" +typedef enum { + GB_RUMBLE_DISABLED, + GB_RUMBLE_CARTRIDGE_ONLY, + GB_RUMBLE_ALL_GAMES +} GB_rumble_mode_t; + +#ifdef GB_INTERNAL void GB_handle_rumble(GB_gameboy_t *gb); +#endif +void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode); #endif /* rumble_h */ From 0c91502859496c21e174095eb8a56d74a236c58a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 16:52:32 +0300 Subject: [PATCH 1081/1216] Remove log --- Cocoa/GBPreferencesWindow.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 71183e1c..31eebde5 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -332,9 +332,7 @@ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ joypad_wait = false; }); - - NSLog(@"%@", button); - + if (!button.isPressed) return; if (joystick_configuration_state == -1) return; if (joystick_configuration_state == GBButtonCount) return; From 05cf3656b80df7ebf91baabf13aec9322269d733 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 16:58:38 +0300 Subject: [PATCH 1082/1216] Fix libretro --- .github/workflows/sanity.yml | 1 + libretro/Makefile.common | 1 + libretro/libretro.c | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index d25a1807..6c795fbb 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -3,6 +3,7 @@ on: push jobs: sanity: + fail-fast: false strategy: matrix: os: [macos-latest, ubuntu-latest, ubuntu-16.04] diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 947b14ca..7f7688a3 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -13,6 +13,7 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/Core/joypad.c \ $(CORE_DIR)/Core/save_state.c \ $(CORE_DIR)/Core/random.c \ + $(CORE_DIR)/Core/rumble.c \ $(CORE_DIR)/libretro/agb_boot.c \ $(CORE_DIR)/libretro/cgb_boot.c \ $(CORE_DIR)/libretro/dmg_boot.c \ diff --git a/libretro/libretro.c b/libretro/libretro.c index 2ea1cb65..48310655 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -376,6 +376,7 @@ static void init_for_current_model(unsigned id) GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); GB_apu_set_sample_callback(&gameboy[i], audio_callback); GB_set_rumble_callback(&gameboy[i], rumble_callback); + GB_set_rumble_mode(&gameboy[i], GB_RUMBLE_CARTRIDGE_ONLY); /* todo: attempt to make these more generic */ From 5c9d50e25ff3fe1b1e4c0aa273dd07b4cce4b70c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 17:02:20 +0300 Subject: [PATCH 1083/1216] Fix job --- .github/workflows/sanity.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index 6c795fbb..f460931b 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -3,8 +3,8 @@ on: push jobs: sanity: - fail-fast: false strategy: + fail-fast: false matrix: os: [macos-latest, ubuntu-latest, ubuntu-16.04] cc: [gcc, clang] From 66112af37e330f322be3a6ff16bb768e16f0e0f3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 19:53:47 +0300 Subject: [PATCH 1084/1216] Fix PWM performence issue --- JoyKit/JOYController.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 2b218618..c18fbdb1 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -115,6 +115,7 @@ typedef struct __attribute__((packed)) { volatile double _rumblePWMRatio; bool _physicallyConnected; bool _logicallyConnected; + bool _rumblePWMThreadRunning; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device @@ -609,7 +610,7 @@ typedef struct __attribute__((packed)) { [NSThread sleepForTimeInterval:(1 - _rumblePWMRatio) / 10]; } [_rumblePWMThreadLock lock]; - [_rumblePWMThreadLock signal]; + _rumblePWMThreadRunning = false; [_rumblePWMThreadLock unlock]; } @@ -657,23 +658,23 @@ typedef struct __attribute__((packed)) { } else { if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { - if (_rumblePWMRatio == 0) { // PWM thread not running, start it. + [_rumblePWMThreadLock lock]; + if (!_rumblePWMThreadRunning) { // PWM thread not running, start it. if (amp != 0) { _rumblePWMRatio = amp; + _rumblePWMThreadRunning = true; [self performSelectorInBackground:@selector(pwmThread) withObject:nil]; } } else { if (amp == 0) { // Thread is running, signal it to stop - [_rumblePWMThreadLock lock]; _rumblePWMRatio = 0; - [_rumblePWMThreadLock wait]; - [_rumblePWMThreadLock unlock]; } else { _rumblePWMRatio = amp; } } + [_rumblePWMThreadLock unlock]; } else { [_rumbleElement setValue:amp * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; From 9f876e380c55251d16842d710b17c9e379ab65ba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 20:08:00 +0300 Subject: [PATCH 1085/1216] Offical WUP-028s require an activation sequence --- JoyKit/ControllerConfiguration.inc | 2 ++ JoyKit/JOYController.m | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index cdbdce35..cf327f91 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -120,6 +120,8 @@ hacksByName = @{ JOYConnectedUsage: @2, JOYConnectedUsagePage: @0xFF00, + + JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1], JOYSubElementStructs: @{ diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index c18fbdb1..b4810abe 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -19,7 +19,7 @@ static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage"; static NSString const *JOYRumbleMin = @"JOYRumbleMin"; static NSString const *JOYRumbleMax = @"JOYRumbleMax"; static NSString const *JOYSwapZRz = @"JOYSwapZRz"; - +static NSString const *JOYActivationReport = @"JOYActivationReport"; static NSMutableDictionary *controllers; // Physical controllers static NSMutableArray *exposedControllers; // Logical controllers @@ -691,15 +691,25 @@ typedef struct __attribute__((packed)) { { NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); NSDictionary *hacks = hacksByName[name]; + if (!hacks) { + hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))]; + } NSArray *filters = hacks[JOYReportIDFilters]; + JOYController *controller = nil; if (filters) { - JOYController *controller = [[JOYMultiplayerController alloc] initWithDevice:device - reportIDFilters:filters]; - [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; + controller = [[JOYMultiplayerController alloc] initWithDevice:device + reportIDFilters:filters]; } else { - [controllers setObject:[[JOYController alloc] initWithDevice:device] forKey:[NSValue valueWithPointer:device]]; + controller = [[JOYController alloc] initWithDevice:device]; } + + if (hacks[JOYActivationReport]) { + [controller sendReport:hacks[JOYActivationReport]]; + } + [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; + + } + (void)controllerRemoved:(IOHIDDeviceRef) device From 03ea6dc708cd6d72800a803bd80556e876afb022 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 20:44:55 +0300 Subject: [PATCH 1086/1216] Make builds possible without Xcode --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 5c702a13..9236a11a 100644 --- a/Makefile +++ b/Makefile @@ -125,6 +125,13 @@ endif ifeq ($(PLATFORM),Darwin) SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> $(NULL)) +ifeq ($(SYSROOT),) +SYSROOT := $(shell ls /Library/Developer/CommandLineTools/SDKs/ | grep 10 | tail -n 1) +endif +ifeq ($(SYSROOT),) +$(error Could not find a macOS SDK) +endif + CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 From 0f73282e4ee47e9f844499ace3f8538140c868a3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 20:54:28 +0300 Subject: [PATCH 1087/1216] Actually allow it --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9236a11a..03569e85 100644 --- a/Makefile +++ b/Makefile @@ -126,7 +126,7 @@ endif ifeq ($(PLATFORM),Darwin) SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> $(NULL)) ifeq ($(SYSROOT),) -SYSROOT := $(shell ls /Library/Developer/CommandLineTools/SDKs/ | grep 10 | tail -n 1) +SYSROOT := /Library/Developer/CommandLineTools/SDKs/$(shell ls /Library/Developer/CommandLineTools/SDKs/ | grep 10 | tail -n 1) endif ifeq ($(SYSROOT),) $(error Could not find a macOS SDK) From 4c1f073d20f4eb917709da896c4f33e34ac4042d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 20:59:19 +0300 Subject: [PATCH 1088/1216] Fix error report --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 03569e85..afc8d897 100644 --- a/Makefile +++ b/Makefile @@ -128,7 +128,7 @@ SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> $(NULL)) ifeq ($(SYSROOT),) SYSROOT := /Library/Developer/CommandLineTools/SDKs/$(shell ls /Library/Developer/CommandLineTools/SDKs/ | grep 10 | tail -n 1) endif -ifeq ($(SYSROOT),) +ifeq ($(SYSROOT),/Library/Developer/CommandLineTools/SDKs/) $(error Could not find a macOS SDK) endif From 6bcaffe27d3f1ddd73e2a3e6ef4d19a5c1677798 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 23:47:18 +0300 Subject: [PATCH 1089/1216] Fix sendReport on JOYMultiplayerControlle --- JoyKit/JOYMultiplayerController.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/JoyKit/JOYMultiplayerController.m b/JoyKit/JOYMultiplayerController.m index 0952c840..50840a17 100644 --- a/JoyKit/JOYMultiplayerController.m +++ b/JoyKit/JOYMultiplayerController.m @@ -4,6 +4,7 @@ - (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix; - (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value; - (void)disconnected; +- (void)sendReport:(NSData *)report; @end @implementation JOYMultiplayerController @@ -41,4 +42,9 @@ } } +- (void)sendReport:(NSData *)report +{ + [[_children firstObject] sendReport:report]; +} + @end From 60ad3160cf32ea86ea5a9662d67fdc4b8e93cf15 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Apr 2020 23:52:28 +0300 Subject: [PATCH 1090/1216] Fix an XIB oops --- Cocoa/Document.xib | 1 - 1 file changed, 1 deletion(-) diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index f1f2f5a1..e1c5fa09 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -9,7 +9,6 @@ - From 160282c42af29f80b7e271e319015a60d2ad6e30 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 30 Apr 2020 23:56:14 +0300 Subject: [PATCH 1091/1216] Fix WUP-028 on Catalina, make controller configuration compatible between macOS versions --- JoyKit/ControllerConfiguration.inc | 4 ++-- JoyKit/JOYAxes2D.m | 2 +- JoyKit/JOYAxis.m | 2 +- JoyKit/JOYButton.m | 2 +- JoyKit/JOYController.m | 6 +++++- JoyKit/JOYElement.h | 1 + JoyKit/JOYSubElement.m | 1 + 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index cf327f91..6c81e9f3 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -126,14 +126,14 @@ hacksByName = @{ JOYSubElementStructs: @{ // Rumble - @(1364): @[ + @(1357): @[ @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(2), @"size":@1, @"offset":@1, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(3), @"size":@1, @"offset":@2, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(4), @"size":@1, @"offset":@3, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, ], - @(11): @[ + @(4): @[ // Player 1 diff --git a/JoyKit/JOYAxes2D.m b/JoyKit/JOYAxes2D.m index 624ccef4..a1b91d2d 100644 --- a/JoyKit/JOYAxes2D.m +++ b/JoyKit/JOYAxes2D.m @@ -36,7 +36,7 @@ - (uint64_t)uniqueID { - return _element1.uniqueID; + return _element1.persistentUniqueID; } - (NSString *)description diff --git a/JoyKit/JOYAxis.m b/JoyKit/JOYAxis.m index 169eaee8..74a4563f 100644 --- a/JoyKit/JOYAxis.m +++ b/JoyKit/JOYAxis.m @@ -40,7 +40,7 @@ - (uint64_t)uniqueID { - return _element.uniqueID; + return _element.persistentUniqueID; } - (NSString *)description diff --git a/JoyKit/JOYButton.m b/JoyKit/JOYButton.m index 3e6026d1..a9042990 100644 --- a/JoyKit/JOYButton.m +++ b/JoyKit/JOYButton.m @@ -50,7 +50,7 @@ - (uint64_t)uniqueID { - return _element.uniqueID; + return _element.persistentUniqueID; } - (NSString *)description diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index b4810abe..10ec6e4e 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -173,13 +173,17 @@ typedef struct __attribute__((packed)) { JOYElement *previousAxisElement = nil; id previous = nil; + unsigned persistentUniqueID = 0; for (id _element in array) { if (_element == previous) continue; // Some elements are reported twice for some reason previous = _element; NSArray *elements = nil; JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; + /* Cookie is not persistent across macOS versions because Apple added kIOHIDElementTypeInput_NULL + in a backwards incompatible manner. We must maintain our own cookie-like ID. */ + element.persistentUniqueID = persistentUniqueID++; - NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)]; + NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.persistentUniqueID)]; bool isOutput = false; if (subElementDefs && element.uniqueID != element.parentID) { diff --git a/JoyKit/JOYElement.h b/JoyKit/JOYElement.h index 860c2476..4a6c3119 100644 --- a/JoyKit/JOYElement.h +++ b/JoyKit/JOYElement.h @@ -10,6 +10,7 @@ @property (readonly) uint16_t usage; @property (readonly) uint16_t usagePage; @property (readonly) uint32_t uniqueID; +@property unsigned persistentUniqueID; @property int32_t min; @property int32_t max; @property (readonly) int32_t reportID; diff --git a/JoyKit/JOYSubElement.m b/JoyKit/JOYSubElement.m index 55e289e4..01b121a7 100644 --- a/JoyKit/JOYSubElement.m +++ b/JoyKit/JOYSubElement.m @@ -34,6 +34,7 @@ _usage = usage; _usagePage = usagePage; _uniqueID = (uint32_t)((_parent.uniqueID << 16) | offset); + self.persistentUniqueID = (uint32_t)((_parent.persistentUniqueID << 16) | offset); _min = min; _max = max; _reportID = _parent.reportID; From 40562b1c54176bc52021ba6bfed56a1dcbd4b94e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 00:25:40 +0300 Subject: [PATCH 1092/1216] Revert "Fix WUP-028 on Catalina, make controller configuration compatible between macOS versions" This reverts commit 160282c42af29f80b7e271e319015a60d2ad6e30. --- JoyKit/ControllerConfiguration.inc | 4 ++-- JoyKit/JOYAxes2D.m | 2 +- JoyKit/JOYAxis.m | 2 +- JoyKit/JOYButton.m | 2 +- JoyKit/JOYController.m | 6 +----- JoyKit/JOYElement.h | 1 - JoyKit/JOYSubElement.m | 1 - 7 files changed, 6 insertions(+), 12 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index 6c81e9f3..cf327f91 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -126,14 +126,14 @@ hacksByName = @{ JOYSubElementStructs: @{ // Rumble - @(1357): @[ + @(1364): @[ @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(2), @"size":@1, @"offset":@1, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(3), @"size":@1, @"offset":@2, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(4), @"size":@1, @"offset":@3, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, ], - @(4): @[ + @(11): @[ // Player 1 diff --git a/JoyKit/JOYAxes2D.m b/JoyKit/JOYAxes2D.m index a1b91d2d..624ccef4 100644 --- a/JoyKit/JOYAxes2D.m +++ b/JoyKit/JOYAxes2D.m @@ -36,7 +36,7 @@ - (uint64_t)uniqueID { - return _element1.persistentUniqueID; + return _element1.uniqueID; } - (NSString *)description diff --git a/JoyKit/JOYAxis.m b/JoyKit/JOYAxis.m index 74a4563f..169eaee8 100644 --- a/JoyKit/JOYAxis.m +++ b/JoyKit/JOYAxis.m @@ -40,7 +40,7 @@ - (uint64_t)uniqueID { - return _element.persistentUniqueID; + return _element.uniqueID; } - (NSString *)description diff --git a/JoyKit/JOYButton.m b/JoyKit/JOYButton.m index a9042990..3e6026d1 100644 --- a/JoyKit/JOYButton.m +++ b/JoyKit/JOYButton.m @@ -50,7 +50,7 @@ - (uint64_t)uniqueID { - return _element.persistentUniqueID; + return _element.uniqueID; } - (NSString *)description diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 10ec6e4e..b4810abe 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -173,17 +173,13 @@ typedef struct __attribute__((packed)) { JOYElement *previousAxisElement = nil; id previous = nil; - unsigned persistentUniqueID = 0; for (id _element in array) { if (_element == previous) continue; // Some elements are reported twice for some reason previous = _element; NSArray *elements = nil; JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; - /* Cookie is not persistent across macOS versions because Apple added kIOHIDElementTypeInput_NULL - in a backwards incompatible manner. We must maintain our own cookie-like ID. */ - element.persistentUniqueID = persistentUniqueID++; - NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.persistentUniqueID)]; + NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)]; bool isOutput = false; if (subElementDefs && element.uniqueID != element.parentID) { diff --git a/JoyKit/JOYElement.h b/JoyKit/JOYElement.h index 4a6c3119..860c2476 100644 --- a/JoyKit/JOYElement.h +++ b/JoyKit/JOYElement.h @@ -10,7 +10,6 @@ @property (readonly) uint16_t usage; @property (readonly) uint16_t usagePage; @property (readonly) uint32_t uniqueID; -@property unsigned persistentUniqueID; @property int32_t min; @property int32_t max; @property (readonly) int32_t reportID; diff --git a/JoyKit/JOYSubElement.m b/JoyKit/JOYSubElement.m index 01b121a7..55e289e4 100644 --- a/JoyKit/JOYSubElement.m +++ b/JoyKit/JOYSubElement.m @@ -34,7 +34,6 @@ _usage = usage; _usagePage = usagePage; _uniqueID = (uint32_t)((_parent.uniqueID << 16) | offset); - self.persistentUniqueID = (uint32_t)((_parent.persistentUniqueID << 16) | offset); _min = min; _max = max; _reportID = _parent.reportID; From 5da80062d9de17a4fdcfdb993021da4c27115cc0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 00:34:00 +0300 Subject: [PATCH 1093/1216] Fix WUP-028 on Catalina, make controller configuration compatible between macOS versions --- JoyKit/JOYElement.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/JoyKit/JOYElement.m b/JoyKit/JOYElement.m index 56fcb369..6539c2e9 100644 --- a/JoyKit/JOYElement.m +++ b/JoyKit/JOYElement.m @@ -41,6 +41,24 @@ IOHIDElementRef parent = IOHIDElementGetParent(element); _parentID = parent? (uint32_t)IOHIDElementGetCookie(parent) : -1; _device = IOHIDElementGetDevice(element); + + /* Catalina added a new input type in a way that breaks cookie consistency across macOS versions, + we shall adjust our cookies to to compensate */ + unsigned cookieShift = 0, parentCookieShift = 0; + NSArray *nones = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(IOHIDElementGetDevice(element), + (__bridge CFDictionaryRef)@{@(kIOHIDElementTypeKey): @(kIOHIDElementTypeInput_NULL)}, + 0)); + for (id none in nones) { + if (IOHIDElementGetCookie((__bridge IOHIDElementRef) none) < _uniqueID) { + cookieShift++; + } + if (IOHIDElementGetCookie((__bridge IOHIDElementRef) none) < (int32_t)_parentID) { + parentCookieShift++; + } + } + + _uniqueID -= cookieShift; + _parentID -= parentCookieShift; } return self; } From ea18ba9335044c9d6b5d47ccc5adc8df1e2e1a01 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 15:50:22 +0300 Subject: [PATCH 1094/1216] Add rumble settings to libretro --- libretro/libretro.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 48310655..2baf3540 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -211,6 +211,7 @@ static const struct retro_variable vars_single[] = { { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { "sameboy_border", "Super Game Boy border; enabled|disabled" }, + { "sameboy_rumble", "Enable rumble; never|rumble-enabled games|all games" }, { NULL } }; @@ -226,6 +227,8 @@ static const struct retro_variable vars_dual[] = { { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; off|accurate|remove dc offset" }, + { "sameboy_rumble_1", "Enable rumble for Game Boy #1; never|rumble-enabled games|all games" }, + { "sameboy_rumble_2", "Enable rumble for Game Boy #2; never|rumble-enabled games|all games" }, { NULL } }; @@ -497,9 +500,20 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - else if (strcmp(var.value, "reduce_contrast") == 0) + else if (strcmp(var.value, "reduce contrast") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } + + var.key = "sameboy_rumble"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_DISABLED); + else if (strcmp(var.value, "rumble-enabled games") == 0) + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_CARTRIDGE_ONLY); + else if (strcmp(var.value, "all games") == 0) + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); + } var.key = "sameboy_high_pass_filter_mode"; var.value = NULL; @@ -557,7 +571,7 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - else if (strcmp(var.value, "reduce_contrast") == 0) + else if (strcmp(var.value, "reduce contrast") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } @@ -572,10 +586,32 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - else if (strcmp(var.value, "reduce_contrast") == 0) + else if (strcmp(var.value, "reduce contrast") == 0) GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } + + var.key = "sameboy_rumble_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_DISABLED); + else if (strcmp(var.value, "rumble-enabled games") == 0) + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_CARTRIDGE_ONLY); + else if (strcmp(var.value, "all games") == 0) + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); + } + + var.key = "sameboy_rumble_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_DISABLED); + else if (strcmp(var.value, "rumble-enabled games") == 0) + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_CARTRIDGE_ONLY); + else if (strcmp(var.value, "all games") == 0) + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_ALL_GAMES); + } var.key = "sameboy_high_pass_filter_mode_1"; var.value = NULL; From 5a56c3b8825719481f6eb2a779ea96392c37cbcd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 16:03:26 +0300 Subject: [PATCH 1095/1216] Style fixes --- Core/gb.c | 19 +-- Shaders/HQ2x.fsh | 42 ++++-- Shaders/OmniScale.fsh | 39 +++-- libretro/libretro.c | 321 ++++++++++++++++++++++++++++-------------- 4 files changed, 274 insertions(+), 147 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index a17a5a7f..a1f573c1 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -349,8 +349,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) #endif size_t needed_size = bank * 0x4000 + address + length; - if (needed_size > 1024 * 1024 * 32) - goto error; + if (needed_size > 1024 * 1024 * 32) goto error; if (gb->rom_size < needed_size) { gb->rom = realloc(gb->rom, needed_size); @@ -358,8 +357,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) gb->rom_size = needed_size; } - if (fread(gb->rom + (bank * 0x4000 + address), length, 1, f) != 1) - goto error; + if (fread(gb->rom + (bank * 0x4000 + address), length, 1, f) != 1) goto error; break; } @@ -378,16 +376,15 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) length = __builtin_bswap32(length); #endif size_t needed_size = address + length; - if (needed_size > 1024 * 1024 * 32) - goto error; + if (needed_size > 1024 * 1024 * 32) goto error; + if (gb->rom_size < needed_size) { gb->rom = realloc(gb->rom, needed_size); memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); gb->rom_size = needed_size; } - if (fread(gb->rom + address, length, 1, f) != 1) - goto error; + if (fread(gb->rom + address, length, 1, f) != 1) goto error; break; } @@ -406,8 +403,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) #endif while (count--) { READ(length); - if (fread(name, length, 1, f) != 1) - goto error; + if (fread(name, length, 1, f) != 1) goto error; name[length] = 0; READ(flag); // unused @@ -439,8 +435,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) #endif while (count--) { READ(length); - if (fread(name, length + 1, 1, f) != 1) - goto error; + if (fread(name, length + 1, 1, f) != 1) goto error; name[length] = 0; READ(flag); // unused diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index 3871db92..2e19fa65 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -62,40 +62,54 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (is_different(w7, w4)) pattern |= 64; if (is_different(w8, w4)) pattern |= 128; - if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) + if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) { return interp_2px(w4, 3.0, w3, 1.0); - if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) + } + if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) { return interp_2px(w4, 3.0, w1, 1.0); - if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) + } + if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) { return w4; + } if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) || P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) || P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) || - P(0xeb,0x8a)) && is_different(w3, w1)) + P(0xeb,0x8a)) && is_different(w3, w1)) { return interp_2px(w4, 3.0, w0, 1.0); - if (P(0x0b,0x08)) + } + if (P(0x0b,0x08)) { return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); - if (P(0x0b,0x02)) + } + if (P(0x0b,0x02)) { return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); - if (P(0x2f,0x2f)) + } + if (P(0x2f,0x2f)) { return interp_3px(w4, 1.04, w3, 1.0, w1, 1.0); - if (P(0xbf,0x37) || P(0xdb,0x13)) + } + if (P(0xbf,0x37) || P(0xdb,0x13)) { return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); - if (P(0xdb,0x49) || P(0xef,0x6d)) + } + if (P(0xdb,0x49) || P(0xef,0x6d)) { return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); - if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) + } + if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) { return interp_2px(w4, 3.0, w3, 1.0); - if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) + } + if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) { return interp_2px(w4, 3.0, w1, 1.0); - if (P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e)) + } + if (P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e)) { return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); + } if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || - P(0xdf,0xde) || P(0xdf,0x1e)) + P(0xdf,0xde) || P(0xdf,0x1e)) { return interp_2px(w4, 3.0, w0, 1.0); + } if (P(0x0a,0x00) || P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || - P(0x3b,0x1b)) + P(0x3b,0x1b)) { return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); + } return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); } diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index bb2b7d65..c76f7368 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -63,21 +63,27 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (is_different(w7, w4)) pattern |= 1 << 6; if (is_different(w8, w4)) pattern |= 1 << 7; - if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) + if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) { return mix(w4, w3, 0.5 - p.x); - if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) + } + if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) { return mix(w4, w1, 0.5 - p.y); - if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) + } + if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) { return w4; + } if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) || P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) || P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) || - P(0xeb,0x8a)) && is_different(w3, w1)) + P(0xeb,0x8a)) && is_different(w3, w1)) { return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); - if (P(0x0b,0x08)) + } + if (P(0x0b,0x08)) { return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); - if (P(0x0b,0x02)) + } + if (P(0x0b,0x02)) { return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); + } if (P(0x2f,0x2f)) { float dist = length(p - vec2(0.5)); float pixel_size = length(1.0 / (output_resolution / input_resolution)); @@ -142,7 +148,6 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); - } if (P(0x7e,0x2a) || P(0xef,0xab)) { @@ -169,15 +174,18 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); } - if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) + if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) { return mix(w4, w3, 0.5 - p.x); + } - if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) + if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) { return mix(w4, w1, 0.5 - p.y); + } if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || - P(0xdf,0xde) || P(0xdf,0x1e)) + P(0xdf,0xde) || P(0xdf,0x1e)) { return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + } if (P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || @@ -204,17 +212,20 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); } - if (P(0x0b,0x01)) + if (P(0x0b,0x01)) { return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); + } - if (P(0x0b,0x00)) + if (P(0x0b,0x00)) { return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); + } float dist = p.x + p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)); - if (dist > 0.5 + pixel_size / 2) + if (dist > 0.5 + pixel_size / 2) { return w4; + } /* We need more samples to "solve" this diagonal */ vec4 x0 = texture(image, position + vec2( -o.x * 2.0, -o.y * 2.0)); @@ -239,7 +250,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou pattern >>= 1; } - if (diagonal_bias <= 0) { + if (diagonal_bias <= 0) { vec4 r = mix(w1, w3, p.y - p.x + 0.5); if (dist < 0.5 - pixel_size / 2) { return r; diff --git a/libretro/libretro.c b/libretro/libretro.c index 2baf3540..fa478f52 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -370,8 +370,9 @@ static void init_for_current_model(unsigned id) snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Initializing as model: %s\n", model_name); log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - if (GB_load_boot_rom(&gameboy[i], buf)) + if (GB_load_boot_rom(&gameboy[i], buf)) { GB_load_boot_rom_from_buffer(&gameboy[i], boot_code, boot_length); + } GB_set_user_data(&gameboy[i], (void*)NULL); GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * ((model[i] == MODEL_SGB || model[i] == MODEL_SGB2) ? SGB_VIDEO_PIXELS : VIDEO_PIXELS))); @@ -386,8 +387,9 @@ static void init_for_current_model(unsigned id) GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); if (emulated_devices == 2) { GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); - if (link_cable_emulation) + if (link_cable_emulation) { set_link_cable_state(true); + } } struct retro_memory_descriptor descs[11]; @@ -492,56 +494,73 @@ static void check_variables() var.key = "sameboy_color_correction_mode"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) + if (strcmp(var.value, "off") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); - else if (strcmp(var.value, "correct curves") == 0) + } + else if (strcmp(var.value, "correct curves") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); - else if (strcmp(var.value, "emulate hardware") == 0) + } + else if (strcmp(var.value, "emulate hardware") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - else if (strcmp(var.value, "preserve brightness") == 0) + } + else if (strcmp(var.value, "preserve brightness") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - else if (strcmp(var.value, "reduce contrast") == 0) + } + else if (strcmp(var.value, "reduce contrast") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); + } } var.key = "sameboy_rumble"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "never") == 0) + if (strcmp(var.value, "never") == 0) { GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_DISABLED); - else if (strcmp(var.value, "rumble-enabled games") == 0) + } + else if (strcmp(var.value, "rumble-enabled games") == 0) { GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_CARTRIDGE_ONLY); - else if (strcmp(var.value, "all games") == 0) + } + else if (strcmp(var.value, "all games") == 0) { GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); + } } var.key = "sameboy_high_pass_filter_mode"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) + if (strcmp(var.value, "off") == 0) { GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); - else if (strcmp(var.value, "accurate") == 0) + } + else if (strcmp(var.value, "accurate") == 0) { GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); - else if (strcmp(var.value, "remove dc offset") == 0) + } + else if (strcmp(var.value, "remove dc offset") == 0) { GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + } } var.key = "sameboy_model"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum model new_model = model[0]; - if (strcmp(var.value, "Game Boy") == 0) + if (strcmp(var.value, "Game Boy") == 0) { new_model = MODEL_DMG; - else if (strcmp(var.value, "Game Boy Color") == 0) + } + else if (strcmp(var.value, "Game Boy Color") == 0) { new_model = MODEL_CGB; - else if (strcmp(var.value, "Game Boy Advance") == 0) + } + else if (strcmp(var.value, "Game Boy Advance") == 0) { new_model = MODEL_AGB; - else if (strcmp(var.value, "Super Game Boy") == 0) + } + else if (strcmp(var.value, "Super Game Boy") == 0) { new_model = MODEL_SGB; - else if (strcmp(var.value, "Super Game Boy 2") == 0) + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { new_model = MODEL_SGB2; - else + } + else { new_model = MODEL_AUTO; + } if (new_model != model[0]) { geometry_updated = true; @@ -553,104 +572,134 @@ static void check_variables() var.key = "sameboy_border"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "enabled") == 0) + if (strcmp(var.value, "enabled") == 0) { sgb_border = 1; - else if (strcmp(var.value, "disabled") == 0) + } + else if (strcmp(var.value, "disabled") == 0) { sgb_border = 0; + } } } else { var.key = "sameboy_color_correction_mode_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) + if (strcmp(var.value, "off") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); - else if (strcmp(var.value, "correct curves") == 0) + } + else if (strcmp(var.value, "correct curves") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); - else if (strcmp(var.value, "emulate hardware") == 0) + } + else if (strcmp(var.value, "emulate hardware") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - else if (strcmp(var.value, "preserve brightness") == 0) + } + else if (strcmp(var.value, "preserve brightness") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - else if (strcmp(var.value, "reduce contrast") == 0) + } + else if (strcmp(var.value, "reduce contrast") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); + } } var.key = "sameboy_color_correction_mode_2"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) + if (strcmp(var.value, "off") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_DISABLED); - else if (strcmp(var.value, "correct curves") == 0) + } + else if (strcmp(var.value, "correct curves") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_CORRECT_CURVES); - else if (strcmp(var.value, "emulate hardware") == 0) + } + else if (strcmp(var.value, "emulate hardware") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); - else if (strcmp(var.value, "preserve brightness") == 0) + } + else if (strcmp(var.value, "preserve brightness") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); - else if (strcmp(var.value, "reduce contrast") == 0) + } + else if (strcmp(var.value, "reduce contrast") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_REDUCE_CONTRAST); + } } var.key = "sameboy_rumble_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "never") == 0) + if (strcmp(var.value, "never") == 0) { GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_DISABLED); - else if (strcmp(var.value, "rumble-enabled games") == 0) + } + else if (strcmp(var.value, "rumble-enabled games") == 0) { GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_CARTRIDGE_ONLY); - else if (strcmp(var.value, "all games") == 0) + } + else if (strcmp(var.value, "all games") == 0) { GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); + } } var.key = "sameboy_rumble_2"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "never") == 0) + if (strcmp(var.value, "never") == 0) { GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_DISABLED); - else if (strcmp(var.value, "rumble-enabled games") == 0) + } + else if (strcmp(var.value, "rumble-enabled games") == 0) { GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_CARTRIDGE_ONLY); - else if (strcmp(var.value, "all games") == 0) + } + else if (strcmp(var.value, "all games") == 0) { GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_ALL_GAMES); + } } var.key = "sameboy_high_pass_filter_mode_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) + if (strcmp(var.value, "off") == 0) { GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); - else if (strcmp(var.value, "accurate") == 0) + } + else if (strcmp(var.value, "accurate") == 0) { GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); - else if (strcmp(var.value, "remove dc offset") == 0) + } + else if (strcmp(var.value, "remove dc offset") == 0) { GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + } } var.key = "sameboy_high_pass_filter_mode_2"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) + if (strcmp(var.value, "off") == 0) { GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_OFF); - else if (strcmp(var.value, "accurate") == 0) + } + else if (strcmp(var.value, "accurate") == 0) { GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_ACCURATE); - else if (strcmp(var.value, "remove dc offset") == 0) + } + else if (strcmp(var.value, "remove dc offset") == 0) { GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_REMOVE_DC_OFFSET); + } } var.key = "sameboy_model_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum model new_model = model[0]; - if (strcmp(var.value, "Game Boy") == 0) + if (strcmp(var.value, "Game Boy") == 0) { new_model = MODEL_DMG; - else if (strcmp(var.value, "Game Boy Color") == 0) + } + else if (strcmp(var.value, "Game Boy Color") == 0) { new_model = MODEL_CGB; - else if (strcmp(var.value, "Game Boy Advance") == 0) + } + else if (strcmp(var.value, "Game Boy Advance") == 0) { new_model = MODEL_AGB; - else if (strcmp(var.value, "Super Game Boy") == 0) + } + else if (strcmp(var.value, "Super Game Boy") == 0) { new_model = MODEL_SGB; - else if (strcmp(var.value, "Super Game Boy 2") == 0) + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { new_model = MODEL_SGB2; - else + } + else { new_model = MODEL_AUTO; + } if (model[0] != new_model) { model[0] = new_model; @@ -662,18 +711,24 @@ static void check_variables() var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum model new_model = model[1]; - if (strcmp(var.value, "Game Boy") == 0) + if (strcmp(var.value, "Game Boy") == 0) { new_model = MODEL_DMG; - else if (strcmp(var.value, "Game Boy Color") == 0) + } + else if (strcmp(var.value, "Game Boy Color") == 0) { new_model = MODEL_CGB; - else if (strcmp(var.value, "Game Boy Advance") == 0) + } + else if (strcmp(var.value, "Game Boy Advance") == 0) { new_model = MODEL_AGB; - else if (strcmp(var.value, "Super Game Boy") == 0) + } + else if (strcmp(var.value, "Super Game Boy") == 0) { new_model = MODEL_SGB; - else if (strcmp(var.value, "Super Game Boy 2") == 0) + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { new_model = MODEL_SGB; - else + } + else { new_model = MODEL_AUTO; + } if (model[1] != new_model) { model[1] = new_model; @@ -684,10 +739,12 @@ static void check_variables() var.key = "sameboy_screen_layout"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "top-down") == 0) + if (strcmp(var.value, "top-down") == 0) { screen_layout = LAYOUT_TOP_DOWN; - else + } + else { screen_layout = LAYOUT_LEFT_RIGHT; + } geometry_updated = true; } @@ -696,23 +753,29 @@ static void check_variables() var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool tmp = link_cable_emulation; - if (strcmp(var.value, "enabled") == 0) + if (strcmp(var.value, "enabled") == 0) { link_cable_emulation = true; - else + } + else { link_cable_emulation = false; - if (link_cable_emulation && link_cable_emulation != tmp) + } + if (link_cable_emulation && link_cable_emulation != tmp) { set_link_cable_state(true); - else if (!link_cable_emulation && link_cable_emulation != tmp) + } + else if (!link_cable_emulation && link_cable_emulation != tmp) { set_link_cable_state(false); + } } var.key = "sameboy_audio_output"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "Game Boy #1") == 0) + if (strcmp(var.value, "Game Boy #1") == 0) { audio_out = GB_1; - else + } + else { audio_out = GB_2; + } } } } @@ -721,20 +784,26 @@ void retro_init(void) { const char *dir = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) { snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); - else + } + else { snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); + } - if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) + if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) { snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); - else + } + else { snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); + } - if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) + if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) { log_cb = logging.log; - else + } + else { log_cb = fallback_log; + } } void retro_deinit(void) @@ -839,8 +908,9 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { - for (int i = 0; i < emulated_devices; i++) + for (int i = 0; i < emulated_devices; i++) { GB_reset(&gameboy[i]); + } } @@ -849,8 +919,9 @@ void retro_run(void) bool updated = false; - if (!initialized) + if (!initialized) { geometry_updated = false; + } if (geometry_updated) { struct retro_system_av_info info; @@ -859,22 +930,26 @@ void retro_run(void) geometry_updated = false; } - if (!frame_buf) + if (!frame_buf) { return; + } - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) { check_variables(); + } if (emulated_devices == 2) { GB_update_keys_status(&gameboy[0], 0); GB_update_keys_status(&gameboy[1], 1); } else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { - for (unsigned i = 0; i < 4; i++) + for (unsigned i = 0; i < 4; i++) { GB_update_keys_status(&gameboy[0], i); + } } - else + else { GB_update_keys_status(&gameboy[0], 0); + } vblank1_occurred = vblank2_occurred = false; signed delta = 0; @@ -911,16 +986,18 @@ void retro_run(void) } else { if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) { - if (sgb_border == 1) + if (sgb_border == 1) { video_cb(frame_buf, SGB_VIDEO_WIDTH, SGB_VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); + } else { int crop = SGB_VIDEO_WIDTH * ((SGB_VIDEO_HEIGHT - VIDEO_HEIGHT) / 2) + ((SGB_VIDEO_WIDTH - VIDEO_WIDTH) / 2); video_cb(frame_buf + crop, VIDEO_WIDTH, VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); } } - else + else { video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); + } } @@ -955,10 +1032,12 @@ bool retro_load_game(const struct retro_game_info *info) bool achievements = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); - if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) { log_cb(RETRO_LOG_INFO, "Rumble environment supported\n"); - else + } + else { log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); + } check_variables(); return true; @@ -966,8 +1045,9 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { - for (int i = 0; i < emulated_devices; i++) + for (int i = 0; i < emulated_devices; i++) { GB_free(&gameboy[i]); + } } unsigned retro_get_region(void) @@ -978,10 +1058,12 @@ unsigned retro_get_region(void) bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num_info) { - if (type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) + if (type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) { emulated_devices = 2; - else + } + else { return false; /* all other types are unhandled for now */ + } environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_dual); check_variables(); @@ -1012,10 +1094,12 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, bool achievements = true; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); - if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) { log_cb(RETRO_LOG_INFO, "Rumble environment supported\n"); - else + } + else { log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); + } check_variables(); return true; @@ -1048,8 +1132,9 @@ size_t retro_serialize_size(void) bool retro_serialize(void *data, size_t size) { - if (!initialized || !data) + if (!initialized || !data) { return false; + } size_t offset = 0; @@ -1096,19 +1181,23 @@ void *retro_get_memory_data(unsigned type) data = gameboy[0].ram; break; case RETRO_MEMORY_SAVE_RAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { data = gameboy[0].mbc_ram; - else + } + else { data = NULL; + } break; case RETRO_MEMORY_VIDEO_RAM: data = gameboy[0].vram; break; case RETRO_MEMORY_RTC: - if (gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) { data = GB_GET_SECTION(&gameboy[0], rtc); - else + } + else { data = NULL; + } break; default: break; @@ -1117,28 +1206,36 @@ void *retro_get_memory_data(unsigned type) else { switch (type) { case RETRO_MEMORY_GAMEBOY_1_SRAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { data = gameboy[0].mbc_ram; - else + } + else { data = NULL; + } break; case RETRO_MEMORY_GAMEBOY_2_SRAM: - if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) { data = gameboy[1].mbc_ram; - else + } + else { data = NULL; + } break; case RETRO_MEMORY_GAMEBOY_1_RTC: - if (gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) { data = GB_GET_SECTION(&gameboy[0], rtc); - else + } + else { data = NULL; + } break; case RETRO_MEMORY_GAMEBOY_2_RTC: - if (gameboy[1].cartridge_type->has_battery) + if (gameboy[1].cartridge_type->has_battery) { data = GB_GET_SECTION(&gameboy[1], rtc); - else + } + else { data = NULL; + } break; default: break; @@ -1157,19 +1254,23 @@ size_t retro_get_memory_size(unsigned type) size = gameboy[0].ram_size; break; case RETRO_MEMORY_SAVE_RAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { size = gameboy[0].mbc_ram_size; - else + } + else { size = 0; + } break; case RETRO_MEMORY_VIDEO_RAM: size = gameboy[0].vram_size; break; case RETRO_MEMORY_RTC: - if (gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) { size = GB_SECTION_SIZE(rtc); - else + } + else { size = 0; + } break; default: break; @@ -1178,24 +1279,30 @@ size_t retro_get_memory_size(unsigned type) else { switch (type) { case RETRO_MEMORY_GAMEBOY_1_SRAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { size = gameboy[0].mbc_ram_size; - else + } + else { size = 0; + } break; case RETRO_MEMORY_GAMEBOY_2_SRAM: - if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) { size = gameboy[1].mbc_ram_size; - else + } + else { size = 0; + } break; case RETRO_MEMORY_GAMEBOY_1_RTC: - if (gameboy[0].cartridge_type->has_battery) + if (gameboy[0].cartridge_type->has_battery) { size = GB_SECTION_SIZE(rtc); + } break; case RETRO_MEMORY_GAMEBOY_2_RTC: - if (gameboy[1].cartridge_type->has_battery) + if (gameboy[1].cartridge_type->has_battery) { size = GB_SECTION_SIZE(rtc); + } break; default: break; From 4bf252800e562545dc9457be461b356144036e52 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 18:08:44 +0300 Subject: [PATCH 1096/1216] Improve PWM quality, fix a crash --- JoyKit/JOYController.m | 37 +++++++++++++++++++++++-------------- JoyKit/JOYElement.h | 4 ++-- JoyKit/JOYElement.m | 10 ++++++---- JoyKit/JOYSubElement.m | 17 +++++++++-------- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index b4810abe..f9f07752 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -5,6 +5,8 @@ #import "JOYEmulatedButton.h" #include +#define PWM_RESOLUTION 16 + static NSString const *JOYAxisGroups = @"JOYAxisGroups"; static NSString const *JOYReportIDFilters = @"JOYReportIDFilters"; static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping"; @@ -130,7 +132,7 @@ typedef struct __attribute__((packed)) { _physicallyConnected = true; _logicallyConnected = true; - _device = device; + _device = (IOHIDDeviceRef)CFRetain(device); _serialSuffix = suffix; IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self); @@ -603,11 +605,17 @@ typedef struct __attribute__((packed)) { - (void)pwmThread { - while (_rumblePWMRatio != 0) { - [_rumbleElement setValue:1]; - [NSThread sleepForTimeInterval:_rumblePWMRatio / 10]; - [_rumbleElement setValue:0]; - [NSThread sleepForTimeInterval:(1 - _rumblePWMRatio) / 10]; + /* TODO: This does not handle correctly the case of having a multi-port controller where more than one controller + uses rumble. */ + unsigned rumbleCounter = 0; + while (self.connected) { + if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { + break; + } + rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); + if (rumbleCounter >= PWM_RESOLUTION) { + rumbleCounter -= PWM_RESOLUTION; + } } [_rumblePWMThreadLock lock]; _rumblePWMThreadRunning = false; @@ -659,6 +667,7 @@ typedef struct __attribute__((packed)) { else { if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { [_rumblePWMThreadLock lock]; + _rumblePWMRatio = amp; if (!_rumblePWMThreadRunning) { // PWM thread not running, start it. if (amp != 0) { _rumblePWMRatio = amp; @@ -666,14 +675,6 @@ typedef struct __attribute__((packed)) { [self performSelectorInBackground:@selector(pwmThread) withObject:nil]; } } - else { - if (amp == 0) { // Thread is running, signal it to stop - _rumblePWMRatio = 0; - } - else { - _rumblePWMRatio = amp; - } - } [_rumblePWMThreadLock unlock]; } else { @@ -771,4 +772,12 @@ typedef struct __attribute__((packed)) { IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL); IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode); } + +- (void)dealloc +{ + if (_device) { + CFRelease(_device); + _device = NULL; + } +} @end diff --git a/JoyKit/JOYElement.h b/JoyKit/JOYElement.h index 860c2476..0e917dd0 100644 --- a/JoyKit/JOYElement.h +++ b/JoyKit/JOYElement.h @@ -5,8 +5,8 @@ - (instancetype)initWithElement:(IOHIDElementRef)element; - (int32_t)value; - (NSData *)dataValue; -- (void)setValue:(uint32_t)value; -- (void)setDataValue:(NSData *)value; +- (IOReturn)setValue:(uint32_t)value; +- (IOReturn)setDataValue:(NSData *)value; @property (readonly) uint16_t usage; @property (readonly) uint16_t usagePage; @property (readonly) uint32_t uniqueID; diff --git a/JoyKit/JOYElement.m b/JoyKit/JOYElement.m index 6539c2e9..40503120 100644 --- a/JoyKit/JOYElement.m +++ b/JoyKit/JOYElement.m @@ -81,18 +81,20 @@ return [NSData dataWithBytes:IOHIDValueGetBytePtr(value) length:IOHIDValueGetLength(value)]; } -- (void)setValue:(uint32_t)value +- (IOReturn)setValue:(uint32_t)value { IOHIDValueRef ivalue = IOHIDValueCreateWithIntegerValue(NULL, (__bridge IOHIDElementRef)_element, 0, value); - IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); + IOReturn ret = IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); CFRelease(ivalue); + return ret; } -- (void)setDataValue:(NSData *)value +- (IOReturn)setDataValue:(NSData *)value { IOHIDValueRef ivalue = IOHIDValueCreateWithBytes(NULL, (__bridge IOHIDElementRef)_element, 0, value.bytes, value.length); - IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); + IOReturn ret = IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); CFRelease(ivalue); + return ret; } /* For use as a dictionary key */ diff --git a/JoyKit/JOYSubElement.m b/JoyKit/JOYSubElement.m index 55e289e4..c94badc7 100644 --- a/JoyKit/JOYSubElement.m +++ b/JoyKit/JOYSubElement.m @@ -65,15 +65,15 @@ return ret; } -- (void)setValue: (uint32_t) value +- (IOReturn)setValue: (uint32_t) value { NSMutableData *dataValue = [[_parent dataValue] mutableCopy]; - if (!dataValue) return; - if (_size > 32) return; - if (_size + (_offset % 8) > 32) return; + if (!dataValue) return -1; + if (_size > 32) return -1; + if (_size + (_offset % 8) > 32) return -1; size_t parentLength = dataValue.length; - if (_size > parentLength * 8) return; - if (_size + _offset >= parentLength * 8) return; + if (_size > parentLength * 8) return -1; + if (_size + _offset >= parentLength * 8) return -1; uint8_t *bytes = dataValue.mutableBytes; uint8_t temp[4] = {0,}; @@ -81,7 +81,7 @@ (*(uint32_t *)temp) &= ~((1 << (_size - 1)) << (_offset % 8)); (*(uint32_t *)temp) |= (value) << (_offset % 8); memcpy(bytes + _offset / 8, temp, (_offset + _size - 1) / 8 - _offset / 8 + 1); - [_parent setDataValue:dataValue]; + return [_parent setDataValue:dataValue]; } - (NSData *)dataValue @@ -90,9 +90,10 @@ return nil; } -- (void)setDataValue:(NSData *)data +- (IOReturn)setDataValue:(NSData *)data { [self doesNotRecognizeSelector:_cmd]; + return -1; } From 021cdb402dd837c1002c7a296a638c3c2f1e8f61 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 18:16:33 +0300 Subject: [PATCH 1097/1216] Various hacks for stopping the PWM thread when needed, important if we have a WUP-028 connected with more than one controller --- Cocoa/Document.m | 2 +- Cocoa/GBView.m | 2 ++ JoyKit/JOYController.h | 1 + JoyKit/JOYController.m | 13 ++++++++++++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 90a1dc84..ff47cd9f 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -381,7 +381,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); - [_view setRumble:false]; + [_view setRumble:0]; stopping = false; } diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index e733731d..d342497d 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -116,6 +116,7 @@ } [[NSNotificationCenter defaultCenter] removeObserver:self]; [lastController setRumbleAmplitude:0]; + [lastController _forceStopPWMThread]; [JOYController unregisterListener:self]; } - (instancetype)initWithCoder:(NSCoder *)coder @@ -302,6 +303,7 @@ if (![self.window isMainWindow]) return; if (controller != lastController) { [lastController setRumbleAmplitude:0]; + [lastController _forceStopPWMThread]; lastController = controller; } diff --git a/JoyKit/JOYController.h b/JoyKit/JOYController.h index 9ed7cf7b..9363e364 100644 --- a/JoyKit/JOYController.h +++ b/JoyKit/JOYController.h @@ -35,6 +35,7 @@ static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; - (NSArray *) hats; - (void)setRumbleAmplitude:(double)amp; - (void)setPlayerLEDs:(uint8_t)mask; +- (void)_forceStopPWMThread; // Hack @property (readonly, getter=isConnected) bool connected; @end diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index f9f07752..02680546 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -118,6 +118,7 @@ typedef struct __attribute__((packed)) { bool _physicallyConnected; bool _logicallyConnected; bool _rumblePWMThreadRunning; + volatile bool _forceStopPWMThread; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device @@ -608,7 +609,7 @@ typedef struct __attribute__((packed)) { /* TODO: This does not handle correctly the case of having a multi-port controller where more than one controller uses rumble. */ unsigned rumbleCounter = 0; - while (self.connected) { + while (self.connected && !_forceStopPWMThread) { if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { break; } @@ -619,6 +620,7 @@ typedef struct __attribute__((packed)) { } [_rumblePWMThreadLock lock]; _rumblePWMThreadRunning = false; + _forceStopPWMThread = false; [_rumblePWMThreadLock unlock]; } @@ -688,6 +690,15 @@ typedef struct __attribute__((packed)) { return _logicallyConnected && _physicallyConnected; } +- (void)_forceStopPWMThread +{ + [_rumblePWMThreadLock lock]; + if (_rumblePWMThreadRunning) { + _forceStopPWMThread = true; + } + [_rumblePWMThreadLock unlock]; +} + + (void)controllerAdded:(IOHIDDeviceRef) device { NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); From 285457852798cd1738a6f98068c4920d96ebdb98 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 18:26:48 +0300 Subject: [PATCH 1098/1216] Less ugly hacks --- Cocoa/GBView.m | 2 -- JoyKit/JOYController.h | 1 - JoyKit/JOYController.m | 8 +++++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index d342497d..e733731d 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -116,7 +116,6 @@ } [[NSNotificationCenter defaultCenter] removeObserver:self]; [lastController setRumbleAmplitude:0]; - [lastController _forceStopPWMThread]; [JOYController unregisterListener:self]; } - (instancetype)initWithCoder:(NSCoder *)coder @@ -303,7 +302,6 @@ if (![self.window isMainWindow]) return; if (controller != lastController) { [lastController setRumbleAmplitude:0]; - [lastController _forceStopPWMThread]; lastController = controller; } diff --git a/JoyKit/JOYController.h b/JoyKit/JOYController.h index 9363e364..9ed7cf7b 100644 --- a/JoyKit/JOYController.h +++ b/JoyKit/JOYController.h @@ -35,7 +35,6 @@ static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; - (NSArray *) hats; - (void)setRumbleAmplitude:(double)amp; - (void)setPlayerLEDs:(uint8_t)mask; -- (void)_forceStopPWMThread; // Hack @property (readonly, getter=isConnected) bool connected; @end diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 02680546..92662adb 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -607,7 +607,12 @@ typedef struct __attribute__((packed)) { - (void)pwmThread { /* TODO: This does not handle correctly the case of having a multi-port controller where more than one controller - uses rumble. */ + uses rumble. At least make sure any sibling controllers don't have their PWM thread running. */ + for (JOYController *controller in [JOYController allControllers]) { + if (controller != self && controller->_device == _device) { + [controller _forceStopPWMThread]; + } + } unsigned rumbleCounter = 0; while (self.connected && !_forceStopPWMThread) { if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { @@ -619,6 +624,7 @@ typedef struct __attribute__((packed)) { } } [_rumblePWMThreadLock lock]; + [_rumbleElement setValue:0]; _rumblePWMThreadRunning = false; _forceStopPWMThread = false; [_rumblePWMThreadLock unlock]; From 7e124e169eddb8453a1973b351ab76bd6ea9a515 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 18:28:06 +0300 Subject: [PATCH 1099/1216] Avoid races --- JoyKit/JOYController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 92662adb..0d88f288 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -703,6 +703,7 @@ typedef struct __attribute__((packed)) { _forceStopPWMThread = true; } [_rumblePWMThreadLock unlock]; + while (_rumblePWMThreadRunning); } + (void)controllerAdded:(IOHIDDeviceRef) device From 69fb2ad0a37dd40b1b7b46951336da1ef30d1621 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 18:46:00 +0300 Subject: [PATCH 1100/1216] Fix rumble on WUP-028 on ports other than 1 --- JoyKit/ControllerConfiguration.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index cf327f91..c8b49cc6 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -127,10 +127,10 @@ hacksByName = @{ // Rumble @(1364): @[ - @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, - @{@"reportID": @(2), @"size":@1, @"offset":@1, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, - @{@"reportID": @(3), @"size":@1, @"offset":@2, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, - @{@"reportID": @(4), @"size":@1, @"offset":@3, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(2), @"size":@1, @"offset":@8, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(3), @"size":@1, @"offset":@16, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(4), @"size":@1, @"offset":@24, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, ], @(11): @[ From c492022ae69541d80663033611a78a4a3e01583b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 19:31:04 +0300 Subject: [PATCH 1101/1216] Fix a deadlock --- JoyKit/JOYController.m | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 0d88f288..459896dd 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -35,6 +35,8 @@ static bool axesEmulateButtons = false; static bool axes2DEmulateButtons = false; static bool hatsEmulateButtons = false; +static NSLock *globalPWMThreadLock; + @interface JOYController () + (void)controllerAdded:(IOHIDDeviceRef) device; + (void)controllerRemoved:(IOHIDDeviceRef) device; @@ -111,9 +113,9 @@ typedef struct __attribute__((packed)) { JOYElement *_connectedElement; NSMutableDictionary *_iokitToJOY; NSString *_serialSuffix; - bool _isSwitch; // Does thie controller use the Switch protocol? + bool _isSwitch; // Does this controller use the Switch protocol? JOYSwitchPacket _lastSwitchPacket; - NSCondition *_rumblePWMThreadLock; + NSLock *_rumblePWMThreadLock; volatile double _rumblePWMRatio; bool _physicallyConnected; bool _logicallyConnected; @@ -149,7 +151,7 @@ typedef struct __attribute__((packed)) { _hatEmulatedButtons = [NSMutableDictionary dictionary]; _multiElements = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; - _rumblePWMThreadLock = [[NSCondition alloc] init]; + _rumblePWMThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; @@ -606,13 +608,6 @@ typedef struct __attribute__((packed)) { - (void)pwmThread { - /* TODO: This does not handle correctly the case of having a multi-port controller where more than one controller - uses rumble. At least make sure any sibling controllers don't have their PWM thread running. */ - for (JOYController *controller in [JOYController allControllers]) { - if (controller != self && controller->_device == _device) { - [controller _forceStopPWMThread]; - } - } unsigned rumbleCounter = 0; while (self.connected && !_forceStopPWMThread) { if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { @@ -678,9 +673,20 @@ typedef struct __attribute__((packed)) { _rumblePWMRatio = amp; if (!_rumblePWMThreadRunning) { // PWM thread not running, start it. if (amp != 0) { + /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more + than one controller uses rumble. At least make sure any sibling controllers don't have their + PWM thread running. */ + + [globalPWMThreadLock lock]; + for (JOYController *controller in [JOYController allControllers]) { + if (controller != self && controller->_device == _device) { + [controller _forceStopPWMThread]; + } + } _rumblePWMRatio = amp; _rumblePWMThreadRunning = true; [self performSelectorInBackground:@selector(pwmThread) withObject:nil]; + [globalPWMThreadLock unlock]; } } [_rumblePWMThreadLock unlock]; @@ -765,6 +771,7 @@ typedef struct __attribute__((packed)) { controllers = [NSMutableDictionary dictionary]; exposedControllers = [NSMutableArray array]; + globalPWMThreadLock = [[NSLock alloc] init]; NSArray *array = @[ CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), From e5302a9b1e6ce04a245619d971c4ea438fffb9cb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 1 May 2020 23:42:08 +0300 Subject: [PATCH 1102/1216] Set sane libretro defaults, add border settings (Closes #203), general libretro cleanup --- libretro/libretro.c | 194 +++++++++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 83 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index fa478f52..14bfaa81 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -29,13 +29,10 @@ static const char slash = '\\'; static const char slash = '/'; #endif -#define VIDEO_WIDTH 160 -#define VIDEO_HEIGHT 144 -#define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT) +#define MAX_VIDEO_WIDTH 256 +#define MAX_VIDEO_HEIGHT 224 +#define MAX_VIDEO_PIXELS (MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT) -#define SGB_VIDEO_WIDTH 256 -#define SGB_VIDEO_HEIGHT 224 -#define SGB_VIDEO_PIXELS (SGB_VIDEO_WIDTH * SGB_VIDEO_HEIGHT) #define RETRO_MEMORY_GAMEBOY_1_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) #define RETRO_MEMORY_GAMEBOY_1_RTC ((2 << 8) | RETRO_MEMORY_RTC) @@ -92,7 +89,6 @@ static unsigned emulated_devices = 1; static bool initialized = false; static unsigned screen_layout = 0; static unsigned audio_out = 0; -static unsigned sgb_border = 1; static bool geometry_updated = false; static bool link_cable_emulation = false; @@ -207,11 +203,11 @@ static retro_environment_t environ_cb; /* variables for single cart mode */ static const struct retro_variable vars_single[] = { - { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness|reduce contrast" }, - { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, + { "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, + { "sameboy_high_pass_filter_mode", "High-pass filter; accurate|remove dc offset|off" }, { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, - { "sameboy_border", "Super Game Boy border; enabled|disabled" }, - { "sameboy_rumble", "Enable rumble; never|rumble-enabled games|all games" }, + { "sameboy_border", "Display border; Super Game Boy only|always|never" }, + { "sameboy_rumble", "Enable rumble; rumble-enabled games|all games|never" }, { NULL } }; @@ -223,12 +219,12 @@ static const struct retro_variable vars_dual[] = { { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated model for Game Boy #1; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { "sameboy_model_2", "Emulated model for Game Boy #2; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, - { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; off|accurate|remove dc offset" }, - { "sameboy_rumble_1", "Enable rumble for Game Boy #1; never|rumble-enabled games|all games" }, - { "sameboy_rumble_2", "Enable rumble for Game Boy #2; never|rumble-enabled games|all games" }, + { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, + { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, + { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; accurate|remove dc offset|off" }, + { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; accurate|remove dc offset|off" }, + { "sameboy_rumble_1", "Enable rumble for Game Boy #1; rumble-enabled games|all games|never" }, + { "sameboy_rumble_2", "Enable rumble for Game Boy #2; rumble-enabled games|all games|never" }, { NULL } }; @@ -345,6 +341,52 @@ static void set_link_cable_state(bool state) } } +static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) +{ + const char *model_name = (char *[]){ + [GB_BOOT_ROM_DMG0] = "dmg0_boot", + [GB_BOOT_ROM_DMG] = "dmg_boot", + [GB_BOOT_ROM_MGB] = "mgb_boot", + [GB_BOOT_ROM_SGB] = "sgb_boot", + [GB_BOOT_ROM_SGB2] = "sgb2_boot", + [GB_BOOT_ROM_CGB0] = "cgb0_boot", + [GB_BOOT_ROM_CGB] = "cgb_boot", + [GB_BOOT_ROM_AGB] = "agb_boot", + }[type]; + + const uint8_t *boot_code = (const unsigned char *[]) + { + [GB_BOOT_ROM_DMG0] = dmg_boot, // dmg0 not implemented yet + [GB_BOOT_ROM_DMG] = dmg_boot, + [GB_BOOT_ROM_MGB] = dmg_boot, // mgb not implemented yet + [GB_BOOT_ROM_SGB] = sgb_boot, + [GB_BOOT_ROM_SGB2] = sgb2_boot, + [GB_BOOT_ROM_CGB0] = cgb_boot, // cgb0 not implemented yet + [GB_BOOT_ROM_CGB] = cgb_boot, + [GB_BOOT_ROM_AGB] = agb_boot, + }[type]; + + unsigned boot_length = (unsigned []){ + [GB_BOOT_ROM_DMG0] = dmg_boot_length, // dmg0 not implemented yet + [GB_BOOT_ROM_DMG] = dmg_boot_length, + [GB_BOOT_ROM_MGB] = dmg_boot_length, // mgb not implemented yet + [GB_BOOT_ROM_SGB] = sgb_boot_length, + [GB_BOOT_ROM_SGB2] = sgb2_boot_length, + [GB_BOOT_ROM_CGB0] = cgb_boot_length, // cgb0 not implemented yet + [GB_BOOT_ROM_CGB] = cgb_boot_length, + [GB_BOOT_ROM_AGB] = agb_boot_length, + }[type]; + + char buf[256]; + snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); + log_cb(RETRO_LOG_INFO, "Initializing as model: %s\n", model_name); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + + if (GB_load_boot_rom(gb, buf)) { + GB_load_boot_rom_from_buffer(gb, boot_code, boot_length); + } +} + static void init_for_current_model(unsigned id) { unsigned i = id; @@ -362,26 +404,17 @@ static void init_for_current_model(unsigned id) else { GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); } - const char *model_name = (const char *[]){"dmg", "cgb", "agb", "sgb", "sgb2"}[effective_model]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot, sgb_boot, sgb2_boot}[effective_model]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length, sgb_boot_length, sgb2_boot_length}[effective_model]; + + GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load); - char buf[256]; - snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); - log_cb(RETRO_LOG_INFO, "Initializing as model: %s\n", model_name); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - if (GB_load_boot_rom(&gameboy[i], buf)) { - GB_load_boot_rom_from_buffer(&gameboy[i], boot_code, boot_length); - } - GB_set_user_data(&gameboy[i], (void*)NULL); + /* When running multiple devices they are assumed to use the same resolution */ - GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * ((model[i] == MODEL_SGB || model[i] == MODEL_SGB2) ? SGB_VIDEO_PIXELS : VIDEO_PIXELS))); + GB_set_pixels_output(&gameboy[i], + (uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i)); GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); GB_apu_set_sample_callback(&gameboy[i], audio_callback); GB_set_rumble_callback(&gameboy[i], rumble_callback); - GB_set_rumble_mode(&gameboy[i], GB_RUMBLE_CARTRIDGE_ONLY); - /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); @@ -571,16 +604,23 @@ static void check_variables() var.key = "sameboy_border"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "enabled") == 0) { - sgb_border = 1; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); } - else if (strcmp(var.value, "disabled") == 0) { - sgb_border = 0; + else if (strcmp(var.value, "Super Game Boy only") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_SGB); } + else if (strcmp(var.value, "always") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_ALWAYS); + } + + geometry_updated = true; } } - else { + else { + GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); + GB_set_border_mode(&gameboy[1], GB_BORDER_NEVER); var.key = "sameboy_color_correction_mode_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { @@ -844,31 +884,24 @@ void retro_get_system_av_info(struct retro_system_av_info *info) if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { - geom.base_width = VIDEO_WIDTH; - geom.base_height = VIDEO_HEIGHT * emulated_devices; - geom.aspect_ratio = (double)VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); + geom.base_width = GB_get_screen_width(&gameboy[0]); + geom.base_height = GB_get_screen_height(&gameboy[0]) * emulated_devices; + geom.aspect_ratio = (double)GB_get_screen_width(&gameboy[0]) / (emulated_devices * GB_get_screen_height(&gameboy[0])); } else if (screen_layout == LAYOUT_LEFT_RIGHT) { - geom.base_width = VIDEO_WIDTH * emulated_devices; - geom.base_height = VIDEO_HEIGHT; - geom.aspect_ratio = ((double)VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; + geom.base_width = GB_get_screen_width(&gameboy[0]) * emulated_devices; + geom.base_height = GB_get_screen_height(&gameboy[0]); + geom.aspect_ratio = ((double)GB_get_screen_width(&gameboy[0]) * emulated_devices) / GB_get_screen_height(&gameboy[0]); } } else { - if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) { - geom.base_width = SGB_VIDEO_WIDTH; - geom.base_height = SGB_VIDEO_HEIGHT; - geom.aspect_ratio = (double)SGB_VIDEO_WIDTH / SGB_VIDEO_HEIGHT; - } - else { - geom.base_width = VIDEO_WIDTH; - geom.base_height = VIDEO_HEIGHT; - geom.aspect_ratio = (double)VIDEO_WIDTH / VIDEO_HEIGHT; - } + geom.base_width = GB_get_screen_width(&gameboy[0]); + geom.base_height = GB_get_screen_height(&gameboy[0]); + geom.aspect_ratio = (double)GB_get_screen_width(&gameboy[0]) / GB_get_screen_height(&gameboy[0]); } - geom.max_width = SGB_VIDEO_WIDTH * emulated_devices; - geom.max_height = SGB_VIDEO_HEIGHT * emulated_devices; + geom.max_width = MAX_VIDEO_WIDTH * emulated_devices; + geom.max_height = MAX_VIDEO_HEIGHT * emulated_devices; info->geometry = geom; info->timing = timing; @@ -969,35 +1002,30 @@ void retro_run(void) if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); + video_cb(frame_buf, + GB_get_screen_width(&gameboy[0]), + GB_get_screen_height(&gameboy[0]) * emulated_devices, + GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); } else if (screen_layout == LAYOUT_LEFT_RIGHT) { - /* use slow memcpy method for now */ - for (int index = 0; index < emulated_devices; index++) { - for (int y = 0; y < VIDEO_HEIGHT; y++) { - for (int x = 0; x < VIDEO_WIDTH; x++) { - frame_buf_copy[VIDEO_WIDTH * emulated_devices * y + (x + VIDEO_WIDTH * index)] = frame_buf[VIDEO_WIDTH * (y + VIDEO_HEIGHT * index) + x]; - } + unsigned pitch = GB_get_screen_width(&gameboy[0]) * emulated_devices; + unsigned pixels_per_device = GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]); + for (int y = 0; y < GB_get_screen_height(&gameboy[0]); y++) { + for (unsigned i = 0; i < emulated_devices; i++) { + memcpy(frame_buf_copy + y * pitch + GB_get_screen_width(&gameboy[0]) * i, + frame_buf + pixels_per_device * i + y * GB_get_screen_width(&gameboy[0]), + GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); } } - video_cb(frame_buf_copy, VIDEO_WIDTH * emulated_devices, VIDEO_HEIGHT, VIDEO_WIDTH * emulated_devices * sizeof(uint32_t)); + video_cb(frame_buf_copy, GB_get_screen_width(&gameboy[0]) * emulated_devices, GB_get_screen_height(&gameboy[0]), GB_get_screen_width(&gameboy[0]) * emulated_devices * sizeof(uint32_t)); } } - else { - if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) { - if (sgb_border == 1) { - video_cb(frame_buf, SGB_VIDEO_WIDTH, SGB_VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); - } - else { - int crop = SGB_VIDEO_WIDTH * ((SGB_VIDEO_HEIGHT - VIDEO_HEIGHT) / 2) + ((SGB_VIDEO_WIDTH - VIDEO_WIDTH) / 2); - - video_cb(frame_buf + crop, VIDEO_WIDTH, VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); - } - } - else { - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); - } + else { + video_cb(frame_buf, + GB_get_screen_width(&gameboy[0]), + GB_get_screen_height(&gameboy[0]), + GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); } @@ -1009,8 +1037,8 @@ bool retro_load_game(const struct retro_game_info *info) environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); check_variables(); - frame_buf = (uint32_t*)malloc(SGB_VIDEO_PIXELS *emulated_devices * sizeof(uint32_t)); - memset(frame_buf, 0, SGB_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); + frame_buf = (uint32_t *)malloc(MAX_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); + memset(frame_buf, 0, MAX_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { @@ -1068,11 +1096,11 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_dual); check_variables(); - frame_buf = (uint32_t*)malloc(emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); - frame_buf_copy = (uint32_t*)malloc(emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf = (uint32_t*)malloc(emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf_copy = (uint32_t*)malloc(emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); - memset(frame_buf, 0, emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); - memset(frame_buf_copy, 0, emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf, 0, emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf_copy, 0, emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { From 78e2b94cb5b4e16bea2e147dceaa822425fa08ea Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 May 2020 20:55:54 +0300 Subject: [PATCH 1103/1216] Rewrite the "Sub Elements" design into a more powerful Custom Report design that can overwrite an entire report structure of a sepcific report by its ID --- JoyKit/ControllerConfiguration.inc | 14 +- JoyKit/JOYController.m | 514 ++++++++++++++++------------- JoyKit/JOYFullReportElement.h | 10 + JoyKit/JOYFullReportElement.m | 73 ++++ JoyKit/JOYMultiplayerController.h | 2 +- JoyKit/JOYMultiplayerController.m | 6 +- 6 files changed, 384 insertions(+), 235 deletions(-) create mode 100644 JoyKit/JOYFullReportElement.h create mode 100644 JoyKit/JOYFullReportElement.m diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index c8b49cc6..35a6cff0 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -123,17 +123,17 @@ hacksByName = @{ JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1], - JOYSubElementStructs: @{ + JOYCustomReports: @{ // Rumble - @(1364): @[ + @(-17): @[ @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(2), @"size":@1, @"offset":@8, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(3), @"size":@1, @"offset":@16, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(4), @"size":@1, @"offset":@24, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, ], - @(11): @[ + @(33): @[ // Player 1 @@ -141,11 +141,11 @@ hacksByName = @{ @{@"reportID": @(1), @"size":@1, @"offset":@8, @"usagePage":@(kHIDPage_Button), @"usage":@1}, @{@"reportID": @(1), @"size":@1, @"offset":@9, @"usagePage":@(kHIDPage_Button), @"usage":@2}, - @{@"reportID": @(1), @"size":@1, @"offset":@10, @"usagePage":@(kHIDPage_Button), @"usage":@3}, - @{@"reportID": @(1), @"size":@1, @"offset":@11, @"usagePage":@(kHIDPage_Button), @"usage":@4}, + @{@"reportID": @(1), @"size":@1, @"offset":@10,@"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(1), @"size":@1, @"offset":@11,@"usagePage":@(kHIDPage_Button), @"usage":@4}, - @{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, - @{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, @{@"reportID": @(1), @"size":@1, @"offset":@14, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, @{@"reportID": @(1), @"size":@1, @"offset":@15, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 459896dd..131441ca 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -2,6 +2,8 @@ #import "JOYMultiplayerController.h" #import "JOYElement.h" #import "JOYSubElement.h" +#import "JOYFullReportElement.h" + #import "JOYEmulatedButton.h" #include @@ -12,7 +14,7 @@ static NSString const *JOYReportIDFilters = @"JOYReportIDFilters"; static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping"; static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping"; static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping"; -static NSString const *JOYSubElementStructs = @"JOYSubElementStructs"; +static NSString const *JOYCustomReports = @"JOYCustomReports"; static NSString const *JOYIsSwitch = @"JOYIsSwitch"; static NSString const *JOYRumbleUsage = @"JOYRumbleUsage"; static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage"; @@ -41,6 +43,8 @@ static NSLock *globalPWMThreadLock; + (void)controllerAdded:(IOHIDDeviceRef) device; + (void)controllerRemoved:(IOHIDDeviceRef) device; - (void)elementChanged:(IOHIDElementRef) element; +- (void)gotReport:(NSData *)report; + @end @interface JOYButton () @@ -86,6 +90,11 @@ static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)]; } +static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type, + uint32_t reportID, uint8_t *report, CFIndex reportLength) +{ + [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; +} typedef struct __attribute__((packed)) { uint8_t reportID; @@ -102,7 +111,8 @@ typedef struct __attribute__((packed)) { NSMutableDictionary *_axes; NSMutableDictionary *_axes2D; NSMutableDictionary *_hats; - NSMutableDictionary *> *_multiElements; + NSMutableDictionary *_fullReportElements; + NSMutableDictionary *> *_multiElements; // Button emulation NSMutableDictionary *_axisEmulatedButtons; @@ -121,14 +131,182 @@ typedef struct __attribute__((packed)) { bool _logicallyConnected; bool _rumblePWMThreadRunning; volatile bool _forceStopPWMThread; + + NSDictionary *_hacks; + NSMutableData *_lastReport; + + // Used when creating inputs + JOYElement *_previousAxisElement; + } -- (instancetype)initWithDevice:(IOHIDDeviceRef) device +- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks { - return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil]; + return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks]; } -- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix +-(void)createOutputForElement:(JOYElement *)element +{ + uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue]; + uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue]; + + if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { + if (_hacks[JOYRumbleMin]) { + element.min = [_hacks[JOYRumbleMin] unsignedIntValue]; + } + if (_hacks[JOYRumbleMax]) { + element.max = [_hacks[JOYRumbleMax] unsignedIntValue]; + } + _rumbleElement = element; + } +} + +-(void)createInputForElement:(JOYElement *)element +{ + uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue]; + uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue]; + + if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { + _connectedElement = element; + _logicallyConnected = element.value != element.min; + return; + } + + if (element.usagePage == kHIDPage_Button) { + button: { + JOYButton *button = [[JOYButton alloc] initWithElement: element]; + [_buttons setObject:button forKey:element]; + NSNumber *replacementUsage = _hacks[JOYButtonUsageMapping][@(button.usage)]; + if (replacementUsage) { + button.usage = [replacementUsage unsignedIntValue]; + } + return; + } + } + else if (element.usagePage == kHIDPage_GenericDesktop) { + NSDictionary *axisGroups = @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(kHIDUsage_GD_Rz): @(1), + }; + + axisGroups = _hacks[JOYAxisGroups] ?: axisGroups; + + switch (element.usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: { + + JOYElement *other = _previousAxisElement; + _previousAxisElement = element; + if (!other) goto single; + if (other.usage >= element.usage) goto single; + if (other.reportID != element.reportID) goto single; + if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; + if (other.parentID != element.parentID) goto single; + + JOYAxes2D *axes = nil; + if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) { + axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; + } + else { + axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; + } + NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)]; + if (replacementUsage) { + axes.usage = [replacementUsage unsignedIntValue]; + } + + [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; + [_axes removeObjectForKey:other]; + _previousAxisElement = nil; + _axes2D[other] = axes; + _axes2D[element] = axes; + + if (axes2DEmulateButtons) { + _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], + ]; + } + + /* + for (NSArray *group in axes2d) { + break; + IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; + IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; + if (IOHIDElementGetUsage(first) > element.usage) continue; + if (IOHIDElementGetUsage(second) > element.usage) continue; + if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; + if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; + if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; + + [axes2d removeObject:group]; + [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; + found = true; + break; + }*/ + break; + } + single: + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: { + JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; + [_axes setObject:axis forKey:element]; + + NSNumber *replacementUsage = _hacks[JOYAxisUsageMapping][@(axis.usage)]; + if (replacementUsage) { + axis.usage = [replacementUsage unsignedIntValue]; + } + + if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID]; + } + + if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID]; + } + + break; + } + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + case kHIDUsage_GD_SystemMainMenu: + goto button; + + case kHIDUsage_GD_Hatswitch: { + JOYHat *hat = [[JOYHat alloc] initWithElement: element]; + [_hats setObject:hat forKey:element]; + if (hatsEmulateButtons) { + _hatEmulatedButtons[@(hat.uniqueID)] = @[ + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], + ]; + } + break; + } + } + } +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks { self = [super init]; if (!self) return self; @@ -149,229 +327,109 @@ typedef struct __attribute__((packed)) { _axisEmulatedButtons = [NSMutableDictionary dictionary]; _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; _hatEmulatedButtons = [NSMutableDictionary dictionary]; - _multiElements = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; _rumblePWMThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; - NSDictionary *axisGroups = @{ - @(kHIDUsage_GD_X): @(0), - @(kHIDUsage_GD_Y): @(0), - @(kHIDUsage_GD_Z): @(1), - @(kHIDUsage_GD_Rx): @(2), - @(kHIDUsage_GD_Ry): @(2), - @(kHIDUsage_GD_Rz): @(1), - }; - NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); - NSDictionary *hacks = hacksByName[name]; - if (!hacks) { - hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))]; + _hacks = hacks; + _isSwitch = [_hacks[JOYIsSwitch] boolValue]; + NSDictionary *customReports = hacks[JOYCustomReports]; + + if (hacks[JOYCustomReports]) { + _multiElements = [NSMutableDictionary dictionary]; + _fullReportElements = [NSMutableDictionary dictionary]; + _lastReport = [NSMutableData dataWithLength:MAX( + MAX( + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] + ), + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] + )]; + IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + + for (NSNumber *_reportID in customReports) { + signed reportID = [_reportID intValue]; + bool isOutput = false; + if (reportID < 0) { + isOutput = true; + reportID = -reportID; + } + + JOYFullReportElement *element = [[JOYFullReportElement alloc] initWithDevice:device reportID:reportID]; + NSMutableArray *elements = [NSMutableArray array]; + for (NSDictionary *subElementDef in customReports[_reportID]) { + if (filter && subElementDef[@"reportID"] && ![filter containsObject:subElementDef[@"reportID"]]) continue; + JOYSubElement *subElement = [[JOYSubElement alloc] initWithRealElement:element + size:subElementDef[@"size"].unsignedLongValue + offset:subElementDef[@"offset"].unsignedLongValue + 8 // Compensate for the reportID + usagePage:subElementDef[@"usagePage"].unsignedLongValue + usage:subElementDef[@"usage"].unsignedLongValue + min:subElementDef[@"min"].unsignedIntValue + max:subElementDef[@"max"].unsignedIntValue]; + [elements addObject:subElement]; + if (isOutput) { + [self createOutputForElement:subElement]; + } + else { + [self createInputForElement:subElement]; + } + } + _multiElements[element] = elements; + if (!isOutput) { + _fullReportElements[@(reportID)] = element; + } + } } - axisGroups = hacks[JOYAxisGroups] ?: axisGroups; - _isSwitch = [hacks[JOYIsSwitch] boolValue]; - uint16_t rumbleUsagePage = (uint16_t)[hacks[JOYRumbleUsagePage] unsignedIntValue]; - uint16_t rumbleUsage = (uint16_t)[hacks[JOYRumbleUsage] unsignedIntValue]; - uint16_t connectedUsagePage = (uint16_t)[hacks[JOYConnectedUsagePage] unsignedIntValue]; - uint16_t connectedUsage = (uint16_t)[hacks[JOYConnectedUsage] unsignedIntValue]; - - JOYElement *previousAxisElement = nil; + id previous = nil; for (id _element in array) { if (_element == previous) continue; // Some elements are reported twice for some reason previous = _element; - NSArray *elements = nil; JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; - - NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)]; bool isOutput = false; - if (subElementDefs && element.uniqueID != element.parentID) { - elements = [NSMutableArray array]; - for (NSDictionary *virtualInput in subElementDefs) { - if (filter && virtualInput[@"reportID"] && ![filter containsObject:virtualInput[@"reportID"]]) continue; - [(NSMutableArray *)elements addObject:[[JOYSubElement alloc] initWithRealElement:element - size:virtualInput[@"size"].unsignedLongValue - offset:virtualInput[@"offset"].unsignedLongValue - usagePage:virtualInput[@"usagePage"].unsignedLongValue - usage:virtualInput[@"usage"].unsignedLongValue - min:virtualInput[@"min"].unsignedIntValue - max:virtualInput[@"max"].unsignedIntValue]]; - } - isOutput = IOHIDElementGetType((__bridge IOHIDElementRef)_element) == kIOHIDElementTypeOutput; - [_multiElements setObject:elements forKey:element]; + if (filter && ![filter containsObject:@(element.reportID)]) continue; + + switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { + /* Handled */ + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + break; + case kIOHIDElementTypeOutput: + isOutput = true; + break; + /* Ignored */ + default: + case kIOHIDElementTypeInput_ScanCodes: + case kIOHIDElementTypeInput_NULL: + case kIOHIDElementTypeFeature: + case kIOHIDElementTypeCollection: + continue; + } + if ((!isOutput && customReports[@(element.reportID)]) || + (isOutput && customReports[@(-element.reportID)])) continue; + + + if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; + + if (isOutput) { + [self createOutputForElement:element]; } else { - if (filter && ![filter containsObject:@(element.reportID)]) continue; - - switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { - /* Handled */ - case kIOHIDElementTypeInput_Misc: - case kIOHIDElementTypeInput_Button: - case kIOHIDElementTypeInput_Axis: - break; - case kIOHIDElementTypeOutput: - isOutput = true; - break; - /* Ignored */ - default: - case kIOHIDElementTypeInput_ScanCodes: - case kIOHIDElementTypeInput_NULL: - case kIOHIDElementTypeFeature: - case kIOHIDElementTypeCollection: - continue; - } - if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; - - elements = @[element]; + [self createInputForElement:element]; } _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element; - for (JOYElement *element in elements) { - if (isOutput) { - if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { - if (hacks[JOYRumbleMin]) { - element.min = [hacks[JOYRumbleMin] unsignedIntValue]; - } - if (hacks[JOYRumbleMax]) { - element.max = [hacks[JOYRumbleMax] unsignedIntValue]; - } - _rumbleElement = element; - } - continue; - } - else { - if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { - _connectedElement = element; - _logicallyConnected = element.value != element.min; - continue; - } - } - - if (element.usagePage == kHIDPage_Button) { - button: { - JOYButton *button = [[JOYButton alloc] initWithElement: element]; - [_buttons setObject:button forKey:element]; - NSNumber *replacementUsage = hacks[JOYButtonUsageMapping][@(button.usage)]; - if (replacementUsage) { - button.usage = [replacementUsage unsignedIntValue]; - } - continue; - } - } - else if (element.usagePage == kHIDPage_GenericDesktop) { - switch (element.usage) { - case kHIDUsage_GD_X: - case kHIDUsage_GD_Y: - case kHIDUsage_GD_Z: - case kHIDUsage_GD_Rx: - case kHIDUsage_GD_Ry: - case kHIDUsage_GD_Rz: { - - JOYElement *other = previousAxisElement; - previousAxisElement = element; - if (!other) goto single; - if (other.usage >= element.usage) goto single; - if (other.reportID != element.reportID) goto single; - if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; - if (other.parentID != element.parentID) goto single; - - JOYAxes2D *axes = nil; - if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [hacks[JOYSwapZRz] boolValue]) { - axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; - } - else { - axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; - } - NSNumber *replacementUsage = hacks[JOYAxes2DUsageMapping][@(axes.usage)]; - if (replacementUsage) { - axes.usage = [replacementUsage unsignedIntValue]; - } - - [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; - [_axes removeObjectForKey:other]; - previousAxisElement = nil; - _axes2D[other] = axes; - _axes2D[element] = axes; - - if (axes2DEmulateButtons) { - _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], - ]; - } - - /* - for (NSArray *group in axes2d) { - break; - IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; - IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; - if (IOHIDElementGetUsage(first) > element.usage) continue; - if (IOHIDElementGetUsage(second) > element.usage) continue; - if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; - if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; - if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; - - [axes2d removeObject:group]; - [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; - found = true; - break; - }*/ - break; - } - single: - case kHIDUsage_GD_Slider: - case kHIDUsage_GD_Dial: - case kHIDUsage_GD_Wheel: { - JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; - [_axes setObject:axis forKey:element]; - - NSNumber *replacementUsage = hacks[JOYAxisUsageMapping][@(axis.usage)]; - if (replacementUsage) { - axis.usage = [replacementUsage unsignedIntValue]; - } - - if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) { - _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID]; - } - - if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) { - _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID]; - } - - break; - } - case kHIDUsage_GD_DPadUp: - case kHIDUsage_GD_DPadDown: - case kHIDUsage_GD_DPadRight: - case kHIDUsage_GD_DPadLeft: - case kHIDUsage_GD_Start: - case kHIDUsage_GD_Select: - case kHIDUsage_GD_SystemMainMenu: - goto button; - - case kHIDUsage_GD_Hatswitch: { - JOYHat *hat = [[JOYHat alloc] initWithElement: element]; - [_hats setObject:hat forKey:element]; - if (hatsEmulateButtons) { - _hatEmulatedButtons[@(hat.uniqueID)] = @[ - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], - ]; - } - break; - } - } - } + if (_isSwitch && element.reportID == 0x30) { + /* This report does not match its report descriptor (The descriptor ignores several fields) so + we can't use the elements in it directly.*/ + continue; } + } [exposedControllers addObject:self]; @@ -383,6 +441,11 @@ typedef struct __attribute__((packed)) { } } + if (_hacks[JOYActivationReport]) { + [self sendReport:hacks[JOYActivationReport]]; + } + + return self; } @@ -441,6 +504,21 @@ typedef struct __attribute__((packed)) { return [_hats allValues]; } +- (void)gotReport:(NSData *)report +{ + JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)]; + if (!element) return; + [element updateValue:report]; + + NSArray *subElements = _multiElements[element]; + if (subElements) { + for (JOYElement *subElement in subElements) { + [self _elementChanged:subElement]; + } + return; + } +} + - (void)elementChanged:(IOHIDElementRef)element { JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))]; @@ -473,16 +551,6 @@ typedef struct __attribute__((packed)) { } } - { - NSArray *subElements = _multiElements[element]; - if (subElements) { - for (JOYElement *subElement in subElements) { - [self _elementChanged:subElement]; - } - return; - } - } - if (!self.connected) return; { JOYButton *button = _buttons[element]; @@ -602,7 +670,7 @@ typedef struct __attribute__((packed)) { _lastSwitchPacket.sequence &= 0xF; _lastSwitchPacket.command = 0x30; // LED _lastSwitchPacket.commandData[0] = mask; - [self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + //[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; } } @@ -723,15 +791,13 @@ typedef struct __attribute__((packed)) { JOYController *controller = nil; if (filters) { controller = [[JOYMultiplayerController alloc] initWithDevice:device - reportIDFilters:filters]; + reportIDFilters:filters + hacks:hacks]; } else { - controller = [[JOYController alloc] initWithDevice:device]; - } - - if (hacks[JOYActivationReport]) { - [controller sendReport:hacks[JOYActivationReport]]; + controller = [[JOYController alloc] initWithDevice:device hacks:hacks]; } + [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; diff --git a/JoyKit/JOYFullReportElement.h b/JoyKit/JOYFullReportElement.h new file mode 100644 index 00000000..808644e7 --- /dev/null +++ b/JoyKit/JOYFullReportElement.h @@ -0,0 +1,10 @@ +#import +#include +#include "JOYElement.h" + +@interface JOYFullReportElement : JOYElement +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID; +- (void)updateValue:(NSData *)value; +@end + + diff --git a/JoyKit/JOYFullReportElement.m b/JoyKit/JOYFullReportElement.m new file mode 100644 index 00000000..c8efb270 --- /dev/null +++ b/JoyKit/JOYFullReportElement.m @@ -0,0 +1,73 @@ +#import "JOYFullReportElement.h" +#include + +@implementation JOYFullReportElement +{ + IOHIDDeviceRef _device; + NSData *_data; + unsigned _reportID; + size_t _capacity; +} + +- (uint32_t)uniqueID +{ + return _reportID ^ 0xFFFF; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID +{ + if ((self = [super init])) { + _data = [[NSMutableData alloc] initWithLength:[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]]; + *(uint8_t *)(((NSMutableData *)_data).mutableBytes) = reportID; + _reportID = reportID; + _device = device; + } + return self; +} + +- (int32_t)value +{ + [self doesNotRecognizeSelector:_cmd]; + return 0; +} + +- (NSData *)dataValue +{ + return _data; +} + +- (IOReturn)setValue:(uint32_t)value +{ + [self doesNotRecognizeSelector:_cmd]; + return -1; +} + +- (IOReturn)setDataValue:(NSData *)value +{ + + [self updateValue:value]; + return IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, _reportID, [_data bytes], [_data length]);; +} + +- (void)updateValue:(NSData *)value +{ + _data = [value copy]; +} + +/* For use as a dictionary key */ + +- (NSUInteger)hash +{ + return self.uniqueID; +} + +- (BOOL)isEqual:(id)object +{ + return self.uniqueID == self.uniqueID; +} + +- (id)copyWithZone:(nullable NSZone *)zone; +{ + return self; +} +@end diff --git a/JoyKit/JOYMultiplayerController.h b/JoyKit/JOYMultiplayerController.h index 24004f54..44d74219 100644 --- a/JoyKit/JOYMultiplayerController.h +++ b/JoyKit/JOYMultiplayerController.h @@ -2,7 +2,7 @@ #include @interface JOYMultiplayerController : JOYController -- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters; +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters hacks:hacks; @end diff --git a/JoyKit/JOYMultiplayerController.m b/JoyKit/JOYMultiplayerController.m index 50840a17..a31ae921 100644 --- a/JoyKit/JOYMultiplayerController.m +++ b/JoyKit/JOYMultiplayerController.m @@ -1,7 +1,7 @@ #import "JOYMultiplayerController.h" @interface JOYController () -- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix; +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks; - (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value; - (void)disconnected; - (void)sendReport:(NSData *)report; @@ -12,7 +12,7 @@ NSMutableArray *_children; } -- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters hacks:(NSDictionary *)hacks; { self = [super init]; if (!self) return self; @@ -21,7 +21,7 @@ unsigned index = 1; for (NSArray *filter in reportIDFilters) { - JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index]]; + JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index] hacks:hacks]; [_children addObject:controller]; index++; } From 9413d68976c6f076c19acdd54c505ce5ccddf452 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 May 2020 22:14:53 +0300 Subject: [PATCH 1104/1216] Add support for wired Switch Pro Controller --- JoyKit/ControllerConfiguration.inc | 40 ++++++++++++++++++++++++++++++ JoyKit/JOYAxes2D.m | 4 +-- JoyKit/JOYController.m | 4 +++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index 35a6cff0..b7dbfe82 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -367,5 +367,45 @@ hacksByName = @{ AXES2D(1): @(JOYAxes2DUsageLeftStick), AXES2D(4): @(JOYAxes2DUsageRightStick), }, + + JOYCustomReports: @{ + @(0x30): @[ + + // For USB mode, which uses the wrong report descriptor + + @{@"reportID": @(1), @"size":@1, @"offset":@16, @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(1), @"size":@1, @"offset":@17, @"usagePage":@(kHIDPage_Button), @"usage":@4}, + @{@"reportID": @(1), @"size":@1, @"offset":@18, @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(1), @"size":@1, @"offset":@19, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + + // SR and SL not used on the Pro Controller + @{@"reportID": @(1), @"size":@1, @"offset":@22, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(1), @"size":@1, @"offset":@23, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(1), @"size":@1, @"offset":@24, @"usagePage":@(kHIDPage_Button), @"usage":@9}, + @{@"reportID": @(1), @"size":@1, @"offset":@25, @"usagePage":@(kHIDPage_Button), @"usage":@10}, + @{@"reportID": @(1), @"size":@1, @"offset":@26, @"usagePage":@(kHIDPage_Button), @"usage":@12}, + @{@"reportID": @(1), @"size":@1, @"offset":@27, @"usagePage":@(kHIDPage_Button), @"usage":@11}, + + @{@"reportID": @(1), @"size":@1, @"offset":@28, @"usagePage":@(kHIDPage_Button), @"usage":@13}, + @{@"reportID": @(1), @"size":@1, @"offset":@29, @"usagePage":@(kHIDPage_Button), @"usage":@14}, + + @{@"reportID": @(1), @"size":@1, @"offset":@32, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(1), @"size":@1, @"offset":@33, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + @{@"reportID": @(1), @"size":@1, @"offset":@34, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@35, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + + // SR and SL not used on the Pro Controller + @{@"reportID": @(1), @"size":@1, @"offset":@38, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(1), @"size":@1, @"offset":@39, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + + /* Sticks */ + @{@"reportID": @(1), @"size":@12, @"offset":@40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @0xFFF}, + @{@"reportID": @(1), @"size":@12, @"offset":@52, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @0xFFF, @"max": @0}, + + @{@"reportID": @(1), @"size":@12, @"offset":@64, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @0xFFF}, + @{@"reportID": @(1), @"size":@12, @"offset":@76, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0xFFF, @"max": @0}, + ], + }, }, }; diff --git a/JoyKit/JOYAxes2D.m b/JoyKit/JOYAxes2D.m index 624ccef4..272d34f9 100644 --- a/JoyKit/JOYAxes2D.m +++ b/JoyKit/JOYAxes2D.m @@ -57,8 +57,8 @@ uint16_t usage = element1.usage; _usage = JOYAxes2DUsageGeneric0 + usage - kHIDUsage_GD_X + 1; } - initialX = [_element1 value]; - initialY = [_element2 value]; + initialX = 0; + initialY = 0; minX = element1.max; minY = element2.max; maxX = element1.min; diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 131441ca..beb61a3e 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -445,6 +445,10 @@ typedef struct __attribute__((packed)) { [self sendReport:hacks[JOYActivationReport]]; } + if (_isSwitch) { + [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x04} length:2]]; + [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x02} length:2]]; + } return self; } From bb37f8d2f0a7dbfa5814e2d9faa897b52fdfd230 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 May 2020 23:04:12 +0300 Subject: [PATCH 1105/1216] Optimize Joypad initialization --- JoyKit/JOYElement.m | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/JoyKit/JOYElement.m b/JoyKit/JOYElement.m index 40503120..2432002a 100644 --- a/JoyKit/JOYElement.m +++ b/JoyKit/JOYElement.m @@ -1,5 +1,6 @@ #import "JOYElement.h" #include +#include @implementation JOYElement { @@ -28,6 +29,24 @@ _max = max; } +/* Ugly hack because IOHIDDeviceCopyMatchingElements is slow */ ++ (NSArray *) cookiesToSkipForDevice:(IOHIDDeviceRef)device +{ + id _device = (__bridge id)device; + NSMutableArray *ret = objc_getAssociatedObject(_device, _cmd); + if (ret) return ret; + + ret = [NSMutableArray array]; + NSArray *nones = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, + (__bridge CFDictionaryRef)@{@(kIOHIDElementTypeKey): @(kIOHIDElementTypeInput_NULL)}, + 0)); + for (id none in nones) { + [ret addObject:@(IOHIDElementGetCookie((__bridge IOHIDElementRef)none))]; + } + objc_setAssociatedObject(_device, _cmd, ret, OBJC_ASSOCIATION_RETAIN); + return ret; +} + - (instancetype)initWithElement:(IOHIDElementRef)element { if ((self = [super init])) { @@ -45,14 +64,12 @@ /* Catalina added a new input type in a way that breaks cookie consistency across macOS versions, we shall adjust our cookies to to compensate */ unsigned cookieShift = 0, parentCookieShift = 0; - NSArray *nones = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(IOHIDElementGetDevice(element), - (__bridge CFDictionaryRef)@{@(kIOHIDElementTypeKey): @(kIOHIDElementTypeInput_NULL)}, - 0)); - for (id none in nones) { - if (IOHIDElementGetCookie((__bridge IOHIDElementRef) none) < _uniqueID) { + + for (NSNumber *none in [JOYElement cookiesToSkipForDevice:_device]) { + if (none.unsignedIntValue < _uniqueID) { cookieShift++; } - if (IOHIDElementGetCookie((__bridge IOHIDElementRef) none) < (int32_t)_parentID) { + if (none.unsignedIntValue < (int32_t)_parentID) { parentCookieShift++; } } From 6910c3d24bb973a18973fc496c564df86293dbbc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 May 2020 20:23:37 +0300 Subject: [PATCH 1106/1216] Complete DualShock 3 support --- JoyKit/ControllerConfiguration.inc | 66 +++++++++++++++++++ JoyKit/JOYController.m | 102 ++++++++++++++++++++++------- 2 files changed, 143 insertions(+), 25 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index b7dbfe82..ea3ba9a4 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -408,4 +408,70 @@ hacksByName = @{ ], }, }, + + JOYIgnoredReports: @(0x30), // Ignore the real 0x30 report as it's broken + + @"PLAYSTATION(R)3 Controller": @{ // DualShock 3 + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(3), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageSelect), + BUTTON(2): @(JOYButtonUsageL3), + BUTTON(3): @(JOYButtonUsageR3), + BUTTON(4): @(JOYButtonUsageStart), + BUTTON(5): @(JOYButtonUsageDPadUp), + BUTTON(6): @(JOYButtonUsageDPadRight), + BUTTON(7): @(JOYButtonUsageDPadDown), + BUTTON(8): @(JOYButtonUsageDPadLeft), + BUTTON(9): @(JOYButtonUsageL2), + BUTTON(10): @(JOYButtonUsageR2), + BUTTON(11): @(JOYButtonUsageL1), + BUTTON(12): @(JOYButtonUsageR1), + BUTTON(13): @(JOYButtonUsageX), + BUTTON(14): @(JOYButtonUsageA), + BUTTON(15): @(JOYButtonUsageB), + BUTTON(16): @(JOYButtonUsageY), + BUTTON(17): @(JOYButtonUsageHome), + }, + + JOYAxisUsageMapping: @{ + AXIS(4): @(JOYAxisUsageL1), + AXIS(5): @(JOYAxisUsageR1), + AXIS(8): @(JOYAxisUsageL2), + AXIS(9): @(JOYAxisUsageR2), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(3): @(JOYAxes2DUsageRightStick), + }, + + JOYCustomReports: @{ + @(0x01): @[ + /* Pressure sensitive inputs */ + @{@"reportID": @(1), @"size":@8, @"offset":@(13 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(14 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(15 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(16 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(17 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Dial), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(18 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Wheel), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(19 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(20 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(21 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(22 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(23 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(24 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + ] + }, + + JOYIsDualShock3: @YES, + }, + }; diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index beb61a3e..015737ee 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -24,6 +24,8 @@ static NSString const *JOYRumbleMin = @"JOYRumbleMin"; static NSString const *JOYRumbleMax = @"JOYRumbleMax"; static NSString const *JOYSwapZRz = @"JOYSwapZRz"; static NSString const *JOYActivationReport = @"JOYActivationReport"; +static NSString const *JOYIgnoredReports = @"JOYIgnoredReports"; +static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3"; static NSMutableDictionary *controllers; // Physical controllers static NSMutableArray *exposedControllers; // Logical controllers @@ -104,6 +106,30 @@ typedef struct __attribute__((packed)) { uint8_t commandData[26]; } JOYSwitchPacket; +typedef struct __attribute__((packed)) { + uint8_t reportID; + uint8_t padding; + uint8_t rumbleRightDuration; + uint8_t rumbleRightStrength; + uint8_t rumbleLeftDuration; + uint8_t rumbleLeftStrength; + uint32_t padding2; + uint8_t ledsEnabled; + struct { + uint8_t timeEnabled; + uint8_t dutyLength; + uint8_t enabled; + uint8_t dutyOff; + uint8_t dutyOnn; + } __attribute__((packed)) led[5]; + uint8_t padding3[13]; +} JOYDualShock3Output; + +typedef union { + JOYSwitchPacket switchPacket; + JOYDualShock3Output ds3Output; +} JOYVendorSpecificOutput; + @implementation JOYController { IOHIDDeviceRef _device; @@ -124,7 +150,8 @@ typedef struct __attribute__((packed)) { NSMutableDictionary *_iokitToJOY; NSString *_serialSuffix; bool _isSwitch; // Does this controller use the Switch protocol? - JOYSwitchPacket _lastSwitchPacket; + bool _isDualShock3; // Does this controller use DS3 outputs? + JOYVendorSpecificOutput _lastVendorSpecificOutput; NSLock *_rumblePWMThreadLock; volatile double _rumblePWMRatio; bool _physicallyConnected; @@ -335,6 +362,8 @@ typedef struct __attribute__((packed)) { _hacks = hacks; _isSwitch = [_hacks[JOYIsSwitch] boolValue]; + _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue]; + NSDictionary *customReports = hacks[JOYCustomReports]; if (hacks[JOYCustomReports]) { @@ -384,6 +413,11 @@ typedef struct __attribute__((packed)) { } id previous = nil; + NSSet *ignoredReports = nil; + if (hacks[ignoredReports]) { + ignoredReports = [NSSet setWithArray:hacks[ignoredReports]]; + } + for (id _element in array) { if (_element == previous) continue; // Some elements are reported twice for some reason previous = _element; @@ -409,8 +443,8 @@ typedef struct __attribute__((packed)) { case kIOHIDElementTypeCollection: continue; } - if ((!isOutput && customReports[@(element.reportID)]) || - (isOutput && customReports[@(-element.reportID)])) continue; + if ((!isOutput && [ignoredReports containsObject:@(element.reportID)]) || + (isOutput && [ignoredReports containsObject:@(-element.reportID)])) continue; if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; @@ -423,13 +457,6 @@ typedef struct __attribute__((packed)) { } _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element; - - if (_isSwitch && element.reportID == 0x30) { - /* This report does not match its report descriptor (The descriptor ignores several fields) so - we can't use the elements in it directly.*/ - continue; - } - } [exposedControllers addObject:self]; @@ -450,6 +477,20 @@ typedef struct __attribute__((packed)) { [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x02} length:2]]; } + if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){ + .reportID = 1, + .led = { + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, + {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOnn = 0}, + } + }; + + } + return self; } @@ -653,7 +694,7 @@ typedef struct __attribute__((packed)) { } } _physicallyConnected = false; - [self setRumbleAmplitude:0]; // Stop the rumble thread. + [self _forceStopPWMThread]; // Stop the rumble thread. [exposedControllers removeObject:self]; _device = nil; } @@ -669,13 +710,18 @@ typedef struct __attribute__((packed)) { { mask &= 0xF; if (_isSwitch) { - _lastSwitchPacket.reportID = 0x1; // Rumble and LEDs - _lastSwitchPacket.sequence++; - _lastSwitchPacket.sequence &= 0xF; - _lastSwitchPacket.command = 0x30; // LED - _lastSwitchPacket.commandData[0] = mask; + _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED + _lastVendorSpecificOutput.switchPacket.commandData[0] = mask; //[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; } + else if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output.reportID = 1; + _lastVendorSpecificOutput.ds3Output.ledsEnabled = mask << 1; + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; + } } - (void)pwmThread @@ -727,17 +773,23 @@ typedef struct __attribute__((packed)) { lowAmp = 0; } - _lastSwitchPacket.rumbleData[0] = _lastSwitchPacket.rumbleData[4] = highFreq & 0xFF; - _lastSwitchPacket.rumbleData[1] = _lastSwitchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); - _lastSwitchPacket.rumbleData[2] = _lastSwitchPacket.rumbleData[6] = lowFreq; - _lastSwitchPacket.rumbleData[3] = _lastSwitchPacket.rumbleData[7] = lowAmp; + _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; + _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); + _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; + _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; - _lastSwitchPacket.reportID = 0x10; // Rumble only - _lastSwitchPacket.sequence++; - _lastSwitchPacket.sequence &= 0xF; - _lastSwitchPacket.command = 0; // LED - [self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0; // LED + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + } + else if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output.reportID = 1; + _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = amp? 0xff : 0; + _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = amp * 0xff; + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; } else { if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { From 19126df7f4b69ca8001b6d2ca6b2c703b7625195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sun, 3 May 2020 22:41:56 +0200 Subject: [PATCH 1107/1216] Save 8 bytes in the CGB boot ROM --- BootROMs/cgb_boot.asm | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 6ae869b2..618e11a4 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -984,14 +984,13 @@ BrightenColor: and $1F cp $1F jr nz, .blueNotMaxed - res 0, c + dec c .blueNotMaxed ; Is green maxed? ld a, e - and $E0 cp $E0 - jr nz, .greenNotMaxed + jr c, .greenNotMaxed ld a, d and $3 cp $3 @@ -1007,18 +1006,13 @@ BrightenColor: 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 - + ; add de, bc + ; ld [hli], de ld a, e + add c ld [hli], a ld a, d + adc b ld [hli], a ret @@ -1155,7 +1149,7 @@ ReplaceColorInAllPalettes: dec c jr nz, .loop ret - + LoadDMGTilemap: push af call WaitFrame From cb738190be6abca03d1cd3265440908107168a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sun, 3 May 2020 22:44:13 +0200 Subject: [PATCH 1108/1216] Add a 2bpp CGB boot ROM logo, pending palettes --- BootROMs/SameBoyLogo.png | Bin 14763 -> 479 bytes BootROMs/cgb_boot.asm | 116 ++++++++++++++++++++++++++------------- BootROMs/pb12.py | 68 +++++++++++++++++++++++ Makefile | 13 ++--- 4 files changed, 152 insertions(+), 45 deletions(-) create mode 100644 BootROMs/pb12.py diff --git a/BootROMs/SameBoyLogo.png b/BootROMs/SameBoyLogo.png index 4bc9706080a1d5e161d506f2d2be6d9c8e1446d4..c7cfc087dbf7a26a2969fc463d116b9e28eb58ac 100644 GIT binary patch delta 465 zcmV;?0WSWlbKe7y7=Ho-0000_WgT_^000b7OjJex|Nnn~fBygg000170)~4400E9k zL_t(2&yA8XZo@DPMHzU|sHMA(LKiteI>gABqv&c1I&`dpB6F{x05ZBr`2CFIphJcl zW-MEeXKQ3lQsjN*%!4hpQ&S3sG} z^6VxHz^3k}8BhsS(Jj@7f)Dc4Cz~FVo7>Rf2rx_^Q}w%=4I_(c^4B(0xOv*U`OvMd zuWE)kbmkB!&3|3Hv*;eF4m9Dn!u~ zO32V{^YirP=K~A~#fBD}H?nKUF%Iq4y9kbaYQ6(34paiPDNr^e8o<%(0$d4jOWOm$ zj=5L>FZ$J|Bc1pQc=KLvfL`!fZ&qn_HC(2BxEwLy5=#35u>6jP-U$>M#%2~9leL~x z*UVWtYJLF4^3nnX&}Ra14CW{k86!XU`$=!6=YQ&Hl>c47@!eI<`B+HV00000NkvXX Hu0mjf?upU~ literal 14763 zcmeI3Z)_7~9LEp;L>R#cCPQJ-vp}L?dw1P+TaUJ6w1ZCQoTJDl5V+kv-7ahIj=S61 zO_L2wTtG2om>^;#z5o=>e1nl_l!QP+{1Y%lvWyUvCyV*4SD>(o!B#*4lw5lhSP$6yrGkYelFckP%>l6tF}(lS-*eTLzlZbrYHPhO(_WV28J1(XYB%Q>cwS&xI`?sf zDqs{a#dBt|E+X}i|_CWbh?!HobejwG%v4a$ujHol5hh@r{WGU~_yN#qX zHCbXn5~P4?nb60T^+BYuSl(K8xKygF)3h3T;2q?Mly#c%UL7zkz|^`82{iP;nJg+d zdA(D!G_zBi=DzdY)84(MBnpdDG!?sNS{ukH)7b2utj#Hf>xFfR zB*`jEa>QWu%c3RTPnIHWX&y~h62148rJzJ|$dZuIjFf1Fl$6*37+viMFoi}$>6D$& zsG63jN}w?kf(;%;kp(Fs*Cu!%x@&ljck@1$b&C?uyA$m-{yMP+NIt2~!4Zq0_9>|& zn$&GqelE5AfT>p3Ou#MB%-lr|DZ#ZNi$`rsA9re>qTl1R-$t*tpnSNTPHh` zigUi`qG1Ai`vqN-`!9bW<TSb#(f(3z!Ma{mtdKW1TuC$Hvd`Zvj&<-+0fcINzG`29DQzxwG%Fd9Y>g;z=bLst zb6$8@IHuB`vd*@G)61hqq$6j{t}*go-6?@=D|}{vk1I_6d8N2I@_XnZ2ZcL&$cfQ( zWDt}FHYT{Rh)@B+h0?&r1Q!+&Dj>K}8rYcN!XiQi1Q$vJ8xveuM5ut^LTO-Qf(wfX z6%bq~4QxzsVG*GMf(xaAjR`I+B2++dp){~D!G%SH3J5Ng1~w+Ru!v9r!G+Sm#sn7@ z5h@_KP#V~n;KCw81q2sL0~-@uSVX9R;6iC&V}c8d2o(@qC=F~(aA6Ul0)h*rfsF|+ zEFx4uaG^A?F~NmJgbD~Qlm<2?xUh&&0l|gRz{Ug@77;2SxKJ9{nBc-9LIngDN&_3; z6IVsy4M3p6*Z$J*{k}Iw7w&=Y2GUZrB}P$w@Lj@zw<&7kUwFMnQQa&>UEfMk!iN+! zUmMzTdKJ{(X^hmzGozc%p4k0#ebwokXYXF{E~GCF9sKjqqe};_ZU10k-ppA~y?4qN zd1Bepi)fZq`s#8JI9X?ogR5z*!0Mf_cwl)ur4faY3mu=I6C;s&nthpddy#4 zIeYNp`MYzMpW6M$@R9Ycx0jE6KKtg~TjJwu+?Q8h*{7WJ4d1%W&;9Gn#NR)yT<}X| q|J65BRq~ZvM=n-=HTda`G&Q(G{(bH7&EG-_RAWO^WO(J)9sdD_A1D9- diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 618e11a4..68376150 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -87,20 +87,24 @@ ELSE .tilemapRowLoop + call .write_with_palette + + ; Repeat the 3 tiles common between E and B. This saves 27 bytes after + ; compression, with a cost of 17 bytes of code. push af - ; Switch to second VRAM Bank - ld a, 1 - ldh [$4F], a - ld [hl], 8 - ; Switch to back first VRAM Bank - xor a - ldh [$4F], a + sub $20 + sub $3 + jr nc, .notspecial + add $20 + call .write_with_palette + dec c +.notspecial pop af - ldi [hl], a - add d + + add d ; d = 3 for SameBoy logo, d = 1 for Nintendo logo dec c jr nz, .tilemapRowLoop - sub 47 + sub 44 push de ld de, $10 add hl, de @@ -116,6 +120,19 @@ ELSE ld l, $a7 ld bc, $0107 jr .tilemapRowLoop + +.write_with_palette + push af + ; Switch to second VRAM Bank + ld a, 1 + ldh [$4F], a + ld [hl], 8 + ; Switch to back first VRAM Bank + xor a + ldh [$4F], a + pop af + ldi [hl], a + ret .endTilemap ENDC @@ -532,7 +549,7 @@ TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SameBoyLogo: - incbin "SameBoyLogo.pb8" + incbin "SameBoyLogo.pb12" AnimationColors: dw $7FFF ; White @@ -634,30 +651,28 @@ ReadCGBLogoHalfTile: ld a, e ret -; LoadTileset using PB8 codec, 2019 Damian Yerrick -; -; The logo is compressed using PB8, a form of RLE with unary-coded -; run lengths. Each block representing 8 bytes consists of a control -; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat -; previous, followed by the literals in that block. +; LoadTileset using PB12 codec, 2020 Jakub Kądziołka +; (based on PB8 codec, 2019 Damian Yerrick) SameBoyLogo_dst = $8080 SameBoyLogo_length = (128 * 24) / 64 LoadTileset: ld hl, SameBoyLogo - ld de, SameBoyLogo_dst + ld de, SameBoyLogo_dst - 1 ld c, SameBoyLogo_length -.pb8BlockLoop: - ; Register map for PB8 decompression +.refill + ; Register map for PB12 decompression ; HL: source address in boot ROM ; DE: destination address in VRAM ; A: Current literal value ; B: Repeat bits, terminated by 1000... - ; C: Number of 8-byte blocks left in this block ; Source address in HL lets the repeat bits go straight to B, ; bypassing A and avoiding spilling registers to the stack. ld b, [hl] + dec b + jr z, .sameboyLogoEnd + inc b inc hl ; Shift a 1 into lower bit of shift value. Once this bit @@ -665,26 +680,53 @@ LoadTileset: scf rl b -.pb8BitLoop: +.loop ; If not a repeat, load a literal byte - jr c,.pb8Repeat - ld a, [hli] -.pb8Repeat: - ; Decompressed data uses colors 0 and 1, so write once, inc twice - ld [de], a - inc de - inc de + jr c, .simple_repeat sla b - jr nz, .pb8BitLoop - - dec c - jr nz, .pb8BlockLoop - -; End PB8 decoding. The rest uses HL as the destination - ld h, d - ld l, e + jr c, .shifty_repeat + ld a, [hli] + jr .got_byte +.shifty_repeat + sla b + jr nz, .no_refill_during_shift + ld b, [hl] ; see above. Also, no, factoring it out into a callable + inc hl ; routine doesn't save bytes, even with conditional calls + scf + rl b +.no_refill_during_shift + ld c, a + jr nc, .shift_left + srl a + db $fe ; eat the add a with cp d8 +.shift_left + add a + sla b + jr c, .go_and + or c + db $fe ; eat the and c with cp d8 +.go_and + and c + jr .got_byte +.simple_repeat + sla b + jr c, .got_byte + ; far repeat + dec de + ld a, [de] + inc de +.got_byte + inc de + ld [de], a + sla b + jr nz, .loop + jr .refill +; End PB12 decoding. The rest uses HL as the destination .sameboyLogoEnd + ld h, d + ld l, $80 + ; Copy (unresized) ROM logo ld de, $104 .CGBROMLogoLoop diff --git a/BootROMs/pb12.py b/BootROMs/pb12.py new file mode 100644 index 00000000..0c06538c --- /dev/null +++ b/BootROMs/pb12.py @@ -0,0 +1,68 @@ +import sys + +def opts(byte): + # top bit: 0 = left, 1 = right + # bottom bit: 0 = or, 1 = and + if byte is None: return [] + return [ + byte | (byte << 1) & 0xff, + byte & (byte << 1), + byte | (byte >> 1) & 0xff, + byte & (byte >> 1), + ] + +def pb12(data): + data = iter(data) + + literals = bytearray() + bits = 0 + control = 0 + prev = [None, None] + gotta_end = False + + chunk = bytearray() + while True: + try: + byte = next(data) + except StopIteration: + if bits == 0: break + byte = 0 + chunk.append(byte) + + if byte in prev: + bits += 2 + control <<= 1 + control |= 1 + control <<= 1 + if prev[1] == byte: + control |= 1 # 10 = out[-2], 11 = out[-1] + else: + bits += 2 + control <<= 2 + options = opts(prev[1]) + if byte in options: + # 01 = modify + control |= 1 + + bits += 2 + control <<= 2 + control |= options.index(byte) + else: + # 00 = literal + literals.append(byte) + prev = [prev[1], byte] + if bits >= 8: + outctl = control >> (bits - 8) + assert outctl != 1 # that's the end byte + yield bytes([outctl]) + literals + bits -= 8 + control &= (1 << bits) - 1 + literals = bytearray() + chunk = bytearray() + yield b'\x01' + +_, infile, outfile = sys.argv +with open(infile, 'rb') as f: + data = f.read() +with open(outfile, 'wb') as f: + f.writelines(pb12(data)) diff --git a/Makefile b/Makefile index afc8d897..1f818628 100644 --- a/Makefile +++ b/Makefile @@ -382,21 +382,18 @@ $(BIN)/SDL/Shaders: Shaders # Boot ROMs -$(OBJ)/%.1bpp: %.png +$(OBJ)/%.2bpp: %.png -@$(MKDIR) -p $(dir $@) - rgbgfx -d 1 -h -o $@ $< + rgbgfx -h -u -o $@ $< -$(OBJ)/BootROMs/SameBoyLogo.pb8: $(OBJ)/BootROMs/SameBoyLogo.1bpp $(PB8_COMPRESS) - $(realpath $(PB8_COMPRESS)) -l 384 $< $@ - -$(PB8_COMPRESS): BootROMs/pb8.c - $(CC) $< -o $@ +$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp BootROMs/pb12.py + python3 BootROMs/pb12.py $< $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm -$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb8 +$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb12 -@$(MKDIR) -p $(dir $@) rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $< rgblink -o $@.tmp2 $@.tmp From b057e0d10af8394671e2af8d463c2f948e860f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sun, 3 May 2020 23:07:53 +0200 Subject: [PATCH 1109/1216] Save 4 more bytes in the CGB boot ROM --- BootROMs/cgb_boot.asm | 52 ++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 68376150..1d9cc6a8 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -21,7 +21,7 @@ Start: ldh [InputPalette], a ; Clear title checksum ldh [TitleChecksum], a - + ; Clear OAM ld h, $fe ld c, $a0 @@ -753,29 +753,6 @@ ReadTrademarkSymbol: jr nz, .loadTrademarkSymbolLoop ret -LoadObjPalettes: - ld c, $6A - jr LoadPalettes - -LoadBGPalettes64: - ld d, 64 - -LoadBGPalettes: - ld e, 0 - ld c, $68 - -LoadPalettes: - ld a, $80 - or e - ld [c], a - inc c -.loop - ld a, [hli] - ld [c], a - dec d - jr nz, .loop - ret - DoIntroAnimation: ; Animate the intro ld a, 1 @@ -902,8 +879,7 @@ EmulateDMG: call LoadPalettesFromIndex ld a, 4 ; Set the final values for DMG mode - ld d, 0 - ld e, $8 + ld de, 8 ld l, $7c ret @@ -997,7 +973,8 @@ LoadPalettesFromIndex: ; a = index of combination ld c, a add hl, bc ld d, 8 - call LoadObjPalettes + ld c, $6A + call LoadPalettes pop hl bit 3, e jr nz, .loadBGPalette @@ -1011,7 +988,26 @@ LoadPalettesFromIndex: ; a = index of combination ld c, a add hl, bc ld d, 8 - jp LoadBGPalettes + jr LoadBGPalettes + +LoadBGPalettes64: + ld d, 64 + +LoadBGPalettes: + ld e, 0 + ld c, $68 + +LoadPalettes: + ld a, $80 + or e + ld [c], a + inc c +.loop + ld a, [hli] + ld [c], a + dec d + jr nz, .loop + ret BrightenColor: ld a, [hli] From 2225fd114c795a4a607fa54267a2a695c80a83d6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 May 2020 02:07:19 +0300 Subject: [PATCH 1110/1216] Handle 2bpp palettes --- BootROMs/cgb_boot.asm | 74 +++++++++++++++++++------------------------ BootROMs/pb12.py | 2 +- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 1d9cc6a8..94087884 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -138,41 +138,24 @@ ENDC ; Expand Palettes ld de, AnimationColors - ld c, 8 + ld c, 16 ld hl, BgPalettes xor a .expandPalettesLoop: -IF !DEF(FAST) cpl -ENDC - ; One white + ; One white or black ldi [hl], a ldi [hl], a - -IF DEF(FAST) - ; 3 more whites - ldi [hl], a - ldi [hl], a - ldi [hl], a - ldi [hl], a - ldi [hl], a - ldi [hl], a -ELSE - ; The actual color + + ; One color + push af ld a, [de] inc de ldi [hl], a ld a, [de] inc de ldi [hl], a - - xor a - ; Two blacks - ldi [hl], a - ldi [hl], a - ldi [hl], a - ldi [hl], a -ENDC + pop af dec c jr nz, .expandPalettesLoop @@ -551,19 +534,20 @@ TrademarkSymbol: SameBoyLogo: incbin "SameBoyLogo.pb12" -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: +animation_color: MACRO + dw ((\1) >> 1) | $4210, (\1) +ENDM -DMGPalettes: - dw $7FFF, $32BF, $00D0, $0000 +AnimationColors: + animation_color $7FFF, ($7FFF >> 1) ; White + animation_color $774F, ($774F >> 1) ; Cyan + animation_color $22C7, ($22C7 >> 1) ; Green + animation_color $039F, ($039F >> 1) ; Yellow + animation_color $017D, ($017D >> 1) ; Orange + animation_color $241D, ($241D >> 1) ; Red + animation_color $6D38, ($6D38 >> 1) ; Purple + animation_color $7102, ($7102 >> 1) ; Blue +AnimationColorsEnd: ; Helper Functions DoubleBitsAndWriteRowTwice: @@ -1140,27 +1124,33 @@ ChangeAnimationPalette: .isWhite push af ld a, [hli] + push hl - ld hl, BgPalettes ; First color, all palette + ld hl, BgPalettes ; First color, all palettes + call ReplaceColorInAllPalettes + ld l, LOW(BgPalettes + 2) ; Second color, all palettes call ReplaceColorInAllPalettes pop hl - ldh [BgPalettes + 2], a ; Second color, first palette + ldh [BgPalettes + 6], a ; Fourth color, first palette ld a, [hli] push hl - ld hl, BgPalettes + 1 ; First color, all palette + ld hl, BgPalettes + 1 ; First color, all palettes + call ReplaceColorInAllPalettes + ld l, LOW(BgPalettes + 3) ; Second color, all palettes call ReplaceColorInAllPalettes pop hl - ldh [BgPalettes + 3], a ; Second color, first palette + ldh [BgPalettes + 7], a ; Fourth color, first palette + pop af jr z, .isNotWhite inc hl inc hl .isNotWhite ld a, [hli] - ldh [BgPalettes + 7 * 8 + 2], a ; Second color, 7th palette + ldh [BgPalettes + 7 * 8 + 6], a ; Fourth color, 7th palette ld a, [hli] - ldh [BgPalettes + 7 * 8 + 3], a ; Second color, 7th palette + ldh [BgPalettes + 7 * 8 + 7], a ; Fourth color, 7th palette ld a, [hli] ldh [BgPalettes + 4], a ; Third color, first palette ld a, [hl] @@ -1180,7 +1170,7 @@ ChangeAnimationPalette: ReplaceColorInAllPalettes: ld de, 8 - ld c, 8 + ld c, e .loop ld [hl], a add hl, de diff --git a/BootROMs/pb12.py b/BootROMs/pb12.py index 0c06538c..dc53d6b9 100644 --- a/BootROMs/pb12.py +++ b/BootROMs/pb12.py @@ -63,6 +63,6 @@ def pb12(data): _, infile, outfile = sys.argv with open(infile, 'rb') as f: - data = f.read() + data = f.read().rstrip(b'\x00') with open(outfile, 'wb') as f: f.writelines(pb12(data)) From 72a90ba91c8b8ee07859fc25f6b536a5f5afaaad Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 May 2020 02:17:03 +0300 Subject: [PATCH 1111/1216] Hacky color blending --- BootROMs/cgb_boot.asm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 94087884..cbbcf962 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -1093,12 +1093,11 @@ GetInputPaletteIndex: ; 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 c, a ld b, 0 add hl, bc ld a, [hl] @@ -1149,6 +1148,7 @@ ChangeAnimationPalette: .isNotWhite ld a, [hli] ldh [BgPalettes + 7 * 8 + 6], a ; Fourth color, 7th palette + ldh [BgPalettes + 7 * 8 + 2], a ; Second color, half, 7th palette; rough color mixing ld a, [hli] ldh [BgPalettes + 7 * 8 + 7], a ; Fourth color, 7th palette ld a, [hli] @@ -1165,7 +1165,6 @@ ChangeAnimationPalette: pop de pop bc pop hl - pop af ret ReplaceColorInAllPalettes: From 260f61f33a5df32f1bd1542e18d645591a189758 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 May 2020 22:48:00 +0300 Subject: [PATCH 1112/1216] =?UTF-8?q?This=20window=20shouldn=E2=80=99t=20b?= =?UTF-8?q?e=20resizeable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cocoa/Document.xib | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index e1c5fa09..81ce0188 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -116,7 +116,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -187,7 +187,7 @@ - + @@ -312,7 +312,7 @@ - + @@ -970,7 +970,7 @@ - + From f46f138e9fbda089a1a9f71b88d608b38b932315 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 May 2020 23:54:43 +0300 Subject: [PATCH 1113/1216] Clear VRAM correctly --- BootROMs/cgb_boot.asm | 155 ++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 81 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index cbbcf962..33e18e56 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -16,6 +16,15 @@ Start: xor a call ClearMemoryPage ld [c], a + + ld hl, $FF30 +; Init waveform + ld c, $10 +.waveformLoop + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop ; Clear chosen input palette ldh [InputPalette], a @@ -40,8 +49,6 @@ Start: ld a, $77 ldh [$24], a - call InitWaveform - ; Init BG palette ld a, $fc ldh [$47], a @@ -776,25 +783,68 @@ IF !DEF(FAST) ld hl, BgPalettes .frameLoop push bc - call BrightenColor + + ; Brighten Color + 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 + dec c +.blueNotMaxed + + ; Is green maxed? + ld a, e + cp $E0 + jr c, .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, bc + ; ld [hli], de + ld a, e + add c + ld [hli], a + ld a, d + adc b + ld [hli], a pop bc + dec c jr nz, .frameLoop - call WaitFrame call WaitFrame ld hl, BgPalettes call LoadBGPalettes64 + call WaitFrame dec b jr nz, .fadeLoop ENDC + ld a, 1 call ClearVRAMViaHDMA - ; Select the first bank - xor a - ldh [$4F], a - cpl + call _ClearVRAMViaHDMA + call ClearVRAMViaHDMA ; A = $40, so it's bank 0 + ; A should be $FF ldh [$00], a - call ClearVRAMViaHDMA ; Final values for CGB mode ld de, $ff56 @@ -993,70 +1043,24 @@ LoadPalettes: jr nz, .loop ret -BrightenColor: - ld a, [hli] - ld e, a - ld a, [hld] - ld d, a - ; RGB(1,1,1) - ld bc, $421 - - ; Is blue maxed? - ld a, e - and $1F - cp $1F - jr nz, .blueNotMaxed - dec c -.blueNotMaxed - - ; Is green maxed? - ld a, e - cp $E0 - jr c, .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, bc - ; ld [hli], de - ld a, e - add c - ld [hli], a - ld a, d - adc b - ld [hli], a - ret - ClearVRAMViaHDMA: - ld hl, $FF51 - - ; Src - ld a, $88 - ld [hli], a - xor a - ld [hli], a - - ; Dest - ld a, $98 - ld [hli], a - ld a, $A0 - ld [hli], a - - ; Do it - ld [hl], $12 + ldh [$4F], a + ld hl, HDMAData +_ClearVRAMViaHDMA: + ld c, $51 + ld b, 5 +.loop + ld a, [hli] + ldh [c], a + inc c + dec b + jr nz, .loop ret +HDMAData: + db $88, $00, $98, $A0, $12 + db $88, $00, $80, $00, $40 + GetInputPaletteIndex: ld a, $20 ; Select directions ldh [$00], a @@ -1196,17 +1200,6 @@ LoadDMGTilemap: pop af ret -InitWaveform: - ld hl, $FF30 -; Init waveform - xor a - ld c, $10 -.waveformLoop - ldi [hl], a - cpl - dec c - jr nz, .waveformLoop - ret SECTION "ROMMax", ROM0[$900] ; Prevent us from overflowing From a3f261184d2a732b32eb30d6b3538b04fba31988 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 5 May 2020 01:44:48 +0300 Subject: [PATCH 1114/1216] Optimize more --- BootROMs/cgb_boot.asm | 65 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 33e18e56..a83c27a3 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -6,8 +6,7 @@ Start: ld sp, $fffe ; Clear memory VRAM - ld hl, $8000 - call ClearMemoryPage + call ClearMemoryPage8000 ld a, 2 ld c, $70 ld [c], a @@ -74,9 +73,7 @@ Start: ; Clear the second VRAM bank ld a, 1 ldh [$4F], a - xor a - ld hl, $8000 - call ClearMemoryPage + call ClearMemoryPage8000 call LoadTileset ld b, 3 @@ -177,8 +174,10 @@ ENDC IF !DEF(FAST) call DoIntroAnimation + ld a, 45 + ldh [WaitLoopCounter], a ; Wait ~0.75 seconds - ld b, 45 + ld b, a call WaitBFrames ; Play first sound @@ -189,10 +188,6 @@ IF !DEF(FAST) ; Play second sound ld a, $c1 call PlaySound - -; Wait ~0.5 seconds - ld a, 30 - ldh [WaitLoopCounter], a .waitLoop call GetInputPaletteIndex @@ -602,8 +597,11 @@ PlaySound: ldh [$14], a ret +ClearMemoryPage8000: + ld hl, $8000 ; Clear from HL to HL | 0x2000 ClearMemoryPage: + xor a ldi [hl], a bit 5, h jr z, ClearMemoryPage @@ -781,6 +779,7 @@ IF !DEF(FAST) .fadeLoop ld c, 32 ; 32 colors to fade ld hl, BgPalettes + push hl .frameLoop push bc @@ -833,7 +832,7 @@ IF !DEF(FAST) jr nz, .frameLoop call WaitFrame - ld hl, BgPalettes + pop hl call LoadBGPalettes64 call WaitFrame dec b @@ -888,6 +887,14 @@ ENDC ldh [$4C], a ld a, $1 ret + +GetKeyComboPalette: + 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] + ret EmulateDMG: ld a, 1 @@ -900,11 +907,7 @@ EmulateDMG: ldh a, [InputPalette] and a jr z, .nothingDown - ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down - ld c ,a - ld b, 0 - add hl, bc - ld a, [hl] + call GetKeyComboPalette jr .paletteFromKeys .nothingDown ld a, b @@ -985,17 +988,21 @@ GetPaletteIndex: .notNintendo xor a ret - -LoadPalettesFromIndex: ; a = index of combination + +GetPaletteCombo: ld b, a ; Multiply by 3 add b add b - + ld hl, PaletteCombinations ld b, 0 ld c, a add hl, bc + ret + +LoadPalettesFromIndex: ; a = index of combination + call GetPaletteCombo ; Obj Palettes ld e, 0 @@ -1100,20 +1107,10 @@ ChangeAnimationPalette: 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 + call GetKeyComboPalette + call GetPaletteCombo + inc l + inc l ld a, [hl] ld hl, Palettes + 1 ld b, 0 @@ -1164,7 +1161,7 @@ ChangeAnimationPalette: ld hl, BgPalettes call LoadBGPalettes64 ; Delay the wait loop while the user is selecting a palette - ld a, 30 + ld a, 45 ldh [WaitLoopCounter], a pop de pop bc From 730567dc609a1213d6395ca92d4be00c072a2817 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 6 May 2020 01:06:22 +0300 Subject: [PATCH 1115/1216] Proper color mixing --- BootROMs/cgb_boot.asm | 198 ++++++++++++++++++++++++------------------ 1 file changed, 115 insertions(+), 83 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index a83c27a3..332279ca 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -10,35 +10,16 @@ Start: ld a, 2 ld c, $70 ld [c], a -; Clear RAM Bank 2 (Like the original boot ROM +; Clear RAM Bank 2 (Like the original boot ROM) ld h, $D0 - xor a call ClearMemoryPage ld [c], a - ld hl, $FF30 -; Init waveform - ld c, $10 -.waveformLoop - ldi [hl], a - cpl - dec c - jr nz, .waveformLoop - ; Clear chosen input palette ldh [InputPalette], a ; Clear title checksum ldh [TitleChecksum], a - -; Clear OAM - ld h, $fe - ld c, $a0 -.clearOAMLoop - ldi [hl], a - dec c - jr nz, .clearOAMLoop - -; Init Audio + ld a, $80 ldh [$26], a ldh [$11], a @@ -47,6 +28,23 @@ Start: ldh [$25], a ld a, $77 ldh [$24], a + ld hl, $FF30 +; Init waveform + ld c, $10 +.waveformLoop + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop + + +; Clear OAM + ld h, $fe + ld c, $a0 +.clearOAMLoop + ldi [hl], a + dec c + jr nz, .clearOAMLoop ; Init BG palette ld a, $fc @@ -142,25 +140,44 @@ ENDC ; Expand Palettes ld de, AnimationColors - ld c, 16 + ld c, 8 ld hl, BgPalettes xor a .expandPalettesLoop: cpl - ; One white or black - ldi [hl], a - ldi [hl], a + ; One white + ld [hli], a + ld [hli], a + + ; Mixed with white + ld a, [de] + inc e + or $20 + ld b, a + + ld a, [de] + dec e + or $84 + rra + rr b + ld [hl], b + inc l + ld [hli], a + + ; One black + xor a + ld [hli], a + ld [hli], a ; One color - push af ld a, [de] - inc de - ldi [hl], a + inc e + ld [hli], a ld a, [de] - inc de - ldi [hl], a - pop af - + inc e + ld [hli], a + + xor a dec c jr nz, .expandPalettesLoop @@ -200,6 +217,9 @@ ELSE call PlaySound ENDC call Preboot +IF DEF(AGB) + ld b, 1 +ENDC ; Will be filled with NOPs @@ -208,7 +228,6 @@ BootGame: ldh [$50], a SECTION "MoreStuff", ROM0[$200] - ; Game Palettes Data TitleChecksums: db $00 ; Default @@ -512,43 +531,40 @@ Palettes: dw $4778, $3290, $1D87, $0861 ; DMG LCD KeyCombinationPalettes - db 1 ; Right - db 48 ; Left - db 5 ; Up - db 8 ; Down - db 0 ; Right + A - db 40 ; Left + A - db 43 ; Up + A - db 3 ; Down + A - db 6 ; Right + B - db 7 ; Left + B - db 28 ; Up + B - db 49 ; Down + B + db 1 * 3 ; Right + db 48 * 3 ; Left + db 5 * 3 ; Up + db 8 * 3 ; Down + db 0 * 3 ; Right + A + db 40 * 3 ; Left + A + db 43 * 3 ; Up + A + db 3 * 3 ; Down + A + db 6 * 3 ; Right + B + db 7 * 3 ; Left + B + db 28 * 3 ; Up + B + db 49 * 3 ; Down + B ; SameBoy "Exclusives" - db 51 ; Right + A + B - db 52 ; Left + A + B - db 53 ; Up + A + B - db 54 ; Down + A + B - + db 51 * 3 ; Right + A + B + db 52 * 3 ; Left + A + B + db 53 * 3 ; Up + A + B + db 54 * 3 ; Down + A + B + TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SameBoyLogo: incbin "SameBoyLogo.pb12" -animation_color: MACRO - dw ((\1) >> 1) | $4210, (\1) -ENDM AnimationColors: - animation_color $7FFF, ($7FFF >> 1) ; White - animation_color $774F, ($774F >> 1) ; Cyan - animation_color $22C7, ($22C7 >> 1) ; Green - animation_color $039F, ($039F >> 1) ; Yellow - animation_color $017D, ($017D >> 1) ; Orange - animation_color $241D, ($241D >> 1) ; Red - animation_color $6D38, ($6D38 >> 1) ; Purple - animation_color $7102, ($7102 >> 1) ; Blue + 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: ; Helper Functions @@ -842,11 +858,13 @@ ENDC call ClearVRAMViaHDMA call _ClearVRAMViaHDMA call ClearVRAMViaHDMA ; A = $40, so it's bank 0 + cpl ; A should be $FF ldh [$00], a ; Final values for CGB mode - ld de, $ff56 + ld d, a + ld e, c ld l, $0d ld a, [$143] @@ -870,7 +888,7 @@ IF DEF(AGB) ld c, a add a, $11 ld h, c - ld b, 1 + ; B is set to 1 after ret ELSE ; Set registers to match the original CGB boot ; AF = $1180, C = 0 @@ -990,11 +1008,6 @@ GetPaletteIndex: ret GetPaletteCombo: - ld b, a - ; Multiply by 3 - add b - add b - ld hl, PaletteCombinations ld b, 0 ld c, a @@ -1064,10 +1077,6 @@ _ClearVRAMViaHDMA: jr nz, .loop ret -HDMAData: - db $88, $00, $98, $A0, $12 - db $88, $00, $80, $00, $40 - GetInputPaletteIndex: ld a, $20 ; Select directions ldh [$00], a @@ -1104,7 +1113,6 @@ GetInputPaletteIndex: ; Slide into change Animation Palette ChangeAnimationPalette: - push hl push bc push de call GetKeyComboPalette @@ -1147,16 +1155,37 @@ ChangeAnimationPalette: inc hl inc hl .isNotWhite + ; Mixing code by ISSOtm + ldh a, [BgPalettes + 7 * 8 + 2] + and ~$21 + ld b, a + ld a, [hli] + and ~$21 + add a, b + ld b, a + ld a, [BgPalettes + 7 * 8 + 3] + res 2, a ; and ~$04, but not touching carry + ld c, [hl] + res 2, c ; and ~$04, but not touching carry + adc a, c + rra ; Carry sort of "extends" the accumulator, we're bringing that bit back home + ld [BgPalettes + 7 * 8 + 3], a + ld a, b + rra + ld [BgPalettes + 7 * 8 + 2], a + dec l + ld a, [hli] ldh [BgPalettes + 7 * 8 + 6], a ; Fourth color, 7th palette - ldh [BgPalettes + 7 * 8 + 2], a ; Second color, half, 7th palette; rough color mixing ld a, [hli] ldh [BgPalettes + 7 * 8 + 7], a ; Fourth color, 7th palette + ld a, [hli] ldh [BgPalettes + 4], a ; Third color, first palette - ld a, [hl] + ld a, [hli] ldh [BgPalettes + 5], a ; Third color, first palette + call WaitFrame ld hl, BgPalettes call LoadBGPalettes64 @@ -1165,7 +1194,6 @@ ChangeAnimationPalette: ldh [WaitLoopCounter], a pop de pop bc - pop hl ret ReplaceColorInAllPalettes: @@ -1181,7 +1209,7 @@ ReplaceColorInAllPalettes: LoadDMGTilemap: push af call WaitFrame - ld a,$19 ; Trademark symbol + 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 @@ -1191,16 +1219,20 @@ LoadDMGTilemap: ldd [hl], a dec c jr nz, .tilemapLoop - ld l,$0f ; Jump to top row + ld l, $0f ; Jump to top row jr .tilemapLoop .tilemapDone pop af ret - - -SECTION "ROMMax", ROM0[$900] - ; Prevent us from overflowing - ds 1 + +HDMAData: + db $88, $00, $98, $A0, $12 + db $88, $00, $80, $00, $40 + +BootEnd: +IF BootEnd > $900 + FAIL "BootROM overflowed: {BootEnd}" +ENDC SECTION "HRAM", HRAM[$FF80] TitleChecksum: From 184743637eebf34f5fc673057dbca954037f3599 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 6 May 2020 01:10:46 +0300 Subject: [PATCH 1116/1216] Fix silly regression --- BootROMs/cgb_boot.asm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 332279ca..f07fbcfd 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -858,8 +858,7 @@ ENDC call ClearVRAMViaHDMA call _ClearVRAMViaHDMA call ClearVRAMViaHDMA ; A = $40, so it's bank 0 - cpl - ; A should be $FF + ld a, $ff ldh [$00], a ; Final values for CGB mode From 7cff35368d26e87a0638e9be2d56642665af0ac0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 6 May 2020 23:30:01 +0300 Subject: [PATCH 1117/1216] Port to C to remove the Python dep, remove leftovers --- BootROMs/pb12.c | 95 +++++++++++++ BootROMs/pb12.py | 68 ---------- BootROMs/pb8.c | 341 ----------------------------------------------- Makefile | 9 +- 4 files changed, 101 insertions(+), 412 deletions(-) create mode 100644 BootROMs/pb12.c delete mode 100644 BootROMs/pb12.py delete mode 100644 BootROMs/pb8.c diff --git a/BootROMs/pb12.c b/BootROMs/pb12.c new file mode 100644 index 00000000..878dd0d2 --- /dev/null +++ b/BootROMs/pb12.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include + +void opts(uint8_t byte, uint8_t *options) +{ + *(options++) = byte | ((byte << 1) & 0xff); + *(options++) = byte & (byte << 1); + *(options++) = byte | ((byte >> 1) & 0xff); + *(options++) = byte & (byte >> 1); +} + +int main() +{ + static uint8_t source[0x4000]; + size_t size = read(STDIN_FILENO, &source, sizeof(source)); + unsigned pos = 0; + assert(size <= 0x4000); + while (size && source[size - 1] == 0) { + size--; + } + + uint8_t *literals = NULL; + size_t literals_size = 0; + unsigned bits = 0; + unsigned control = 0; + unsigned prev[2] = {-1, -1}; // Unsigned to allow "not set" values + + while (true) { + + uint8_t byte = 0; + if (pos == size){ + if (bits == 0) break; + } + else { + byte = source[pos++]; + } + + if (byte == prev[0] || byte == prev[1]) { + bits += 2; + control <<= 1; + control |= 1; + control <<= 1; + if (byte == prev[1]) { + control |= 1; + } + } + else { + bits += 2; + control <<= 2; + uint8_t options[4]; + opts(prev[1], options); + bool found = false; + for (unsigned i = 0; i < 4; i++) { + if (options[i] == byte) { + // 01 = modify + control |= 1; + + bits += 2; + control <<= 2; + control |= i; + found = true; + break; + } + } + if (!found) { + literals = realloc(literals, literals_size++); + literals[literals_size - 1] = byte; + } + } + + prev[0] = prev[1]; + prev[1] = byte; + if (bits >= 8) { + uint8_t outctl = control >> (bits - 8); + assert(outctl != 1); + write(STDOUT_FILENO, &outctl, 1); + write(STDOUT_FILENO, literals, literals_size); + bits -= 8; + control &= (1 << bits) - 1; + literals_size = 0; + } + } + uint8_t end_byte = 1; + write(STDOUT_FILENO, &end_byte, 1); + + if (literals) { + free(literals); + } + + return 0; +} diff --git a/BootROMs/pb12.py b/BootROMs/pb12.py deleted file mode 100644 index dc53d6b9..00000000 --- a/BootROMs/pb12.py +++ /dev/null @@ -1,68 +0,0 @@ -import sys - -def opts(byte): - # top bit: 0 = left, 1 = right - # bottom bit: 0 = or, 1 = and - if byte is None: return [] - return [ - byte | (byte << 1) & 0xff, - byte & (byte << 1), - byte | (byte >> 1) & 0xff, - byte & (byte >> 1), - ] - -def pb12(data): - data = iter(data) - - literals = bytearray() - bits = 0 - control = 0 - prev = [None, None] - gotta_end = False - - chunk = bytearray() - while True: - try: - byte = next(data) - except StopIteration: - if bits == 0: break - byte = 0 - chunk.append(byte) - - if byte in prev: - bits += 2 - control <<= 1 - control |= 1 - control <<= 1 - if prev[1] == byte: - control |= 1 # 10 = out[-2], 11 = out[-1] - else: - bits += 2 - control <<= 2 - options = opts(prev[1]) - if byte in options: - # 01 = modify - control |= 1 - - bits += 2 - control <<= 2 - control |= options.index(byte) - else: - # 00 = literal - literals.append(byte) - prev = [prev[1], byte] - if bits >= 8: - outctl = control >> (bits - 8) - assert outctl != 1 # that's the end byte - yield bytes([outctl]) + literals - bits -= 8 - control &= (1 << bits) - 1 - literals = bytearray() - chunk = bytearray() - yield b'\x01' - -_, infile, outfile = sys.argv -with open(infile, 'rb') as f: - data = f.read().rstrip(b'\x00') -with open(outfile, 'wb') as f: - f.writelines(pb12(data)) diff --git a/BootROMs/pb8.c b/BootROMs/pb8.c deleted file mode 100644 index 4ee4524e..00000000 --- a/BootROMs/pb8.c +++ /dev/null @@ -1,341 +0,0 @@ -/* - -PB8 compressor and decompressor - -Copyright 2019 Damian Yerrick - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - -*/ - -#include -#include -#include -#include -#include -#include - -// For setting stdin/stdout to binary mode -#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) -#include -#define fd_isatty isatty -#elif defined (_WIN32) -#include -#include -#define fd_isatty _isatty -#endif - -/* - -; The logo is compressed using PB8, a form of RLE with unary-coded -; run lengths. Each block representing 8 bytes consists of a control -; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat -; previous, followed by the literals in that block. - -SameBoyLogo_dst = $8080 -SameBoyLogo_length = (128 * 24) / 64 - -LoadTileset: - ld hl, SameBoyLogo - ld de, SameBoyLogo_dst - ld c, SameBoyLogo_length -.pb8BlockLoop: - ; Register map for PB8 decompression - ; HL: source address in boot ROM - ; DE: destination address in VRAM - ; A: Current literal value - ; B: Repeat bits, terminated by 1000... - ; C: Number of 8-byte blocks left in this block - ; Source address in HL lets the repeat bits go straight to B, - ; bypassing A and avoiding spilling registers to the stack. - ld b, [hl] - inc hl - - ; Shift a 1 into lower bit of shift value. Once this bit - ; reaches the carry, B becomes 0 and the byte is over - scf - rl b - -.pb8BitLoop: - ; If not a repeat, load a literal byte - jr c,.pb8Repeat - ld a, [hli] -.pb8Repeat: - ; Decompressed data uses colors 0 and 1, so write once, inc twice - ld [de], a - inc de - inc de - sla b - jr nz, .pb8BitLoop - - dec c - jr nz, .pb8BlockLoop - ret - -*/ - -/* Compressor and decompressor *************************************/ - -/** - * Compresses an input stream to PB8 data on an output stream. - * @param infp input stream - * @param outfp output stream - * @param blocklength size of an independent input block in bytes - * @return 0 for reaching infp end of file, or EOF for error - */ -int pb8(FILE *infp, FILE *outfp, size_t blocklength) -{ - blocklength >>= 3; // convert bytes to blocks - assert(blocklength > 0); - while (1) { - int last_byte = EOF; // value that never occurs in a file - for (size_t blkleft = blocklength; blkleft > 0; --blkleft) { - unsigned int control_byte = 0x0001; - unsigned char literals[8]; - size_t nliterals = 0; - while (control_byte < 0x100) { - int c = fgetc(infp); - if (c == EOF) break; - - control_byte <<= 1; - if (c == last_byte) { - control_byte |= 0x01; - } - else { - literals[nliterals++] = last_byte = c; - } - } - if (control_byte > 1) { - // Fill partial block with repeats - while (control_byte < 0x100) { - control_byte = (control_byte << 1) | 1; - } - - // Write control byte and check for write failure - int ok = fputc(control_byte & 0xFF, outfp); - if (ok == EOF) return EOF; - size_t ok2 = fwrite(literals, 1, nliterals, outfp); - if (ok2 < nliterals) return EOF; - } - - // If finished, return success or failure - if (ferror(infp) || ferror(outfp)) return EOF; - if (feof(infp)) return 0; - } // End 8-byte block - } // End packet, resetting last_byte -} - -/** - * Decompresses PB8 data on an input stream to an output stream. - * @param infp input stream - * @param outfp output stream - * @return 0 for reaching infp end of file, or EOF for error - */ -int unpb8(FILE *infp, FILE *outfp) -{ - int last_byte = 0; - while (1) { - int control_byte = fgetc(infp); - if (control_byte == EOF) { - return feof(infp) ? 0 : EOF; - } - control_byte &= 0xFF; - for (size_t bytesleft = 8; bytesleft > 0; --bytesleft) { - if (!(control_byte & 0x80)) { - last_byte = fgetc(infp); - if (last_byte == EOF) return EOF; // read error - } - control_byte <<= 1; - int ok = fputc(last_byte, outfp); - if (ok == EOF) return EOF; - } - } -} - -/* CLI frontend ****************************************************/ - -static inline void set_fd_binary(unsigned int fd) -{ -#ifdef _WIN32 - _setmode(fd, _O_BINARY); -#else - (void) fd; -#endif -} - -static const char *usage_msg = -"usage: pb8 [-d] [-l blocklength] [infile [outfile]]\n" -"Compresses a file using RLE with unary run and literal lengths.\n" -"\n" -"options:\n" -" -d decompress\n" -" -l blocklength allow RLE packets to span up to blocklength\n" -" input bytes (multiple of 8; default 8)\n" -" -h, -?, --help show this usage page\n" -" --version show copyright info\n" -"\n" -"If infile is - or missing, it is standard input.\n" -"If outfile is - or missing, it is standard output.\n" -"You cannot compress to or decompress from a terminal.\n" -; -static const char *version_msg = -"PB8 compressor (C version) v0.01\n" -"Copyright 2019 Damian Yerrick \n" -"This software is provided 'as-is', without any express or implied\n" -"warranty.\n" -; -static const char *toomanyfilenames_msg = -"pb8: too many filenames; try pb8 --help\n"; - -int main(int argc, char **argv) -{ - const char *infilename = NULL; - const char *outfilename = NULL; - bool decompress = false; - size_t blocklength = 8; - - for (int i = 1; i < argc; ++i) { - if (argv[i][0] == '-' && argv[i][1] != 0) { - if (!strcmp(argv[i], "--help")) { - fputs(usage_msg, stdout); - return 0; - } - if (!strcmp(argv[i], "--version")) { - fputs(version_msg, stdout); - return 0; - } - - // -t1 or -t 1 - int argtype = argv[i][1]; - switch (argtype) { - case 'h': - case '?': - fputs(usage_msg, stdout); - return 0; - - case 'd': - decompress = true; - break; - - case 'l': { - const char *argvalue = argv[i][2] ? argv[i] + 2 : argv[++i]; - const char *endptr = NULL; - - unsigned long tvalue = strtoul(argvalue, (char **)&endptr, 10); - if (endptr == argvalue || tvalue == 0 || tvalue > SIZE_MAX) { - fprintf(stderr, "pb8: block length %s not a positive integer\n", - argvalue); - return EXIT_FAILURE; - } - if (tvalue % 8 != 0) { - fprintf(stderr, "pb8: block length %s not a multiple of 8\n", - argvalue); - return EXIT_FAILURE; - } - blocklength = tvalue; - } break; - - default: - fprintf(stderr, "pb8: unknown option -%c\n", argtype); - return EXIT_FAILURE; - } - } - else if (!infilename) { - infilename = argv[i]; - } - else if (!outfilename) { - outfilename = argv[i]; - } - else { - fputs(toomanyfilenames_msg, stderr); - return EXIT_FAILURE; - } - } - if (infilename && !strcmp(infilename, "-")) { - infilename = NULL; - } - if (!infilename && decompress && fd_isatty(0)) { - fputs("pb8: cannot decompress from terminal; try redirecting stdin\n", - stderr); - return EXIT_FAILURE; - } - if (outfilename && !strcmp(outfilename, "-")) { - outfilename = NULL; - } - if (!outfilename && !decompress && fd_isatty(1)) { - fputs("pb8: cannot compress to terminal; try redirecting stdout or pb8 --help\n", - stderr); - return EXIT_FAILURE; - } - - FILE *infp = NULL; - if (infilename) { - infp = fopen(infilename, "rb"); - if (!infp) { - fprintf(stderr, "pb8: error opening %s ", infilename); - perror("for reading"); - return EXIT_FAILURE; - } - } - else { - infp = stdin; - set_fd_binary(0); - } - - FILE *outfp = NULL; - if (outfilename) { - outfp = fopen(outfilename, "wb"); - if (!outfp) { - fprintf(stderr, "pb8: error opening %s ", outfilename); - perror("for writing"); - fclose(infp); - return EXIT_FAILURE; - } - } - else { - outfp = stdout; - set_fd_binary(1); - } - - int compfailed = 0; - int has_ferror = 0; - if (decompress) { - compfailed = unpb8(infp, outfp); - } - else { - compfailed = pb8(infp, outfp, blocklength); - } - fflush(outfp); - if (ferror(infp)) { - fprintf(stderr, "pb8: error reading %s\n", - infilename ? infilename : ""); - has_ferror = EOF; - } - fclose(infp); - if (ferror(outfp)) { - fprintf(stderr, "pb8: error writing %s\n", - outfilename ? outfilename : ""); - has_ferror = EOF; - } - fclose(outfp); - - if (compfailed && !has_ferror) { - fputs("pb8: unknown compression failure\n", stderr); - } - - return (compfailed || has_ferror) ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/Makefile b/Makefile index 1f818628..78978e49 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ else EXESUFFIX:= endif -PB8_COMPRESS := build/pb8$(EXESUFFIX) +PB12_COMPRESS := build/pb12$(EXESUFFIX) ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa @@ -386,8 +386,11 @@ $(OBJ)/%.2bpp: %.png -@$(MKDIR) -p $(dir $@) rgbgfx -h -u -o $@ $< -$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp BootROMs/pb12.py - python3 BootROMs/pb12.py $< $@ +$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS) + $(PB12_COMPRESS) < $< > $@ + +$(PB12_COMPRESS): BootROMs/pb12.c + $(CC) -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm From 99ec31dfdca5603b61d3836f62b80e5a3bc591a1 Mon Sep 17 00:00:00 2001 From: Fredrik Ljungdahl Date: Thu, 7 May 2020 00:12:35 +0200 Subject: [PATCH 1118/1216] Allow more than 1 symbol per debug address --- Core/symbol_hash.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 33e33996..75a7837d 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -28,8 +28,6 @@ GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const c { size_t index = GB_map_find_symbol_index(map, addr); - if (index < map->n_symbols && map->symbols[index].addr == addr) return NULL; - map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0])); memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0])); map->symbols[index].addr = addr; From 8625b23c0d0571b127b6a45ca88577979d11a465 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 7 May 2020 01:32:03 +0300 Subject: [PATCH 1119/1216] Whoops --- BootROMs/pb12.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BootROMs/pb12.c b/BootROMs/pb12.c index 878dd0d2..9ab38fcd 100644 --- a/BootROMs/pb12.c +++ b/BootROMs/pb12.c @@ -67,7 +67,7 @@ int main() } } if (!found) { - literals = realloc(literals, literals_size++); + literals = realloc(literals, ++literals_size); literals[literals_size - 1] = byte; } } From e0636718163c77bbde211b66a6de85a006d2fd14 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 7 May 2020 22:46:06 +0300 Subject: [PATCH 1120/1216] No need to use malloc here, the buffer never gets large --- BootROMs/pb12.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/BootROMs/pb12.c b/BootROMs/pb12.c index 9ab38fcd..3f6d5f8e 100644 --- a/BootROMs/pb12.c +++ b/BootROMs/pb12.c @@ -23,7 +23,7 @@ int main() size--; } - uint8_t *literals = NULL; + uint8_t literals[8]; size_t literals_size = 0; unsigned bits = 0; unsigned control = 0; @@ -67,8 +67,7 @@ int main() } } if (!found) { - literals = realloc(literals, ++literals_size); - literals[literals_size - 1] = byte; + literals[literals_size++] = byte; } } @@ -87,9 +86,5 @@ int main() uint8_t end_byte = 1; write(STDOUT_FILENO, &end_byte, 1); - if (literals) { - free(literals); - } - return 0; } From 620ee3cf514e8d6219d96a1dd7c9d5da808a8c4f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 7 May 2020 23:43:49 +0300 Subject: [PATCH 1121/1216] Make the libretro frontend not crash on rumble-less frontends --- libretro/libretro.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 14bfaa81..9e0f9a35 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -142,6 +142,8 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) static void rumble_callback(GB_gameboy_t *gb, double amplitude) { + if (!rumble.set_rumble_state) return; + if (gb == &gameboy[0]) { rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535 * amplitude); } From 24220defd649fc8984b0e9e959b7ab70694f480a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Fri, 8 May 2020 13:48:31 +0200 Subject: [PATCH 1122/1216] Save 16 bytes in the CGB boot ROM --- BootROMs/cgb_boot.asm | 99 ++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index f07fbcfd..8770b8aa 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -14,12 +14,12 @@ Start: ld h, $D0 call ClearMemoryPage ld [c], a - + ; Clear chosen input palette ldh [InputPalette], a ; Clear title checksum ldh [TitleChecksum], a - + ld a, $80 ldh [$26], a ldh [$11], a @@ -36,7 +36,7 @@ Start: cpl dec c jr nz, .waveformLoop - + ; Clear OAM ld h, $fe @@ -45,7 +45,7 @@ Start: ldi [hl], a dec c jr nz, .clearOAMLoop - + ; Init BG palette ld a, $fc ldh [$47], a @@ -148,13 +148,13 @@ ENDC ; One white ld [hli], a ld [hli], a - + ; Mixed with white ld a, [de] inc e or $20 ld b, a - + ld a, [de] dec e or $84 @@ -163,26 +163,25 @@ ENDC ld [hl], b inc l ld [hli], a - + ; One black xor a ld [hli], a ld [hli], a - + ; One color ld a, [de] inc e ld [hli], a ld a, [de] inc e - ld [hli], a - + ld [hli], a + xor a dec c jr nz, .expandPalettesLoop - ld hl, BgPalettes - call LoadBGPalettes64 + call LoadPalettesFromHRAM ; Turn on LCD ld a, $91 @@ -205,7 +204,7 @@ IF !DEF(FAST) ; Play second sound ld a, $c1 call PlaySound - + .waitLoop call GetInputPaletteIndex call WaitFrame @@ -530,7 +529,7 @@ Palettes: dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1 dw $4778, $3290, $1D87, $0861 ; DMG LCD -KeyCombinationPalettes +KeyCombinationPalettes: db 1 * 3 ; Right db 48 * 3 ; Left db 5 * 3 ; Up @@ -548,7 +547,7 @@ KeyCombinationPalettes db 52 * 3 ; Left + A + B db 53 * 3 ; Up + A + B db 54 * 3 ; Down + A + B - + TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c @@ -795,10 +794,9 @@ IF !DEF(FAST) .fadeLoop ld c, 32 ; 32 colors to fade ld hl, BgPalettes - push hl .frameLoop push bc - + ; Brighten Color ld a, [hli] ld e, a @@ -843,13 +841,12 @@ IF !DEF(FAST) adc b ld [hli], a pop bc - + dec c jr nz, .frameLoop call WaitFrame - pop hl - call LoadBGPalettes64 + call LoadPalettesFromHRAM call WaitFrame dec b jr nz, .fadeLoop @@ -860,21 +857,21 @@ ENDC call ClearVRAMViaHDMA ; A = $40, so it's bank 0 ld a, $ff ldh [$00], a - + ; Final values for CGB mode ld d, a ld e, c ld l, $0d - + ld a, [$143] bit 7, a call z, EmulateDMG bit 7, a - + ldh [$4C], a ldh a, [TitleChecksum] ld b, a - + jr z, .skipDMGForCGBCheck ldh a, [InputPalette] and a @@ -904,10 +901,10 @@ ENDC ldh [$4C], a ld a, $1 ret - + GetKeyComboPalette: ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down - ld c ,a + ld c, a ld b, 0 add hl, bc ld a, [hl] @@ -1005,7 +1002,8 @@ GetPaletteIndex: .notNintendo xor a ret - + +; optimizations in callers rely on this returning with b = 0 GetPaletteCombo: ld hl, PaletteCombinations ld b, 0 @@ -1022,7 +1020,7 @@ LoadPalettesFromIndex: ; a = index of combination ld a, [hli] push hl ld hl, Palettes - ld b, 0 + ; b is already 0 ld c, a add hl, bc ld d, 8 @@ -1035,15 +1033,15 @@ LoadPalettesFromIndex: ; a = index of combination jr .loadObjPalette .loadBGPalette ;BG Palette - ld a, [hli] + ld c, [hl] + ; b is already 0 ld hl, Palettes - ld b, 0 - ld c, a add hl, bc ld d, 8 jr LoadBGPalettes -LoadBGPalettes64: +LoadPalettesFromHRAM: + ld hl, BgPalettes ld d, 64 LoadBGPalettes: @@ -1076,6 +1074,7 @@ _ClearVRAMViaHDMA: jr nz, .loop ret +; clobbers AF and HL GetInputPaletteIndex: ld a, $20 ; Select directions ldh [$00], a @@ -1083,11 +1082,10 @@ GetInputPaletteIndex: cpl and $F ret z ; No direction keys pressed, no palette - push bc - ld c, 0 + ld l, 0 .directionLoop - inc c + inc l rra jr nc, .directionLoop @@ -1100,15 +1098,13 @@ GetInputPaletteIndex: rla rla and $C - add c - ld b, a + add l + ld l, a ldh a, [InputPalette] - ld c, a - ld a, b - ldh [InputPalette], a - cp c - pop bc + cp l ret z ; No change, don't load + ld a, l + ldh [InputPalette], a ; Slide into change Animation Palette ChangeAnimationPalette: @@ -1118,10 +1114,8 @@ ChangeAnimationPalette: call GetPaletteCombo inc l inc l - ld a, [hl] + ld c, [hl] ld hl, Palettes + 1 - ld b, 0 - ld c, a add hl, bc ld a, [hld] cp $7F ; Is white color? @@ -1131,7 +1125,7 @@ ChangeAnimationPalette: .isWhite push af ld a, [hli] - + push hl ld hl, BgPalettes ; First color, all palettes call ReplaceColorInAllPalettes @@ -1148,7 +1142,7 @@ ChangeAnimationPalette: call ReplaceColorInAllPalettes pop hl ldh [BgPalettes + 7], a ; Fourth color, first palette - + pop af jr z, .isNotWhite inc hl @@ -1173,12 +1167,12 @@ ChangeAnimationPalette: rra ld [BgPalettes + 7 * 8 + 2], a dec l - + ld a, [hli] ldh [BgPalettes + 7 * 8 + 6], a ; Fourth color, 7th palette ld a, [hli] ldh [BgPalettes + 7 * 8 + 7], a ; Fourth color, 7th palette - + ld a, [hli] ldh [BgPalettes + 4], a ; Third color, first palette ld a, [hli] @@ -1186,8 +1180,7 @@ ChangeAnimationPalette: call WaitFrame - ld hl, BgPalettes - call LoadBGPalettes64 + call LoadPalettesFromHRAM ; Delay the wait loop while the user is selecting a palette ld a, 45 ldh [WaitLoopCounter], a @@ -1223,11 +1216,11 @@ LoadDMGTilemap: .tilemapDone pop af ret - + HDMAData: db $88, $00, $98, $A0, $12 db $88, $00, $80, $00, $40 - + BootEnd: IF BootEnd > $900 FAIL "BootROM overflowed: {BootEnd}" From 5f2c7b966feb03b4a8d87b140c39f48a3ac7c953 Mon Sep 17 00:00:00 2001 From: Rupert Carmichael <5050061-carmiker@users.noreply.gitlab.com> Date: Sat, 9 May 2020 11:49:20 -0400 Subject: [PATCH 1123/1216] Pre-buffer audio samples before passing to SDL's queue --- SDL/audio/sdl.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/SDL/audio/sdl.c b/SDL/audio/sdl.c index 1f8a529e..d8e7e379 100644 --- a/SDL/audio/sdl.c +++ b/SDL/audio/sdl.c @@ -25,6 +25,10 @@ static SDL_AudioDeviceID device_id; static SDL_AudioSpec want_aspec, have_aspec; +#define BUFFERSIZE 1024 +static int bufferpos = 0; +static int16_t audiobuffer[BUFFERSIZE]; + bool GB_audio_is_playing(void) { return SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; @@ -53,7 +57,14 @@ size_t GB_audio_get_queue_length(void) void GB_audio_queue_sample(GB_sample_t *sample) { - SDL_QueueAudio(device_id, sample, sizeof(*sample)); + audiobuffer[bufferpos++] = sample->left; + audiobuffer[bufferpos++] = sample->right; + + if (bufferpos == BUFFERSIZE) + { + bufferpos = 0; + SDL_QueueAudio(device_id, (const void*)audiobuffer, BUFFERSIZE * sizeof(int16_t)); + } } void GB_audio_init(void) @@ -63,7 +74,7 @@ void GB_audio_init(void) want_aspec.freq = AUDIO_FREQUENCY; want_aspec.format = AUDIO_S16SYS; want_aspec.channels = 2; - want_aspec.samples = 512; + want_aspec.samples = 1024; SDL_version _sdl_version; SDL_GetVersion(&_sdl_version); From 3cba3e8e27e568e7daefbb7f4ff2eb73c4f18ffe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 May 2020 00:37:52 +0300 Subject: [PATCH 1124/1216] Emulate CGB-C PCM read glitch, fix a potential noise volume envelope bug --- Core/apu.c | 23 +++++++++++++++++++++-- Core/apu.h | 1 + Core/memory.c | 8 ++++---- Core/timing.c | 3 ++- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index afb970c8..7e7ab31f 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -283,6 +283,15 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) { if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { + if (gb->cgb_double_speed) { + if (index == GB_SQUARE_1) { + gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1; + } + else { + gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F; + } + } + if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { gb->apu.square_channels[index].current_volume++; } @@ -305,7 +314,10 @@ static void tick_noise_envelope(GB_gameboy_t *gb) uint8_t nr42 = gb->io_registers[GB_IO_NR42]; if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) { - if (!--gb->apu.noise_channel.volume_countdown) { + if (!gb->apu.noise_channel.volume_countdown || !--gb->apu.noise_channel.volume_countdown) { + if (gb->cgb_double_speed) { + gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; + } if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { gb->apu.noise_channel.current_volume++; } @@ -423,7 +435,7 @@ void GB_apu_run(GB_gameboy_t *gb) uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; - + if (likely(!gb->stopped || GB_is_cgb(gb))) { /* To align the square signal to 1MHz */ gb->apu.lf_div ^= cycles & 1; @@ -455,6 +467,9 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; gb->apu.square_channels[i].current_sample_index++; gb->apu.square_channels[i].current_sample_index &= 0x7; + if (cycles_left == 0 && gb->apu.samples[i] == 0) { + gb->apu.pcm_mask[0] &= i == GB_SQUARE_1? 0xF0 : 0x0F; + } update_square_sample(gb, i); } @@ -506,6 +521,10 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) { + gb->apu.pcm_mask[1] &= 0x0F; + } + update_sample(gb, GB_NOISE, gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, diff --git a/Core/apu.h b/Core/apu.h index 398b903a..a3a36a63 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -119,6 +119,7 @@ typedef struct #define GB_SKIP_DIV_EVENT_SKIP 2 uint8_t skip_div_event; bool current_lfsr_sample; + uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch } GB_apu_t; typedef enum { diff --git a/Core/memory.c b/Core/memory.c index 49296ddb..9f52af51 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -303,12 +303,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_PCM_12: if (!GB_is_cgb(gb)) return 0xFF; - return (gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | - (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0); + return ((gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | + (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[0] : 0xFF); case GB_IO_PCM_34: if (!GB_is_cgb(gb)) return 0xFF; - return (gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | - (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0); + return ((gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | + (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[1] : 0xFF); case GB_IO_JOYP: GB_timing_sync(gb); case GB_IO_TMA: diff --git a/Core/timing.c b/Core/timing.c index f79727c7..f734caf2 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -214,7 +214,8 @@ static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) } void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) -{ +{ + gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right // Affected by speed boost gb->dma_cycles += cycles; From 0200596391983291320a7aab1a6e849a5984d0e8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 May 2020 22:05:47 +0300 Subject: [PATCH 1125/1216] Fix #256 --- libretro/libretro.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 9e0f9a35..a7d64328 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -346,14 +346,14 @@ static void set_link_cable_state(bool state) static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) { const char *model_name = (char *[]){ - [GB_BOOT_ROM_DMG0] = "dmg0_boot", - [GB_BOOT_ROM_DMG] = "dmg_boot", - [GB_BOOT_ROM_MGB] = "mgb_boot", - [GB_BOOT_ROM_SGB] = "sgb_boot", - [GB_BOOT_ROM_SGB2] = "sgb2_boot", - [GB_BOOT_ROM_CGB0] = "cgb0_boot", - [GB_BOOT_ROM_CGB] = "cgb_boot", - [GB_BOOT_ROM_AGB] = "agb_boot", + [GB_BOOT_ROM_DMG0] = "dmg0", + [GB_BOOT_ROM_DMG] = "dmg", + [GB_BOOT_ROM_MGB] = "mgb", + [GB_BOOT_ROM_SGB] = "sgb", + [GB_BOOT_ROM_SGB2] = "sgb2", + [GB_BOOT_ROM_CGB0] = "cgb0", + [GB_BOOT_ROM_CGB] = "cgb", + [GB_BOOT_ROM_AGB] = "agb", }[type]; const uint8_t *boot_code = (const unsigned char *[]) From 1b7c3c4c7c29cff93f89b5a9836e5620a9ea6459 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 May 2020 22:16:49 +0300 Subject: [PATCH 1126/1216] Minor fixes, style update --- SDL/audio/sdl.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/SDL/audio/sdl.c b/SDL/audio/sdl.c index d8e7e379..12ee69ae 100644 --- a/SDL/audio/sdl.c +++ b/SDL/audio/sdl.c @@ -25,9 +25,9 @@ static SDL_AudioDeviceID device_id; static SDL_AudioSpec want_aspec, have_aspec; -#define BUFFERSIZE 1024 -static int bufferpos = 0; -static int16_t audiobuffer[BUFFERSIZE]; +#define AUDIO_BUFFER_SIZE 512 +static unsigned buffer_pos = 0; +static GB_sample_t audio_buffer[AUDIO_BUFFER_SIZE]; bool GB_audio_is_playing(void) { @@ -57,13 +57,11 @@ size_t GB_audio_get_queue_length(void) void GB_audio_queue_sample(GB_sample_t *sample) { - audiobuffer[bufferpos++] = sample->left; - audiobuffer[bufferpos++] = sample->right; + audio_buffer[buffer_pos++] = *sample; - if (bufferpos == BUFFERSIZE) - { - bufferpos = 0; - SDL_QueueAudio(device_id, (const void*)audiobuffer, BUFFERSIZE * sizeof(int16_t)); + if (buffer_pos == AUDIO_BUFFER_SIZE) { + buffer_pos = 0; + SDL_QueueAudio(device_id, (const void *)audio_buffer, sizeof(audio_buffer)); } } @@ -74,7 +72,7 @@ void GB_audio_init(void) want_aspec.freq = AUDIO_FREQUENCY; want_aspec.format = AUDIO_S16SYS; want_aspec.channels = 2; - want_aspec.samples = 1024; + want_aspec.samples = 512; SDL_version _sdl_version; SDL_GetVersion(&_sdl_version); From 2cc980755e4dc5acdf18b844f8ac0ab6be694fe6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 13 May 2020 22:21:31 +0300 Subject: [PATCH 1127/1216] HuC1 IR support --- Core/debugger.c | 4 +++- Core/gb.h | 1 + Core/mbc.h | 2 +- Core/memory.c | 20 ++++++++++++++++---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index a46de861..20884c66 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1439,7 +1439,9 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); if (cartridge->has_ram) { GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); - GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + if (gb->cartridge_type->mbc_type != GB_HUC1) { + GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + } } if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); diff --git a/Core/gb.h b/Core/gb.h index 97a8069b..29d394f8 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -430,6 +430,7 @@ struct GB_gameboy_internal_s { bool camera_registers_mapped; uint8_t camera_registers[0x36]; bool rumble_state; + bool cart_ir; ); diff --git a/Core/mbc.h b/Core/mbc.h index 7e9b47f0..6a23300f 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -10,7 +10,7 @@ typedef struct { GB_MBC2, GB_MBC3, GB_MBC5, - GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */ + GB_HUC1, GB_HUC3, } mbc_type; enum { diff --git a/Core/memory.c b/Core/memory.c index 9f52af51..6a65a98e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -150,6 +150,10 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) gb->cartridge_type->mbc_subtype != GB_CAMERA && gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF; + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { + return 0xc0 | gb->cart_ir | gb->infrared_input | (gb->io_registers[GB_IO_RP] & 1); + } + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ @@ -379,7 +383,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_RP: { if (!gb->cgb_mode) return 0xFF; /* You will read your own IR LED if it's on. */ - bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1); + bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) { ret |= 2; @@ -493,10 +497,9 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_HUC1: switch (addr & 0xF000) { - case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0000: case 0x1000: gb->huc1.mode = (value & 0xF) == 0xE; break; case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; - case 0x6000: case 0x7000: gb->huc1.mode = value; break; } break; case GB_HUC3: @@ -526,7 +529,16 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return; + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_type != GB_HUC1) return; + + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { + if (gb->cart_ir != (value & 1) && gb->infrared_callback) { + gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); + gb->cycles_since_ir_change = 0; + } + gb->cart_ir = value & 1; + return; + } if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; From a9023d08c69f0c1fbbd685d30a2e002a61f941ab Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 May 2020 23:27:17 +0300 Subject: [PATCH 1128/1216] =?UTF-8?q?Emulate=20HuC-3=E2=80=99s=20IR=20and?= =?UTF-8?q?=20RTC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/debugger.c | 4 +- Core/gb.c | 98 ++++++++++++++++++++++++++++++++++++++- Core/gb.h | 12 ++++- Core/mbc.c | 4 +- Core/memory.c | 121 ++++++++++++++++++++++++++++++++++++++++++------ Core/sm83_cpu.c | 5 +- Core/timing.c | 12 +++++ Core/timing.h | 5 +- 8 files changed, 230 insertions(+), 31 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 20884c66..0f08fb10 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1431,8 +1431,8 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg [GB_MBC2] = "MBC2", [GB_MBC3] = "MBC3", [GB_MBC5] = "MBC5", - [GB_HUC1] = "HUC1", - [GB_HUC3] = "HUC3", + [GB_HUC1] = "HUC-1", + [GB_HUC3] = "HUC-3", }; GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); } diff --git a/Core/gb.c b/Core/gb.c index a1f573c1..d0632ddd 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -560,6 +560,12 @@ typedef struct { uint8_t padding5[3]; } GB_vba_rtc_time_t; +typedef struct __attribute__((packed)) { + uint64_t last_rtc_second; + uint16_t minutes; + uint16_t days; +} GB_huc3_rtc_time_t; + typedef union { struct __attribute__((packed)) { GB_rtc_time_t rtc_real; @@ -582,6 +588,9 @@ int GB_save_battery_size(GB_gameboy_t *gb) if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + if (gb->cartridge_type->mbc_type == GB_HUC3) { + return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); + } GB_rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); } @@ -595,7 +604,25 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); - if (gb->cartridge_type->has_rtc) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + buffer += gb->mbc_ram_size; + +#ifdef GB_BIG_ENDIAN + GB_huc3_rtc_time_t rtc_save = { + __builtin_bswap64(gb->last_rtc_second), + __builtin_bswap16(gb->huc3_minutes), + __builtin_bswap16(gb->huc3_days), + }; +#else + GB_huc3_rtc_time_t rtc_save = { + gb->last_rtc_second, + gb->huc3_minutes, + gb->huc3_days, + }; +#endif + memcpy(buffer, &rtc_save, sizeof(rtc_save)); + } + else if (gb->cartridge_type->has_rtc) { GB_rtc_save_t rtc_save = {{{{0,}},},}; rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; @@ -633,7 +660,27 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) fclose(f); return EIO; } - if (gb->cartridge_type->has_rtc) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { +#ifdef GB_BIG_ENDIAN + GB_huc3_rtc_time_t rtc_save = { + __builtin_bswap64(gb->last_rtc_second), + __builtin_bswap16(gb->huc3_minutes), + __builtin_bswap16(gb->huc3_days), + }; +#else + GB_huc3_rtc_time_t rtc_save = { + gb->last_rtc_second, + gb->huc3_minutes, + gb->huc3_days, + }; +#endif + + if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + fclose(f); + return EIO; + } + } + else if (gb->cartridge_type->has_rtc) { GB_rtc_save_t rtc_save = {{{{0,}},},}; rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; @@ -668,6 +715,28 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t if (size <= gb->mbc_ram_size) { goto reset_rtc; } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + GB_huc3_rtc_time_t rtc_save; + if (size - gb->mbc_ram_size < sizeof(rtc_save)) { + goto reset_rtc; + } + memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); + gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3_days = __builtin_bswap16(rtc_save.days); +#else + gb->last_rtc_second = rtc_save.last_rtc_second; + gb->huc3_minutes = rtc_save.minutes; + gb->huc3_days = rtc_save.days; +#endif + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } GB_rtc_save_t rtc_save; memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size)); @@ -731,6 +800,8 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ + gb->huc3_days = 0xFFFF; + gb->huc3_minutes = 0xFFF; exit: return; } @@ -746,6 +817,27 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { goto reset_rtc; } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + GB_huc3_rtc_time_t rtc_save; + if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + goto reset_rtc; + } +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); + gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3_days = __builtin_bswap16(rtc_save.days); +#else + gb->last_rtc_second = rtc_save.last_rtc_second; + gb->huc3_minutes = rtc_save.minutes; + gb->huc3_days = rtc_save.days; +#endif + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } GB_rtc_save_t rtc_save; switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) { @@ -808,6 +900,8 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ + gb->huc3_days = 0xFFFF; + gb->huc3_minutes = 0xFFF; exit: fclose(f); return; diff --git a/Core/gb.h b/Core/gb.h index 29d394f8..f5f7df50 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -422,8 +422,9 @@ struct GB_gameboy_internal_s { } huc1; struct { - uint8_t rom_bank; - uint8_t ram_bank; + uint8_t rom_bank:7; + uint8_t padding:1; + uint8_t ram_bank:4; } huc3; }; uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ @@ -431,6 +432,13 @@ struct GB_gameboy_internal_s { uint8_t camera_registers[0x36]; bool rumble_state; bool cart_ir; + + // TODO: move to huc3 struct when breaking save compat + uint8_t huc3_mode; + uint8_t huc3_access_index; + uint16_t huc3_minutes, huc3_days; + uint8_t huc3_read; + uint8_t huc3_access_flags; ); diff --git a/Core/mbc.c b/Core/mbc.c index 2ee53e81..72073f60 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -37,8 +37,8 @@ const GB_cartridge_t GB_cart_defs[256] = { [0xFC] = { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) - { GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only) - { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings) + { GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3 + { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY }; void GB_update_mbc_mappings(GB_gameboy_t *gb) diff --git a/Core/memory.c b/Core/memory.c index 6a65a98e..fbf1318e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -113,6 +113,11 @@ static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); } +static bool effective_ir_input(GB_gameboy_t *gb) +{ + return gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; +} + static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0x100 && !gb->boot_rom_finished) { @@ -146,12 +151,33 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + switch (gb->huc3_mode) { + case 0xC: // RTC read + if (gb->huc3_access_flags == 0x2) { + return 1; + } + return gb->huc3_read; + case 0xD: // RTC status + return 1; + case 0xE: // IR mode + return effective_ir_input(gb); // TODO: What are the other bits? + default: + GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr); + return 1; // TODO: What happens in this case? + case 0: // TODO: R/O RAM? (or is it disabled?) + case 0xA: // RAM + break; + } + } + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_subtype != GB_CAMERA && - gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF; + gb->cartridge_type->mbc_type != GB_HUC1 && + gb->cartridge_type->mbc_type != GB_HUC3) return 0xFF; if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { - return 0xc0 | gb->cart_ir | gb->infrared_input | (gb->io_registers[GB_IO_RP] & 1); + return 0xc0 | effective_ir_input(gb); } if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { @@ -383,9 +409,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_RP: { if (!gb->cgb_mode) return 0xFF; /* You will read your own IR LED if it's on. */ - bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; - if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) { + if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && effective_ir_input(gb)) { ret |= 2; } return ret; @@ -504,7 +529,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_HUC3: switch (addr & 0xF000) { - case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0000: case 0x1000: + gb->huc3_mode = value & 0xF; + gb->mbc_ram_enable = gb->huc3_mode == 0xA; + break; case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break; case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; } @@ -524,19 +552,82 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + switch (gb->huc3_mode) { + case 0xB: // RTC Write + switch (value >> 4) { + case 1: + if (gb->huc3_access_index < 3) { + gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; + } + else if (gb->huc3_access_index < 7) { + gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; + } + else { + GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); + } + gb->huc3_access_index++; + return; + case 3: + if (gb->huc3_access_index < 3) { + gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); + gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); + } + else if (gb->huc3_access_index < 7) { + gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); + gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); + } + gb->huc3_access_index++; + return; + case 4: + gb->huc3_access_index &= 0xF0; + gb->huc3_access_index |= value & 0xF; + return; + case 5: + gb->huc3_access_index &= 0x0F; + gb->huc3_access_index |= (value & 0xF) << 4; + return; + case 6: + gb->huc3_access_flags = (value & 0xF); + return; + + default: + GB_log(gb, "HuC-3 RTC Write %02x\n", value); + break; + } + + return; + case 0xD: // RTC status + // Not sure what writes here mean, they're always 0xFE + return; + case 0xE: // IR mode + gb->cart_ir = value & 1; + return; + default: + GB_log(gb, "Unsupported HuC-3 mode %x write: [%04x] = %02x\n", gb->huc3_mode, addr, value); + return; + case 0: // Disabled + case 0xA: // RAM + break; + } + } + if (gb->camera_registers_mapped) { GB_camera_write_register(gb, addr, value); return; } - if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_type != GB_HUC1) return; + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) + && gb->cartridge_type->mbc_type != GB_HUC1) return; if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { - if (gb->cart_ir != (value & 1) && gb->infrared_callback) { - gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); + bool old_input = effective_ir_input(gb); + gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); gb->cycles_since_ir_change = 0; } - gb->cart_ir = value & 1; return; } @@ -943,13 +1034,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (!GB_is_cgb(gb)) { return; } - if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) { - if (gb->infrared_callback) { - gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); - gb->cycles_since_ir_change = 0; - } - } + bool old_input = effective_ir_input(gb); gb->io_registers[GB_IO_RP] = value; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + gb->cycles_since_ir_change = 0; + } return; } diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 13f05df7..d0b8ec4c 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -917,10 +917,7 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) { assert(gb->pending_cycles == 4); gb->pending_cycles = 0; - GB_advance_cycles(gb, 1); - GB_advance_cycles(gb, 1); - GB_advance_cycles(gb, 1); - GB_advance_cycles(gb, 1); + GB_advance_cycles(gb, 4); gb->halted = true; /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ diff --git a/Core/timing.c b/Core/timing.c index f734caf2..17983bc2 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -279,6 +279,18 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) void GB_rtc_run(GB_gameboy_t *gb) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + time_t current_time = time(NULL); + while (gb->last_rtc_second / 60 < current_time / 60) { + gb->last_rtc_second += 60; + gb->huc3_minutes++; + if (gb->huc3_minutes == 60 * 24) { + gb->huc3_days++; + gb->huc3_minutes = 0; + } + } + return; + } if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ time_t current_time = time(NULL); while (gb->last_rtc_second < current_time) { diff --git a/Core/timing.h b/Core/timing.h index 02ca54ce..d4fa07f9 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -15,7 +15,6 @@ enum { GB_TIMA_RELOADED = 2 }; -#define GB_HALT_VALUE (0xFFFF) #define GB_SLEEP(gb, unit, state, cycles) do {\ (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ @@ -26,12 +25,10 @@ enum { }\ } while (0) -#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE - #define GB_STATE_MACHINE(gb, unit, cycles, divisor) \ static const int __state_machine_divisor = divisor;\ (gb)->unit##_cycles += cycles; \ -if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\ +if ((gb)->unit##_cycles <= 0) {\ return;\ }\ switch ((gb)->unit##_state) From a588993f28257641ffde349af265a29a6e20c1f7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 17 May 2020 00:10:43 +0300 Subject: [PATCH 1129/1216] Add an HuC command required by Pocket Family 2 --- Core/memory.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index fbf1318e..5075c6f4 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -568,6 +568,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } gb->huc3_access_index++; return; + case 2: case 3: if (gb->huc3_access_index < 3) { gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); @@ -577,7 +578,9 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); } - gb->huc3_access_index++; + if ((value >> 4) == 3) { + gb->huc3_access_index++; + } return; case 4: gb->huc3_access_index &= 0xF0; From 157123e118589e48273efe3f90eb93afb49b3eab Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 17 May 2020 19:24:11 +0300 Subject: [PATCH 1130/1216] Fix clearing OAM and initializeing wave RAM --- BootROMs/cgb_boot.asm | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 8770b8aa..dc3544f5 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -15,6 +15,22 @@ Start: call ClearMemoryPage ld [c], a +; Clear OAM + ld h, $fe + ld c, $a0 +.clearOAMLoop + ldi [hl], a + dec c + jr nz, .clearOAMLoop + +; Init waveform + ld c, $10 +.waveformLoop + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop + ; Clear chosen input palette ldh [InputPalette], a ; Clear title checksum @@ -29,22 +45,6 @@ Start: ld a, $77 ldh [$24], a ld hl, $FF30 -; Init waveform - ld c, $10 -.waveformLoop - ldi [hl], a - cpl - dec c - jr nz, .waveformLoop - - -; Clear OAM - ld h, $fe - ld c, $a0 -.clearOAMLoop - ldi [hl], a - dec c - jr nz, .clearOAMLoop ; Init BG palette ld a, $fc From 933b6228862a871006052ab32bec77fc5515fc46 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 19 May 2020 01:24:02 +0300 Subject: [PATCH 1131/1216] Allow more GameShark cheats --- Core/cheats.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Core/cheats.c b/Core/cheats.c index 0525816f..14875e01 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -133,9 +133,6 @@ bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *descriptio uint8_t value; uint16_t address; if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) { - if (address > 0x7FFF) { - return false; - } if (bank >= 0x80) { bank &= 0xF; } @@ -144,7 +141,7 @@ bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *descriptio } } - /* GameGnie */ + /* GameGenie */ { char stripped_cheat[10] = {0,}; for (unsigned i = 0; i < 9 && *cheat; i++) { From ce9114ed556f71afcca17ad31b7d9858810a75d4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 19 May 2020 01:24:09 +0300 Subject: [PATCH 1132/1216] Fix IR bugs --- Core/memory.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 5075c6f4..474eda6e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -595,7 +595,6 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; default: - GB_log(gb, "HuC-3 RTC Write %02x\n", value); break; } @@ -603,9 +602,18 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0xD: // RTC status // Not sure what writes here mean, they're always 0xFE return; - case 0xE: // IR mode + case 0xE: { // IR mode + bool old_input = effective_ir_input(gb); gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } + gb->cycles_since_ir_change = 0; + } return; + } default: GB_log(gb, "Unsupported HuC-3 mode %x write: [%04x] = %02x\n", gb->huc3_mode, addr, value); return; @@ -628,7 +636,9 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->cart_ir = value & 1; bool new_input = effective_ir_input(gb); if (new_input != old_input) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } gb->cycles_since_ir_change = 0; } return; @@ -1041,7 +1051,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_RP] = value; bool new_input = effective_ir_input(gb); if (new_input != old_input) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } gb->cycles_since_ir_change = 0; } return; From 08ca56eec7f53f6f8849f667c2137666162ca585 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 May 2020 00:05:43 +0300 Subject: [PATCH 1133/1216] Cleanup --- Core/gb.h | 2 +- Core/memory.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index f5f7df50..426a3efb 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -418,7 +418,7 @@ struct GB_gameboy_internal_s { struct { uint8_t bank_low:6; uint8_t bank_high:3; - uint8_t mode:1; + bool mode:1; } huc1; struct { diff --git a/Core/memory.c b/Core/memory.c index 474eda6e..61a397a7 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -174,13 +174,16 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_subtype != GB_CAMERA && gb->cartridge_type->mbc_type != GB_HUC1 && - gb->cartridge_type->mbc_type != GB_HUC3) return 0xFF; + gb->cartridge_type->mbc_type != GB_HUC3) { + return 0xFF; + } if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { return 0xc0 | effective_ir_input(gb); } - if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && + gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ return gb->rtc_latched.data[gb->mbc_ram_bank - 8]; From 369410f3705b155117273548898389d649d52a20 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 May 2020 00:09:30 +0300 Subject: [PATCH 1134/1216] Fix HuC-1 regression --- Core/gb.h | 1 + Core/memory.c | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 426a3efb..4e6cb3ee 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -419,6 +419,7 @@ struct GB_gameboy_internal_s { uint8_t bank_low:6; uint8_t bank_high:3; bool mode:1; + bool ir_mode:1; } huc1; struct { diff --git a/Core/memory.c b/Core/memory.c index 61a397a7..85ee0999 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -178,7 +178,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) return 0xFF; } - if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { return 0xc0 | effective_ir_input(gb); } @@ -525,9 +525,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_HUC1: switch (addr & 0xF000) { - case 0x0000: case 0x1000: gb->huc1.mode = (value & 0xF) == 0xE; break; + case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break; case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->huc1.mode = value; break; } break; case GB_HUC3: @@ -634,7 +635,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_type != GB_HUC1) return; - if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { bool old_input = effective_ir_input(gb); gb->cart_ir = value & 1; bool new_input = effective_ir_input(gb); From 7af66387def986861992d977aab82502e1fe6b6b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 May 2020 14:50:54 +0300 Subject: [PATCH 1135/1216] HuC-3 alarm clock emulation --- Cocoa/AppDelegate.h | 2 +- Cocoa/AppDelegate.m | 6 ++ Cocoa/Document.m | 33 ++++++++++ Core/gb.c | 39 +++++++++++ Core/gb.h | 5 ++ Core/memory.c | 156 +++++++++++++++++++++++++------------------- 6 files changed, 172 insertions(+), 69 deletions(-) diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index 608a50ce..22e0c365 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -1,6 +1,6 @@ #import -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject @property IBOutlet NSWindow *preferencesWindow; @property (strong) IBOutlet NSView *graphicsTab; diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 34046202..e54012fd 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -50,6 +50,8 @@ JOYAxes2DEmulateButtonsKey: @YES, JOYHatsEmulateButtonsKey: @YES, }]; + + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; } - (IBAction)toggleDeveloperMode:(id)sender @@ -101,4 +103,8 @@ return YES; } +- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification +{ + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; +} @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ff47cd9f..ff77f8ef 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -358,6 +358,23 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) } NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; + + /* Clear pending alarms, don't play alarms while playing*/ + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + for (NSUserNotification *notification in [center scheduledNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeScheduledNotification:notification]; + break; + } + } + + for (NSUserNotification *notification in [center deliveredNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeDeliveredNotification:notification]; + break; + } + } + while (running) { if (rewind) { rewind = false; @@ -381,6 +398,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); + unsigned time_to_alarm = GB_time_to_alarm(&gb); + + if (time_to_alarm) { + NSUserNotification *notification = [[NSUserNotification alloc] init]; + NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension]; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil]; + friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""]; + friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName]; + notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName]; + notification.identifier = self.fileName; + notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm]; + notification.soundName = NSUserNotificationDefaultSoundName; + [center scheduleNotification:notification]; + } [_view setRumble:0]; stopping = false; } diff --git a/Core/gb.c b/Core/gb.c index d0632ddd..ce9b9af9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -564,6 +564,8 @@ typedef struct __attribute__((packed)) { uint64_t last_rtc_second; uint16_t minutes; uint16_t days; + uint16_t alarm_minutes, alarm_days; + uint8_t alarm_enabled; } GB_huc3_rtc_time_t; typedef union { @@ -612,12 +614,18 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) __builtin_bswap64(gb->last_rtc_second), __builtin_bswap16(gb->huc3_minutes), __builtin_bswap16(gb->huc3_days), + __builtin_bswap16(gb->huc3_alarm_minutes), + __builtin_bswap16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, }; #else GB_huc3_rtc_time_t rtc_save = { gb->last_rtc_second, gb->huc3_minutes, gb->huc3_days, + gb->huc3_alarm_minutes, + gb->huc3_alarm_days, + gb->huc3_alarm_enabled, }; #endif memcpy(buffer, &rtc_save, sizeof(rtc_save)); @@ -666,12 +674,18 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) __builtin_bswap64(gb->last_rtc_second), __builtin_bswap16(gb->huc3_minutes), __builtin_bswap16(gb->huc3_days), + __builtin_bswap16(gb->huc3_alarm_minutes), + __builtin_bswap16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, }; #else GB_huc3_rtc_time_t rtc_save = { gb->last_rtc_second, gb->huc3_minutes, gb->huc3_days, + gb->huc3_alarm_minutes, + gb->huc3_alarm_days, + gb->huc3_alarm_enabled, }; #endif @@ -726,10 +740,16 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); gb->huc3_days = __builtin_bswap16(rtc_save.days); + gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #else gb->last_rtc_second = rtc_save.last_rtc_second; gb->huc3_minutes = rtc_save.minutes; gb->huc3_days = rtc_save.days; + gb->huc3_alarm_minutes = rtc_save.alarm_minutes; + gb->huc3_alarm_days = rtc_save.alarm_days; + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -802,6 +822,7 @@ reset_rtc: gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ gb->huc3_days = 0xFFFF; gb->huc3_minutes = 0xFFF; + gb->huc3_alarm_enabled = false; exit: return; } @@ -827,10 +848,16 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); gb->huc3_days = __builtin_bswap16(rtc_save.days); + gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #else gb->last_rtc_second = rtc_save.last_rtc_second; gb->huc3_minutes = rtc_save.minutes; gb->huc3_days = rtc_save.days; + gb->huc3_alarm_minutes = rtc_save.alarm_minutes; + gb->huc3_alarm_days = rtc_save.alarm_days; + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -902,6 +929,7 @@ reset_rtc: gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ gb->huc3_days = 0xFFFF; gb->huc3_minutes = 0xFFF; + gb->huc3_alarm_enabled = false; exit: fclose(f); return; @@ -1568,3 +1596,14 @@ void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t gb->boot_rom_load_callback = callback; request_boot_rom(gb); } + +unsigned GB_time_to_alarm(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3) return 0; + if (!gb->huc3_alarm_enabled) return 0; + if (!(gb->huc3_alarm_days & 0x2000)) return 0; + unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60); + unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60; + if (current_time > alarm_time) return 0; + return alarm_time - current_time; +} diff --git a/Core/gb.h b/Core/gb.h index 4e6cb3ee..27b95b33 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -438,6 +438,8 @@ struct GB_gameboy_internal_s { uint8_t huc3_mode; uint8_t huc3_access_index; uint16_t huc3_minutes, huc3_days; + uint16_t huc3_alarm_minutes, huc3_alarm_days; + bool huc3_alarm_enabled; uint8_t huc3_read; uint8_t huc3_access_flags; ); @@ -779,6 +781,9 @@ void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data); void GB_disconnect_serial(GB_gameboy_t *gb); +/* For cartridges with an alarm clock */ +unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm + /* For integration with SFC/SNES emulators */ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); diff --git a/Core/memory.c b/Core/memory.c index 85ee0999..7f7686a4 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -554,77 +554,97 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; } +static bool huc3_write(GB_gameboy_t *gb, uint8_t value) +{ + switch (gb->huc3_mode) { + case 0xB: // RTC Write + switch (value >> 4) { + case 1: + if (gb->huc3_access_index < 3) { + gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; + } + else if (gb->huc3_access_index < 7) { + gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; + } + else { + // GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); + } + gb->huc3_access_index++; + break; + case 2: + case 3: + if (gb->huc3_access_index < 3) { + gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); + gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); + } + else if (gb->huc3_access_index < 7) { + gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); + gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); + } + else if (gb->huc3_access_index >= 0x58 && gb->huc3_access_index <= 0x5a) { + gb->huc3_alarm_minutes &= ~(0xF << ((gb->huc3_access_index - 0x58) * 4)); + gb->huc3_alarm_minutes |= ((value & 0xF) << ((gb->huc3_access_index - 0x58) * 4)); + } + else if (gb->huc3_access_index >= 0x5b && gb->huc3_access_index <= 0x5e) { + gb->huc3_alarm_days &= ~(0xF << ((gb->huc3_access_index - 0x5b) * 4)); + gb->huc3_alarm_days |= ((value & 0xF) << ((gb->huc3_access_index - 0x5b) * 4)); + } + else if (gb->huc3_access_index == 0x5f) { + gb->huc3_alarm_enabled = value & 1; + } + else { + // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3_access_index); + } + if ((value >> 4) == 3) { + gb->huc3_access_index++; + } + break; + case 4: + gb->huc3_access_index &= 0xF0; + gb->huc3_access_index |= value & 0xF; + break; + case 5: + gb->huc3_access_index &= 0x0F; + gb->huc3_access_index |= (value & 0xF) << 4; + break; + case 6: + gb->huc3_access_flags = (value & 0xF); + break; + + default: + break; + } + + return true; + case 0xD: // RTC status + // Not sure what writes here mean, they're always 0xFE + return true; + case 0xE: { // IR mode + bool old_input = effective_ir_input(gb); + gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } + gb->cycles_since_ir_change = 0; + } + return true; + } + case 0xC: + return true; + default: + return false; + case 0: // Disabled + case 0xA: // RAM + return false; + } +} + static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (gb->cartridge_type->mbc_type == GB_HUC3) { - switch (gb->huc3_mode) { - case 0xB: // RTC Write - switch (value >> 4) { - case 1: - if (gb->huc3_access_index < 3) { - gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; - } - else if (gb->huc3_access_index < 7) { - gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; - } - else { - GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); - } - gb->huc3_access_index++; - return; - case 2: - case 3: - if (gb->huc3_access_index < 3) { - gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); - gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); - } - else if (gb->huc3_access_index < 7) { - gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); - gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); - } - if ((value >> 4) == 3) { - gb->huc3_access_index++; - } - return; - case 4: - gb->huc3_access_index &= 0xF0; - gb->huc3_access_index |= value & 0xF; - return; - case 5: - gb->huc3_access_index &= 0x0F; - gb->huc3_access_index |= (value & 0xF) << 4; - return; - case 6: - gb->huc3_access_flags = (value & 0xF); - return; - - default: - break; - } - - return; - case 0xD: // RTC status - // Not sure what writes here mean, they're always 0xFE - return; - case 0xE: { // IR mode - bool old_input = effective_ir_input(gb); - gb->cart_ir = value & 1; - bool new_input = effective_ir_input(gb); - if (new_input != old_input) { - if (gb->infrared_callback) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); - } - gb->cycles_since_ir_change = 0; - } - return; - } - default: - GB_log(gb, "Unsupported HuC-3 mode %x write: [%04x] = %02x\n", gb->huc3_mode, addr, value); - return; - case 0: // Disabled - case 0xA: // RAM - break; - } + if (huc3_write(gb, value)) return; } if (gb->camera_registers_mapped) { From f1442b0ea6b86712a1b93c8e1375b08ddb985fcf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 24 May 2020 23:04:36 +0300 Subject: [PATCH 1136/1216] Attempt to add rumble support to SDL. Who knows it might work. --- Cocoa/Preferences.xib | 6 ++--- SDL/gui.c | 56 +++++++++++++++++++++++++++++++++++++++---- SDL/gui.h | 2 ++ SDL/main.c | 7 ++++++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 149f71ee..aa4a87da 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -603,13 +603,13 @@ - + - - + + diff --git a/SDL/gui.c b/SDL/gui.c index 5650d9b1..81a9e425 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -108,10 +108,11 @@ configuration_t configuration = .rewind_length = 60 * 2, .model = MODEL_CGB, .volume = 100, + .rumble_mode = GB_RUMBLE_ALL_GAMES, }; -static const char *help[] ={ +static const char *help[] = { "Drop a ROM to play.\n" "\n" "Keyboard Shortcuts:\n" @@ -763,6 +764,7 @@ static void enter_controls_menu(unsigned index) static unsigned joypad_index = 0; static SDL_Joystick *joystick = NULL; static SDL_GameController *controller = NULL; +SDL_Haptic *haptic = NULL; const char *current_joypad_name(unsigned index) { @@ -792,6 +794,12 @@ static void cycle_joypads(unsigned index) if (joypad_index >= SDL_NumJoysticks()) { joypad_index = 0; } + + if (haptic) { + SDL_HapticClose(haptic); + haptic = NULL; + } + if (controller) { SDL_GameControllerClose(controller); controller = NULL; @@ -806,14 +814,22 @@ static void cycle_joypads(unsigned index) else { joystick = SDL_JoystickOpen(joypad_index); } -} + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + }} static void cycle_joypads_backwards(unsigned index) { - joypad_index++; + joypad_index--; if (joypad_index >= SDL_NumJoysticks()) { joypad_index = SDL_NumJoysticks() - 1; } + + if (haptic) { + SDL_HapticClose(haptic); + haptic = NULL; + } + if (controller) { SDL_GameControllerClose(controller); controller = NULL; @@ -828,7 +844,9 @@ static void cycle_joypads_backwards(unsigned index) else { joystick = SDL_JoystickOpen(joypad_index); } -} + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + }} static void detect_joypad_layout(unsigned index) { @@ -837,9 +855,36 @@ static void detect_joypad_layout(unsigned index) joypad_axis_temp = -1; } +static void cycle_rumble_mode(unsigned index) +{ + if (configuration.rumble_mode == GB_RUMBLE_ALL_GAMES) { + configuration.rumble_mode = GB_RUMBLE_DISABLED; + } + else { + configuration.rumble_mode++; + } +} + +static void cycle_rumble_mode_backwards(unsigned index) +{ + if (configuration.rumble_mode == GB_RUMBLE_DISABLED) { + configuration.rumble_mode = GB_RUMBLE_ALL_GAMES; + } + else { + configuration.rumble_mode--; + } +} + +const char *current_rumble_mode(unsigned index) +{ + return (const char *[]){"Disabled", "Rumble Game Paks Only", "All Games"} + [configuration.rumble_mode]; +} + static const struct menu_item joypad_menu[] = { {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, {"Configure layout", detect_joypad_layout}, + {"Rumble Mode:", cycle_rumble_mode, current_rumble_mode, cycle_rumble_mode_backwards}, {"Back", return_to_root_menu}, {NULL,} }; @@ -893,6 +938,9 @@ void connect_joypad(void) joystick = SDL_JoystickOpen(0); } } + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + } } void run_gui(bool is_running) diff --git a/SDL/gui.h b/SDL/gui.h index 4a3b55fc..af7543b5 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -21,6 +21,7 @@ extern SDL_Window *window; extern SDL_Renderer *renderer; extern SDL_Texture *texture; extern SDL_PixelFormat *pixel_format; +extern SDL_Haptic *haptic; extern shader_t shader; enum scaling_mode { @@ -105,6 +106,7 @@ typedef struct { uint8_t dmg_palette; GB_border_mode_t border_mode; uint8_t volume; + GB_rumble_mode_t rumble_mode; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index c4a4d0f6..f75e3aac 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -367,6 +367,11 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return SDL_MapRGB(pixel_format, r, g, b); } +static void rumble(GB_gameboy_t *gb, double amp) +{ + SDL_HapticRumblePlay(haptic, amp, 250); +} + static void debugger_interrupt(int ignore) { if (!GB_is_inited(&gb)) return; @@ -488,6 +493,8 @@ restart: GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_pixels_output(&gb, active_pixel_buffer); GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_rumble_callback(&gb, rumble); + GB_set_rumble_mode(&gb, configuration.rumble_mode); GB_set_sample_rate(&gb, GB_audio_get_frequency()); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); update_palette(); From 17dfe0dd6a4e9ec2c6433059a5c94be56ffa1f4c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 May 2020 16:30:40 +0300 Subject: [PATCH 1137/1216] Fix minor CGB-C regression --- Core/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index be2e1088..4bfae7ac 100644 --- a/Core/display.c +++ b/Core/display.c @@ -851,8 +851,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 37, 2); gb->cgb_palettes_blocked = true; - gb->cycles_for_line += 3; - GB_SLEEP(gb, display, 38, 3); + gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3; + GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3); gb->vram_read_blocked = true; gb->vram_write_blocked = true; From 29b64d7545a4464a294d9096f6bbf7318913a6d4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 May 2020 16:51:20 +0300 Subject: [PATCH 1138/1216] Slightly reduce the scanline-ish LCD effect --- Shaders/MasterShader.metal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index ee8dec9a..a0b63935 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -42,7 +42,7 @@ static inline float4 texture(texture2d texture, float2 pos) #line 1 {filter} -#define BLEND_BIAS (1.0/3.0) +#define BLEND_BIAS (2.0/5.0) enum frame_blending_mode { DISABLED, From ffa569deeb1656c2a473e95a782132608152bf8a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 May 2020 23:10:23 +0300 Subject: [PATCH 1139/1216] Partial emulation of reading VRAM right after mode 3 --- Core/display.c | 10 ++++++---- Core/gb.h | 2 ++ Core/memory.c | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 4bfae7ac..94d7f77b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -603,14 +603,15 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; } - gb->current_tile = gb->vram[map + x + y / 8 * 32]; + gb->last_tile_index_address = map + x + y / 8 * 32; + gb->current_tile = gb->vram[gb->last_tile_index_address]; if (gb->vram_ppu_blocked) { gb->current_tile = 0xFF; } if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. - This probably means the CGB has a 16-bit data bus for the VRAM. */ - gb->current_tile_attributes = gb->vram[map + x + y / 8 * 32 + 0x2000]; + This probably means the CGB has a 16-bit data bus for the VRAM. */ + gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000]; if (gb->vram_ppu_blocked) { gb->current_tile_attributes = 0xFF; } @@ -667,8 +668,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } + gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; gb->current_tile_data[1] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; + gb->vram[gb->last_tile_data_address]; if (gb->vram_ppu_blocked) { gb->current_tile_data[1] = 0xFF; } diff --git a/Core/gb.h b/Core/gb.h index 27b95b33..68c4ea9b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -538,6 +538,8 @@ struct GB_gameboy_internal_s { uint8_t window_tile_x; uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. bool is_odd_frame; + uint16_t last_tile_data_address; + uint16_t last_tile_index_address; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index 7f7686a4..3f924bc7 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -146,6 +146,18 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) if (gb->vram_read_blocked) { return 0xFF; } + if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done*/ + } + else { + addr = gb->last_tile_data_address; + } + } return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; } @@ -551,6 +563,19 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); return; } + /* TODO: not verified */ + if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done */ + } + else { + addr = gb->last_tile_data_address; + } + } gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; } From fa7232944f277e4abdadd6edcd3f2bc487aca41f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 01:25:21 +0300 Subject: [PATCH 1140/1216] =?UTF-8?q?Better=20emulation=20of=20CGB?= =?UTF-8?q?=E2=80=99s=20first=20frame=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 11 +++++++++-- Core/gb.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 94d7f77b..2eb8c424 100644 --- a/Core/display.c +++ b/Core/display.c @@ -128,7 +128,7 @@ static void display_vblank(GB_gameboy_t *gb) bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; - if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (!GB_is_sgb(gb)) { uint32_t color = 0; @@ -796,6 +796,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { GB_SLEEP(gb, display, 1, LCDC_PERIOD); display_vblank(gb); + gb->cgb_repeated_a_frame = true; } return; } @@ -1240,11 +1241,17 @@ abort_fetching_object: } } else { - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { gb->is_odd_frame ^= true; display_vblank(gb); } + if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) { + gb->cgb_repeated_a_frame = true; + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else { + gb->cgb_repeated_a_frame = false; + } } } diff --git a/Core/gb.h b/Core/gb.h index 68c4ea9b..1376a12d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -540,6 +540,7 @@ struct GB_gameboy_internal_s { bool is_odd_frame; uint16_t last_tile_data_address; uint16_t last_tile_index_address; + bool cgb_repeated_a_frame; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 4d91081046f75efc701e0fda7787daebe982c72c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 17:58:02 +0300 Subject: [PATCH 1141/1216] Do not send LED updates if nothing changed --- JoyKit/JOYController.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 015737ee..12f0d39d 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -164,6 +164,8 @@ typedef union { // Used when creating inputs JOYElement *_previousAxisElement; + + uint8_t _playerLEDs; } @@ -342,6 +344,7 @@ typedef union { _logicallyConnected = true; _device = (IOHIDDeviceRef)CFRetain(device); _serialSuffix = suffix; + _playerLEDs = -1; IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self); IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); @@ -709,6 +712,10 @@ typedef union { - (void)setPlayerLEDs:(uint8_t)mask { mask &= 0xF; + if (mask == _playerLEDs) { + return; + } + _playerLEDs = mask; if (_isSwitch) { _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs _lastVendorSpecificOutput.switchPacket.sequence++; From 59b94b92ca0469f8b207081dd609fd2846fd7937 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 18:43:09 +0300 Subject: [PATCH 1142/1216] Make sure reports are only sent from one thread --- Cocoa/GBView.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index e733731d..726259d2 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -115,7 +115,7 @@ [NSCursor unhide]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; - [lastController setRumbleAmplitude:0]; + [self setRumble:0]; [JOYController unregisterListener:self]; } - (instancetype)initWithCoder:(NSCoder *)coder @@ -272,7 +272,9 @@ - (void)setRumble:(double)amp { - [lastController setRumbleAmplitude:amp]; + dispatch_async(dispatch_get_main_queue(), ^{ + [lastController setRumbleAmplitude:amp]; + }); } - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis @@ -301,7 +303,7 @@ { if (![self.window isMainWindow]) return; if (controller != lastController) { - [lastController setRumbleAmplitude:0]; + [self setRumble:0]; lastController = controller; } @@ -319,7 +321,9 @@ ![preferred_joypad isEqualToString:controller.uniqueID]) { continue; } - [controller setPlayerLEDs:1 << player]; + dispatch_async(dispatch_get_main_queue(), ^{ + [controller setPlayerLEDs:1 << player]; + }); NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; if (!mapping) { mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; From e678b50101271ede54d2f0371af2cbc3775ed550 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 20:15:13 +0300 Subject: [PATCH 1143/1216] Force all controllers to use a rumble thread --- JoyKit/JOYController.m | 96 +++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 12f0d39d..968e2e90 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -39,7 +39,7 @@ static bool axesEmulateButtons = false; static bool axes2DEmulateButtons = false; static bool hatsEmulateButtons = false; -static NSLock *globalPWMThreadLock; +static NSLock *globalRumbleThreadLock; @interface JOYController () + (void)controllerAdded:(IOHIDDeviceRef) device; @@ -152,12 +152,12 @@ typedef union { bool _isSwitch; // Does this controller use the Switch protocol? bool _isDualShock3; // Does this controller use DS3 outputs? JOYVendorSpecificOutput _lastVendorSpecificOutput; - NSLock *_rumblePWMThreadLock; + NSLock *_rumbleThreadLock; volatile double _rumblePWMRatio; bool _physicallyConnected; bool _logicallyConnected; - bool _rumblePWMThreadRunning; - volatile bool _forceStopPWMThread; + bool _rumbleThreadRunning; + volatile bool _forceStopRumbleThread; NSDictionary *_hacks; NSMutableData *_lastReport; @@ -358,7 +358,7 @@ typedef union { _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; _hatEmulatedButtons = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; - _rumblePWMThreadLock = [[NSLock alloc] init]; + _rumbleThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; @@ -697,7 +697,7 @@ typedef union { } } _physicallyConnected = false; - [self _forceStopPWMThread]; // Stop the rumble thread. + [self _forceStopRumbleThread]; // Stop the rumble thread. [exposedControllers removeObject:self]; _device = nil; } @@ -731,23 +731,30 @@ typedef union { } } -- (void)pwmThread +- (void)rumbleThread { unsigned rumbleCounter = 0; - while (self.connected && !_forceStopPWMThread) { - if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { - break; - } - rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); - if (rumbleCounter >= PWM_RESOLUTION) { - rumbleCounter -= PWM_RESOLUTION; + if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { + while (self.connected && !_forceStopRumbleThread) { + if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { + break; + } + rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); + if (rumbleCounter >= PWM_RESOLUTION) { + rumbleCounter -= PWM_RESOLUTION; + } } } - [_rumblePWMThreadLock lock]; + else { + while (self.connected && !_forceStopRumbleThread) { + [_rumbleElement setValue:_rumblePWMRatio * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; + } + } + [_rumbleThreadLock lock]; [_rumbleElement setValue:0]; - _rumblePWMThreadRunning = false; - _forceStopPWMThread = false; - [_rumblePWMThreadLock unlock]; + _rumbleThreadRunning = false; + _forceStopRumbleThread = false; + [_rumbleThreadLock unlock]; } - (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ @@ -799,32 +806,27 @@ typedef union { [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; } else { - if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { - [_rumblePWMThreadLock lock]; - _rumblePWMRatio = amp; - if (!_rumblePWMThreadRunning) { // PWM thread not running, start it. - if (amp != 0) { - /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more - than one controller uses rumble. At least make sure any sibling controllers don't have their - PWM thread running. */ - - [globalPWMThreadLock lock]; - for (JOYController *controller in [JOYController allControllers]) { - if (controller != self && controller->_device == _device) { - [controller _forceStopPWMThread]; - } + [_rumbleThreadLock lock]; + _rumblePWMRatio = amp; + if (!_rumbleThreadRunning) { // PWM thread not running, start it. + if (amp != 0) { + /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more + than one controller uses rumble. At least make sure any sibling controllers don't have their + PWM thread running. */ + + [globalRumbleThreadLock lock]; + for (JOYController *controller in [JOYController allControllers]) { + if (controller != self && controller->_device == _device) { + [controller _forceStopRumbleThread]; } - _rumblePWMRatio = amp; - _rumblePWMThreadRunning = true; - [self performSelectorInBackground:@selector(pwmThread) withObject:nil]; - [globalPWMThreadLock unlock]; } + _rumblePWMRatio = amp; + _rumbleThreadRunning = true; + [self performSelectorInBackground:@selector(rumbleThread) withObject:nil]; + [globalRumbleThreadLock unlock]; } - [_rumblePWMThreadLock unlock]; - } - else { - [_rumbleElement setValue:amp * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; } + [_rumbleThreadLock unlock]; } } @@ -833,14 +835,14 @@ typedef union { return _logicallyConnected && _physicallyConnected; } -- (void)_forceStopPWMThread +- (void)_forceStopRumbleThread { - [_rumblePWMThreadLock lock]; - if (_rumblePWMThreadRunning) { - _forceStopPWMThread = true; + [_rumbleThreadLock lock]; + if (_rumbleThreadRunning) { + _forceStopRumbleThread = true; } - [_rumblePWMThreadLock unlock]; - while (_rumblePWMThreadRunning); + [_rumbleThreadLock unlock]; + while (_rumbleThreadRunning); } + (void)controllerAdded:(IOHIDDeviceRef) device @@ -900,7 +902,7 @@ typedef union { controllers = [NSMutableDictionary dictionary]; exposedControllers = [NSMutableArray array]; - globalPWMThreadLock = [[NSLock alloc] init]; + globalRumbleThreadLock = [[NSLock alloc] init]; NSArray *array = @[ CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), From af5cb72edc7b819969a3079ca11f83ae5f6c64fa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 21:31:00 +0300 Subject: [PATCH 1144/1216] Restore Switch LED support --- JoyKit/JOYController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 968e2e90..886dea40 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -722,7 +722,7 @@ typedef union { _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED _lastVendorSpecificOutput.switchPacket.commandData[0] = mask; - //[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; } else if (_isDualShock3) { _lastVendorSpecificOutput.ds3Output.reportID = 1; From c9b401135fc35119cda01bebdbd5f3e4e83a7288 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 22:18:32 +0300 Subject: [PATCH 1145/1216] =?UTF-8?q?Actually,=20don=E2=80=99t=20use=20rum?= =?UTF-8?q?ble=20threads=20at=20all,=20because=20IOHIDDeviceSetReport=20se?= =?UTF-8?q?ems=20to=20queue=20stuff=20despite=20being=20blocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cocoa/GBView.m | 4 +- JoyKit/JOYController.m | 206 +++++++++++++++++------------------------ 2 files changed, 85 insertions(+), 125 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 726259d2..e5cb7c8b 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -272,9 +272,7 @@ - (void)setRumble:(double)amp { - dispatch_async(dispatch_get_main_queue(), ^{ - [lastController setRumbleAmplitude:amp]; - }); + [lastController setRumbleAmplitude:amp]; } - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 886dea40..96de291f 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -39,8 +39,6 @@ static bool axesEmulateButtons = false; static bool axes2DEmulateButtons = false; static bool hatsEmulateButtons = false; -static NSLock *globalRumbleThreadLock; - @interface JOYController () + (void)controllerAdded:(IOHIDDeviceRef) device; + (void)controllerRemoved:(IOHIDDeviceRef) device; @@ -95,7 +93,9 @@ static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type, uint32_t reportID, uint8_t *report, CFIndex reportLength) { - [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; + if (reportLength) { + [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; + } } typedef struct __attribute__((packed)) { @@ -152,12 +152,9 @@ typedef union { bool _isSwitch; // Does this controller use the Switch protocol? bool _isDualShock3; // Does this controller use DS3 outputs? JOYVendorSpecificOutput _lastVendorSpecificOutput; - NSLock *_rumbleThreadLock; - volatile double _rumblePWMRatio; + volatile double _rumbleAmplitude; bool _physicallyConnected; bool _logicallyConnected; - bool _rumbleThreadRunning; - volatile bool _forceStopRumbleThread; NSDictionary *_hacks; NSMutableData *_lastReport; @@ -166,7 +163,8 @@ typedef union { JOYElement *_previousAxisElement; uint8_t _playerLEDs; - + double _sentRumbleAmp; + unsigned _rumbleCounter; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -358,7 +356,6 @@ typedef union { _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; _hatEmulatedButtons = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; - _rumbleThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; @@ -368,18 +365,19 @@ typedef union { _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue]; NSDictionary *customReports = hacks[JOYCustomReports]; - + _lastReport = [NSMutableData dataWithLength:MAX( + MAX( + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] + ), + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] + )]; + IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + if (hacks[JOYCustomReports]) { _multiElements = [NSMutableDictionary dictionary]; _fullReportElements = [NSMutableDictionary dictionary]; - _lastReport = [NSMutableData dataWithLength:MAX( - MAX( - [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], - [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] - ), - [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] - )]; - IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + for (NSNumber *_reportID in customReports) { signed reportID = [_reportID intValue]; @@ -555,16 +553,17 @@ typedef union { - (void)gotReport:(NSData *)report { JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)]; - if (!element) return; - [element updateValue:report]; - - NSArray *subElements = _multiElements[element]; - if (subElements) { - for (JOYElement *subElement in subElements) { - [self _elementChanged:subElement]; + if (element) { + [element updateValue:report]; + + NSArray *subElements = _multiElements[element]; + if (subElements) { + for (JOYElement *subElement in subElements) { + [self _elementChanged:subElement]; + } } - return; } + [self updateRumble]; } - (void)elementChanged:(IOHIDElementRef)element @@ -697,7 +696,6 @@ typedef union { } } _physicallyConnected = false; - [self _forceStopRumbleThread]; // Stop the rumble thread. [exposedControllers removeObject:self]; _device = nil; } @@ -731,103 +729,78 @@ typedef union { } } -- (void)rumbleThread +- (void)updateRumble { - unsigned rumbleCounter = 0; + if (!self.connected) { + return; + } if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { - while (self.connected && !_forceStopRumbleThread) { - if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { - break; - } - rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); - if (rumbleCounter >= PWM_RESOLUTION) { - rumbleCounter -= PWM_RESOLUTION; - } + double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION); + if (ampToSend != _sentRumbleAmp) { + [_rumbleElement setValue:ampToSend]; + _sentRumbleAmp = ampToSend; + } + _rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION); + if (_rumbleCounter >= PWM_RESOLUTION) { + _rumbleCounter -= PWM_RESOLUTION; } } else { - while (self.connected && !_forceStopRumbleThread) { - [_rumbleElement setValue:_rumblePWMRatio * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; + if (_rumbleAmplitude == _sentRumbleAmp) { + return; + } + _sentRumbleAmp = _rumbleAmplitude; + if (_isSwitch) { + double frequency = 144; + double amp = _rumbleAmplitude; + + uint8_t highAmp = amp * 0x64; + uint8_t lowAmp = amp * 0x32 + 0x40; + if (frequency < 0) frequency = 0; + if (frequency > 1252) frequency = 1252; + uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); + + uint16_t highFreq = (encodedFrequency - 0x60) * 4; + uint8_t lowFreq = encodedFrequency - 0x40; + + //if (frequency < 82 || frequency > 312) { + if (amp) { + highAmp = 0; + } + + if (frequency < 40 || frequency > 626) { + lowAmp = 0; + } + + _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; + _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); + _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; + _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; + + + _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0; // LED + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + } + else if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output.reportID = 1; + _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xff : 0; + _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xff); + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; + } + else { + [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; } } - [_rumbleThreadLock lock]; - [_rumbleElement setValue:0]; - _rumbleThreadRunning = false; - _forceStopRumbleThread = false; - [_rumbleThreadLock unlock]; } - (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ { - double frequency = 144; // I have no idea what I'm doing. - if (amp < 0) amp = 0; if (amp > 1) amp = 1; - if (_isSwitch) { - if (amp == 0) { - amp = 1; - frequency = 0; - } - - uint8_t highAmp = amp * 0x64; - uint8_t lowAmp = amp * 0x32 + 0x40; - if (frequency < 0) frequency = 0; - if (frequency > 1252) frequency = 1252; - uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); - - uint16_t highFreq = (encodedFrequency - 0x60) * 4; - uint8_t lowFreq = encodedFrequency - 0x40; - - //if (frequency < 82 || frequency > 312) { - if (amp) { - highAmp = 0; - } - - if (frequency < 40 || frequency > 626) { - lowAmp = 0; - } - - _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; - _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); - _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; - _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; - - - _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only - _lastVendorSpecificOutput.switchPacket.sequence++; - _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; - _lastVendorSpecificOutput.switchPacket.command = 0; // LED - [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; - } - else if (_isDualShock3) { - _lastVendorSpecificOutput.ds3Output.reportID = 1; - _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = amp? 0xff : 0; - _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = amp * 0xff; - [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; - } - else { - [_rumbleThreadLock lock]; - _rumblePWMRatio = amp; - if (!_rumbleThreadRunning) { // PWM thread not running, start it. - if (amp != 0) { - /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more - than one controller uses rumble. At least make sure any sibling controllers don't have their - PWM thread running. */ - - [globalRumbleThreadLock lock]; - for (JOYController *controller in [JOYController allControllers]) { - if (controller != self && controller->_device == _device) { - [controller _forceStopRumbleThread]; - } - } - _rumblePWMRatio = amp; - _rumbleThreadRunning = true; - [self performSelectorInBackground:@selector(rumbleThread) withObject:nil]; - [globalRumbleThreadLock unlock]; - } - } - [_rumbleThreadLock unlock]; - } + _rumbleAmplitude = amp; } - (bool)isConnected @@ -835,16 +808,6 @@ typedef union { return _logicallyConnected && _physicallyConnected; } -- (void)_forceStopRumbleThread -{ - [_rumbleThreadLock lock]; - if (_rumbleThreadRunning) { - _forceStopRumbleThread = true; - } - [_rumbleThreadLock unlock]; - while (_rumbleThreadRunning); -} - + (void)controllerAdded:(IOHIDDeviceRef) device { NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); @@ -902,7 +865,6 @@ typedef union { controllers = [NSMutableDictionary dictionary]; exposedControllers = [NSMutableArray array]; - globalRumbleThreadLock = [[NSLock alloc] init]; NSArray *array = @[ CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), From c665fcb2ed2705d017bd6dcaef7f405a836c9ce5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 22:20:45 +0300 Subject: [PATCH 1146/1216] Minor fixes --- JoyKit/JOYController.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 96de291f..b815ddb1 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -697,6 +697,8 @@ typedef union { } _physicallyConnected = false; [exposedControllers removeObject:self]; + [self setRumbleAmplitude:0]; + [self updateRumble]; _device = nil; } @@ -734,6 +736,9 @@ typedef union { if (!self.connected) { return; } + if (!_rumbleElement && !_isSwitch && !_isDualShock3) { + return; + } if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION); if (ampToSend != _sentRumbleAmp) { From 83b959c12625ad8840fdb3634a0a7b9ea8be6f8c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 22:46:06 +0300 Subject: [PATCH 1147/1216] Delay requests to show notifications --- Cocoa/Document.m | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ff77f8ef..035c0e2d 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -359,19 +359,21 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; - /* Clear pending alarms, don't play alarms while playing*/ - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - for (NSUserNotification *notification in [center scheduledNotifications]) { - if ([notification.identifier isEqualToString:self.fileName]) { - [center removeScheduledNotification:notification]; - break; + /* Clear pending alarms, don't play alarms while playing */ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + for (NSUserNotification *notification in [center scheduledNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeScheduledNotification:notification]; + break; + } } - } - - for (NSUserNotification *notification in [center deliveredNotifications]) { - if ([notification.identifier isEqualToString:self.fileName]) { - [center removeDeliveredNotification:notification]; - break; + + for (NSUserNotification *notification in [center deliveredNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeDeliveredNotification:notification]; + break; + } } } @@ -412,7 +414,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) notification.identifier = self.fileName; notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm]; notification.soundName = NSUserNotificationDefaultSoundName; - [center scheduleNotification:notification]; + [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"]; } [_view setRumble:0]; stopping = false; From f105f2801791f9cafe309adaa407e3c22b2ac3c7 Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Sat, 30 May 2020 15:54:51 -0400 Subject: [PATCH 1148/1216] Add ld b,b breakpoint Signed-off-by: James Larrowe --- Core/sm83_cpu.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 13f05df7..9dbc90fe 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1,6 +1,7 @@ #include #include #include +#include "debugger.h" #include "gb.h" @@ -789,6 +790,11 @@ LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) +// simply fire the debugger +static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_debugger_break(gb); +} static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { @@ -1462,7 +1468,7 @@ static GB_opcode_t *opcodes[256] = { 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, - nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_b_b, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, From abdece7737c272dc3f1e771fd18256b4d6da8bb9 Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Sat, 30 May 2020 16:35:07 -0400 Subject: [PATCH 1149/1216] add debugger command to enable and disable --- Core/debugger.c | 18 ++++++++++++++++++ Core/gb.h | 2 +- Core/sm83_cpu.c | 6 ++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index a46de861..ed498c95 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -832,6 +832,23 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } +/* Enable or disable software breakpoints */ +static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strcmp(lstrip(arguments), "on") == 0) { + gb->has_software_breakpoints = true; + } + else if(strcmp(lstrip(arguments), "off") == 0) { + gb->has_software_breakpoints = false; + } + else { + print_usage(gb, command); + } + + return true; +} + /* Find the index of the closest breakpoint equal or greater to addr */ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) { @@ -1780,6 +1797,7 @@ static const debugger_command_t commands[] = { "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, + {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE "Can also modify the condition of existing breakpoints." HELP_NEWLINE "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE diff --git a/Core/gb.h b/Core/gb.h index 97a8069b..1445a682 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -601,7 +601,7 @@ struct GB_gameboy_internal_s { /* Breakpoints */ uint16_t n_breakpoints; struct GB_breakpoint_s *breakpoints; - bool has_jump_to_breakpoints; + bool has_jump_to_breakpoints, has_software_breakpoints; void *nontrivial_jump_state; bool non_trivial_jump_breakpoint_occured; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 9dbc90fe..b337b362 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -790,10 +790,12 @@ LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) -// simply fire the debugger +// fire the debugger if software breakpoints are enabled static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) { - GB_debugger_break(gb); + if(gb->has_software_breakpoints) { + GB_debugger_break(gb); + } } static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) From 6fcf77c7f6e83b3f6e0a207f6a80ea2a2ba3e4af Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Sat, 30 May 2020 16:46:17 -0400 Subject: [PATCH 1150/1216] Make no argument for softbreak be equivalent to "on" --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index ed498c95..09f78c81 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -836,7 +836,7 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS - if (strcmp(lstrip(arguments), "on") == 0) { + if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) { gb->has_software_breakpoints = true; } else if(strcmp(lstrip(arguments), "off") == 0) { From fd97e1191914df4d1ea78024e1f7255dfe7f4fb9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 00:54:13 +0300 Subject: [PATCH 1151/1216] Spacing --- Core/sm83_cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index b337b362..f6a69fb3 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -793,7 +793,7 @@ LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL // fire the debugger if software breakpoints are enabled static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) { - if(gb->has_software_breakpoints) { + if (gb->has_software_breakpoints) { GB_debugger_break(gb); } } From f1ea39f1c660ef2e377473a3e3264bed8ed05332 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 00:54:49 +0300 Subject: [PATCH 1152/1216] Spacing --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 09f78c81..87b09142 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -839,7 +839,7 @@ static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) { gb->has_software_breakpoints = true; } - else if(strcmp(lstrip(arguments), "off") == 0) { + else if (strcmp(lstrip(arguments), "off") == 0) { gb->has_software_breakpoints = false; } else { From 97e844a0b743529d17309026e43072fc290e32e5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 01:01:06 +0300 Subject: [PATCH 1153/1216] GB_debugger_break is for external APIs, not available on libretro builds --- Core/sm83_cpu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 84105ba8..3b3ecebb 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1,7 +1,6 @@ #include #include #include -#include "debugger.h" #include "gb.h" @@ -794,7 +793,7 @@ LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) { if (gb->has_software_breakpoints) { - GB_debugger_break(gb); + gb->debug_stopped = true; } } From 0c0ca8e862ebf5aa049b68d067d5db8a7a82b83b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 01:41:27 +0300 Subject: [PATCH 1154/1216] =?UTF-8?q?Last=20resort=20for=20Macs=20that=20c?= =?UTF-8?q?an=E2=80=99t=20send=20reports=20to=20certain=20devices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JoyKit/JOYController.m | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index b815ddb1..ca2d1b18 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -120,7 +120,7 @@ typedef struct __attribute__((packed)) { uint8_t dutyLength; uint8_t enabled; uint8_t dutyOff; - uint8_t dutyOnn; + uint8_t dutyOn; } __attribute__((packed)) led[5]; uint8_t padding3[13]; } JOYDualShock3Output; @@ -165,6 +165,7 @@ typedef union { uint8_t _playerLEDs; double _sentRumbleAmp; unsigned _rumbleCounter; + bool _deviceCantSendReports; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -482,11 +483,11 @@ typedef union { _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){ .reportID = 1, .led = { - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOnn = 0}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0}, } }; @@ -706,7 +707,13 @@ typedef union { { if (!report.length) return; if (!_device) return; - IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length); + if (_deviceCantSendReports) return; + /* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after + freezing for 5 seconds. Stop sending reports if that's the case. */ + if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) { + _deviceCantSendReports = true; + NSLog(@"This Mac appears to be incapable of sending output reports to %@", self); + } } - (void)setPlayerLEDs:(uint8_t)mask From 08efb46d41bc06c193cba85edfe00522bb81626d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 20:32:00 +0300 Subject: [PATCH 1155/1216] =?UTF-8?q?Made=20the=20command=20line=20debugge?= =?UTF-8?q?r=20output=20=E2=80=9C>=E2=80=9D=20before=20inputs,=20added=20s?= =?UTF-8?q?pecial=20magic=20sequence=20to=20break=20the=20debugger=20from?= =?UTF-8?q?=20stdin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/gb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index ce9b9af9..1bc2235a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -62,6 +62,9 @@ static char *default_input_callback(GB_gameboy_t *gb) { char *expression = NULL; size_t size = 0; + if (gb->debug_stopped) { + printf(">"); + } if (getline(&expression, &size, stdin) == -1) { /* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */ @@ -77,6 +80,12 @@ static char *default_input_callback(GB_gameboy_t *gb) if (expression[length - 1] == '\n') { expression[length - 1] = 0; } + + if (expression[0] == '\x03') { + gb->debug_stopped = true; + free(expression); + return strdup(""); + } return expression; } From 9521729e4e7ae90133d8cdc23b5e82fe837f865d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 21:54:54 +0300 Subject: [PATCH 1156/1216] Fixed Windows build --- Makefile | 4 ++-- Windows/unistd.h | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Windows/unistd.h diff --git a/Makefile b/Makefile index 78978e49..8f96bb61 100644 --- a/Makefile +++ b/Makefile @@ -387,10 +387,10 @@ $(OBJ)/%.2bpp: %.png rgbgfx -h -u -o $@ $< $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS) - $(PB12_COMPRESS) < $< > $@ + $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(CC) -Wall -Werror $< -o $@ + $(CC) $(LDFLAGS) $(CFLAGS) -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm diff --git a/Windows/unistd.h b/Windows/unistd.h new file mode 100644 index 00000000..b7aabf29 --- /dev/null +++ b/Windows/unistd.h @@ -0,0 +1,7 @@ +#include +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define read(...) _read(__VA_ARGS__) +#define write(...) _write(__VA_ARGS__) From 9e8b4345c03aac473dfc1471b3e22fc540ed961d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 21:55:04 +0300 Subject: [PATCH 1157/1216] Update version to 0.13 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8f96bb61..1c6b01ae 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.12.3 +VERSION := 0.13 export VERSION CONF ?= debug SDL_AUDIO_DRIVER ?= sdl From 6a3cd371d01e5d253b0ab999da53d87ac84ab7cc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Jun 2020 20:54:06 +0300 Subject: [PATCH 1158/1216] Fix potential memory corruption when execution malformed ROMs --- Core/mbc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/mbc.c b/Core/mbc.c index 72073f60..ba5055fd 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -135,7 +135,10 @@ void GB_configure_cart(GB_gameboy_t *gb) static const unsigned 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); + + if (gb->mbc_ram_size) { + gb->mbc_ram = malloc(gb->mbc_ram_size); + } /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */ memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); From b7a9039e50426d80f136403c95e85f23aad11ea3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Jun 2020 21:06:47 +0300 Subject: [PATCH 1159/1216] Sanitize SDL preferences for cross-version stability --- SDL/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SDL/main.c b/SDL/main.c index f75e3aac..3df369f8 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -671,7 +671,19 @@ int main(int argc, char **argv) if (prefs_file) { fread(&configuration, 1, sizeof(configuration), prefs_file); fclose(prefs_file); + + /* Sanitize for stability */ + configuration.color_correction_mode %= GB_COLOR_CORRECTION_REDUCE_CONTRAST +1; + configuration.scaling_mode %= GB_SDL_SCALING_MAX; + configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1; + configuration.highpass_mode %= GB_HIGHPASS_MAX; + configuration.model %= MODEL_MAX; + configuration.sgb_revision %= SGB_MAX; + configuration.dmg_palette %= 3; + configuration.border_mode %= GB_BORDER_ALWAYS + 1; + configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1; } + if (configuration.model >= MODEL_MAX) { configuration.model = MODEL_CGB; } From ef203cf0e5b6dd1e9ee5da47b0827b0ef132fa02 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Jun 2020 21:18:09 +0300 Subject: [PATCH 1160/1216] Update version to 0.13.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1c6b01ae..4b9c2915 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.13 +VERSION := 0.13.1 export VERSION CONF ?= debug SDL_AUDIO_DRIVER ?= sdl From c07588e3bd7db8f2bfe9e483046ba6856d23fd1e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Jun 2020 02:10:05 +0300 Subject: [PATCH 1161/1216] Console auto complete --- Cocoa/Document.m | 5 + Cocoa/GBTerminalTextFieldCell.h | 3 +- Cocoa/GBTerminalTextFieldCell.m | 40 ++++++- Core/debugger.c | 194 ++++++++++++++++++++++++++++++-- Core/debugger.h | 2 +- 5 files changed, 231 insertions(+), 13 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 035c0e2d..095cfe3d 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -8,6 +8,7 @@ #include "GBMemoryByteArray.h" #include "GBWarningPopover.h" #include "GBCheatWindowController.h" +#include "GBTerminalTextFieldCell.h" /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: Split into category files! This is so messy!!! */ @@ -546,6 +547,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) self.debuggerSideViewInput.textColor = [NSColor whiteColor]; self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style; [self.debuggerSideViewInput setString:@"registers\nbacktrace\n"]; + ((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSideView) name:NSTextDidChangeNotification @@ -1008,6 +1010,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [debugger_input_queue removeObjectAtIndex:0]; } [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; + if ((id)input == [NSNull null]) { + return NULL; + } return input? strdup([input UTF8String]): NULL; } diff --git a/Cocoa/GBTerminalTextFieldCell.h b/Cocoa/GBTerminalTextFieldCell.h index eae02e5c..484e0c35 100644 --- a/Cocoa/GBTerminalTextFieldCell.h +++ b/Cocoa/GBTerminalTextFieldCell.h @@ -1,5 +1,6 @@ #import +#include @interface GBTerminalTextFieldCell : NSTextFieldCell - +@property GB_gameboy_t *gb; @end diff --git a/Cocoa/GBTerminalTextFieldCell.m b/Cocoa/GBTerminalTextFieldCell.m index e95e7854..c1ed2036 100644 --- a/Cocoa/GBTerminalTextFieldCell.m +++ b/Cocoa/GBTerminalTextFieldCell.m @@ -2,6 +2,7 @@ #import "GBTerminalTextFieldCell.h" @interface GBTerminalTextView : NSTextView +@property GB_gameboy_t *gb; @end @implementation GBTerminalTextFieldCell @@ -12,10 +13,12 @@ - (NSTextView *)fieldEditorForView:(NSView *)controlView { if (field_editor) { + field_editor.gb = self.gb; return field_editor; } field_editor = [[GBTerminalTextView alloc] init]; [field_editor setFieldEditor:YES]; + field_editor.gb = self.gb; return field_editor; } @@ -26,6 +29,8 @@ NSMutableOrderedSet *lines; NSUInteger current_line; bool reverse_search_mode; + NSRange auto_complete_range; + uintptr_t auto_complete_context; } - (instancetype)init @@ -170,6 +175,7 @@ -(void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag { reverse_search_mode = false; + auto_complete_context = 0; [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag]; } @@ -188,6 +194,38 @@ [attributes setObject:color forKey:NSForegroundColorAttributeName]; [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; } - } + +/* Todo: lazy design, make it use a delegate instead of having a gb reference*/ + +- (void)insertTab:(id)sender +{ + if (auto_complete_context == 0) { + NSRange selection = self.selectedRange; + if (selection.length) { + [self delete:nil]; + } + auto_complete_range = NSMakeRange(selection.location, 0); + } + char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String); + uintptr_t context = auto_complete_context; + char *completion = GB_debugger_complete_substring(self.gb, substring, &context); + free(substring); + if (completion) { + NSString *ns_completion = @(completion); + free(completion); + if (!ns_completion) { + goto error; + } + self.selectedRange = auto_complete_range; + auto_complete_range.length = ns_completion.length; + [self replaceCharactersInRange:self.selectedRange withString:ns_completion]; + auto_complete_context = context; + return; + } +error: + auto_complete_context = context; + NSBeep(); +} + @end diff --git a/Core/debugger.c b/Core/debugger.c index 34144df5..038f76f3 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -689,6 +689,7 @@ exit: struct debugger_command_s; typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); +typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context); typedef struct debugger_command_s { const char *command; @@ -697,6 +698,8 @@ typedef struct debugger_command_s { const char *help_string; // Null if should not appear in help const char *arguments_format; // For usage message const char *modifiers_format; // For usage message + debugger_completer_imp_t *argument_completer; + debugger_completer_imp_t *modifiers_completer; } debugger_command_t; static const char *lstrip(const char *str) @@ -832,6 +835,19 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } +static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"on", "off"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + /* Enable or disable software breakpoints */ static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { @@ -873,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } +static inline bool is_legal_symbol_char(char c) +{ + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'Z') return true; + if (c >= 'a' && c <= 'z') return true; + if (c == '_') return true; + if (c == '.') return true; + return false; +} + +static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context) +{ + const char *symbol_prefix = string; + while (*string) { + if (!is_legal_symbol_char(*string)) { + symbol_prefix = string + 1; + } + string++; + } + + if (*symbol_prefix == '$') { + return NULL; + } + + struct { + uint16_t bank; + uint32_t symbol; + } *context = (void *)_context; + + + size_t length = strlen(symbol_prefix); + while (context->bank < 0x200) { + if (gb->bank_symbols[context->bank] == NULL || + context->symbol >= gb->bank_symbols[context->bank]->n_symbols) { + context->bank++; + context->symbol = 0; + continue; + } + const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name; + if (memcmp(symbol_prefix, candidate, length) == 0) { + return strdup(candidate + length); + } + } + return NULL; +} + +static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"j"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { bool is_jump_to = true; @@ -1040,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } +static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"r", "rw", "w"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { @@ -1277,6 +1365,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) return _should_break(gb, full_addr, jump_to); } +static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"a", "b", "d", "o", "x"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { @@ -1740,6 +1841,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg return true; } +static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"c", "f", "l"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) { @@ -1787,7 +1901,7 @@ static const debugger_command_t commands[] = { {"finish", 1, finish, "Run until the current function returns"}, {"backtrace", 2, backtrace, "Displays the current call stack"}, {"bt", 2, }, /* Alias */ - {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, + {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"}, {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE "used"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, @@ -1796,30 +1910,33 @@ static const debugger_command_t commands[] = { {"apu", 3, apu, "Displays information about the current state of the audio chip"}, {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE - "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"}, + "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, - {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"}, + {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE "Can also modify the condition of existing breakpoints." HELP_NEWLINE "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE "jumping to the target.", - "[ if ]", "j"}, - {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, + "[ if ]", "j", + .argument_completer = symbol_completer, .modifiers_completer = j_completer}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]", .argument_completer = symbol_completer}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE "Default watchpoint type is write-only.", - "[ if ]", "(r|w|rw)"}, - {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"}, + "[ if ]", "(r|w|rw)", + .argument_completer = symbol_completer, .modifiers_completer = rw_completer + }, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]", .argument_completer = symbol_completer}, {"list", 1, list, "List all set breakpoints and watchpoints"}, {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE "decimal (d), hexadecimal (x), octal (o) or binary (b).", - "", "format"}, + "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, {"eval", 2, }, /* Alias */ - {"examine", 2, examine, "Examine values at address", "", "count"}, + {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, {"x", 1, }, /* Alias */ - {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count"}, + {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, {"help", 1, help, "List available commands or show help for the specified command", "[]"}, @@ -2075,6 +2192,63 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) } } +/* Returns true if debugger waits for more commands */ +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context) +{ + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + + const debugger_command_t *command = find_command(command_string); + if (command && command->implementation == help && arguments) { + command_string = arguments; + arguments = NULL; + } + + /* No commands and no modifiers, complete the command */ + if (!arguments && !modifiers) { + size_t length = strlen(command_string); + if (*context >= sizeof(commands) / sizeof(commands[0])) { + return NULL; + } + for (const debugger_command_t *command = &commands[*context]; command->command; command++) { + (*context)++; + if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */ + return strdup(command->command + length); + } + } + return NULL; + } + + if (command) { + if (arguments) { + if (command->argument_completer) { + return command->argument_completer(gb, arguments, context); + } + return NULL; + } + + if (modifiers) { + if (command->modifiers_completer) { + return command->modifiers_completer(gb, modifiers, context); + } + return NULL; + } + } + return NULL; +} + typedef enum { JUMP_TO_NONE, JUMP_TO_BREAK, diff --git a/Core/debugger.h b/Core/debugger.h index b6a12d97..0678b30c 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G void #endif GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */ - +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); From 4a51f5c95698ad3df8bae22acd01e4e60a74de80 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 9 Jun 2020 20:09:50 +0300 Subject: [PATCH 1162/1216] Cherry-picking libretro memory map bugfix (Closes #227, #205). Fixing libretro build with modern macOS SDKs. --- Makefile | 2 +- libretro/libretro.c | 81 ++++++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 4b9c2915..587f2f8d 100644 --- a/Makefile +++ b/Makefile @@ -390,7 +390,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(CC) $(LDFLAGS) $(CFLAGS) -Wall -Werror $< -o $@ + cc -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm diff --git a/libretro/libretro.c b/libretro/libretro.c index a7d64328..24514d4f 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -389,47 +389,12 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) } } -static void init_for_current_model(unsigned id) +static void retro_set_memory_maps(void) { - unsigned i = id; - enum model effective_model; - - effective_model = model[i]; - if (effective_model == MODEL_AUTO) { - effective_model = auto_model; - } - - - if (GB_is_inited(&gameboy[i])) { - GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]); - } - else { - GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); - } - - GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load); - - /* When running multiple devices they are assumed to use the same resolution */ - - GB_set_pixels_output(&gameboy[i], - (uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i)); - GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); - GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); - GB_apu_set_sample_callback(&gameboy[i], audio_callback); - GB_set_rumble_callback(&gameboy[i], rumble_callback); - - /* todo: attempt to make these more generic */ - GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); - if (emulated_devices == 2) { - GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); - if (link_cable_emulation) { - set_link_cable_state(true); - } - } - struct retro_memory_descriptor descs[11]; size_t size; uint16_t bank; + unsigned i; /* todo: add netplay awareness for this so achievements can be granted on the respective client */ @@ -489,6 +454,45 @@ static void init_for_current_model(unsigned id) mmaps.descriptors = descs; mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); +} + +static void init_for_current_model(unsigned id) +{ + unsigned i = id; + enum model effective_model; + + effective_model = model[i]; + if (effective_model == MODEL_AUTO) { + effective_model = auto_model; + } + + + if (GB_is_inited(&gameboy[i])) { + GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]); + } + else { + GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); + } + + GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load); + + /* When running multiple devices they are assumed to use the same resolution */ + + GB_set_pixels_output(&gameboy[i], + (uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i)); + GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); + GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); + GB_apu_set_sample_callback(&gameboy[i], audio_callback); + GB_set_rumble_callback(&gameboy[i], rumble_callback); + + /* todo: attempt to make these more generic */ + GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); + if (emulated_devices == 2) { + GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); + if (link_cable_emulation) { + set_link_cable_state(true); + } + } /* Let's be extremely nitpicky about how devices and descriptors are set */ if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { @@ -1070,6 +1074,9 @@ bool retro_load_game(const struct retro_game_info *info) } check_variables(); + + retro_set_memory_maps(); + return true; } From edf77624087f09decd3656adf71bc0400a17af06 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 10 Jun 2020 01:10:11 +0300 Subject: [PATCH 1163/1216] Improved Dark Mode support, improved Hex Fiend's general system-native appearance --- Cocoa/Document.m | 1 - Cocoa/Document.xib | 22 +++++------ Cocoa/GBColorCell.m | 8 +++- Cocoa/GBPreferencesWindow.m | 20 ++++++++++ Cocoa/Joypad~dark.png | Bin 0 -> 6244 bytes Cocoa/Joypad~dark@2x.png | Bin 0 -> 7175 bytes Cocoa/NSImageNamedDarkSupport.m | 42 ++++++++++++++++++++ Cocoa/Preferences.xib | 10 ++--- Cocoa/Speaker~dark.png | Bin 0 -> 4562 bytes Cocoa/Speaker~dark@2x.png | Bin 0 -> 5992 bytes HexFiend/HFFunctions.h | 2 +- HexFiend/HFLineCountingRepresenter.m | 10 ++--- HexFiend/HFLineCountingView.m | 56 ++++++++++----------------- HexFiend/HFRepresenterTextView.m | 18 ++++++--- HexFiend/HFStatusBarRepresenter.m | 44 +++++---------------- HexFiend/HFTextRepresenter.m | 12 ++++-- 16 files changed, 141 insertions(+), 104 deletions(-) create mode 100644 Cocoa/Joypad~dark.png create mode 100644 Cocoa/Joypad~dark@2x.png create mode 100644 Cocoa/NSImageNamedDarkSupport.m create mode 100644 Cocoa/Speaker~dark.png create mode 100644 Cocoa/Speaker~dark@2x.png diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 095cfe3d..fdb7d97a 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -647,7 +647,6 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { hex_controller = [[HFController alloc] init]; [hex_controller setBytesPerColumn:1]; - [hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]]; [hex_controller setEditMode:HFOverwriteMode]; [hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]]; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 81ce0188..e2b0ca63 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -116,7 +116,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -187,7 +187,7 @@ - + @@ -339,7 +339,7 @@ - + @@ -505,9 +505,9 @@ - + - + @@ -640,7 +640,7 @@ - + @@ -649,7 +649,7 @@ - + @@ -800,7 +800,7 @@ @@ -970,7 +977,7 @@ - + @@ -1069,6 +1076,7 @@ + From 4f42f4f718ec17c38c729b1fa9d9906effa88e36 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 11 Jun 2020 00:38:53 +0300 Subject: [PATCH 1167/1216] Minor layout fixes --- Cocoa/Document.xib | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index c13c9ddb..1197c0f1 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -243,9 +243,9 @@ - + - + @@ -903,7 +903,7 @@ diff --git a/Core/gb.c b/Core/gb.c index 1bc2235a..7325d79a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1131,8 +1131,9 @@ void GB_disconnect_serial(GB_gameboy_t *gb) gb->serial_transfer_bit_start_callback = NULL; gb->serial_transfer_bit_end_callback = NULL; - /* Reset any internally-emulated device. Currently, only the printer. */ + /* Reset any internally-emulated device. */ memset(&gb->printer, 0, sizeof(gb->printer)); + memset(&gb->workboy, 0, sizeof(gb->workboy)); } bool GB_is_inited(GB_gameboy_t *gb) diff --git a/Core/gb.h b/Core/gb.h index f085eacb..90439368 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -23,6 +23,7 @@ #include "sgb.h" #include "cheats.h" #include "rumble.h" +#include "workboy.h" #define GB_STRUCT_VERSION 13 @@ -372,6 +373,7 @@ struct GB_gameboy_internal_s { GB_printer_t printer; uint8_t extra_oam[0xff00 - 0xfea0]; uint32_t ram_size; // Different between CGB and DMG + GB_workboy_t workboy; ); /* DMA and HDMA */ @@ -608,6 +610,9 @@ struct GB_gameboy_internal_s { GB_read_memory_callback_t read_memory_callback; GB_boot_rom_load_callback_t boot_rom_load_callback; GB_print_image_callback_t printer_callback; + GB_workboy_set_time_callback workboy_set_time_callback; + GB_workboy_get_time_callback workboy_get_time_callback; + /* IR */ uint64_t cycles_since_ir_change; // In 8MHz units uint64_t cycles_since_input_ir_change; // In 8MHz units diff --git a/Core/printer.c b/Core/printer.c index 7b47acec..f04e54dd 100644 --- a/Core/printer.c +++ b/Core/printer.c @@ -189,13 +189,13 @@ static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received) static void serial_start(GB_gameboy_t *gb, bool bit_received) { - gb->printer.byte_being_recieved <<= 1; - gb->printer.byte_being_recieved |= bit_received; - gb->printer.bits_recieved++; - if (gb->printer.bits_recieved == 8) { - byte_reieve_completed(gb, gb->printer.byte_being_recieved); - gb->printer.bits_recieved = 0; - gb->printer.byte_being_recieved = 0; + gb->printer.byte_being_received <<= 1; + gb->printer.byte_being_received |= bit_received; + gb->printer.bits_received++; + if (gb->printer.bits_received == 8) { + byte_reieve_completed(gb, gb->printer.byte_being_received); + gb->printer.bits_received = 0; + gb->printer.byte_being_received = 0; } } diff --git a/Core/printer.h b/Core/printer.h index 71f919a0..b29650ff 100644 --- a/Core/printer.h +++ b/Core/printer.h @@ -54,8 +54,8 @@ typedef struct uint8_t compression_run_lenth; bool compression_run_is_compressed; - uint8_t bits_recieved; - uint8_t byte_being_recieved; + uint8_t bits_received; + uint8_t byte_being_received; bool bit_to_send; } GB_printer_t; diff --git a/Core/workboy.c b/Core/workboy.c new file mode 100644 index 00000000..3b103796 --- /dev/null +++ b/Core/workboy.c @@ -0,0 +1,169 @@ +#include "gb.h" +#include + +static inline uint8_t int_to_bcd(uint8_t i) +{ + return (i % 10) + ((i / 10) << 4); +} + +static inline uint8_t bcd_to_int(uint8_t i) +{ + return (i & 0xF) + (i >> 4) * 10; +} + +/* + Note: This peripheral was never released. This is a hacky software reimplementation of it that allows + reaccessing all of the features present in Workboy's ROM. Some of the implementation details are + obviously wrong, but without access to the actual hardware, this is the best I can do. +*/ + +static void serial_start(GB_gameboy_t *gb, bool bit_received) +{ + gb->workboy.byte_being_received <<= 1; + gb->workboy.byte_being_received |= bit_received; + gb->workboy.bits_received++; + if (gb->workboy.bits_received == 8) { + gb->workboy.byte_to_send = 0; + if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'R') { + gb->workboy.byte_to_send = 'D'; + gb->workboy.key = GB_WORKBOY_NONE; + gb->workboy.mode = gb->workboy.byte_being_received; + gb->workboy.buffer_index = 1; + + time_t time = gb->workboy_get_time_callback(gb); + struct tm tm; + tm = *localtime(&time); + memset(gb->workboy.buffer, 0, sizeof(gb->workboy.buffer)); + + gb->workboy.buffer[0] = 4; // Unknown, unused, but appears to be expected to be 4 + gb->workboy.buffer[2] = int_to_bcd(tm.tm_sec); // Seconds, BCD + gb->workboy.buffer[3] = int_to_bcd(tm.tm_min); // Minutes, BCD + gb->workboy.buffer[4] = int_to_bcd(tm.tm_hour); // Hours, BCD + gb->workboy.buffer[5] = int_to_bcd(tm.tm_mday); // Days, BCD. Upper most 2 bits are added to Year for some reason + gb->workboy.buffer[6] = int_to_bcd(tm.tm_mon + 1); // Months, BCD + gb->workboy.buffer[0xF] = tm.tm_year; // Years, plain number, since 1900 + + } + else if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'W') { + gb->workboy.byte_to_send = 'D'; // It is actually unknown what this value should be + gb->workboy.key = GB_WORKBOY_NONE; + gb->workboy.mode = gb->workboy.byte_being_received; + gb->workboy.buffer_index = 0; + } + else if (gb->workboy.mode != 'W' && (gb->workboy.byte_being_received == 'O' || gb->workboy.mode == 'O')) { + gb->workboy.mode = 'O'; + gb->workboy.byte_to_send = gb->workboy.key; + if (gb->workboy.key != GB_WORKBOY_NONE) { + if (gb->workboy.key & GB_WORKBOY_REQUIRE_SHIFT) { + gb->workboy.key &= ~GB_WORKBOY_REQUIRE_SHIFT; + if (gb->workboy.shift_down) { + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + else { + gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_DOWN; + gb->workboy.shift_down = true; + } + } + else if (gb->workboy.key & GB_WORKBOY_FORBID_SHIFT) { + gb->workboy.key &= ~GB_WORKBOY_FORBID_SHIFT; + if (!gb->workboy.shift_down) { + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + else { + gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_UP; + gb->workboy.shift_down = false; + } + } + else { + if (gb->workboy.key == GB_WORKBOY_SHIFT_DOWN) { + gb->workboy.shift_down = true; + gb->workboy.user_shift_down = true; + } + else if (gb->workboy.key == GB_WORKBOY_SHIFT_UP) { + gb->workboy.shift_down = false; + gb->workboy.user_shift_down = false; + } + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + } + } + else if (gb->workboy.mode == 'R') { + if (gb->workboy.buffer_index / 2 >= sizeof(gb->workboy.buffer)) { + gb->workboy.byte_to_send = 0; + } + else { + if (gb->workboy.buffer_index & 1) { + gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] & 0xF]; + } + else { + gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] >> 4]; + } + gb->workboy.buffer_index++; + } + } + else if (gb->workboy.mode == 'W') { + gb->workboy.byte_to_send = 'D'; + if (gb->workboy.buffer_index < 2) { + gb->workboy.buffer_index++; + } + else if ((gb->workboy.buffer_index - 2) < sizeof(gb->workboy.buffer)) { + gb->workboy.buffer[gb->workboy.buffer_index - 2] = gb->workboy.byte_being_received; + gb->workboy.buffer_index++; + if (gb->workboy.buffer_index - 2 == sizeof(gb->workboy.buffer)) { + struct tm tm = {0,}; + tm.tm_sec = bcd_to_int(gb->workboy.buffer[7]); + tm.tm_min = bcd_to_int(gb->workboy.buffer[8]); + tm.tm_hour = bcd_to_int(gb->workboy.buffer[9]); + tm.tm_mday = bcd_to_int(gb->workboy.buffer[0xA]); + tm.tm_mon = bcd_to_int(gb->workboy.buffer[0xB] & 0x3F) - 1; + tm.tm_year = (uint8_t)(gb->workboy.buffer[0x14] + (gb->workboy.buffer[0xA] >> 6)); // What were they thinking? + gb->workboy_set_time_callback(gb, mktime(&tm)); + gb->workboy.mode = 'O'; + } + } + } + gb->workboy.bits_received = 0; + gb->workboy.byte_being_received = 0; + } +} + +static bool serial_end(GB_gameboy_t *gb) +{ + bool ret = gb->workboy.bit_to_send; + gb->workboy.bit_to_send = gb->workboy.byte_to_send & 0x80; + gb->workboy.byte_to_send <<= 1; + return ret; +} + +void GB_connect_workboy(GB_gameboy_t *gb, + GB_workboy_set_time_callback set_time_callback, + GB_workboy_get_time_callback get_time_callback) +{ + memset(&gb->workboy, 0, sizeof(gb->workboy)); + GB_set_serial_transfer_bit_start_callback(gb, serial_start); + GB_set_serial_transfer_bit_end_callback(gb, serial_end); + gb->workboy_set_time_callback = set_time_callback; + gb->workboy_get_time_callback = get_time_callback; +} + +bool GB_workboy_is_enabled(GB_gameboy_t *gb) +{ + return gb->workboy.mode; +} + +void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key) +{ + if (gb->workboy.user_shift_down != gb->workboy.shift_down && + (key & (GB_WORKBOY_REQUIRE_SHIFT | GB_WORKBOY_FORBID_SHIFT)) == 0) { + if (gb->workboy.user_shift_down) { + key |= GB_WORKBOY_REQUIRE_SHIFT; + } + else { + key |= GB_WORKBOY_FORBID_SHIFT; + } + } + gb->workboy.key = key; +} diff --git a/Core/workboy.h b/Core/workboy.h new file mode 100644 index 00000000..d21f2731 --- /dev/null +++ b/Core/workboy.h @@ -0,0 +1,118 @@ +#ifndef workboy_h +#define workboy_h +#include +#include +#include +#include "gb_struct_def.h" + + +typedef struct { + uint8_t byte_to_send; + bool bit_to_send; + uint8_t byte_being_received; + uint8_t bits_received; + uint8_t mode; + uint8_t key; + bool shift_down; + bool user_shift_down; + uint8_t buffer[0x15]; + uint8_t buffer_index; // In nibbles during read, in bytes during write +} GB_workboy_t; + +typedef void (*GB_workboy_set_time_callback)(GB_gameboy_t *gb, time_t time); +typedef time_t (*GB_workboy_get_time_callback)(GB_gameboy_t *gb); + +enum { + GB_WORKBOY_NONE = 0xFF, + GB_WORKBOY_REQUIRE_SHIFT = 0x40, + GB_WORKBOY_FORBID_SHIFT = 0x80, + + GB_WORKBOY_CLOCK = 1, + GB_WORKBOY_TEMPERATURE = 2, + GB_WORKBOY_MONEY = 3, + GB_WORKBOY_CALCULATOR = 4, + GB_WORKBOY_DATE = 5, + GB_WORKBOY_CONVERSION = 6, + GB_WORKBOY_RECORD = 7, + GB_WORKBOY_WORLD = 8, + GB_WORKBOY_PHONE = 9, + GB_WORKBOY_ESCAPE = 10, + GB_WORKBOY_BACKSPACE = 11, + GB_WORKBOY_UNKNOWN = 12, + GB_WORKBOY_LEFT = 13, + GB_WORKBOY_Q = 17, + GB_WORKBOY_1 = 17 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_W = 18, + GB_WORKBOY_2 = 18 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_E = 19, + GB_WORKBOY_3 = 19 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_R = 20, + GB_WORKBOY_T = 21, + GB_WORKBOY_Y = 22 , + GB_WORKBOY_U = 23 , + GB_WORKBOY_I = 24, + GB_WORKBOY_EXCLAMATION_MARK = 24 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_O = 25, + GB_WORKBOY_TILDE = 25 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_P = 26, + GB_WORKBOY_ASTERISK = 26 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_DOLLAR = 27 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_HASH = 27 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_A = 28, + GB_WORKBOY_4 = 28 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_S = 29, + GB_WORKBOY_5 = 29 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_D = 30, + GB_WORKBOY_6 = 30 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_F = 31, + GB_WORKBOY_PLUS = 31 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_G = 32, + GB_WORKBOY_MINUS = 32 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_H = 33, + GB_WORKBOY_J = 34, + GB_WORKBOY_K = 35, + GB_WORKBOY_LEFT_PARENTHESIS = 35 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_L = 36, + GB_WORKBOY_RIGHT_PARENTHESIS = 36 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SEMICOLON = 37 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_COLON = 37, + GB_WORKBOY_ENTER = 38, + GB_WORKBOY_SHIFT_DOWN = 39, + GB_WORKBOY_Z = 40, + GB_WORKBOY_7 = 40 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_X = 41, + GB_WORKBOY_8 = 41 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_C = 42, + GB_WORKBOY_9 = 42 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_V = 43, + GB_WORKBOY_DECIMAL_POINT = 43 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_B = 44, + GB_WORKBOY_PERCENT = 44 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_N = 45, + GB_WORKBOY_EQUAL = 45 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_M = 46, + GB_WORKBOY_COMMA = 47 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_LT = 47 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_DOT = 48 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_GT = 48 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SLASH = 49 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_QUESTION_MARK = 49 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SHIFT_UP = 50, + GB_WORKBOY_0 = 51 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_UMLAUT = 51, + GB_WORKBOY_SPACE = 52, + GB_WORKBOY_QUOTE = 53 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_AT = 53 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_UP = 54, + GB_WORKBOY_DOWN = 55, + GB_WORKBOY_RIGHT = 56, +}; + + +void GB_connect_workboy(GB_gameboy_t *gb, + GB_workboy_set_time_callback set_time_callback, + GB_workboy_get_time_callback get_time_callback); +bool GB_workboy_is_enabled(GB_gameboy_t *gb); +void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key); + +#endif From 1e9e961e9ce793963c94583a48bc66401e5a7434 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 29 Sep 2020 20:43:47 +0300 Subject: [PATCH 1199/1216] Create CONTRIBUTING.md --- CONTRIBUTING.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..94627d1a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +# SameBoy Coding and Contribution Guidelines + +## Issues + +GitHub Issues are the most effective way to report a bug or request a feature in SameBoy. When reporting a bug, make sure you use the latest stable release, and make sure you mention the SameBoy frontend (Cocoa, SDL, Libretro) and operating system you're using. If you're using Linux/BSD/etc, or you build your own copy of SameBoy for another reason, give as much details as possible on your environment. + +If your bug involves a crash, please attach a crash log or a core dump. If you're using Linux/BSD/etc, or if you're using the Libretro core, please attach the `sameboy` binary (or `libretro_sameboy` library) in that case. + +If your bug is a regression, it'd be extremely helpful if you can report the the first affected version. You get extra credits if you use `git bisect` to point the exact breaking commit. + +If your bug is an emulation bug (Such as a failing test ROM), and you have access to a Game Boy you can test on, please confirm SameBoy is indeed behaving differently from hardware, and report both the emulated model and revision in SameBoy, and the hardware revision you're testing on. + +If your issue is a feature request, demonstrating use cases can help me better prioritize it. + +## Pull Requests + +To allow quicker integration into SameBoy's master branch, contributors are asked to follow SameBoy's style and coding guidelines. Keep in mind that despite the seemingly strict guidelines, all pull requests are welcome – not following the guidelines does not mean your pull request will not be accepted, but it will require manual tweaks from my side for integrating. + +### Languages and Compilers + +SameBoy's core, SDL frontend, Libretro frontend, and automatic tester (Folders `Core`, `SDL` & `OpenDialog`, `libretro`, and `Tester`; respectively) are all written in C11. The Cocoa frontend, SameBoy's fork of Hex Fiend, JoyKit and the Quick Look previewer (Folders `Cocoa`, `HexFiend`, `JoyKit` and `QuickLook`; respectively) are all written in ARC-enabled Objective-C. The SameBoot ROMs (Under `BootROMs`) are written in rgbds-flavor SM83 assembly, with build tools in C11. The shaders (inside `Shaders`) are written in a polyglot GLSL and Metal style, with a few GLSL- and Metal-specific sources. The build system uses standalone Make, in the GNU flavor. Avoid adding new languages (C++, Swift, Python, CMake...) to any of the existing sub-projects. + +SameBoy's main target compiler is Clang, but GCC is also supported when targeting Linux and Libretro. Other compilers (e.g. MSVC) are not supported, and unless there's a good reason, there's no need to go out of your way to add specific support for them. Extensions that are supported by both compilers (Such as `typeof`) may be used if it makes sense. It's OK if you can't test one of these compilers yourself; once you push a commit, the CI bot will let you know if you broke something. + +### Third Party Libraries and Tools + +Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its MIT license. + +### Spacing, Indentation and Formatting + +In all files and languages (Other than Makefiles when required), 4 spaces are used for indentation. Unix line endings (`\n`) are used exclusively, even in Windows-specific source files. (`\r` and `\t` shouldn't appear in any source file). Opening braces belong on the same line as their control flow directive, and on their own line when following a function prototype. The `else` keyword always starts on its own line. The `case` keyword is indented relative to its `switch` block, and the code inside a `case` is indented relative to its label. A control flow keyword should have a space between it and the following `(`, commas should follow a space, and operator (except `.` and `->`) should be surrounded by spaces. + +Control flow statements must use `{}`, with the exception of `if` statements that only contain a single `break`, `continue`, or trivial `return` statements. If `{}`s are omitted, the statement must be on the same line as the `if` condition. Functions that do not have any argument must be specified as `(void)`, as mandated by the C standard. The `sizeof` and `typeof` operators should be used as if they're functions (With `()`). `*`, when used to declare pointer types (including functions that return pointers), and when used to dereference a pointer, is attached to the right side (The variable name) – not to the left, and not with spaces on both sides. + +No strict limitations on a line's maximum width, but use your best judgement if you think a statement would benefit from an additional line break. + +Well formatted code example: + +``` +static void my_function(void) +{ + GB_something_t *thing = GB_function(&gb, GB_FLAG_ONE | GB_FLAG_TWO, sizeof(thing)); + if (GB_is_thing(thing)) return; + + switch (*thing) { + case GB_QUACK: + // Something + case GB_DUCK: + // Something else + } +} +``` + +Badly formatted code example: +``` +static void my_function(){ + GB_something_t* thing=GB_function(&gb , GB_FLAG_ONE|GB_FLAG_TWO , sizeof thing); + if( GB_is_thing ( thing ) ) + return; + + switch(* thing) + { + case GB_QUACK: + // Something + case GB_DUCK: + // Something else + } +} +``` + +### Other Coding Conventions + +The primitive types to be used in SameBoy are `unsigned` and `signed` (Without the `int` keyword), the `(u)int*_t` types, `char *` for UTF-8 strings, `double` for non-integer numbers, and `bool` for booleans (Including in Objective-C code, avoid `BOOL`). As long as it's not mandated by a 3rd-party API (e.g. `int` when using file descriptors), avoid using other primitive types. Use `const` whenever possible. + +Most C names should be `lower_case_snake_case`. Constants and macros use `UPPER_CASE_SNAKE_CASE`. Type definitions use a `_t` suffix. Type definitions, as well as non-static (exported) core symbols, should be prefixed with `GB_` (SameBoy's core is intended to be used as a library, so it shouldn't contaminate the global namespace without prefixes). Exported symbols that are only meant to be used by other parts of the core should still get the `GB_` prefix, but their header definition should be inside `#ifdef GB_INTERNAL`. + +For Objective-C naming conventions, use Apple's conventions (Some old Objective-C code mixes these with the C naming convention; new code should use Apple's convention exclusively). The name prefix for SameBoy classes and constants is `GB`. JoyKit's prefix is `JOY`, and Hex Fiend's prefix is `HF`. + +In all languages, prefer long, unambiguous names over short ambiguous ones. From 2a5aed626da3cfe6d8e2adac9116e82a280e93c5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 29 Sep 2020 20:50:14 +0300 Subject: [PATCH 1200/1216] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 88b8caa8..dcffabe9 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ Features currently supported only with the Cocoa version: ## Compatibility SameBoy passes all of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), all of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) tests (Some tests require the original boot ROMs), and all of [Wilbert Pol's tests](https://github.com/wilbertpol/mooneye-gb/tree/master/tests/acceptance). SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](https://sameboy.github.io/automation/). +## Contributing +SameBoy is an open-source project licensed under the MIT license, and you're welcome to contribute by creating issues, implementing new features, improving emulation accuracy and fixing existing open issues. You can read the [contribution guidelines](CONTRIBUTING.md) to make sure your contributions are as effective as possible. + ## Compilation SameBoy requires the following tools and libraries to build: * clang From 04e5f1b8cf78313e68aeac07a46f75c8db0cdd09 Mon Sep 17 00:00:00 2001 From: yo Date: Mon, 5 Oct 2020 14:33:36 -0700 Subject: [PATCH 1201/1216] Updated for Windows clang and SDL2 changes --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index be522874..78c28b6b 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ endif ifeq ($(PLATFORM),windows32) _ := $(shell chcp 65001) EXESUFFIX:=.exe -NATIVE_CC = clang -IWindows -Wno-deprecated-declarations +NATIVE_CC = clang -IWindows -Wno-deprecated-declarations --target=i386-pc-windows else EXESUFFIX:= NATIVE_CC := cc @@ -129,8 +129,8 @@ GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl) GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL) endif ifeq ($(PLATFORM),windows32) -CFLAGS += -IWindows -Drandom=rand -LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lSDL2main -Wl,/MANIFESTFILE:NUL +CFLAGS += -IWindows -Drandom=rand --target=i386-pc-windows +LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows SDL_LDFLAGS := -lSDL2 GL_LDFLAGS := -lopengl32 else @@ -404,7 +404,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ + $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ --target=i386-pc-windows $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm From 28234da2d29a35f381c3015e0a5eab38ccdf4623 Mon Sep 17 00:00:00 2001 From: yo Date: Mon, 5 Oct 2020 14:34:00 -0700 Subject: [PATCH 1202/1216] Updated instructions for Windows building --- README.md | 4 ++-- build-faq.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dcffabe9..5df1d5c9 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,14 @@ SameBoy is an open-source project licensed under the MIT license, and you're wel SameBoy requires the following tools and libraries to build: * clang * make - * Cocoa port: OS X SDK and Xcode command line tools + * Cocoa port: OS X SDK and Xcode command line tools [OSX Only] * SDL port: libsdl2 * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation On Windows, SameBoy also requires: * Visual Studio (For headers, etc.) * [GnuWin](http://gnuwin32.sourceforge.net/) - * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ] (https://github.com/LIJI32/SameBoy/blob/master/build-faq.md)) To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs. diff --git a/build-faq.md b/build-faq.md index 2b056dd2..481d1b9e 100644 --- a/build-faq.md +++ b/build-faq.md @@ -4,4 +4,58 @@ When building on macOS, the build system will make a native Cocoa app by default # Attempting to build the SDL frontend on macOS fails on linking -SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. \ No newline at end of file +SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. + +# Windows build process + +For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment: + +#### clang + +This may be installed via a Visual Studio installer packages instead of built from source. + +#### SDL Port + +[libsdl2](https://libsdl.org/download-2.0.php) has two separate files that must be downloaded + 1. The `-x86` Runtime Binary (e.g., `SDL2-2.0.12-win32-x86.zip` (as of writing)) + 2. The Visual C++ Development Library (e.g., `SDL2-devel-2.0.12-VC.zip` (as of writing)) + +For the Runtime Binary, place the extracted `SDL2.dll` into a known folder for later. + +- `C:\SDL2\bin\SDL2.dll` will be used as an example + +For the Visual C++ Development Library, place the extracted files within a known folder for later. + +The following examples will be referenced later: + +- `C:\SDL2\lib\x86\*` +- `C:\SDL2\include\*` + +#### Gnuwin + +Ensure that this is in %PATH%. + +If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. + +### Building + +Within a command prompt in the project directory: + +``` +vcvars32 +set path=%path%;C:\SDL2\bin +set lib=%lib%;C:\SDL2\lib\x86 +set include=%include%;C:\SDL2\include +make +``` +Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `path`, `lib`, and `include` paths are updated appropriately with the SDL2 downloads. + +#### Error -1073741819 + +If encountering an error that appears as follows: + +> make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819 + +Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. + + From 0b5853070aa71da85482c60a49100b309cc7a57d Mon Sep 17 00:00:00 2001 From: yo Date: Mon, 5 Oct 2020 14:37:49 -0700 Subject: [PATCH 1203/1216] Updated instructions for Windows building --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5df1d5c9..1218fc11 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ SameBoy requires the following tools and libraries to build: On Windows, SameBoy also requires: * Visual Studio (For headers, etc.) * [GnuWin](http://gnuwin32.sourceforge.net/) - * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ] (https://github.com/LIJI32/SameBoy/blob/master/build-faq.md)) + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ](https://github.com/LIJI32/SameBoy/blob/master/build-faq.md) for more details on Windows compilation) To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs. From 38afb187cf7891724624f2f5199d163761e1b3a1 Mon Sep 17 00:00:00 2001 From: yo Date: Tue, 6 Oct 2020 23:03:39 -0700 Subject: [PATCH 1204/1216] Resolving some comments and clarifying some language --- Makefile | 2 +- build-faq.md | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 78c28b6b..0881fe99 100644 --- a/Makefile +++ b/Makefile @@ -404,7 +404,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ --target=i386-pc-windows + $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm diff --git a/build-faq.md b/build-faq.md index 481d1b9e..56c59ae3 100644 --- a/build-faq.md +++ b/build-faq.md @@ -10,30 +10,22 @@ SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a frame For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment: -#### clang - -This may be installed via a Visual Studio installer packages instead of built from source. - #### SDL Port -[libsdl2](https://libsdl.org/download-2.0.php) has two separate files that must be downloaded - 1. The `-x86` Runtime Binary (e.g., `SDL2-2.0.12-win32-x86.zip` (as of writing)) - 2. The Visual C++ Development Library (e.g., `SDL2-devel-2.0.12-VC.zip` (as of writing)) +For [libSDL2](https://libsdl.org/download-2.0.php), download the Visual C++ Development Library pack. Place the extracted files within a known folder for later. Both the `\x86\` and `\include\` paths will be needed. -For the Runtime Binary, place the extracted `SDL2.dll` into a known folder for later. - -- `C:\SDL2\bin\SDL2.dll` will be used as an example - -For the Visual C++ Development Library, place the extracted files within a known folder for later. - -The following examples will be referenced later: +The following examples will be referenced later: - `C:\SDL2\lib\x86\*` - `C:\SDL2\include\*` -#### Gnuwin +#### rgbds -Ensure that this is in %PATH%. +After downloading [rgbds](https://github.com/bentley/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. + +#### GnuWin + +Ensure that the `gnuwin32\bin\` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\gnuwin32\bin`. If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. @@ -43,12 +35,11 @@ Within a command prompt in the project directory: ``` vcvars32 -set path=%path%;C:\SDL2\bin set lib=%lib%;C:\SDL2\lib\x86 set include=%include%;C:\SDL2\include make ``` -Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `path`, `lib`, and `include` paths are updated appropriately with the SDL2 downloads. +Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `gnuwin32\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories. #### Error -1073741819 From 64963e1746f0a548d3412e115fd7fba934193226 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Oct 2020 15:57:23 +0300 Subject: [PATCH 1205/1216] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1218fc11..97cd2e4e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ SameBoy is an open-source project licensed under the MIT license, and you're wel SameBoy requires the following tools and libraries to build: * clang * make - * Cocoa port: OS X SDK and Xcode command line tools [OSX Only] + * macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool) * SDL port: libsdl2 * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation From 99ec5b32fc09700b20bade5e976242830770c4e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Oct 2020 16:03:32 +0300 Subject: [PATCH 1206/1216] Update build-faq.md --- build-faq.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/build-faq.md b/build-faq.md index 56c59ae3..9def1349 100644 --- a/build-faq.md +++ b/build-faq.md @@ -1,16 +1,19 @@ -# Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException +# macOS Specific Issues +## Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException When building on macOS, the build system will make a native Cocoa app by default. In this case, the build system uses the Xcode `ibtool` command to build user interface files. If this command fails, you can fix this issue by starting Xcode and letting it install components. After this is done, you should be able to close Xcode and build successfully. -# Attempting to build the SDL frontend on macOS fails on linking +## Attempting to build the SDL frontend on macOS fails on linking SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. -# Windows build process +# Windows Build Process + +## Tools and Libraries Installation For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment: -#### SDL Port +### SDL2 For [libSDL2](https://libsdl.org/download-2.0.php), download the Visual C++ Development Library pack. Place the extracted files within a known folder for later. Both the `\x86\` and `\include\` paths will be needed. @@ -19,17 +22,15 @@ The following examples will be referenced later: - `C:\SDL2\lib\x86\*` - `C:\SDL2\include\*` -#### rgbds +### rgbds After downloading [rgbds](https://github.com/bentley/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. -#### GnuWin +### GnuWin Ensure that the `gnuwin32\bin\` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\gnuwin32\bin`. -If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. - -### Building +## Building Within a command prompt in the project directory: @@ -41,12 +42,16 @@ make ``` Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `gnuwin32\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories. -#### Error -1073741819 +## Common Errors + +### Error -1073741819 If encountering an error that appears as follows: -> make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819 +``` make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819``` -Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. +Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. This appears to be an issue with GnuWin. +### The system cannot find the file specified (`usr/bin/mkdir`) +If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. This happens because the Git for Windows version of `which` is used instead of the GnuWin one, and it returns a Unix-style path instead of a Windows one. From c35fe8b5179cf8022454714d1848abe6cbe2644d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Oct 2020 16:39:23 +0300 Subject: [PATCH 1207/1216] Make `gb.h` compatible with C++ again for bsnes integration. Fixed #300 --- Core/save_state.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/save_state.h b/Core/save_state.h index fcb91355..8e5fc4e0 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -5,10 +5,16 @@ #define GB_PADDING(type, old_usage) type old_usage##__do_not_use +#ifdef __cplusplus +/* For bsnes integration. C++ code does not need section information, and throws a fit over certain types such + as anonymous enums inside unions */ +#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__ +#else #define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0] #define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) #define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) #define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) +#endif #define GB_aligned_double __attribute__ ((aligned (8))) double From faeb1d2e184a5bd4c9cdd023baaf09298eb57b68 Mon Sep 17 00:00:00 2001 From: slash0042 <57612744+slash0042@users.noreply.github.com> Date: Fri, 9 Oct 2020 23:21:20 +0000 Subject: [PATCH 1208/1216] Add libnx port --- libretro/Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libretro/Makefile b/libretro/Makefile index b3276288..00b28cdb 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -97,6 +97,15 @@ else ifeq ($(platform), switch) include $(LIBTRANSISTOR_HOME)/libtransistor.mk CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls STATIC_LINKING=1 +# Nintendo Switch (libnx) +else ifeq ($(platform), libnx) + include $(DEVKITPRO)/libnx/switch_rules + TARGET := $(TARGET_NAME)_libretro_$(platform).a + DEFINES += -DSWITCH=1 -D__SWITCH__ -DARM + CFLAGS += $(DEFINES) -fPIE -I$(LIBNX)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec + CFLAGS += -march=armv8-a -mtune=cortex-a57 -mtp=soft -mcpu=cortex-a57+crc+fp+simd -ffast-math + CXXFLAGS := $(ASFLAGS) $(CFLAGS) + STATIC_LINKING = 1 # Nintendo WiiU else ifeq ($(platform), wiiu) TARGET := $(TARGET_NAME)_libretro_$(platform).a From efe8d6b643c0829739bdb9892e80cbcbd4c27464 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Sat, 10 Oct 2020 01:21:13 +0000 Subject: [PATCH 1209/1216] Update Makefile --- libretro/Makefile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 00b28cdb..366ec17b 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -109,8 +109,8 @@ else ifeq ($(platform), libnx) # Nintendo WiiU else ifeq ($(platform), wiiu) TARGET := $(TARGET_NAME)_libretro_$(platform).a - CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) - AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) + CC ?= $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) + AR ?= $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) CFLAGS += -DGEKKO -DHW_RVL -DWIIU -mwup -mcpu=750 -meabi -mhard-float -D__ppc__ -DMSB_FIRST -I$(DEVKITPRO)/libogc/include CFLAGS += -U__INT32_TYPE__ -U __UINT32_TYPE__ -D__INT32_TYPE__=int STATIC_LINKING = 1 @@ -149,7 +149,7 @@ else ifeq ($(platform), emscripten) fpic := -fPIC SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined else ifeq ($(platform), vita) - TARGET := $(TARGET_NAME)_vita.a + TARGET := $(TARGET_NAME)_libretro_vita.a CC = arm-vita-eabi-gcc AR = arm-vita-eabi-ar CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls @@ -181,14 +181,14 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix)) - CC = cl.exe - CXX = cl.exe - LD = link.exe + CC ?= cl.exe + CXX ?= cl.exe + LD ?= link.exe reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>nul))) fix_path = $(subst $(SPACE),\ ,$(subst \,/,$1)) - ProgramFiles86w := $(shell cmd /c "echo %PROGRAMFILES(x86)%") + ProgramFiles86w := $(shell cmd //c "echo %PROGRAMFILES(x86)%") ProgramFiles86 := $(shell cygpath "$(ProgramFiles86w)") WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0) @@ -251,7 +251,7 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) LDFLAGS += -DLL else - CC = gcc + CC ?= gcc TARGET := $(TARGET_NAME)_libretro.dll SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined endif From 8dc60d0b87d77db1effa7ba1c6003775affc16db Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 10 Oct 2020 03:52:22 +0000 Subject: [PATCH 1210/1216] update makefile --- libretro/Makefile | 35 +++++++++++++++++++++++++++++++++-- libretro/Makefile.common | 2 ++ libretro/jni/Android.mk | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 366ec17b..c72b1f76 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -51,7 +51,7 @@ ifeq ($(platform), win) INCFLAGS += -I Windows endif -CORE_DIR += .. +CORE_DIR = ../ TARGET_NAME = sameboy LIBM = -lm @@ -90,7 +90,38 @@ else ifeq ($(platform), linux-portable) TARGET := $(TARGET_NAME)_libretro.$(EXT) fpic := -fPIC -nostdlib SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T - LIBM := + LIBM := +# (armv7 a7, hard point, neon based) ### +# NESC, SNESC, C64 mini +else ifeq ($(platform), classic_armv7_a7) + TARGET := $(TARGET_NAME)_libretro.so + fpic := -fPIC + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined + CFLAGS += -Ofast \ + -flto=4 -fwhole-program -fuse-linker-plugin \ + -fdata-sections -ffunction-sections -Wl,--gc-sections \ + -fno-stack-protector -fno-ident -fomit-frame-pointer \ + -falign-functions=1 -falign-jumps=1 -falign-loops=1 \ + -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-unroll-loops \ + -fmerge-all-constants -fno-math-errno \ + -marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard + CXXFLAGS += $(CFLAGS) + CPPFLAGS += $(CFLAGS) + ASFLAGS += $(CFLAGS) + HAVE_NEON = 1 + ARCH = arm + BUILTIN_GPU = neon + USE_DYNAREC = 1 + ifeq ($(shell echo `$(CC) -dumpversion` "< 4.9" | bc -l), 1) + CFLAGS += -march=armv7-a + else + CFLAGS += -march=armv7ve + # If gcc is 5.0 or later + ifeq ($(shell echo `$(CC) -dumpversion` ">= 5" | bc -l), 1) + LDFLAGS += -static-libgcc -static-libstdc++ + endif + endif +####################################### # Nintendo Switch (libtransistor) else ifeq ($(platform), switch) TARGET := $(TARGET_NAME)_libretro_$(platform).a diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 7f7688a3..430c03d2 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -1,3 +1,5 @@ +VERSION := 0.13.6 + INCFLAGS := -I$(CORE_DIR) SOURCES_C := $(CORE_DIR)/Core/gb.c \ diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 3b2d74b2..e0646b9a 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -8,7 +8,7 @@ include $(CORE_DIR)/libretro/Makefile.common GENERATED_SOURCES := $(filter %_boot.c,$(SOURCES_C)) -COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar +COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar -DANDROID GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)" ifneq ($(GIT_VERSION)," unknown") From cd526d960e9eb8c5074478e1251aabb5d6158b4f Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Wed, 7 Oct 2020 21:59:29 -0500 Subject: [PATCH 1211/1216] libretro: changing model requires manual game restart --- libretro/libretro.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 24514d4f..3f70611f 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -207,7 +207,7 @@ static retro_environment_t environ_cb; static const struct retro_variable vars_single[] = { { "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, { "sameboy_high_pass_filter_mode", "High-pass filter; accurate|remove dc offset|off" }, - { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, + { "sameboy_model", "Emulated model (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { "sameboy_border", "Display border; Super Game Boy only|always|never" }, { "sameboy_rumble", "Enable rumble; rumble-enabled games|all games|never" }, { NULL } @@ -219,8 +219,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_model_1", "Emulated model for Game Boy #1 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_model_2", "Emulated model for Game Boy #2 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; accurate|remove dc offset|off" }, @@ -601,11 +601,7 @@ static void check_variables() new_model = MODEL_AUTO; } - if (new_model != model[0]) { - geometry_updated = true; - model[0] = new_model; - init_for_current_model(0); - } + model[0] = new_model; } var.key = "sameboy_border"; @@ -747,10 +743,7 @@ static void check_variables() new_model = MODEL_AUTO; } - if (model[0] != new_model) { - model[0] = new_model; - init_for_current_model(0); - } + model[0] = new_model; } var.key = "sameboy_model_2"; @@ -776,10 +769,7 @@ static void check_variables() new_model = MODEL_AUTO; } - if (model[1] != new_model) { - model[1] = new_model; - init_for_current_model(1); - } + model[1] = new_model; } var.key = "sameboy_screen_layout"; @@ -947,10 +937,14 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { + check_variables(); + for (int i = 0; i < emulated_devices; i++) { + init_for_current_model(i); GB_reset(&gameboy[i]); } + geometry_updated = true; } void retro_run(void) From 2bfca48e0f5f1c4eaa982bd2e4d50a2735c13ae2 Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Fri, 9 Oct 2020 23:01:42 -0500 Subject: [PATCH 1212/1216] libretro: fix core version --- libretro/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index c72b1f76..2ed87b8c 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -17,8 +17,6 @@ filter_out2 = $(call filter_out1,$(call filter_out1,$1)) unixpath = $(subst \,/,$1) unixcygpath = /$(subst :,,$(call unixpath,$1)) -CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" - ifeq ($(platform),) platform = unix ifeq ($(shell uname -a),) @@ -302,6 +300,8 @@ endif include Makefile.common +CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" + OBJECTS := $(patsubst $(CORE_DIR)/%.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) OBJOUT = -o From 526c2e029a8475806e40f2b027cc5cae42f96b52 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Oct 2020 14:50:11 +0300 Subject: [PATCH 1213/1216] Fix #296 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97cd2e4e..1107fdc4 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ SameBoy is an open-source project licensed under the MIT license, and you're wel ## Compilation SameBoy requires the following tools and libraries to build: - * clang + * clang (Recommended; required for macOS) or GCC * make * macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool) * SDL port: libsdl2 From 714227883fbc196a73905bb38332aede65bfeed8 Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Sat, 10 Oct 2020 08:45:59 -0500 Subject: [PATCH 1214/1216] cross-compile friendly --- BootROMs/pb12.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BootROMs/pb12.c b/BootROMs/pb12.c index 3a72fab2..cfedf6bb 100644 --- a/BootROMs/pb12.c +++ b/BootROMs/pb12.c @@ -4,7 +4,6 @@ #include #include #include -#include void opts(uint8_t byte, uint8_t *options) { @@ -18,7 +17,8 @@ void write_all(int fd, const void *buf, size_t count) { while (count) { ssize_t written = write(fd, buf, count); if (written < 0) { - err(1, "write"); + fprintf(stderr, "write"); + exit(1); } count -= written; buf += written; From 696bebc673bf1d5ab7356e142c4a84f1fbbef184 Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Sat, 10 Oct 2020 17:14:10 +0000 Subject: [PATCH 1215/1216] libretro: joypad bitmasks --- libretro/libretro.c | 39 +- libretro/libretro.h | 1393 +++++++++++++++++++++++++++++++------------ 2 files changed, 1039 insertions(+), 393 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 3f70611f..9e27f031 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -85,6 +85,8 @@ static retro_audio_sample_t audio_sample_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; +static bool libretro_supports_bitmasks = false; + static unsigned emulated_devices = 1; static bool initialized = false; static unsigned screen_layout = 0; @@ -119,24 +121,39 @@ static struct retro_rumble_interface rumble; static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { + uint16_t joypad_bits = 0; + input_poll_cb(); + if (libretro_supports_bitmasks) { + joypad_bits = input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); + } + else { + unsigned j; + + for (j = 0; j < (RETRO_DEVICE_ID_JOYPAD_R3+1); j++) { + if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, j)) { + joypad_bits |= (1 << j); + } + } + } + GB_set_key_state_for_player(gb, GB_KEY_RIGHT, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT)); GB_set_key_state_for_player(gb, GB_KEY_LEFT, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT)); GB_set_key_state_for_player(gb, GB_KEY_UP, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_UP)); GB_set_key_state_for_player(gb, GB_KEY_DOWN, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)); GB_set_key_state_for_player(gb, GB_KEY_A, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_A)); GB_set_key_state_for_player(gb, GB_KEY_B, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_B)); GB_set_key_state_for_player(gb, GB_KEY_SELECT, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT)); GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_START)); } @@ -840,6 +857,10 @@ void retro_init(void) else { log_cb = fallback_log; } + + if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) { + libretro_supports_bitmasks = true; + } } void retro_deinit(void) @@ -848,6 +869,8 @@ void retro_deinit(void) free(frame_buf_copy); frame_buf = NULL; frame_buf_copy = NULL; + + libretro_supports_bitmasks = false; } unsigned retro_api_version(void) diff --git a/libretro/libretro.h b/libretro/libretro.h index a4df6be4..1fd2f5b7 100644 --- a/libretro/libretro.h +++ b/libretro/libretro.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2016 The RetroArch team +/* Copyright (C) 2010-2018 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this libretro API header (libretro.h). @@ -32,7 +32,7 @@ extern "C" { #endif #ifndef __cplusplus -#if defined(_MSC_VER) && !defined(SN_TARGET_PS3) +#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(SN_TARGET_PS3) /* Hack applied for MSVC when compiling in C89 mode * as it isn't C99-compliant. */ #define bool unsigned char @@ -77,7 +77,7 @@ extern "C" { # endif #endif -/* Used for checking API/ABI mismatches that can break libretro +/* Used for checking API/ABI mismatches that can break libretro * implementations. * It is not incremented for compatible changes to the API. */ @@ -87,13 +87,13 @@ extern "C" { * Libretro's fundamental device abstractions. * * Libretro's input system consists of some standardized device types, - * such as a joypad (with/without analog), mouse, keyboard, lightgun + * such as a joypad (with/without analog), mouse, keyboard, lightgun * and a pointer. * - * The functionality of these devices are fixed, and individual cores + * The functionality of these devices are fixed, and individual cores * map their own concept of a controller to libretro's abstractions. - * This makes it possible for frontends to map the abstract types to a - * real input device, and not having to worry about binding input + * This makes it possible for frontends to map the abstract types to a + * real input device, and not having to worry about binding input * correctly to arbitrary controller layouts. */ @@ -104,43 +104,52 @@ extern "C" { /* Input disabled. */ #define RETRO_DEVICE_NONE 0 -/* The JOYPAD is called RetroPad. It is essentially a Super Nintendo - * controller, but with additional L2/R2/L3/R3 buttons, similar to a +/* The JOYPAD is called RetroPad. It is essentially a Super Nintendo + * controller, but with additional L2/R2/L3/R3 buttons, similar to a * PS1 DualShock. */ #define RETRO_DEVICE_JOYPAD 1 /* The mouse is a simple mouse, similar to Super Nintendo's mouse. * X and Y coordinates are reported relatively to last poll (poll callback). - * It is up to the libretro implementation to keep track of where the mouse + * It is up to the libretro implementation to keep track of where the mouse * pointer is supposed to be on the screen. - * The frontend must make sure not to interfere with its own hardware + * The frontend must make sure not to interfere with its own hardware * mouse pointer. */ #define RETRO_DEVICE_MOUSE 2 /* KEYBOARD device lets one poll for raw key pressed. - * It is poll based, so input callback will return with the current + * It is poll based, so input callback will return with the current * pressed state. * For event/text based keyboard input, see * RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. */ #define RETRO_DEVICE_KEYBOARD 3 -/* Lightgun X/Y coordinates are reported relatively to last poll, - * similar to mouse. */ +/* LIGHTGUN device is similar to Guncon-2 for PlayStation 2. + * It reports X/Y coordinates in screen space (similar to the pointer) + * in the range [-0x8000, 0x7fff] in both axes, with zero being center and + * -0x8000 being out of bounds. + * As well as reporting on/off screen state. It features a trigger, + * start/select buttons, auxiliary action buttons and a + * directional pad. A forced off-screen shot can be requested for + * auto-reloading function in some games. + */ #define RETRO_DEVICE_LIGHTGUN 4 /* The ANALOG device is an extension to JOYPAD (RetroPad). - * Similar to DualShock it adds two analog sticks. - * This is treated as a separate device type as it returns values in the - * full analog range of [-0x8000, 0x7fff]. Positive X axis is right. - * Positive Y axis is down. - * Only use ANALOG type when polling for analog values of the axes. + * Similar to DualShock2 it adds two analog sticks and all buttons can + * be analog. This is treated as a separate device type as it returns + * axis values in the full analog range of [-0x7fff, 0x7fff], + * although some devices may return -0x8000. + * Positive X axis is right. Positive Y axis is down. + * Buttons are returned in the range [0, 0x7fff]. + * Only use ANALOG type when polling for analog values. */ #define RETRO_DEVICE_ANALOG 5 /* Abstracts the concept of a pointing mechanism, e.g. touch. - * This allows libretro to query in absolute coordinates where on the + * This allows libretro to query in absolute coordinates where on the * screen a mouse (or something similar) is being placed. * For a touch centric device, coordinates reported are the coordinates * of the press. @@ -148,33 +157,34 @@ extern "C" { * Coordinates in X and Y are reported as: * [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen, * and 0x7fff corresponds to the far right/bottom of the screen. - * The "screen" is here defined as area that is passed to the frontend and + * The "screen" is here defined as area that is passed to the frontend and * later displayed on the monitor. * * The frontend is free to scale/resize this screen as it sees fit, however, - * (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the + * (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the * game image, etc. * - * To check if the pointer coordinates are valid (e.g. a touch display + * To check if the pointer coordinates are valid (e.g. a touch display * actually being touched), PRESSED returns 1 or 0. * - * If using a mouse on a desktop, PRESSED will usually correspond to the + * If using a mouse on a desktop, PRESSED will usually correspond to the * left mouse button, but this is a frontend decision. * PRESSED will only return 1 if the pointer is inside the game screen. * - * For multi-touch, the index variable can be used to successively query + * For multi-touch, the index variable can be used to successively query * more presses. * If index = 0 returns true for _PRESSED, coordinates can be extracted - * with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with + * with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with * index = 1, and so on. - * Eventually _PRESSED will return false for an index. No further presses + * Eventually _PRESSED will return false for an index. No further presses * are registered at this point. */ #define RETRO_DEVICE_POINTER 6 /* Buttons for the RetroPad (JOYPAD). - * The placement of these is equivalent to placements on the + * The placement of these is equivalent to placements on the * Super Nintendo controller. - * L2/R2/L3/R3 buttons correspond to the PS1 DualShock. */ + * L2/R2/L3/R3 buttons correspond to the PS1 DualShock. + * Also used as id values for RETRO_DEVICE_INDEX_ANALOG_BUTTON */ #define RETRO_DEVICE_ID_JOYPAD_B 0 #define RETRO_DEVICE_ID_JOYPAD_Y 1 #define RETRO_DEVICE_ID_JOYPAD_SELECT 2 @@ -192,11 +202,14 @@ extern "C" { #define RETRO_DEVICE_ID_JOYPAD_L3 14 #define RETRO_DEVICE_ID_JOYPAD_R3 15 +#define RETRO_DEVICE_ID_JOYPAD_MASK 256 + /* Index / Id values for ANALOG device. */ -#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 -#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 -#define RETRO_DEVICE_ID_ANALOG_X 0 -#define RETRO_DEVICE_ID_ANALOG_Y 1 +#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 +#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 +#define RETRO_DEVICE_INDEX_ANALOG_BUTTON 2 +#define RETRO_DEVICE_ID_ANALOG_X 0 +#define RETRO_DEVICE_ID_ANALOG_Y 1 /* Id values for MOUSE. */ #define RETRO_DEVICE_ID_MOUSE_X 0 @@ -208,20 +221,36 @@ extern "C" { #define RETRO_DEVICE_ID_MOUSE_MIDDLE 6 #define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP 7 #define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN 8 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_4 9 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_5 10 -/* Id values for LIGHTGUN types. */ -#define RETRO_DEVICE_ID_LIGHTGUN_X 0 -#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 -#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 -#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 -#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 -#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 -#define RETRO_DEVICE_ID_LIGHTGUN_START 6 +/* Id values for LIGHTGUN. */ +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X 13 /*Absolute Position*/ +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y 14 /*Absolute*/ +#define RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN 15 /*Status Check*/ +#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 +#define RETRO_DEVICE_ID_LIGHTGUN_RELOAD 16 /*Forced off-screen shot*/ +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_A 3 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_B 4 +#define RETRO_DEVICE_ID_LIGHTGUN_START 6 +#define RETRO_DEVICE_ID_LIGHTGUN_SELECT 7 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_C 8 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP 9 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN 10 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT 11 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT 12 +/* deprecated */ +#define RETRO_DEVICE_ID_LIGHTGUN_X 0 /*Relative Position*/ +#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 /*Relative*/ +#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 /*Use Aux:A*/ +#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 /*Use Aux:B*/ +#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 /*Use Start*/ /* Id values for POINTER. */ #define RETRO_DEVICE_ID_POINTER_X 0 #define RETRO_DEVICE_ID_POINTER_Y 1 #define RETRO_DEVICE_ID_POINTER_PRESSED 2 +#define RETRO_DEVICE_ID_POINTER_COUNT 3 /* Returned from retro_get_region(). */ #define RETRO_REGION_NTSC 0 @@ -230,28 +259,33 @@ extern "C" { /* Id values for LANGUAGE */ enum retro_language { - RETRO_LANGUAGE_ENGLISH = 0, - RETRO_LANGUAGE_JAPANESE = 1, - RETRO_LANGUAGE_FRENCH = 2, - RETRO_LANGUAGE_SPANISH = 3, - RETRO_LANGUAGE_GERMAN = 4, - RETRO_LANGUAGE_ITALIAN = 5, - RETRO_LANGUAGE_DUTCH = 6, - RETRO_LANGUAGE_PORTUGUESE = 7, - RETRO_LANGUAGE_RUSSIAN = 8, - RETRO_LANGUAGE_KOREAN = 9, - RETRO_LANGUAGE_CHINESE_TRADITIONAL = 10, - RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 11, - RETRO_LANGUAGE_ESPERANTO = 12, - RETRO_LANGUAGE_POLISH = 13, + RETRO_LANGUAGE_ENGLISH = 0, + RETRO_LANGUAGE_JAPANESE = 1, + RETRO_LANGUAGE_FRENCH = 2, + RETRO_LANGUAGE_SPANISH = 3, + RETRO_LANGUAGE_GERMAN = 4, + RETRO_LANGUAGE_ITALIAN = 5, + RETRO_LANGUAGE_DUTCH = 6, + RETRO_LANGUAGE_PORTUGUESE_BRAZIL = 7, + RETRO_LANGUAGE_PORTUGUESE_PORTUGAL = 8, + RETRO_LANGUAGE_RUSSIAN = 9, + RETRO_LANGUAGE_KOREAN = 10, + RETRO_LANGUAGE_CHINESE_TRADITIONAL = 11, + RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 12, + RETRO_LANGUAGE_ESPERANTO = 13, + RETRO_LANGUAGE_POLISH = 14, + RETRO_LANGUAGE_VIETNAMESE = 15, + RETRO_LANGUAGE_ARABIC = 16, + RETRO_LANGUAGE_GREEK = 17, + RETRO_LANGUAGE_TURKISH = 18, RETRO_LANGUAGE_LAST, /* Ensure sizeof(enum) == sizeof(int) */ - RETRO_LANGUAGE_DUMMY = INT_MAX + RETRO_LANGUAGE_DUMMY = INT_MAX }; /* Passed to retro_get_memory_data/size(). - * If the memory type doesn't apply to the + * If the memory type doesn't apply to the * implementation NULL/0 can be returned. */ #define RETRO_MEMORY_MASK 0xff @@ -349,6 +383,10 @@ enum retro_key RETROK_x = 120, RETROK_y = 121, RETROK_z = 122, + RETROK_LEFTBRACE = 123, + RETROK_BAR = 124, + RETROK_RIGHTBRACE = 125, + RETROK_TILDE = 126, RETROK_DELETE = 127, RETROK_KP0 = 256, @@ -419,6 +457,7 @@ enum retro_key RETROK_POWER = 320, RETROK_EURO = 321, RETROK_UNDO = 322, + RETROK_OEM_102 = 323, RETROK_LAST, @@ -441,7 +480,7 @@ enum retro_mod RETROKMOD_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ }; -/* If set, this call is not part of the public libretro API yet. It can +/* If set, this call is not part of the public libretro API yet. It can * change or be removed at any time. */ #define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000 /* Environment callback to be used internally in frontend. */ @@ -450,12 +489,14 @@ enum retro_mod /* Environment commands. */ #define RETRO_ENVIRONMENT_SET_ROTATION 1 /* const unsigned * -- * Sets screen rotation of graphics. - * Is only implemented if rotation can be accelerated by hardware. - * Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, + * Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, * 270 degrees counter-clockwise respectively. */ #define RETRO_ENVIRONMENT_GET_OVERSCAN 2 /* bool * -- - * Boolean value whether or not the implementation should use overscan, + * NOTE: As of 2019 this callback is considered deprecated in favor of + * using core options to manage overscan in a more nuanced, core-specific way. + * + * Boolean value whether or not the implementation should use overscan, * or crop away overscan. */ #define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 /* bool * -- @@ -463,15 +504,15 @@ enum retro_mod * passing NULL to video frame callback. */ - /* Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), + /* Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), * and reserved to avoid possible ABI clash. */ #define RETRO_ENVIRONMENT_SET_MESSAGE 6 /* const struct retro_message * -- - * Sets a message to be displayed in implementation-specific manner + * Sets a message to be displayed in implementation-specific manner * for a certain amount of 'frames'. - * Should not be used for trivial messages, which should simply be - * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a + * Should not be used for trivial messages, which should simply be + * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a * fallback, stderr). */ #define RETRO_ENVIRONMENT_SHUTDOWN 7 /* N/A (NULL) -- @@ -499,15 +540,15 @@ enum retro_mod #define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9 /* const char ** -- * Returns the "system" directory of the frontend. - * This directory can be used to store system specific + * This directory can be used to store system specific * content such as BIOSes, configuration data, etc. * The returned value can be NULL. * If so, no such directory is defined, * and it's up to the implementation to find a suitable directory. * - * NOTE: Some cores used this folder also for "save" data such as + * NOTE: Some cores used this folder also for "save" data such as * memory cards, etc, for lack of a better place to put it. - * This is now discouraged, and if possible, cores should try to + * This is now discouraged, and if possible, cores should try to * use the new GET_SAVE_DIRECTORY. */ #define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 @@ -515,19 +556,19 @@ enum retro_mod * Sets the internal pixel format used by the implementation. * The default pixel format is RETRO_PIXEL_FORMAT_0RGB1555. * This pixel format however, is deprecated (see enum retro_pixel_format). - * If the call returns false, the frontend does not support this pixel + * If the call returns false, the frontend does not support this pixel * format. * - * This function should be called inside retro_load_game() or + * This function should be called inside retro_load_game() or * retro_get_system_av_info(). */ #define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11 /* const struct retro_input_descriptor * -- * Sets an array of retro_input_descriptors. * It is up to the frontend to present this in a usable way. - * The array is terminated by retro_input_descriptor::description + * The array is terminated by retro_input_descriptor::description * being set to NULL. - * This function can be called at any time, but it is recommended + * This function can be called at any time, but it is recommended * to call it as early as possible. */ #define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12 @@ -536,52 +577,55 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13 /* const struct retro_disk_control_callback * -- - * Sets an interface which frontend can use to eject and insert + * Sets an interface which frontend can use to eject and insert * disk images. - * This is used for games which consist of multiple images and + * This is used for games which consist of multiple images and * must be manually swapped out by the user (e.g. PSX). */ #define RETRO_ENVIRONMENT_SET_HW_RENDER 14 /* struct retro_hw_render_callback * -- - * Sets an interface to let a libretro core render with + * Sets an interface to let a libretro core render with * hardware acceleration. * Should be called in retro_load_game(). - * If successful, libretro cores will be able to render to a + * If successful, libretro cores will be able to render to a * frontend-provided framebuffer. - * The size of this framebuffer will be at least as large as + * The size of this framebuffer will be at least as large as * max_width/max_height provided in get_av_info(). - * If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or + * If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or * NULL to retro_video_refresh_t. */ #define RETRO_ENVIRONMENT_GET_VARIABLE 15 /* struct retro_variable * -- * Interface to acquire user-defined information from environment * that cannot feasibly be supported in a multi-system way. - * 'key' should be set to a key which has already been set by + * 'key' should be set to a key which has already been set by * SET_VARIABLES. * 'data' will be set to a value or NULL. */ #define RETRO_ENVIRONMENT_SET_VARIABLES 16 /* const struct retro_variable * -- * Allows an implementation to signal the environment - * which variables it might want to check for later using + * which variables it might want to check for later using * GET_VARIABLE. - * This allows the frontend to present these variables to + * This allows the frontend to present these variables to * a user dynamically. - * This should be called as early as possible (ideally in - * retro_set_environment). + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterward it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. * - * 'data' points to an array of retro_variable structs + * 'data' points to an array of retro_variable structs * terminated by a { NULL, NULL } element. - * retro_variable::key should be namespaced to not collide - * with other implementations' keys. E.g. A core called + * retro_variable::key should be namespaced to not collide + * with other implementations' keys. E.g. A core called * 'foo' should use keys named as 'foo_option'. - * retro_variable::value should contain a human readable - * description of the key as well as a '|' delimited list + * retro_variable::value should contain a human readable + * description of the key as well as a '|' delimited list * of expected values. * - * The number of possible options should be very limited, - * i.e. it should be feasible to cycle through options + * The number of possible options should be very limited, + * i.e. it should be feasible to cycle through options * without a keyboard. * * First entry should be treated as a default. @@ -589,11 +633,11 @@ enum retro_mod * Example entry: * { "foo_option", "Speed hack coprocessor X; false|true" } * - * Text before first ';' is description. This ';' must be - * followed by a space, and followed by a list of possible + * Text before first ';' is description. This ';' must be + * followed by a space, and followed by a list of possible * values split up with '|'. * - * Only strings are operated on. The possible values will + * Only strings are operated on. The possible values will * generally be displayed and stored as-is by the frontend. */ #define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17 @@ -604,72 +648,72 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME 18 /* const bool * -- - * If true, the libretro implementation supports calls to + * If true, the libretro implementation supports calls to * retro_load_game() with NULL as argument. * Used by cores which can run without particular game data. * This should be called within retro_set_environment() only. */ #define RETRO_ENVIRONMENT_GET_LIBRETRO_PATH 19 /* const char ** -- - * Retrieves the absolute path from where this libretro + * Retrieves the absolute path from where this libretro * implementation was loaded. - * NULL is returned if the libretro was loaded statically - * (i.e. linked statically to frontend), or if the path cannot be + * NULL is returned if the libretro was loaded statically + * (i.e. linked statically to frontend), or if the path cannot be * determined. - * Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can + * Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can * be loaded without ugly hacks. */ - - /* Environment 20 was an obsolete version of SET_AUDIO_CALLBACK. + + /* Environment 20 was an obsolete version of SET_AUDIO_CALLBACK. * It was not used by any known core at the time, * and was removed from the API. */ +#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 + /* const struct retro_frame_time_callback * -- + * Lets the core know how much time has passed since last + * invocation of retro_run(). + * The frontend can tamper with the timing to fake fast-forward, + * slow-motion, frame stepping, etc. + * In this case the delta time will use the reference value + * in frame_time_callback.. + */ #define RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK 22 /* const struct retro_audio_callback * -- - * Sets an interface which is used to notify a libretro core about audio + * Sets an interface which is used to notify a libretro core about audio * being available for writing. - * The callback can be called from any thread, so a core using this must + * The callback can be called from any thread, so a core using this must * have a thread safe audio implementation. - * It is intended for games where audio and video are completely + * It is intended for games where audio and video are completely * asynchronous and audio can be generated on the fly. - * This interface is not recommended for use with emulators which have + * This interface is not recommended for use with emulators which have * highly synchronous audio. * - * The callback only notifies about writability; the libretro core still + * The callback only notifies about writability; the libretro core still * has to call the normal audio callbacks - * to write audio. The audio callbacks must be called from within the + * to write audio. The audio callbacks must be called from within the * notification callback. * The amount of audio data to write is up to the implementation. * Generally, the audio callback will be called continously in a loop. * - * Due to thread safety guarantees and lack of sync between audio and - * video, a frontend can selectively disallow this interface based on - * internal configuration. A core using this interface must also + * Due to thread safety guarantees and lack of sync between audio and + * video, a frontend can selectively disallow this interface based on + * internal configuration. A core using this interface must also * implement the "normal" audio interface. * - * A libretro core using SET_AUDIO_CALLBACK should also make use of + * A libretro core using SET_AUDIO_CALLBACK should also make use of * SET_FRAME_TIME_CALLBACK. */ -#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 - /* const struct retro_frame_time_callback * -- - * Lets the core know how much time has passed since last - * invocation of retro_run(). - * The frontend can tamper with the timing to fake fast-forward, - * slow-motion, frame stepping, etc. - * In this case the delta time will use the reference value - * in frame_time_callback.. - */ #define RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE 23 /* struct retro_rumble_interface * -- - * Gets an interface which is used by a libretro core to set + * Gets an interface which is used by a libretro core to set * state of rumble motors in controllers. - * A strong and weak motor is supported, and they can be + * A strong and weak motor is supported, and they can be * controlled indepedently. */ #define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24 /* uint64_t * -- - * Gets a bitmask telling which device type are expected to be + * Gets a bitmask telling which device type are expected to be * handled properly in a call to retro_input_state_t. - * Devices which are not handled or recognized always return + * Devices which are not handled or recognized always return * 0 in retro_input_state_t. * Example bitmask: caps = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG). * Should only be called in retro_run(). @@ -678,56 +722,56 @@ enum retro_mod /* struct retro_sensor_interface * -- * Gets access to the sensor interface. * The purpose of this interface is to allow - * setting state related to sensors such as polling rate, + * setting state related to sensors such as polling rate, * enabling/disable it entirely, etc. - * Reading sensor state is done via the normal + * Reading sensor state is done via the normal * input_state_callback API. */ #define RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE (26 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* struct retro_camera_callback * -- * Gets an interface to a video camera driver. - * A libretro core can use this interface to get access to a + * A libretro core can use this interface to get access to a * video camera. - * New video frames are delivered in a callback in same + * New video frames are delivered in a callback in same * thread as retro_run(). * * GET_CAMERA_INTERFACE should be called in retro_load_game(). * - * Depending on the camera implementation used, camera frames + * Depending on the camera implementation used, camera frames * will be delivered as a raw framebuffer, * or as an OpenGL texture directly. * - * The core has to tell the frontend here which types of + * The core has to tell the frontend here which types of * buffers can be handled properly. - * An OpenGL texture can only be handled when using a + * An OpenGL texture can only be handled when using a * libretro GL core (SET_HW_RENDER). - * It is recommended to use a libretro GL core when + * It is recommended to use a libretro GL core when * using camera interface. * - * The camera is not started automatically. The retrieved start/stop + * The camera is not started automatically. The retrieved start/stop * functions must be used to explicitly * start and stop the camera driver. */ #define RETRO_ENVIRONMENT_GET_LOG_INTERFACE 27 /* struct retro_log_callback * -- - * Gets an interface for logging. This is useful for + * Gets an interface for logging. This is useful for * logging in a cross-platform way - * as certain platforms cannot use use stderr for logging. + * as certain platforms cannot use stderr for logging. * It also allows the frontend to * show logging information in a more suitable way. - * If this interface is not used, libretro cores should + * If this interface is not used, libretro cores should * log to stderr as desired. */ #define RETRO_ENVIRONMENT_GET_PERF_INTERFACE 28 /* struct retro_perf_callback * -- - * Gets an interface for performance counters. This is useful - * for performance logging in a cross-platform way and for detecting + * Gets an interface for performance counters. This is useful + * for performance logging in a cross-platform way and for detecting * architecture-specific features, such as SIMD support. */ #define RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE 29 /* struct retro_location_callback * -- * Gets access to the location interface. - * The purpose of this interface is to be able to retrieve + * The purpose of this interface is to be able to retrieve * location-based information from the host device, * such as current latitude / longitude. */ @@ -735,7 +779,7 @@ enum retro_mod #define RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY 30 /* const char ** -- * Returns the "core assets" directory of the frontend. - * This directory can be used to store specific assets that the + * This directory can be used to store specific assets that the * core relies upon, such as art assets, * input data, etc etc. * The returned value can be NULL. @@ -744,76 +788,77 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY 31 /* const char ** -- - * Returns the "save" directory of the frontend. - * This directory can be used to store SRAM, memory cards, - * high scores, etc, if the libretro core + * Returns the "save" directory of the frontend, unless there is no + * save directory available. The save directory should be used to + * store SRAM, memory cards, high scores, etc, if the libretro core * cannot use the regular memory interface (retro_get_memory_data()). * - * NOTE: libretro cores used to check GET_SYSTEM_DIRECTORY for - * similar things before. - * They should still check GET_SYSTEM_DIRECTORY if they want to - * be backwards compatible. - * The path here can be NULL. It should only be non-NULL if the - * frontend user has set a specific save path. + * If the frontend cannot designate a save directory, it will return + * NULL to indicate that the core should attempt to operate without a + * save directory set. + * + * NOTE: early libretro cores used the system directory for save + * files. Cores that need to be backwards-compatible can still check + * GET_SYSTEM_DIRECTORY. */ #define RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO 32 /* const struct retro_system_av_info * -- - * Sets a new av_info structure. This can only be called from + * Sets a new av_info structure. This can only be called from * within retro_run(). - * This should *only* be used if the core is completely altering the + * This should *only* be used if the core is completely altering the * internal resolutions, aspect ratios, timings, sampling rate, etc. - * Calling this can require a full reinitialization of video/audio + * Calling this can require a full reinitialization of video/audio * drivers in the frontend, * - * so it is important to call it very sparingly, and usually only with + * so it is important to call it very sparingly, and usually only with * the users explicit consent. - * An eventual driver reinitialize will happen so that video and + * An eventual driver reinitialize will happen so that video and * audio callbacks - * happening after this call within the same retro_run() call will + * happening after this call within the same retro_run() call will * target the newly initialized driver. * - * This callback makes it possible to support configurable resolutions + * This callback makes it possible to support configurable resolutions * in games, which can be useful to * avoid setting the "worst case" in max_width/max_height. * - * ***HIGHLY RECOMMENDED*** Do not call this callback every time + * ***HIGHLY RECOMMENDED*** Do not call this callback every time * resolution changes in an emulator core if it's - * expected to be a temporary change, for the reasons of possible + * expected to be a temporary change, for the reasons of possible * driver reinitialization. - * This call is not a free pass for not trying to provide - * correct values in retro_get_system_av_info(). If you need to change - * things like aspect ratio or nominal width/height, - * use RETRO_ENVIRONMENT_SET_GEOMETRY, which is a softer variant + * This call is not a free pass for not trying to provide + * correct values in retro_get_system_av_info(). If you need to change + * things like aspect ratio or nominal width/height, + * use RETRO_ENVIRONMENT_SET_GEOMETRY, which is a softer variant * of SET_SYSTEM_AV_INFO. * - * If this returns false, the frontend does not acknowledge a + * If this returns false, the frontend does not acknowledge a * changed av_info struct. */ #define RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK 33 /* const struct retro_get_proc_address_interface * -- - * Allows a libretro core to announce support for the + * Allows a libretro core to announce support for the * get_proc_address() interface. - * This interface allows for a standard way to extend libretro where + * This interface allows for a standard way to extend libretro where * use of environment calls are too indirect, * e.g. for cases where the frontend wants to call directly into the core. * - * If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK + * If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK * **MUST** be called from within retro_set_environment(). */ #define RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO 34 /* const struct retro_subsystem_info * -- * This environment call introduces the concept of libretro "subsystems". - * A subsystem is a variant of a libretro core which supports + * A subsystem is a variant of a libretro core which supports * different kinds of games. - * The purpose of this is to support e.g. emulators which might + * The purpose of this is to support e.g. emulators which might * have special needs, e.g. Super Nintendo's Super GameBoy, Sufami Turbo. - * It can also be used to pick among subsystems in an explicit way + * It can also be used to pick among subsystems in an explicit way * if the libretro implementation is a multi-system emulator itself. * * Loading a game via a subsystem is done with retro_load_game_special(), - * and this environment call allows a libretro core to expose which + * and this environment call allows a libretro core to expose which * subsystems are supported for use with retro_load_game_special(). - * A core passes an array of retro_game_special_info which is terminated + * A core passes an array of retro_game_special_info which is terminated * with a zeroed out retro_game_special_info struct. * * If a core wants to use this functionality, SET_SUBSYSTEM_INFO @@ -821,68 +866,81 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 /* const struct retro_controller_info * -- - * This environment call lets a libretro core tell the frontend - * which controller types are recognized in calls to + * This environment call lets a libretro core tell the frontend + * which controller subclasses are recognized in calls to * retro_set_controller_port_device(). * - * Some emulators such as Super Nintendo - * support multiple lightgun types which must be specifically - * selected from. - * It is therefore sometimes necessary for a frontend to be able - * to tell the core about a special kind of input device which is - * not covered by the libretro input API. + * Some emulators such as Super Nintendo support multiple lightgun + * types which must be specifically selected from. It is therefore + * sometimes necessary for a frontend to be able to tell the core + * about a special kind of input device which is not specifcally + * provided by the Libretro API. * - * In order for a frontend to understand the workings of an input device, - * it must be a specialized type - * of the generic device types already defined in the libretro API. + * In order for a frontend to understand the workings of those devices, + * they must be defined as a specialized subclass of the generic device + * types already defined in the libretro API. * - * Which devices are supported can vary per input port. - * The core must pass an array of const struct retro_controller_info which - * is terminated with a blanked out struct. Each element of the struct - * corresponds to an ascending port index to - * retro_set_controller_port_device(). - * Even if special device types are set in the libretro core, + * The core must pass an array of const struct retro_controller_info which + * is terminated with a blanked out struct. Each element of the + * retro_controller_info struct corresponds to the ascending port index + * that is passed to retro_set_controller_port_device() when that function + * is called to indicate to the core that the frontend has changed the + * active device subclass. SEE ALSO: retro_set_controller_port_device() + * + * The ascending input port indexes provided by the core in the struct + * are generally presented by frontends as ascending User # or Player #, + * such as Player 1, Player 2, Player 3, etc. Which device subclasses are + * supported can vary per input port. + * + * The first inner element of each entry in the retro_controller_info array + * is a retro_controller_description struct that specifies the names and + * codes of all device subclasses that are available for the corresponding + * User or Player, beginning with the generic Libretro device that the + * subclasses are derived from. The second inner element of each entry is the + * total number of subclasses that are listed in the retro_controller_description. + * + * NOTE: Even if special device types are set in the libretro core, * libretro should only poll input based on the base input device types. */ #define RETRO_ENVIRONMENT_SET_MEMORY_MAPS (36 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const struct retro_memory_map * -- - * This environment call lets a libretro core tell the frontend + * This environment call lets a libretro core tell the frontend * about the memory maps this core emulates. * This can be used to implement, for example, cheats in a core-agnostic way. * - * Should only be used by emulators; it doesn't make much sense for + * Should only be used by emulators; it doesn't make much sense for * anything else. - * It is recommended to expose all relevant pointers through + * It is recommended to expose all relevant pointers through * retro_get_memory_* as well. * * Can be called from retro_init and retro_load_game. */ #define RETRO_ENVIRONMENT_SET_GEOMETRY 37 /* const struct retro_game_geometry * -- - * This environment call is similar to SET_SYSTEM_AV_INFO for changing - * video parameters, but provides a guarantee that drivers will not be + * This environment call is similar to SET_SYSTEM_AV_INFO for changing + * video parameters, but provides a guarantee that drivers will not be * reinitialized. * This can only be called from within retro_run(). * - * The purpose of this call is to allow a core to alter nominal - * width/heights as well as aspect ratios on-the-fly, which can be + * The purpose of this call is to allow a core to alter nominal + * width/heights as well as aspect ratios on-the-fly, which can be * useful for some emulators to change in run-time. * * max_width/max_height arguments are ignored and cannot be changed - * with this call as this could potentially require a reinitialization or a + * with this call as this could potentially require a reinitialization or a * non-constant time operation. * If max_width/max_height are to be changed, SET_SYSTEM_AV_INFO is required. * - * A frontend must guarantee that this environment call completes in + * A frontend must guarantee that this environment call completes in * constant time. */ -#define RETRO_ENVIRONMENT_GET_USERNAME 38 +#define RETRO_ENVIRONMENT_GET_USERNAME 38 /* const char ** * Returns the specified username of the frontend, if specified by the user. - * This username can be used as a nickname for a core that has online facilities + * This username can be used as a nickname for a core that has online facilities * or any other mode where personalization of the user is desirable. * The returned value can be NULL. - * If this environ callback is used by a core that requires a valid username, + * If this environ callback is used by a core that requires a valid username, * a default username should be specified by the core. */ #define RETRO_ENVIRONMENT_GET_LANGUAGE 39 @@ -920,20 +978,6 @@ enum retro_mod * A frontend must make sure that the pointer obtained from this function is * writeable (and readable). */ - -enum retro_hw_render_interface_type -{ - RETRO_HW_RENDER_INTERFACE_VULKAN = 0, - RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX -}; - -/* Base struct. All retro_hw_render_interface_* types - * contain at least these fields. */ -struct retro_hw_render_interface -{ - enum retro_hw_render_interface_type interface_type; - unsigned interface_version; -}; #define RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE (41 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const struct retro_hw_render_interface ** -- * Returns an API specific rendering interface for accessing API specific data. @@ -945,7 +989,6 @@ struct retro_hw_render_interface * Similarly, after context_destroyed callback returns, * the contents of the HW_RENDER_INTERFACE are invalidated. */ - #define RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS (42 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const bool * -- * If true, the libretro implementation supports achievements @@ -954,6 +997,483 @@ struct retro_hw_render_interface * * This must be called before the first call to retro_run. */ +#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const struct retro_hw_render_context_negotiation_interface * -- + * Sets an interface which lets the libretro core negotiate with frontend how a context is created. + * The semantics of this interface depends on which API is used in SET_HW_RENDER earlier. + * This interface will be used when the frontend is trying to create a HW rendering context, + * so it will be used after SET_HW_RENDER, but before the context_reset callback. + */ +#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 44 + /* uint64_t * -- + * Sets quirk flags associated with serialization. The frontend will zero any flags it doesn't + * recognize or support. Should be set in either retro_init or retro_load_game, but not both. + */ +#define RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT (44 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* N/A (null) * -- + * The frontend will try to use a 'shared' hardware context (mostly applicable + * to OpenGL) when a hardware context is being set up. + * + * Returns true if the frontend supports shared hardware contexts and false + * if the frontend does not support shared hardware contexts. + * + * This will do nothing on its own until SET_HW_RENDER env callbacks are + * being used. + */ +#define RETRO_ENVIRONMENT_GET_VFS_INTERFACE (45 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_vfs_interface_info * -- + * Gets access to the VFS interface. + * VFS presence needs to be queried prior to load_game or any + * get_system/save/other_directory being called to let front end know + * core supports VFS before it starts handing out paths. + * It is recomended to do so in retro_set_environment + */ +#define RETRO_ENVIRONMENT_GET_LED_INTERFACE (46 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_led_interface * -- + * Gets an interface which is used by a libretro core to set + * state of LEDs. + */ +#define RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE (47 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* int * -- + * Tells the core if the frontend wants audio or video. + * If disabled, the frontend will discard the audio or video, + * so the core may decide to skip generating a frame or generating audio. + * This is mainly used for increasing performance. + * Bit 0 (value 1): Enable Video + * Bit 1 (value 2): Enable Audio + * Bit 2 (value 4): Use Fast Savestates. + * Bit 3 (value 8): Hard Disable Audio + * Other bits are reserved for future use and will default to zero. + * If video is disabled: + * * The frontend wants the core to not generate any video, + * including presenting frames via hardware acceleration. + * * The frontend's video frame callback will do nothing. + * * After running the frame, the video output of the next frame should be + * no different than if video was enabled, and saving and loading state + * should have no issues. + * If audio is disabled: + * * The frontend wants the core to not generate any audio. + * * The frontend's audio callbacks will do nothing. + * * After running the frame, the audio output of the next frame should be + * no different than if audio was enabled, and saving and loading state + * should have no issues. + * Fast Savestates: + * * Guaranteed to be created by the same binary that will load them. + * * Will not be written to or read from the disk. + * * Suggest that the core assumes loading state will succeed. + * * Suggest that the core updates its memory buffers in-place if possible. + * * Suggest that the core skips clearing memory. + * * Suggest that the core skips resetting the system. + * * Suggest that the core may skip validation steps. + * Hard Disable Audio: + * * Used for a secondary core when running ahead. + * * Indicates that the frontend will never need audio from the core. + * * Suggests that the core may stop synthesizing audio, but this should not + * compromise emulation accuracy. + * * Audio output for the next frame does not matter, and the frontend will + * never need an accurate audio state in the future. + * * State will never be saved when using Hard Disable Audio. + */ +#define RETRO_ENVIRONMENT_GET_MIDI_INTERFACE (48 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_midi_interface ** -- + * Returns a MIDI interface that can be used for raw data I/O. + */ + +#define RETRO_ENVIRONMENT_GET_FASTFORWARDING (49 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Boolean value that indicates whether or not the frontend is in + * fastforwarding mode. + */ + +#define RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE (50 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* float * -- + * Float value that lets us know what target refresh rate + * is curently in use by the frontend. + * + * The core can use the returned value to set an ideal + * refresh rate/framerate. + */ + +#define RETRO_ENVIRONMENT_GET_INPUT_BITMASKS (51 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Boolean value that indicates whether or not the frontend supports + * input bitmasks being returned by retro_input_state_t. The advantage + * of this is that retro_input_state_t has to be only called once to + * grab all button states instead of multiple times. + * + * If it returns true, you can pass RETRO_DEVICE_ID_JOYPAD_MASK as 'id' + * to retro_input_state_t (make sure 'device' is set to RETRO_DEVICE_JOYPAD). + * It will return a bitmask of all the digital buttons. + */ + +#define RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION 52 + /* unsigned * -- + * Unsigned value is the API version number of the core options + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, core options are set by passing an array of + * retro_variable structs to RETRO_ENVIRONMENT_SET_VARIABLES. + * This may be still be done regardless of the core options + * interface version. + * + * If version is 1 however, core options may instead be set by + * passing an array of retro_core_option_definition structs to + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS, or a 2D array of + * retro_core_option_definition structs to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This allows the core to additionally set option sublabel information + * and/or provide localisation support. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53 + /* const struct retro_core_option_definition ** -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_ENHANCED_CORE_OPTIONS + * returns an API version of 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * 'data' points to an array of retro_core_option_definition structs + * terminated by a { NULL, NULL, NULL, {{0}}, NULL } element. + * retro_core_option_definition::key should be namespaced to not collide + * with other implementations' keys. e.g. A core called + * 'foo' should use keys named as 'foo_option'. + * retro_core_option_definition::desc should contain a human readable + * description of the key. + * retro_core_option_definition::info should contain any additional human + * readable information text that a typical user may need to + * understand the functionality of the option. + * retro_core_option_definition::values is an array of retro_core_option_value + * structs terminated by a { NULL, NULL } element. + * > retro_core_option_definition::values[index].value is an expected option + * value. + * > retro_core_option_definition::values[index].label is a human readable + * label used when displaying the value on screen. If NULL, + * the value itself is used. + * retro_core_option_definition::default_value is the default core option + * setting. It must match one of the expected option values in the + * retro_core_option_definition::values array. If it does not, or the + * default value is NULL, the first entry in the + * retro_core_option_definition::values array is treated as the default. + * + * The number of possible options should be very limited, + * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX. + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * First entry should be treated as a default. + * + * Example entry: + * { + * "foo_option", + * "Speed hack coprocessor X", + * "Provides increased performance at the expense of reduced accuracy", + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL 54 + /* const struct retro_core_options_intl * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_ENHANCED_CORE_OPTIONS + * returns an API version of 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_intl struct. + * + * retro_core_options_intl::us is a pointer to an array of + * retro_core_option_definition structs defining the US English + * core options implementation. It must point to a valid array. + * + * retro_core_options_intl::local is a pointer to an array of + * retro_core_option_definition structs defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_intl::us is used by the frontend). Any items + * missing from this array will be read from retro_core_options_intl::us + * instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_intl::us array. Any default values in + * retro_core_options_intl::local array will be ignored. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY 55 + /* struct retro_core_option_display * -- + * + * Allows an implementation to signal the environment to show + * or hide a variable when displaying core options. This is + * considered a *suggestion*. The frontend is free to ignore + * this callback, and its implementation not considered mandatory. + * + * 'data' points to a retro_core_option_display struct + * + * retro_core_option_display::key is a variable identifier + * which has already been set by SET_VARIABLES/SET_CORE_OPTIONS. + * + * retro_core_option_display::visible is a boolean, specifying + * whether variable should be displayed + * + * Note that all core option variables will be set visible by + * default when calling SET_VARIABLES/SET_CORE_OPTIONS. + */ + +/* VFS functionality */ + +/* File paths: + * File paths passed as parameters when using this API shall be well formed UNIX-style, + * using "/" (unquoted forward slash) as directory separator regardless of the platform's native separator. + * Paths shall also include at least one forward slash ("game.bin" is an invalid path, use "./game.bin" instead). + * Other than the directory separator, cores shall not make assumptions about path format: + * "C:/path/game.bin", "http://example.com/game.bin", "#game/game.bin", "./game.bin" (without quotes) are all valid paths. + * Cores may replace the basename or remove path components from the end, and/or add new components; + * however, cores shall not append "./", "../" or multiple consecutive forward slashes ("//") to paths they request to front end. + * The frontend is encouraged to make such paths work as well as it can, but is allowed to give up if the core alters paths too much. + * Frontends are encouraged, but not required, to support native file system paths (modulo replacing the directory separator, if applicable). + * Cores are allowed to try using them, but must remain functional if the front rejects such requests. + * Cores are encouraged to use the libretro-common filestream functions for file I/O, + * as they seamlessly integrate with VFS, deal with directory separator replacement as appropriate + * and provide platform-specific fallbacks in cases where front ends do not support VFS. */ + +/* Opaque file handle + * Introduced in VFS API v1 */ +struct retro_vfs_file_handle; + +/* Opaque directory handle + * Introduced in VFS API v3 */ +struct retro_vfs_dir_handle; + +/* File open flags + * Introduced in VFS API v1 */ +#define RETRO_VFS_FILE_ACCESS_READ (1 << 0) /* Read only mode */ +#define RETRO_VFS_FILE_ACCESS_WRITE (1 << 1) /* Write only mode, discard contents and overwrites existing file unless RETRO_VFS_FILE_ACCESS_UPDATE is also specified */ +#define RETRO_VFS_FILE_ACCESS_READ_WRITE (RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE) /* Read-write mode, discard contents and overwrites existing file unless RETRO_VFS_FILE_ACCESS_UPDATE is also specified*/ +#define RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING (1 << 2) /* Prevents discarding content of existing files opened for writing */ + +/* These are only hints. The frontend may choose to ignore them. Other than RAM/CPU/etc use, + and how they react to unlikely external interference (for example someone else writing to that file, + or the file's server going down), behavior will not change. */ +#define RETRO_VFS_FILE_ACCESS_HINT_NONE (0) +/* Indicate that the file will be accessed many times. The frontend should aggressively cache everything. */ +#define RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS (1 << 0) + +/* Seek positions */ +#define RETRO_VFS_SEEK_POSITION_START 0 +#define RETRO_VFS_SEEK_POSITION_CURRENT 1 +#define RETRO_VFS_SEEK_POSITION_END 2 + +/* stat() result flags + * Introduced in VFS API v3 */ +#define RETRO_VFS_STAT_IS_VALID (1 << 0) +#define RETRO_VFS_STAT_IS_DIRECTORY (1 << 1) +#define RETRO_VFS_STAT_IS_CHARACTER_SPECIAL (1 << 2) + +/* Get path from opaque handle. Returns the exact same path passed to file_open when getting the handle + * Introduced in VFS API v1 */ +typedef const char *(RETRO_CALLCONV *retro_vfs_get_path_t)(struct retro_vfs_file_handle *stream); + +/* Open a file for reading or writing. If path points to a directory, this will + * fail. Returns the opaque file handle, or NULL for error. + * Introduced in VFS API v1 */ +typedef struct retro_vfs_file_handle *(RETRO_CALLCONV *retro_vfs_open_t)(const char *path, unsigned mode, unsigned hints); + +/* Close the file and release its resources. Must be called if open_file returns non-NULL. Returns 0 on success, -1 on failure. + * Whether the call succeeds ot not, the handle passed as parameter becomes invalid and should no longer be used. + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_close_t)(struct retro_vfs_file_handle *stream); + +/* Return the size of the file in bytes, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_size_t)(struct retro_vfs_file_handle *stream); + +/* Truncate file to specified size. Returns 0 on success or -1 on error + * Introduced in VFS API v2 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_truncate_t)(struct retro_vfs_file_handle *stream, int64_t length); + +/* Get the current read / write position for the file. Returns -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_tell_t)(struct retro_vfs_file_handle *stream); + +/* Set the current read/write position for the file. Returns the new position, -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_seek_t)(struct retro_vfs_file_handle *stream, int64_t offset, int seek_position); + +/* Read data from a file. Returns the number of bytes read, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_read_t)(struct retro_vfs_file_handle *stream, void *s, uint64_t len); + +/* Write data to a file. Returns the number of bytes written, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_write_t)(struct retro_vfs_file_handle *stream, const void *s, uint64_t len); + +/* Flush pending writes to file, if using buffered IO. Returns 0 on sucess, or -1 on failure. + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_flush_t)(struct retro_vfs_file_handle *stream); + +/* Delete the specified file. Returns 0 on success, -1 on failure + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_remove_t)(const char *path); + +/* Rename the specified file. Returns 0 on success, -1 on failure + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_rename_t)(const char *old_path, const char *new_path); + +/* Stat the specified file. Retruns a bitmask of RETRO_VFS_STAT_* flags, none are set if path was not valid. + * Additionally stores file size in given variable, unless NULL is given. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_stat_t)(const char *path, int32_t *size); + +/* Create the specified directory. Returns 0 on success, -1 on unknown failure, -2 if already exists. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_mkdir_t)(const char *dir); + +/* Open the specified directory for listing. Returns the opaque dir handle, or NULL for error. + * Support for the include_hidden argument may vary depending on the platform. + * Introduced in VFS API v3 */ +typedef struct retro_vfs_dir_handle *(RETRO_CALLCONV *retro_vfs_opendir_t)(const char *dir, bool include_hidden); + +/* Read the directory entry at the current position, and move the read pointer to the next position. + * Returns true on success, false if already on the last entry. + * Introduced in VFS API v3 */ +typedef bool (RETRO_CALLCONV *retro_vfs_readdir_t)(struct retro_vfs_dir_handle *dirstream); + +/* Get the name of the last entry read. Returns a string on success, or NULL for error. + * The returned string pointer is valid until the next call to readdir or closedir. + * Introduced in VFS API v3 */ +typedef const char *(RETRO_CALLCONV *retro_vfs_dirent_get_name_t)(struct retro_vfs_dir_handle *dirstream); + +/* Check if the last entry read was a directory. Returns true if it was, false otherwise (or on error). + * Introduced in VFS API v3 */ +typedef bool (RETRO_CALLCONV *retro_vfs_dirent_is_dir_t)(struct retro_vfs_dir_handle *dirstream); + +/* Close the directory and release its resources. Must be called if opendir returns non-NULL. Returns 0 on success, -1 on failure. + * Whether the call succeeds ot not, the handle passed as parameter becomes invalid and should no longer be used. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_closedir_t)(struct retro_vfs_dir_handle *dirstream); + +struct retro_vfs_interface +{ + /* VFS API v1 */ + retro_vfs_get_path_t get_path; + retro_vfs_open_t open; + retro_vfs_close_t close; + retro_vfs_size_t size; + retro_vfs_tell_t tell; + retro_vfs_seek_t seek; + retro_vfs_read_t read; + retro_vfs_write_t write; + retro_vfs_flush_t flush; + retro_vfs_remove_t remove; + retro_vfs_rename_t rename; + /* VFS API v2 */ + retro_vfs_truncate_t truncate; + /* VFS API v3 */ + retro_vfs_stat_t stat; + retro_vfs_mkdir_t mkdir; + retro_vfs_opendir_t opendir; + retro_vfs_readdir_t readdir; + retro_vfs_dirent_get_name_t dirent_get_name; + retro_vfs_dirent_is_dir_t dirent_is_dir; + retro_vfs_closedir_t closedir; +}; + +struct retro_vfs_interface_info +{ + /* Set by core: should this be higher than the version the front end supports, + * front end will return false in the RETRO_ENVIRONMENT_GET_VFS_INTERFACE call + * Introduced in VFS API v1 */ + uint32_t required_interface_version; + + /* Frontend writes interface pointer here. The frontend also sets the actual + * version, must be at least required_interface_version. + * Introduced in VFS API v1 */ + struct retro_vfs_interface *iface; +}; + +enum retro_hw_render_interface_type +{ + RETRO_HW_RENDER_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_INTERFACE_D3D9 = 1, + RETRO_HW_RENDER_INTERFACE_D3D10 = 2, + RETRO_HW_RENDER_INTERFACE_D3D11 = 3, + RETRO_HW_RENDER_INTERFACE_D3D12 = 4, + RETRO_HW_RENDER_INTERFACE_GSKIT_PS2 = 5, + RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX +}; + +/* Base struct. All retro_hw_render_interface_* types + * contain at least these fields. */ +struct retro_hw_render_interface +{ + enum retro_hw_render_interface_type interface_type; + unsigned interface_version; +}; + +typedef void (RETRO_CALLCONV *retro_set_led_state_t)(int led, int state); +struct retro_led_interface +{ + retro_set_led_state_t set_led_state; +}; + +/* Retrieves the current state of the MIDI input. + * Returns true if it's enabled, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_input_enabled_t)(void); + +/* Retrieves the current state of the MIDI output. + * Returns true if it's enabled, false otherwise */ +typedef bool (RETRO_CALLCONV *retro_midi_output_enabled_t)(void); + +/* Reads next byte from the input stream. + * Returns true if byte is read, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_read_t)(uint8_t *byte); + +/* Writes byte to the output stream. + * 'delta_time' is in microseconds and represent time elapsed since previous write. + * Returns true if byte is written, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_write_t)(uint8_t byte, uint32_t delta_time); + +/* Flushes previously written data. + * Returns true if successful, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_flush_t)(void); + +struct retro_midi_interface +{ + retro_midi_input_enabled_t input_enabled; + retro_midi_output_enabled_t output_enabled; + retro_midi_read_t read; + retro_midi_write_t write; + retro_midi_flush_t flush; +}; enum retro_hw_render_context_negotiation_interface_type { @@ -968,77 +1488,97 @@ struct retro_hw_render_context_negotiation_interface enum retro_hw_render_context_negotiation_interface_type interface_type; unsigned interface_version; }; -#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL) - /* const struct retro_hw_render_context_negotiation_interface * -- - * Sets an interface which lets the libretro core negotiate with frontend how a context is created. - * The semantics of this interface depends on which API is used in SET_HW_RENDER earlier. - * This interface will be used when the frontend is trying to create a HW rendering context, - * so it will be used after SET_HW_RENDER, but before the context_reset callback. - */ -#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */ -#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */ -#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */ -#define RETRO_MEMDESC_ALIGN_4 (2 << 16) -#define RETRO_MEMDESC_ALIGN_8 (3 << 16) -#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) /* All memory in this region is accessed at least 2 bytes at the time. */ -#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) -#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) +/* Serialized state is incomplete in some way. Set if serialization is + * usable in typical end-user cases but should not be relied upon to + * implement frame-sensitive frontend features such as netplay or + * rerecording. */ +#define RETRO_SERIALIZATION_QUIRK_INCOMPLETE (1 << 0) +/* The core must spend some time initializing before serialization is + * supported. retro_serialize() will initially fail; retro_unserialize() + * and retro_serialize_size() may or may not work correctly either. */ +#define RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE (1 << 1) +/* Serialization size may change within a session. */ +#define RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE (1 << 2) +/* Set by the frontend to acknowledge that it supports variable-sized + * states. */ +#define RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE (1 << 3) +/* Serialized state can only be loaded during the same session. */ +#define RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION (1 << 4) +/* Serialized state cannot be loaded on an architecture with a different + * endianness from the one it was saved on. */ +#define RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT (1 << 5) +/* Serialized state cannot be loaded on a different platform from the one it + * was saved on for reasons other than endianness, such as word size + * dependence */ +#define RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT (1 << 6) + +#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */ +#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */ +#define RETRO_MEMDESC_SYSTEM_RAM (1 << 2) /* The memory area is system RAM. This is main RAM of the gaming system. */ +#define RETRO_MEMDESC_SAVE_RAM (1 << 3) /* The memory area is save RAM. This RAM is usually found on a game cartridge, backed up by a battery. */ +#define RETRO_MEMDESC_VIDEO_RAM (1 << 4) /* The memory area is video RAM (VRAM) */ +#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */ +#define RETRO_MEMDESC_ALIGN_4 (2 << 16) +#define RETRO_MEMDESC_ALIGN_8 (3 << 16) +#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) /* All memory in this region is accessed at least 2 bytes at the time. */ +#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) +#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) struct retro_memory_descriptor { uint64_t flags; /* Pointer to the start of the relevant ROM or RAM chip. - * It's strongly recommended to use 'offset' if possible, rather than + * It's strongly recommended to use 'offset' if possible, rather than * doing math on the pointer. * - * If the same byte is mapped my multiple descriptors, their descriptors + * If the same byte is mapped my multiple descriptors, their descriptors * must have the same pointer. - * If 'start' does not point to the first byte in the pointer, put the + * If 'start' does not point to the first byte in the pointer, put the * difference in 'offset' instead. * - * May be NULL if there's nothing usable here (e.g. hardware registers and + * May be NULL if there's nothing usable here (e.g. hardware registers and * open bus). No flags should be set if the pointer is NULL. * It's recommended to minimize the number of descriptors if possible, * but not mandatory. */ void *ptr; size_t offset; - /* This is the location in the emulated address space + /* This is the location in the emulated address space * where the mapping starts. */ size_t start; /* Which bits must be same as in 'start' for this mapping to apply. - * The first memory descriptor to claim a certain byte is the one + * The first memory descriptor to claim a certain byte is the one * that applies. * A bit which is set in 'start' must also be set in this. - * Can be zero, in which case each byte is assumed mapped exactly once. + * Can be zero, in which case each byte is assumed mapped exactly once. * In this case, 'len' must be a power of two. */ size_t select; - /* If this is nonzero, the set bits are assumed not connected to the + /* If this is nonzero, the set bits are assumed not connected to the * memory chip's address pins. */ size_t disconnect; /* This one tells the size of the current memory area. - * If, after start+disconnect are applied, the address is higher than + * If, after start+disconnect are applied, the address is higher than * this, the highest bit of the address is cleared. * * If the address is still too high, the next highest bit is cleared. - * Can be zero, in which case it's assumed to be infinite (as limited + * Can be zero, in which case it's assumed to be infinite (as limited * by 'select' and 'disconnect'). */ size_t len; - /* To go from emulated address to physical address, the following + /* To go from emulated address to physical address, the following * order applies: * Subtract 'start', pick off 'disconnect', apply 'len', add 'offset'. */ - /* The address space name must consist of only a-zA-Z0-9_-, + /* The address space name must consist of only a-zA-Z0-9_-, * should be as short as feasible (maximum length is 8 plus the NUL), - * and may not be any other address space plus one or more 0-9A-F + * and may not be any other address space plus one or more 0-9A-F * at the end. - * However, multiple memory descriptors for the same address space is - * allowed, and the address space name can be empty. NULL is treated + * However, multiple memory descriptors for the same address space is + * allowed, and the address space name can be empty. NULL is treated * as empty. * * Address space names are case sensitive, but avoid lowercase if possible. @@ -1052,11 +1592,11 @@ struct retro_memory_descriptor * 'a'+blank - valid ('a' is not in 0-9A-F) * 'a'+'A' - valid (neither is a prefix of each other) * 'AR'+blank - valid ('R' is not in 0-9A-F) - * 'ARB'+blank - valid (the B can't be part of the address either, because + * 'ARB'+blank - valid (the B can't be part of the address either, because * there is no namespace 'AR') - * blank+'B' - not valid, because it's ambigous which address space B1234 + * blank+'B' - not valid, because it's ambigous which address space B1234 * would refer to. - * The length can't be used for that purpose; the frontend may want + * The length can't be used for that purpose; the frontend may want * to append arbitrary data to an address, without a separator. */ const char *addrspace; @@ -1078,32 +1618,32 @@ struct retro_memory_descriptor * the most recent addition and continue on the next bit. * TODO: Can the above be optimized? Is "remove the lowest bit set in both * pointer and 'len'" equivalent? */ - + /* TODO: Some emulators (MAME?) emulate big endian systems by only accessing * the emulated memory in 32-bit chunks, native endian. But that's nothing * compared to Darek Mihocka * (section Emulation 103 - Nearly Free Byte Reversal) - he flips the ENTIRE * RAM backwards! I'll want to represent both of those, via some flags. - * + * * I suspect MAME either didn't think of that idea, or don't want the #ifdef. * Not sure which, nor do I really care. */ - + /* TODO: Some of those flags are unused and/or don't really make sense. Clean * them up. */ }; -/* The frontend may use the largest value of 'start'+'select' in a +/* The frontend may use the largest value of 'start'+'select' in a * certain namespace to infer the size of the address space. * - * If the address space is larger than that, a mapping with .ptr=NULL - * should be at the end of the array, with .select set to all ones for + * If the address space is larger than that, a mapping with .ptr=NULL + * should be at the end of the array, with .select set to all ones for * as long as the address space is big. * * Sample descriptors (minus .ptr, and RETRO_MEMFLAG_ on the flags): * SNES WRAM: * .start=0x7E0000, .len=0x20000 - * (Note that this must be mapped before the ROM in most cases; some of the - * ROM mappers + * (Note that this must be mapped before the ROM in most cases; some of the + * ROM mappers * try to claim $7E0000, or at least $7E8000.) * SNES SPC700 RAM: * .addrspace="S", .len=0x10000 @@ -1112,7 +1652,7 @@ struct retro_memory_descriptor * .flags=MIRROR, .start=0x800000, .select=0xC0E000, .len=0x2000 * SNES WRAM mirrors, alternate equivalent descriptor: * .flags=MIRROR, .select=0x40E000, .disconnect=~0x1FFF - * (Various similar constructions can be created by combining parts of + * (Various similar constructions can be created by combining parts of * the above two.) * SNES LoROM (512KB, mirrored a couple of times): * .flags=CONST, .start=0x008000, .select=0x408000, .disconnect=0x8000, .len=512*1024 @@ -1138,13 +1678,13 @@ struct retro_memory_map struct retro_controller_description { - /* Human-readable description of the controller. Even if using a generic - * input device type, this can be set to the particular device type the + /* Human-readable description of the controller. Even if using a generic + * input device type, this can be set to the particular device type the * core uses. */ const char *desc; - /* Device type passed to retro_set_controller_port_device(). If the device - * type is a sub-class of a generic input device type, use the + /* Device type passed to retro_set_controller_port_device(). If the device + * type is a sub-class of a generic input device type, use the * RETRO_DEVICE_SUBCLASS macro to create an ID. * * E.g. RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1). */ @@ -1162,8 +1702,8 @@ struct retro_subsystem_memory_info /* The extension associated with a memory type, e.g. "psram". */ const char *extension; - /* The memory type for retro_get_memory(). This should be at - * least 0x100 to avoid conflict with standardized + /* The memory type for retro_get_memory(). This should be at + * least 0x100 to avoid conflict with standardized * libretro memory types. */ unsigned type; }; @@ -1182,11 +1722,11 @@ struct retro_subsystem_rom_info /* Same definition as retro_get_system_info(). */ bool block_extract; - /* This is set if the content is required to load a game. + /* This is set if the content is required to load a game. * If this is set to false, a zeroed-out retro_game_info can be passed. */ bool required; - /* Content can have multiple associated persistent + /* Content can have multiple associated persistent * memory types (retro_get_memory()). */ const struct retro_subsystem_memory_info *memory; unsigned num_memory; @@ -1204,17 +1744,17 @@ struct retro_subsystem_info */ const char *ident; - /* Infos for each content file. The first entry is assumed to be the + /* Infos for each content file. The first entry is assumed to be the * "most significant" content for frontend purposes. - * E.g. with Super GameBoy, the first content should be the GameBoy ROM, + * E.g. with Super GameBoy, the first content should be the GameBoy ROM, * as it is the most "significant" content to a user. - * If a frontend creates new file paths based on the content used + * If a frontend creates new file paths based on the content used * (e.g. savestates), it should use the path for the first ROM to do so. */ const struct retro_subsystem_rom_info *roms; /* Number of content files associated with a subsystem. */ unsigned num_roms; - + /* The type passed to retro_load_game_special(). */ unsigned id; }; @@ -1225,13 +1765,13 @@ typedef void (RETRO_CALLCONV *retro_proc_address_t)(void); * (None here so far). * * Get a symbol from a libretro core. - * Cores should only return symbols which are actual + * Cores should only return symbols which are actual * extensions to the libretro API. * - * Frontends should not use this to obtain symbols to standard + * Frontends should not use this to obtain symbols to standard * libretro entry points (static linking or dlsym). * - * The symbol name must be equal to the function name, + * The symbol name must be equal to the function name, * e.g. if void retro_foo(void); exists, the symbol must be called "retro_foo". * The returned function pointer must be cast to the corresponding type. */ @@ -1285,6 +1825,7 @@ struct retro_log_callback #define RETRO_SIMD_POPCNT (1 << 18) #define RETRO_SIMD_MOVBE (1 << 19) #define RETRO_SIMD_CMOV (1 << 20) +#define RETRO_SIMD_ASIMD (1 << 21) typedef uint64_t retro_perf_tick_t; typedef int64_t retro_time_t; @@ -1305,7 +1846,7 @@ struct retro_perf_counter typedef retro_time_t (RETRO_CALLCONV *retro_perf_get_time_usec_t)(void); /* A simple counter. Usually nanoseconds, but can also be CPU cycles. - * Can be used directly if desired (when creating a more sophisticated + * Can be used directly if desired (when creating a more sophisticated * performance counter system). * */ typedef retro_perf_tick_t (RETRO_CALLCONV *retro_perf_get_counter_t)(void); @@ -1319,9 +1860,9 @@ typedef uint64_t (RETRO_CALLCONV *retro_get_cpu_features_t)(void); typedef void (RETRO_CALLCONV *retro_perf_log_t)(void); /* Register a performance counter. - * ident field must be set with a discrete value and other values in + * ident field must be set with a discrete value and other values in * retro_perf_counter must be 0. - * Registering can be called multiple times. To avoid calling to + * Registering can be called multiple times. To avoid calling to * frontend redundantly, you can check registered field first. */ typedef void (RETRO_CALLCONV *retro_perf_register_t)(struct retro_perf_counter *counter); @@ -1392,7 +1933,7 @@ enum retro_sensor_action #define RETRO_SENSOR_ACCELEROMETER_Y 1 #define RETRO_SENSOR_ACCELEROMETER_Z 2 -typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, +typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, enum retro_sensor_action action, unsigned rate); typedef float (RETRO_CALLCONV *retro_sensor_get_input_t)(unsigned port, unsigned id); @@ -1417,7 +1958,7 @@ typedef bool (RETRO_CALLCONV *retro_camera_start_t)(void); /* Stops the camera driver. Can only be called in retro_run(). */ typedef void (RETRO_CALLCONV *retro_camera_stop_t)(void); -/* Callback which signals when the camera driver is initialized +/* Callback which signals when the camera driver is initialized * and/or deinitialized. * retro_camera_start_t can be called in initialized callback. */ @@ -1427,36 +1968,36 @@ typedef void (RETRO_CALLCONV *retro_camera_lifetime_status_t)(void); * Width, height and pitch are similar to retro_video_refresh_t. * First pixel is top-left origin. */ -typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, +typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, unsigned width, unsigned height, size_t pitch); /* A callback for when OpenGL textures are used. * * texture_id is a texture owned by camera driver. - * Its state or content should be considered immutable, except for things like + * Its state or content should be considered immutable, except for things like * texture filtering and clamping. * * texture_target is the texture target for the GL texture. - * These can include e.g. GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE, and possibly + * These can include e.g. GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE, and possibly * more depending on extensions. * - * affine points to a packed 3x3 column-major matrix used to apply an affine + * affine points to a packed 3x3 column-major matrix used to apply an affine * transform to texture coordinates. (affine_matrix * vec3(coord_x, coord_y, 1.0)) - * After transform, normalized texture coord (0, 0) should be bottom-left + * After transform, normalized texture coord (0, 0) should be bottom-left * and (1, 1) should be top-right (or (width, height) for RECTANGLE). * - * GL-specific typedefs are avoided here to avoid relying on gl.h in + * GL-specific typedefs are avoided here to avoid relying on gl.h in * the API definition. */ -typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id, +typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id, unsigned texture_target, const float *affine); struct retro_camera_callback { - /* Set by libretro core. + /* Set by libretro core. * Example bitmask: caps = (1 << RETRO_CAMERA_BUFFER_OPENGL_TEXTURE) | (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER). */ - uint64_t caps; + uint64_t caps; /* Desired resolution for camera. Is only used as a hint. */ unsigned width; @@ -1470,22 +2011,22 @@ struct retro_camera_callback retro_camera_frame_raw_framebuffer_t frame_raw_framebuffer; /* Set by libretro core if OpenGL texture callbacks will be used. */ - retro_camera_frame_opengl_texture_t frame_opengl_texture; + retro_camera_frame_opengl_texture_t frame_opengl_texture; - /* Set by libretro core. Called after camera driver is initialized and + /* Set by libretro core. Called after camera driver is initialized and * ready to be started. * Can be NULL, in which this callback is not called. */ retro_camera_lifetime_status_t initialized; - /* Set by libretro core. Called right before camera driver is + /* Set by libretro core. Called right before camera driver is * deinitialized. * Can be NULL, in which this callback is not called. */ retro_camera_lifetime_status_t deinitialized; }; -/* Sets the interval of time and/or distance at which to update/poll +/* Sets the interval of time and/or distance at which to update/poll * location-based data. * * To ensure compatibility with all location-based implementations, @@ -1498,20 +2039,20 @@ typedef void (RETRO_CALLCONV *retro_location_set_interval_t)(unsigned interval_m unsigned interval_distance); /* Start location services. The device will start listening for changes to the - * current location at regular intervals (which are defined with + * current location at regular intervals (which are defined with * retro_location_set_interval_t). */ typedef bool (RETRO_CALLCONV *retro_location_start_t)(void); -/* Stop location services. The device will stop listening for changes +/* Stop location services. The device will stop listening for changes * to the current location. */ typedef void (RETRO_CALLCONV *retro_location_stop_t)(void); -/* Get the position of the current location. Will set parameters to +/* Get the position of the current location. Will set parameters to * 0 if no new location update has happened since the last time. */ typedef bool (RETRO_CALLCONV *retro_location_get_position_t)(double *lat, double *lon, double *horiz_accuracy, double *vert_accuracy); -/* Callback which signals when the location driver is initialized +/* Callback which signals when the location driver is initialized * and/or deinitialized. * retro_location_start_t can be called in initialized callback. */ @@ -1536,14 +2077,14 @@ enum retro_rumble_effect RETRO_RUMBLE_DUMMY = INT_MAX }; -/* Sets rumble state for joypad plugged in port 'port'. +/* Sets rumble state for joypad plugged in port 'port'. * Rumble effects are controlled independently, * and setting e.g. strong rumble does not override weak rumble. * Strength has a range of [0, 0xffff]. * - * Returns true if rumble state request was honored. + * Returns true if rumble state request was honored. * Calling this before first retro_run() is likely to return false. */ -typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, +typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, enum retro_rumble_effect effect, uint16_t strength); struct retro_rumble_interface @@ -1554,10 +2095,10 @@ struct retro_rumble_interface /* Notifies libretro that audio data should be written. */ typedef void (RETRO_CALLCONV *retro_audio_callback_t)(void); -/* True: Audio driver in frontend is active, and callback is +/* True: Audio driver in frontend is active, and callback is * expected to be called regularily. - * False: Audio driver in frontend is paused or inactive. - * Audio callback will not be called until set_state has been + * False: Audio driver in frontend is paused or inactive. + * Audio callback will not be called until set_state has been * called with true. * Initial state is false (inactive). */ @@ -1569,11 +2110,11 @@ struct retro_audio_callback retro_audio_set_state_callback_t set_state; }; -/* Notifies a libretro core of time spent since last invocation +/* Notifies a libretro core of time spent since last invocation * of retro_run() in microseconds. * * It will be called right before retro_run() every frame. - * The frontend can tamper with timing to support cases like + * The frontend can tamper with timing to support cases like * fast-forward, slow-motion and framestepping. * * In those scenarios the reference frame time value will be used. */ @@ -1582,8 +2123,8 @@ typedef void (RETRO_CALLCONV *retro_frame_time_callback_t)(retro_usec_t usec); struct retro_frame_time_callback { retro_frame_time_callback_t callback; - /* Represents the time of one frame. It is computed as - * 1000000 / fps, but the implementation will resolve the + /* Represents the time of one frame. It is computed as + * 1000000 / fps, but the implementation will resolve the * rounding to ensure that framestepping, etc is exact. */ retro_usec_t reference; }; @@ -1599,7 +2140,7 @@ struct retro_frame_time_callback * it should implement context_destroy callback. * If called, all GPU resources must be reinitialized. * Usually called when frontend reinits video driver. - * Also called first time video driver is initialized, + * Also called first time video driver is initialized, * allowing libretro core to initialize resources. */ typedef void (RETRO_CALLCONV *retro_hw_context_reset_t)(void); @@ -1616,7 +2157,7 @@ enum retro_hw_context_type { RETRO_HW_CONTEXT_NONE = 0, /* OpenGL 2.x. Driver can choose to use latest compatibility context. */ - RETRO_HW_CONTEXT_OPENGL = 1, + RETRO_HW_CONTEXT_OPENGL = 1, /* OpenGL ES 2.0. */ RETRO_HW_CONTEXT_OPENGLES2 = 2, /* Modern desktop core GL context. Use version_major/ @@ -1631,6 +2172,10 @@ enum retro_hw_context_type /* Vulkan, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE. */ RETRO_HW_CONTEXT_VULKAN = 6, + /* Direct3D, set version_major to select the type of interface + * returned by RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_DIRECT3D = 7, + RETRO_HW_CONTEXT_DUMMY = INT_MAX }; @@ -1642,10 +2187,10 @@ struct retro_hw_render_callback /* Called when a context has been created or when it has been reset. * An OpenGL context is only valid after context_reset() has been called. * - * When context_reset is called, OpenGL resources in the libretro + * When context_reset is called, OpenGL resources in the libretro * implementation are guaranteed to be invalid. * - * It is possible that context_reset is called multiple times during an + * It is possible that context_reset is called multiple times during an * application lifecycle. * If context_reset is called without any notification (context_destroy), * the OpenGL context was lost and resources should just be recreated @@ -1658,7 +2203,8 @@ struct retro_hw_render_callback * be providing preallocated framebuffers. */ retro_hw_get_current_framebuffer_t get_current_framebuffer; - /* Set by frontend. */ + /* Set by frontend. + * Can return all relevant functions, including glClear on Windows. */ retro_hw_get_proc_address_t get_proc_address; /* Set if render buffers should have depth component attached. @@ -1669,48 +2215,48 @@ struct retro_hw_render_callback * TODO: Obsolete. */ bool stencil; - /* If depth and stencil are true, a packed 24/8 buffer will be added. + /* If depth and stencil are true, a packed 24/8 buffer will be added. * Only attaching stencil is invalid and will be ignored. */ - /* Use conventional bottom-left origin convention. If false, + /* Use conventional bottom-left origin convention. If false, * standard libretro top-left origin semantics are used. * TODO: Move to GL specific interface. */ bool bottom_left_origin; - + /* Major version number for core GL context or GLES 3.1+. */ unsigned version_major; /* Minor version number for core GL context or GLES 3.1+. */ unsigned version_minor; - /* If this is true, the frontend will go very far to avoid + /* If this is true, the frontend will go very far to avoid * resetting context in scenarios like toggling fullscreen, etc. * TODO: Obsolete? Maybe frontend should just always assume this ... */ bool cache_context; - /* The reset callback might still be called in extreme situations + /* The reset callback might still be called in extreme situations * such as if the context is lost beyond recovery. * - * For optimal stability, set this to false, and allow context to be + * For optimal stability, set this to false, and allow context to be * reset at any time. */ - - /* A callback to be called before the context is destroyed in a + + /* A callback to be called before the context is destroyed in a * controlled way by the frontend. */ retro_hw_context_reset_t context_destroy; /* OpenGL resources can be deinitialized cleanly at this step. - * context_destroy can be set to NULL, in which resources will + * context_destroy can be set to NULL, in which resources will * just be destroyed without any notification. * - * Even when context_destroy is non-NULL, it is possible that + * Even when context_destroy is non-NULL, it is possible that * context_reset is called without any destroy notification. - * This happens if context is lost by external factors (such as + * This happens if context is lost by external factors (such as * notified by GL_ARB_robustness). * * In this case, the context is assumed to be already dead, - * and the libretro implementation must not try to free any OpenGL + * and the libretro implementation must not try to free any OpenGL * resources in the subsequent context_reset. */ @@ -1718,7 +2264,7 @@ struct retro_hw_render_callback bool debug_context; }; -/* Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. +/* Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. * Called by the frontend in response to keyboard events. * down is set if the key is being pressed, or false if it is being released. * keycode is the RETROK value of the char. @@ -1726,16 +2272,16 @@ struct retro_hw_render_callback * key_modifiers is a set of RETROKMOD values or'ed together. * * The pressed/keycode state can be indepedent of the character. - * It is also possible that multiple characters are generated from a + * It is also possible that multiple characters are generated from a * single keypress. * Keycode events should be treated separately from character events. * However, when possible, the frontend should try to synchronize these. * If only a character is posted, keycode should be RETROK_UNKNOWN. * - * Similarily if only a keycode event is generated with no corresponding + * Similarily if only a keycode event is generated with no corresponding * character, character should be 0. */ -typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, +typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); struct retro_keyboard_callback @@ -1744,15 +2290,15 @@ struct retro_keyboard_callback }; /* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. - * Should be set for implementations which can swap out multiple disk + * Should be set for implementations which can swap out multiple disk * images in runtime. * * If the implementation can do this automatically, it should strive to do so. * However, there are cases where the user must manually do so. * - * Overview: To swap a disk image, eject the disk image with + * Overview: To swap a disk image, eject the disk image with * set_eject_state(true). - * Set the disk index with set_image_index(index). Insert the disk again + * Set the disk index with set_image_index(index). Insert the disk again * with set_eject_state(false). */ @@ -1770,7 +2316,7 @@ typedef bool (RETRO_CALLCONV *retro_get_eject_state_t)(void); typedef unsigned (RETRO_CALLCONV *retro_get_image_index_t)(void); /* Sets image index. Can only be called when disk is ejected. - * The implementation supports setting "no disk" by using an + * The implementation supports setting "no disk" by using an * index >= get_num_images(). */ typedef bool (RETRO_CALLCONV *retro_set_image_index_t)(unsigned index); @@ -1784,11 +2330,11 @@ struct retro_game_info; * Arguments to pass in info have same requirements as retro_load_game(). * Virtual disk tray must be ejected when calling this. * - * Replacing a disk image with info = NULL will remove the disk image + * Replacing a disk image with info = NULL will remove the disk image * from the internal list. * As a result, calls to get_image_index() can change. * - * E.g. replace_image_index(1, NULL), and previous get_image_index() + * E.g. replace_image_index(1, NULL), and previous get_image_index() * returned 4 before. * Index 1 will be removed, and the new index is 3. */ @@ -1797,7 +2343,7 @@ typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, /* Adds a new valid index (get_num_images()) to the internal disk list. * This will increment subsequent return values from get_num_images() by 1. - * This image index cannot be used until a disk image has been set + * This image index cannot be used until a disk image has been set * with replace_image_index. */ typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void); @@ -1828,7 +2374,7 @@ enum retro_pixel_format /* RGB565, native endian. * This pixel format is the recommended format to use if a 15/16-bit - * format is desired as it is the pixel format that is typically + * format is desired as it is the pixel format that is typically * available on a wide range of low-power devices. * * It is also natively supported in APIs like OpenGL ES. */ @@ -1858,43 +2404,52 @@ struct retro_input_descriptor /* Human readable description for parameters. * The pointer must remain valid until * retro_unload_game() is called. */ - const char *description; + const char *description; }; struct retro_system_info { - /* All pointers are owned by libretro implementation, and pointers must + /* All pointers are owned by libretro implementation, and pointers must * remain valid until retro_deinit() is called. */ - const char *library_name; /* Descriptive name of library. Should not + const char *library_name; /* Descriptive name of library. Should not * contain any version numbers, etc. */ const char *library_version; /* Descriptive version of core. */ - const char *valid_extensions; /* A string listing probably content - * extensions the core will be able to + const char *valid_extensions; /* A string listing probably content + * extensions the core will be able to * load, separated with pipe. * I.e. "bin|rom|iso". - * Typically used for a GUI to filter + * Typically used for a GUI to filter * out extensions. */ - /* If true, retro_load_game() is guaranteed to provide a valid pathname - * in retro_game_info::path. - * ::data and ::size are both invalid. + /* Libretro cores that need to have direct access to their content + * files, including cores which use the path of the content files to + * determine the paths of other files, should set need_fullpath to true. * - * If false, ::data and ::size are guaranteed to be valid, but ::path - * might not be valid. + * Cores should strive for setting need_fullpath to false, + * as it allows the frontend to perform patching, etc. * - * This is typically set to true for libretro implementations that must - * load from file. - * Implementations should strive for setting this to false, as it allows - * the frontend to perform patching, etc. */ - bool need_fullpath; + * If need_fullpath is true and retro_load_game() is called: + * - retro_game_info::path is guaranteed to have a valid path + * - retro_game_info::data and retro_game_info::size are invalid + * + * If need_fullpath is false and retro_load_game() is called: + * - retro_game_info::path may be NULL + * - retro_game_info::data and retro_game_info::size are guaranteed + * to be valid + * + * See also: + * - RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY + * - RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY + */ + bool need_fullpath; - /* If true, the frontend is not allowed to extract any archives before + /* If true, the frontend is not allowed to extract any archives before * loading the real content. - * Necessary for certain libretro implementations that load games + * Necessary for certain libretro implementations that load games * from zipped archives. */ - bool block_extract; + bool block_extract; }; struct retro_game_geometry @@ -1926,27 +2481,87 @@ struct retro_system_av_info struct retro_variable { /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. - * If NULL, obtains the complete environment string if more + * If NULL, obtains the complete environment string if more * complex parsing is necessary. - * The environment string is formatted as key-value pairs + * The environment string is formatted as key-value pairs * delimited by semicolons as so: * "key1=value1;key2=value2;..." */ const char *key; - + /* Value to be obtained. If key does not exist, it is set to NULL. */ const char *value; }; +struct retro_core_option_display +{ + /* Variable to configure in RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY */ + const char *key; + + /* Specifies whether variable should be displayed + * when presenting core options to the user */ + bool visible; +}; + +/* Maximum number of values permitted for a core option + * NOTE: This may be increased on a core-by-core basis + * if required (doing so has no effect on the frontend) */ +#define RETRO_NUM_CORE_OPTION_VALUES_MAX 128 + +struct retro_core_option_value +{ + /* Expected option value */ + const char *value; + + /* Human-readable value label. If NULL, value itself + * will be displayed by the frontend */ + const char *label; +}; + +struct retro_core_option_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. */ + const char *key; + + /* Human-readable core option description (used as menu label) */ + const char *desc; + + /* Human-readable core option information (used as menu sublabel) */ + const char *info; + + /* Array of retro_core_option_value structs, terminated by NULL */ + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + + /* Default core option value. Must match one of the values + * in the retro_core_option_value array, otherwise will be + * ignored */ + const char *default_value; +}; + +struct retro_core_options_intl +{ + /* Pointer to an array of retro_core_option_definition structs + * - US English implementation + * - Must point to a valid array */ + struct retro_core_option_definition *us; + + /* Pointer to an array of retro_core_option_definition structs + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_option_definition *local; +}; + struct retro_game_info { const char *path; /* Path to game, UTF-8 encoded. - * Usually used as a reference. - * May be NULL if rom was loaded from stdin - * or similar. - * retro_system_info::need_fullpath guaranteed + * Sometimes used as a reference for building other paths. + * May be NULL if game was loaded from stdin or similar, + * but in this case some cores will be unable to load `data`. + * So, it is preferable to fabricate something here instead + * of passing NULL, which will help more cores to succeed. + * retro_system_info::need_fullpath requires * that this path is valid. */ - const void *data; /* Memory buffer of loaded game. Will be NULL + const void *data; /* Memory buffer of loaded game. Will be NULL * if need_fullpath was set. */ size_t size; /* Size of memory buffer. */ const char *meta; /* String of implementation specific meta-data. */ @@ -1984,25 +2599,25 @@ struct retro_framebuffer /* Callbacks */ -/* Environment callback. Gives implementations a way of performing +/* Environment callback. Gives implementations a way of performing * uncommon tasks. Extensible. */ typedef bool (RETRO_CALLCONV *retro_environment_t)(unsigned cmd, void *data); -/* Render a frame. Pixel format is 15-bit 0RGB1555 native endian +/* Render a frame. Pixel format is 15-bit 0RGB1555 native endian * unless changed (see RETRO_ENVIRONMENT_SET_PIXEL_FORMAT). * * Width and height specify dimensions of buffer. * Pitch specifices length in bytes between two lines in buffer. * - * For performance reasons, it is highly recommended to have a frame + * For performance reasons, it is highly recommended to have a frame * that is packed in memory, i.e. pitch == width * byte_per_pixel. - * Certain graphic APIs, such as OpenGL ES, do not like textures + * Certain graphic APIs, such as OpenGL ES, do not like textures * that are not packed in memory. */ typedef void (RETRO_CALLCONV *retro_video_refresh_t)(const void *data, unsigned width, unsigned height, size_t pitch); -/* Renders a single audio frame. Should only be used if implementation +/* Renders a single audio frame. Should only be used if implementation * generates a single sample at a time. * Format is signed 16-bit native endian. */ @@ -2020,20 +2635,20 @@ typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_t)(const int16_t *data, /* Polls input. */ typedef void (RETRO_CALLCONV *retro_input_poll_t)(void); -/* Queries for input for player 'port'. device will be masked with +/* Queries for input for player 'port'. device will be masked with * RETRO_DEVICE_MASK. * - * Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that + * Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that * have been set with retro_set_controller_port_device() * will still use the higher level RETRO_DEVICE_JOYPAD to request input. */ -typedef int16_t (RETRO_CALLCONV *retro_input_state_t)(unsigned port, unsigned device, +typedef int16_t (RETRO_CALLCONV *retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id); -/* Sets callbacks. retro_set_environment() is guaranteed to be called +/* Sets callbacks. retro_set_environment() is guaranteed to be called * before retro_init(). * - * The rest of the set_* functions are guaranteed to have been called + * The rest of the set_* functions are guaranteed to have been called * before the first call to retro_run() is made. */ RETRO_API void retro_set_environment(retro_environment_t); RETRO_API void retro_set_video_refresh(retro_video_refresh_t); @@ -2050,27 +2665,33 @@ RETRO_API void retro_deinit(void); * when the API is revised. */ RETRO_API unsigned retro_api_version(void); -/* Gets statically known system info. Pointers provided in *info +/* Gets statically known system info. Pointers provided in *info * must be statically allocated. * Can be called at any time, even before retro_init(). */ RETRO_API void retro_get_system_info(struct retro_system_info *info); /* Gets information about system audio/video timings and geometry. * Can be called only after retro_load_game() has successfully completed. - * NOTE: The implementation of this function might not initialize every + * NOTE: The implementation of this function might not initialize every * variable if needed. - * E.g. geom.aspect_ratio might not be initialized if core doesn't + * E.g. geom.aspect_ratio might not be initialized if core doesn't * desire a particular aspect ratio. */ RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info); /* Sets device to be used for player 'port'. - * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all + * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all * available ports. - * Setting a particular device type is not a guarantee that libretro cores - * will only poll input based on that particular device type. It is only a - * hint to the libretro core when a core cannot automatically detect the - * appropriate input device type on its own. It is also relevant when a - * core can change its behavior depending on device type. */ + * Setting a particular device type is not a guarantee that libretro cores + * will only poll input based on that particular device type. It is only a + * hint to the libretro core when a core cannot automatically detect the + * appropriate input device type on its own. It is also relevant when a + * core can change its behavior depending on device type. + * + * As part of the core's implementation of retro_set_controller_port_device, + * the core should call RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS to notify the + * frontend if the descriptions for any controls have changed as a + * result of changing the device type. + */ RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device); /* Resets the current game. */ @@ -2078,18 +2699,18 @@ RETRO_API void retro_reset(void); /* Runs the game for one video frame. * During retro_run(), input_poll callback must be called at least once. - * + * * If a frame is not rendered for reasons where a game "dropped" a frame, - * this still counts as a frame, and retro_run() should explicitly dupe + * this still counts as a frame, and retro_run() should explicitly dupe * a frame if GET_CAN_DUPE returns true. * In this case, the video callback can take a NULL argument for data. */ RETRO_API void retro_run(void); -/* Returns the amount of data the implementation requires to serialize +/* Returns the amount of data the implementation requires to serialize * internal state (save states). - * Between calls to retro_load_game() and retro_unload_game(), the - * returned size is never allowed to be larger than a previous returned + * Between calls to retro_load_game() and retro_unload_game(), the + * returned size is never allowed to be larger than a previous returned * value, to ensure that the frontend can allocate a save state buffer once. */ RETRO_API size_t retro_serialize_size(void); @@ -2102,7 +2723,9 @@ RETRO_API bool retro_unserialize(const void *data, size_t size); RETRO_API void retro_cheat_reset(void); RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code); -/* Loads a game. */ +/* Loads a game. + * Return true to indicate successful loading and false to indicate load failure. + */ RETRO_API bool retro_load_game(const struct retro_game_info *game); /* Loads a "special" kind of game. Should not be used, @@ -2112,7 +2735,7 @@ RETRO_API bool retro_load_game_special( const struct retro_game_info *info, size_t num_info ); -/* Unloads a currently loaded game. */ +/* Unloads the currently loaded game. Called before retro_deinit(void). */ RETRO_API void retro_unload_game(void); /* Gets region of game. */ From eb295de2184e9654c7de83348a7e490fd40ac49e Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Sat, 10 Oct 2020 23:33:10 +0000 Subject: [PATCH 1216/1216] shared version.mk --- Makefile | 2 +- libretro/Makefile.common | 2 +- version.mk | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 version.mk diff --git a/Makefile b/Makefile index 0881fe99..c3d030aa 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.13.6 +include version.mk export VERSION CONF ?= debug SDL_AUDIO_DRIVER ?= sdl diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 430c03d2..fabe3ad4 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -1,4 +1,4 @@ -VERSION := 0.13.6 +include $(CORE_DIR)/version.mk INCFLAGS := -I$(CORE_DIR) diff --git a/version.mk b/version.mk new file mode 100644 index 00000000..35ae0ad6 --- /dev/null +++ b/version.mk @@ -0,0 +1 @@ +VERSION := 0.13.6
    @@ -45,6 +45,7 @@ + @@ -119,15 +120,15 @@ - + - + - + diff --git a/SDL/gui.c b/SDL/gui.c index c84895b3..03c9513e 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -350,6 +350,7 @@ struct shader_name { {"NearestNeighbor", "Nearest Neighbor"}, {"Bilinear", "Bilinear"}, {"SmoothBilinear", "Smooth Bilinear"}, + {"LCD", "LCD Display"}, {"Scale2x", "Scale2x"}, {"Scale4x", "Scale4x"}, {"AAScale2x", "Anti-aliased Scale2x"}, diff --git a/Shaders/LCD.fsh b/Shaders/LCD.fsh new file mode 100644 index 00000000..980f3bdd --- /dev/null +++ b/Shaders/LCD.fsh @@ -0,0 +1,70 @@ +#define COLOR_LOW 0.8 +#define COLOR_HIGH 1.0 +#define SCANLINE_DEPTH 0.1 + +vec4 scale(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + vec2 pos = fract(texCoord * textureDimensions); + vec2 sub_pos = fract(texCoord * textureDimensions * 6); + + vec4 center = texture(image, texCoord); + vec4 left = texture(image, texCoord - vec2(1.0 / textureDimensions.x, 0)); + vec4 right = texture(image, texCoord + vec2(1.0 / textureDimensions.x, 0)); + + if (pos.y < 1.0 / 6.0) { + center = mix(center, texture(image, texCoord + vec2(0, -1.0 / textureDimensions.y)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture(image, texCoord + vec2(-1.0 / textureDimensions.x, -1.0 / textureDimensions.y)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture(image, texCoord + vec2( 1.0 / textureDimensions.x, -1.0 / textureDimensions.y)), 0.5 - sub_pos.y / 2.0); + center *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + left *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + right *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else if (pos.y > 5.0 / 6.0) { + center = mix(center, texture(image, texCoord + vec2(0, 1.0 / textureDimensions.y)), sub_pos.y / 2.0); + left = mix(left, texture(image, texCoord + vec2(-1.0 / textureDimensions.x, 1.0 / textureDimensions.y)), sub_pos.y / 2.0); + right = mix(right, texture(image, texCoord + vec2( 1.0 / textureDimensions.x, 1.0 / textureDimensions.y)), sub_pos.y / 2.0); + center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + + vec4 midleft = mix(left, center, 0.5); + vec4 midright = mix(right, center, 0.5); + + vec4 ret; + if (pos.x < 1.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + sub_pos.x); + } + else if (pos.x < 2.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + sub_pos.x); + } + else if (pos.x < 3.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1), + sub_pos.x); + } + else if (pos.x < 4.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else if (pos.x < 5.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1), + sub_pos.x); + } + + return ret; +} From 058913f8a2d85a2573947d6a6ed0f3ab966cb06a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Feb 2018 19:57:33 +0200 Subject: [PATCH 0529/1216] Fixed libretro-Android build --- libretro/jni/Android.mk | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 3401c104..4c4a7b67 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -23,14 +23,29 @@ ifeq ($(TARGET_ARCH),mips) LOCAL_CXXFLAGS += -DANDROID_MIPS endif -CORE_DIR := ../.. +CORE_DIR := $(realpath ../..) include ../Makefile.common LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) -LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" - +LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -DDISABLE_DEBUGGER -DDISABLE_TIMEKEEPING -Wno-multichar LOCAL_C_INCLUDES = $(INCFLAGS) include $(BUILD_SHARED_LIBRARY) +$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/build/bin/BootROMs/%_boot.bin + echo "/* AUTO-GENERATED */" > $@ + echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ +ifneq ($(findstring Haiku,$(shell uname -s)),) + # turns out od is posix, hexdump is not hence is less portable + # this is still rather ugly and could be done better I guess + od -A none -t x1 -v $< | sed -e 's/^\ /0x/' -e 's/\ /,\ 0x/g' -e 's/$$/,/g' | tr '\n' ' ' >> $@ +else + hexdump -v -e '/1 "0x%02x, "' $< >> $@ +endif + echo "};" >> $@ + echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ + +$(CORE_DIR)/build/bin/BootROMs/%_boot.bin: + $(MAKE) -C .. $(patsubst $(CORE_DIR)/%,%,$@) + From 9802ca41ddb9ec4d81e7870c658fbc8dca76e1c1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Feb 2018 21:17:12 +0200 Subject: [PATCH 0530/1216] =?UTF-8?q?Components=20not=20affected=20by=20CG?= =?UTF-8?q?B=E2=80=99s=20double=20speed=20mode=20now=20operate=20in=208MHz?= =?UTF-8?q?=20mode=20to=20theoretically=20make=20advance=5Fcycles(gb,=201)?= =?UTF-8?q?=20safe.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 2 +- Core/apu.h | 2 +- Core/debugger.c | 4 ++-- Core/display.c | 56 +++++++++++++++++++++++++------------------------ Core/gb.c | 2 +- Core/gb.h | 14 ++++++------- Core/memory.c | 4 ++-- Core/timing.c | 6 +++--- 8 files changed, 46 insertions(+), 44 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 59970ec2..b79aad96 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -385,7 +385,7 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - double cycles_per_sample = GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; + double cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ if (gb->apu_output.sample_cycles > cycles_per_sample) { gb->apu_output.sample_cycles -= cycles_per_sample; diff --git a/Core/apu.h b/Core/apu.h index bd69ac4f..fd947f77 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -127,7 +127,7 @@ typedef struct { volatile bool copy_in_progress; volatile bool lock; - double sample_cycles; + double sample_cycles; // In 8 MHz units // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! unsigned cycles_since_render; diff --git a/Core/debugger.c b/Core/debugger.c index 7fc7b2a9..75178c82 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1411,8 +1411,8 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); - GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles); - GB_log(gb, "Current line: %d\n", gb->display_cycles / 456); + GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles / 2); + GB_log(gb, "Current line: %d\n", gb->display_cycles / 456 / 2); GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); diff --git a/Core/display.c b/Core/display.c index 41f85bf1..a3370b90 100755 --- a/Core/display.c +++ b/Core/display.c @@ -15,13 +15,15 @@ Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt */ -#define MODE2_LENGTH (80) -#define MODE3_LENGTH (172) -#define MODE0_LENGTH (204) +/* The display (logically) runs in 8MHz units, so we double our length constants */ +#define MODE2_LENGTH (80 * 2) +#define MODE3_LENGTH (172 * 2) +#define MODE0_LENGTH (204 * 2) #define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 #define LINES (144) #define WIDTH (160) -#define VIRTUAL_LINES (LCDC_PERIOD / LINE_LENGTH) // = 154 +#define FRAME_LENGTH (LCDC_PERIOD * 2) +#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154 typedef struct __attribute__((packed)) { uint8_t y; @@ -332,9 +334,9 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* Keep sending vblanks to user even if the screen is off */ gb->display_cycles += cycles; - if (gb->display_cycles >= LCDC_PERIOD) { + if (gb->display_cycles >= FRAME_LENGTH) { /* VBlank! */ - gb->display_cycles -= LCDC_PERIOD; + gb->display_cycles -= FRAME_LENGTH; display_vblank(gb); } @@ -344,23 +346,23 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) return; } - uint8_t atomic_increase = gb->cgb_double_speed? 2 : 4; + uint8_t atomic_increase = gb->cgb_double_speed? 4 : 8; /* According to AntonioND's docs this value should be 0 in CGB mode, but tests I ran on my CGB seem to contradict these findings. Todo: Investigate what causes the difference between our findings */ - uint8_t stat_delay = gb->cgb_double_speed? 2 : 4; // (gb->cgb_mode? 0 : 4); + uint8_t stat_delay = gb->cgb_double_speed? 4 : 8; // (gb->cgb_mode? 0 : 8); /* Todo: Is this correct for DMG mode CGB? */ - uint8_t scx_delay = gb->effective_scx & 7; + uint8_t scx_delay = (gb->effective_scx & 7) * 2; if (gb->cgb_double_speed) { - scx_delay = (scx_delay + 1) & ~1; + scx_delay = (scx_delay + 2) & ~3; } else { - scx_delay = (scx_delay + (gb->first_scanline ? 2 : 0)) & ~3; + scx_delay = (scx_delay + (gb->first_scanline ? 4 : 0)) & ~7; } /* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */ - uint8_t oam_blocking_rush = gb->cgb_double_speed? 2 : 4; - uint8_t vram_blocking_rush = gb->is_cgb? 0 : 4; + uint8_t oam_blocking_rush = gb->cgb_double_speed? 4 : 8; + uint8_t vram_blocking_rush = gb->is_cgb? 0 : 8; for (; cycles; cycles -= atomic_increase) { bool dmg_future_stat = false; @@ -371,11 +373,11 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) gb->stat_interrupt_line = false; gb->display_cycles += atomic_increase; - /* The very first line is 4 clocks shorter when the LCD turns on. Verified on SGB2, CGB in CGB mode and + /* The very first line is 1 M-cycle shorter when the LCD turns on. Verified on SGB2, CGB in CGB mode and CGB in double speed mode. */ - if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - 8) { + if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - 0x10) { gb->first_scanline = false; - gb->display_cycles += 4; + gb->display_cycles += 8; } bool should_compare_ly = true; uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH; @@ -383,7 +385,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* Handle cycle completion. STAT's initial value depends on model and mode */ - if (gb->display_cycles == LCDC_PERIOD) { + if (gb->display_cycles == FRAME_LENGTH) { /* VBlank! */ gb->display_cycles = 0; gb->io_registers[GB_IO_STAT] &= ~3; @@ -542,11 +544,11 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) case 0: should_compare_ly = false; break; - case 4: + case 8: gb->io_registers[GB_IO_LY] = 0; ly_for_comparison = VIRTUAL_LINES - 1; break; - case 8: + case 16: gb->io_registers[GB_IO_LY] = 0; should_compare_ly = false; break; @@ -561,9 +563,9 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) case 0: ly_for_comparison = VIRTUAL_LINES - 2; break; - case 4: - break; case 8: + break; + case 16: gb->io_registers[GB_IO_LY] = 0; break; default: @@ -576,7 +578,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { case 0: break; - case 4: + case 8: gb->io_registers[GB_IO_LY] = 0; break; default: @@ -591,11 +593,11 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) case 0: ly_for_comparison = VIRTUAL_LINES - 2; break; - case 2: case 4: - break; - case 6: case 8: + break; + case 12: + case 16: gb->io_registers[GB_IO_LY] = 0; break; default: @@ -666,7 +668,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) This is based on AntonioND's docs, however I could not reproduce these findings on my CGB. Todo: Find out why my tests contradict these docs */ if (gb->cgb_mode && !gb->cgb_double_speed && - gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 4) { + gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 8) { uint8_t glitch_pattern[] = {0, 0, 2, 0, 4, 4, 6, 0, 8}; if ((gb->io_registers[GB_IO_LY] & 0xF) == 0xF) { gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] >> 4] << 4; @@ -709,7 +711,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Render */ - int16_t current_lcdc_x = gb->display_cycles % LINE_LENGTH - MODE2_LENGTH - (gb->effective_scx & 0x7) - 7; + int16_t current_lcdc_x = (gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) / 2 - (gb->effective_scx & 0x7) - 7; for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { if (gb->previous_lcdc_x >= WIDTH) { diff --git a/Core/gb.c b/Core/gb.c index 43c29806..b7aa3652 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -309,7 +309,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb) } gb->turbo = old_turbo; gb->turbo_dont_skip = old_dont_skip; - return gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb); + return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ } void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) diff --git a/Core/gb.h b/Core/gb.h index 3dea9961..7e6abb0b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -267,7 +267,7 @@ struct GB_gameboy_internal_s { bool hdma_on; bool hdma_on_hblank; uint8_t hdma_steps_left; - uint16_t hdma_cycles; + uint16_t hdma_cycles; // in 8MHz units uint16_t hdma_current_src, hdma_current_dest; uint8_t dma_steps_left; @@ -332,7 +332,7 @@ struct GB_gameboy_internal_s { /* Timing */ GB_SECTION(timing, - uint32_t display_cycles; + uint32_t display_cycles; // In 8 MHz units uint32_t div_cycles; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; @@ -418,7 +418,7 @@ struct GB_gameboy_internal_s { /* Timing */ uint64_t last_sync; - uint64_t cycles_since_last_sync; + uint64_t cycles_since_last_sync; // In 8MHz units /* Audio */ GB_apu_output_t apu_output; @@ -438,8 +438,8 @@ struct GB_gameboy_internal_s { GB_serial_transfer_end_callback_t serial_transfer_end_callback; /* IR */ - long cycles_since_ir_change; - long cycles_since_input_ir_change; + long cycles_since_ir_change; // In 8MHz units + long cycles_since_input_ir_change; // In 8MHz units GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; size_t ir_queue_length; @@ -493,7 +493,7 @@ struct GB_gameboy_internal_s { uint32_t ram_size; // Different between CGB and DMG uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank - uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run() + uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units double clock_multiplier; ); }; @@ -563,7 +563,7 @@ void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const cha void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); void GB_set_infrared_input(GB_gameboy_t *gb, bool state); -void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); /* In 8MHz units*/ 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); diff --git a/Core/memory.c b/Core/memory.c index ef2f007a..01d4a456 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -708,8 +708,8 @@ void GB_dma_run(GB_gameboy_t *gb) void GB_hdma_run(GB_gameboy_t *gb) { if (!gb->hdma_on) return; - while (gb->hdma_cycles >= 8) { - gb->hdma_cycles -= 8; + while (gb->hdma_cycles >= 0x10) { + gb->hdma_cycles -= 0x10; for (uint8_t i = 0; i < 0x10; i++) { GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); diff --git a/Core/timing.c b/Core/timing.c index c03803f6..64986c0a 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -55,9 +55,9 @@ void GB_timing_sync(GB_gameboy_t *gb) return; } /* Prevent syncing if not enough time has passed.*/ - if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return; + if (gb->cycles_since_last_sync < LCDC_PERIOD / 8) return; - uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb); + uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) { nsleep(target_nanoseconds + gb->last_sync - nanoseconds); @@ -145,7 +145,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->debugger_ticks += cycles; - cycles >>= gb->cgb_double_speed; + cycles <<= !gb->cgb_double_speed; // Not affected by speed boost gb->hdma_cycles += cycles; From 56eac9f875470df8f28090e912b56fde7dba3e46 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Feb 2018 21:23:27 +0200 Subject: [PATCH 0531/1216] Removed some dead code from display.c --- Core/display.c | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/Core/display.c b/Core/display.c index a3370b90..86a4e776 100755 --- a/Core/display.c +++ b/Core/display.c @@ -390,12 +390,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) gb->display_cycles = 0; gb->io_registers[GB_IO_STAT] &= ~3; if (gb->is_cgb) { - if (stat_delay) { - gb->io_registers[GB_IO_STAT] |= 1; - } - else { - gb->io_registers[GB_IO_STAT] |= 2; - } + gb->io_registers[GB_IO_STAT] |= 1; } ly_for_comparison = gb->io_registers[GB_IO_LY] = 0; @@ -502,20 +497,20 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* Handle everything else */ /* OAM interrupt happens slightly before STAT is actually updated. (About 1-3 T-cycles) Todo: Test double speed CGB */ - if (position_in_line == 0 && stat_delay) { + if (position_in_line == 0) { if (gb->io_registers[GB_IO_STAT] & 0x20) { gb->stat_interrupt_line = true; dmg_future_stat = true; } + if (gb->display_cycles != 0) { + should_compare_ly = gb->is_cgb; + ly_for_comparison--; + } } - if (position_in_line == stat_delay) { + else if (position_in_line == stat_delay) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; } - else if (position_in_line == 0 && gb->display_cycles != 0) { - should_compare_ly = gb->is_cgb; - ly_for_comparison--; - } else if (position_in_line == MODE2_LENGTH + stat_delay) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; @@ -609,7 +604,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* Lines 144 - 152 */ else { - if (stat_delay && gb->display_cycles % LINE_LENGTH == 0) { + if (gb->display_cycles % LINE_LENGTH == 0) { should_compare_ly = gb->is_cgb; ly_for_comparison--; } @@ -662,22 +657,6 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) } } }; - -#if 0 - /* The value of LY is glitched in the last cycle of every line in CGB mode CGB in single speed - This is based on AntonioND's docs, however I could not reproduce these findings on my CGB. - Todo: Find out why my tests contradict these docs */ - if (gb->cgb_mode && !gb->cgb_double_speed && - gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 8) { - uint8_t glitch_pattern[] = {0, 0, 2, 0, 4, 4, 6, 0, 8}; - if ((gb->io_registers[GB_IO_LY] & 0xF) == 0xF) { - gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] >> 4] << 4; - } - else { - gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] & 7] | (gb->io_registers[GB_IO_LY] & 0xF8); - } - } -#endif } void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) From 5974092c94c5ec3b8854dc7c1c165628cf8ffd3e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 20 Feb 2018 23:04:35 +0200 Subject: [PATCH 0532/1216] Bugfix --- Core/z80_cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 16dbd08e..d53e96dd 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -21,8 +21,8 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); if (gb->io_registers[GB_IO_KEY1] & 0x1) { - /* Make sure we don't leave display_cycles not divisble by 4 in single speed mode */ - if (gb->display_cycles % 4 == 2) { + /* Make sure we don't leave display_cycles not divisble by 8 in single speed mode */ + if (gb->display_cycles % 8 == 4) { GB_advance_cycles(gb, 4); } From c48097a484861bf4acf42c3c3f7a8356be239aba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Feb 2018 13:16:05 +0200 Subject: [PATCH 0533/1216] Convert div counter to the SM mechanism --- Core/apu.c | 4 +-- Core/gb.h | 3 +- Core/memory.c | 4 +-- Core/timing.c | 97 ++++++++++++++++++++++++++------------------------- Core/timing.h | 26 ++++++++++++-- 5 files changed, 79 insertions(+), 55 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index b79aad96..cfc0d11a 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -289,8 +289,8 @@ void GB_apu_div_event(GB_gameboy_t *gb) void GB_apu_run(GB_gameboy_t *gb) { - /* Convert 4MHZ to 2MHz. apu_cycles is always even. */ - uint8_t cycles = gb->apu.apu_cycles >> 1; + /* Convert 4MHZ to 2MHz. apu_cycles is always divisable by 4. */ + uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; diff --git a/Core/gb.h b/Core/gb.h index 7e6abb0b..ec3b328a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -333,7 +333,8 @@ struct GB_gameboy_internal_s { /* Timing */ GB_SECTION(timing, uint32_t display_cycles; // In 8 MHz units - uint32_t div_cycles; + GB_UNIT(div); + uint32_t div_counter; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; uint16_t serial_length; diff --git a/Core/memory.c b/Core/memory.c index 01d4a456..ca27741e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -176,7 +176,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return gb->io_registers[GB_IO_TIMA]; case GB_IO_DIV: - return gb->div_cycles >> 8; + return gb->div_counter >> 8; case GB_IO_HDMA5: if (!gb->cgb_mode) return 0xFF; return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); @@ -484,7 +484,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: - GB_set_internal_div_counter(gb, 0); + gb->div_state = 0; // Reset the div state machine return; case GB_IO_JOYP: diff --git a/Core/timing.c b/Core/timing.c index 64986c0a..67d0190f 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -6,6 +6,8 @@ #include #endif +static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; + #ifndef DISABLE_TIMEKEEPING static int64_t get_nanoseconds(void) { @@ -105,25 +107,56 @@ static void advance_tima_state_machine(GB_gameboy_t *gb) } } +static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) +{ + return (old & (max >> 1)) && !(new & (max >> 1)); +} + +static void increase_tima(GB_gameboy_t *gb) +{ + 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->tima_reload_state = GB_TIMA_RELOADING; + } +} + +static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +{ + /* TIMA increases when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + if ((gb->io_registers[GB_IO_TAC] & 4) && + counter_overflow_check(gb->div_counter, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + if (counter_overflow_check(gb->div_counter, value, gb->cgb_double_speed? 0x4000 : 0x2000)) { + GB_apu_run(gb); + GB_apu_div_event(gb); + } + gb->div_counter = value; +} + +static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) +{ + GB_STATE_MACHINE(gb, div, cycles) { + GB_STATE(gb, div, 1); + } + + GB_set_internal_div_counter(gb, 0); + while (true) { + advance_tima_state_machine(gb); + GB_set_internal_div_counter(gb, gb->div_counter + 4); + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + GB_SLEEP(gb, div, 1, 4); + } +} + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost gb->dma_cycles += cycles; - advance_tima_state_machine(gb); - for (int i = 0; i < cycles; i += 4) { - /* This is a bit tricky. The DIV and APU are tightly coupled, but DIV is affected - by the speed boost while the APU is not */ - GB_set_internal_div_counter(gb, gb->div_cycles + 4); - gb->apu.apu_cycles += 4 >> gb->cgb_double_speed; - } - - if (cycles > 4) { - advance_tima_state_machine(gb); - if (cycles > 8) { - advance_tima_state_machine(gb); - } - } + GB_timers_run(gb, cycles); uint16_t previous_serial_cycles = gb->serial_cycles; gb->serial_cycles += cycles; @@ -161,38 +194,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) GB_ir_run(gb); } -/* Standard Timers */ -static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; - -static void increase_tima(GB_gameboy_t *gb) -{ - 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->tima_reload_state = GB_TIMA_RELOADING; - } -} - -static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) -{ - return (old & (max >> 1)) && !(new & (max >> 1)); -} - -void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) -{ - /* TIMA increases when a specific high-bit becomes a low-bit. */ - value &= INTERNAL_DIV_CYCLES - 1; - if ((gb->io_registers[GB_IO_TAC] & 4) && - counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { - increase_tima(gb); - } - if (counter_overflow_check(gb->div_cycles, value, gb->cgb_double_speed? 0x4000 : 0x2000)) { - GB_apu_run(gb); - GB_apu_div_event(gb); - } - gb->div_cycles = value; -} - /* This glitch is based on the expected results of mooneye-gb rapid_toggle test. This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. @@ -207,9 +208,9 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) unsigned int new_clocks = GB_TAC_RATIOS[new_tac & 3]; /* The bit used for overflow testing must have been 1 */ - if (gb->div_cycles & (old_clocks >> 1)) { + if (gb->div_counter & (old_clocks >> 1)) { /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ - if (!(new_tac & 4) || gb->div_cycles & (new_clocks >> 1)) { + if (!(new_tac & 4) || gb->div_counter & (new_clocks >> 1)) { increase_tima(gb); } } diff --git a/Core/timing.h b/Core/timing.h index ed9e15a5..68755702 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -4,18 +4,40 @@ #ifdef GB_INTERNAL void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value); void GB_rtc_run(GB_gameboy_t *gb); void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ void GB_timing_sync(GB_gameboy_t *gb); - enum { GB_TIMA_RUNNING = 0, GB_TIMA_RELOADING = 1, GB_TIMA_RELOADED = 2 }; + +#define GB_HALT_VALUE (0xFFFF) + +#define GB_SLEEP(gb, unit, state, cycles) do {\ + (gb)->unit##_cycles -= cycles; \ + if ((gb)->unit##_cycles <= 0) {\ + (gb)->unit##_state = state;\ + return;\ + unit##state:; \ + }\ +} while (0) + +#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE + +#define GB_STATE_MACHINE(gb, unit, cycles) \ +(gb)->unit##_cycles += cycles; \ +if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\ + return;\ +}\ +switch ((gb)->unit##_state) #endif +#define GB_STATE(gb, unit, state) case state: goto unit##state + +#define GB_UNIT(unit) uint32_t unit##_cycles, unit##_state + #endif /* timing_h */ From 42ab746a661bb9a3cd3494e810b352b6db0bb7b3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Feb 2018 15:33:44 +0200 Subject: [PATCH 0534/1216] =?UTF-8?q?Starting=20to=20remove=20the=20delaye?= =?UTF-8?q?d=20interrupts=20hack=20=E2=80=93=20done=20for=20timer=20interr?= =?UTF-8?q?upt,=20broken=20for=20display=20interrupts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 2 ++ Core/gb.h | 1 + Core/memory.c | 4 +++- Core/timing.c | 8 ++++---- Core/timing.h | 2 +- Core/z80_cpu.c | 42 +++++++++++++++++++++++++----------------- 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Core/display.c b/Core/display.c index 86a4e776..d815e22f 100755 --- a/Core/display.c +++ b/Core/display.c @@ -661,6 +661,8 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { + if (gb->display_hack == 1) return; + if (gb->display_hack == 2) cycles *= 2; update_display_state(gb, cycles); if (gb->disable_rendering) { return; diff --git a/Core/gb.h b/Core/gb.h index ec3b328a..c39fbea0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -340,6 +340,7 @@ struct GB_gameboy_internal_s { uint16_t serial_length; uint8_t future_interrupts; /* Interrupts can occur in any T-cycle. Some timings result in different interrupt timing when the CPU is in halt mode, and might also affect the DI instruction. */ + uint8_t display_hack; // Temporary hack until the display is rewritten to operate in T-cycle rates; ); /* APU */ diff --git a/Core/memory.c b/Core/memory.c index ca27741e..d4835064 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -484,7 +484,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: - gb->div_state = 0; // Reset the div state machine + /* Reset the div state machine */ + gb->div_state = 0; + gb->div_cycles = 0; return; case GB_IO_JOYP: diff --git a/Core/timing.c b/Core/timing.c index 67d0190f..57c9a69f 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -96,13 +96,11 @@ static void GB_ir_run(GB_gameboy_t *gb) static void advance_tima_state_machine(GB_gameboy_t *gb) { - gb->io_registers[GB_IO_IF] |= gb->future_interrupts & 4; - gb->future_interrupts &= ~4; if (gb->tima_reload_state == GB_TIMA_RELOADED) { gb->tima_reload_state = GB_TIMA_RUNNING; } else if (gb->tima_reload_state == GB_TIMA_RELOADING) { - gb->future_interrupts |= 4; + gb->io_registers[GB_IO_IF] |= 4; gb->tima_reload_state = GB_TIMA_RELOADED; } } @@ -140,14 +138,16 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) { GB_STATE_MACHINE(gb, div, cycles) { GB_STATE(gb, div, 1); + GB_STATE(gb, div, 2); } GB_set_internal_div_counter(gb, 0); + GB_SLEEP(gb, div, 1, 2); while (true) { advance_tima_state_machine(gb); GB_set_internal_div_counter(gb, gb->div_counter + 4); gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; - GB_SLEEP(gb, div, 1, 4); + GB_SLEEP(gb, div, 2, 4); } } diff --git a/Core/timing.h b/Core/timing.h index 68755702..edc41fae 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -38,6 +38,6 @@ switch ((gb)->unit##_state) #define GB_STATE(gb, unit, state) case state: goto unit##state -#define GB_UNIT(unit) uint32_t unit##_cycles, unit##_state +#define GB_UNIT(unit) int32_t unit##_cycles, unit##_state #endif /* timing_h */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index d53e96dd..863dc8bc 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1337,23 +1337,29 @@ static GB_opcode_t *opcodes[256] = { }; void GB_cpu_run(GB_gameboy_t *gb) { + if (gb->hdma_on) { + GB_advance_cycles(gb, 4); + return; + } + gb->vblank_just_occured = false; + if (gb->halted) { + gb->display_hack = 1; + GB_advance_cycles(gb, 2); + } + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; - if (!gb->halted) { - interrupt_queue |= gb->future_interrupts & gb->interrupt_enable; + + if (gb->halted) { + gb->display_hack = 2; + GB_advance_cycles(gb, 2); + gb->display_hack = 0; } gb->io_registers[GB_IO_IF] |= gb->future_interrupts; gb->future_interrupts = 0; - if (interrupt_queue) { - gb->halted = false; - } - - if (gb->hdma_on) { - GB_advance_cycles(gb, 4); - return; - } + bool effecitve_ime = gb->ime; if (gb->ime_toggle) { @@ -1361,11 +1367,15 @@ void GB_cpu_run(GB_gameboy_t *gb) gb->ime_toggle = false; } - if (effecitve_ime && interrupt_queue) { - - nop(gb, 0); + /* Wake up from HALT mode without calling interrupt code. */ + if (gb->halted && !effecitve_ime && interrupt_queue) { + gb->halted = false; + } + /* Call interrupt */ + else if (effecitve_ime && interrupt_queue) { + gb->halted = false; uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 12); gb->registers[GB_REGISTER_SP] -= 2; GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); interrupt_queue = gb->interrupt_enable; @@ -1391,6 +1401,7 @@ void GB_cpu_run(GB_gameboy_t *gb) gb->ime = false; GB_debugger_call_hook(gb, call_addr); } + /* Run mode */ else if(!gb->halted && !gb->stopped) { uint8_t opcode = GB_read_memory(gb, gb->pc++); if (gb->halt_bug) { @@ -1399,7 +1410,4 @@ void GB_cpu_run(GB_gameboy_t *gb) } opcodes[opcode](gb, opcode); } - else { - GB_advance_cycles(gb, 4); - } } From ef670986c6210c59a999e0bb433adeb48bd5f35c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 25 Feb 2018 00:48:45 +0200 Subject: [PATCH 0535/1216] =?UTF-8?q?Rewrote=20PPU=20(currently=20only=20e?= =?UTF-8?q?mulates=20DMG=20correctly)=20to=20use=20the=20new=20timing=20me?= =?UTF-8?q?chanism.=20Removed=20=E2=80=9Cfuture=20interrupts=E2=80=9D=20(N?= =?UTF-8?q?o=20longer=20required=20because=20SameBoy=20is=20now=20T-cycle?= =?UTF-8?q?=20based)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 613 +++++++++++++++++++------------------------------ Core/display.h | 2 + Core/gb.h | 10 +- Core/joypad.c | 1 + Core/memory.c | 13 +- Core/timing.c | 4 +- Core/timing.h | 5 +- Core/z80_cpu.c | 21 +- 8 files changed, 261 insertions(+), 408 deletions(-) diff --git a/Core/display.c b/Core/display.c index d815e22f..16ef88cf 100755 --- a/Core/display.c +++ b/Core/display.c @@ -15,14 +15,13 @@ Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt */ -/* The display (logically) runs in 8MHz units, so we double our length constants */ -#define MODE2_LENGTH (80 * 2) -#define MODE3_LENGTH (172 * 2) -#define MODE0_LENGTH (204 * 2) +#define MODE2_LENGTH (80) +#define MODE3_LENGTH (172) +#define MODE0_LENGTH (204) #define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 #define LINES (144) #define WIDTH (160) -#define FRAME_LENGTH (LCDC_PERIOD * 2) +#define FRAME_LENGTH (LCDC_PERIOD) #define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154 typedef struct __attribute__((packed)) { @@ -199,7 +198,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) } static void display_vblank(GB_gameboy_t *gb) -{ +{ gb->vblank_just_occured = true; if (gb->turbo) { @@ -308,406 +307,258 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m */ -static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) +void GB_STAT_update(GB_gameboy_t *gb) { - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { - /* LCD is disabled, state is constant */ - - /* When the LCD is off, LY is 0 and STAT mode is 0. - Todo: Verify the LY=LYC flag should be on. */ - gb->io_registers[GB_IO_LY] = 0; - gb->io_registers[GB_IO_STAT] &= ~3; + bool previous_interrupt_line = gb->stat_interrupt_line; + gb->stat_interrupt_line = false; + /* Set LY=LYC bit */ + if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { gb->io_registers[GB_IO_STAT] |= 4; - gb->effective_scx = gb->io_registers[GB_IO_SCX]; - if (gb->hdma_on_hblank) { - gb->hdma_on_hblank = false; - gb->hdma_on = false; - - /* Todo: is this correct? */ - gb->hdma_steps_left = 0xff; - } - - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - - /* Keep sending vblanks to user even if the screen is off */ - gb->display_cycles += cycles; - if (gb->display_cycles >= FRAME_LENGTH) { - /* VBlank! */ - gb->display_cycles -= FRAME_LENGTH; - display_vblank(gb); - } - - /* Reset window rendering state */ - gb->wy_diff = 0; - gb->window_disabled_while_active = false; - return; - } - - uint8_t atomic_increase = gb->cgb_double_speed? 4 : 8; - /* According to AntonioND's docs this value should be 0 in CGB mode, but tests I ran on my CGB seem to contradict - these findings. - Todo: Investigate what causes the difference between our findings */ - uint8_t stat_delay = gb->cgb_double_speed? 4 : 8; // (gb->cgb_mode? 0 : 8); - /* Todo: Is this correct for DMG mode CGB? */ - uint8_t scx_delay = (gb->effective_scx & 7) * 2; - if (gb->cgb_double_speed) { - scx_delay = (scx_delay + 2) & ~3; } else { - scx_delay = (scx_delay + (gb->first_scanline ? 4 : 0)) & ~7; + gb->io_registers[GB_IO_STAT] &= ~4; } - /* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */ - uint8_t oam_blocking_rush = gb->cgb_double_speed? 4 : 8; - uint8_t vram_blocking_rush = gb->is_cgb? 0 : 8; + switch (gb->io_registers[GB_IO_STAT] & 3) { + case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8); break; + case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; + case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + } - for (; cycles; cycles -= atomic_increase) { - bool dmg_future_stat = false; - gb->io_registers[GB_IO_IF] |= gb->future_interrupts & 3; - gb->future_interrupts &= ~3; - - bool previous_stat_interrupt_line = gb->stat_interrupt_line; - gb->stat_interrupt_line = false; - - gb->display_cycles += atomic_increase; - /* The very first line is 1 M-cycle shorter when the LCD turns on. Verified on SGB2, CGB in CGB mode and - CGB in double speed mode. */ - if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - 0x10) { - gb->first_scanline = false; - gb->display_cycles += 8; - } - bool should_compare_ly = true; - uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH; - bool just_entered_hblank = false; - - - /* Handle cycle completion. STAT's initial value depends on model and mode */ - if (gb->display_cycles == FRAME_LENGTH) { - /* VBlank! */ - gb->display_cycles = 0; - gb->io_registers[GB_IO_STAT] &= ~3; - if (gb->is_cgb) { - gb->io_registers[GB_IO_STAT] |= 1; - } - ly_for_comparison = gb->io_registers[GB_IO_LY] = 0; - - /* Todo: verify timing */ - gb->oam_read_blocked = true; - gb->vram_read_blocked = false; - gb->oam_write_blocked = true; - gb->vram_write_blocked = false; + /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ + if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) { + gb->stat_interrupt_line = true; + } + + if (gb->stat_interrupt_line && ! previous_interrupt_line) { + gb->io_registers[GB_IO_IF] |= 2; + } +} - - /* Reset window rendering state */ - gb->wy_diff = 0; - gb->window_disabled_while_active = false; - } - - /* Entered VBlank state, update STAT and IF */ - else if (gb->display_cycles == LINES * LINE_LENGTH + stat_delay) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 1; - if (gb->is_cgb) { - gb->future_interrupts |= 1; - } - else { - gb->io_registers[GB_IO_IF] |= 1; - } - - /* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */ - if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->is_cgb) { - gb->stat_interrupt_line = true; - } - if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { - if (!gb->is_cgb) { - display_vblank(gb); - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; - } - else { - gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED; - } - } - else { - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; - display_vblank(gb); - } - } +static unsigned scx_delay(GB_gameboy_t *gb) +{ + return (gb->effective_scx & 7) + 0; +} + +void GB_lcd_off(GB_gameboy_t *gb) +{ + gb->display_state = 0; + gb->display_cycles = 0; + /* When the LCD is disabled, state is constant */ + + /* When the LCD is off, LY is 0 and STAT mode is 0. + Todo: Verify the LY=LYC flag should be on. */ + gb->io_registers[GB_IO_LY] = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 4; + gb->effective_scx = gb->io_registers[GB_IO_SCX]; + if (gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + gb->hdma_on = false; - /* Handle line 0 right after turning the LCD on */ - else if (gb->first_scanline) { - /* OAM and VRAM blocking is not rushed in the very first scanline */ - if (gb->display_cycles == atomic_increase) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - } - else if (gb->display_cycles == MODE2_LENGTH) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 3; - gb->effective_scx = gb->io_registers[GB_IO_SCX]; - gb->oam_read_blocked = true; - gb->vram_read_blocked = true; - gb->oam_write_blocked = true; - gb->vram_write_blocked = true; - } - else if (gb->display_cycles == MODE2_LENGTH + MODE3_LENGTH + scx_delay) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - just_entered_hblank = true; - } - } - - /* Handle STAT changes for lines 0-143 */ - else if (gb->display_cycles < LINES * LINE_LENGTH) { - unsigned position_in_line = gb->display_cycles % LINE_LENGTH; - - /* Handle OAM and VRAM blocking */ - /* Todo: verify CGB timing for write blocking */ - if (position_in_line == stat_delay - oam_blocking_rush || - // In case stat_delay is 0 - (position_in_line == LINE_LENGTH + stat_delay - oam_blocking_rush && gb->io_registers[GB_IO_LY] != 143)) { - gb->oam_read_blocked = true; - gb->oam_write_blocked = gb->is_cgb; - } - else if (position_in_line == MODE2_LENGTH + stat_delay - vram_blocking_rush) { - gb->vram_read_blocked = true; - gb->vram_write_blocked = gb->is_cgb; - } - - if (position_in_line == stat_delay) { - gb->oam_write_blocked = true; - } - else if (!gb->is_cgb && position_in_line == MODE2_LENGTH + stat_delay - oam_blocking_rush) { - gb->oam_write_blocked = false; - } - else if (position_in_line == MODE2_LENGTH + stat_delay) { - gb->vram_write_blocked = true; - gb->oam_write_blocked = true; - } - - /* Handle everything else */ - /* OAM interrupt happens slightly before STAT is actually updated. (About 1-3 T-cycles) - Todo: Test double speed CGB */ - if (position_in_line == 0) { - if (gb->io_registers[GB_IO_STAT] & 0x20) { - gb->stat_interrupt_line = true; - dmg_future_stat = true; - } - if (gb->display_cycles != 0) { - should_compare_ly = gb->is_cgb; - ly_for_comparison--; - } - } - else if (position_in_line == stat_delay) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 2; - } - else if (position_in_line == MODE2_LENGTH + stat_delay) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 3; - gb->effective_scx = gb->io_registers[GB_IO_SCX]; - gb->previous_lcdc_x = - (gb->effective_scx & 0x7); - } - else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay) { - just_entered_hblank = true; - gb->io_registers[GB_IO_STAT] &= ~3; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - if (gb->hdma_on_hblank) { - gb->hdma_on = true; - gb->hdma_cycles = 0; - } - } - } - - /* Line 153 is special */ - else if (gb->display_cycles >= (VIRTUAL_LINES - 1) * LINE_LENGTH) { - /* DMG */ - if (!gb->is_cgb) { - switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { - case 0: - should_compare_ly = false; - break; - case 8: - gb->io_registers[GB_IO_LY] = 0; - ly_for_comparison = VIRTUAL_LINES - 1; - break; - case 16: - gb->io_registers[GB_IO_LY] = 0; - should_compare_ly = false; - break; - default: - gb->io_registers[GB_IO_LY] = 0; - ly_for_comparison = 0; - } - } - /* CGB in DMG mode */ - else if (!gb->cgb_mode) { - switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { - case 0: - ly_for_comparison = VIRTUAL_LINES - 2; - break; - case 8: - break; - case 16: - gb->io_registers[GB_IO_LY] = 0; - break; - default: - gb->io_registers[GB_IO_LY] = 0; - ly_for_comparison = 0; - } - } - /* Single speed CGB */ - else if (!gb->cgb_double_speed) { - switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { - case 0: - break; - case 8: - gb->io_registers[GB_IO_LY] = 0; - break; - default: - gb->io_registers[GB_IO_LY] = 0; - ly_for_comparison = 0; - } - } - - /* Double speed CGB */ - else { - switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { - case 0: - ly_for_comparison = VIRTUAL_LINES - 2; - break; - case 4: - case 8: - break; - case 12: - case 16: - gb->io_registers[GB_IO_LY] = 0; - break; - default: - gb->io_registers[GB_IO_LY] = 0; - ly_for_comparison = 0; - } - } - } - - /* Lines 144 - 152 */ - else { - if (gb->display_cycles % LINE_LENGTH == 0) { - should_compare_ly = gb->is_cgb; - ly_for_comparison--; - } - } - - /* Set LY=LYC bit */ - if (should_compare_ly && (ly_for_comparison == gb->io_registers[GB_IO_LYC])) { - gb->io_registers[GB_IO_STAT] |= 4; - } - else { - gb->io_registers[GB_IO_STAT] &= ~4; - } - - if (!gb->stat_interrupt_line) { - switch (gb->io_registers[GB_IO_STAT] & 3) { - case 0: - gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8); - if (!gb->cgb_double_speed && just_entered_hblank && ((gb->effective_scx + (gb->first_scanline ? 2 : 0)) & 3) == 3) { - gb->stat_interrupt_line = false; - } - else if (just_entered_hblank && ((gb->effective_scx + (gb->first_scanline ? 2 : 0)) & 3) != 0) { - dmg_future_stat = true; - } - break; - case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; - case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; - } - - /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ - if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) { - gb->stat_interrupt_line = true; - dmg_future_stat = false; - } - } - - /* On the CGB, the last cycle of line 144 triggers an OAM interrupt - Todo: Verify timing for CGB in CGB mode and double speed CGB */ - if (gb->is_cgb && - gb->display_cycles == LINES * LINE_LENGTH + stat_delay - atomic_increase && - (gb->io_registers[GB_IO_STAT] & 0x20)) { - gb->stat_interrupt_line = true; - } - - if (gb->stat_interrupt_line && !previous_stat_interrupt_line) { - if (gb->is_cgb || dmg_future_stat) { - gb->future_interrupts |= 2; - } - else { - gb->io_registers[GB_IO_IF] |= 2; - } - } - }; + /* Todo: is this correct? */ + gb->hdma_steps_left = 0xff; + } + + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + + /* Reset window rendering state */ + gb->wy_diff = 0; + gb->window_disabled_while_active = false; + gb->current_line = 0; + gb->ly_for_comparison = 0; + + GB_STAT_update(gb); } void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { - if (gb->display_hack == 1) return; - if (gb->display_hack == 2) cycles *= 2; - update_display_state(gb, cycles); - if (gb->disable_rendering) { - return; + + GB_STATE_MACHINE(gb, display, cycles, 2) { + GB_STATE(gb, display, 1); + GB_STATE(gb, display, 2); + GB_STATE(gb, display, 3); + GB_STATE(gb, display, 4); + GB_STATE(gb, display, 5); + GB_STATE(gb, display, 6); + GB_STATE(gb, display, 7); + GB_STATE(gb, display, 8); + GB_STATE(gb, display, 9); + GB_STATE(gb, display, 10); + GB_STATE(gb, display, 11); + GB_STATE(gb, display, 12); + GB_STATE(gb, display, 13); + GB_STATE(gb, display, 14); + GB_STATE(gb, display, 15); + GB_STATE(gb, display, 16); + GB_STATE(gb, display, 17); + GB_STATE(gb, display, 18); } - /* - 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 = 144. - - See http://forums.nesdev.com/viewtopic.php?f=20&t=13727 - */ - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { - /* LCD is disabled, do nothing */ - return; - } - if (gb->display_cycles >= LINE_LENGTH * 144) { /* VBlank */ + while (true) { + GB_SLEEP(gb, display, 18, LCDC_PERIOD); + display_vblank(gb); + } return; } + + /* Handle the very first line 0 */ + gb->current_line = 0; + gb->ly_for_comparison = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 1, MODE2_LENGTH - 4); - uint8_t effective_ly = gb->display_cycles / LINE_LENGTH; - - - if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH) { /* Mode 2 */ - return; - } - - - /* Render */ - int16_t current_lcdc_x = (gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) / 2 - (gb->effective_scx & 0x7) - 7; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->effective_scx = gb->io_registers[GB_IO_SCX]; + gb->oam_read_blocked = true; + gb->vram_read_blocked = true; + gb->oam_write_blocked = true; + gb->vram_write_blocked = true; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 2, MODE3_LENGTH + scx_delay(gb) + 2); - for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { - if (gb->previous_lcdc_x >= WIDTH) { - continue; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + GB_SLEEP(gb, display, 17, 1); + GB_STAT_update(gb); + /* Mode 0 is shorter in the very first line */ + GB_SLEEP(gb, display, 3, MODE0_LENGTH - scx_delay(gb) - 2 - 1 - 4); + + gb->current_line = 1; + while (true) { + /* Lines 0 - 143 */ + for (; gb->current_line < LINES; gb->current_line++) { + gb->io_registers[GB_IO_LY] = gb->current_line; + gb->oam_read_blocked = true; + gb->oam_write_blocked = false; + gb->ly_for_comparison = gb->current_line? -1 : gb->current_line; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 4, 4); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + gb->oam_write_blocked = true; + gb->ly_for_comparison = gb->current_line; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 5, MODE2_LENGTH - 4); + + gb->vram_read_blocked = true; + gb->vram_write_blocked = false; + gb->oam_write_blocked = false; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 6, 4); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->effective_scx = gb->io_registers[GB_IO_SCX]; + gb->vram_write_blocked = true; + gb->oam_write_blocked = true; + GB_STAT_update(gb); + + for (gb->position_in_line = 0; gb->position_in_line < WIDTH + 7; gb->position_in_line++) { + if (!gb->disable_rendering) { + signed screen_pos = (signed) gb->position_in_line - (gb->effective_scx & 0x7); + if (((screen_pos + gb->effective_scx) & 7) == 0) { + gb->effective_scy = gb->io_registers[GB_IO_SCY]; + } + if (screen_pos >= 0 && screen_pos < WIDTH) { + gb->screen[gb->current_line * WIDTH + screen_pos] = get_pixel(gb, screen_pos, gb->current_line); + } + } + GB_SLEEP(gb, display, 15, 1); + } + + GB_SLEEP(gb, display, 7, MODE3_LENGTH + scx_delay(gb) - WIDTH - 7); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + if (gb->hdma_on_hblank) { + gb->hdma_on = true; + gb->hdma_cycles = 0; + } + GB_SLEEP(gb, display, 16, 1); + GB_STAT_update(gb); + GB_SLEEP(gb, display, 8, MODE0_LENGTH - scx_delay(gb) - 4 - 1); } - if (((gb->previous_lcdc_x + gb->effective_scx) & 7) == 0) { - gb->effective_scy = gb->io_registers[GB_IO_SCY]; + /* Lines 144 - 152 */ + for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { + gb->io_registers[GB_IO_LY] = gb->current_line; + gb->ly_for_comparison = -1; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 9, 4); + gb->ly_for_comparison = gb->current_line; + + if (gb->current_line == LINES) { + /* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */ + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + GB_STAT_update(gb); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 1; + gb->io_registers[GB_IO_IF] |= 1; + + if (gb->io_registers[GB_IO_STAT] & 0x20) { + gb->stat_interrupt_line = true; + } + + if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + display_vblank(gb); + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else { + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + display_vblank(gb); + } + } + + GB_STAT_update(gb); + GB_SLEEP(gb, display, 10, LINE_LENGTH - 4); } - if (gb->previous_lcdc_x < 0) { - continue; - } - gb->screen[effective_ly * WIDTH + gb->previous_lcdc_x] = - get_pixel(gb, gb->previous_lcdc_x, effective_ly); + /* Lines 153 */ + gb->io_registers[GB_IO_LY] = 153; + gb->ly_for_comparison = -1; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 11, 4); + + gb->io_registers[GB_IO_LY] = 0; + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 12, 4); + + gb->ly_for_comparison = -1; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 13, 4); + + gb->ly_for_comparison = 0; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 14, LINE_LENGTH - 12); + + gb->io_registers[GB_IO_STAT] &= ~3; + + /* Reset window rendering state */ + gb->wy_diff = 0; + gb->window_disabled_while_active = false; + gb->current_line = 0; } } diff --git a/Core/display.h b/Core/display.h index 06448c44..0546fda5 100644 --- a/Core/display.h +++ b/Core/display.h @@ -6,6 +6,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value); +void GB_STAT_update(GB_gameboy_t *gb); +void GB_lcd_off(GB_gameboy_t *gb); #endif typedef enum { diff --git a/Core/gb.h b/Core/gb.h index c39fbea0..25ef1267 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -332,15 +332,12 @@ struct GB_gameboy_internal_s { /* Timing */ GB_SECTION(timing, - uint32_t display_cycles; // In 8 MHz units + GB_UNIT(display); GB_UNIT(div); uint32_t div_counter; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; uint16_t serial_length; - uint8_t future_interrupts; /* Interrupts can occur in any T-cycle. Some timings result in different interrupt - timing when the CPU is in halt mode, and might also affect the DI instruction. */ - uint8_t display_hack; // Temporary hack until the display is rewritten to operate in T-cycle rates; ); /* APU */ @@ -371,7 +368,7 @@ struct GB_gameboy_internal_s { uint8_t oam[0xA0]; uint8_t background_palettes_data[0x40]; uint8_t sprite_palettes_data[0x40]; - int16_t previous_lcdc_x; + uint8_t position_in_line; bool stat_interrupt_line; uint8_t effective_scx; uint8_t wy_diff; @@ -385,13 +382,14 @@ struct GB_gameboy_internal_s { GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG GB_FRAMESKIP_SECOND_FRAME_RENDERED, } frame_skip_state; - bool first_scanline; // The very first scan line after turning the LCD behaves differently. bool oam_read_blocked; bool vram_read_blocked; bool oam_write_blocked; bool vram_write_blocked; bool window_disabled_while_active; uint8_t effective_scy; // SCY is latched when starting to draw a tile + uint8_t current_line; + uint16_t ly_for_comparison; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/joypad.c b/Core/joypad.c index c780b2fd..15885d39 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -52,6 +52,7 @@ void GB_update_joyp(GB_gameboy_t *gb) if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ gb->io_registers[GB_IO_IF] |= 0x10; + gb->stopped = false; } gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support } diff --git a/Core/memory.c b/Core/memory.c index d4835064..f3cb4081 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -136,7 +136,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (addr < 0xFF80) { switch (addr & 0xFF) { case GB_IO_IF: - return gb->io_registers[GB_IO_IF] | 0xE0 | gb->future_interrupts; + return gb->io_registers[GB_IO_IF] | 0xE0; case GB_IO_TAC: return gb->io_registers[GB_IO_TAC] | 0xF8; case GB_IO_STAT: @@ -417,10 +417,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_window_related_write(gb, addr & 0xFF, value); break; case GB_IO_IF: - gb->future_interrupts = 0; case GB_IO_SCX: case GB_IO_SCY: - case GB_IO_LYC: case GB_IO_BGP: case GB_IO_OBP0: case GB_IO_OBP1: @@ -433,6 +431,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_UNKNOWN5: gb->io_registers[addr & 0xFF] = value; return; + case GB_IO_LYC: + gb->io_registers[addr & 0xFF] = value; + GB_STAT_update(gb); + return; case GB_IO_TIMA: if (gb->tima_reload_state != GB_TIMA_RELOADED) { @@ -456,7 +458,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_LCDC: if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->display_cycles = 0; - gb->first_scanline = true; + gb->display_state = 0; if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; } @@ -464,6 +466,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { /* Sync after turning off LCD */ GB_timing_sync(gb); + GB_lcd_off(gb); } /* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */ GB_window_related_write(gb, addr & 0xFF, value); @@ -481,6 +484,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_STAT] |= value & ~7; /* Set unused bit to 1 */ gb->io_registers[GB_IO_STAT] |= 0x80; + + GB_STAT_update(gb); return; case GB_IO_DIV: diff --git a/Core/timing.c b/Core/timing.c index 57c9a69f..2e160175 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -57,7 +57,7 @@ void GB_timing_sync(GB_gameboy_t *gb) return; } /* Prevent syncing if not enough time has passed.*/ - if (gb->cycles_since_last_sync < LCDC_PERIOD / 8) return; + if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return; uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); @@ -136,7 +136,7 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) { - GB_STATE_MACHINE(gb, div, cycles) { + GB_STATE_MACHINE(gb, div, cycles, 1) { GB_STATE(gb, div, 1); GB_STATE(gb, div, 2); } diff --git a/Core/timing.h b/Core/timing.h index edc41fae..1ea4b8af 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -18,7 +18,7 @@ enum { #define GB_HALT_VALUE (0xFFFF) #define GB_SLEEP(gb, unit, state, cycles) do {\ - (gb)->unit##_cycles -= cycles; \ + (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ if ((gb)->unit##_cycles <= 0) {\ (gb)->unit##_state = state;\ return;\ @@ -28,7 +28,8 @@ enum { #define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE -#define GB_STATE_MACHINE(gb, unit, cycles) \ +#define GB_STATE_MACHINE(gb, unit, cycles, divisor) \ +static const int __state_machine_divisor = divisor;\ (gb)->unit##_cycles += cycles; \ if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\ return;\ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 863dc8bc..1d464b00 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1337,30 +1337,27 @@ static GB_opcode_t *opcodes[256] = { }; void GB_cpu_run(GB_gameboy_t *gb) { + gb->vblank_just_occured = false; + if (gb->hdma_on) { GB_advance_cycles(gb, 4); return; } + if (gb->stopped) { + GB_advance_cycles(gb, 64); + return; + } - gb->vblank_just_occured = false; if (gb->halted) { - gb->display_hack = 1; GB_advance_cycles(gb, 2); } uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; if (gb->halted) { - gb->display_hack = 2; GB_advance_cycles(gb, 2); - gb->display_hack = 0; } - gb->io_registers[GB_IO_IF] |= gb->future_interrupts; - gb->future_interrupts = 0; - - - bool effecitve_ime = gb->ime; if (gb->ime_toggle) { gb->ime = !gb->ime; @@ -1381,9 +1378,7 @@ void GB_cpu_run(GB_gameboy_t *gb) interrupt_queue = gb->interrupt_enable; GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - interrupt_queue &= (gb->io_registers[GB_IO_IF] | gb->future_interrupts) & 0x1F; - gb->io_registers[GB_IO_IF] |= gb->future_interrupts; - gb->future_interrupts = 0; + interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; GB_advance_cycles(gb, 4); if (interrupt_queue) { @@ -1402,7 +1397,7 @@ void GB_cpu_run(GB_gameboy_t *gb) GB_debugger_call_hook(gb, call_addr); } /* Run mode */ - else if(!gb->halted && !gb->stopped) { + else if(!gb->halted) { uint8_t opcode = GB_read_memory(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; From 90a943d05a007155d67f6528fd260718e0e7582d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 25 Feb 2018 22:32:41 +0200 Subject: [PATCH 0536/1216] Emulate an HDMA quirk required to properly emulate Aevilia --- Core/display.c | 2 ++ Core/gb.h | 3 ++- Core/memory.c | 6 +++++- Core/z80_cpu.c | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 41f85bf1..dda9fab2 100755 --- a/Core/display.c +++ b/Core/display.c @@ -527,6 +527,8 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; + } + else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay + 16) { if (gb->hdma_on_hblank) { gb->hdma_on = true; gb->hdma_cycles = 0; diff --git a/Core/gb.h b/Core/gb.h index 3dea9961..c93aa3dc 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -254,7 +254,7 @@ struct GB_gameboy_internal_s { bool halted; bool stopped; bool boot_rom_finished; - bool ime_toggle; /* ei (and di in CGB) have delayed effects.*/ + bool ime_toggle; /* ei has delayed a effect.*/ bool halt_bug; /* Misc state */ @@ -275,6 +275,7 @@ struct GB_gameboy_internal_s { uint16_t dma_current_src; int16_t dma_cycles; bool is_dma_restarting; + uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ ); /* MBC */ diff --git a/Core/memory.c b/Core/memory.c index ef2f007a..dca92aac 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -111,6 +111,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0xFE00) { + if (gb->hdma_on) { + return gb->last_opcode_read; + } return gb->ram[addr & 0x0FFF]; } @@ -122,7 +125,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF00) { - /* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */ + /* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */ + /* Also, writes to this area are not emulated */ if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */ return 0xFF; } diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 16dbd08e..4794df59 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1392,12 +1392,12 @@ void GB_cpu_run(GB_gameboy_t *gb) GB_debugger_call_hook(gb, call_addr); } else if(!gb->halted && !gb->stopped) { - uint8_t opcode = GB_read_memory(gb, gb->pc++); + gb->last_opcode_read = GB_read_memory(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; gb->halt_bug = false; } - opcodes[opcode](gb, opcode); + opcodes[gb->last_opcode_read](gb, gb->last_opcode_read); } else { GB_advance_cycles(gb, 4); From b02e40d5a2865a97fa4e65c940660e74baa44fdd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 25 Feb 2018 23:23:55 +0200 Subject: [PATCH 0537/1216] Refinement to that last fix --- Core/memory.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index dca92aac..2bd8215a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -110,10 +110,11 @@ static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) { + if (gb->hdma_on) { + return gb->last_opcode_read; + } + if (addr < 0xFE00) { - if (gb->hdma_on) { - return gb->last_opcode_read; - } return gb->ram[addr & 0x0FFF]; } From 980acc3fb877a8e5d13d18c85d7a844f470ec45b Mon Sep 17 00:00:00 2001 From: Panda Habert Date: Mon, 26 Feb 2018 02:27:50 +0100 Subject: [PATCH 0538/1216] Make the last option's purpose more explicit I mistook it several times for the option to exit the menu :/ --- SDL/gui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index 03c9513e..c152e596 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -239,7 +239,7 @@ static const struct menu_item paused_menu[] = { {"Keyboard", enter_controls_menu}, {"Joypad", enter_joypad_menu}, {"Help", item_help}, - {"Exit", item_exit}, + {"Quit SameBoy", item_exit}, {NULL,} }; From fb03479a1f0598c2783b3978be09a476c810cf9d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 28 Feb 2018 19:39:22 +0200 Subject: [PATCH 0539/1216] Added 16-bit dereferencing operator (`{address}`) to the debugger. Closes #38 --- Core/debugger.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 7fc7b2a9..9fe68544 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -12,6 +12,7 @@ typedef struct { typedef struct { enum { LVALUE_MEMORY, + LVALUE_MEMORY16, LVALUE_REG16, LVALUE_REG_H, LVALUE_REG_L, @@ -209,6 +210,19 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) return r; } return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + + case LVALUE_MEMORY16: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | + (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); + case LVALUE_REG16: return VALUE_16(*lvalue.register_address); @@ -235,6 +249,19 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) } GB_write_memory(gb, lvalue.memory_address.value, value); return; + + case LVALUE_MEMORY16: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); + GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8); + return; case LVALUE_REG16: *lvalue.register_address = value; @@ -382,6 +409,22 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; } } + else if (string[0] == '{' && string[length - 1] == '}') { + // Attempt to strip curly 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_MEMORY16, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + } + } // Registers if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { @@ -486,7 +529,33 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } goto exit; } - + } + else if (string[0] == '{' && string[length - 1] == '}') { + // Attempt to strip curly 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) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + if (addr.bank) { + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + } + ret = VALUE_16(GB_read_memory(gb, addr.value) | (GB_read_memory(gb, addr.value + 1) * 0x100)); + if (addr.bank) { + restore_banking_state(gb, &state); + } + goto exit; + } } // Search for lowest priority operator signed int depth = 0; From 7248403be7c8a8e56f6945543b56b4de85dd1091 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 1 Mar 2018 00:12:04 +0200 Subject: [PATCH 0540/1216] Fixed several DMG regressions, fixes Pinball Deluxe again --- Core/display.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 16ef88cf..b15ce542 100755 --- a/Core/display.c +++ b/Core/display.c @@ -309,6 +309,8 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m void GB_STAT_update(GB_gameboy_t *gb) { + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; + bool previous_interrupt_line = gb->stat_interrupt_line; gb->stat_interrupt_line = false; /* Set LY=LYC bit */ @@ -360,6 +362,7 @@ void GB_lcd_off(GB_gameboy_t *gb) /* Todo: is this correct? */ gb->hdma_steps_left = 0xff; } + gb->stat_interrupt_line = false; gb->oam_read_blocked = false; gb->vram_read_blocked = false; @@ -371,8 +374,6 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->window_disabled_while_active = false; gb->current_line = 0; gb->ly_for_comparison = 0; - - GB_STAT_update(gb); } void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) @@ -397,6 +398,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); GB_STATE(gb, display, 18); + GB_STATE(gb, display, 19); + } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -447,7 +450,17 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->oam_write_blocked = false; gb->ly_for_comparison = gb->current_line? -1 : gb->current_line; GB_STAT_update(gb); - GB_SLEEP(gb, display, 4, 4); + GB_SLEEP(gb, display, 4, 3); + + /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 1. + PPU glitch? */ + if (gb->current_line != 0) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + GB_STAT_update(gb); + gb->io_registers[GB_IO_STAT] &= ~3; + } + GB_SLEEP(gb, display, 19, 1); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; From 94c6dbd2811031d2dce02b7fd9784d80160deeb9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 1 Mar 2018 21:12:37 +0200 Subject: [PATCH 0541/1216] =?UTF-8?q?Fixed=20=E2=80=98call=E2=80=99=20inst?= =?UTF-8?q?ruction=20not=20being=20properly=20symbolicated.=20Closes=20#37?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/z80_disassembler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c index 08fb62f0..d45cbe70 100644 --- a/Core/z80_disassembler.c +++ b/Core/z80_disassembler.c @@ -503,7 +503,7 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); - const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr); + const char *symbol = GB_debugger_name_for_address(gb, addr); if (symbol) { GB_log(gb, "CALL %s ; =$%04x\n", symbol, addr); } From a67db0595bdf0f0efb5e7e1138d95c0a9ad1f4fc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 1 Mar 2018 22:03:56 +0200 Subject: [PATCH 0542/1216] Fixed window behavior --- Core/display.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index b15ce542..56845668 100755 --- a/Core/display.c +++ b/Core/display.c @@ -760,22 +760,21 @@ void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value) gb->io_registers[addr] = value; bool after = window_enabled(gb); - if (before != after && gb->display_cycles < LINES * LINE_LENGTH) { + if (before != after && gb->current_line >= LINES) { /* Window was disabled or enabled outside of vblank */ - uint8_t current_line = gb->display_cycles / LINE_LENGTH; - if (current_line >= gb->io_registers[GB_IO_WY]) { + if (gb->current_line >= gb->io_registers[GB_IO_WY]) { if (after) { if (!gb->window_disabled_while_active) { /* Window was turned on for the first time this frame while LY > WY, should start window in the next line */ - gb->wy_diff = current_line + 1 - gb->io_registers[GB_IO_WY]; + gb->wy_diff = gb->current_line + 1 - gb->io_registers[GB_IO_WY]; } else { - gb->wy_diff += current_line; + gb->wy_diff += gb->current_line; } } else { - gb->wy_diff -= current_line; + gb->wy_diff -= gb->current_line; gb->window_disabled_while_active = true; } } From 3c8f3ad3fc07f8dee9d948c6de4054e5808ce4af Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 2 Mar 2018 19:37:40 +0200 Subject: [PATCH 0543/1216] Stop annoying beeps and exceptions --- Cocoa/GBView.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 5e1e31a5..4773d38b 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -204,7 +204,7 @@ } } - if (!handled) { + if (!handled && [theEvent type] != NSEventTypeFlagsChanged) { [super keyDown:theEvent]; } } @@ -238,7 +238,7 @@ } } - if (!handled) { + if (!handled && [theEvent type] != NSEventTypeFlagsChanged) { [super keyUp:theEvent]; } } From 6e8567eadca6ccdef5d78dd399e4f458e7216777 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 2 Mar 2018 19:42:02 +0200 Subject: [PATCH 0544/1216] =?UTF-8?q?Silence=20some=20annoying=20Cocoa=20w?= =?UTF-8?q?arnings,=20hopefully=20it=20won=E2=80=99t=20affect=20performanc?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cocoa/Document.m | 4 ++-- Cocoa/GBView.m | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index d4a4288c..a7ab0e1a 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -174,11 +174,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) vblank { - self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [self.view flip]; GB_set_pixels_output(&gb, self.view.pixels); if (self.vramWindow.isVisible) { dispatch_async(dispatch_get_main_queue(), ^{ + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [self reloadVRAMData: nil]; }); } @@ -196,7 +196,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { GB_apu_copy_buffer(&gb, buffer, nFrames); } andSampleRate:96000]; - self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { [self.audioClient start]; } @@ -225,6 +224,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) start { if (running) return; + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; } diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 4773d38b..d329328e 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -166,7 +166,9 @@ GB_set_clock_multiplier(_gb, clockMultiplier); } current_buffer = (current_buffer + 1) % self.numberOfBuffers; - [self setNeedsDisplay:YES]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self setNeedsDisplay:YES]; + }); } - (uint32_t *) pixels From b08f02c4f3265c78f53ffd4597f9fcb4db5d85b2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Mar 2018 15:47:36 +0200 Subject: [PATCH 0545/1216] Rewriting the PPU rendering: T-cycle accurate background rendering. DMG only, CGB completely broken --- Core/display.c | 209 +++++++++++++++++++++++++++++++++++----------- Core/gb.h | 20 +++++ Core/save_state.c | 12 +++ 3 files changed, 191 insertions(+), 50 deletions(-) diff --git a/Core/display.c b/Core/display.c index 56845668..df338908 100755 --- a/Core/display.c +++ b/Core/display.c @@ -4,6 +4,43 @@ #include #include "gb.h" +/* FIFO functions */ + +static inline unsigned fifo_size(GB_fifo_t *fifo) +{ + return (fifo->write_end - fifo->read_end) & 0xF; +} + +static void fifo_clear(GB_fifo_t *fifo) +{ + fifo->read_end = fifo->write_end = 0; +} + +static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) +{ + GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end]; + fifo->read_end++; + fifo->read_end &= 0xF; + return ret; +} + +static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority) +{ + for (unsigned i = 8; i--;) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + (lower >> 7) | ((upper >> 7) << 1), + palette, + 0, + bg_priority, + }; + lower <<= 1; + upper <<= 1; + + fifo->write_end++; + fifo->write_end &= 0xF; + } +} + /* Each line is 456 cycles, approximately: Mode 2 - 80 cycles / OAM Transfer @@ -41,7 +78,8 @@ static bool window_enabled(GB_gameboy_t *gb) return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167; } -static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) +/* Kept as a reference until I finish rewriting the PPU */ +static uint32_t __attribute__((unused)) get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) { /* Bit 7 - LCD Display Enable (0=Off, 1=On) @@ -337,12 +375,6 @@ void GB_STAT_update(GB_gameboy_t *gb) } } -static unsigned scx_delay(GB_gameboy_t *gb) -{ - return (gb->effective_scx & 7) + 0; - -} - void GB_lcd_off(GB_gameboy_t *gb) { gb->display_state = 0; @@ -354,7 +386,6 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->io_registers[GB_IO_LY] = 0; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 4; - gb->effective_scx = gb->io_registers[GB_IO_SCX]; if (gb->hdma_on_hblank) { gb->hdma_on_hblank = false; gb->hdma_on = false; @@ -376,6 +407,31 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->ly_for_comparison = 0; } +static void render_pixel_if_possible(GB_gameboy_t *gb) +{ + /* Drop pixels for scrollings */ + if (gb->position_in_line >= 160 || gb->disable_rendering) { + gb->position_in_line++; + if (fifo_size(&gb->bg_fifo) != 0) { + fifo_pop(&gb->bg_fifo); + } + return; + } + +#ifndef NDEBUG + if (fifo_size(&gb->bg_fifo) == 0) { + GB_log(gb, "Defective BG FIFO!\n"); + return; + } +#endif + + GB_fifo_item_t *fifo_item = fifo_pop(&gb->bg_fifo); + + uint8_t pixel = ((gb->io_registers[GB_IO_BGP] >> (fifo_item->pixel << 1)) & 3); + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[pixel]; + gb->position_in_line++; +} + void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { @@ -397,14 +453,21 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 15); GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); - GB_STATE(gb, display, 18); - GB_STATE(gb, display, 19); + + GB_STATE(gb, display, 21); + GB_STATE(gb, display, 22); + GB_STATE(gb, display, 23); + GB_STATE(gb, display, 24); + GB_STATE(gb, display, 25); + GB_STATE(gb, display, 26); + GB_STATE(gb, display, 27); + GB_STATE(gb, display, 28); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { while (true) { - GB_SLEEP(gb, display, 18, LCDC_PERIOD); + GB_SLEEP(gb, display, 1, LCDC_PERIOD); display_vblank(gb); } return; @@ -418,28 +481,30 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; + gb->cycles_for_line = MODE2_LENGTH - 4; GB_STAT_update(gb); - GB_SLEEP(gb, display, 1, MODE2_LENGTH - 4); + GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; - gb->effective_scx = gb->io_registers[GB_IO_SCX]; gb->oam_read_blocked = true; gb->vram_read_blocked = true; gb->oam_write_blocked = true; gb->vram_write_blocked = true; GB_STAT_update(gb); - GB_SLEEP(gb, display, 2, MODE3_LENGTH + scx_delay(gb) + 2); + gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) + 2; + GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) + 2); gb->io_registers[GB_IO_STAT] &= ~3; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; - GB_SLEEP(gb, display, 17, 1); + gb->cycles_for_line += 1; + GB_SLEEP(gb, display, 4, 1); GB_STAT_update(gb); /* Mode 0 is shorter in the very first line */ - GB_SLEEP(gb, display, 3, MODE0_LENGTH - scx_delay(gb) - 2 - 1 - 4); + GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); gb->current_line = 1; while (true) { @@ -450,7 +515,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->oam_write_blocked = false; gb->ly_for_comparison = gb->current_line? -1 : gb->current_line; GB_STAT_update(gb); - GB_SLEEP(gb, display, 4, 3); + GB_SLEEP(gb, display, 6, 3); /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 1. PPU glitch? */ @@ -460,55 +525,100 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); gb->io_registers[GB_IO_STAT] &= ~3; } - GB_SLEEP(gb, display, 19, 1); + GB_SLEEP(gb, display, 7, 1); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; gb->oam_write_blocked = true; gb->ly_for_comparison = gb->current_line; GB_STAT_update(gb); - GB_SLEEP(gb, display, 5, MODE2_LENGTH - 4); + GB_SLEEP(gb, display, 8, MODE2_LENGTH - 4); gb->vram_read_blocked = true; gb->vram_write_blocked = false; gb->oam_write_blocked = false; GB_STAT_update(gb); - GB_SLEEP(gb, display, 6, 4); + GB_SLEEP(gb, display, 9, 4); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; - gb->effective_scx = gb->io_registers[GB_IO_SCX]; gb->vram_write_blocked = true; gb->oam_write_blocked = true; GB_STAT_update(gb); - for (gb->position_in_line = 0; gb->position_in_line < WIDTH + 7; gb->position_in_line++) { - if (!gb->disable_rendering) { - signed screen_pos = (signed) gb->position_in_line - (gb->effective_scx & 0x7); - if (((screen_pos + gb->effective_scx) & 7) == 0) { - gb->effective_scy = gb->io_registers[GB_IO_SCY]; - } - if (screen_pos >= 0 && screen_pos < WIDTH) { - gb->screen[gb->current_line * WIDTH + screen_pos] = get_pixel(gb, screen_pos, gb->current_line); + gb->cycles_for_line = MODE2_LENGTH + 4; + fifo_clear(&gb->bg_fifo); + gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; + gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; + +#define RENDER_AND_SLEEP(label) \ +{ \ +render_pixel_if_possible(gb); \ +if (gb->position_in_line == 160) break; \ +else if (gb->position_in_line == 159) {\ + gb->io_registers[GB_IO_STAT] &= ~3; \ + gb->oam_read_blocked = false; \ + gb->vram_read_blocked = false; \ + gb->oam_write_blocked = false; \ + gb->vram_write_blocked = false; \ + if (gb->hdma_on_hblank) { \ + gb->hdma_on = true; \ + gb->hdma_cycles = 0; \ + } \ +} \ +gb->cycles_for_line++; \ +GB_SLEEP(gb, display, label, 1); \ +} + gb->cycles_for_line += 6; + GB_SLEEP(gb, display, 10, 6); + + /* The actual rendering cycle */ + while (true) { + // First read; the tile + RENDER_AND_SLEEP(21); + { + uint16_t map = 0x1800; + if (gb->io_registers[GB_IO_LCDC] & 0x08) { + map = 0x1C00; } + uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY]; + gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; + gb->fetcher_x++; + gb->fetcher_x &= 0x1f; } - GB_SLEEP(gb, display, 15, 1); + RENDER_AND_SLEEP(22); + + // Second read, lower tile data + RENDER_AND_SLEEP(23); + { + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + gb->current_tile_address = gb->current_tile * 0x10; + } + else { + gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + gb->current_tile_data[0] = + gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2]; + } + RENDER_AND_SLEEP(24); + + + // Third read, upper tile data + RENDER_AND_SLEEP(25); + gb->current_tile_data[1] = + gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2 + 1]; + RENDER_AND_SLEEP(26); + + + // The cycle ends with a fetcher sleep + RENDER_AND_SLEEP(27); + RENDER_AND_SLEEP(28); + + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); } - GB_SLEEP(gb, display, 7, MODE3_LENGTH + scx_delay(gb) - WIDTH - 7); - - gb->io_registers[GB_IO_STAT] &= ~3; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - if (gb->hdma_on_hblank) { - gb->hdma_on = true; - gb->hdma_cycles = 0; - } - GB_SLEEP(gb, display, 16, 1); GB_STAT_update(gb); - GB_SLEEP(gb, display, 8, MODE0_LENGTH - scx_delay(gb) - 4 - 1); + GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); } /* Lines 144 - 152 */ @@ -516,7 +626,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_LY] = gb->current_line; gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 9, 4); + GB_SLEEP(gb, display, 12, 4); gb->ly_for_comparison = gb->current_line; if (gb->current_line == LINES) { @@ -544,27 +654,27 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } GB_STAT_update(gb); - GB_SLEEP(gb, display, 10, LINE_LENGTH - 4); + GB_SLEEP(gb, display, 13, LINE_LENGTH - 4); } /* Lines 153 */ gb->io_registers[GB_IO_LY] = 153; gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 11, 4); + GB_SLEEP(gb, display, 14, 4); gb->io_registers[GB_IO_LY] = 0; gb->ly_for_comparison = 153; GB_STAT_update(gb); - GB_SLEEP(gb, display, 12, 4); + GB_SLEEP(gb, display, 15, 4); gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 13, 4); + GB_SLEEP(gb, display, 16, 4); gb->ly_for_comparison = 0; GB_STAT_update(gb); - GB_SLEEP(gb, display, 14, LINE_LENGTH - 12); + GB_SLEEP(gb, display, 17, LINE_LENGTH - 12); gb->io_registers[GB_IO_STAT] &= ~3; @@ -728,7 +838,6 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } } - for (unsigned i = 0; i < count; i++) { uint16_t vram_address = dest[i].tile * 0x10; uint8_t flags = dest[i].flags; diff --git a/Core/gb.h b/Core/gb.h index 9ff5853a..2ea65bde 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -189,6 +189,19 @@ typedef struct { struct GB_breakpoint_s; struct GB_watchpoint_s; +typedef struct { + uint8_t pixel; // Color, 0-3 + uint8_t palette; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) + uint8_t priority; // Sprite priority – 0 in DMG, OAM index in CGB + bool bg_priority; // For sprite FIFO – the BG priority bit. For the BG FIFO – the CGB attributes priority bit +} GB_fifo_item_t; + +typedef struct { + GB_fifo_item_t fifo[16]; + uint8_t read_end; + uint8_t write_end; +} GB_fifo_t; + /* When state saving, each section is dumped independently of other sections. This allows adding data to the end of the section without worrying about future compatibility. Some other changes might be "safe" as well. @@ -391,6 +404,13 @@ struct GB_gameboy_internal_s { uint8_t effective_scy; // SCY is latched when starting to draw a tile uint8_t current_line; uint16_t ly_for_comparison; + GB_fifo_t bg_fifo, oam_fifo; + uint8_t fetcher_x; + uint16_t cycles_for_line; + uint8_t current_tile; + uint16_t current_tile_address; // TODO: is this actually cached? If not, it could be used to "mix" two tiles + uint8_t current_tile_data[2]; + ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/save_state.c b/Core/save_state.c index 933b4178..e060ae60 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -218,6 +218,12 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) GB_palette_changed(gb, true, i * 2); } + gb->bg_fifo.read_end &= 0xF; + gb->bg_fifo.write_end &= 0xF; + gb->oam_fifo.read_end &= 0xF; + gb->oam_fifo.write_end &= 0xF; + gb->current_tile_address &= (gb->vram_size - 1); + error: fclose(f); return errno; @@ -308,6 +314,12 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le GB_palette_changed(gb, true, i * 2); } + gb->bg_fifo.read_end &= 0xF; + gb->bg_fifo.write_end &= 0xF; + gb->oam_fifo.read_end &= 0xF; + gb->oam_fifo.write_end &= 0xF; + gb->current_tile_address &= (gb->vram_size - 1); + return 0; } From 5ea33cc9319dc961a0087b663b27232b3a51a3d0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Mar 2018 17:22:23 +0200 Subject: [PATCH 0546/1216] Cleanup --- Core/display.c | 140 ++++++++++++++++++++++--------------------------- Core/gb.h | 9 ++++ 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/Core/display.c b/Core/display.c index df338908..fa5a40c7 100755 --- a/Core/display.c +++ b/Core/display.c @@ -409,23 +409,18 @@ void GB_lcd_off(GB_gameboy_t *gb) static void render_pixel_if_possible(GB_gameboy_t *gb) { + GB_fifo_item_t *fifo_item = NULL; + if (!gb->fifo_paused) { + fifo_item = fifo_pop(&gb->bg_fifo); + } + /* Drop pixels for scrollings */ if (gb->position_in_line >= 160 || gb->disable_rendering) { gb->position_in_line++; - if (fifo_size(&gb->bg_fifo) != 0) { - fifo_pop(&gb->bg_fifo); - } return; } - -#ifndef NDEBUG - if (fifo_size(&gb->bg_fifo) == 0) { - GB_log(gb, "Defective BG FIFO!\n"); - return; - } -#endif - GB_fifo_item_t *fifo_item = fifo_pop(&gb->bg_fifo); + if (gb->fifo_paused) return; uint8_t pixel = ((gb->io_registers[GB_IO_BGP] >> (fifo_item->pixel << 1)) & 3); gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[pixel]; @@ -453,15 +448,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 15); GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); - - GB_STATE(gb, display, 21); - GB_STATE(gb, display, 22); - GB_STATE(gb, display, 23); - GB_STATE(gb, display, 24); - GB_STATE(gb, display, 25); - GB_STATE(gb, display, 26); - GB_STATE(gb, display, 27); - GB_STATE(gb, display, 28); + + GB_STATE(gb, display, 20); } @@ -551,72 +539,72 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; -#define RENDER_AND_SLEEP(label) \ -{ \ -render_pixel_if_possible(gb); \ -if (gb->position_in_line == 160) break; \ -else if (gb->position_in_line == 159) {\ - gb->io_registers[GB_IO_STAT] &= ~3; \ - gb->oam_read_blocked = false; \ - gb->vram_read_blocked = false; \ - gb->oam_write_blocked = false; \ - gb->vram_write_blocked = false; \ - if (gb->hdma_on_hblank) { \ - gb->hdma_on = true; \ - gb->hdma_cycles = 0; \ - } \ -} \ -gb->cycles_for_line++; \ -GB_SLEEP(gb, display, label, 1); \ -} gb->cycles_for_line += 6; GB_SLEEP(gb, display, 10, 6); /* The actual rendering cycle */ + gb->fetcher_divisor = false; + gb->fetcher_state = GB_FETCHER_GET_TILE; + gb->fifo_paused = true; while (true) { - // First read; the tile - RENDER_AND_SLEEP(21); - { - uint16_t map = 0x1800; - if (gb->io_registers[GB_IO_LCDC] & 0x08) { - map = 0x1C00; + if (gb->fetcher_divisor) { + switch (gb->fetcher_state) { + case GB_FETCHER_GET_TILE: { + uint16_t map = 0x1800; + if (gb->io_registers[GB_IO_LCDC] & 0x08) { + map = 0x1C00; + } + uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY]; + gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; + gb->fetcher_x++; + gb->fetcher_x &= 0x1f; + } + break; + + case GB_FETCHER_GET_TILE_DATA_LOWER: { + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + gb->current_tile_address = gb->current_tile * 0x10; + } + else { + gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + gb->current_tile_data[0] = + gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2]; + } + break; + + case GB_FETCHER_GET_TILE_DATA_HIGH: { + gb->current_tile_data[1] = + gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2 + 1]; + } + break; + + case GB_FETCHER_SLEEP: + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); + gb->fifo_paused = false; + break; } - uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY]; - gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; - gb->fetcher_x++; - gb->fetcher_x &= 0x1f; + gb->fetcher_state++; + gb->fetcher_state &= 3; } - RENDER_AND_SLEEP(22); - - // Second read, lower tile data - RENDER_AND_SLEEP(23); - { - if (gb->io_registers[GB_IO_LCDC] & 0x10) { - gb->current_tile_address = gb->current_tile * 0x10; + gb->fetcher_divisor ^= true; + + render_pixel_if_possible(gb); + if (gb->position_in_line == 160) break; + else if (gb->position_in_line == 159) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + if (gb->hdma_on_hblank) { + gb->hdma_on = true; + gb->hdma_cycles = 0; } - else { - gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; - } - gb->current_tile_data[0] = - gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2]; } - RENDER_AND_SLEEP(24); - - - // Third read, upper tile data - RENDER_AND_SLEEP(25); - gb->current_tile_data[1] = - gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2 + 1]; - RENDER_AND_SLEEP(26); - - - // The cycle ends with a fetcher sleep - RENDER_AND_SLEEP(27); - RENDER_AND_SLEEP(28); - - fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 20, 1); } - GB_STAT_update(gb); GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); } diff --git a/Core/gb.h b/Core/gb.h index 2ea65bde..68c7c0db 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -410,6 +410,15 @@ struct GB_gameboy_internal_s { uint8_t current_tile; uint16_t current_tile_address; // TODO: is this actually cached? If not, it could be used to "mix" two tiles uint8_t current_tile_data[2]; + enum { + GB_FETCHER_GET_TILE, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_SLEEP, + GB_FETCHER_MAX = GB_FETCHER_SLEEP, + } fetcher_state:8; + bool fetcher_divisor; // The fetcher runs at 2MHz + bool fifo_paused; ); From 496c5589e6a1506596c116026a268b76b25daac0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Mar 2018 19:36:21 +0200 Subject: [PATCH 0547/1216] Added window support --- Core/display.c | 54 ++++++++++++++++++++++++++++++++++++++++++-------- Core/gb.h | 2 +- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Core/display.c b/Core/display.c index fa5a40c7..f227724d 100755 --- a/Core/display.c +++ b/Core/display.c @@ -419,14 +419,37 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) gb->position_in_line++; return; } - if (gb->fifo_paused) return; - uint8_t pixel = ((gb->io_registers[GB_IO_BGP] >> (fifo_item->pixel << 1)) & 3); - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[pixel]; + /* Mixing */ + bool bg_enabled = true, bg_behind = false; + + if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if (gb->cgb_mode) { + bg_behind = true; + } + else { + bg_enabled = false; + } + } + if (!bg_enabled) { + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + } + else { + uint8_t pixel = fifo_item->pixel; + if (!gb->cgb_mode) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[pixel]; + } gb->position_in_line++; } +static inline uint8_t scrolled_y(GB_gameboy_t *gb) +{ + return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); +} + void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { @@ -546,16 +569,31 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_divisor = false; gb->fetcher_state = GB_FETCHER_GET_TILE; gb->fifo_paused = true; + gb->in_window = false; while (true) { + /* Handle window */ + /* Todo: Timing not verified by test ROM */ + if (!gb->in_window && window_enabled(gb) && + gb->current_line >= gb->io_registers[GB_IO_WY] + gb->wy_diff && + gb->position_in_line + 7 == gb->io_registers[GB_IO_WX]) { + gb->in_window = true; + fifo_clear(&gb->bg_fifo); + gb->fifo_paused = true; + gb->fetcher_x = 0; + gb->fetcher_state = GB_FETCHER_GET_TILE; + } + if (gb->fetcher_divisor) { switch (gb->fetcher_state) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; - if (gb->io_registers[GB_IO_LCDC] & 0x08) { + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) { map = 0x1C00; } - uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY]; - gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { + map = 0x1C00; + } + gb->current_tile = gb->vram[map + gb->fetcher_x + scrolled_y(gb) / 8 * 32]; gb->fetcher_x++; gb->fetcher_x &= 0x1f; } @@ -569,13 +607,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; } gb->current_tile_data[0] = - gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2]; + gb->vram[gb->current_tile_address + (scrolled_y(gb) & 7) * 2]; } break; case GB_FETCHER_GET_TILE_DATA_HIGH: { gb->current_tile_data[1] = - gb->vram[gb->current_tile_address + ((gb->current_line + gb->io_registers[GB_IO_SCY]) & 7) * 2 + 1]; + gb->vram[gb->current_tile_address + (scrolled_y(gb) & 7) * 2 + 1]; } break; diff --git a/Core/gb.h b/Core/gb.h index 68c7c0db..b4ff2abc 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -419,7 +419,7 @@ struct GB_gameboy_internal_s { } fetcher_state:8; bool fetcher_divisor; // The fetcher runs at 2MHz bool fifo_paused; - + bool in_window; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 518746f664a63c6f210b391aaa66871ca45da590 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Mar 2018 19:52:48 +0200 Subject: [PATCH 0548/1216] fixed rendering off by one --- Core/display.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index f227724d..22cd8470 100755 --- a/Core/display.c +++ b/Core/display.c @@ -582,7 +582,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_x = 0; gb->fetcher_state = GB_FETCHER_GET_TILE; } - + bool push = false; if (gb->fetcher_divisor) { switch (gb->fetcher_state) { case GB_FETCHER_GET_TILE: { @@ -618,8 +618,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) break; case GB_FETCHER_SLEEP: - fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); - gb->fifo_paused = false; + push = true; break; } gb->fetcher_state++; @@ -628,6 +627,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_divisor ^= true; render_pixel_if_possible(gb); + if (push) { + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); + gb->fifo_paused = false; + } if (gb->position_in_line == 160) break; else if (gb->position_in_line == 159) { gb->io_registers[GB_IO_STAT] &= ~3; From 476133abd04fc27cdff5ab22d90cca13090de3d7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Mar 2018 20:51:38 +0200 Subject: [PATCH 0549/1216] The scrolled y value is cached and not recalculated --- Core/display.c | 14 +++++--------- Core/gb.h | 5 +++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Core/display.c b/Core/display.c index 22cd8470..f6c0e575 100755 --- a/Core/display.c +++ b/Core/display.c @@ -444,12 +444,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } gb->position_in_line++; } - -static inline uint8_t scrolled_y(GB_gameboy_t *gb) -{ - return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); -} - void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { @@ -593,7 +587,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { map = 0x1C00; } - gb->current_tile = gb->vram[map + gb->fetcher_x + scrolled_y(gb) / 8 * 32]; + gb->fetcher_y = + gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); + gb->current_tile = gb->vram[map + gb->fetcher_x + gb->fetcher_y / 8 * 32]; gb->fetcher_x++; gb->fetcher_x &= 0x1f; } @@ -607,13 +603,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; } gb->current_tile_data[0] = - gb->vram[gb->current_tile_address + (scrolled_y(gb) & 7) * 2]; + gb->vram[gb->current_tile_address + (gb->fetcher_y & 7) * 2]; } break; case GB_FETCHER_GET_TILE_DATA_HIGH: { gb->current_tile_data[1] = - gb->vram[gb->current_tile_address + (scrolled_y(gb) & 7) * 2 + 1]; + gb->vram[gb->current_tile_address + (gb->fetcher_y & 7) * 2 + 1]; } break; diff --git a/Core/gb.h b/Core/gb.h index b4ff2abc..56215c1b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -401,14 +401,15 @@ struct GB_gameboy_internal_s { bool oam_write_blocked; bool vram_write_blocked; bool window_disabled_while_active; - uint8_t effective_scy; // SCY is latched when starting to draw a tile + uint8_t effective_scy; // Todo: delete me! uint8_t current_line; uint16_t ly_for_comparison; GB_fifo_t bg_fifo, oam_fifo; uint8_t fetcher_x; + uint8_t fetcher_y; uint16_t cycles_for_line; uint8_t current_tile; - uint16_t current_tile_address; // TODO: is this actually cached? If not, it could be used to "mix" two tiles + uint16_t current_tile_address; uint8_t current_tile_data[2]; enum { GB_FETCHER_GET_TILE, From 3d1c8b50c43fe992b5e557142bdb8ae193047d5a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 4 Mar 2018 22:21:56 +0200 Subject: [PATCH 0550/1216] OAM search and OAM timing in mode 3 --- Core/display.c | 98 +++++++++++++++++++++++++++++++++++++++----------- Core/gb.h | 3 ++ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/Core/display.c b/Core/display.c index f6c0e575..bf580e24 100755 --- a/Core/display.c +++ b/Core/display.c @@ -6,7 +6,7 @@ /* FIFO functions */ -static inline unsigned fifo_size(GB_fifo_t *fifo) +static inline unsigned __attribute__((unused)) fifo_size(GB_fifo_t *fifo) { return (fifo->write_end - fifo->read_end) & 0xF; } @@ -66,7 +66,7 @@ typedef struct __attribute__((packed)) { uint8_t x; uint8_t tile; uint8_t flags; -} GB_sprite_t; +} GB_object_t; static bool window_enabled(GB_gameboy_t *gb) { @@ -97,7 +97,7 @@ static uint32_t __attribute__((unused)) get_pixel(GB_gameboy_t *gb, uint8_t x, u uint8_t sprite_palette = 0; uint16_t tile_address = 0; uint8_t background_pixel = 0, sprite_pixel = 0; - GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + GB_object_t *sprite = (GB_object_t *) &gb->oam; uint8_t 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; @@ -407,6 +407,40 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->ly_for_comparison = 0; } +static void search_oam(GB_gameboy_t *gb) +{ + /* + We have very little means to test how the OAM search timing actually + works so we currently implement it atomically. + + This is enough for most cases except (All are TODOs): + - The OAM bug on the DMG (Not emulated at all) + - Changing object height via LCDC during mode 2 + - What about changing during mode 3? + - Enabling and disabling sprites during mode 2 + - Does this flag actually do anything in mode 2? Or only during mode 3? + */ + + /* This reverse sorts the visible objects by location and priority */ + + GB_object_t *objects = (GB_object_t *) &gb->oam; + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; + gb->n_visible_objs = 0; + for (uint8_t i = 0; i < 40; i++) { + signed y = objects[i].y - 16; + if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { + unsigned j = 0; + for (; j < gb->n_visible_objs; j++) { + if (objects[gb->visible_objs[j]].x <= objects[i].x) break; + } + memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); + gb->visible_objs[j] = i; + gb->n_visible_objs++; + if (gb->n_visible_objs == 10) return; + } + } +} + static void render_pixel_if_possible(GB_gameboy_t *gb) { GB_fifo_item_t *fifo_item = NULL; @@ -444,8 +478,10 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } gb->position_in_line++; } + void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { + GB_object_t *objects = (GB_object_t *) &gb->oam; GB_STATE_MACHINE(gb, display, cycles, 2) { GB_STATE(gb, display, 1); @@ -465,9 +501,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 15); GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); - + GB_STATE(gb, display, 19); GB_STATE(gb, display, 20); - + GB_STATE(gb, display, 21); + GB_STATE(gb, display, 22); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -529,6 +566,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] |= 2; GB_STAT_update(gb); gb->io_registers[GB_IO_STAT] &= ~3; + search_oam(gb); } GB_SLEEP(gb, display, 7, 1); @@ -556,8 +594,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; - gb->cycles_for_line += 6; - GB_SLEEP(gb, display, 10, 6); + gb->cycles_for_line += 5; + GB_SLEEP(gb, display, 10, 5); /* The actual rendering cycle */ gb->fetcher_divisor = false; @@ -565,6 +603,26 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fifo_paused = true; gb->in_window = false; while (true) { + /* Handle objects */ + while (gb->n_visible_objs != 0 && + (gb->io_registers[GB_IO_LCDC] & 2) != 0 && + objects[gb->visible_objs[gb->n_visible_objs - 1]].x == (uint8_t)(gb->position_in_line + 8)) { + + if (!gb->fetching_objects) { + /* Penalty for interrupting the fetcher */ + uint8_t penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state * 2 + gb->fetcher_divisor]; + gb->cycles_for_line += penalty; + GB_SLEEP(gb, display, 19, penalty); + } + + gb->fetching_objects = true; + gb->cycles_for_line += 6; + GB_SLEEP(gb, display, 20, 6); + gb->n_visible_objs--; + } + gb->fetching_objects = false; + + /* Handle window */ /* Todo: Timing not verified by test ROM */ if (!gb->in_window && window_enabled(gb) && @@ -628,20 +686,20 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fifo_paused = false; } if (gb->position_in_line == 160) break; - else if (gb->position_in_line == 159) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - if (gb->hdma_on_hblank) { - gb->hdma_on = true; - gb->hdma_cycles = 0; - } - } gb->cycles_for_line++; - GB_SLEEP(gb, display, 20, 1); + GB_SLEEP(gb, display, 21, 1); } + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + if (gb->hdma_on_hblank) { + gb->hdma_on = true; + gb->hdma_cycles = 0; + } + gb->cycles_for_line++; + GB_SLEEP(gb, display, 22, 1); GB_STAT_update(gb); GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); } @@ -836,7 +894,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; uint8_t oam_to_dest_index[40] = {0,}; for (unsigned y = 0; y < LINES; y++) { - GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + GB_object_t *sprite = (GB_object_t *) &gb->oam; uint8_t sprites_in_line = 0; for (uint8_t i = 0; i < 40; i++, sprite++) { int sprite_y = sprite->y - 16; diff --git a/Core/gb.h b/Core/gb.h index 56215c1b..c0c4514b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -421,6 +421,9 @@ struct GB_gameboy_internal_s { bool fetcher_divisor; // The fetcher runs at 2MHz bool fifo_paused; bool in_window; + uint8_t visible_objs[10]; + uint8_t n_visible_objs; + bool fetching_objects; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 88a11b891f1a73f6d6ba4efe429670fbd8f356d3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 4 Mar 2018 23:27:31 +0200 Subject: [PATCH 0551/1216] Object rendering --- Core/display.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index bf580e24..3bafb3e5 100755 --- a/Core/display.c +++ b/Core/display.c @@ -6,7 +6,7 @@ /* FIFO functions */ -static inline unsigned __attribute__((unused)) fifo_size(GB_fifo_t *fifo) +static inline unsigned fifo_size(GB_fifo_t *fifo) { return (fifo->write_end - fifo->read_end) & 0xF; } @@ -41,6 +41,31 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint } } +static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x) +{ + while (fifo_size(fifo) < 8) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,}; + fifo->write_end++; + fifo->write_end &= 0xF; + } + + uint8_t flip_xor = flip_x? 0: 0x7; + + for (unsigned i = 8; i--;) { + uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); + GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & 0xF]; + if (pixel != 0 && (target->pixel == 0 || target->priority > priority)) { + target->pixel = pixel; + target->palette = palette; + target->bg_priority = bg_priority; + target->priority = priority; + } + lower <<= 1; + upper <<= 1; + } +} + + /* Each line is 456 cycles, approximately: Mode 2 - 80 cycles / OAM Transfer @@ -444,10 +469,22 @@ static void search_oam(GB_gameboy_t *gb) static void render_pixel_if_possible(GB_gameboy_t *gb) { GB_fifo_item_t *fifo_item = NULL; + GB_fifo_item_t *oam_fifo_item = NULL; + bool draw_oam = false; + bool bg_enabled = true, bg_priority = false; + if (!gb->fifo_paused) { fifo_item = fifo_pop(&gb->bg_fifo); } + if (fifo_size(&gb->oam_fifo)) { + oam_fifo_item = fifo_pop(&gb->oam_fifo); + if (oam_fifo_item->pixel) { + draw_oam = true; + bg_priority |= oam_fifo_item->bg_priority; + } + } + /* Drop pixels for scrollings */ if (gb->position_in_line >= 160 || gb->disable_rendering) { gb->position_in_line++; @@ -456,26 +493,41 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (gb->fifo_paused) return; /* Mixing */ - bool bg_enabled = true, bg_behind = false; if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { if (gb->cgb_mode) { - bg_behind = true; + bg_priority = true; } else { bg_enabled = false; } } + if (!gb->is_cgb && gb->in_window) { + bg_enabled = true; + } + if (!bg_enabled) { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); } else { uint8_t pixel = fifo_item->pixel; + if (pixel && bg_priority) { + draw_oam = false; + } if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[pixel]; } + + if (draw_oam) { + uint8_t pixel = oam_fifo_item->pixel; + if (!gb->cgb_mode) { + pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); + } + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + } + gb->position_in_line++; } @@ -591,6 +643,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line = MODE2_LENGTH + 4; fifo_clear(&gb->bg_fifo); + fifo_clear(&gb->oam_fifo); gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; @@ -618,6 +671,32 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetching_objects = true; gb->cycles_for_line += 6; GB_SLEEP(gb, display, 20, 6); + GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ + uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); + + if (object->flags & 0x40) { /* Flip Y */ + tile_y ^= height_16? 0xF : 7; + } + uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; + + if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */ + line_address += 0x2000; + } + + uint8_t palette = (object->flags & 0x10) ? 1 : 0; + if (gb->cgb_mode) { + palette = object->flags & 0x7; + } +#if 1 + fifo_overlay_object_row(&gb->oam_fifo, + gb->vram[line_address], + gb->vram[line_address + 1], + palette, + object->flags & 0x80, + gb->cgb_mode? gb->visible_objs[gb->n_visible_objs - 1] : 0, + object->flags & 0x20); +#endif gb->n_visible_objs--; } gb->fetching_objects = false; From 544ca2be4c43c43c78c433a4f1751a027a4936d2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 5 Mar 2018 21:17:37 +0200 Subject: [PATCH 0552/1216] =?UTF-8?q?Changing=20the=20timings=20of=20memor?= =?UTF-8?q?y=20writes=20so=20they=E2=80=99re=20not=20effectively=20one=20T?= =?UTF-8?q?-cycle=20late.=20This=20screws=20up=20APU=E2=80=99s=20cycle=20a?= =?UTF-8?q?ccuracy=20for=20now.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 3 ++ Core/gb.c | 4 +-- Core/timing.c | 10 +++++- Core/z80_cpu.c | 85 ++++++++++++++++++++++++++++++-------------------- 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/Core/display.c b/Core/display.c index 3bafb3e5..d6eb07e3 100755 --- a/Core/display.c +++ b/Core/display.c @@ -557,6 +557,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 20); GB_STATE(gb, display, 21); GB_STATE(gb, display, 22); + GB_STATE(gb, display, 23); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -566,6 +567,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } return; } + + GB_SLEEP(gb, display, 23, 1); /* Handle the very first line 0 */ gb->current_line = 0; diff --git a/Core/gb.c b/Core/gb.c index b7aa3652..f382c8ee 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -496,8 +496,8 @@ void GB_reset(GB_gameboy_t *gb) } gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; } - /* The serial interrupt always occur on the 0xF8th cycle of every 0x100 cycle since boot. */ - gb->serial_cycles = 0x100 - 0xF8; + /* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */ + gb->serial_cycles = 0x100-0xF7; gb->io_registers[GB_IO_SC] = 0x7E; gb->magic = (uintptr_t)'SAME'; } diff --git a/Core/timing.c b/Core/timing.c index 2e160175..a9ff1560 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -178,7 +178,15 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->debugger_ticks += cycles; - cycles <<= !gb->cgb_double_speed; + if (!gb->cgb_double_speed) { + cycles <<= 1; + if ((cycles & 6) == 2) { + cycles--; + } + else if ((cycles & 6) == 6) { + cycles++; + } + } // Not affected by speed boost gb->hdma_cycles += cycles; diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 6a3cf705..0b582c4f 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -5,6 +5,24 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); +/* + About memroy timings: + + Each M-cycle consists of 4 T-cycles. Every time the CPU accesses the memory it happens on the 1st T-cycle of an + M-cycle. During that cycle, other things may happen, such the PPU drawing to the screen. Since we can't really run + things in parallel, we run non-CPU "activities" serially using advnace_cycles(...). This is normally not a problem, + unless two entities (e.g. both the CPU and the PPU) read the same register at the same time (e.g. BGP). Since memory + accesses happen for an enitre T-cycle, if someone reads a value while someone else changes it during in the same + T-cycle, the read will return the new value. To correctly emulate this, a memory access T-cycle looks like this: + + - Perform memory write (If needed) + - Run everything else + - Perform memory read (If needed) + + This is equivalent to running the memory write 1 T-cycle before the memory read. + + */ + static void ill(GB_gameboy_t *gb, uint8_t opcode) { GB_log(gb, "Illegal Opcode. Halting.\n"); @@ -63,10 +81,10 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); register_id = (opcode >> 4) + 1; GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -155,11 +173,11 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8; - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); GB_advance_cycles(gb, 4); GB_write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -392,16 +410,16 @@ static void ccf(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) @@ -425,9 +443,9 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; GB_advance_cycles(gb, 4); value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((value & 0x0F) == 0) { @@ -444,9 +462,9 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; GB_advance_cycles(gb, 4); value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; @@ -463,9 +481,9 @@ static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); uint8_t data = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], data); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) @@ -501,8 +519,9 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) gb->registers[GB_REGISTER_AF] |= value << 8; } else { + GB_advance_cycles(gb, 3); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 1); } } else { @@ -540,9 +559,9 @@ GB_advance_cycles(gb, 4);\ #define LD_DHL_Y(y) \ static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ { \ -GB_advance_cycles(gb, 4); \ +GB_advance_cycles(gb, 3); \ GB_write_memory(gb, gb->registers[GB_REGISTER_HL], gb->y); \ -GB_advance_cycles(gb, 4);\ +GB_advance_cycles(gb, 5);\ } LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) @@ -762,11 +781,11 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) uint16_t addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= (GB_read_memory(gb, gb->pc++) << 8); - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 7); GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); @@ -780,13 +799,13 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 7); register_id = ((opcode >> 4) + 1) & 3; gb->registers[GB_REGISTER_SP] -= 2; GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) @@ -931,12 +950,12 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 7); gb->registers[GB_REGISTER_SP] -= 2; GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); gb->pc = opcode ^ 0xC7; GB_debugger_call_hook(gb, call_addr); } @@ -966,11 +985,11 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) uint16_t addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= (GB_read_memory(gb, gb->pc++) << 8); - GB_advance_cycles(gb, 8); + GB_advance_cycles(gb, 7); GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); GB_advance_cycles(gb, 4); GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); } @@ -979,9 +998,9 @@ static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); uint8_t temp = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) @@ -996,9 +1015,9 @@ static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) @@ -1043,9 +1062,9 @@ static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= GB_read_memory(gb, gb->pc++) << 8; - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 3); GB_write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) @@ -1372,7 +1391,7 @@ void GB_cpu_run(GB_gameboy_t *gb) else if (effecitve_ime && interrupt_queue) { gb->halted = false; uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 12); + GB_advance_cycles(gb, 11); gb->registers[GB_REGISTER_SP] -= 2; GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); interrupt_queue = gb->interrupt_enable; @@ -1380,7 +1399,7 @@ void GB_cpu_run(GB_gameboy_t *gb) GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); if (interrupt_queue) { uint8_t interrupt_bit = 0; while (!(interrupt_queue & 1)) { From 1149c266cfc4bb6848dd7f5282af6eeab916909f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 8 Mar 2018 22:11:10 +0200 Subject: [PATCH 0553/1216] More regression fixes, actually fix Pinball Deluxe this time --- Core/display.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/display.c b/Core/display.c index d6eb07e3..cc61a92a 100755 --- a/Core/display.c +++ b/Core/display.c @@ -607,22 +607,22 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { + GB_SLEEP(gb, display, 6, 3); gb->io_registers[GB_IO_LY] = gb->current_line; gb->oam_read_blocked = true; gb->oam_write_blocked = false; gb->ly_for_comparison = gb->current_line? -1 : gb->current_line; - GB_STAT_update(gb); - GB_SLEEP(gb, display, 6, 3); - /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 1. + /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. PPU glitch? */ if (gb->current_line != 0) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; - GB_STAT_update(gb); - gb->io_registers[GB_IO_STAT] &= ~3; - search_oam(gb); } + GB_STAT_update(gb); + gb->io_registers[GB_IO_STAT] &= ~3; + + search_oam(gb); GB_SLEEP(gb, display, 7, 1); gb->io_registers[GB_IO_STAT] &= ~3; @@ -826,12 +826,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_LY] = 153; gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 14, 4); + GB_SLEEP(gb, display, 14, 6); gb->io_registers[GB_IO_LY] = 0; gb->ly_for_comparison = 153; GB_STAT_update(gb); - GB_SLEEP(gb, display, 15, 4); + GB_SLEEP(gb, display, 15, 2); gb->ly_for_comparison = -1; GB_STAT_update(gb); From a32f232bb1079977e90d27a5145a16ac4f678a2f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Mar 2018 17:10:19 +0200 Subject: [PATCH 0554/1216] Fixed OAM-window priority glitch, fixed OAM glitch in Prehistoric Man --- Core/display.c | 21 ++++++++++++--------- Core/gb.h | 3 ++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Core/display.c b/Core/display.c index cc61a92a..34ba843a 100755 --- a/Core/display.c +++ b/Core/display.c @@ -473,11 +473,11 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bool draw_oam = false; bool bg_enabled = true, bg_priority = false; - if (!gb->fifo_paused) { + if (!gb->bg_fifo_paused) { fifo_item = fifo_pop(&gb->bg_fifo); } - if (fifo_size(&gb->oam_fifo)) { + if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) { oam_fifo_item = fifo_pop(&gb->oam_fifo); if (oam_fifo_item->pixel) { draw_oam = true; @@ -490,7 +490,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) gb->position_in_line++; return; } - if (gb->fifo_paused) return; + if (gb->bg_fifo_paused) return; /* Mixing */ @@ -622,7 +622,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); gb->io_registers[GB_IO_STAT] &= ~3; - search_oam(gb); GB_SLEEP(gb, display, 7, 1); gb->io_registers[GB_IO_STAT] &= ~3; @@ -632,6 +631,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); GB_SLEEP(gb, display, 8, MODE2_LENGTH - 4); + search_oam(gb); gb->vram_read_blocked = true; gb->vram_write_blocked = false; gb->oam_write_blocked = false; @@ -656,7 +656,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The actual rendering cycle */ gb->fetcher_divisor = false; gb->fetcher_state = GB_FETCHER_GET_TILE; - gb->fifo_paused = true; + gb->bg_fifo_paused = true; + gb->oam_fifo_paused = false; gb->in_window = false; while (true) { /* Handle objects */ @@ -691,7 +692,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->cgb_mode) { palette = object->flags & 0x7; } -#if 1 + fifo_overlay_object_row(&gb->oam_fifo, gb->vram[line_address], gb->vram[line_address + 1], @@ -699,7 +700,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) object->flags & 0x80, gb->cgb_mode? gb->visible_objs[gb->n_visible_objs - 1] : 0, object->flags & 0x20); -#endif + gb->n_visible_objs--; } gb->fetching_objects = false; @@ -712,7 +713,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line + 7 == gb->io_registers[GB_IO_WX]) { gb->in_window = true; fifo_clear(&gb->bg_fifo); - gb->fifo_paused = true; + gb->bg_fifo_paused = true; + gb->oam_fifo_paused = true; gb->fetcher_x = 0; gb->fetcher_state = GB_FETCHER_GET_TILE; } @@ -765,7 +767,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) render_pixel_if_possible(gb); if (push) { fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); - gb->fifo_paused = false; + gb->bg_fifo_paused = false; + gb->oam_fifo_paused = false; } if (gb->position_in_line == 160) break; gb->cycles_for_line++; diff --git a/Core/gb.h b/Core/gb.h index c0c4514b..db39779d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -419,7 +419,8 @@ struct GB_gameboy_internal_s { GB_FETCHER_MAX = GB_FETCHER_SLEEP, } fetcher_state:8; bool fetcher_divisor; // The fetcher runs at 2MHz - bool fifo_paused; + bool bg_fifo_paused; + bool oam_fifo_paused; bool in_window; uint8_t visible_objs[10]; uint8_t n_visible_objs; From 9083e883fe5e20633669a2b41f0bb83211c8c364 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Mar 2018 18:52:36 +0200 Subject: [PATCH 0555/1216] CGB BG rendering --- Core/display.c | 68 +++++++++++++++++++++++++++++++++++++------------- Core/gb.h | 1 + 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/Core/display.c b/Core/display.c index 34ba843a..e7b15dad 100755 --- a/Core/display.c +++ b/Core/display.c @@ -24,20 +24,37 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) return ret; } -static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority) +static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) { - for (unsigned i = 8; i--;) { - fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { - (lower >> 7) | ((upper >> 7) << 1), - palette, - 0, - bg_priority, - }; - lower <<= 1; - upper <<= 1; - - fifo->write_end++; - fifo->write_end &= 0xF; + if (!flip_x) { + for (unsigned i = 8; i--;) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + (lower >> 7) | ((upper >> 7) << 1), + palette, + 0, + bg_priority, + }; + lower <<= 1; + upper <<= 1; + + fifo->write_end++; + fifo->write_end &= 0xF; + } + } + else { + for (unsigned i = 8; i--;) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + (lower & 1) | ((upper & 1) << 1), + palette, + 0, + bg_priority, + }; + lower >>= 1; + upper >>= 1; + + fifo->write_end++; + fifo->write_end &= 0xF; + } } } @@ -475,6 +492,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->bg_fifo_paused) { fifo_item = fifo_pop(&gb->bg_fifo); + bg_priority = fifo_item->bg_priority; } if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) { @@ -517,7 +535,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[pixel]; + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; } if (draw_oam) { @@ -732,26 +750,41 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_y = gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); gb->current_tile = gb->vram[map + gb->fetcher_x + gb->fetcher_y / 8 * 32]; + if (gb->is_cgb) { + /* TODO: The timing is wrong (two reads a the same time)*/ + gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + gb->fetcher_y / 8 * 32 + 0x2000]; + } gb->fetcher_x++; gb->fetcher_x &= 0x1f; } break; case GB_FETCHER_GET_TILE_DATA_LOWER: { + uint8_t y_flip = 0; if (gb->io_registers[GB_IO_LCDC] & 0x10) { gb->current_tile_address = gb->current_tile * 0x10; } else { gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; } + if (gb->current_tile_attributes & 8) { + gb->current_tile_address += 0x2000; + } + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } gb->current_tile_data[0] = - gb->vram[gb->current_tile_address + (gb->fetcher_y & 7) * 2]; + gb->vram[gb->current_tile_address + ((gb->fetcher_y & 7) ^ y_flip) * 2]; } break; case GB_FETCHER_GET_TILE_DATA_HIGH: { + uint8_t y_flip = 0; + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } gb->current_tile_data[1] = - gb->vram[gb->current_tile_address + (gb->fetcher_y & 7) * 2 + 1]; + gb->vram[gb->current_tile_address + ((gb->fetcher_y & 7) ^ y_flip) * 2 + 1]; } break; @@ -766,7 +799,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) render_pixel_if_possible(gb); if (push) { - fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], 0, false); + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], + gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; } diff --git a/Core/gb.h b/Core/gb.h index db39779d..b7133e81 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -409,6 +409,7 @@ struct GB_gameboy_internal_s { uint8_t fetcher_y; uint16_t cycles_for_line; uint8_t current_tile; + uint8_t current_tile_attributes; uint16_t current_tile_address; uint8_t current_tile_data[2]; enum { From cb6bb0590ed9be96e366de850b8686e776ab7dd8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Mar 2018 21:11:35 +0200 Subject: [PATCH 0556/1216] Starting to fix CGB timing quirks --- Core/display.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index e7b15dad..057ab3a9 100755 --- a/Core/display.c +++ b/Core/display.c @@ -596,9 +596,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; - gb->cycles_for_line = MODE2_LENGTH - 4; + gb->cycles_for_line = MODE2_LENGTH - 2; GB_STAT_update(gb); - GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4); + GB_SLEEP(gb, display, 2, MODE2_LENGTH - 2); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; @@ -607,8 +607,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->oam_write_blocked = true; gb->vram_write_blocked = true; GB_STAT_update(gb); - gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) + 2; - GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) + 2); + gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7); + GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7)); gb->io_registers[GB_IO_STAT] &= ~3; gb->oam_read_blocked = false; @@ -668,8 +668,14 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; - gb->cycles_for_line += 5; - GB_SLEEP(gb, display, 10, 5); + { + uint8_t rendering_delay = 5; + if (gb->is_cgb) { + rendering_delay = gb->current_line == 0? 2 : 6; + } + gb->cycles_for_line += rendering_delay; + GB_SLEEP(gb, display, 10, rendering_delay); + } /* The actual rendering cycle */ gb->fetcher_divisor = false; From e8b107efdb5fd5f8b17638a9136d31ee8db1bcb9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Mar 2018 23:31:49 +0200 Subject: [PATCH 0557/1216] =?UTF-8?q?In=20double=20speed=20mode,=20there?= =?UTF-8?q?=20are=20no=20quirks=20where=20IF=20and=20STAT=20don=E2=80=99t?= =?UTF-8?q?=20update=20together?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 057ab3a9..0cd6fbc0 100755 --- a/Core/display.c +++ b/Core/display.c @@ -610,13 +610,22 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7); GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7)); + if (!gb->cgb_double_speed) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + } + gb->cycles_for_line += 1; + GB_SLEEP(gb, display, 4, 1); + gb->io_registers[GB_IO_STAT] &= ~3; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; - gb->cycles_for_line += 1; - GB_SLEEP(gb, display, 4, 1); + GB_STAT_update(gb); /* Mode 0 is shorter in the very first line */ GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); @@ -632,8 +641,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->ly_for_comparison = gb->current_line? -1 : gb->current_line; /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. - PPU glitch? */ - if (gb->current_line != 0) { + PPU glitch? (Todo: and in double speed mode?) */ + if (gb->current_line != 0 && gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; } From c267ad00b5dbba93f11411cb4a030bf0599fbe09 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Mar 2018 23:34:23 +0200 Subject: [PATCH 0558/1216] Goodbye --- Core/display.c | 157 ------------------------------------------------- Core/gb.h | 1 - 2 files changed, 158 deletions(-) diff --git a/Core/display.c b/Core/display.c index 0cd6fbc0..8e4a66e6 100755 --- a/Core/display.c +++ b/Core/display.c @@ -120,163 +120,6 @@ static bool window_enabled(GB_gameboy_t *gb) return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167; } -/* Kept as a reference until I finish rewriting the PPU */ -static uint32_t __attribute__((unused)) get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t 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) - */ - uint16_t map = 0x1800; - uint8_t tile = 0; - uint8_t attributes = 0; - uint8_t sprite_palette = 0; - uint16_t tile_address = 0; - uint8_t background_pixel = 0, sprite_pixel = 0; - GB_object_t *sprite = (GB_object_t *) &gb->oam; - uint8_t 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; - uint8_t lowest_sprite_x = 0xFF; - bool use_obp1 = false, priority = false; - bool in_window = false; - bool bg_enabled = true; - bool bg_behind = false; - if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { - if (gb->cgb_mode) { - bg_behind = true; - } - else { - bg_enabled = false; - } - } - - if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] + gb->wy_diff && x + 7 >= gb->io_registers[GB_IO_WX]) { - in_window = true; - } - - if (sprites_enabled) { - // Loop all sprites - for (uint8_t 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) { - uint8_t tile_x, tile_y, current_sprite_pixel; - uint16_t 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; // Todo: This value is probably latched - y -= gb->io_registers[GB_IO_WY] + gb->wy_diff; - } - else { - x += gb->effective_scx; - y += gb->effective_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 = !bg_behind && bg_enabled; - } - - 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_palettes_rgb[sprite_palette * 4 + sprite_pixel]; - } - - if (bg_enabled) { - if (gb->io_registers[GB_IO_LCDC] & 0x10) { - tile_address = tile * 0x10; - } - else { - tile_address = (int8_t) 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_palettes_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_palettes_rgb[(attributes & 7) * 4 + background_pixel]; -} - static void display_vblank(GB_gameboy_t *gb) { gb->vblank_just_occured = true; diff --git a/Core/gb.h b/Core/gb.h index b7133e81..87026290 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -401,7 +401,6 @@ struct GB_gameboy_internal_s { bool oam_write_blocked; bool vram_write_blocked; bool window_disabled_while_active; - uint8_t effective_scy; // Todo: delete me! uint8_t current_line; uint16_t ly_for_comparison; GB_fifo_t bg_fifo, oam_fifo; From 15b6c48d7cbe624bb00b69b3307db9a671d47d39 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Mar 2018 15:52:22 +0200 Subject: [PATCH 0559/1216] Fixed vblank_stat_intr-C --- Core/display.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 8e4a66e6..b7e3e805 100755 --- a/Core/display.c +++ b/Core/display.c @@ -685,7 +685,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { gb->io_registers[GB_IO_LY] = gb->current_line; gb->ly_for_comparison = -1; - GB_STAT_update(gb); + if (gb->is_cgb && gb->current_line == LINES) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + GB_STAT_update(gb); + gb->io_registers[GB_IO_STAT] &= ~3; + } + else { + GB_STAT_update(gb); + } GB_SLEEP(gb, display, 12, 4); gb->ly_for_comparison = gb->current_line; From 21b75494a2a08d106b475c77ff5490bba9e08424 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Mar 2018 00:17:57 +0200 Subject: [PATCH 0560/1216] =?UTF-8?q?More=20CGB=20fixes=20(currently=20on?= =?UTF-8?q?=20DMG-mode=20CGB=20is=20verified).=20Halt=20interrupt=20timing?= =?UTF-8?q?=20isn=E2=80=99t=20correct=20yet.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index b7e3e805..58f114d5 100755 --- a/Core/display.c +++ b/Core/display.c @@ -392,6 +392,11 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) gb->position_in_line++; } +/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have + slightly different timings than CPUs <= C. + + Todo: Add support to CPU C and older */ + void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { GB_object_t *objects = (GB_object_t *) &gb->oam; @@ -419,6 +424,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 21); GB_STATE(gb, display, 22); GB_STATE(gb, display, 23); + GB_STATE(gb, display, 24); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -446,12 +452,20 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; gb->oam_read_blocked = true; - gb->vram_read_blocked = true; + gb->vram_read_blocked = !gb->is_cgb; gb->oam_write_blocked = true; - gb->vram_write_blocked = true; + gb->vram_write_blocked = !gb->is_cgb; GB_STAT_update(gb); - gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7); - GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7)); + + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 24, 2); + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + + gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2; + GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2); + + if (!gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; @@ -477,11 +491,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { + gb->oam_write_blocked = gb->is_cgb; GB_SLEEP(gb, display, 6, 3); gb->io_registers[GB_IO_LY] = gb->current_line; gb->oam_read_blocked = true; - gb->oam_write_blocked = false; - gb->ly_for_comparison = gb->current_line? -1 : gb->current_line; + gb->ly_for_comparison = gb->current_line? (gb->is_cgb? gb->current_line - 1 : -1) : 0; /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. PPU glitch? (Todo: and in double speed mode?) */ @@ -502,14 +516,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 8, MODE2_LENGTH - 4); search_oam(gb); - gb->vram_read_blocked = true; + gb->vram_read_blocked = !gb->is_cgb; gb->vram_write_blocked = false; - gb->oam_write_blocked = false; + gb->oam_write_blocked = gb->is_cgb; GB_STAT_update(gb); GB_SLEEP(gb, display, 9, 4); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; + gb->vram_read_blocked = true; gb->vram_write_blocked = true; gb->oam_write_blocked = true; GB_STAT_update(gb); @@ -684,7 +699,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Lines 144 - 152 */ for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { gb->io_registers[GB_IO_LY] = gb->current_line; - gb->ly_for_comparison = -1; + if (!gb->is_cgb) { + gb->ly_for_comparison = -1; + } if (gb->is_cgb && gb->current_line == LINES) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; @@ -727,16 +744,23 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Lines 153 */ gb->io_registers[GB_IO_LY] = 153; - gb->ly_for_comparison = -1; + if (!gb->cgb_mode) { + gb->ly_for_comparison = gb->is_cgb? 153 : -1; + } GB_STAT_update(gb); GB_SLEEP(gb, display, 14, 6); gb->io_registers[GB_IO_LY] = 0; - gb->ly_for_comparison = 153; + gb->ly_for_comparison = gb->cgb_mode? 0 : 153; GB_STAT_update(gb); GB_SLEEP(gb, display, 15, 2); - gb->ly_for_comparison = -1; + if (gb->cgb_mode) { + gb->ly_for_comparison = 0; + } + else if(!gb->is_cgb) { + gb->ly_for_comparison = -1; + } GB_STAT_update(gb); GB_SLEEP(gb, display, 16, 4); From 05ba352f2f874127d91ccbab51f2ad777afa9881 Mon Sep 17 00:00:00 2001 From: Alvaro Burnett Date: Mon, 12 Mar 2018 22:38:05 +0100 Subject: [PATCH 0561/1216] Makefile: Find SDL2.dll --- Makefile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 140a2c2b..3d5a6322 100755 --- a/Makefile +++ b/Makefile @@ -235,12 +235,9 @@ $(OBJ)/%.res: %.rc %.o: %.res cvtres /OUT:"$@" $^ -# We must provide SDL2.dll with the Windows port. This is an AWFUL HACK to find it. -SPACE := -SPACE += +# We must provide SDL2.dll with the Windows port. $(BIN)/SDL/SDL2.dll: - @$(eval POTENTIAL_MATCHES := $(subst @@@," ",$(patsubst %,%/SDL2.dll,$(subst ;,$(SPACE),$(subst $(SPACE),@@@,$(lib)))))) - @$(eval MATCH := $(shell ls $(POTENTIAL_MATCHES) 2> NUL | head -n 1)) + @$(eval MATCH := $(shell where $$LIB:SDL2.dll)) cp "$(MATCH)" $@ # Tester From 0c80ac32967f3af6619ca82018506f9870d1f768 Mon Sep 17 00:00:00 2001 From: LMLB Date: Sun, 11 Mar 2018 14:56:15 +0100 Subject: [PATCH 0562/1216] Fix shaders that sample at texel edges Sampling at the edge between texels causes one or the other to be chosen semi-randomly, depending on rounding errors. Add half a pixel so they sample at texel centers instead. --- Shaders/AAOmniScaleLegacy.fsh | 8 ++++---- Shaders/Bilinear.fsh | 8 ++++---- Shaders/SmoothBilinear.fsh | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Shaders/AAOmniScaleLegacy.fsh b/Shaders/AAOmniScaleLegacy.fsh index 6f325ac1..d667edfe 100644 --- a/Shaders/AAOmniScaleLegacy.fsh +++ b/Shaders/AAOmniScaleLegacy.fsh @@ -8,10 +8,10 @@ vec4 omniScale(sampler2D image, vec2 texCoord) { vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q12 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); vec2 pos = fract(pixel); diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh index a519e12d..6fa5e21f 100644 --- a/Shaders/Bilinear.fsh +++ b/Shaders/Bilinear.fsh @@ -4,10 +4,10 @@ vec4 scale(sampler2D image) vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q12 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); vec4 r1 = mix(q11, q21, fract(pixel.x)); vec4 r2 = mix(q12, q22, fract(pixel.x)); diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index ab242f5d..e7060b7c 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -4,10 +4,10 @@ vec4 scale(sampler2D image) vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q12 = texture(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); - vec4 q21 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y)); - vec4 q22 = texture(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y)); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); vec2 s = smoothstep(0., 1., fract(pixel)); From c365c450a75c843e0f3e7a1a25b836fe887194f4 Mon Sep 17 00:00:00 2001 From: LMLB Date: Sun, 11 Mar 2018 15:09:30 +0100 Subject: [PATCH 0563/1216] Fix shaders that break with specific pixel factors OmniScaleLegacy seems to has problems with every odd factor (3x, 5x, 7x, 9x, etc.). SmoothBilinear has problems with 5x and 11x and probably more. --- Shaders/OmniScaleLegacy.fsh | 8 ++++---- Shaders/SmoothBilinear.fsh | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Shaders/OmniScaleLegacy.fsh b/Shaders/OmniScaleLegacy.fsh index cd4257d3..f4bdea9a 100644 --- a/Shaders/OmniScaleLegacy.fsh +++ b/Shaders/OmniScaleLegacy.fsh @@ -10,10 +10,10 @@ vec4 scale(sampler2D image) vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - vec4 q11 = texture(image, (pixel ) / textureDimensions); - vec4 q12 = texture(image, (pixel + vec2(0.0, 1.0)) / textureDimensions); - vec4 q21 = texture(image, (pixel + vec2(1.0, 0.0)) / textureDimensions); - vec4 q22 = texture(image, (pixel + vec2(1.0, 1.0)) / textureDimensions); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); vec2 pos = fract(pixel); diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index e7060b7c..b796d88a 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -11,8 +11,8 @@ vec4 scale(sampler2D image) vec2 s = smoothstep(0., 1., fract(pixel)); - vec4 r1 = mix(q11, q21, fract(s.x)); - vec4 r2 = mix(q12, q22, fract(s.x)); + vec4 r1 = mix(q11, q21, s.x); + vec4 r2 = mix(q12, q22, s.x); - return mix (r1, r2, fract(s.y)); + return mix (r1, r2, s.y); } \ No newline at end of file From 269bac46265565f417fc5c34ee7de1eeb28396e7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Mar 2018 20:34:55 +0200 Subject: [PATCH 0564/1216] More CGB fixes --- Core/display.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index 58f114d5..9a11e728 100755 --- a/Core/display.c +++ b/Core/display.c @@ -499,12 +499,14 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. PPU glitch? (Todo: and in double speed mode?) */ - if (gb->current_line != 0 && gb->cgb_double_speed) { + if (gb->current_line != 0 && !gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; } GB_STAT_update(gb); - gb->io_registers[GB_IO_STAT] &= ~3; + if (gb->current_line != 0) { + gb->io_registers[GB_IO_STAT] &= ~3; + } GB_SLEEP(gb, display, 7, 1); @@ -538,7 +540,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { uint8_t rendering_delay = 5; if (gb->is_cgb) { - rendering_delay = gb->current_line == 0? 2 : 6; + rendering_delay = 6; } gb->cycles_for_line += rendering_delay; GB_SLEEP(gb, display, 10, rendering_delay); @@ -768,8 +770,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); GB_SLEEP(gb, display, 17, LINE_LENGTH - 12); - gb->io_registers[GB_IO_STAT] &= ~3; - /* Reset window rendering state */ gb->wy_diff = 0; gb->window_disabled_while_active = false; From 12ae5745dbf8cb12c0fcd89f4b0bb79e9e4ca5c7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Mar 2018 21:04:48 +0200 Subject: [PATCH 0565/1216] While fixing some rendering issues, this change was incorrect. --- Core/display.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Core/display.c b/Core/display.c index 9a11e728..18fe3bd1 100755 --- a/Core/display.c +++ b/Core/display.c @@ -537,14 +537,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; - { - uint8_t rendering_delay = 5; - if (gb->is_cgb) { - rendering_delay = 6; - } - gb->cycles_for_line += rendering_delay; - GB_SLEEP(gb, display, 10, rendering_delay); - } + gb->cycles_for_line += 5; + GB_SLEEP(gb, display, 10, 5); /* The actual rendering cycle */ gb->fetcher_divisor = false; From 0dc30f081a3cad084c6e086a08f6849b14485c40 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Mar 2018 23:21:14 +0200 Subject: [PATCH 0566/1216] CGB halt interrupt timing --- Core/z80_cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 0b582c4f..dc93ffab 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1367,14 +1367,14 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } - if (gb->halted) { + if (gb->halted && !gb->is_cgb) { GB_advance_cycles(gb, 2); } uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; if (gb->halted) { - GB_advance_cycles(gb, 2); + GB_advance_cycles(gb, gb->is_cgb? 4 : 2); } bool effecitve_ime = gb->ime; From 80b1275e0791a381b71b78f9daab30ff173f887a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 18 Mar 2018 20:08:45 +0200 Subject: [PATCH 0567/1216] Fix stat_lyc_onoff --- Core/display.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index dda9fab2..1adcc501 100755 --- a/Core/display.c +++ b/Core/display.c @@ -311,11 +311,9 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { /* LCD is disabled, state is constant */ - /* When the LCD is off, LY is 0 and STAT mode is 0. - Todo: Verify the LY=LYC flag should be on. */ + /* When the LCD is off, LY is 0 and STAT mode is 0. */ gb->io_registers[GB_IO_LY] = 0; gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 4; gb->effective_scx = gb->io_registers[GB_IO_SCX]; if (gb->hdma_on_hblank) { gb->hdma_on_hblank = false; From 202eb2b5cc1c63224ac80be530a46ac1c9aa5c3e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 18 Mar 2018 20:32:19 +0200 Subject: [PATCH 0568/1216] Fix stat_lyc_onoff --- Core/display.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 18fe3bd1..dca64989 100755 --- a/Core/display.c +++ b/Core/display.c @@ -266,11 +266,9 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->display_cycles = 0; /* When the LCD is disabled, state is constant */ - /* When the LCD is off, LY is 0 and STAT mode is 0. - Todo: Verify the LY=LYC flag should be on. */ + /* When the LCD is off, LY is 0 and STAT mode is 0. */ gb->io_registers[GB_IO_LY] = 0; gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 4; if (gb->hdma_on_hblank) { gb->hdma_on_hblank = false; gb->hdma_on = false; @@ -278,7 +276,6 @@ void GB_lcd_off(GB_gameboy_t *gb) /* Todo: is this correct? */ gb->hdma_steps_left = 0xff; } - gb->stat_interrupt_line = false; gb->oam_read_blocked = false; gb->vram_read_blocked = false; From b50c97f4a71f2a3d535477ee88c0469f42151cd5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Mar 2018 20:01:31 +0200 Subject: [PATCH 0569/1216] Prevent starting HDMA in the middle of an instruction, making both the CPU and DMA access memory at the same time. Closes #47 --- Core/display.c | 3 +-- Core/gb.h | 1 + Core/z80_cpu.c | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 1adcc501..b55d66f8 100755 --- a/Core/display.c +++ b/Core/display.c @@ -528,8 +528,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) } else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay + 16) { if (gb->hdma_on_hblank) { - gb->hdma_on = true; - gb->hdma_cycles = 0; + gb->hdma_starting = true; } } } diff --git a/Core/gb.h b/Core/gb.h index c93aa3dc..48171f84 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -276,6 +276,7 @@ struct GB_gameboy_internal_s { int16_t dma_cycles; bool is_dma_restarting; uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ + bool hdma_starting; ); /* MBC */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 4794df59..6ff69b4b 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1402,4 +1402,10 @@ void GB_cpu_run(GB_gameboy_t *gb) else { GB_advance_cycles(gb, 4); } + + if (gb->hdma_starting) { + gb->hdma_starting = false; + gb->hdma_on = true; + gb->hdma_cycles = 0; + } } From 3b5b400909ea934988514af9ce44cd5607df086d Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 8 Mar 2018 10:57:57 -0500 Subject: [PATCH 0570/1216] implement RTC saves in dual mode, capitalization nitpicking in core opts --- libretro/libretro.c | 72 +++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index cbeece9d..569f6617 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -202,39 +202,39 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; static const struct retro_variable vars[] = { - { "sameboy_dual", "Dual Game Boy Mode (restart); disabled|enabled" }, - { "sameboy_color_correction_mode", "Color Correction; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode", "High Pass Filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated Model; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, + { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, + { "sameboy_model", "Emulated model; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { NULL } }; static const struct retro_variable vars_sameboy_dual[] = { - { "sameboy_dual", "Dual Game Boy Mode (restart); disabled|enabled" }, - { "sameboy_link", "Link Cable Emulation; enabled|disabled" }, + { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, + { "sameboy_link", "Link cable emulation; enabled|disabled" }, /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ - { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_color_correction_mode_1", "Color Correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_color_correction_mode_2", "Color Correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode_1", "High Pass Filter for Game Boy #1; off|accurate|remove dc offset" }, - { "sameboy_high_pass_filter_mode_2", "High Pass Filter for Game Boy #2; off|accurate|remove dc offset" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, + { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; off|accurate|remove dc offset" }, { NULL } }; static const struct retro_variable vars_link_dual[] = { - { "sameboy_link", "Link Cable Emulation; enabled|disabled" }, + { "sameboy_link", "Link cable emulation; enabled|disabled" }, /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ - { "sameboy_screen_layout", "Screen Layout; top-down|left-right" }, + { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated Model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated Model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_color_correction_mode_1", "Color Correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_color_correction_mode_2", "Color Correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode_1", "High Pass Filter for Game Boy #1; off|accurate|remove dc offset" }, - { "sameboy_high_pass_filter_mode_2", "High Pass Filter for Game Boy #2; off|accurate|remove dc offset" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, + { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; off|accurate|remove dc offset" }, { NULL } }; @@ -492,7 +492,7 @@ static void check_variables(bool link) new_model = MODEL_AGB; else new_model = MODEL_AUTO; - + if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { model[0] = new_model; init_for_current_model(); @@ -513,7 +513,7 @@ static void check_variables(bool link) new_model = MODEL_AGB; else new_model = MODEL_AUTO; - + if (GB_is_inited(&gameboy[1]) && new_model != model[1]) { model[1] = new_model; init_for_current_model(); @@ -973,7 +973,7 @@ void *retro_get_memory_data(unsigned type) } } break; - case MODE_DUAL_GAME: /* todo: hook up other memory types */ + case MODE_DUAL_GAME: { switch (type) { @@ -989,6 +989,18 @@ void *retro_get_memory_data(unsigned type) else data = NULL; break; + case RETRO_MEMORY_GAMEBOY_1_RTC: + if(gameboy[0].cartridge_type->has_battery) + data = &gameboy[0].rtc_real; + else + data = NULL; + break; + case RETRO_MEMORY_GAMEBOY_2_RTC: + if(gameboy[1].cartridge_type->has_battery) + data = &gameboy[1].rtc_real; + else + data = NULL; + break; default: break; } @@ -1033,7 +1045,7 @@ size_t retro_get_memory_size(unsigned type) } } break; - case MODE_DUAL_GAME: /* todo: hook up other memory types */ + case MODE_DUAL_GAME: { switch (type) { @@ -1049,8 +1061,16 @@ size_t retro_get_memory_size(unsigned type) else size = 0; break; + case RETRO_MEMORY_GAMEBOY_1_RTC: + if(gameboy[0].cartridge_type->has_battery) + size = sizeof (gameboy[0].rtc_real); + break; + case RETRO_MEMORY_GAMEBOY_2_RTC: + if(gameboy[1].cartridge_type->has_battery) + size = sizeof (gameboy[1].rtc_real); + break; default: - break;; + break; } } break; From b1ea7c5f06a930a14e6e5723a0e1edf51f8ba353 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 8 Mar 2018 12:01:39 -0500 Subject: [PATCH 0571/1216] handle saving for GB2 in single cart mode --- libretro/libretro.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 569f6617..7954595a 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -116,6 +117,25 @@ static void fallback_log(enum retro_log_level level, const char *fmt, ...) static struct retro_rumble_interface rumble; +static void extract_basename(char *buf, const char *path, size_t size) +{ + const char *base = strrchr(path, '/'); + if (!base) + base = strrchr(path, '\\'); + if (!base) + base = path; + + if (*base == '\\' || *base == '/') + base++; + + strncpy(buf, base, size - 1); + buf[size - 1] = '\0'; + + char *ext = strrchr(buf, '.'); + if (ext) + *ext = '\0'; +} + static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { @@ -821,11 +841,33 @@ bool retro_load_game(const struct retro_game_info *info) log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); check_variables(emulated_devices == 2 ? true : false); + + if (mode == MODE_SINGLE_GAME_DUAL) + { + char path[PATH_MAX]; + char file[PATH_MAX]; + + extract_basename(file, retro_game_path, sizeof(file)); + snprintf (path, sizeof(path), "%s%c%s.srm.2", retro_save_directory, slash, file); + log_cb(RETRO_LOG_INFO, "Loading battery for Game Boy 2 from: %s\n", path); + GB_load_battery(&gameboy[1], path); + } + return true; } void retro_unload_game(void) { + if (mode == MODE_SINGLE_GAME_DUAL) + { + char path[PATH_MAX]; + char file[PATH_MAX]; + + extract_basename(file, retro_game_path, sizeof(file)); + snprintf (path, sizeof(path), "%s%c%s.srm.2", retro_save_directory, slash, file); + log_cb(RETRO_LOG_INFO, "Saving battery for Game Boy 2 to: %s\n", path); + GB_save_battery(&gameboy[1], path); + } for (int i = 0; i < emulated_devices; i++) GB_free(&gameboy[i]); } From 007765daa6c0c156f4cc46a71729935f82477b6a Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 8 Mar 2018 14:45:58 -0500 Subject: [PATCH 0572/1216] refine variables, add comments --- libretro/libretro.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 7954595a..4a9911da 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -221,7 +221,8 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; -static const struct retro_variable vars[] = { +/* variables for single cart mode */ +static const struct retro_variable vars_single[] = { { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, @@ -229,7 +230,8 @@ static const struct retro_variable vars[] = { { NULL } }; -static const struct retro_variable vars_sameboy_dual[] = { +/* variables for single cart dual gameboy mode */ +static const struct retro_variable vars_single_dual[] = { { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, { "sameboy_link", "Link cable emulation; enabled|disabled" }, /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ @@ -244,7 +246,8 @@ static const struct retro_variable vars_sameboy_dual[] = { { NULL } }; -static const struct retro_variable vars_link_dual[] = { +/* variables for dual cart dual gameboy mode */ +static const struct retro_variable vars_dual[] = { { "sameboy_link", "Link cable emulation; enabled|disabled" }, /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, @@ -793,13 +796,13 @@ void retro_run(void) bool retro_load_game(const struct retro_game_info *info) { - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); check_variables(false); if (sameboy_dual) { emulated_devices = 2; mode = MODE_SINGLE_GAME_DUAL; - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_sameboy_dual); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single_dual); check_variables(true); } else @@ -842,6 +845,7 @@ bool retro_load_game(const struct retro_game_info *info) check_variables(emulated_devices == 2 ? true : false); + /* hack: use upstream's file based I/O for Game Boy 2 battery in single cart mode */ if (mode == MODE_SINGLE_GAME_DUAL) { char path[PATH_MAX]; @@ -858,6 +862,7 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { + /* hack: use upstream's file based I/O for Game Boy 2 battery in single cart mode */ if (mode == MODE_SINGLE_GAME_DUAL) { char path[PATH_MAX]; @@ -868,6 +873,7 @@ void retro_unload_game(void) log_cb(RETRO_LOG_INFO, "Saving battery for Game Boy 2 to: %s\n", path); GB_save_battery(&gameboy[1], path); } + for (int i = 0; i < emulated_devices; i++) GB_free(&gameboy[i]); } @@ -888,7 +894,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, else return false; /* all other types are unhandled for now */ - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_link_dual); + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_dual); check_variables(true); frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); From cb33a5b25a0a31484f268e68e5cb23a00afa6781 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Mar 2018 23:49:53 +0200 Subject: [PATCH 0573/1216] Fix Aevilla --- Core/display.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 901bf037..7c457444 100755 --- a/Core/display.c +++ b/Core/display.c @@ -422,6 +422,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 22); GB_STATE(gb, display, 23); GB_STATE(gb, display, 24); + GB_STATE(gb, display, 25); + } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -679,12 +681,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; - if (gb->hdma_on_hblank) { - gb->hdma_starting = true; - } + gb->cycles_for_line++; GB_SLEEP(gb, display, 22, 1); GB_STAT_update(gb); + + /* Todo: Measure this value */ + + gb->cycles_for_line += 16; + GB_SLEEP(gb, display, 25, 16); + + if (gb->hdma_on_hblank) { + gb->hdma_starting = true; + } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); } From e9f243a9138e8f1d8d4d0942a70b4a3fb4d5371d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 21 Mar 2018 00:02:35 +0200 Subject: [PATCH 0574/1216] Fix sprite priority --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 7c457444..c9b28191 100755 --- a/Core/display.c +++ b/Core/display.c @@ -354,7 +354,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { if (gb->cgb_mode) { - bg_priority = true; + bg_priority = false; } else { bg_enabled = false; From 337617afbb7c30c282eb4097010238a72ed78005 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 22 Mar 2018 00:00:03 -0500 Subject: [PATCH 0575/1216] change sameboy emulated model selection to apply on restart (because otherwise it may crash due the change on savestate size with rewind enabled) --- libretro/libretro.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 4a9911da..61ee987d 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -82,7 +82,7 @@ static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; static unsigned emulated_devices = 1; -static unsigned pre_init = 1; +static bool initialized = false; static unsigned screen_layout = 0; static unsigned audio_out = 0; @@ -226,7 +226,7 @@ static const struct retro_variable vars_single[] = { { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated model; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model", "Emulated model (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { NULL } }; @@ -237,8 +237,8 @@ static const struct retro_variable vars_single_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -252,8 +252,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -441,10 +441,6 @@ static void check_variables(bool link) else new_model = MODEL_AUTO; - if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { - model[0] = new_model; - init_for_current_model(); - } model[0] = new_model; } } @@ -516,10 +512,6 @@ static void check_variables(bool link) else new_model = MODEL_AUTO; - if (GB_is_inited(&gameboy[0]) && new_model != model[0]) { - model[0] = new_model; - init_for_current_model(); - } model[0] = new_model; } @@ -537,10 +529,6 @@ static void check_variables(bool link) else new_model = MODEL_AUTO; - if (GB_is_inited(&gameboy[1]) && new_model != model[1]) { - model[1] = new_model; - init_for_current_model(); - } model[1] = new_model; } @@ -738,9 +726,10 @@ void retro_reset(void) void retro_run(void) { + bool updated = false; - if (pre_init) + if (!initialized) geometry_updated = false; if (geometry_updated) { @@ -750,8 +739,6 @@ void retro_run(void) environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &info.geometry); } - pre_init = 0; - if (!frame_buf) return; @@ -792,6 +779,8 @@ void retro_run(void) video_cb(frame_buf_copy, VIDEO_WIDTH * emulated_devices, VIDEO_HEIGHT, VIDEO_WIDTH * emulated_devices * sizeof(uint32_t)); } + + initialized = true; } bool retro_load_game(const struct retro_game_info *info) @@ -945,7 +934,8 @@ size_t retro_serialize_size(void) bool retro_serialize(void *data, size_t size) { - if (pre_init == 1) + + if (!initialized) return false; void* save_data[2]; From f050457976d611464053167ce80192413cd7c2fb Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 22 Mar 2018 00:20:51 -0500 Subject: [PATCH 0576/1216] fix error in savestate code --- libretro/libretro.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 61ee987d..275df1d9 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -940,13 +940,15 @@ bool retro_serialize(void *data, size_t size) void* save_data[2]; size_t state_size[2]; + size_t offset = 0; for (int i = 0; i < emulated_devices; i++) { state_size[i] = GB_get_save_state_size(&gameboy[i]); save_data[i] = (uint8_t*)malloc(state_size[i]); GB_save_state_to_buffer(&gameboy[i], (uint8_t*) save_data[i]); - memcpy(data + (state_size[i] * i), save_data[i], state_size[i]); + memcpy(data + offset, save_data[i], state_size[i]); + offset += state_size[i]; free(save_data[i]); } From 577e23925bc304b93a3048e080def73eff37a784 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 22 Mar 2018 20:09:01 +0200 Subject: [PATCH 0577/1216] Fixed sources-dmgABCXmgbS --- Core/gb.c | 5 +++-- Core/memory.c | 40 +++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 43c29806..dabf9130 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -476,7 +476,6 @@ void GB_reset(GB_gameboy_t *gb) gb->is_cgb = true; gb->cgb_mode = true; - gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0x00; } else { gb->ram_size = 0x2000; @@ -494,8 +493,10 @@ void GB_reset(GB_gameboy_t *gb) gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = gb->rgb_encode_callback(gb, 0, 0, 0); } - gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; } + + /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */ + gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = gb->is_cgb? 0x00 : 0xFF; /* The serial interrupt always occur on the 0xF8th cycle of every 0x100 cycle since boot. */ gb->serial_cycles = 0x100 - 0xF8; gb->io_registers[GB_IO_SC] = 0x7E; diff --git a/Core/memory.c b/Core/memory.c index 2bd8215a..4180c436 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -31,7 +31,7 @@ static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) { - if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting)) return false; + if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xFE00) return false; return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); } @@ -174,6 +174,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_WX: case GB_IO_SC: case GB_IO_SB: + case GB_IO_DMA: return gb->io_registers[addr & 0xFF]; case GB_IO_TIMA: if (gb->tima_reload_state == GB_TIMA_RELOADING) { @@ -232,9 +233,6 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return ret; } - case GB_IO_DMA: - /* Todo: is this documented? */ - return gb->is_cgb? 0x00 : 0xFF; case GB_IO_UNKNOWN2: case GB_IO_UNKNOWN3: return gb->is_cgb? gb->io_registers[addr & 0xFF] : 0xFF; @@ -509,21 +507,18 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DMA: - if (value <= 0xE0) { - if (gb->dma_steps_left) { - /* This is not correct emulation, since we're not really delaying the second DMA. - One write that should have happened in the first DMA will not happen. However, - since that byte will be overwritten by the second DMA before it can actually be - read, it doesn't actually matter. */ - gb->is_dma_restarting = true; - } - gb->dma_cycles = -7; - gb->dma_current_dest = 0; - gb->dma_current_src = value << 8; - gb->dma_steps_left = 0xa0; + if (gb->dma_steps_left) { + /* This is not correct emulation, since we're not really delaying the second DMA. + One write that should have happened in the first DMA will not happen. However, + since that byte will be overwritten by the second DMA before it can actually be + read, it doesn't actually matter. */ + gb->is_dma_restarting = true; } - /* else { what? } */ - + gb->dma_cycles = -7; + gb->dma_current_dest = 0; + gb->dma_current_src = value << 8; + gb->dma_steps_left = 0xa0; + gb->io_registers[GB_IO_DMA] = value; return; case GB_IO_SVBK: if (!gb->cgb_mode) { @@ -701,7 +696,14 @@ void GB_dma_run(GB_gameboy_t *gb) /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; - gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); + if (gb->dma_current_src < 0xe000) { + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); + } + else { + /* Todo: Not correct on the CGB */ + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000); + } + /* dma_current_src must be the correct value during GB_read_memory */ gb->dma_current_src++; if (!gb->dma_steps_left) { From 2a5407cf7082e7ccd0f35af98e1ebc70ce3c23d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 22 Mar 2018 20:22:09 +0200 Subject: [PATCH 0578/1216] Fix libretro build on master --- libretro/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/Makefile b/libretro/Makefile index 6350fd19..cde75a05 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -161,7 +161,7 @@ CFLAGS += -Wall -D__LIBRETRO__ $(fpic) $(INCFLAGS) -std=gnu11 -D_GNU_SOURCE -D all: $(TARGET) -$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/BootROMs/prebuilt/%_boot.bin +$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/build/bin/BootROMs/%_boot.bin echo "/* AUTO-GENERATED */" > $@ echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ ifneq ($(findstring Haiku,$(shell uname -s)),) From 3e5e17d1a3f8ce26ac43ef3fd1229ebd6cbedd67 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 12:35:37 +0300 Subject: [PATCH 0579/1216] Fixed CB [hl] opcodes timings --- Core/z80_cpu.c | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 28ceae6e..63b43496 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -486,7 +486,7 @@ static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) GB_advance_cycles(gb, 5); } -uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) +uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t cycles_after_read) { uint8_t src_register_id; uint8_t src_low; @@ -497,7 +497,7 @@ uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) return gb->registers[GB_REGISTER_AF] >> 8; } uint8_t ret = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, cycles_after_read); return ret; } if (src_low) { @@ -519,9 +519,9 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) gb->registers[GB_REGISTER_AF] |= value << 8; } else { - GB_advance_cycles(gb, 3); - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); GB_advance_cycles(gb, 1); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); } } else { @@ -578,7 +578,7 @@ static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a + value) << 8; if ((uint8_t)(a + value) == 0) { @@ -596,7 +596,7 @@ static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); 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; @@ -616,7 +616,7 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; if (a == value) { @@ -634,7 +634,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); 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; @@ -654,7 +654,7 @@ static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; if ((a & value) == 0) { @@ -666,7 +666,7 @@ static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; if ((a ^ value) == 0) { @@ -678,7 +678,7 @@ static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a | value) << 8; if ((a | value) == 0) { @@ -690,7 +690,7 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 4); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; @@ -1126,7 +1126,7 @@ static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; uint8_t value; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 3); carry = (value & 0x80) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value << 1) | carry); @@ -1143,7 +1143,7 @@ static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; uint8_t value; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 3); carry = (value & 0x01) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; value = (value >> 1) | (carry << 7); @@ -1162,7 +1162,7 @@ static void rl_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool bit7; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 3); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; bit7 = (value & 0x80) != 0; @@ -1184,7 +1184,7 @@ static void rr_r(GB_gameboy_t *gb, uint8_t opcode) bool bit1; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 3); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; bit1 = (value & 0x1) != 0; @@ -1204,7 +1204,7 @@ static void sla_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool carry; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 3); carry = (value & 0x80) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value << 1)); @@ -1220,8 +1220,8 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t bit7; uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + GB_advance_cycles(gb, 3); + value = get_src_value(gb, opcode, 4); bit7 = value & 0x80; gb->registers[GB_REGISTER_AF] &= 0xFF00; if (value & 1) { @@ -1237,8 +1237,8 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + GB_advance_cycles(gb, 3); + value = get_src_value(gb, opcode, 4); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 1)); if (value & 1) { @@ -1252,8 +1252,8 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode) static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + GB_advance_cycles(gb, 3); + value = get_src_value(gb, opcode, 4); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 4) | (value << 4)); if (!value) { @@ -1266,7 +1266,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; uint8_t bit; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode); + value = get_src_value(gb, opcode, 3); bit = 1 << ((opcode >> 3) & 7); if ((opcode & 0xC0) == 0x40) { /* Bit */ gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; From c11af7ea26839c1cfd24943cbed7d3f3cb7da761 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 12:58:51 +0300 Subject: [PATCH 0580/1216] Fix CGB timings --- Core/display.c | 20 ++++++++++++++------ Core/timing.c | 13 +++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index c9b28191..b06329e9 100755 --- a/Core/display.c +++ b/Core/display.c @@ -434,7 +434,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) return; } - GB_SLEEP(gb, display, 23, 1); + if (!gb->is_cgb) { + GB_SLEEP(gb, display, 23, 1); + } /* Handle the very first line 0 */ gb->current_line = 0; @@ -464,8 +466,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2; GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2); - - if (!gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; gb->oam_read_blocked = false; @@ -676,14 +676,22 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); } + if (!gb->cgb_double_speed) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + } + + gb->cycles_for_line++; + GB_SLEEP(gb, display, 22, 1); + gb->io_registers[GB_IO_STAT] &= ~3; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; - - gb->cycles_for_line++; - GB_SLEEP(gb, display, 22, 1); GB_STAT_update(gb); /* Todo: Measure this value */ diff --git a/Core/timing.c b/Core/timing.c index a9ff1560..5328007c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -153,6 +153,13 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { + /* It appears that on the CGB, write timing is a bit different then on the DMG, effectively + making writes 1 T-cycle late when compared to the DMG. */ + if (gb->is_cgb) { + cycles = (cycles + 1) & ~3; + if (cycles == 0) return; + } + // Affected by speed boost gb->dma_cycles += cycles; @@ -180,12 +187,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) if (!gb->cgb_double_speed) { cycles <<= 1; - if ((cycles & 6) == 2) { - cycles--; - } - else if ((cycles & 6) == 6) { - cycles++; - } } // Not affected by speed boost From 04bfc89816eb35b071ec03453723be8750a42b14 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 19:07:14 +0300 Subject: [PATCH 0581/1216] Cycle accurate OAM search mode --- Core/display.c | 55 ++++++++++++++++++++------------------------------ Core/gb.h | 1 + 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/Core/display.c b/Core/display.c index b06329e9..ee31a987 100755 --- a/Core/display.c +++ b/Core/display.c @@ -289,37 +289,22 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->ly_for_comparison = 0; } -static void search_oam(GB_gameboy_t *gb) +static void add_object_from_index(GB_gameboy_t *gb, unsigned index) { - /* - We have very little means to test how the OAM search timing actually - works so we currently implement it atomically. + if (gb->n_visible_objs == 10) return; - This is enough for most cases except (All are TODOs): - - The OAM bug on the DMG (Not emulated at all) - - Changing object height via LCDC during mode 2 - - What about changing during mode 3? - - Enabling and disabling sprites during mode 2 - - Does this flag actually do anything in mode 2? Or only during mode 3? - */ - /* This reverse sorts the visible objects by location and priority */ - GB_object_t *objects = (GB_object_t *) &gb->oam; bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; - gb->n_visible_objs = 0; - for (uint8_t i = 0; i < 40; i++) { - signed y = objects[i].y - 16; - if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { - unsigned j = 0; - for (; j < gb->n_visible_objs; j++) { - if (objects[gb->visible_objs[j]].x <= objects[i].x) break; - } - memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); - gb->visible_objs[j] = i; - gb->n_visible_objs++; - if (gb->n_visible_objs == 10) return; + signed y = objects[index].y - 16; + if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { + unsigned j = 0; + for (; j < gb->n_visible_objs; j++) { + if (objects[gb->visible_objs[j]].x <= objects[index].x) break; } + memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); + gb->visible_objs[j] = index; + gb->n_visible_objs++; } } @@ -407,7 +392,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 6); GB_STATE(gb, display, 7); GB_STATE(gb, display, 8); - GB_STATE(gb, display, 9); + // GB_STATE(gb, display, 9); GB_STATE(gb, display, 10); GB_STATE(gb, display, 11); GB_STATE(gb, display, 12); @@ -514,14 +499,18 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->oam_write_blocked = true; gb->ly_for_comparison = gb->current_line; GB_STAT_update(gb); - GB_SLEEP(gb, display, 8, MODE2_LENGTH - 4); + gb->n_visible_objs = 0; - search_oam(gb); - gb->vram_read_blocked = !gb->is_cgb; - gb->vram_write_blocked = false; - gb->oam_write_blocked = gb->is_cgb; - GB_STAT_update(gb); - GB_SLEEP(gb, display, 9, 4); + for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { + add_object_from_index(gb, gb->oam_search_index); + GB_SLEEP(gb, display, 8, 2); + if (gb->oam_search_index == 37) { + gb->vram_read_blocked = !gb->is_cgb; + gb->vram_write_blocked = false; + gb->oam_write_blocked = gb->is_cgb; + GB_STAT_update(gb); + } + } gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; diff --git a/Core/gb.h b/Core/gb.h index 2e646e1e..3d0a9193 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -426,6 +426,7 @@ struct GB_gameboy_internal_s { uint8_t visible_objs[10]; uint8_t n_visible_objs; bool fetching_objects; + uint8_t oam_search_index; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From e9eeace995f8885a27660ad740795900ecb50cfd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 19:50:19 +0300 Subject: [PATCH 0582/1216] The object enabled bit is checked only when popping from the object FIFO. Objects affect timing even when disabled. --- Core/display.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index ee31a987..70b3a1fe 100755 --- a/Core/display.c +++ b/Core/display.c @@ -357,6 +357,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (pixel && bg_priority) { draw_oam = false; } + else if ((gb->io_registers[GB_IO_LCDC] & 2) == 0) { + draw_oam = false; + } if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } @@ -537,7 +540,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Handle objects */ while (gb->n_visible_objs != 0 && - (gb->io_registers[GB_IO_LCDC] & 2) != 0 && objects[gb->visible_objs[gb->n_visible_objs - 1]].x == (uint8_t)(gb->position_in_line + 8)) { if (!gb->fetching_objects) { From 48a8db233da2bd580398261a714643b3869bad9b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 19:54:11 +0300 Subject: [PATCH 0583/1216] Refinement to the last fix --- Core/display.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 70b3a1fe..2eb758d3 100755 --- a/Core/display.c +++ b/Core/display.c @@ -322,7 +322,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) { oam_fifo_item = fifo_pop(&gb->oam_fifo); - if (oam_fifo_item->pixel) { + if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { draw_oam = true; bg_priority |= oam_fifo_item->bg_priority; } @@ -357,9 +357,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (pixel && bg_priority) { draw_oam = false; } - else if ((gb->io_registers[GB_IO_LCDC] & 2) == 0) { - draw_oam = false; - } if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } From 4e3928df81652278a1f8623a888ba52c2669d60b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 20:01:27 +0300 Subject: [PATCH 0584/1216] =?UTF-8?q?Turns=20out=20the=20behavior=20differ?= =?UTF-8?q?s=20between=20DMG=20and=20CGB=20=E2=80=93=20in=20DMG=20mode,=20?= =?UTF-8?q?the=20objects=20enabled=20bit=20is=20checked=20before=20halting?= =?UTF-8?q?=20the=20FIFOs,=20meaning=20that=20disabled=20sprites=20do=20no?= =?UTF-8?q?t=20affect=20Mode=203=E2=80=99s=20length=20on=20the=20DMG.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/display.c b/Core/display.c index 2eb758d3..f95a2525 100755 --- a/Core/display.c +++ b/Core/display.c @@ -536,7 +536,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->in_window = false; while (true) { /* Handle objects */ + /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. + On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ while (gb->n_visible_objs != 0 && + (gb->io_registers[GB_IO_LCDC] & 2 || gb->is_cgb) && objects[gb->visible_objs[gb->n_visible_objs - 1]].x == (uint8_t)(gb->position_in_line + 8)) { if (!gb->fetching_objects) { From f8c6b9e7a0f7915840c7e59164239b2c43c62f55 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Mar 2018 21:26:49 +0300 Subject: [PATCH 0585/1216] Fixed the lcd command --- Core/debugger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 98247b3d..5e169d5d 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1480,8 +1480,8 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); - GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles / 2); - GB_log(gb, "Current line: %d\n", gb->display_cycles / 456 / 2); + GB_log(gb, "\nCycles until next event: %d\n", -gb->display_cycles / 2); + GB_log(gb, "Current line: %d\n", gb->current_line); GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); From d343152fca43895f6560cc6b0c5db763fedb99fa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Mar 2018 00:32:19 +0300 Subject: [PATCH 0586/1216] Basic emulation of the OAM bug --- Core/z80_cpu.c | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 63b43496..1ebac448 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -23,6 +23,21 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); */ +static void trigger_oam_bug(GB_gameboy_t *gb, uint8_t register_id) +{ + if (gb->is_cgb) return; + + if (gb->registers[register_id] >= 0xFE00 && gb->registers[register_id] < 0xFF00) { + if (gb->oam_search_index < 38 && gb->oam_search_index > 0) { + /* Todo: bytes 2-7 are copied from the previous 8 bytes, but bytes 0 and 1 behave differently + on real hardware*/ + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->oam_search_index * 4 + 4 + i] = gb->oam[gb->oam_search_index * 4 - 4 + i]; + } + } + } +} + static void ill(GB_gameboy_t *gb, uint8_t opcode) { GB_log(gb, "Illegal Opcode. Halting.\n"); @@ -89,9 +104,10 @@ static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t register_id; - GB_advance_cycles(gb, 8); - register_id = (opcode >> 4) + 1; + uint8_t register_id = (opcode >> 4) + 1; + GB_advance_cycles(gb, 4); + trigger_oam_bug(gb, register_id); /* Todo: test T-cycle timing */ + GB_advance_cycles(gb, 4); gb->registers[register_id]++; } @@ -213,9 +229,10 @@ static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t register_id; - GB_advance_cycles(gb, 8); - register_id = (opcode >> 4) + 1; + uint8_t register_id = (opcode >> 4) + 1; + GB_advance_cycles(gb, 4); + trigger_oam_bug(gb, register_id); /* Todo: test T-cycle timing */ + GB_advance_cycles(gb, 4); gb->registers[register_id]--; } @@ -411,6 +428,7 @@ static void ccf(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 3); + trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 5); } @@ -418,6 +436,7 @@ static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 3); + trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 5); } @@ -425,6 +444,7 @@ static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); + trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; GB_advance_cycles(gb, 4); @@ -433,6 +453,7 @@ static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); + trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; GB_advance_cycles(gb, 4); @@ -738,12 +759,13 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 3; - gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ + gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); GB_advance_cycles(gb, 4); - gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ + gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; GB_advance_cycles(gb, 4); 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, uint8_t opcode) @@ -801,10 +823,11 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 7); register_id = ((opcode >> 4) + 1) & 3; - gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); + trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); GB_advance_cycles(gb, 5); } From 5cb74fb68479e5bea95958a9ca5667b732463f98 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Mar 2018 02:58:37 +0300 Subject: [PATCH 0587/1216] Bugfix: turning the PPU off during OAM mode made the OAM bug persist while the LCD is off --- Core/display.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/display.c b/Core/display.c index f95a2525..36363fb5 100755 --- a/Core/display.c +++ b/Core/display.c @@ -287,6 +287,8 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->window_disabled_while_active = false; gb->current_line = 0; gb->ly_for_comparison = 0; + + gb->oam_search_index = 0; } static void add_object_from_index(GB_gameboy_t *gb, unsigned index) From 9093f22293d38d21b8fa662bf03c4cc587b2862e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Mar 2018 14:46:51 +0300 Subject: [PATCH 0588/1216] More accurate emulation of the OAM bug --- Core/z80_cpu.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 1ebac448..12a3073e 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -23,15 +23,24 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); */ +static uint8_t bitwise_glitch(uint8_t a, uint8_t b, uint8_t c) +{ + return ((a ^ c) & (b ^ c)) ^ c; +} + static void trigger_oam_bug(GB_gameboy_t *gb, uint8_t register_id) { if (gb->is_cgb) return; if (gb->registers[register_id] >= 0xFE00 && gb->registers[register_id] < 0xFF00) { if (gb->oam_search_index < 38 && gb->oam_search_index > 0) { - /* Todo: bytes 2-7 are copied from the previous 8 bytes, but bytes 0 and 1 behave differently - on real hardware*/ - for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->oam_search_index * 4 + 4] = bitwise_glitch(gb->oam[gb->oam_search_index * 4 + 4], + gb->oam[gb->oam_search_index * 4 - 4], + gb->oam[gb->oam_search_index * 4]); + gb->oam[gb->oam_search_index * 4 + 5] = bitwise_glitch(gb->oam[gb->oam_search_index * 4 + 5], + gb->oam[gb->oam_search_index * 4 - 3], + gb->oam[gb->oam_search_index * 4 + 1]); + for (unsigned i = 2; i < 8; i++) { gb->oam[gb->oam_search_index * 4 + 4 + i] = gb->oam[gb->oam_search_index * 4 - 4 + i]; } } From 4986930511f28ecfc902cabc9a0839291be75f02 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 15:46:00 +0300 Subject: [PATCH 0589/1216] Mostly complete emulation of the OAM bug. Passes oam_bug-2. --- Core/display.c | 13 +++- Core/gb.c | 2 + Core/gb.h | 1 + Core/memory.c | 189 +++++++++++++++++++++++++++++++++++++++++++++---- Core/memory.h | 2 + Core/z80_cpu.c | 96 ++++++++++--------------- 6 files changed, 227 insertions(+), 76 deletions(-) diff --git a/Core/display.c b/Core/display.c index 36363fb5..b614dca9 100755 --- a/Core/display.c +++ b/Core/display.c @@ -288,7 +288,7 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->current_line = 0; gb->ly_for_comparison = 0; - gb->oam_search_index = 0; + gb->accessed_oam_row = -1; } static void add_object_from_index(GB_gameboy_t *gb, unsigned index) @@ -478,6 +478,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { gb->oam_write_blocked = gb->is_cgb; + gb->accessed_oam_row = 0; GB_SLEEP(gb, display, 6, 3); gb->io_registers[GB_IO_LY] = gb->current_line; gb->oam_read_blocked = true; @@ -504,8 +505,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->n_visible_objs = 0; for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { - add_object_from_index(gb, gb->oam_search_index); + if (gb->is_cgb) { + add_object_from_index(gb, gb->oam_search_index); + /* The CGB does not care about the accessed OAM row as there's no OAM bug*/ + } GB_SLEEP(gb, display, 8, 2); + if (!gb->is_cgb) { + add_object_from_index(gb, gb->oam_search_index); + gb->accessed_oam_row = (gb->oam_search_index & ~1) * 4 + 8; + } if (gb->oam_search_index == 37) { gb->vram_read_blocked = !gb->is_cgb; gb->vram_write_blocked = false; @@ -513,6 +521,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); } } + gb->accessed_oam_row = -1; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; diff --git a/Core/gb.c b/Core/gb.c index 01c049ae..fe8fe5a6 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -500,6 +500,8 @@ void GB_reset(GB_gameboy_t *gb) /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */ gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = gb->is_cgb? 0x00 : 0xFF; + + gb->accessed_oam_row = -1; gb->magic = (uintptr_t)'SAME'; } diff --git a/Core/gb.h b/Core/gb.h index 3d0a9193..bf836cc0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -427,6 +427,7 @@ struct GB_gameboy_internal_s { uint8_t n_visible_objs; bool fetching_objects; uint8_t oam_search_index; + uint8_t accessed_oam_row; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index 41345daf..ecef4c86 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -29,6 +29,84 @@ static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) return GB_BUS_INTERNAL; } +static uint8_t bitwise_glitch(uint8_t a, uint8_t b, uint8_t c) +{ + return ((a ^ c) & (b ^ c)) ^ c; +} + +static uint8_t bitwise_glitch_read(uint8_t a, uint8_t b, uint8_t c) +{ + return b | (a & c); +} + +static uint8_t bitwise_glitch_pop(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +{ + return (b & a) | (b & c) | (b & d) | (a & c & d); +} + +void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) +{ + if (gb->is_cgb) return; + + if (address >= 0xFE00 && address < 0xFF00) { + if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) { + gb->oam[gb->accessed_oam_row] = bitwise_glitch(gb->oam[gb->accessed_oam_row], + gb->oam[gb->accessed_oam_row - 8], + gb->oam[gb->accessed_oam_row - 4]); + gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch(gb->oam[gb->accessed_oam_row + 1], + gb->oam[gb->accessed_oam_row - 7], + gb->oam[gb->accessed_oam_row - 3]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; + } + } + } +} + +void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) +{ + if (gb->is_cgb) return; + + if (address >= 0xFE00 && address < 0xFF00) { + if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) { + gb->oam[gb->accessed_oam_row - 8] = + gb->oam[gb->accessed_oam_row] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row], + gb->oam[gb->accessed_oam_row - 8], + gb->oam[gb->accessed_oam_row - 4]); + gb->oam[gb->accessed_oam_row - 7] = + gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row + 1], + gb->oam[gb->accessed_oam_row - 7], + gb->oam[gb->accessed_oam_row - 3]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; + } + } + } +} + +void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address) +{ + if (gb->is_cgb) return; + + if (address >= 0xFE00 && address < 0xFF00) { + if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 0x20 && gb->accessed_oam_row < 0x98) { + gb->oam[gb->accessed_oam_row - 0x8] = bitwise_glitch_pop(gb->oam[gb->accessed_oam_row - 0x10], + gb->oam[gb->accessed_oam_row - 0x08], + gb->oam[gb->accessed_oam_row ], + gb->oam[gb->accessed_oam_row - 0x04] + ); + gb->oam[gb->accessed_oam_row - 0x7] = bitwise_glitch_pop(gb->oam[gb->accessed_oam_row - 0x0f], + gb->oam[gb->accessed_oam_row - 0x07], + gb->oam[gb->accessed_oam_row + 0x01], + gb->oam[gb->accessed_oam_row - 0x03] + ); + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; + } + } + } +} + static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) { if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xFE00) return false; @@ -118,14 +196,54 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->ram[addr & 0x0FFF]; } - if (addr < 0xFEA0) { - if (gb->oam_read_blocked || (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { - return 0xFF; - } - return gb->oam[addr & 0xFF]; - } - if (addr < 0xFF00) { + if (gb->oam_write_blocked) { + GB_trigger_oam_bug_read(gb, addr); + return 0xff; + } + + if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + /* Todo: Does reading from OAM during DMA causes the OAM bug? */ + return 0xff; + } + + if (gb->oam_read_blocked) { + if (!gb->is_cgb) { + if (addr < 0xFEA0) { + if (gb->accessed_oam_row == 0) { + gb->oam[(addr & 0xf8)] = + gb->oam[0] = bitwise_glitch_read(gb->oam[0], + gb->oam[(addr & 0xf8)], + gb->oam[(addr & 0xfe)]); + gb->oam[(addr & 0xf8) + 1] = + gb->oam[1] = bitwise_glitch_read(gb->oam[1], + gb->oam[(addr & 0xf8) + 1], + gb->oam[(addr & 0xfe) | 1]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[i] = gb->oam[(addr & 0xf8) + i]; + } + } + else if (gb->accessed_oam_row == 0xa0) { + gb->oam[0x9e] = bitwise_glitch_read(gb->oam[0x9c], + gb->oam[0x9e], + gb->oam[(addr & 0xf8) | 6]); + gb->oam[0x9f] = bitwise_glitch_read(gb->oam[0x9d], + gb->oam[0x9f], + gb->oam[(addr & 0xf8) | 7]); + + for (unsigned i = 0; i < 8; i++) { + gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; + } + } + } + } + return 0xff; + } + + if (addr < 0xFEA0) { + return gb->oam[addr & 0xFF]; + } + /* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */ /* Also, writes to this area are not emulated */ if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */ @@ -134,6 +252,10 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (gb->is_cgb) { return (addr & 0xF0) | ((addr >> 4) & 0xF); } + } + + if (addr < 0xFF00) { + return 0; } @@ -398,16 +520,53 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - if (addr < 0xFEA0) { - if (gb->oam_write_blocked|| (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + if (addr < 0xFF00) { + if (gb->oam_write_blocked) { + GB_trigger_oam_bug(gb, addr); return; } - gb->oam[addr & 0xFF] = value; - return; - } - - if (addr < 0xFF00) { - GB_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr); + + if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + /* Todo: Does writing to OAM during DMA causes the OAM bug? */ + return; + } + + if (gb->is_cgb) { + if (addr < 0xFEA0) { + gb->oam[addr & 0xFF] = value; + } + return; + } + + if (addr < 0xFEA0) { + if (gb->accessed_oam_row == 0xa0) { + for (unsigned i = 0; i < 8; i++) { + if ((i & 6) != (addr & 6)) { + gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; + } + else { + gb->oam[(addr & 0xf8) + i] = bitwise_glitch(gb->oam[(addr & 0xf8) + i], gb->oam[0x9c], gb->oam[0x98 + i]); + } + } + } + + gb->oam[addr & 0xFF] = value; + + if (gb->accessed_oam_row == 0) { + gb->oam[0] = bitwise_glitch(gb->oam[0], + gb->oam[(addr & 0xf8)], + gb->oam[(addr & 0xfe)]); + gb->oam[1] = bitwise_glitch(gb->oam[1], + gb->oam[(addr & 0xf8) + 1], + gb->oam[(addr & 0xfe) | 1]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[i] = gb->oam[(addr & 0xf8) + i]; + } + } + } + else if (gb->accessed_oam_row == 0) { + gb->oam[addr & 0x7] = value; + } return; } diff --git a/Core/memory.h b/Core/memory.h index 16b3b922..c000b7a3 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -7,6 +7,8 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); #ifdef GB_INTERNAL void GB_dma_run(GB_gameboy_t *gb); void GB_hdma_run(GB_gameboy_t *gb); +void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); +void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address); #endif #endif /* memory_h */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 12a3073e..4db336c9 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -22,31 +22,6 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); This is equivalent to running the memory write 1 T-cycle before the memory read. */ - -static uint8_t bitwise_glitch(uint8_t a, uint8_t b, uint8_t c) -{ - return ((a ^ c) & (b ^ c)) ^ c; -} - -static void trigger_oam_bug(GB_gameboy_t *gb, uint8_t register_id) -{ - if (gb->is_cgb) return; - - if (gb->registers[register_id] >= 0xFE00 && gb->registers[register_id] < 0xFF00) { - if (gb->oam_search_index < 38 && gb->oam_search_index > 0) { - gb->oam[gb->oam_search_index * 4 + 4] = bitwise_glitch(gb->oam[gb->oam_search_index * 4 + 4], - gb->oam[gb->oam_search_index * 4 - 4], - gb->oam[gb->oam_search_index * 4]); - gb->oam[gb->oam_search_index * 4 + 5] = bitwise_glitch(gb->oam[gb->oam_search_index * 4 + 5], - gb->oam[gb->oam_search_index * 4 - 3], - gb->oam[gb->oam_search_index * 4 + 1]); - for (unsigned i = 2; i < 8; i++) { - gb->oam[gb->oam_search_index * 4 + 4 + i] = gb->oam[gb->oam_search_index * 4 - 4 + i]; - } - } - } -} - static void ill(GB_gameboy_t *gb, uint8_t opcode) { GB_log(gb, "Illegal Opcode. Halting.\n"); @@ -115,7 +90,7 @@ static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id = (opcode >> 4) + 1; GB_advance_cycles(gb, 4); - trigger_oam_bug(gb, register_id); /* Todo: test T-cycle timing */ + GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ GB_advance_cycles(gb, 4); gb->registers[register_id]++; } @@ -240,7 +215,7 @@ static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id = (opcode >> 4) + 1; GB_advance_cycles(gb, 4); - trigger_oam_bug(gb, register_id); /* Todo: test T-cycle timing */ + GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ GB_advance_cycles(gb, 4); gb->registers[register_id]--; } @@ -437,7 +412,6 @@ static void ccf(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 3); - trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 5); } @@ -445,7 +419,6 @@ static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 3); - trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); GB_advance_cycles(gb, 5); } @@ -453,7 +426,7 @@ static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { GB_advance_cycles(gb, 4); - trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ + GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_HL]); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; GB_advance_cycles(gb, 4); @@ -461,8 +434,9 @@ static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ GB_advance_cycles(gb, 4); - trigger_oam_bug(gb, GB_REGISTER_HL); /* Todo: test T-cycle timing */ + GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_HL]); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; GB_advance_cycles(gb, 4); @@ -748,15 +722,14 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) { - /* Todo: Verify timing */ if (condition_code(gb, opcode)) { GB_debugger_ret_hook(gb); GB_advance_cycles(gb, 8); - gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); GB_advance_cycles(gb, 4); - gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; GB_advance_cycles(gb, 8); - gb->registers[GB_REGISTER_SP] += 2; } else { GB_advance_cycles(gb, 8); @@ -768,10 +741,9 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 3; - trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ + GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); GB_advance_cycles(gb, 4); - trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. @@ -808,14 +780,15 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) uint16_t call_addr = gb->pc - 1; if (condition_code(gb, opcode)) { GB_advance_cycles(gb, 4); - gb->registers[GB_REGISTER_SP] -= 2; uint16_t addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= (GB_read_memory(gb, gb->pc++) << 8); - GB_advance_cycles(gb, 7); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 3); + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 5); gb->pc = addr; @@ -830,12 +803,12 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 7); + GB_advance_cycles(gb, 3); + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ + GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 3; - trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); GB_advance_cycles(gb, 4); - trigger_oam_bug(gb, GB_REGISTER_SP); /* Todo: test T-cycle timing */ GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); GB_advance_cycles(gb, 5); } @@ -982,11 +955,12 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 7); - gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 3); + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 5); gb->pc = opcode ^ 0xC7; GB_debugger_call_hook(gb, call_addr); @@ -996,11 +970,11 @@ static void ret(GB_gameboy_t *gb, uint8_t opcode) { GB_debugger_ret_hook(gb); GB_advance_cycles(gb, 4); - gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); GB_advance_cycles(gb, 4); - gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; GB_advance_cycles(gb, 8); - gb->registers[GB_REGISTER_SP] += 2; } static void reti(GB_gameboy_t *gb, uint8_t opcode) @@ -1013,14 +987,15 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; GB_advance_cycles(gb, 4); - gb->registers[GB_REGISTER_SP] -= 2; uint16_t addr = GB_read_memory(gb, gb->pc++); GB_advance_cycles(gb, 4); addr |= (GB_read_memory(gb, gb->pc++) << 8); - GB_advance_cycles(gb, 7); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 3); + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 5); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); @@ -1419,16 +1394,19 @@ void GB_cpu_run(GB_gameboy_t *gb) if (gb->halted && !effecitve_ime && interrupt_queue) { gb->halted = false; } + /* Call interrupt */ else if (effecitve_ime && interrupt_queue) { gb->halted = false; uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 11); - gb->registers[GB_REGISTER_SP] -= 2; - GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + + GB_advance_cycles(gb, 7); + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); interrupt_queue = gb->interrupt_enable; GB_advance_cycles(gb, 4); - GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; GB_advance_cycles(gb, 5); From 4cbade9a88d65b6b254a477b7aea6a9251bcb24d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 15:55:12 +0300 Subject: [PATCH 0590/1216] Function name change --- Core/memory.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index ecef4c86..a4edb843 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -39,7 +39,7 @@ static uint8_t bitwise_glitch_read(uint8_t a, uint8_t b, uint8_t c) return b | (a & c); } -static uint8_t bitwise_glitch_pop(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +static uint8_t bitwise_glitch_read_increase(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { return (b & a) | (b & c) | (b & d) | (a & c & d); } @@ -90,16 +90,16 @@ void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address) if (address >= 0xFE00 && address < 0xFF00) { if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 0x20 && gb->accessed_oam_row < 0x98) { - gb->oam[gb->accessed_oam_row - 0x8] = bitwise_glitch_pop(gb->oam[gb->accessed_oam_row - 0x10], - gb->oam[gb->accessed_oam_row - 0x08], - gb->oam[gb->accessed_oam_row ], - gb->oam[gb->accessed_oam_row - 0x04] - ); - gb->oam[gb->accessed_oam_row - 0x7] = bitwise_glitch_pop(gb->oam[gb->accessed_oam_row - 0x0f], - gb->oam[gb->accessed_oam_row - 0x07], - gb->oam[gb->accessed_oam_row + 0x01], - gb->oam[gb->accessed_oam_row - 0x03] - ); + gb->oam[gb->accessed_oam_row - 0x8] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x10], + gb->oam[gb->accessed_oam_row - 0x08], + gb->oam[gb->accessed_oam_row ], + gb->oam[gb->accessed_oam_row - 0x04] + ); + gb->oam[gb->accessed_oam_row - 0x7] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x0f], + gb->oam[gb->accessed_oam_row - 0x07], + gb->oam[gb->accessed_oam_row + 0x01], + gb->oam[gb->accessed_oam_row - 0x03] + ); for (unsigned i = 0; i < 8; i++) { gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; } From 7543461c24c22e07c2533fed8b7c3f5c84d91404 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 16:36:39 +0300 Subject: [PATCH 0591/1216] Increasing PC in OAM triggers the OAM bug --- Core/z80_cpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 4db336c9..a6d180e7 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1427,6 +1427,7 @@ void GB_cpu_run(GB_gameboy_t *gb) } /* Run mode */ else if(!gb->halted) { + GB_trigger_oam_bug_read_increase(gb, gb->pc); /* Todo: test T-cycle timing */ gb->last_opcode_read = GB_read_memory(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; From 7671648fcaf42562b82db1c5e5161173cbbfabc0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 19:06:36 +0300 Subject: [PATCH 0592/1216] Simplified a function --- Core/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index a4edb843..6d9f766d 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -41,7 +41,7 @@ static uint8_t bitwise_glitch_read(uint8_t a, uint8_t b, uint8_t c) static uint8_t bitwise_glitch_read_increase(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { - return (b & a) | (b & c) | (b & d) | (a & c & d); + return (b & (a | c | d)) | (a & c & d); } void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) From f5493e023dbbcee3a485176df2f7b4539eab19ac Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 20:21:24 +0300 Subject: [PATCH 0593/1216] Fixed a timing regression in the CB opcodes --- Core/z80_cpu.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index a6d180e7..2b319abc 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -523,9 +523,8 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) gb->registers[GB_REGISTER_AF] |= value << 8; } else { - GB_advance_cycles(gb, 1); GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 4); + GB_advance_cycles(gb, 5); } } else { @@ -1227,8 +1226,8 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t bit7; uint8_t value; - GB_advance_cycles(gb, 3); - value = get_src_value(gb, opcode, 4); + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode, 3); bit7 = value & 0x80; gb->registers[GB_REGISTER_AF] &= 0xFF00; if (value & 1) { @@ -1244,8 +1243,8 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 3); - value = get_src_value(gb, opcode, 4); + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode, 3); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 1)); if (value & 1) { @@ -1259,8 +1258,8 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode) static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 3); - value = get_src_value(gb, opcode, 4); + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode, 3); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 4) | (value << 4)); if (!value) { From 0912a30bb9eeec464028a45bdf264f4834f15b54 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 21:04:55 +0300 Subject: [PATCH 0594/1216] Fixed a regression in dmg_sound-2 --- Core/apu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index cfc0d11a..4d3d9523 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -553,7 +553,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1; gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f)); if (!gb->apu.global_enable) { - gb->io_registers[reg] &= 0x3f; + value &= 0x3f; } break; } From 0e3d2770d92e12603d4426d3aace7b09814298d3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 22:13:08 +0300 Subject: [PATCH 0595/1216] =?UTF-8?q?Properly=20handle=20cases=20where=20a?= =?UTF-8?q?n=20object=E2=80=99s=20X=20position=20is=20modified=20between?= =?UTF-8?q?=20the=20OAM=20mode=20and=20rendering=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 6 ++++-- Core/gb.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index b614dca9..15cbbe4b 100755 --- a/Core/display.c +++ b/Core/display.c @@ -302,10 +302,12 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { unsigned j = 0; for (; j < gb->n_visible_objs; j++) { - if (objects[gb->visible_objs[j]].x <= objects[index].x) break; + if (gb->obj_comperators[j] <= objects[index].x) break; } memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); + memmove(gb->obj_comperators + j + 1, gb->obj_comperators + j, gb->n_visible_objs - j); gb->visible_objs[j] = index; + gb->obj_comperators[j] = objects[index].x; gb->n_visible_objs++; } } @@ -551,7 +553,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ while (gb->n_visible_objs != 0 && (gb->io_registers[GB_IO_LCDC] & 2 || gb->is_cgb) && - objects[gb->visible_objs[gb->n_visible_objs - 1]].x == (uint8_t)(gb->position_in_line + 8)) { + gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { if (!gb->fetching_objects) { /* Penalty for interrupting the fetcher */ diff --git a/Core/gb.h b/Core/gb.h index bf836cc0..37685b45 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -424,6 +424,7 @@ struct GB_gameboy_internal_s { bool oam_fifo_paused; bool in_window; uint8_t visible_objs[10]; + uint8_t obj_comperators[10]; uint8_t n_visible_objs; bool fetching_objects; uint8_t oam_search_index; From fd2e169dc904c53bb45d100a2898b0b1aa67c61e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 23:05:08 +0300 Subject: [PATCH 0596/1216] =?UTF-8?q?Seems=20like=20the=20audio=20bug=20in?= =?UTF-8?q?troduced=20in=20SDL=202.0.5=20for=20macOS=20wasn=E2=80=99t=20fi?= =?UTF-8?q?xed.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SDL/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 4387b2d5..dda7803f 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -447,9 +447,10 @@ int main(int argc, char **argv) want_aspec.freq = AUDIO_FREQUENCY; want_aspec.format = AUDIO_S16SYS; want_aspec.channels = 2; -#if SDL_COMPILEDVERSION == 2005 && defined(__APPLE__) + printf("SDL version is %d\n", SDL_COMPILEDVERSION); +#if SDL_COMPILEDVERSION >= 2005 && defined(__APPLE__) /* SDL 2.0.5 on macOS introduced a bug where certain combinations of buffer lengths and frequencies - fail to produce audio correctly. This bug was fixed 2.0.6. */ + fail to produce audio correctly. */ want_aspec.samples = 2048; #else want_aspec.samples = 512; From 4cf78139a8495aaaeed58421e381239fadc52be7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Mar 2018 23:33:31 +0300 Subject: [PATCH 0597/1216] Fixed a bug where SameBoy freezes for a while after leaving turbo mode --- Core/timing.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/timing.c b/Core/timing.c index 5328007c..c0cd1701 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -61,8 +61,9 @@ void GB_timing_sync(GB_gameboy_t *gb) uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); - if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) { - nsleep(target_nanoseconds + gb->last_sync - nanoseconds); + int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; + if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1000000000LL / GB_get_clock_rate(gb)) { + nsleep(time_to_sleep); gb->last_sync += target_nanoseconds; } else { From 96063fb0da5e687c60bdc63880c427fc0fd5856d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 28 Mar 2018 21:59:27 +0300 Subject: [PATCH 0598/1216] Fixed Windows build, added Unicode support in Windows. --- Core/gb.c | 7 ++++--- Core/gb.h | 1 - Makefile | 4 ++++ SDL/gui.c | 1 + SDL/main.c | 1 + Windows/utf8_compat.c | 14 ++++++++++++++ libretro/Makefile.common | 8 +------- libretro/jni/Android.mk | 2 +- 8 files changed, 26 insertions(+), 12 deletions(-) create mode 100755 Windows/utf8_compat.c diff --git a/Core/gb.c b/Core/gb.c index fe8fe5a6..e0da5799 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -11,9 +11,10 @@ #endif #include "gb.h" -/* The libretro frontend does not link against rewind.c, so we provide empty weak alternatives to its functions */ -void __attribute__((weak)) GB_rewind_free(GB_gameboy_t *gb) { } -void __attribute__((weak)) GB_rewind_push(GB_gameboy_t *gb) { } +#ifdef DISABLE_REWIND +#define GB_rewind_free(...) +#define GB_rewind_push(...) +#endif void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { diff --git a/Core/gb.h b/Core/gb.h index 37685b45..43bb6116 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -2,7 +2,6 @@ #define GB_h #include #include -#include #include #include "gb_struct_def.h" diff --git a/Makefile b/Makefile index 3d5a6322..f970d8e7 100755 --- a/Makefile +++ b/Makefile @@ -106,6 +106,10 @@ COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) QUICKLOOK_SOURCES := $(shell ls QuickLook/*.m) $(shell ls QuickLook/*.c) endif +ifeq ($(PLATFORM),windows32) +CORE_SOURCES += $(shell ls Windows/*.c) +endif + CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES)) COCOA_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(COCOA_SOURCES)) QUICKLOOK_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(QUICKLOOK_SOURCES)) diff --git a/SDL/gui.c b/SDL/gui.c index c152e596..a09cb49b 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1,5 +1,6 @@ #include #include +#include #include "utils.h" #include "gui.h" #include "font.h" diff --git a/SDL/main.c b/SDL/main.c index dda7803f..bd14377d 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/Windows/utf8_compat.c b/Windows/utf8_compat.c new file mode 100755 index 00000000..1005f22a --- /dev/null +++ b/Windows/utf8_compat.c @@ -0,0 +1,14 @@ +#include +#include +#include + +FILE *fopen(const char *filename, const char *mode) +{ + wchar_t w_filename[MAX_PATH] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename) / sizeof(w_filename[0])); + + wchar_t w_mode[8] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, mode, -1, w_mode, sizeof(w_mode) / sizeof(w_mode[0])); + + return _wfopen(w_filename, w_mode); +} \ No newline at end of file diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 8e5af51f..889e972c 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -16,13 +16,7 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/libretro/dmg_boot.c \ $(CORE_DIR)/libretro/libretro.c -ifeq ($(HAVE_DEBUGGER), 1) -SOURCES_C += $(CORE_DIR)/Core/debugger.c \ - $(CORE_DIR)/Core/z80_disassembler.c -else -CFLAGS += -DDISABLE_DEBUGGER -endif -CFLAGS += -DDISABLE_TIMEKEEPING +CFLAGS += -DDISABLE_TIMEKEEPING -DDISABLE_REWIND -DDISABLE_DEBUGGER SOURCES_CXX := diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 4c4a7b67..1d1ac940 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -28,7 +28,7 @@ CORE_DIR := $(realpath ../..) include ../Makefile.common LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) -LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -DDISABLE_DEBUGGER -DDISABLE_TIMEKEEPING -Wno-multichar +LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -DDISABLE_DEBUGGER -DDISABLE_REWIND -DDISABLE_TIMEKEEPING -Wno-multichar LOCAL_C_INCLUDES = $(INCFLAGS) include $(BUILD_SHARED_LIBRARY) From 7bfe5de9c72013cd6a72f249cac013cf8912f1c7 Mon Sep 17 00:00:00 2001 From: Kyle Swanson Date: Wed, 28 Mar 2018 21:37:34 -0700 Subject: [PATCH 0599/1216] chmod -x --- Core/apu.c | 0 Core/display.c | 0 Core/gb.c | 0 Core/symbol_hash.c | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Core/apu.c mode change 100755 => 100644 Core/display.c mode change 100755 => 100644 Core/gb.c mode change 100755 => 100644 Core/symbol_hash.c diff --git a/Core/apu.c b/Core/apu.c old mode 100755 new mode 100644 diff --git a/Core/display.c b/Core/display.c old mode 100755 new mode 100644 diff --git a/Core/gb.c b/Core/gb.c old mode 100755 new mode 100644 diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c old mode 100755 new mode 100644 From 7ffe132e795003a68eda934eec50c24ecd01337e Mon Sep 17 00:00:00 2001 From: Kyle Swanson Date: Wed, 28 Mar 2018 21:38:48 -0700 Subject: [PATCH 0600/1216] fix typo --- Core/z80_cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 2b319abc..fb85c7f3 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -6,7 +6,7 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); /* - About memroy timings: + About memory timings: Each M-cycle consists of 4 T-cycles. Every time the CPU accesses the memory it happens on the 1st T-cycle of an M-cycle. During that cycle, other things may happen, such the PPU drawing to the screen. Since we can't really run From e380a00b672ffa8fa277771bec3c52f68efb3a12 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Mar 2018 21:06:53 +0300 Subject: [PATCH 0601/1216] Fixed another timing regression with the CB opcodes --- BootROMs/dmg_boot.asm | 2 +- Core/z80_cpu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm index d4176a87..46b389a9 100644 --- a/BootROMs/dmg_boot.asm +++ b/BootROMs/dmg_boot.asm @@ -125,7 +125,7 @@ DoubleBitsAndWriteRow: WaitFrame: push hl ld hl, $FF0F - res 0, [hl] + res 0, [hl] .wait bit 0, [hl] jr z, .wait diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 2b319abc..b8b5f639 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1272,7 +1272,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; uint8_t bit; GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode, (opcode & 0xC0) == 0x40? 4 : 3); bit = 1 << ((opcode >> 3) & 7); if ((opcode & 0xC0) == 0x40) { /* Bit */ gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; From c7ca786e7701c457f37d58ee4021ead11da5966f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Mar 2018 21:27:19 +0300 Subject: [PATCH 0602/1216] Attempt to fix building using MINGW. Affects #55 --- Core/timing.c | 2 ++ Makefile | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Core/timing.c b/Core/timing.c index c0cd1701..38b27ac3 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -1,6 +1,8 @@ #include "gb.h" #ifdef _WIN32 +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0500 +#endif #include #else #include diff --git a/Makefile b/Makefile index f970d8e7..ade58630 100755 --- a/Makefile +++ b/Makefile @@ -4,6 +4,9 @@ # Set target, configuration, version and destination folders PLATFORM := $(shell uname -s) +ifneq ($(findstring MINGW,$(PLATFORM)),) +PLATFORM := windows32 +endif ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa From b757b4c59049f074299aa945af27108034ca9d9a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Mar 2018 21:46:14 +0300 Subject: [PATCH 0603/1216] Another attempt to fix building using MINGW. Affects #55 --- Windows/math.h | 4 +++- Windows/stdio.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Windows/math.h b/Windows/math.h index 41d6d8a7..2b934f90 100755 --- a/Windows/math.h +++ b/Windows/math.h @@ -1,7 +1,9 @@ #pragma once #include_next +#ifndef __MINGW32__ /* "Old" (Pre-2015) Windows headers/libc don't have round. */ static inline double round(double f) { return f >= 0? (int)(f + 0.5) : (int)(f - 0.5); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Windows/stdio.h b/Windows/stdio.h index ceba4d16..d68c9569 100755 --- a/Windows/stdio.h +++ b/Windows/stdio.h @@ -2,6 +2,7 @@ #include_next #include +#ifndef __MINGW32__ #ifndef __LIBRETRO__ static inline int vasprintf(char **str, const char *fmt, va_list args) { @@ -16,6 +17,7 @@ static inline int vasprintf(char **str, const char *fmt, va_list args) return ret; } #endif +#endif /* This code is public domain -- Will Hartung 4/9/09 */ static inline size_t getline(char **lineptr, size_t *n, FILE *stream) { From 5a81d83d301cb1568199941baece3cd1390d14b5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Mar 2018 22:02:01 +0300 Subject: [PATCH 0604/1216] Yet another attempt to fix building using MINGW. Affects #55 --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index ade58630..3be722d5 100755 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ PLATFORM := $(shell uname -s) ifneq ($(findstring MINGW,$(PLATFORM)),) PLATFORM := windows32 +USE_WINDRES := true endif ifeq ($(PLATFORM),Darwin) @@ -235,12 +236,18 @@ $(BIN)/SDL/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/r -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:console +ifneq ($(USE_WINDRES),) +$(OBJ)/%.o: %.rc + -@$(MKDIR) -p $(dir $@) + windres -DVERSION=\"$(VERSION)\" $^ $@ +else $(OBJ)/%.res: %.rc -@$(MKDIR) -p $(dir $@) rc /fo $@ /dVERSION=\"$(VERSION)\" $^ %.o: %.res cvtres /OUT:"$@" $^ +endif # We must provide SDL2.dll with the Windows port. $(BIN)/SDL/SDL2.dll: From 3a52d2da97625a2c94ee8edbf7eccafdcbf636b7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Mar 2018 22:22:50 +0300 Subject: [PATCH 0605/1216] Updated Windows copyright --- Windows/resources.rc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Windows/resources.rc b/Windows/resources.rc index 74e3839d..7a6964f2 100644 --- a/Windows/resources.rc +++ b/Windows/resources.rc @@ -9,7 +9,7 @@ FILETYPE 0x1 VALUE "CompanyName", "Lior Halphon" VALUE "FileDescription", "SameBoy" VALUE "FileVersion", VERSION - VALUE "LegalCopyright", "Copyright © 2015-2016 Lior Halphon" + VALUE "LegalCopyright", "Copyright © 2015-2018 Lior Halphon" VALUE "ProductName", "SameBoy" VALUE "ProductVersion", VERSION VALUE "WWW", "https://github.com/LIJI32/SameBoy" @@ -21,4 +21,4 @@ FILETYPE 0x1 } } -IDI_ICON1 ICON DISCARDABLE "SameBoy.ico" \ No newline at end of file +IDI_ICON1 ICON DISCARDABLE "SameBoy.ico" From 311a47080679a3fa61e426f0c3f2775b19a449a9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Mar 2018 23:11:53 +0300 Subject: [PATCH 0606/1216] Maybe now? Affects #55 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3be722d5..f32e9310 100755 --- a/Makefile +++ b/Makefile @@ -239,7 +239,7 @@ $(BIN)/SDL/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/r ifneq ($(USE_WINDRES),) $(OBJ)/%.o: %.rc -@$(MKDIR) -p $(dir $@) - windres -DVERSION=\"$(VERSION)\" $^ $@ + windres --preprocessor cpp -DVERSION=\"$(VERSION)\" $^ $@ else $(OBJ)/%.res: %.rc -@$(MKDIR) -p $(dir $@) From 2c44ffbe391d18032c9099f6cafb92a77158480d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Mar 2018 02:53:49 +0300 Subject: [PATCH 0607/1216] More accurate fetcher penalty emulation, fixed intr_2_mode0_timing_sprites_nops, affects #54 --- Core/display.c | 13 ++++++------- Core/gb.h | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Core/display.c b/Core/display.c index 15cbbe4b..4215197b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -555,14 +555,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) (gb->io_registers[GB_IO_LCDC] & 2 || gb->is_cgb) && gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { - if (!gb->fetching_objects) { + if (gb->fetcher_stop_penalty == 0) { /* Penalty for interrupting the fetcher */ - uint8_t penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state * 2 + gb->fetcher_divisor]; - gb->cycles_for_line += penalty; - GB_SLEEP(gb, display, 19, penalty); + gb->fetcher_stop_penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state * 2 + gb->fetcher_divisor]; } - gb->fetching_objects = true; gb->cycles_for_line += 6; GB_SLEEP(gb, display, 20, 6); GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; @@ -593,8 +590,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->n_visible_objs--; } - gb->fetching_objects = false; - /* Handle window */ /* Todo: Timing not verified by test ROM */ @@ -671,6 +666,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) render_pixel_if_possible(gb); if (push) { + gb->cycles_for_line += gb->fetcher_stop_penalty; + GB_SLEEP(gb, display, 19, gb->fetcher_stop_penalty); + gb->fetcher_stop_penalty = 0; + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); gb->bg_fifo_paused = false; diff --git a/Core/gb.h b/Core/gb.h index 43bb6116..6d4fe1be 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -425,7 +425,7 @@ struct GB_gameboy_internal_s { uint8_t visible_objs[10]; uint8_t obj_comperators[10]; uint8_t n_visible_objs; - bool fetching_objects; + uint8_t fetcher_stop_penalty; uint8_t oam_search_index; uint8_t accessed_oam_row; ); From 9811dceca141823cbd779829cf1b0b4b760481a9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Mar 2018 17:06:27 +0300 Subject: [PATCH 0608/1216] Emulate another OAM timing quirk; a sprite at x = 0 has extra penalty if SCX is not 0. Fixes intr_2_mode0_timing_sprites_scx*_nops, affects #54 --- Core/display.c | 15 ++++++++++++++- Core/gb.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 4215197b..b99d17de 100644 --- a/Core/display.c +++ b/Core/display.c @@ -326,6 +326,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) { oam_fifo_item = fifo_pop(&gb->oam_fifo); + /* Todo: Verify access timings */ if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { draw_oam = true; bg_priority |= oam_fifo_item->bg_priority; @@ -341,6 +342,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Mixing */ + /* Todo: Verify access timings */ if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { if (gb->cgb_mode) { bg_priority = false; @@ -370,6 +372,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (draw_oam) { uint8_t pixel = oam_fifo_item->pixel; if (!gb->cgb_mode) { + /* Todo: Verify access timings */ pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); } gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; @@ -535,8 +538,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line = MODE2_LENGTH + 4; fifo_clear(&gb->bg_fifo); fifo_clear(&gb->oam_fifo); + /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; + gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); gb->cycles_for_line += 5; GB_SLEEP(gb, display, 10, 5); @@ -556,8 +561,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { if (gb->fetcher_stop_penalty == 0) { + /* TODO: figure out why the penalty works this way and actual access timings */ /* Penalty for interrupting the fetcher */ gb->fetcher_stop_penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state * 2 + gb->fetcher_divisor]; + if (gb->obj_comperators[gb->n_visible_objs - 1] == 0) { + gb->fetcher_stop_penalty += gb->extra_penalty_for_sprite_at_0; + } } gb->cycles_for_line += 6; @@ -592,7 +601,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } /* Handle window */ - /* Todo: Timing not verified by test ROM */ + /* Todo: Timing (Including penalty and access timings) not verified by test ROM */ if (!gb->in_window && window_enabled(gb) && gb->current_line >= gb->io_registers[GB_IO_WY] + gb->wy_diff && gb->position_in_line + 7 == gb->io_registers[GB_IO_WX]) { @@ -608,6 +617,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) switch (gb->fetcher_state) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; + + /* Todo: Verify access timings */ if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) { map = 0x1C00; } @@ -628,6 +639,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) case GB_FETCHER_GET_TILE_DATA_LOWER: { uint8_t y_flip = 0; + + /* Todo: Verify access timings */ if (gb->io_registers[GB_IO_LCDC] & 0x10) { gb->current_tile_address = gb->current_tile * 0x10; } diff --git a/Core/gb.h b/Core/gb.h index 6d4fe1be..897dfb90 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -428,6 +428,7 @@ struct GB_gameboy_internal_s { uint8_t fetcher_stop_penalty; uint8_t oam_search_index; uint8_t accessed_oam_row; + uint8_t extra_penalty_for_sprite_at_0; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 7706b8727a3cda61cf0bc13161acca3f3940099c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 30 Mar 2018 17:06:39 +0300 Subject: [PATCH 0609/1216] Updated Cocoa copyright --- Cocoa/Info.plist | 2 +- QuickLook/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 0cc120bc..65f0df85 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -70,7 +70,7 @@ LSMinimumSystemVersion 10.9 NSHumanReadableCopyright - Copyright © 2015-2017 Lior Halphon + Copyright © 2015-2018 Lior Halphon NSMainNibFile MainMenu NSPrincipalClass diff --git a/QuickLook/Info.plist b/QuickLook/Info.plist index da833754..051e26c5 100644 --- a/QuickLook/Info.plist +++ b/QuickLook/Info.plist @@ -47,7 +47,7 @@ CFPlugInUnloadFunction NSHumanReadableCopyright - Copyright © 2015-2017 Lior Halphon + Copyright © 2015-2018 Lior Halphon QLNeedsToBeRunInMainThread QLPreviewHeight From 0a2d6e6dcb6b3ef1b630ffc935735648a5f1753b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 31 Mar 2018 12:21:34 +0300 Subject: [PATCH 0610/1216] Fixed DMG timing regression --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index b99d17de..cb3c090b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -496,7 +496,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] |= 2; } GB_STAT_update(gb); - if (gb->current_line != 0) { + if (gb->current_line != 0 || !gb->is_cgb) { gb->io_registers[GB_IO_STAT] &= ~3; } From 73dc3560a5e62f40718367d91709fcd7e1c5eb85 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 31 Mar 2018 13:18:02 +0300 Subject: [PATCH 0611/1216] Mode 0 interrupts do not occur in the glitched mode 0 of the first line 0. The extra OAM interrupt bug also affects DMG. --- Core/display.c | 6 ++++-- Core/gb.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index cb3c090b..7d0be765 100644 --- a/Core/display.c +++ b/Core/display.c @@ -245,7 +245,7 @@ void GB_STAT_update(GB_gameboy_t *gb) } switch (gb->io_registers[GB_IO_STAT] & 3) { - case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8); break; + case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->is_first_line_mode2; break; case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; } @@ -431,6 +431,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } /* Handle the very first line 0 */ + gb->is_first_line_mode2 = true; gb->current_line = 0; gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; @@ -448,6 +449,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = !gb->is_cgb; gb->oam_write_blocked = true; gb->vram_write_blocked = !gb->is_cgb; + gb->is_first_line_mode2 = false; GB_STAT_update(gb); gb->cycles_for_line += 2; @@ -727,7 +729,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (!gb->is_cgb) { gb->ly_for_comparison = -1; } - if (gb->is_cgb && gb->current_line == LINES) { + if (gb->current_line == LINES) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; GB_STAT_update(gb); diff --git a/Core/gb.h b/Core/gb.h index 897dfb90..599b6b9b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -429,6 +429,7 @@ struct GB_gameboy_internal_s { uint8_t oam_search_index; uint8_t accessed_oam_row; uint8_t extra_penalty_for_sprite_at_0; + bool is_first_line_mode2; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 0d0d9ccdaeecd7ddb81eb8775fc9be7f9ba8b5b8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 31 Mar 2018 15:52:31 +0300 Subject: [PATCH 0612/1216] Fixed a timer regression, fixes timer_if in DMG mode. Relates to #54 --- Core/timing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/timing.c b/Core/timing.c index 38b27ac3..04a87da8 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -145,7 +145,7 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) } GB_set_internal_div_counter(gb, 0); - GB_SLEEP(gb, div, 1, 2); + GB_SLEEP(gb, div, 1, 3); while (true) { advance_tima_state_machine(gb); GB_set_internal_div_counter(gb, gb->div_counter + 4); From 69a625af97eeec929051a219dcc403b8cef5b3e9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 1 Apr 2018 16:53:21 +0300 Subject: [PATCH 0613/1216] How did I miss this thing after over 2 years?! --- Cocoa/MainMenu.xib | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 18af33e9..3658f83d 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -19,7 +19,7 @@ - + @@ -37,7 +37,7 @@ - + @@ -55,7 +55,7 @@ - + @@ -355,7 +355,7 @@ - + @@ -438,7 +438,7 @@ - + From ec64c041ab7f5f69332c5c79d0a24458362f95eb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 1 Apr 2018 21:45:56 +0300 Subject: [PATCH 0614/1216] The OAM interrupt is internally implemented differently from the other 3. Fixed the stat_write_if tests, relates to #54 --- Core/display.c | 49 +++++++++++++++++++++++++++++++------------------ Core/gb.h | 1 + 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Core/display.c b/Core/display.c index 7d0be765..a7128073 100644 --- a/Core/display.c +++ b/Core/display.c @@ -230,12 +230,20 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m */ +static void trigger_oam_interrupt(GB_gameboy_t *gb) +{ + if (!gb->stat_interrupt_line && gb->oam_interrupt_line) { + gb->io_registers[GB_IO_IF] |= 2; + gb->stat_interrupt_line = true; + } +} + void GB_STAT_update(GB_gameboy_t *gb) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; - bool previous_interrupt_line = gb->stat_interrupt_line; - gb->stat_interrupt_line = false; + bool previous_interrupt_line = gb->stat_interrupt_line | gb->oam_interrupt_line; + gb->stat_interrupt_line = gb->oam_interrupt_line; /* Set LY=LYC bit */ if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { gb->io_registers[GB_IO_STAT] |= 4; @@ -247,7 +255,9 @@ void GB_STAT_update(GB_gameboy_t *gb) switch (gb->io_registers[GB_IO_STAT] & 3) { case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->is_first_line_mode2; break; case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; - case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + /* The OAM interrupt is handled differently, it reads the writable flags from STAT less frequenctly, + and is not based on the mode bits of STAT. */ + // case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; } /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ @@ -255,7 +265,7 @@ void GB_STAT_update(GB_gameboy_t *gb) gb->stat_interrupt_line = true; } - if (gb->stat_interrupt_line && ! previous_interrupt_line) { + if (gb->stat_interrupt_line && !previous_interrupt_line) { gb->io_registers[GB_IO_IF] |= 2; } } @@ -494,9 +504,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. PPU glitch? (Todo: and in double speed mode?) */ if (gb->current_line != 0 && !gb->cgb_double_speed) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 2; + gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; } + trigger_oam_interrupt(gb); GB_STAT_update(gb); if (gb->current_line != 0 || !gb->is_cgb) { gb->io_registers[GB_IO_STAT] &= ~3; @@ -508,6 +518,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] |= 2; gb->oam_write_blocked = true; gb->ly_for_comparison = gb->current_line; + if (gb->current_line == 0 || gb->cgb_double_speed) { + gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; + } + trigger_oam_interrupt(gb); GB_STAT_update(gb); gb->n_visible_objs = 0; @@ -529,14 +543,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } } gb->accessed_oam_row = -1; - gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; gb->vram_read_blocked = true; gb->vram_write_blocked = true; gb->oam_write_blocked = true; + gb->oam_interrupt_line = false; + trigger_oam_interrupt(gb); GB_STAT_update(gb); - + gb->cycles_for_line = MODE2_LENGTH + 4; fifo_clear(&gb->bg_fifo); fifo_clear(&gb->oam_fifo); @@ -730,22 +745,20 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->ly_for_comparison = -1; } if (gb->current_line == LINES) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 2; - GB_STAT_update(gb); - gb->io_registers[GB_IO_STAT] &= ~3; - } - else { - GB_STAT_update(gb); + gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; + trigger_oam_interrupt(gb); } + GB_STAT_update(gb); + gb->oam_interrupt_line = false; GB_SLEEP(gb, display, 12, 4); gb->ly_for_comparison = gb->current_line; if (gb->current_line == LINES) { - /* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */ - gb->io_registers[GB_IO_STAT] &= ~3; - gb->io_registers[GB_IO_STAT] |= 2; + /* Entering VBlank state triggers the OAM interrupt*/ + gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; + trigger_oam_interrupt(gb); GB_STAT_update(gb); + gb->oam_interrupt_line = false; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 1; diff --git a/Core/gb.h b/Core/gb.h index 599b6b9b..d4defd12 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -430,6 +430,7 @@ struct GB_gameboy_internal_s { uint8_t accessed_oam_row; uint8_t extra_penalty_for_sprite_at_0; bool is_first_line_mode2; + bool oam_interrupt_line; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 9339a6027fd12916ab610d47cccebafbbe45b0b1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 1 Apr 2018 22:20:26 +0300 Subject: [PATCH 0615/1216] Slight refinement to the last fix --- Core/display.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index a7128073..dfb8e3bb 100644 --- a/Core/display.c +++ b/Core/display.c @@ -518,9 +518,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] |= 2; gb->oam_write_blocked = true; gb->ly_for_comparison = gb->current_line; - if (gb->current_line == 0 || gb->cgb_double_speed) { - gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; - } + gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; trigger_oam_interrupt(gb); GB_STAT_update(gb); gb->n_visible_objs = 0; From e163026ca9433618cf0902fb12fe20b7887214e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 2 Apr 2018 01:05:32 +0300 Subject: [PATCH 0616/1216] The STAT bug does not occur during the glitched mode 0 --- Core/display.c | 1 + Core/memory.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index dfb8e3bb..e0f9708e 100644 --- a/Core/display.c +++ b/Core/display.c @@ -238,6 +238,7 @@ static void trigger_oam_interrupt(GB_gameboy_t *gb) } } +/* Todo: A proper test ROM of cases where both the PPU and the CPU write to IF in the same M-cycle is needed. */ void GB_STAT_update(GB_gameboy_t *gb) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; diff --git a/Core/memory.c b/Core/memory.c index 6d9f766d..e74e2134 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -636,7 +636,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_STAT: /* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */ - if (!gb->is_cgb && !gb->stat_interrupt_line && + if (!gb->is_cgb && !gb->stat_interrupt_line && !gb->is_first_line_mode2 && (gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->io_registers[GB_IO_IF] |= 2; } From ba07e7ba85606cedee791b5e96be9a808d0fba1f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 2 Apr 2018 19:57:39 +0300 Subject: [PATCH 0617/1216] Fixed a bug where 0:$dxxx reads/writes from the wrong bank in CGB mode. Made sure symbols are reset after reloading a sym file. --- Cocoa/Document.m | 1 + Core/debugger.c | 20 ++++++++++++++++++++ Core/debugger.h | 1 + Core/gb.c | 13 +------------ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index a7ab0e1a..40b68477 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -425,6 +425,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, NSString *rom_warnings = [self captureOutputForBlock:^{ GB_load_rom(&gb, [self.fileName UTF8String]); GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + GB_debugger_clear_symbols(&gb); GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]); }]; diff --git a/Core/debugger.c b/Core/debugger.c index 5e169d5d..02cf6d63 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -115,6 +115,9 @@ static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) if (gb->is_cgb) { gb->cgb_ram_bank = bank & 7; gb->cgb_vram_bank = bank & 1; + if (gb->cgb_ram_bank == 0) { + gb->cgb_ram_bank = 1; + } } } @@ -1871,6 +1874,23 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) fclose(f); } +void GB_debugger_clear_symbols(GB_gameboy_t *gb) +{ + for (int i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) { + if (gb->bank_symbols[i]) { + GB_map_free(gb->bank_symbols[i]); + gb->bank_symbols[i] = 0; + } + } + for (int i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) { + while (gb->reversed_symbol_map.buckets[i]) { + GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next; + free(gb->reversed_symbol_map.buckets[i]); + gb->reversed_symbol_map.buckets[i] = next; + } + } +} + const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) { uint16_t bank = bank_for_addr(gb, addr); diff --git a/Core/debugger.h b/Core/debugger.h index 143a6c02..2906ad9f 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -39,4 +39,5 @@ bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result void GB_debugger_break(GB_gameboy_t *gb); bool GB_debugger_is_stopped(GB_gameboy_t *gb); void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled); +void GB_debugger_clear_symbols(GB_gameboy_t *gb); #endif /* debugger_h */ diff --git a/Core/gb.c b/Core/gb.c index e0da5799..b2420152 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -144,18 +144,7 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } - for (int i = 0x200; i--;) { - if (gb->bank_symbols[i]) { - GB_map_free(gb->bank_symbols[i]); - } - } - for (int i = 0x400; i--;) { - while (gb->reversed_symbol_map.buckets[i]) { - GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next; - free(gb->reversed_symbol_map.buckets[i]); - gb->reversed_symbol_map.buckets[i] = next; - } - } + GB_debugger_clear_symbols(gb); GB_rewind_free(gb); memset(gb, 0, sizeof(*gb)); } From 5d63892949c4123bc64efd3e6456e9c43e356f93 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Apr 2018 01:43:24 +0300 Subject: [PATCH 0618/1216] T-cycle accurate timing of the extra OAM interrupt. Fixes vblank_stat_intr-GS, related to #54 --- Core/display.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/display.c b/Core/display.c index e0f9708e..7d71d1b0 100644 --- a/Core/display.c +++ b/Core/display.c @@ -426,7 +426,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 23); GB_STATE(gb, display, 24); GB_STATE(gb, display, 25); - + GB_STATE(gb, display, 26); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -743,25 +743,25 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (!gb->is_cgb) { gb->ly_for_comparison = -1; } + GB_SLEEP(gb, display, 26, 2); if (gb->current_line == LINES) { gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; trigger_oam_interrupt(gb); } GB_STAT_update(gb); gb->oam_interrupt_line = false; - GB_SLEEP(gb, display, 12, 4); + GB_SLEEP(gb, display, 12, 2); gb->ly_for_comparison = gb->current_line; if (gb->current_line == LINES) { - /* Entering VBlank state triggers the OAM interrupt*/ + /* Entering VBlank state triggers the OAM interrupt */ gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; - trigger_oam_interrupt(gb); - GB_STAT_update(gb); - gb->oam_interrupt_line = false; - gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 1; gb->io_registers[GB_IO_IF] |= 1; + trigger_oam_interrupt(gb); + GB_STAT_update(gb); + gb->oam_interrupt_line = false; if (gb->io_registers[GB_IO_STAT] & 0x20) { gb->stat_interrupt_line = true; From b1d65fd84ffa2f9ede1a22c2098f407a866a3bbc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Apr 2018 20:10:11 +0300 Subject: [PATCH 0619/1216] =?UTF-8?q?Fixed=20`boot=5Fhwio-C`=20(for=20Same?= =?UTF-8?q?Boy=E2=80=99s=20boot).=20Closes=20#59?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BootROMs/cgb_boot.asm | 1845 +++++++++++++++++++++-------------------- 1 file changed, 924 insertions(+), 921 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 6b0d6011..2682dc5d 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -5,1141 +5,1144 @@ SECTION "BootCode", ROM0[$0] Start: ; Init stack pointer - ld sp, $fffe + ld sp, $fffe - xor a + xor a ; Clear chosen input palette - ld [InputPalette], a + ldh [InputPalette], a ; Clear memory VRAM - ld hl, $8000 - call ClearMemoryPage - ld h, $d0 - call ClearMemoryPage + ld hl, $8000 + call ClearMemoryPage + ld h, $d0 + call ClearMemoryPage ; Clear OAM - ld hl, $fe00 - ld c, $a0 - xor a + ld hl, $fe00 + ld c, $a0 + xor a .clearOAMLoop - ldi [hl], a - dec c - jr nz, .clearOAMLoop + ldi [hl], a + dec c + jr nz, .clearOAMLoop ; Init Audio - ld a, $80 - ldh [$26], a - ldh [$11], a - ld a, $f3 - ldh [$12], a - ldh [$25], a - ld a, $77 - ldh [$24], a - - call InitWaveform + ld a, $80 + ldh [$26], a + ldh [$11], a + ld a, $f3 + ldh [$12], a + ldh [$25], a + ld a, $77 + ldh [$24], a + + call InitWaveform ; Init BG palette - ld a, $fc - ldh [$47], a + 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 + 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 + 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 + 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 + 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 + 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 + ld de, $104 + ld c, 6 .CGBROMLogoLoop - push bc - call ReadCGBLogoTile - pop bc - dec c - jr nz, .CGBROMLogoLoop - inc hl - call ReadTrademarkSymbol + 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 + ld hl, $98C2 + ld b, 3 + ld a, 8 .tilemapLoop - ld c, $10 + 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 + 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 + cp $38 + jr nz, .doneTilemap - ld hl, $99a7 - ld b, 1 - ld c, 7 - jr .tilemapRowLoop + 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 + ; Expand Palettes + ld de, AnimationColors + ld c, 8 + ld hl, BgPalettes + xor a .expandPalettesLoop: - cpl - ; One white - ldi [hl], a - ldi [hl], a + 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 + ; 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 + xor a + ; Two blacks + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a - dec c - jr nz, .expandPalettesLoop + dec c + jr nz, .expandPalettesLoop - ld hl, BgPalettes - ld d, 64 ; Length of write - ld e, 0 ; Index of write - call LoadBGPalettes + 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 + ; Turn on LCD + ld a, $91 + ldh [$40], a - call DoIntroAnimation + call DoIntroAnimation ; Wait ~0.75 seconds - ld b, 45 - call WaitBFrames + 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 + ; Play first sound + ld a, $83 + call PlaySound + ld b, 5 + call WaitBFrames + ; Play second sound + ld a, $c1 + call PlaySound ; Wait ~0.5 seconds - ld a, 30 - ld [WaitLoopCounter], a - + ld a, 30 + ldh [WaitLoopCounter], a + .waitLoop - call GetInputPaletteIndex - call WaitFrame - ld hl, WaitLoopCounter - dec [hl] - jr nz, .waitLoop - call Preboot + call GetInputPaletteIndex + call WaitFrame + ld hl, WaitLoopCounter + dec [hl] + jr nz, .waitLoop + call Preboot ; Will be filled with NOPs SECTION "BootGame", ROM0[$fe] BootGame: - ldh [$50], a + 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 + 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 + ; 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 + 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" + 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 + 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 $4778, $3290, $1D87, $0861 ; DMG LCD + dw $7FFF, $32BF, $00D0, $0000 + dw $639F, $4279, $15B0, $04CB + dw $7FFF, $6E31, $454A, $0000 + dw $7FFF, $1BEF, $0200, $0000 + dw $7FFF, $421F, $1CF2, $0000 + dw $7FFF, $5294, $294A, $0000 + dw $7FFF, $03FF, $012F, $0000 + dw $7FFF, $03EF, $01D6, $0000 + dw $7FFF, $42B5, $3DC8, $0000 + dw $7E74, $03FF, $0180, $0000 + dw $67FF, $77AC, $1A13, $2D6B + dw $7ED6, $4BFF, $2175, $0000 + dw $53FF, $4A5F, $7E52, $0000 + dw $4FFF, $7ED2, $3A4C, $1CE0 + dw $03ED, $7FFF, $255F, $0000 + dw $036A, $021F, $03FF, $7FFF + dw $7FFF, $01DF, $0112, $0000 + dw $231F, $035F, $00F2, $0009 + dw $7FFF, $03EA, $011F, $0000 + dw $299F, $001A, $000C, $0000 + dw $7FFF, $027F, $001F, $0000 + dw $7FFF, $03E0, $0206, $0120 + dw $7FFF, $7EEB, $001F, $7C00 + dw $7FFF, $3FFF, $7E00, $001F + dw $7FFF, $03FF, $001F, $0000 + dw $03FF, $001F, $000C, $0000 + dw $7FFF, $033F, $0193, $0000 + dw $0000, $4200, $037F, $7FFF + dw $7FFF, $7E8C, $7C00, $0000 + dw $7FFF, $1BEF, $6180, $0000 + ; Sameboy "Exclusives" + dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1 + dw $4778, $3290, $1D87, $0861 ; DMG LCD KeyCombinationPalettes - db 1 ; Right - db 48 ; Left - db 5 ; Up - db 8 ; Down - db 0 ; Right + A - db 40 ; Left + A - db 43 ; Up + A - db 3 ; Down + A - db 6 ; Right + B - db 7 ; Left + B - db 28 ; Up + B - db 49 ; Down + B - ; Sameboy "Exclusives" - db 51 ; Right + A + B - db 52 ; Left + A + B - db 53 ; Up + A + B - db 54 ; Down + A + B + 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 + db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SameboyLogo: - incbin "SameboyLogo.1bpp" + 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 + 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 + 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 + 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 + 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 + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret WaitFrame: - push hl - ld hl, $FF0F - res 0, [hl] + push hl + ld hl, $FF0F + res 0, [hl] .wait - bit 0, [hl] - jr z, .wait - pop hl - ret + bit 0, [hl] + jr z, .wait + pop hl + ret WaitBFrames: - call GetInputPaletteIndex - call WaitFrame - dec b - jr nz, WaitBFrames - ret + call GetInputPaletteIndex + call WaitFrame + dec b + jr nz, WaitBFrames + ret PlaySound: - ldh [$13], a - ld a, $87 - ldh [$14], a - ret + 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 + 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 + 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 + 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 + 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 + 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 + ld de, TrademarkSymbol + ld c,$08 .loadTrademarkSymbolLoop: - ld a,[de] - inc de - ldi [hl],a - inc hl - dec c - jr nz, .loadTrademarkSymbolLoop - ret + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + ret LoadObjPalettes: - ld c, $6A - jr LoadPalettes + ld c, $6A + jr LoadPalettes LoadBGPalettes: - ld c, $68 + ld c, $68 LoadPalettes: - ld a, $80 - or e - ld [c], a - inc c + ld a, $80 + or e + ld [c], a + inc c .loop - ld a, [hli] - ld [c], a - dec d - jr nz, .loop - ret + ld a, [hli] + ld [c], a + dec d + jr nz, .loop + ret AdvanceIntroAnimation: - ld hl, $98C0 - ld c, 3 ; Row count + 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 + 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 + inc hl + jr .loop .nextLine - ld a, l - or $1F - ld l, a - inc hl - dec c - ret z - jr .loop + 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 + ; 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 + 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 FadeOut + call ClearVRAMViaHDMA + ; Select the first bank + xor a + ldh [$4F], a + cpl + ldh [$00], a + call ClearVRAMViaHDMA - call EmulateDMG + 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 + ldh [$4C], a ; One day, I will know what this switch does and how it differs from FF6C + ldh a, [InputPalette] + and a + jr nz, .emulateDMGForCGBGame IF DEF(AGB) - ; Set registers to match the original AGB-CGB boot - ld bc, $1100 - push bc - pop af - ld h, c - ld b, 1 - ld c, c - ld e, $08 - ld l, $7c + ; Set registers to match the original AGB-CGB boot + ld bc, $1100 + push bc + pop af + ld h, c + ld b, 1 + ld c, c + ld e, $08 + ld l, $7c ELSE - ; Set registers to match the original CGB boot - ld bc, $1180 - push bc - pop af - ld c, 0 - ld h, c - ld b, c - ld c, c - ld e, $08 - ld l, $7c + ; Set registers to match the original CGB boot + ld bc, $1180 + push bc + pop af + ld c, 0 + ld h, c + ld b, c + ld c, c + ld e, $08 + ld l, $7c ENDC - ret + ret .emulateDMGForCGBGame - call EmulateDMG - ldh [$4C], a - ld a, $1; - ret + call EmulateDMG + ldh [$4C], a + ld a, $1; + ret EmulateDMG: - ld a, 1 - ldh [$6C], a ; DMG Emulation - call GetPaletteIndex - bit 7, a - call nz, LoadDMGTilemap - and $7F - ld b, a - 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 + ld a, 1 + ldh [$6C], a ; DMG Emulation + call GetPaletteIndex + bit 7, a + call nz, LoadDMGTilemap + and $7F + ld b, a + ldh a, [InputPalette] + and a + jr z, .nothingDown + ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down + ld c ,a + ld b, 0 + add hl, bc + ld a, [hl] + jr .paletteFromKeys .nothingDown - ld a, b + ld a, b .paletteFromKeys - call WaitFrame - call LoadPalettesFromIndex - ld a, 4 - ret + 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 + 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 + 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 + ld hl, $134 + ld c, $10 + ld b, 0 .checksumLoop - ld a, [hli] - add b - ld b, a - dec c - jr nz, .checksumLoop + ld a, [hli] + add b + ld b, a + dec c + jr nz, .checksumLoop - ; c = 0 - ld hl, TitleChecksums + ; c = 0 + ld hl, TitleChecksums .searchLoop - ld a, l - cp ChecksumsEnd & $FF - jr z, .notNintendo - ld a, [hli] - cp b - jr nz, .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 + ; 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 + ld a, l + add PalettePerChecksum - TitleChecksums - 1; -1 since hl was incremented + ld l, a + ld a, [hl] + ret .notNintendo - xor a - ret + xor a + ret LoadPalettesFromIndex: ; a = index of combination - ld b, a - ; Multiply by 3 - add b - add b + ld b, a + ; Multiply by 3 + add b + add b - ld hl, PaletteCombinations - ld b, 0 - ld c, a - add hl, bc + ld hl, PaletteCombinations + ld b, 0 + ld c, a + add hl, bc - ; Obj Palettes - ld e, 0 + ; 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 + 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 + ;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 BrightenColor: - ld a, [hli] - ld e, a - ld a, [hld] - ld d, a - ; RGB(1,1,1) - ld bc, $421 + 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 + ; 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 + ; 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 + ; 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 + ; 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 + ld a, e + ld [hli], a + ld a, d + ld [hli], a + ret FadeOut: - ld b, 32 ; 32 times to fade + ld b, 32 ; 32 times to fade .fadeLoop - ld c, 32 ; 32 colors to fade - ld hl, BgPalettes + ld c, 32 ; 32 colors to fade + ld hl, BgPalettes .frameLoop - push bc - call BrightenColor - pop bc - dec c - jr nz, .frameLoop + push bc + call BrightenColor + 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 + 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 + ld hl, $FF51 - ; Src - ld a, $D0 - ld [hli], a - xor a - ld [hli], a + ; 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 + ; Dest + ld a, $98 + ld [hli], a + ld a, $A0 + ld [hli], a - ; Do it - ld a, $12 - ld [hli], a - ret + ; 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 + 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 + inc c + rra + jr nc, .directionLoop - ; c = 1: Right, 2: Left, 3: Up, 4: Down + ; 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 + ld a, $10 ; Select buttons + ldh [$00], a + ldh a, [$00] + cpl + rla + rla + and $C + add c + ld b, a + ldh a, [InputPalette] + ld c, a + ld a, b + ldh [InputPalette], a + cp c + pop bc + ret z ; No change, don't load + ; Slide into change Animation Palette ChangeAnimationPalette: - push af - push hl - push bc - push de - ld hl, KeyCombinationPalettes - 1 ; Input palettes are 1-based, 0 means nothing down - ld c ,a - ld b, 0 - add hl, bc - ld a, [hl] - ld b, a - ; Multiply by 3 - add b - add b + 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 + 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 + push af + ld a, [hli] + push hl + ld hl, BgPalettes ; First color, all palette + call ReplaceColorInAllPalettes + pop hl + ldh [BgPalettes + 2], a ; Second color, first palette - ld a, [hli] - push hl - ld hl, BgPalettes + 1 ; First color, all palette - call ReplaceColorInAllPalettes - pop hl - ld [BgPalettes + 3], a ; Second color, first palette - pop af - jr z, .isNotWhite - inc hl - inc hl + ld a, [hli] + push hl + ld hl, BgPalettes + 1 ; First color, all palette + call ReplaceColorInAllPalettes + pop hl + ldh [BgPalettes + 3], a ; Second color, first palette + pop af + jr z, .isNotWhite + inc hl + inc hl .isNotWhite - ld a, [hli] - 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 + ld a, [hli] + ldh [BgPalettes + 7 * 8 + 2], a ; Second color, 7th palette + ld a, [hli] + ldh [BgPalettes + 7 * 8 + 3], a ; Second color, 7th palette + ld a, [hli] + ldh [BgPalettes + 4], a ; Third color, first palette + ld a, [hl] + ldh [BgPalettes + 5], a ; Third color, first palette - call WaitFrame - ld hl, BgPalettes - ld d, 64 ; Length of write - ld e, 0 ; Index of write - call LoadBGPalettes - ; Delay the wait loop while the user is selecting a palette - ld a, 30 - ld [WaitLoopCounter], a - pop de - pop bc - pop hl - pop af - ret + call WaitFrame + ld hl, BgPalettes + ld d, 64 ; Length of write + ld e, 0 ; Index of write + call LoadBGPalettes + ; Delay the wait loop while the user is selecting a palette + ld a, 30 + ldh [WaitLoopCounter], a + pop de + pop bc + pop hl + pop af + ret ReplaceColorInAllPalettes: - ld de, 8 - ld c, 8 + ld de, 8 + ld c, 8 .loop - ld [hl], a - add hl, de - dec c - jr nz, .loop - ret - + ld [hl], a + add hl, de + dec c + jr nz, .loop + ret + LoadDMGTilemap: - push af - call WaitFrame - ld a,$19 ; Trademark symbol - ld [$9910], a ; ... put in the superscript position - ld hl,$992f ; Bottom right corner of the logo - ld c,$c ; Tiles in a logo row + 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 + 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 + pop af + ret InitWaveform: - ld hl, $FF30 + ld hl, $FF30 ; Init waveform - xor a - ld c, $10 + xor a + ld c, $10 .waveformLoop - ldi [hl], a - cpl - dec c - jr nz, .waveformLoop - ret + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop + ret SECTION "ROMMax", ROM0[$900] - ; Prevent us from overflowing - ds 1 + ; Prevent us from overflowing + ds 1 -SECTION "RAM", WRAM0[$C000] +SECTION "HRAM", HRAM[$FF80] BgPalettes: - ds 8 * 4 * 2 + ds 8 * 4 * 2 InputPalette: - ds 1 + ds 1 WaitLoopCounter: - ds 1 \ No newline at end of file + ds 1 \ No newline at end of file From d8e0683c3597f3f1fede037e471d6f126e54e104 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 5 Apr 2018 00:51:37 +0300 Subject: [PATCH 0620/1216] Fixed a bug where skipping a sprite by modifying LCDC flags mid-scanline will disable sprites for the rest of the scalene. --- Core/display.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/display.c b/Core/display.c index 7d71d1b0..fbdc23b9 100644 --- a/Core/display.c +++ b/Core/display.c @@ -572,6 +572,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Handle objects */ /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ + + while (gb->n_visible_objs != 0 && + gb->obj_comperators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { + gb->n_visible_objs--; + } while (gb->n_visible_objs != 0 && (gb->io_registers[GB_IO_LCDC] & 2 || gb->is_cgb) && gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { From d785e453083b5c1bf9c00d49e16011c329634d30 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 5 Apr 2018 12:27:01 +0300 Subject: [PATCH 0621/1216] More accurate emulation of LCDC.0 --- Core/display.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index fbdc23b9..d20ea076 100644 --- a/Core/display.c +++ b/Core/display.c @@ -366,11 +366,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bg_enabled = true; } - if (!bg_enabled) { - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); - } - else { - uint8_t pixel = fifo_item->pixel; + { + uint8_t pixel = bg_enabled? fifo_item->pixel : 0; if (pixel && bg_priority) { draw_oam = false; } From 9aadc80f75d475ea39de3f276b7c23f1416ae01d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 5 Apr 2018 15:33:21 +0300 Subject: [PATCH 0622/1216] Implemented some obscure PPU rendering quirks, verified some timings --- Core/display.c | 53 ++++++++++++++++++++++++++++++++++++----------- Core/gb.h | 1 - Core/save_state.c | 2 -- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/Core/display.c b/Core/display.c index d20ea076..0b5e6316 100644 --- a/Core/display.c +++ b/Core/display.c @@ -394,6 +394,11 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) Todo: Add support to CPU C and older */ +static inline uint8_t fetcher_y(GB_gameboy_t *gb) +{ + return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); +} + void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { GB_object_t *objects = (GB_object_t *) &gb->oam; @@ -632,23 +637,29 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } bool push = false; if (gb->fetcher_divisor) { + switch (gb->fetcher_state) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; - /* Todo: Verify access timings */ + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) { map = 0x1C00; } else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { map = 0x1C00; } - gb->fetcher_y = - gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); - gb->current_tile = gb->vram[map + gb->fetcher_x + gb->fetcher_y / 8 * 32]; + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + uint8_t y = fetcher_y(gb); + if (gb->is_cgb) { + /* This value is cached on the CGB, so it cannot be used to mix tiles together */ + gb->fetcher_y = y; + } + gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; if (gb->is_cgb) { /* TODO: The timing is wrong (two reads a the same time)*/ - gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + gb->fetcher_y / 8 * 32 + 0x2000]; + gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; } gb->fetcher_x++; gb->fetcher_x &= 0x1f; @@ -657,32 +668,50 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) case GB_FETCHER_GET_TILE_DATA_LOWER: { uint8_t y_flip = 0; - - /* Todo: Verify access timings */ + uint16_t tile_address = 0; + uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ if (gb->io_registers[GB_IO_LCDC] & 0x10) { - gb->current_tile_address = gb->current_tile * 0x10; + tile_address = gb->current_tile * 0x10; } else { - gb->current_tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; } if (gb->current_tile_attributes & 8) { - gb->current_tile_address += 0x2000; + tile_address += 0x2000; } if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } gb->current_tile_data[0] = - gb->vram[gb->current_tile_address + ((gb->fetcher_y & 7) ^ y_flip) * 2]; + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; } break; case GB_FETCHER_GET_TILE_DATA_HIGH: { + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. + Additionally, on the CGB mixing two tiles by changing the tileset bit + mid-fetching causes a glitched mixing of the two, in comparison to the + more logical DMG version. */ + uint16_t tile_address = 0; + uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); + + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } uint8_t y_flip = 0; if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } gb->current_tile_data[1] = - gb->vram[gb->current_tile_address + ((gb->fetcher_y & 7) ^ y_flip) * 2 + 1]; + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; } break; diff --git a/Core/gb.h b/Core/gb.h index d4defd12..90b45655 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -409,7 +409,6 @@ struct GB_gameboy_internal_s { uint16_t cycles_for_line; uint8_t current_tile; uint8_t current_tile_attributes; - uint16_t current_tile_address; uint8_t current_tile_data[2]; enum { GB_FETCHER_GET_TILE, diff --git a/Core/save_state.c b/Core/save_state.c index e060ae60..22788534 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -222,7 +222,6 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->bg_fifo.write_end &= 0xF; gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; - gb->current_tile_address &= (gb->vram_size - 1); error: fclose(f); @@ -318,7 +317,6 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->bg_fifo.write_end &= 0xF; gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; - gb->current_tile_address &= (gb->vram_size - 1); return 0; } From cc95c89d3c34fb3abc4e01be846119478c2db778 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 5 Apr 2018 16:15:51 +0300 Subject: [PATCH 0623/1216] Surprise! The CGB has a 16-bit VRAM data bus --- Core/display.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 0b5e6316..27c3649a 100644 --- a/Core/display.c +++ b/Core/display.c @@ -658,7 +658,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; if (gb->is_cgb) { - /* TODO: The timing is wrong (two reads a the same time)*/ + /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. + This probably means the CGB has a 16-bit data bus for the VRAM. */ gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; } gb->fetcher_x++; From a6ed2029b7cea406580126ff4f793dd4a156071a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Apr 2018 03:19:47 +0300 Subject: [PATCH 0624/1216] New information about PPU changes between CGB-B and CGB-E --- Core/display.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/display.c b/Core/display.c index 27c3649a..057507e1 100644 --- a/Core/display.c +++ b/Core/display.c @@ -654,6 +654,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) uint8_t y = fetcher_y(gb); if (gb->is_cgb) { /* This value is cached on the CGB, so it cannot be used to mix tiles together */ + /* Todo: This is NOT true on CGB-B! This is likely the case for all CGBs prior to D. + Currently, SameBoy is emulating CGB-E, but if other revisions are added in the future + this should be taken care of*/ gb->fetcher_y = y; } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; From 0c86ff1ee426b2356515082ab5a6f7291274c9c2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Apr 2018 04:00:37 +0300 Subject: [PATCH 0625/1216] More CGB revision quirks --- Core/memory.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/memory.c b/Core/memory.c index e74e2134..50dba99b 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -538,6 +538,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } + /* Todo: This is writable, but glitchy, on CGB-B and CGB-D. */ if (addr < 0xFEA0) { if (gb->accessed_oam_row == 0xa0) { for (unsigned i = 0; i < 8; i++) { From cb012590739cd230489fdbe6d688ec8e0e3ccc46 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Apr 2018 11:37:49 +0300 Subject: [PATCH 0626/1216] Fixed #61 --- Core/display.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/display.c b/Core/display.c index 057507e1..b8935f77 100644 --- a/Core/display.c +++ b/Core/display.c @@ -576,6 +576,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ while (gb->n_visible_objs != 0 && + (gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) && gb->obj_comperators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { gb->n_visible_objs--; } From 0461fb5b2a1acd04d03267af102db49b3b3042e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Apr 2018 18:26:04 +0300 Subject: [PATCH 0627/1216] Simplified FIFO logic --- Core/display.c | 42 +++++++++++++++++++++--------------------- Core/gb.h | 4 +++- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Core/display.c b/Core/display.c index b8935f77..763c7149 100644 --- a/Core/display.c +++ b/Core/display.c @@ -8,7 +8,7 @@ static inline unsigned fifo_size(GB_fifo_t *fifo) { - return (fifo->write_end - fifo->read_end) & 0xF; + return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1); } static void fifo_clear(GB_fifo_t *fifo) @@ -20,7 +20,7 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) { GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end]; fifo->read_end++; - fifo->read_end &= 0xF; + fifo->read_end &= (GB_FIFO_LENGTH - 1); return ret; } @@ -38,7 +38,7 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint upper <<= 1; fifo->write_end++; - fifo->write_end &= 0xF; + fifo->write_end &= (GB_FIFO_LENGTH - 1); } } else { @@ -53,7 +53,7 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint upper >>= 1; fifo->write_end++; - fifo->write_end &= 0xF; + fifo->write_end &= (GB_FIFO_LENGTH - 1); } } } @@ -63,14 +63,14 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe while (fifo_size(fifo) < 8) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,}; fifo->write_end++; - fifo->write_end &= 0xF; + fifo->write_end &= (GB_FIFO_LENGTH - 1); } uint8_t flip_xor = flip_x? 0: 0x7; for (unsigned i = 8; i--;) { uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); - GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & 0xF]; + GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)]; if (pixel != 0 && (target->pixel == 0 || target->priority > priority)) { target->pixel = pixel; target->palette = palette; @@ -329,7 +329,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) GB_fifo_item_t *oam_fifo_item = NULL; bool draw_oam = false; bool bg_enabled = true, bg_priority = false; - + if (!gb->bg_fifo_paused) { fifo_item = fifo_pop(&gb->bg_fifo); bg_priority = fifo_item->bg_priority; @@ -556,6 +556,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line = MODE2_LENGTH + 4; fifo_clear(&gb->bg_fifo); fifo_clear(&gb->oam_fifo); + /* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */ + fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; @@ -567,7 +569,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The actual rendering cycle */ gb->fetcher_divisor = false; gb->fetcher_state = GB_FETCHER_GET_TILE; - gb->bg_fifo_paused = true; + gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; gb->in_window = false; while (true) { @@ -636,7 +638,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_x = 0; gb->fetcher_state = GB_FETCHER_GET_TILE; } - bool push = false; + if (gb->fetcher_divisor) { switch (gb->fetcher_state) { @@ -720,8 +722,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } break; - case GB_FETCHER_SLEEP: - push = true; + case GB_FETCHER_SLEEP: { + gb->cycles_for_line += gb->fetcher_stop_penalty; + GB_SLEEP(gb, display, 19, gb->fetcher_stop_penalty); + gb->fetcher_stop_penalty = 0; + + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], + gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); + gb->bg_fifo_paused = false; + gb->oam_fifo_paused = false; + } break; } gb->fetcher_state++; @@ -730,16 +740,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_divisor ^= true; render_pixel_if_possible(gb); - if (push) { - gb->cycles_for_line += gb->fetcher_stop_penalty; - GB_SLEEP(gb, display, 19, gb->fetcher_stop_penalty); - gb->fetcher_stop_penalty = 0; - - fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], - gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); - gb->bg_fifo_paused = false; - gb->oam_fifo_paused = false; - } if (gb->position_in_line == 160) break; gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); diff --git a/Core/gb.h b/Core/gb.h index 90b45655..abd5fc7e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -195,8 +195,9 @@ typedef struct { bool bg_priority; // For sprite FIFO – the BG priority bit. For the BG FIFO – the CGB attributes priority bit } GB_fifo_item_t; +#define GB_FIFO_LENGTH 16 typedef struct { - GB_fifo_item_t fifo[16]; + GB_fifo_item_t fifo[GB_FIFO_LENGTH]; uint8_t read_end; uint8_t write_end; } GB_fifo_t; @@ -430,6 +431,7 @@ struct GB_gameboy_internal_s { uint8_t extra_penalty_for_sprite_at_0; bool is_first_line_mode2; bool oam_interrupt_line; + bool ready_to_push; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 0751eae90bc7bef495e8ed8b103447c99835b7a9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Apr 2018 19:29:49 +0300 Subject: [PATCH 0628/1216] Moved the fetcher state machine to another function --- Core/display.c | 235 +++++++++++++++++++++++++++---------------------- Core/gb.h | 9 +- 2 files changed, 133 insertions(+), 111 deletions(-) diff --git a/Core/display.c b/Core/display.c index 763c7149..0d7db15f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -399,6 +399,130 @@ static inline uint8_t fetcher_y(GB_gameboy_t *gb) return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); } +static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) +{ + typedef enum { + GB_FETCHER_GET_TILE, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_PUSH, + GB_FETCHER_SLEEP, + } fetcher_step_t; + + fetcher_step_t fetcher_state_machine [8] = { + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE, + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_SLEEP, + GB_FETCHER_PUSH, + }; + + uint8_t delay = 0; + + switch (fetcher_state_machine[gb->fetcher_state]) { + case GB_FETCHER_GET_TILE: { + uint16_t map = 0x1800; + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) { + map = 0x1C00; + } + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { + map = 0x1C00; + } + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + uint8_t y = fetcher_y(gb); + if (gb->is_cgb) { + /* This value is cached on the CGB, so it cannot be used to mix tiles together */ + /* Todo: This is NOT true on CGB-B! This is likely the case for all CGBs prior to D. + Currently, SameBoy is emulating CGB-E, but if other revisions are added in the future + this should be taken care of*/ + gb->fetcher_y = y; + } + gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; + if (gb->is_cgb) { + /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. + This probably means the CGB has a 16-bit data bus for the VRAM. */ + gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; + } + gb->fetcher_x++; + gb->fetcher_x &= 0x1f; + } + break; + + case GB_FETCHER_GET_TILE_DATA_LOWER: { + uint8_t y_flip = 0; + uint16_t tile_address = 0; + uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + gb->current_tile_data[0] = + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + } + break; + + case GB_FETCHER_GET_TILE_DATA_HIGH: { + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. + Additionally, on the CGB mixing two tiles by changing the tileset bit + mid-fetching causes a glitched mixing of the two, in comparison to the + more logical DMG version. */ + uint16_t tile_address = 0; + uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); + + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + uint8_t y_flip = 0; + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + gb->current_tile_data[1] = + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; + } + break; + + case GB_FETCHER_PUSH: { + + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], + gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); + gb->bg_fifo_paused = false; + gb->oam_fifo_paused = false; + delay = gb->fetcher_stop_penalty; + gb->fetcher_stop_penalty = 0; + } + break; + + case GB_FETCHER_SLEEP: + break; + } + + gb->fetcher_state++; + gb->fetcher_state &= 7; + return delay; +} + void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { GB_object_t *objects = (GB_object_t *) &gb->oam; @@ -567,8 +691,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 10, 5); /* The actual rendering cycle */ - gb->fetcher_divisor = false; - gb->fetcher_state = GB_FETCHER_GET_TILE; + gb->fetcher_state = 0; gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; gb->in_window = false; @@ -589,7 +712,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->fetcher_stop_penalty == 0) { /* TODO: figure out why the penalty works this way and actual access timings */ /* Penalty for interrupting the fetcher */ - gb->fetcher_stop_penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state * 2 + gb->fetcher_divisor]; + gb->fetcher_stop_penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state]; if (gb->obj_comperators[gb->n_visible_objs - 1] == 0) { gb->fetcher_stop_penalty += gb->extra_penalty_for_sprite_at_0; } @@ -636,108 +759,14 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->bg_fifo_paused = true; gb->oam_fifo_paused = true; gb->fetcher_x = 0; - gb->fetcher_state = GB_FETCHER_GET_TILE; + gb->fetcher_state = 0; } - - if (gb->fetcher_divisor) { - - switch (gb->fetcher_state) { - case GB_FETCHER_GET_TILE: { - uint16_t map = 0x1800; - - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) { - map = 0x1C00; - } - else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { - map = 0x1C00; - } - - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - uint8_t y = fetcher_y(gb); - if (gb->is_cgb) { - /* This value is cached on the CGB, so it cannot be used to mix tiles together */ - /* Todo: This is NOT true on CGB-B! This is likely the case for all CGBs prior to D. - Currently, SameBoy is emulating CGB-E, but if other revisions are added in the future - this should be taken care of*/ - gb->fetcher_y = y; - } - gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; - if (gb->is_cgb) { - /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. - This probably means the CGB has a 16-bit data bus for the VRAM. */ - gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; - } - gb->fetcher_x++; - gb->fetcher_x &= 0x1f; - } - break; - - case GB_FETCHER_GET_TILE_DATA_LOWER: { - uint8_t y_flip = 0; - uint16_t tile_address = 0; - uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); - - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & 0x10) { - tile_address = gb->current_tile * 0x10; - } - else { - tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; - } - if (gb->current_tile_attributes & 8) { - tile_address += 0x2000; - } - if (gb->current_tile_attributes & 0x40) { - y_flip = 0x7; - } - gb->current_tile_data[0] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; - } - break; - - case GB_FETCHER_GET_TILE_DATA_HIGH: { - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. - Additionally, on the CGB mixing two tiles by changing the tileset bit - mid-fetching causes a glitched mixing of the two, in comparison to the - more logical DMG version. */ - uint16_t tile_address = 0; - uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); - - if (gb->io_registers[GB_IO_LCDC] & 0x10) { - tile_address = gb->current_tile * 0x10; - } - else { - tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; - } - if (gb->current_tile_attributes & 8) { - tile_address += 0x2000; - } - uint8_t y_flip = 0; - if (gb->current_tile_attributes & 0x40) { - y_flip = 0x7; - } - gb->current_tile_data[1] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; - } - break; - - case GB_FETCHER_SLEEP: { - gb->cycles_for_line += gb->fetcher_stop_penalty; - GB_SLEEP(gb, display, 19, gb->fetcher_stop_penalty); - gb->fetcher_stop_penalty = 0; - - fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], - gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); - gb->bg_fifo_paused = false; - gb->oam_fifo_paused = false; - } - break; - } - gb->fetcher_state++; - gb->fetcher_state &= 3; + + { + uint8_t fetcher_delay = advance_fetcher_state_machine(gb); + gb->cycles_for_line += fetcher_delay; + GB_SLEEP(gb, display, 19, fetcher_delay); } - gb->fetcher_divisor ^= true; render_pixel_if_possible(gb); if (gb->position_in_line == 160) break; diff --git a/Core/gb.h b/Core/gb.h index abd5fc7e..95f81efc 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -411,14 +411,7 @@ struct GB_gameboy_internal_s { uint8_t current_tile; uint8_t current_tile_attributes; uint8_t current_tile_data[2]; - enum { - GB_FETCHER_GET_TILE, - GB_FETCHER_GET_TILE_DATA_LOWER, - GB_FETCHER_GET_TILE_DATA_HIGH, - GB_FETCHER_SLEEP, - GB_FETCHER_MAX = GB_FETCHER_SLEEP, - } fetcher_state:8; - bool fetcher_divisor; // The fetcher runs at 2MHz + uint8_t fetcher_state; bool bg_fifo_paused; bool oam_fifo_paused; bool in_window; From fed2556fc3102f5c21fffc9d0aaab56f95476d2b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 7 Apr 2018 03:00:26 +0300 Subject: [PATCH 0629/1216] More reasonable implementation of sprite timings --- Core/display.c | 30 ++++++++++++++++++++---------- Core/gb.h | 2 -- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index 0d7db15f..d3dbef31 100644 --- a/Core/display.c +++ b/Core/display.c @@ -421,7 +421,6 @@ static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) }; uint8_t delay = 0; - switch (fetcher_state_machine[gb->fetcher_state]) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; @@ -452,6 +451,7 @@ static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_x++; gb->fetcher_x &= 0x1f; } + gb->fetcher_state++; break; case GB_FETCHER_GET_TILE_DATA_LOWER: { @@ -475,6 +475,7 @@ static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) gb->current_tile_data[0] = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; } + gb->fetcher_state++; break; case GB_FETCHER_GET_TILE_DATA_HIGH: { @@ -501,24 +502,26 @@ static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) gb->current_tile_data[1] = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; } + gb->fetcher_state++; break; case GB_FETCHER_PUSH: { - + if (fifo_size(&gb->bg_fifo) > 1) break; fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; - delay = gb->fetcher_stop_penalty; - gb->fetcher_stop_penalty = 0; + gb->fetcher_state++; } break; case GB_FETCHER_SLEEP: + { + gb->fetcher_state++; + } break; } - gb->fetcher_state++; gb->fetcher_state &= 7; return delay; } @@ -553,6 +556,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 24); GB_STATE(gb, display, 25); GB_STATE(gb, display, 26); + GB_STATE(gb, display, 27); + GB_STATE(gb, display, 28); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -709,12 +714,17 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) (gb->io_registers[GB_IO_LCDC] & 2 || gb->is_cgb) && gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { - if (gb->fetcher_stop_penalty == 0) { - /* TODO: figure out why the penalty works this way and actual access timings */ - /* Penalty for interrupting the fetcher */ - gb->fetcher_stop_penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state]; + while (gb->fetcher_state < 5) { + advance_fetcher_state_machine(gb); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 27, 1); + } + + if (gb->extra_penalty_for_sprite_at_0 != 0) { if (gb->obj_comperators[gb->n_visible_objs - 1] == 0) { - gb->fetcher_stop_penalty += gb->extra_penalty_for_sprite_at_0; + gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0; + GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0); + gb->extra_penalty_for_sprite_at_0 = 0; } } diff --git a/Core/gb.h b/Core/gb.h index 95f81efc..ed7d98a9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -418,13 +418,11 @@ struct GB_gameboy_internal_s { uint8_t visible_objs[10]; uint8_t obj_comperators[10]; uint8_t n_visible_objs; - uint8_t fetcher_stop_penalty; uint8_t oam_search_index; uint8_t accessed_oam_row; uint8_t extra_penalty_for_sprite_at_0; bool is_first_line_mode2; bool oam_interrupt_line; - bool ready_to_push; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 9ce028056aacd3cf8b691d11f41f35f70dfc57d0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 7 Apr 2018 03:26:10 +0300 Subject: [PATCH 0630/1216] Cleanup --- Core/display.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Core/display.c b/Core/display.c index d3dbef31..8a754aaf 100644 --- a/Core/display.c +++ b/Core/display.c @@ -399,7 +399,7 @@ static inline uint8_t fetcher_y(GB_gameboy_t *gb) return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); } -static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) +static void advance_fetcher_state_machine(GB_gameboy_t *gb) { typedef enum { GB_FETCHER_GET_TILE, @@ -420,7 +420,6 @@ static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_PUSH, }; - uint8_t delay = 0; switch (fetcher_state_machine[gb->fetcher_state]) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; @@ -523,7 +522,6 @@ static uint8_t advance_fetcher_state_machine(GB_gameboy_t *gb) } gb->fetcher_state &= 7; - return delay; } void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) @@ -548,7 +546,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 15); GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); - GB_STATE(gb, display, 19); + // GB_STATE(gb, display, 19); GB_STATE(gb, display, 20); GB_STATE(gb, display, 21); GB_STATE(gb, display, 22); @@ -720,6 +718,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 27, 1); } + /* Todo: Measure if penalty occurs before or after waiting for the fetcher. */ if (gb->extra_penalty_for_sprite_at_0 != 0) { if (gb->obj_comperators[gb->n_visible_objs - 1] == 0) { gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0; @@ -772,11 +771,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_state = 0; } - { - uint8_t fetcher_delay = advance_fetcher_state_machine(gb); - gb->cycles_for_line += fetcher_delay; - GB_SLEEP(gb, display, 19, fetcher_delay); - } + advance_fetcher_state_machine(gb); render_pixel_if_possible(gb); if (gb->position_in_line == 160) break; From 097b76812738f2728d52bbe06a7732e40d937e4e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 7 Apr 2018 03:36:47 +0300 Subject: [PATCH 0631/1216] Update comments --- Core/display.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Core/display.c b/Core/display.c index 8a754aaf..5ea0688b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -84,19 +84,19 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe /* - Each line is 456 cycles, approximately: + Each line is 456 cycles. Without scrolling, sprites or a window:: Mode 2 - 80 cycles / OAM Transfer Mode 3 - 172 cycles / Rendering Mode 0 - 204 cycles / HBlank Mode 1 is VBlank - - Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt */ +/* Todo: Clean up the glitched line 0 and get rid of these defines */ #define MODE2_LENGTH (80) #define MODE3_LENGTH (172) #define MODE0_LENGTH (204) + #define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 #define LINES (144) #define WIDTH (160) @@ -238,7 +238,7 @@ static void trigger_oam_interrupt(GB_gameboy_t *gb) } } -/* Todo: A proper test ROM of cases where both the PPU and the CPU write to IF in the same M-cycle is needed. */ +/* Todo: When the CPU and PPU write to IF at the same T-cycle, the PPU write is ignored. */ void GB_STAT_update(GB_gameboy_t *gb) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; @@ -438,7 +438,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* This value is cached on the CGB, so it cannot be used to mix tiles together */ /* Todo: This is NOT true on CGB-B! This is likely the case for all CGBs prior to D. Currently, SameBoy is emulating CGB-E, but if other revisions are added in the future - this should be taken care of*/ + this should be taken care of */ gb->fetcher_y = y; } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; @@ -479,8 +479,8 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) case GB_FETCHER_GET_TILE_DATA_HIGH: { /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. - Additionally, on the CGB mixing two tiles by changing the tileset bit - mid-fetching causes a glitched mixing of the two, in comparison to the + Additionally, on CGB-D and newer mixing two tiles by changing the tileset + bit mid-fetching causes a glitched mixing of the two, in comparison to the more logical DMG version. */ uint16_t tile_address = 0; uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); @@ -736,6 +736,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (object->flags & 0x40) { /* Flip Y */ tile_y ^= height_16? 0xF : 7; } + /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */ From 0725b008bec315c51eea951a56a57adfff2e2c93 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 7 Apr 2018 13:02:53 +0300 Subject: [PATCH 0632/1216] Further simplifications --- Core/display.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 5ea0688b..0ad64611 100644 --- a/Core/display.c +++ b/Core/display.c @@ -505,7 +505,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) break; case GB_FETCHER_PUSH: { - if (fifo_size(&gb->bg_fifo) > 1) break; + if (fifo_size(&gb->bg_fifo) > 0) break; fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); gb->bg_fifo_paused = false; @@ -772,9 +772,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_state = 0; } - advance_fetcher_state_machine(gb); - render_pixel_if_possible(gb); + advance_fetcher_state_machine(gb); + if (gb->position_in_line == 160) break; gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); From 5be2b3db2915755e0e1777208de3896fe6658a1c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 7 Apr 2018 13:59:36 +0300 Subject: [PATCH 0633/1216] It appears that OAM DMA blocks PPU access to OAM --- Core/display.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Core/display.c b/Core/display.c index 0ad64611..45a6fd95 100644 --- a/Core/display.c +++ b/Core/display.c @@ -305,6 +305,11 @@ void GB_lcd_off(GB_gameboy_t *gb) static void add_object_from_index(GB_gameboy_t *gb, unsigned index) { if (gb->n_visible_objs == 10) return; + + /* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */ + if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) { + return; + } /* This reverse sorts the visible objects by location and priority */ GB_object_t *objects = (GB_object_t *) &gb->oam; @@ -729,7 +734,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += 6; GB_SLEEP(gb, display, 20, 6); + /* TODO: what does the PPU read if DMA is active? */ GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); From 89094950f85e201be12ccef3bed8657a927002b2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 7 Apr 2018 16:45:31 +0300 Subject: [PATCH 0634/1216] Correct emulation of mapping both button sets. Fixes #60 --- Core/joypad.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/joypad.c b/Core/joypad.c index 15885d39..8b7a8da3 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -39,10 +39,8 @@ void GB_update_joyp(GB_gameboy_t *gb) break; case 0: - /* Todo: verifiy this is correct */ for (uint8_t 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; + gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[i] || gb->keys[i + 4])) << i; } break; From ebe0aa0c76503f8475a0aeb7be09a2261b9d5905 Mon Sep 17 00:00:00 2001 From: webgeek1234 Date: Tue, 10 Apr 2018 21:38:30 -0500 Subject: [PATCH 0635/1216] Refactor android jni makefiles (#17) --- libretro/jni/Android.mk | 53 +++++++++++++---------------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 1d1ac940..3b2d74b2 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -1,51 +1,32 @@ LOCAL_PATH := $(call my-dir) -GIT_VERSION ?= " $(shell git rev-parse --short HEAD || echo unknown)" +CORE_DIR := $(LOCAL_PATH)/../.. + +CFLAGS := + +include $(CORE_DIR)/libretro/Makefile.common + +GENERATED_SOURCES := $(filter %_boot.c,$(SOURCES_C)) + +COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar + +GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)" ifneq ($(GIT_VERSION)," unknown") - LOCAL_CXXFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" + COREFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" endif include $(CLEAR_VARS) - -HAVE_NETWORK = 1 -LOCAL_MODULE := libretro - -ifeq ($(TARGET_ARCH),arm) -LOCAL_CXXFLAGS += -DANDROID_ARM -LOCAL_ARM_MODE := arm -endif - -ifeq ($(TARGET_ARCH),x86) -LOCAL_CXXFLAGS += -DANDROID_X86 -endif - -ifeq ($(TARGET_ARCH),mips) -LOCAL_CXXFLAGS += -DANDROID_MIPS -endif - -CORE_DIR := $(realpath ../..) - -include ../Makefile.common - -LOCAL_SRC_FILES := $(SOURCES_CXX) $(SOURCES_C) -LOCAL_CFLAGS += -DINLINE=inline -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DNDEBUG -D_USE_MATH_DEFINES -DGB_INTERNAL -std=c99 -I$(CORE_DIR) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -DDISABLE_DEBUGGER -DDISABLE_REWIND -DDISABLE_TIMEKEEPING -Wno-multichar -LOCAL_C_INCLUDES = $(INCFLAGS) - +LOCAL_MODULE := retro +LOCAL_SRC_FILES := $(SOURCES_C) +LOCAL_CFLAGS := -std=c99 $(COREFLAGS) $(CFLAGS) +LOCAL_LDFLAGS := -Wl,-version-script=$(CORE_DIR)/libretro/link.T include $(BUILD_SHARED_LIBRARY) $(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/build/bin/BootROMs/%_boot.bin echo "/* AUTO-GENERATED */" > $@ echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ -ifneq ($(findstring Haiku,$(shell uname -s)),) - # turns out od is posix, hexdump is not hence is less portable - # this is still rather ugly and could be done better I guess - od -A none -t x1 -v $< | sed -e 's/^\ /0x/' -e 's/\ /,\ 0x/g' -e 's/$$/,/g' | tr '\n' ' ' >> $@ -else hexdump -v -e '/1 "0x%02x, "' $< >> $@ -endif echo "};" >> $@ echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ -$(CORE_DIR)/build/bin/BootROMs/%_boot.bin: - $(MAKE) -C .. $(patsubst $(CORE_DIR)/%,%,$@) - +.INTERMEDIATE: $(GENERATED_SOURCES) From 10dc12c502caa6e251f622c1f44897afc86ad0e7 Mon Sep 17 00:00:00 2001 From: orbea Date: Wed, 11 Apr 2018 14:21:46 -0700 Subject: [PATCH 0636/1216] Core: Fix libretro builds --- Core/gb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index b2420152..da1e9419 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -144,7 +144,9 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } +#ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); +#endif GB_rewind_free(gb); memset(gb, 0, sizeof(*gb)); } From f1ec42d4babbf010648811bb4d458087ed0c89e8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Apr 2018 14:41:39 +0300 Subject: [PATCH 0637/1216] H/GDMA was 4 times faster than it should have been. Made it also more accurate. Fixes #56 --- Core/gb.h | 2 +- Core/memory.c | 33 +++++++++++++++++---------------- Core/z80_cpu.c | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index ed7d98a9..bad1c80d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -280,7 +280,7 @@ struct GB_gameboy_internal_s { bool hdma_on; bool hdma_on_hblank; uint8_t hdma_steps_left; - uint16_t hdma_cycles; // in 8MHz units + int16_t hdma_cycles; // in 8MHz units uint16_t hdma_current_src, hdma_current_dest; uint8_t dma_steps_left; diff --git a/Core/memory.c b/Core/memory.c index 50dba99b..7bced24e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -767,7 +767,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->hdma_on_hblank = (value & 0x80) != 0; if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) { gb->hdma_on = true; - gb->hdma_cycles = 0; + gb->hdma_cycles = -8; } gb->io_registers[GB_IO_HDMA5] = value; gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; @@ -775,7 +775,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) { gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4; } - gb->hdma_cycles = 0; + gb->hdma_cycles = -8; return; /* Todo: what happens when starting a transfer during a transfer? @@ -863,6 +863,7 @@ void GB_dma_run(GB_gameboy_t *gb) /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; + if (gb->dma_current_src < 0xe000) { gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); } @@ -882,22 +883,22 @@ void GB_dma_run(GB_gameboy_t *gb) void GB_hdma_run(GB_gameboy_t *gb) { if (!gb->hdma_on) return; - while (gb->hdma_cycles >= 0x10) { - gb->hdma_cycles -= 0x10; + while (gb->hdma_cycles >= 0x4) { + gb->hdma_cycles -= 0x4; - for (uint8_t i = 0; i < 0x10; i++) { - GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); - } + GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); - 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; + if ((gb->hdma_current_dest & 0xf) == 0) { + 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; + } } } } diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index ce56f7ee..f4252139 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1438,6 +1438,6 @@ void GB_cpu_run(GB_gameboy_t *gb) if (gb->hdma_starting) { gb->hdma_starting = false; gb->hdma_on = true; - gb->hdma_cycles = 0; + gb->hdma_cycles = -8; } } From d667d87bbe504d2d2b77afdf4193c3e7487f7707 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Apr 2018 13:23:33 +0300 Subject: [PATCH 0638/1216] Refactor CPU code so handling access conflicts is possible --- Core/gb.h | 2 + Core/z80_cpu.c | 455 +++++++++++++++++++------------------------------ 2 files changed, 181 insertions(+), 276 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index bad1c80d..627e1cf2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -437,6 +437,8 @@ struct GB_gameboy_internal_s { GB_MBC1M_WIRING, } mbc1_wiring; + unsigned pending_cycles; + /* Various RAMs */ uint8_t *ram; uint8_t *vram; diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index f4252139..2ba59c74 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1,5 +1,6 @@ #include #include +#include #include "gb.h" @@ -22,6 +23,41 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); This is equivalent to running the memory write 1 T-cycle before the memory read. */ + +static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + uint8_t ret = GB_read_memory(gb, addr); + gb->pending_cycles = 4; + return ret; +} + +static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + assert(gb->pending_cycles); + GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_write_memory(gb, addr, value); + GB_advance_cycles(gb, 1); + gb->pending_cycles = 4; +} + +static void cycle_no_access(GB_gameboy_t *gb) +{ + gb->pending_cycles += 4; +} + +static void flush_pending_cycles(GB_gameboy_t *gb) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + gb->pending_cycles = 0; +} + +/* Todo: all multi-byte opcodes probably trigger the OAM bug when they increase pc */ + static void ill(GB_gameboy_t *gb, uint8_t opcode) { GB_log(gb, "Illegal Opcode. Halting.\n"); @@ -31,16 +67,14 @@ static void ill(GB_gameboy_t *gb, uint8_t opcode) static void nop(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); } static void stop(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); if (gb->io_registers[GB_IO_KEY1] & 0x1) { /* Make sure we don't leave display_cycles not divisble by 8 in single speed mode */ if (gb->display_cycles % 8 == 4) { - GB_advance_cycles(gb, 4); + cycle_no_access(gb); } /* Todo: the switch is not instant. We should emulate this. */ @@ -68,37 +102,31 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; uint16_t value; - GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - value |= GB_read_memory(gb, gb->pc++) << 8; - GB_advance_cycles(gb, 4); + value = cycle_read(gb, gb->pc++); + value |= cycle_read(gb, gb->pc++) << 8; gb->registers[register_id] = value; } static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 3); register_id = (opcode >> 4) + 1; - GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 5); + cycle_write(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); } static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id = (opcode >> 4) + 1; - GB_advance_cycles(gb, 4); + flush_pending_cycles(gb); GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); + cycle_no_access(gb); gb->registers[register_id]++; } static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 4); 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); @@ -114,7 +142,6 @@ static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] -= 0x100; gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); @@ -132,18 +159,15 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] &= 0xFF; - gb->registers[register_id] |= GB_read_memory(gb, gb->pc++) << 8; - GB_advance_cycles(gb, 4); + gb->registers[register_id] |= cycle_read(gb, gb->pc++) << 8; } static void rlca(GB_gameboy_t *gb, uint8_t opcode) { bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; if (carry) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100; @@ -155,7 +179,6 @@ static void rla(GB_gameboy_t *gb, uint8_t opcode) bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; if (carry) { gb->registers[GB_REGISTER_AF] |= 0x0100; @@ -169,15 +192,10 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify order is correct */ uint16_t addr; - GB_advance_cycles(gb, 4); - addr = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - addr |= GB_read_memory(gb, gb->pc++) << 8; - GB_advance_cycles(gb, 3); - GB_write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); - GB_advance_cycles(gb, 4); - GB_write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); - GB_advance_cycles(gb, 5); + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + cycle_write(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -185,7 +203,7 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) uint16_t hl = gb->registers[GB_REGISTER_HL]; uint16_t rr; uint8_t register_id; - GB_advance_cycles(gb, 8); + cycle_no_access(gb); register_id = (opcode >> 4) + 1; rr = gb->registers[register_id]; gb->registers[GB_REGISTER_HL] = hl + rr; @@ -205,18 +223,16 @@ static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; register_id = (opcode >> 4) + 1; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[register_id]) << 8; - GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[register_id]) << 8; } static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id = (opcode >> 4) + 1; - GB_advance_cycles(gb, 4); + flush_pending_cycles(gb); GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); + cycle_no_access(gb); gb->registers[register_id]--; } @@ -224,7 +240,6 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; uint8_t value; - GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; value = (gb->registers[register_id] & 0xFF) + 1; @@ -244,7 +259,6 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; uint8_t value; - GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; value = (gb->registers[register_id] & 0xFF) - 1; @@ -265,18 +279,15 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 4); register_id = (opcode >> 4) + 1; gb->registers[register_id] &= 0xFF00; - gb->registers[register_id] |= GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + gb->registers[register_id] |= cycle_read(gb, gb->pc++); } static void rrca(GB_gameboy_t *gb, uint8_t opcode) { bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; if (carry) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000; @@ -288,7 +299,6 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode) bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0; bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; if (carry) { gb->registers[GB_REGISTER_AF] |= 0x8000; @@ -300,10 +310,9 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode) static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { - /* Todo: Verify cycles are not 8 and 4 instead */ - GB_advance_cycles(gb, 4); - gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1; - GB_advance_cycles(gb, 8); + /* Todo: Verify timing */ + gb->pc += (int8_t)cycle_read(gb, gb->pc) + 1; + cycle_no_access(gb); } static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) @@ -325,20 +334,17 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { if (condition_code(gb, opcode)) { - GB_advance_cycles(gb, 4); - gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1; - GB_advance_cycles(gb, 8); + gb->pc += (int8_t)cycle_read(gb, gb->pc) + 1; } else { - GB_advance_cycles(gb, 8); gb->pc += 1; } + cycle_no_access(gb); } static void daa(GB_gameboy_t *gb, uint8_t opcode) { /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= ~GB_ZERO_FLAG; if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { @@ -390,66 +396,53 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) static void cpl(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); 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, uint8_t opcode) { - GB_advance_cycles(gb, 4); 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, uint8_t opcode) { - GB_advance_cycles(gb, 4); 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, uint8_t opcode) { - GB_advance_cycles(gb, 3); - GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 5); + cycle_write(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); } static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 3); - GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 5); + cycle_write(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); } static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); + flush_pending_cycles(gb); GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_HL]); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; - GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[GB_REGISTER_HL]++) << 8; } static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { - GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); + flush_pending_cycles(gb); GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_HL]); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; - GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[GB_REGISTER_HL]--) << 8; } static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; - GB_advance_cycles(gb, 3); - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 5); + value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1; + cycle_write(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) { @@ -464,11 +457,8 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; - GB_advance_cycles(gb, 3); - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 5); + value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) - 1; + cycle_write(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; @@ -483,14 +473,11 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); - uint8_t data = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 3); - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], data); - GB_advance_cycles(gb, 5); + uint8_t data = cycle_read(gb, gb->pc++); + cycle_write(gb, gb->registers[GB_REGISTER_HL], data); } -uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t cycles_after_read) +uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) { uint8_t src_register_id; uint8_t src_low; @@ -500,9 +487,7 @@ uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t cycles_after_rea if (src_low) { return gb->registers[GB_REGISTER_AF] >> 8; } - uint8_t ret = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); - GB_advance_cycles(gb, cycles_after_read); - return ret; + return cycle_read(gb, gb->registers[GB_REGISTER_HL]); } if (src_low) { return gb->registers[src_register_id] & 0xFF; @@ -523,8 +508,7 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) gb->registers[GB_REGISTER_AF] |= value << 8; } else { - GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); - GB_advance_cycles(gb, 5); + cycle_write(gb, gb->registers[GB_REGISTER_HL], value); } } else { @@ -547,24 +531,19 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) #define LD_X_Y(x, y) \ static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ { \ - GB_advance_cycles(gb, 4); \ gb->x = gb->y;\ } #define LD_X_DHL(x) \ static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ { \ -GB_advance_cycles(gb, 4); \ -gb->x = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); \ -GB_advance_cycles(gb, 4);\ +gb->x = cycle_read(gb, gb->registers[GB_REGISTER_HL]); \ } #define LD_DHL_Y(y) \ static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ { \ -GB_advance_cycles(gb, 3); \ -GB_write_memory(gb, gb->registers[GB_REGISTER_HL], gb->y); \ -GB_advance_cycles(gb, 5);\ +cycle_write(gb, gb->registers[GB_REGISTER_HL], gb->y); \ } LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) @@ -580,8 +559,7 @@ LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a + value) << 8; if ((uint8_t)(a + value) == 0) { @@ -598,8 +576,7 @@ static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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; @@ -618,8 +595,7 @@ static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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) { @@ -636,8 +612,7 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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; @@ -656,8 +631,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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) { @@ -668,8 +642,7 @@ static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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) { @@ -680,8 +653,7 @@ static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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) { @@ -692,8 +664,7 @@ static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 4); + 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; @@ -710,7 +681,6 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) static void halt(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); gb->halted = true; /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ if (!gb->ime && (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0) { @@ -719,82 +689,60 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) } } -static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) -{ - if (condition_code(gb, opcode)) { - GB_debugger_ret_hook(gb); - GB_advance_cycles(gb, 8); - GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); - GB_advance_cycles(gb, 4); - gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; - GB_advance_cycles(gb, 8); - } - else { - GB_advance_cycles(gb, 8); - } -} - static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 4); register_id = ((opcode >> 4) + 1) & 3; + flush_pending_cycles(gb); GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); - GB_advance_cycles(gb, 4); - gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; - GB_advance_cycles(gb, 4); + gb->registers[register_id] = cycle_read(gb, gb->registers[GB_REGISTER_SP]++); + gb->registers[register_id] |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. } static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { if (condition_code(gb, opcode)) { - GB_advance_cycles(gb, 4); - uint16_t addr = GB_read_memory(gb, gb->pc); - GB_advance_cycles(gb, 4); - addr |= (GB_read_memory(gb, gb->pc + 1) << 8); - GB_advance_cycles(gb, 8); + uint16_t addr = cycle_read(gb, gb->pc); + addr |= (cycle_read(gb, gb->pc + 1) << 8); + cycle_no_access(gb); gb->pc = addr; } else { - GB_advance_cycles(gb, 12); + cycle_no_access(gb); + cycle_no_access(gb); gb->pc += 2; } } static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); - uint16_t addr = GB_read_memory(gb, gb->pc); - GB_advance_cycles(gb, 4); - addr |= (GB_read_memory(gb, gb->pc + 1) << 8); - GB_advance_cycles(gb, 8); - gb->pc = addr;} + uint16_t addr = cycle_read(gb, gb->pc); + addr |= (cycle_read(gb, gb->pc + 1) << 8); + cycle_no_access(gb); + gb->pc = addr; + +} static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; if (condition_code(gb, opcode)) { - GB_advance_cycles(gb, 4); - uint16_t addr = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - addr |= (GB_read_memory(gb, gb->pc++) << 8); - GB_advance_cycles(gb, 3); + uint16_t addr = cycle_read(gb, gb->pc++); + addr |= (cycle_read(gb, gb->pc++) << 8); + flush_pending_cycles(gb); GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - GB_advance_cycles(gb, 5); + cycle_no_access(gb); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); } else { - GB_advance_cycles(gb, 12); + cycle_no_access(gb); + cycle_no_access(gb); gb->pc += 2; } } @@ -802,22 +750,18 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - GB_advance_cycles(gb, 3); + flush_pending_cycles(gb); GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); + cycle_no_access(gb); register_id = ((opcode >> 4) + 1) & 3; - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); - GB_advance_cycles(gb, 5); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); } static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a + value) << 8; if ((uint8_t) (a + value) == 0) { @@ -834,9 +778,7 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(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; @@ -855,9 +797,7 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; if (a == value) { @@ -874,9 +814,7 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(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; @@ -895,9 +833,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(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) { @@ -908,9 +844,7 @@ static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; if ((a ^ value) == 0) { @@ -921,9 +855,7 @@ static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a | value) << 8; if ((a | value) == 0) { @@ -934,9 +866,7 @@ static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - GB_advance_cycles(gb, 4); - value = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; @@ -954,13 +884,11 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 3); + flush_pending_cycles(gb); GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - GB_advance_cycles(gb, 5); + cycle_no_access(gb); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); gb->pc = opcode ^ 0xC7; GB_debugger_call_hook(gb, call_addr); } @@ -968,12 +896,11 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) static void ret(GB_gameboy_t *gb, uint8_t opcode) { GB_debugger_ret_hook(gb); - GB_advance_cycles(gb, 4); + flush_pending_cycles(gb); GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++); - GB_advance_cycles(gb, 4); - gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP]++) << 8; - GB_advance_cycles(gb, 8); + gb->pc = cycle_read(gb, gb->registers[GB_REGISTER_SP]++); + gb->pc |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; + cycle_no_access(gb); } static void reti(GB_gameboy_t *gb, uint8_t opcode) @@ -982,65 +909,62 @@ static void reti(GB_gameboy_t *gb, uint8_t opcode) gb->ime = true; } +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + cycle_no_access(gb); + ret(gb, opcode); + } + else { + cycle_no_access(gb); + } +} + static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - GB_advance_cycles(gb, 4); - uint16_t addr = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - addr |= (GB_read_memory(gb, gb->pc++) << 8); - GB_advance_cycles(gb, 3); + uint16_t addr = cycle_read(gb, gb->pc++); + addr |= (cycle_read(gb, gb->pc++) << 8); + flush_pending_cycles(gb); GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - GB_advance_cycles(gb, 5); + cycle_no_access(gb); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); } static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); - uint8_t temp = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 3); - GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 5); + uint8_t temp = cycle_read(gb, gb->pc++); + cycle_write(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); } static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - uint8_t temp = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + temp) << 8; - GB_advance_cycles(gb, 4); + uint8_t temp = cycle_read(gb, gb->pc++); + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + temp) << 8; } static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 3); - GB_write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 5); + cycle_write(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); } static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; - GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; } static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; uint16_t sp = gb->registers[GB_REGISTER_SP]; - GB_advance_cycles(gb, 4); - offset = (int8_t) GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 12); + offset = (int8_t) cycle_read(gb, gb->pc++); + cycle_no_access(gb); + cycle_no_access(gb); gb->registers[GB_REGISTER_SP] += offset; gb->registers[GB_REGISTER_AF] &= 0xFF00; @@ -1057,47 +981,36 @@ static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); gb->pc = gb->registers[GB_REGISTER_HL]; } static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; - GB_advance_cycles(gb, 4); - addr = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - addr |= GB_read_memory(gb, gb->pc++) << 8; - GB_advance_cycles(gb, 3); - GB_write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); - GB_advance_cycles(gb, 5); + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); } static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF; - addr = GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 4); - addr |= GB_read_memory(gb, gb->pc++) << 8 ; - GB_advance_cycles(gb, 4); - gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, addr) << 8; - GB_advance_cycles(gb, 4); + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8 ; + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8; } static void di(GB_gameboy_t *gb, uint8_t opcode) { /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB for different reasons. */ - GB_advance_cycles(gb, 4); gb->ime = false; } static void ei(GB_gameboy_t *gb, uint8_t opcode) { /* ei is actually "disable interrupts for one instruction, then enable them". */ - GB_advance_cycles(gb, 4); if (!gb->ime && !gb->ime_toggle) { gb->ime_toggle = true; } @@ -1106,10 +1019,9 @@ static void ei(GB_gameboy_t *gb, uint8_t opcode) static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; - GB_advance_cycles(gb, 4); gb->registers[GB_REGISTER_AF] &= 0xFF00; - offset = (int8_t) GB_read_memory(gb, gb->pc++); - GB_advance_cycles(gb, 8); + offset = (int8_t) cycle_read(gb, gb->pc++); + cycle_no_access(gb); gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { @@ -1123,16 +1035,15 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 8); gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; + cycle_no_access(gb); } static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value << 1) | carry); @@ -1148,8 +1059,7 @@ static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) { bool carry; uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); carry = (value & 0x01) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; value = (value >> 1) | (carry << 7); @@ -1167,8 +1077,7 @@ static void rl_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; uint8_t value; bool bit7; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; bit7 = (value & 0x80) != 0; @@ -1189,8 +1098,7 @@ static void rr_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool bit1; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; bit1 = (value & 0x1) != 0; @@ -1209,8 +1117,7 @@ static void sla_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; bool carry; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value << 1)); @@ -1226,8 +1133,7 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t bit7; uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); bit7 = value & 0x80; gb->registers[GB_REGISTER_AF] &= 0xFF00; if (value & 1) { @@ -1243,8 +1149,7 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 1)); if (value & 1) { @@ -1258,8 +1163,7 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode) static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, 3); + value = get_src_value(gb, opcode); gb->registers[GB_REGISTER_AF] &= 0xFF00; set_src_value(gb, opcode, (value >> 4) | (value << 4)); if (!value) { @@ -1271,8 +1175,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; uint8_t bit; - GB_advance_cycles(gb, 4); - value = get_src_value(gb, opcode, (opcode & 0xC0) == 0x40? 4 : 3); + 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; @@ -1291,8 +1194,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) { - GB_advance_cycles(gb, 4); - opcode = GB_read_memory(gb, gb->pc++); + opcode = cycle_read(gb, gb->pc++); switch (opcode >> 3) { case 0: rlc_r(gb, opcode); @@ -1397,18 +1299,18 @@ void GB_cpu_run(GB_gameboy_t *gb) /* Call interrupt */ else if (effecitve_ime && interrupt_queue) { gb->halted = false; - uint16_t call_addr = gb->pc - 1; + uint16_t call_addr = gb->pc; - GB_advance_cycles(gb, 7); + cycle_no_access(gb); + cycle_no_access(gb); GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_no_access(gb); + + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); interrupt_queue = gb->interrupt_enable; - GB_advance_cycles(gb, 4); - GB_write_memory(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; - - GB_advance_cycles(gb, 5); + if (interrupt_queue) { uint8_t interrupt_bit = 0; while (!(interrupt_queue & 1)) { @@ -1427,7 +1329,7 @@ void GB_cpu_run(GB_gameboy_t *gb) /* Run mode */ else if(!gb->halted) { GB_trigger_oam_bug_read_increase(gb, gb->pc); /* Todo: test T-cycle timing */ - gb->last_opcode_read = GB_read_memory(gb, gb->pc++); + gb->last_opcode_read = cycle_read(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; gb->halt_bug = false; @@ -1440,4 +1342,5 @@ void GB_cpu_run(GB_gameboy_t *gb) gb->hdma_on = true; gb->hdma_cycles = -8; } + flush_pending_cycles(gb); } From 84aa06aba52d24e6079a978f0e0af09980ddbcae Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Apr 2018 13:35:16 +0300 Subject: [PATCH 0639/1216] Clean up OAM bug code --- Core/z80_cpu.c | 66 +++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 2ba59c74..90f935a9 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -34,6 +34,17 @@ static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) return ret; } +static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + GB_trigger_oam_bug_read_increase(gb, addr); /* Todo: test T-cycle timing */ + uint8_t ret = GB_read_memory(gb, addr); + gb->pending_cycles = 4; + return ret; +} + static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { assert(gb->pending_cycles); @@ -48,6 +59,21 @@ static void cycle_no_access(GB_gameboy_t *gb) gb->pending_cycles += 4; } +static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) +{ + if (gb->is_cgb) { + /* Slight optimization */ + gb->pending_cycles += 4; + return; + } + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ + gb->pending_cycles = 4; + +} + static void flush_pending_cycles(GB_gameboy_t *gb) { if (gb->pending_cycles) { @@ -118,9 +144,7 @@ static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id = (opcode >> 4) + 1; - flush_pending_cycles(gb); - GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ - cycle_no_access(gb); + cycle_oam_bug(gb, register_id); gb->registers[register_id]++; } @@ -230,9 +254,7 @@ static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id = (opcode >> 4) + 1; - flush_pending_cycles(gb); - GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ - cycle_no_access(gb); + cycle_oam_bug(gb, register_id); gb->registers[register_id]--; } @@ -424,18 +446,14 @@ static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { - flush_pending_cycles(gb); - GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_HL]); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[GB_REGISTER_HL]++) << 8; + gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]++) << 8; } static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { - flush_pending_cycles(gb); - GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_HL]); /* Todo: test T-cycle timing */ gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[GB_REGISTER_HL]--) << 8; + gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]--) << 8; } static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) @@ -693,9 +711,7 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; register_id = ((opcode >> 4) + 1) & 3; - flush_pending_cycles(gb); - GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - gb->registers[register_id] = cycle_read(gb, gb->registers[GB_REGISTER_SP]++); + gb->registers[register_id] = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++); gb->registers[register_id] |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. } @@ -731,9 +747,7 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) if (condition_code(gb, opcode)) { uint16_t addr = cycle_read(gb, gb->pc++); addr |= (cycle_read(gb, gb->pc++) << 8); - flush_pending_cycles(gb); - GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - cycle_no_access(gb); + cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); gb->pc = addr; @@ -750,9 +764,7 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void push_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; - flush_pending_cycles(gb); - GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - cycle_no_access(gb); + cycle_oam_bug(gb, GB_REGISTER_SP); register_id = ((opcode >> 4) + 1) & 3; cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); @@ -884,9 +896,7 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void rst(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - flush_pending_cycles(gb); - GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - cycle_no_access(gb); + cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); gb->pc = opcode ^ 0xC7; @@ -896,9 +906,7 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) static void ret(GB_gameboy_t *gb, uint8_t opcode) { GB_debugger_ret_hook(gb); - flush_pending_cycles(gb); - GB_trigger_oam_bug_read_increase(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - gb->pc = cycle_read(gb, gb->registers[GB_REGISTER_SP]++); + gb->pc = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++); gb->pc |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; cycle_no_access(gb); } @@ -925,9 +933,7 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) uint16_t call_addr = gb->pc - 1; uint16_t addr = cycle_read(gb, gb->pc++); addr |= (cycle_read(gb, gb->pc++) << 8); - flush_pending_cycles(gb); - GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ - cycle_no_access(gb); + cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); gb->pc = addr; From 2c6f7906c5d19897ca3c15b0b749ca4832e1a404 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Apr 2018 15:32:55 +0300 Subject: [PATCH 0640/1216] Make multi-byte opcodes trigger the OAM bug when they increase PC --- Core/z80_cpu.c | 92 ++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 90f935a9..78fa3ffd 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -82,7 +82,7 @@ static void flush_pending_cycles(GB_gameboy_t *gb) gb->pending_cycles = 0; } -/* Todo: all multi-byte opcodes probably trigger the OAM bug when they increase pc */ +/* Todo: test if multi-byte opcodes trigger the OAM bug correctly */ static void ill(GB_gameboy_t *gb, uint8_t opcode) { @@ -110,6 +110,8 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) else { gb->stopped = true; } + + /* Todo: is PC being actually read? */ gb->pc++; } @@ -129,8 +131,8 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; uint16_t value; register_id = (opcode >> 4) + 1; - value = cycle_read(gb, gb->pc++); - value |= cycle_read(gb, gb->pc++) << 8; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + value |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; gb->registers[register_id] = value; } @@ -185,7 +187,7 @@ static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] &= 0xFF; - gb->registers[register_id] |= cycle_read(gb, gb->pc++) << 8; + gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; } static void rlca(GB_gameboy_t *gb, uint8_t opcode) @@ -216,8 +218,8 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify order is correct */ uint16_t addr; - addr = cycle_read(gb, gb->pc++); - addr |= cycle_read(gb, gb->pc++) << 8; + addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); cycle_write(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); } @@ -303,7 +305,7 @@ static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = (opcode >> 4) + 1; gb->registers[register_id] &= 0xFF00; - gb->registers[register_id] |= cycle_read(gb, gb->pc++); + gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++); } static void rrca(GB_gameboy_t *gb, uint8_t opcode) @@ -333,7 +335,7 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode) static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify timing */ - gb->pc += (int8_t)cycle_read(gb, gb->pc) + 1; + gb->pc += (int8_t)cycle_read_inc_oam_bug(gb, gb->pc) + 1; cycle_no_access(gb); } @@ -355,13 +357,11 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { + int8_t offset = cycle_read_inc_oam_bug(gb, gb->pc++); if (condition_code(gb, opcode)) { - gb->pc += (int8_t)cycle_read(gb, gb->pc) + 1; + gb->pc += offset; + cycle_no_access(gb); } - else { - gb->pc += 1; - } - cycle_no_access(gb); } static void daa(GB_gameboy_t *gb, uint8_t opcode) @@ -491,7 +491,7 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t data = cycle_read(gb, gb->pc++); + uint8_t data = cycle_read_inc_oam_bug(gb, gb->pc++); cycle_write(gb, gb->registers[GB_REGISTER_HL], data); } @@ -718,24 +718,18 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); if (condition_code(gb, opcode)) { - uint16_t addr = cycle_read(gb, gb->pc); - addr |= (cycle_read(gb, gb->pc + 1) << 8); cycle_no_access(gb); gb->pc = addr; - - } - else { - cycle_no_access(gb); - cycle_no_access(gb); - gb->pc += 2; } } static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t addr = cycle_read(gb, gb->pc); - addr |= (cycle_read(gb, gb->pc + 1) << 8); + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc + 1) << 8); cycle_no_access(gb); gb->pc = addr; @@ -744,9 +738,9 @@ static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); if (condition_code(gb, opcode)) { - uint16_t addr = cycle_read(gb, gb->pc++); - addr |= (cycle_read(gb, gb->pc++) << 8); cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); @@ -754,11 +748,6 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) GB_debugger_call_hook(gb, call_addr); } - else { - cycle_no_access(gb); - cycle_no_access(gb); - gb->pc += 2; - } } static void push_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -773,7 +762,7 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode) static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a + value) << 8; if ((uint8_t) (a + value) == 0) { @@ -790,7 +779,7 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(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; @@ -809,7 +798,7 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; if (a == value) { @@ -826,7 +815,7 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(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; @@ -845,7 +834,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(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) { @@ -856,7 +845,7 @@ static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; if ((a ^ value) == 0) { @@ -867,7 +856,7 @@ static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a | value) << 8; if ((a | value) == 0) { @@ -878,7 +867,7 @@ static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read(gb, gb->pc++); + value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; @@ -931,8 +920,8 @@ static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - uint16_t addr = cycle_read(gb, gb->pc++); - addr |= (cycle_read(gb, gb->pc++) << 8); + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); @@ -942,14 +931,14 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t temp = cycle_read(gb, gb->pc++); + uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); cycle_write(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); } static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] &= 0xFF; - uint8_t temp = cycle_read(gb, gb->pc++); + uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + temp) << 8; } @@ -968,7 +957,7 @@ static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; uint16_t sp = gb->registers[GB_REGISTER_SP]; - offset = (int8_t) cycle_read(gb, gb->pc++); + offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++); cycle_no_access(gb); cycle_no_access(gb); gb->registers[GB_REGISTER_SP] += offset; @@ -993,8 +982,8 @@ static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; - addr = cycle_read(gb, gb->pc++); - addr |= cycle_read(gb, gb->pc++) << 8; + addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; cycle_write(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); } @@ -1002,8 +991,8 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; gb->registers[GB_REGISTER_AF] &= 0xFF; - addr = cycle_read(gb, gb->pc++); - addr |= cycle_read(gb, gb->pc++) << 8 ; + addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8 ; gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8; } @@ -1026,7 +1015,7 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; gb->registers[GB_REGISTER_AF] &= 0xFF00; - offset = (int8_t) cycle_read(gb, gb->pc++); + offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++); cycle_no_access(gb); gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; @@ -1200,7 +1189,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) { - opcode = cycle_read(gb, gb->pc++); + opcode = cycle_read_inc_oam_bug(gb, gb->pc++); switch (opcode >> 3) { case 0: rlc_r(gb, opcode); @@ -1334,8 +1323,7 @@ void GB_cpu_run(GB_gameboy_t *gb) } /* Run mode */ else if(!gb->halted) { - GB_trigger_oam_bug_read_increase(gb, gb->pc); /* Todo: test T-cycle timing */ - gb->last_opcode_read = cycle_read(gb, gb->pc++); + gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; gb->halt_bug = false; From be9df4d658b6eb6dd55756420443799c1c9a0f7d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Apr 2018 17:57:00 +0300 Subject: [PATCH 0641/1216] Added mechanism to handle MMIO read/write conflicts. Fixes #65 --- Core/timing.c | 9 +---- Core/z80_cpu.c | 91 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 29 deletions(-) diff --git a/Core/timing.c b/Core/timing.c index 04a87da8..6132f40d 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -155,14 +155,7 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) } void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) -{ - /* It appears that on the CGB, write timing is a bit different then on the DMG, effectively - making writes 1 T-cycle late when compared to the DMG. */ - if (gb->is_cgb) { - cycles = (cycles + 1) & ~3; - if (cycles == 0) return; - } - +{ // Affected by speed boost gb->dma_cycles += cycles; diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 78fa3ffd..2e134568 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -6,23 +6,42 @@ typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); -/* - About memory timings: - - Each M-cycle consists of 4 T-cycles. Every time the CPU accesses the memory it happens on the 1st T-cycle of an - M-cycle. During that cycle, other things may happen, such the PPU drawing to the screen. Since we can't really run - things in parallel, we run non-CPU "activities" serially using advnace_cycles(...). This is normally not a problem, - unless two entities (e.g. both the CPU and the PPU) read the same register at the same time (e.g. BGP). Since memory - accesses happen for an enitre T-cycle, if someone reads a value while someone else changes it during in the same - T-cycle, the read will return the new value. To correctly emulate this, a memory access T-cycle looks like this: - - - Perform memory write (If needed) - - Run everything else - - Perform memory read (If needed) - - This is equivalent to running the memory write 1 T-cycle before the memory read. - - */ +typedef enum { + /* Default behavior. If the CPU writes while another component reads, it reads the old value */ + GB_CONFLICT_READ_OLD, + /* If the CPU writes while another component reads, it reads the new value */ + GB_CONFLICT_READ_NEW, + /* If the CPU writes while another component reads, it reads a bitwise OR between the new and old values */ + GB_CONFLICT_READ_OR, + /* If the CPU and another component write at the same time, the CPU's value "wins"*/ + GB_CONFLICT_WRITE_CPU, +} GB_conflict_t; + +static const GB_conflict_t cgb_conflict_map[0x80] = { + [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + + /* Todo: most values not verified, and probably differ between revisions */ +}; + +static const GB_conflict_t dmg_conflict_map[0x80] = { + [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + + /* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */ + [GB_IO_BGP] = GB_CONFLICT_READ_OR, + [GB_IO_OBP0] = GB_CONFLICT_READ_OR, + [GB_IO_OBP1] = GB_CONFLICT_READ_OR, + + /* Todo: These were verified on an SGB2 */ + [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_READ_NEW, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_SCX] = GB_CONFLICT_READ_NEW, + + /* Todo: these were not verified at all */ + [GB_IO_WY] = GB_CONFLICT_READ_NEW, + [GB_IO_WX] = GB_CONFLICT_READ_NEW, + [GB_IO_LYC] = GB_CONFLICT_READ_NEW, +}; static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) { @@ -48,10 +67,40 @@ static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr) static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { assert(gb->pending_cycles); - GB_advance_cycles(gb, gb->pending_cycles - 1); - GB_write_memory(gb, addr, value); - GB_advance_cycles(gb, 1); - gb->pending_cycles = 4; + GB_conflict_t conflict = GB_CONFLICT_READ_OLD; + if ((addr & 0xFF80) == 0xFF00) { + conflict = (gb->is_cgb? cgb_conflict_map : dmg_conflict_map)[addr & 0x7F]; + } + switch (conflict) { + case GB_CONFLICT_READ_OLD: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + return; + + case GB_CONFLICT_READ_NEW: + GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + + case GB_CONFLICT_READ_OR: { + GB_advance_cycles(gb, gb->pending_cycles - 1); + uint8_t old_value = GB_read_memory(gb, addr); + GB_write_memory(gb, addr, value | old_value); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + return; + + case GB_CONFLICT_WRITE_CPU: + GB_advance_cycles(gb, gb->pending_cycles + 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + return; + } + + } } static void cycle_no_access(GB_gameboy_t *gb) From 9343d8162d5e6934d7894d9eea57c55f52faa503 Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Wed, 18 Apr 2018 19:22:08 +0200 Subject: [PATCH 0642/1216] Add fullscreen mode for the SDL2 port --- SDL/gui.c | 1 + SDL/main.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/SDL/gui.c b/SDL/gui.c index a09cb49b..e3b72069 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -76,6 +76,7 @@ static const char *help[] ={ " Pause: " MODIFIER_NAME "+P\n" " Save state: " MODIFIER_NAME "+(0-9)\n" " Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n" +" Toggle Fullscreen " MODIFIER_NAME "+F\n" #ifdef __APPLE__ " Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n" #else diff --git a/SDL/main.c b/SDL/main.c index bd14377d..4b5ee746 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -202,6 +202,17 @@ static void handle_events(GB_gameboy_t *gb) SDL_PauseAudio(SDL_GetAudioStatus() == SDL_AUDIO_PLAYING? true : false); } break; + + case SDL_SCANCODE_F: + if (event.key.keysym.mod & MODIFIER) { + if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else { + SDL_SetWindowFullscreen(window, 0); + } + } + break; default: /* Save states */ From 7fe86cec59c1143275ffb7fc07b6716ae0116033 Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Thu, 19 Apr 2018 14:40:42 +0200 Subject: [PATCH 0643/1216] Fix savestates in SDL2 port Because SDL_SCANCODE_0 comes *after* SDL_SCANCODE_9 in the SDL keycode table, we have to check if the keycode is between >=1 and <=0. We also have to substract SDL_SCANCODE_1 in order to set command_parameter properly. Errata: Currently, the savestate created with CTRL+0 is created, but refuses to load on Windows (working fine on Linux). --- SDL/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index bd14377d..710e1aa2 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -205,9 +205,9 @@ static void handle_events(GB_gameboy_t *gb) default: /* Save states */ - if (event.key.keysym.scancode >= SDL_SCANCODE_0 && event.key.keysym.scancode <= SDL_SCANCODE_9) { + if (event.key.keysym.scancode >= SDL_SCANCODE_1 && event.key.keysym.scancode <= SDL_SCANCODE_0) { if (event.key.keysym.mod & MODIFIER) { - command_parameter = event.key.keysym.scancode - SDL_SCANCODE_0; + command_parameter = event.key.keysym.scancode - SDL_SCANCODE_1; if (event.key.keysym.mod & KMOD_SHIFT) { pending_command = GB_SDL_LOAD_STATE_COMMAND; From 240730417774174315c2f7c148393834eb3297eb Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Thu, 19 Apr 2018 16:47:54 +0200 Subject: [PATCH 0644/1216] SDL2: Write battery file information before issueing RESET_COMMAND Before performing the GB reset, we should perform a GB_save_battery. Otherwise, resetting the emulation will kill ("kill" as in simply don't write them into the .sav) all changes made to the battery save since sameboy was started. --- SDL/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/SDL/main.c b/SDL/main.c index bd14377d..cfdbf4a9 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -302,6 +302,7 @@ static bool handle_pending_command(void) } case GB_SDL_RESET_COMMAND: + GB_save_battery(&gb, battery_save_path_ptr); return true; case GB_SDL_NO_COMMAND: From ca571c6fa5fe78bb3385afe627c74932bd6fb52c Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Sun, 22 Apr 2018 15:22:10 +0200 Subject: [PATCH 0645/1216] SDL2: Update to SDL_OpenAudioDevice() Instead of the legacy SDL_OpenAudio() method, we now use the newer SDL_OpenAudioDevice() functions. This fixes audio in Windows if the SDL version is 2.0.6 or higher. It also allows us to use 48kHz audio for Windows (96kHz somewhat works too, but since we don't get absolutely smooth audio with it, I'd stick with 48kHz for now until we find a solution. 44.1Khz is available as fallback for SDL 2.0.5 and lower. Yes, the 2.0.5 to 2.0.6 transition was quite harsh in terms of Windows audio support... --- SDL/main.c | 60 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index bd14377d..ea03ef01 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -10,8 +10,15 @@ #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 #else -/* Windows (well, at least my VM) can't handle 96KHz sound well :( */ -#define AUDIO_FREQUENCY 44100 +/* LIJI32 says: Windows (well, at least my VM) can't handle 96KHz sound well :( + + felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. + we can get is 48000. 96000 also works, but always has some faint crackling in + the audio, no matter how high or low I set the buffer length... + Not quite satisfied with that solution, because acc. to SDL2 docs, + 96k + WASAPI *should* work. */ + +#define AUDIO_FREQUENCY 48000 #endif GB_gameboy_t gb; @@ -24,6 +31,8 @@ static char *filename = NULL; static bool should_free_filename = false; static char *battery_save_path_ptr; +SDL_AudioDeviceID deviceId; + void set_filename(const char *new_filename, bool new_should_free) { if (filename && should_free_filename) { @@ -132,13 +141,13 @@ static void handle_events(GB_gameboy_t *gb) } else { - bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING; + bool audio_playing = SDL_GetAudioDeviceStatus(deviceId) == SDL_AUDIO_PLAYING; if (audio_playing) { - SDL_PauseAudio(true); + SDL_PauseAudioDevice(deviceId, 1); } run_gui(true); - if (audio_playing) { - SDL_PauseAudio(false); + if (!audio_playing) { + SDL_PauseAudioDevice(deviceId, 0); } GB_set_color_correction_mode(gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(gb, configuration.highpass_mode); @@ -160,13 +169,13 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYDOWN: switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: { - bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING; + bool audio_playing = SDL_GetAudioDeviceStatus(deviceId) == SDL_AUDIO_PLAYING; if (audio_playing) { - SDL_PauseAudio(true); + SDL_PauseAudioDevice(deviceId, 1); } run_gui(true); - if (audio_playing) { - SDL_PauseAudio(false); + if (!audio_playing) { + SDL_PauseAudioDevice(deviceId, 0); } GB_set_color_correction_mode(gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(gb, configuration.highpass_mode); @@ -199,9 +208,14 @@ static void handle_events(GB_gameboy_t *gb) break; } #endif - SDL_PauseAudio(SDL_GetAudioStatus() == SDL_AUDIO_PLAYING? true : false); + bool audio_playing = SDL_GetAudioDeviceStatus(deviceId) == SDL_AUDIO_PLAYING; + if (audio_playing) { + SDL_PauseAudioDevice(deviceId, 1); } - break; + else if (!audio_playing) { + SDL_PauseAudioDevice(deviceId, 0); + } + break; default: /* Save states */ @@ -218,6 +232,7 @@ static void handle_events(GB_gameboy_t *gb) } } break; + } } case SDL_KEYUP: // Fallthrough if (event.key.keysym.scancode == configuration.keys[8]) { @@ -233,9 +248,9 @@ static void handle_events(GB_gameboy_t *gb) break; default: break; + } } } -} static void vblank(GB_gameboy_t *gb) { @@ -456,9 +471,24 @@ int main(int argc, char **argv) #else want_aspec.samples = 512; #endif + +#if SDL_COMPILEDVERSION > 2006 && defined(_WIN32) + /* SDL 2.0.6 offers WASAPI support which allows for much lower audio buffer lengths which at least + theoretically reduces lagging. */ + printf("SDL 2.0.6+ detected, reducing audio buffer to 32 samples\n"); + want_aspec.samples = 32; +#endif + +#if SDL_COMPILEDVERSION <= 2005 && defined(_WIN32) + /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency + to 44100 because otherwise we would get garbled audio output.*/ + printf("Fallback: SDL 2.0.5 detected, lowering audio freqency to 44100\n"); + want_aspec.freq = 44100; +#endif + want_aspec.callback = audio_callback; want_aspec.userdata = &gb; - SDL_OpenAudio(&want_aspec, &have_aspec); + deviceId = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); /* Start Audio */ @@ -484,7 +514,7 @@ int main(int argc, char **argv) if (filename == NULL) { run_gui(false); } - SDL_PauseAudio(false); + SDL_PauseAudioDevice(deviceId, 0); run(); // Never returns return 0; } From 8f3fc1c2ade3c1c632cd3f3998ee26d2962e4dea Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Sun, 22 Apr 2018 16:20:11 +0200 Subject: [PATCH 0646/1216] Fix check for SDL2 version --- SDL/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index ea03ef01..73c34e84 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -472,7 +472,7 @@ int main(int argc, char **argv) want_aspec.samples = 512; #endif -#if SDL_COMPILEDVERSION > 2006 && defined(_WIN32) +#if SDL_COMPILEDVERSION >= 2006 && defined(_WIN32) /* SDL 2.0.6 offers WASAPI support which allows for much lower audio buffer lengths which at least theoretically reduces lagging. */ printf("SDL 2.0.6+ detected, reducing audio buffer to 32 samples\n"); From 0f8385a79823a3ce282824e22954299ad95803b8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 25 Apr 2018 00:08:06 +0300 Subject: [PATCH 0647/1216] Refined line 153 behavior on a CGB. Verified on CGB-E. --- Core/display.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Core/display.c b/Core/display.c index 45a6fd95..8899f882 100644 --- a/Core/display.c +++ b/Core/display.c @@ -859,25 +859,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 13, LINE_LENGTH - 4); } + /* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */ /* Lines 153 */ gb->io_registers[GB_IO_LY] = 153; - if (!gb->cgb_mode) { - gb->ly_for_comparison = gb->is_cgb? 153 : -1; - } + gb->ly_for_comparison = gb->is_cgb? 152 : -1; GB_STAT_update(gb); GB_SLEEP(gb, display, 14, 6); gb->io_registers[GB_IO_LY] = 0; - gb->ly_for_comparison = gb->cgb_mode? 0 : 153; + gb->ly_for_comparison = gb->is_cgb? 0 : 153; GB_STAT_update(gb); GB_SLEEP(gb, display, 15, 2); - if (gb->cgb_mode) { - gb->ly_for_comparison = 0; - } - else if(!gb->is_cgb) { - gb->ly_for_comparison = -1; - } + gb->ly_for_comparison = gb->is_cgb? 153 : -1; GB_STAT_update(gb); GB_SLEEP(gb, display, 16, 4); From af3554c1d1e0639e39a8e17f56d0278bfe75d5d8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Apr 2018 13:40:39 +0300 Subject: [PATCH 0648/1216] More accurate emulation of the LYC register and interrupt. (Still not perfect on a CGB) --- Core/display.c | 6 +++++- Core/gb.h | 1 + Core/z80_cpu.c | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 8899f882..d5af6110 100644 --- a/Core/display.c +++ b/Core/display.c @@ -247,9 +247,13 @@ void GB_STAT_update(GB_gameboy_t *gb) gb->stat_interrupt_line = gb->oam_interrupt_line; /* Set LY=LYC bit */ if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { + gb->lyc_interrupt_line = true; gb->io_registers[GB_IO_STAT] |= 4; } else { + if (gb->ly_for_comparison != (uint16_t)-1) { + gb->lyc_interrupt_line = false; + } gb->io_registers[GB_IO_STAT] &= ~4; } @@ -262,7 +266,7 @@ void GB_STAT_update(GB_gameboy_t *gb) } /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ - if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) { + if ((gb->io_registers[GB_IO_STAT] & 0x40) && gb->lyc_interrupt_line) { gb->stat_interrupt_line = true; } diff --git a/Core/gb.h b/Core/gb.h index 627e1cf2..de08bfc7 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -423,6 +423,7 @@ struct GB_gameboy_internal_s { uint8_t extra_penalty_for_sprite_at_0; bool is_first_line_mode2; bool oam_interrupt_line; + bool lyc_interrupt_line; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 2e134568..bf7da42c 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -13,18 +13,20 @@ typedef enum { GB_CONFLICT_READ_NEW, /* If the CPU writes while another component reads, it reads a bitwise OR between the new and old values */ GB_CONFLICT_READ_OR, - /* If the CPU and another component write at the same time, the CPU's value "wins"*/ + /* If the CPU and another component write at the same time, the CPU's value "wins" */ GB_CONFLICT_WRITE_CPU, } GB_conflict_t; +/* Todo: How does double speed mode affect these? */ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, - + /* Todo: most values not verified, and probably differ between revisions */ }; static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + [GB_IO_LYC] = GB_CONFLICT_READ_OLD, /* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */ [GB_IO_BGP] = GB_CONFLICT_READ_OR, @@ -40,7 +42,6 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { /* Todo: these were not verified at all */ [GB_IO_WY] = GB_CONFLICT_READ_NEW, [GB_IO_WX] = GB_CONFLICT_READ_NEW, - [GB_IO_LYC] = GB_CONFLICT_READ_NEW, }; static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) From 713dc02e464f40af0a4d7927a817ab692f79a275 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 11 May 2018 12:38:55 +0300 Subject: [PATCH 0649/1216] A bit tacky, but T-cycle accurate emulation of LYC write conflicts on the CGB. Only single speed mode verified. Closes #54 --- Core/display.c | 44 +++++++++++++++++++++++++------------------- Core/memory.c | 30 +++++++++++++++++++++++++++++- Core/z80_cpu.c | 1 + 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/Core/display.c b/Core/display.c index d5af6110..c82a2f02 100644 --- a/Core/display.c +++ b/Core/display.c @@ -246,15 +246,17 @@ void GB_STAT_update(GB_gameboy_t *gb) bool previous_interrupt_line = gb->stat_interrupt_line | gb->oam_interrupt_line; gb->stat_interrupt_line = gb->oam_interrupt_line; /* Set LY=LYC bit */ - if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { - gb->lyc_interrupt_line = true; - gb->io_registers[GB_IO_STAT] |= 4; - } - else { - if (gb->ly_for_comparison != (uint16_t)-1) { - gb->lyc_interrupt_line = false; + if (gb->ly_for_comparison != (uint16_t)-1 || !gb->is_cgb) { + if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { + gb->lyc_interrupt_line = true; + gb->io_registers[GB_IO_STAT] |= 4; + } + else { + if (gb->ly_for_comparison != (uint16_t)-1) { + gb->lyc_interrupt_line = false; + } + gb->io_registers[GB_IO_STAT] &= ~4; } - gb->io_registers[GB_IO_STAT] &= ~4; } switch (gb->io_registers[GB_IO_STAT] & 3) { @@ -565,6 +567,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 26); GB_STATE(gb, display, 27); GB_STATE(gb, display, 28); + GB_STATE(gb, display, 29); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -638,7 +641,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 6, 3); gb->io_registers[GB_IO_LY] = gb->current_line; gb->oam_read_blocked = true; - gb->ly_for_comparison = gb->current_line? (gb->is_cgb? gb->current_line - 1 : -1) : 0; + gb->ly_for_comparison = gb->current_line? -1 : 0; /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. PPU glitch? (Todo: and in double speed mode?) */ @@ -822,9 +825,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Lines 144 - 152 */ for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { gb->io_registers[GB_IO_LY] = gb->current_line; - if (!gb->is_cgb) { - gb->ly_for_comparison = -1; - } + gb->ly_for_comparison = -1; GB_SLEEP(gb, display, 26, 2); if (gb->current_line == LINES) { gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; @@ -866,22 +867,27 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */ /* Lines 153 */ gb->io_registers[GB_IO_LY] = 153; - gb->ly_for_comparison = gb->is_cgb? 152 : -1; + gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 14, 6); + GB_SLEEP(gb, display, 14, gb->is_cgb? 4: 6); + + if (!gb->is_cgb) { + gb->io_registers[GB_IO_LY] = 0; + } + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 15, gb->is_cgb? 4: 2); gb->io_registers[GB_IO_LY] = 0; - gb->ly_for_comparison = gb->is_cgb? 0 : 153; - GB_STAT_update(gb); - GB_SLEEP(gb, display, 15, 2); - gb->ly_for_comparison = gb->is_cgb? 153 : -1; GB_STAT_update(gb); GB_SLEEP(gb, display, 16, 4); gb->ly_for_comparison = 0; GB_STAT_update(gb); - GB_SLEEP(gb, display, 17, LINE_LENGTH - 12); + GB_SLEEP(gb, display, 29, 12); /* Writing to LYC during this period on a CGB has side effects */ + GB_SLEEP(gb, display, 17, LINE_LENGTH - 24); + /* Reset window rendering state */ gb->wy_diff = 0; diff --git a/Core/memory.c b/Core/memory.c index 7bced24e..1fbd3316 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -595,8 +595,36 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[addr & 0xFF] = value; return; case GB_IO_LYC: + + /* TODO: Probably completely wrong in double speed mode */ + + /* TODO: This hack is disgusting */ + if (gb->display_state == 29 && gb->is_cgb) { + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + gb->ly_for_comparison = 0; + } + gb->io_registers[addr & 0xFF] = value; - GB_STAT_update(gb); + + /* These are the states when LY changes, let the display routine call GB_STAT_update for use + so it correctly handles T-cycle accurate LYC writes */ + if (!gb->is_cgb || ( + gb->display_state != 6 && + gb->display_state != 26 && + gb->display_state != 15 && + gb->display_state != 16)) { + + /* More hacks to make LYC write conflicts work */ + if (gb->display_state == 14 && gb->is_cgb) { + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + gb->ly_for_comparison = -1; + } + else { + GB_STAT_update(gb); + } + } return; case GB_IO_TIMA: diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index bf7da42c..3d35cead 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -20,6 +20,7 @@ typedef enum { /* Todo: How does double speed mode affect these? */ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, /* Todo: most values not verified, and probably differ between revisions */ }; From bfc96abf8fcfe49865f7b6e04a3fd7ce817b233c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 11 May 2018 12:51:15 +0300 Subject: [PATCH 0650/1216] Make save state names consistent across the Cocoa and SDL port --- SDL/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index 755d054e..34c41be7 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -218,7 +218,7 @@ static void handle_events(GB_gameboy_t *gb) /* Save states */ if (event.key.keysym.scancode >= SDL_SCANCODE_1 && event.key.keysym.scancode <= SDL_SCANCODE_0) { if (event.key.keysym.mod & MODIFIER) { - command_parameter = event.key.keysym.scancode - SDL_SCANCODE_1; + command_parameter = (event.key.keysym.scancode - SDL_SCANCODE_1 + 1) % 10; if (event.key.keysym.mod & KMOD_SHIFT) { pending_command = GB_SDL_LOAD_STATE_COMMAND; From 4527d9ee390a90dd270dc16326f5d6cd07762df7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 11 May 2018 13:29:58 +0300 Subject: [PATCH 0651/1216] Styling fixes, fixed bugs caused by a rebellious brace, removed debug prints --- SDL/main.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 5120e87a..e25c7b28 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -10,9 +10,9 @@ #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 #else -/* LIJI32 says: Windows (well, at least my VM) can't handle 96KHz sound well :( +/* Windows (well, at least my VM) can't handle 96KHz sound well :( */ - felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. +/* felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. we can get is 48000. 96000 also works, but always has some faint crackling in the audio, no matter how high or low I set the buffer length... Not quite satisfied with that solution, because acc. to SDL2 docs, @@ -31,7 +31,7 @@ static char *filename = NULL; static bool should_free_filename = false; static char *battery_save_path_ptr; -SDL_AudioDeviceID deviceId; +SDL_AudioDeviceID device_id; void set_filename(const char *new_filename, bool new_should_free) { @@ -141,13 +141,13 @@ static void handle_events(GB_gameboy_t *gb) } else { - bool audio_playing = SDL_GetAudioDeviceStatus(deviceId) == SDL_AUDIO_PLAYING; + bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; if (audio_playing) { - SDL_PauseAudioDevice(deviceId, 1); + SDL_PauseAudioDevice(device_id, 1); } run_gui(true); if (!audio_playing) { - SDL_PauseAudioDevice(deviceId, 0); + SDL_PauseAudioDevice(device_id, 0); } GB_set_color_correction_mode(gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(gb, configuration.highpass_mode); @@ -169,13 +169,13 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYDOWN: switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: { - bool audio_playing = SDL_GetAudioDeviceStatus(deviceId) == SDL_AUDIO_PLAYING; + bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; if (audio_playing) { - SDL_PauseAudioDevice(deviceId, 1); + SDL_PauseAudioDevice(device_id, 1); } run_gui(true); if (!audio_playing) { - SDL_PauseAudioDevice(deviceId, 0); + SDL_PauseAudioDevice(device_id, 0); } GB_set_color_correction_mode(gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(gb, configuration.highpass_mode); @@ -208,12 +208,13 @@ static void handle_events(GB_gameboy_t *gb) break; } #endif - bool audio_playing = SDL_GetAudioDeviceStatus(deviceId) == SDL_AUDIO_PLAYING; - if (audio_playing) { - SDL_PauseAudioDevice(deviceId, 1); - } - else if (!audio_playing) { - SDL_PauseAudioDevice(deviceId, 0); + bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; + if (audio_playing) { + SDL_PauseAudioDevice(device_id, 1); + } + else if (!audio_playing) { + SDL_PauseAudioDevice(device_id, 0); + } } break; @@ -243,7 +244,6 @@ static void handle_events(GB_gameboy_t *gb) } } break; - } } case SDL_KEYUP: // Fallthrough if (event.key.keysym.scancode == configuration.keys[8]) { @@ -475,7 +475,6 @@ int main(int argc, char **argv) want_aspec.freq = AUDIO_FREQUENCY; want_aspec.format = AUDIO_S16SYS; want_aspec.channels = 2; - printf("SDL version is %d\n", SDL_COMPILEDVERSION); #if SDL_COMPILEDVERSION >= 2005 && defined(__APPLE__) /* SDL 2.0.5 on macOS introduced a bug where certain combinations of buffer lengths and frequencies fail to produce audio correctly. */ @@ -487,20 +486,18 @@ int main(int argc, char **argv) #if SDL_COMPILEDVERSION >= 2006 && defined(_WIN32) /* SDL 2.0.6 offers WASAPI support which allows for much lower audio buffer lengths which at least theoretically reduces lagging. */ - printf("SDL 2.0.6+ detected, reducing audio buffer to 32 samples\n"); want_aspec.samples = 32; #endif #if SDL_COMPILEDVERSION <= 2005 && defined(_WIN32) /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency to 44100 because otherwise we would get garbled audio output.*/ - printf("Fallback: SDL 2.0.5 detected, lowering audio freqency to 44100\n"); want_aspec.freq = 44100; #endif want_aspec.callback = audio_callback; want_aspec.userdata = &gb; - deviceId = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); /* Start Audio */ @@ -526,7 +523,7 @@ int main(int argc, char **argv) if (filename == NULL) { run_gui(false); } - SDL_PauseAudioDevice(deviceId, 0); + SDL_PauseAudioDevice(device_id, 0); run(); // Never returns return 0; } From 1fcde88d8a69d4a4ccc0c4c2d9b4a8f35131d625 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 May 2018 22:13:52 +0300 Subject: [PATCH 0652/1216] Improved accuracy of the halt bug --- Core/gb.h | 1 + Core/z80_cpu.c | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index de08bfc7..be2c68b2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -269,6 +269,7 @@ struct GB_gameboy_internal_s { bool boot_rom_finished; bool ime_toggle; /* ei has delayed a effect.*/ bool halt_bug; + bool just_halted; /* Misc state */ bool infrared_input; diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 3d35cead..a00ad3b0 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -750,12 +750,26 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) static void halt(GB_gameboy_t *gb, uint8_t opcode) { + assert(gb->pending_cycles == 4); + gb->pending_cycles = 0; + GB_advance_cycles(gb, 1); + GB_advance_cycles(gb, 1); + GB_advance_cycles(gb, 1); + GB_advance_cycles(gb, 1); + gb->halted = true; /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ - if (!gb->ime && (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0) { - gb->halted = false; - gb->halt_bug = true; + if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) { + if (gb->ime) { + gb->halted = false; + gb->pc--; + } + else { + gb->halted = false; + gb->halt_bug = true; + } } + gb->just_halted = true; } static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -1321,15 +1335,16 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } - if (gb->halted && !gb->is_cgb) { + if (gb->halted && !gb->is_cgb && !gb->just_halted) { GB_advance_cycles(gb, 2); } uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; if (gb->halted) { - GB_advance_cycles(gb, gb->is_cgb? 4 : 2); + GB_advance_cycles(gb, (gb->is_cgb || gb->just_halted) ? 4 : 2); } + gb->just_halted = false; bool effecitve_ime = gb->ime; if (gb->ime_toggle) { From 7df571d42f78b57b651ff8d0f873fbab8e84ff4c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 13 May 2018 23:17:23 +0300 Subject: [PATCH 0653/1216] Less strict matching for `delete` and `unwatch`. Fixes #71 --- Core/debugger.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 02cf6d63..a843f9bc 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -929,16 +929,32 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb if (error) return true; - uint16_t index = find_breakpoint(gb, result); - if (index >= gb->n_breakpoints || gb->breakpoints[index].key != key) { + uint16_t index = 0; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (gb->breakpoints[i].key == key) { + /* Full match */ + index = i; + break; + } + if (gb->breakpoints[i].addr == result.value && result.has_bank != (gb->breakpoints[i].bank != (uint16_t) -1)) { + /* Partial match */ + index = i; + } + } + + if (index >= gb->n_breakpoints) { GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } + result.bank = gb->breakpoints[index].bank; + result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1; + if (gb->breakpoints[index].condition) { free(gb->breakpoints[index].condition); } + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); @@ -1086,11 +1102,26 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de if (error) return true; - uint16_t index = find_watchpoint(gb, result); - if (index >= gb->n_watchpoints || gb->watchpoints[index].key != key) { + uint16_t index = 0; + for (unsigned i = 0; i < gb->n_watchpoints; i++) { + if (gb->watchpoints[i].key == key) { + /* Full match */ + index = i; + break; + } + if (gb->watchpoints[i].addr == result.value && result.has_bank != (gb->watchpoints[i].bank != (uint16_t) -1)) { + /* Partial match */ + index = i; + } + } + + if (index >= gb->n_watchpoints) { GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } + + result.bank = gb->watchpoints[index].bank; + result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1; if (gb->watchpoints[index].condition) { free(gb->watchpoints[index].condition); From 562b43a7c560a760ba55ed5990588ddbfa5d4f09 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 15 May 2018 23:02:07 +0300 Subject: [PATCH 0654/1216] Notes about the DMG wave-ram glitch --- Core/apu.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 4d3d9523..703355ac 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -691,8 +691,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.enable) { unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; - /* On SGB2 (and probably SGB1 and MGB as well) this behavior is not accurate, - however these systems are not currently emulated. */ + /* This glitch varies between models and even specific instances: + DMG-B: Most of them behave as emulated. A few behave differently. + SGB: As far as I know, all tested instances behave as emulated. + MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated. + + Additionally, I believe DMGs, including those we behave differently than emulated, + are all deterministic. */ if (offset < 4) { gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2]; From 249acb04cc59eeefd215eded0c69a19740e39058 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 16 May 2018 00:59:11 +0300 Subject: [PATCH 0655/1216] Verified some timings on a DMG. Fixed palette write conflict timing (Although the fix kind of implies time traveling). Closes #65 --- Core/z80_cpu.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index a00ad3b0..6f179178 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -28,21 +28,19 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, - + [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_READ_NEW, + /* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */ [GB_IO_BGP] = GB_CONFLICT_READ_OR, [GB_IO_OBP0] = GB_CONFLICT_READ_OR, [GB_IO_OBP1] = GB_CONFLICT_READ_OR, - - /* Todo: These were verified on an SGB2 */ - [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, - [GB_IO_STAT] = GB_CONFLICT_READ_NEW, - [GB_IO_SCY] = GB_CONFLICT_READ_NEW, - [GB_IO_SCX] = GB_CONFLICT_READ_NEW, /* Todo: these were not verified at all */ [GB_IO_WY] = GB_CONFLICT_READ_NEW, [GB_IO_WX] = GB_CONFLICT_READ_NEW, + [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) @@ -87,12 +85,12 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_CONFLICT_READ_OR: { - GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_advance_cycles(gb, gb->pending_cycles - 2); uint8_t old_value = GB_read_memory(gb, addr); GB_write_memory(gb, addr, value | old_value); GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); - gb->pending_cycles = 4; + gb->pending_cycles = 5; return; case GB_CONFLICT_WRITE_CPU: From 855ffb490af34b5407443d769be8129685f343c3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 25 May 2018 23:42:36 +0300 Subject: [PATCH 0656/1216] =?UTF-8?q?A=20HBlank=20interrupt=20cannot=20occ?= =?UTF-8?q?ur=20in=20the=20last=20M-cycle=20of=20HBlank.=20Correct=20emula?= =?UTF-8?q?tion=20of=20STAT=20access=20conflicts=20on=20the=20CGB=20(Test:?= =?UTF-8?q?=20CPU-E,=20single=20speed=20only).=20Fixes=20a=20minor=20graph?= =?UTF-8?q?ical=20glitch=20in=20Pok=C3=A9mon=20Puzzle=20Challenge.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 13 +++++++++---- Core/gb.h | 2 +- Core/memory.c | 3 ++- Core/z80_cpu.c | 14 +++++++++++++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Core/display.c b/Core/display.c index c82a2f02..3ebf48de 100644 --- a/Core/display.c +++ b/Core/display.c @@ -84,7 +84,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe /* - Each line is 456 cycles. Without scrolling, sprites or a window:: + Each line is 456 cycles. Without scrolling, sprites or a window: Mode 2 - 80 cycles / OAM Transfer Mode 3 - 172 cycles / Rendering Mode 0 - 204 cycles / HBlank @@ -260,7 +260,7 @@ void GB_STAT_update(GB_gameboy_t *gb) } switch (gb->io_registers[GB_IO_STAT] & 3) { - case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->is_first_line_mode2; break; + case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->mode_0_interrupt_disable; break; case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; /* The OAM interrupt is handled differently, it reads the writable flags from STAT less frequenctly, and is not based on the mode bits of STAT. */ @@ -582,8 +582,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 23, 1); } + /* Todo: Merge this with the normal line routine */ /* Handle the very first line 0 */ - gb->is_first_line_mode2 = true; + gb->mode_0_interrupt_disable = true; gb->current_line = 0; gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; @@ -601,7 +602,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = !gb->is_cgb; gb->oam_write_blocked = true; gb->vram_write_blocked = !gb->is_cgb; - gb->is_first_line_mode2 = false; + gb->mode_0_interrupt_disable = false; GB_STAT_update(gb); gb->cycles_for_line += 2; @@ -632,6 +633,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Mode 0 is shorter in the very first line */ GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); + gb->mode_0_interrupt_disable = true; gb->current_line = 1; while (true) { /* Lines 0 - 143 */ @@ -656,6 +658,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 7, 1); + gb->mode_0_interrupt_disable = false; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; gb->oam_write_blocked = true; @@ -820,6 +823,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->hdma_starting = true; } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); + gb->mode_0_interrupt_disable = true; } /* Lines 144 - 152 */ @@ -842,6 +846,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 1; gb->io_registers[GB_IO_IF] |= 1; + gb->mode_0_interrupt_disable = false; trigger_oam_interrupt(gb); GB_STAT_update(gb); gb->oam_interrupt_line = false; diff --git a/Core/gb.h b/Core/gb.h index be2c68b2..cce91207 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -422,7 +422,7 @@ struct GB_gameboy_internal_s { uint8_t oam_search_index; uint8_t accessed_oam_row; uint8_t extra_penalty_for_sprite_at_0; - bool is_first_line_mode2; + bool mode_0_interrupt_disable; bool oam_interrupt_line; bool lyc_interrupt_line; ); diff --git a/Core/memory.c b/Core/memory.c index 1fbd3316..254417c3 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -665,7 +665,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_STAT: /* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */ - if (!gb->is_cgb && !gb->stat_interrupt_line && !gb->is_first_line_mode2 && + /* TODO: Confirm gb->mode_0_interrupt_disable usage */ + if (!gb->is_cgb && !gb->stat_interrupt_line && !gb->mode_0_interrupt_disable && (gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->io_registers[GB_IO_IF] |= 2; } diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 6f179178..5119ff77 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -15,12 +15,15 @@ typedef enum { GB_CONFLICT_READ_OR, /* If the CPU and another component write at the same time, the CPU's value "wins" */ GB_CONFLICT_WRITE_CPU, + /* Register specific values */ + GB_CONFLICT_STAT_CGB, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, + [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, /* Todo: most values not verified, and probably differ between revisions */ }; @@ -92,14 +95,23 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_write_memory(gb, addr, value); gb->pending_cycles = 5; return; + } case GB_CONFLICT_WRITE_CPU: GB_advance_cycles(gb, gb->pending_cycles + 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; return; + + case GB_CONFLICT_STAT_CGB: { + /* The LYC bit behaves differently */ + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, (old_value & 0x40) | (value & ~0x40)); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; } - } } From 9693b2de6a08ef75dbabb3c4fffc56e9b11b19a2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 May 2018 17:06:49 +0300 Subject: [PATCH 0657/1216] Refined the STAT bug behavior. Still not perfect. --- Core/display.c | 1 + Core/memory.c | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 3ebf48de..eb92cb06 100644 --- a/Core/display.c +++ b/Core/display.c @@ -610,6 +610,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = true; gb->vram_write_blocked = true; + /* TODO: How does the window affect this line? */ gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2; GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2); diff --git a/Core/memory.c b/Core/memory.c index 254417c3..1cb2eb97 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -665,9 +665,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_STAT: /* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */ - /* TODO: Confirm gb->mode_0_interrupt_disable usage */ - if (!gb->is_cgb && !gb->stat_interrupt_line && !gb->mode_0_interrupt_disable && - (gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) { + if (!gb->is_cgb && /* Only occurs on DMGs */ + (gb->io_registers[GB_IO_LCDC] & 0x80) && /* LCD must be on */ + !gb->stat_interrupt_line && /* The interrupt line must be off */ + gb->display_state != 2 && /* State 2 is line 0's faux Mode 2 */ + !gb->oam_read_blocked /* OAM must be readable (Modes 0 and 1) */ + ) { gb->io_registers[GB_IO_IF] |= 2; } /* Delete previous R/W bits */ From 6532aef089dc7a434b982eb59248682e5a714db8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 May 2018 18:06:40 +0300 Subject: [PATCH 0658/1216] Correct emulation of the DMG stat write bug --- Core/memory.c | 9 --------- Core/z80_cpu.c | 12 +++++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 1cb2eb97..f280c94a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -664,15 +664,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_STAT: - /* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */ - if (!gb->is_cgb && /* Only occurs on DMGs */ - (gb->io_registers[GB_IO_LCDC] & 0x80) && /* LCD must be on */ - !gb->stat_interrupt_line && /* The interrupt line must be off */ - gb->display_state != 2 && /* State 2 is line 0's faux Mode 2 */ - !gb->oam_read_blocked /* OAM must be readable (Modes 0 and 1) */ - ) { - gb->io_registers[GB_IO_IF] |= 2; - } /* Delete previous R/W bits */ gb->io_registers[GB_IO_STAT] &= 7; /* Set them by value */ diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 5119ff77..90c50f5a 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -17,6 +17,7 @@ typedef enum { GB_CONFLICT_WRITE_CPU, /* Register specific values */ GB_CONFLICT_STAT_CGB, + GB_CONFLICT_STAT_DMG, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -33,7 +34,7 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_LYC] = GB_CONFLICT_READ_OLD, [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, [GB_IO_SCY] = GB_CONFLICT_READ_NEW, - [GB_IO_STAT] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, /* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */ [GB_IO_BGP] = GB_CONFLICT_READ_OR, @@ -112,6 +113,15 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_write_memory(gb, addr, value); gb->pending_cycles = 3; } + + /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle*/ + case GB_CONFLICT_STAT_DMG: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, 0xFF); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + return; } } From 80c92daacd0d71c5a6112edeccf9061c2e7a304c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 27 May 2018 19:30:23 +0300 Subject: [PATCH 0659/1216] Include cleanup (#73) --- Core/apu.h | 1 + Core/display.h | 3 +++ Core/joypad.h | 1 + Core/mbc.h | 1 + Core/memory.h | 3 ++- Core/rewind.c | 5 ++++- Core/rewind.h | 3 ++- Core/symbol_hash.c | 4 ++++ Core/symbol_hash.h | 1 + Core/timing.h | 2 +- Core/z80_cpu.h | 3 ++- SDL/gui.c | 6 +++++- SDL/gui.h | 2 ++ SDL/utils.h | 1 + 14 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Core/apu.h b/Core/apu.h index fd947f77..f75d25e8 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -2,6 +2,7 @@ #define apu_h #include #include +#include #include "gb_struct_def.h" diff --git a/Core/display.h b/Core/display.h index 0546fda5..9d1d1b47 100644 --- a/Core/display.h +++ b/Core/display.h @@ -2,6 +2,9 @@ #define display_h #include "gb.h" +#include +#include + #ifdef GB_INTERNAL void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); diff --git a/Core/joypad.h b/Core/joypad.h index def4b9ac..83d3734f 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -1,6 +1,7 @@ #ifndef joypad_h #define joypad_h #include "gb_struct_def.h" +#include typedef enum { GB_KEY_RIGHT, diff --git a/Core/mbc.h b/Core/mbc.h index e7260f56..7e9b47f0 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -1,6 +1,7 @@ #ifndef MBC_h #define MBC_h #include "gb_struct_def.h" +#include typedef struct { enum { diff --git a/Core/memory.h b/Core/memory.h index c000b7a3..03d636d0 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -1,6 +1,7 @@ #ifndef memory_h #define memory_h -#include "gb.h" +#include "gb_struct_def.h" +#include uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); diff --git a/Core/rewind.c b/Core/rewind.c index ae711a2b..c3900d60 100644 --- a/Core/rewind.c +++ b/Core/rewind.c @@ -1,4 +1,7 @@ -#include "rewind.h" +#include "gb.h" +#include +#include +#include #include static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size) diff --git a/Core/rewind.h b/Core/rewind.h index 30d795cc..ad548410 100644 --- a/Core/rewind.h +++ b/Core/rewind.h @@ -1,7 +1,8 @@ #ifndef rewind_h #define rewind_h -#include "gb.h" +#include +#include "gb_struct_def.h" #ifdef GB_INTERNAL void GB_rewind_push(GB_gameboy_t *gb); diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 709421c2..208e72d6 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -1,4 +1,8 @@ #include "gb.h" +#include +#include +#include +#include static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) { diff --git a/Core/symbol_hash.h b/Core/symbol_hash.h index 239b0e31..2a03c96b 100644 --- a/Core/symbol_hash.h +++ b/Core/symbol_hash.h @@ -3,6 +3,7 @@ #include #include +#include typedef struct { char *name; diff --git a/Core/timing.h b/Core/timing.h index 1ea4b8af..02ca54ce 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -1,6 +1,6 @@ #ifndef timing_h #define timing_h -#include "gb.h" +#include "gb_struct_def.h" #ifdef GB_INTERNAL void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); diff --git a/Core/z80_cpu.h b/Core/z80_cpu.h index 1434ed7b..3541fa81 100644 --- a/Core/z80_cpu.h +++ b/Core/z80_cpu.h @@ -1,6 +1,7 @@ #ifndef z80_cpu_h #define z80_cpu_h -#include "gb.h" +#include "gb_struct_def.h" +#include void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); #ifdef GB_INTERNAL diff --git a/SDL/gui.c b/SDL/gui.c index e3b72069..86517647 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include #include "utils.h" #include "gui.h" #include "font.h" @@ -48,7 +51,8 @@ void render_texture(void *pixels, void *previous) configuration_t configuration = { - .keys = { SDL_SCANCODE_RIGHT, + .keys = { + SDL_SCANCODE_RIGHT, SDL_SCANCODE_LEFT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, diff --git a/SDL/gui.h b/SDL/gui.h index 2c99b9fd..692c7666 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -3,8 +3,10 @@ #include #include +#include #include "shader.h" + extern SDL_Window *window; extern SDL_Renderer *renderer; extern SDL_Texture *texture; diff --git a/SDL/utils.h b/SDL/utils.h index 21e900a7..7995da90 100644 --- a/SDL/utils.h +++ b/SDL/utils.h @@ -1,5 +1,6 @@ #ifndef utils_h #define utils_h +#include const char *executable_folder(void); char *executable_relative_path(const char *filename); From 7003e31b7e29911c54fd8efc36ecba48efec9ba2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Jun 2018 04:00:10 +0300 Subject: [PATCH 0660/1216] Fixed a regression with STAT blocking. --- Core/display.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index eb92cb06..b65381d3 100644 --- a/Core/display.c +++ b/Core/display.c @@ -535,6 +535,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state &= 7; } +/* + TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles. + The PPU logic can be greatly simplified if that delay is simply emulated. + */ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { GB_object_t *objects = (GB_object_t *) &gb->oam; @@ -634,7 +638,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Mode 0 is shorter in the very first line */ GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); - gb->mode_0_interrupt_disable = true; + gb->mode_0_interrupt_disable = !(gb->io_registers[GB_IO_STAT] & 0x20); gb->current_line = 1; while (true) { /* Lines 0 - 143 */ @@ -824,7 +828,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->hdma_starting = true; } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); - gb->mode_0_interrupt_disable = true; + /* Todo: The last cycle of move 0 can't trigger an interrupt... unless OAM interrupt is requested? + This doesn't make too much sense. */ + gb->mode_0_interrupt_disable = !(gb->io_registers[GB_IO_STAT] & 0x20); } /* Lines 144 - 152 */ From 8721a482064e44937a35a2e778e6d560d5d3f7e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Jun 2018 00:36:05 +0300 Subject: [PATCH 0661/1216] Fixed incorrect double speed behavior. --- Core/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index b65381d3..85816221 100644 --- a/Core/display.c +++ b/Core/display.c @@ -651,8 +651,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->ly_for_comparison = gb->current_line? -1 : 0; /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. - PPU glitch? (Todo: and in double speed mode?) */ - if (gb->current_line != 0 && !gb->cgb_double_speed) { + PPU glitch. */ + if (gb->current_line != 0) { gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; } trigger_oam_interrupt(gb); From 6f1070cccd4fb9731d3fda8b1c236245a9e3a694 Mon Sep 17 00:00:00 2001 From: Nadia Pedersen Date: Sun, 3 Jun 2018 00:21:43 +0200 Subject: [PATCH 0662/1216] SDL: Add controller hat support in-game and in the GUI. --- SDL/gui.c | 17 +++++++++++++++++ SDL/main.c | 15 +++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/SDL/gui.c b/SDL/gui.c index 86517647..3e0151b6 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -729,6 +729,23 @@ void run_gui(bool is_running) else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) event.key.keysym.scancode = SDL_SCANCODE_LEFT; else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; break; + + case SDL_JOYHATMOTION: { + uint8_t value = event.jhat.value; + if (value != 0) { + uint32_t scancode = + value == SDL_HAT_UP ? SDL_SCANCODE_UP + : value == SDL_HAT_DOWN ? SDL_SCANCODE_DOWN + : value == SDL_HAT_LEFT ? SDL_SCANCODE_LEFT + : value == SDL_HAT_RIGHT ? SDL_SCANCODE_RIGHT + : 0; + + if (scancode != 0) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = scancode; + } + } + } case SDL_JOYAXISMOTION: { static bool axis_active[2] = {false, false}; diff --git a/SDL/main.c b/SDL/main.c index e25c7b28..f3ee39e7 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -165,6 +165,21 @@ static void handle_events(GB_gameboy_t *gb) GB_set_key_state(gb, GB_KEY_LEFT, event.jaxis.value < -0x4000); } break; + + case SDL_JOYHATMOTION: + { + uint8_t value = event.jhat.value; + int8_t updown = + value == SDL_HAT_LEFTUP || value == SDL_HAT_UP || value == SDL_HAT_RIGHTUP ? -1 : (value == SDL_HAT_LEFTDOWN || value == SDL_HAT_DOWN || value == SDL_HAT_RIGHTDOWN ? 1 : 0); + int8_t leftright = + value == SDL_HAT_LEFTUP || value == SDL_HAT_LEFT || value == SDL_HAT_LEFTDOWN ? -1 : (value == SDL_HAT_RIGHTUP || value == SDL_HAT_RIGHT || value == SDL_HAT_RIGHTDOWN ? 1 : 0); + + GB_set_key_state(gb, GB_KEY_LEFT, leftright == -1); + GB_set_key_state(gb, GB_KEY_RIGHT, leftright == 1); + GB_set_key_state(gb, GB_KEY_UP, updown == -1); + GB_set_key_state(gb, GB_KEY_DOWN, updown == 1); + break; + }; case SDL_KEYDOWN: switch (event.key.keysym.scancode) { From 0481ff9af55fd132d2c4a6a69770e5ed57f634c6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 Jun 2018 01:52:24 +0300 Subject: [PATCH 0663/1216] Whoops --- Core/z80_cpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 90c50f5a..86b479eb 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -112,6 +112,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; + return; } /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle*/ From f1dfa2a1bc6dc0ae37fd7acf47c29b14f6cf74db Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 4 Jun 2018 02:07:38 +0300 Subject: [PATCH 0664/1216] More sensible implementation of the STAT interrupt. --- Core/display.c | 68 +++++++++++++++++++------------------------------- Core/gb.h | 3 +-- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/Core/display.c b/Core/display.c index 85816221..24723bab 100644 --- a/Core/display.c +++ b/Core/display.c @@ -230,21 +230,12 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m */ -static void trigger_oam_interrupt(GB_gameboy_t *gb) -{ - if (!gb->stat_interrupt_line && gb->oam_interrupt_line) { - gb->io_registers[GB_IO_IF] |= 2; - gb->stat_interrupt_line = true; - } -} - /* Todo: When the CPU and PPU write to IF at the same T-cycle, the PPU write is ignored. */ void GB_STAT_update(GB_gameboy_t *gb) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; - bool previous_interrupt_line = gb->stat_interrupt_line | gb->oam_interrupt_line; - gb->stat_interrupt_line = gb->oam_interrupt_line; + bool previous_interrupt_line = gb->stat_interrupt_line; /* Set LY=LYC bit */ if (gb->ly_for_comparison != (uint16_t)-1 || !gb->is_cgb) { if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { @@ -259,12 +250,11 @@ void GB_STAT_update(GB_gameboy_t *gb) } } - switch (gb->io_registers[GB_IO_STAT] & 3) { - case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->mode_0_interrupt_disable; break; + switch (gb->mode_for_interrupt) { + case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break; case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; - /* The OAM interrupt is handled differently, it reads the writable flags from STAT less frequenctly, - and is not based on the mode bits of STAT. */ - // case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + default: gb->stat_interrupt_line = false; } /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ @@ -588,10 +578,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: Merge this with the normal line routine */ /* Handle the very first line 0 */ - gb->mode_0_interrupt_disable = true; gb->current_line = 0; gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = -1; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; @@ -602,11 +592,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; + gb->mode_for_interrupt = 3; gb->oam_read_blocked = true; gb->vram_read_blocked = !gb->is_cgb; gb->oam_write_blocked = true; gb->vram_write_blocked = !gb->is_cgb; - gb->mode_0_interrupt_disable = false; GB_STAT_update(gb); gb->cycles_for_line += 2; @@ -620,6 +610,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (!gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; @@ -629,6 +620,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 4, 1); gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; @@ -638,7 +630,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Mode 0 is shorter in the very first line */ GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); - gb->mode_0_interrupt_disable = !(gb->io_registers[GB_IO_STAT] & 0x20); + gb->mode_for_interrupt = 2; gb->current_line = 1; while (true) { /* Lines 0 - 143 */ @@ -653,23 +645,23 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. PPU glitch. */ if (gb->current_line != 0) { - gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; - } - trigger_oam_interrupt(gb); - GB_STAT_update(gb); - if (gb->current_line != 0 || !gb->is_cgb) { + gb->mode_for_interrupt = 2; gb->io_registers[GB_IO_STAT] &= ~3; } + else if (!gb->is_cgb) { + gb->io_registers[GB_IO_STAT] &= ~3; + } + GB_STAT_update(gb); GB_SLEEP(gb, display, 7, 1); - gb->mode_0_interrupt_disable = false; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; + gb->mode_for_interrupt = 2; gb->oam_write_blocked = true; gb->ly_for_comparison = gb->current_line; - gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; - trigger_oam_interrupt(gb); + GB_STAT_update(gb); + gb->mode_for_interrupt = -1; GB_STAT_update(gb); gb->n_visible_objs = 0; @@ -693,11 +685,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->accessed_oam_row = -1; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; + gb->mode_for_interrupt = 3; gb->vram_read_blocked = true; gb->vram_write_blocked = true; gb->oam_write_blocked = true; - gb->oam_interrupt_line = false; - trigger_oam_interrupt(gb); GB_STAT_update(gb); gb->cycles_for_line = MODE2_LENGTH + 4; @@ -803,6 +794,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } if (!gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; @@ -813,6 +805,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 22, 1); gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; gb->oam_read_blocked = false; gb->vram_read_blocked = false; gb->oam_write_blocked = false; @@ -828,9 +821,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->hdma_starting = true; } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); - /* Todo: The last cycle of move 0 can't trigger an interrupt... unless OAM interrupt is requested? - This doesn't make too much sense. */ - gb->mode_0_interrupt_disable = !(gb->io_registers[GB_IO_STAT] & 0x20); + gb->mode_for_interrupt = 2; } /* Lines 144 - 152 */ @@ -839,28 +830,21 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->ly_for_comparison = -1; GB_SLEEP(gb, display, 26, 2); if (gb->current_line == LINES) { - gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; - trigger_oam_interrupt(gb); + gb->mode_for_interrupt = 2; } GB_STAT_update(gb); - gb->oam_interrupt_line = false; GB_SLEEP(gb, display, 12, 2); gb->ly_for_comparison = gb->current_line; if (gb->current_line == LINES) { /* Entering VBlank state triggers the OAM interrupt */ - gb->oam_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 1; gb->io_registers[GB_IO_IF] |= 1; - gb->mode_0_interrupt_disable = false; - trigger_oam_interrupt(gb); + gb->mode_for_interrupt = 2; + GB_STAT_update(gb); + gb->mode_for_interrupt = 1; GB_STAT_update(gb); - gb->oam_interrupt_line = false; - - if (gb->io_registers[GB_IO_STAT] & 0x20) { - gb->stat_interrupt_line = true; - } if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { display_vblank(gb); diff --git a/Core/gb.h b/Core/gb.h index cce91207..aa16ede3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -422,8 +422,7 @@ struct GB_gameboy_internal_s { uint8_t oam_search_index; uint8_t accessed_oam_row; uint8_t extra_penalty_for_sprite_at_0; - bool mode_0_interrupt_disable; - bool oam_interrupt_line; + uint8_t mode_for_interrupt; bool lyc_interrupt_line; ); From 127324d2d670f29387b87e959f661295f7a595e4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 7 Jun 2018 23:08:46 +0300 Subject: [PATCH 0665/1216] Fixed regression involving rendering a window with negative X position. Closes #75 --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 24723bab..20249049 100644 --- a/Core/display.c +++ b/Core/display.c @@ -776,7 +776,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: Timing (Including penalty and access timings) not verified by test ROM */ if (!gb->in_window && window_enabled(gb) && gb->current_line >= gb->io_registers[GB_IO_WY] + gb->wy_diff && - gb->position_in_line + 7 == gb->io_registers[GB_IO_WX]) { + (uint8_t)(gb->position_in_line + 7) == gb->io_registers[GB_IO_WX]) { gb->in_window = true; fifo_clear(&gb->bg_fifo); gb->bg_fifo_paused = true; From ca01ff6f798363429b3001511250e10f7877c38d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Jun 2018 17:16:15 +0300 Subject: [PATCH 0666/1216] Finally, perfect emulation of the STAT write bug. --- Core/display.c | 3 +-- Core/z80_cpu.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 20249049..20580dee 100644 --- a/Core/display.c +++ b/Core/display.c @@ -230,7 +230,6 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m */ -/* Todo: When the CPU and PPU write to IF at the same T-cycle, the PPU write is ignored. */ void GB_STAT_update(GB_gameboy_t *gb) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; @@ -643,7 +642,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->ly_for_comparison = gb->current_line? -1 : 0; /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. - PPU glitch. */ + PPU glitch? */ if (gb->current_line != 0) { gb->mode_for_interrupt = 2; gb->io_registers[GB_IO_STAT] &= ~3; diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 86b479eb..693804a0 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -115,10 +115,19 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle*/ + /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ case GB_CONFLICT_STAT_DMG: GB_advance_cycles(gb, gb->pending_cycles); - GB_write_memory(gb, addr, 0xFF); + /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird. + The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite + the timing not making much sense for that. + This is a hack to simulate this effect */ + if (gb->display_state == 7 && (gb->io_registers[GB_IO_STAT] & 0x28) == 0x08) { + GB_write_memory(gb, addr, ~0x20); + } + else { + GB_write_memory(gb, addr, 0xFF); + } GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; From 593cb7c1072903e2abe5dab9ee0d0ab43106757c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Jun 2018 18:44:03 +0300 Subject: [PATCH 0667/1216] Pixel accurate emulation of Prehistorik Man on a CGB-CPU-E --- Core/z80_cpu.c | 76 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 693804a0..721008fb 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -11,13 +11,13 @@ typedef enum { GB_CONFLICT_READ_OLD, /* If the CPU writes while another component reads, it reads the new value */ GB_CONFLICT_READ_NEW, - /* If the CPU writes while another component reads, it reads a bitwise OR between the new and old values */ - GB_CONFLICT_READ_OR, /* If the CPU and another component write at the same time, the CPU's value "wins" */ GB_CONFLICT_WRITE_CPU, /* Register specific values */ GB_CONFLICT_STAT_CGB, GB_CONFLICT_STAT_DMG, + GB_CONFLICT_PALETTE_DMG, + GB_CONFLICT_PALETTE_CGB, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -25,6 +25,10 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, + [GB_IO_BGP] = GB_CONFLICT_PALETTE_CGB, + [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB, + [GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB, + /* Todo: most values not verified, and probably differ between revisions */ }; @@ -37,9 +41,9 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, /* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */ - [GB_IO_BGP] = GB_CONFLICT_READ_OR, - [GB_IO_OBP0] = GB_CONFLICT_READ_OR, - [GB_IO_OBP1] = GB_CONFLICT_READ_OR, + [GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG, + [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG, + [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG, /* Todo: these were not verified at all */ [GB_IO_WY] = GB_CONFLICT_READ_NEW, @@ -88,22 +92,30 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->pending_cycles = 5; return; - case GB_CONFLICT_READ_OR: { - GB_advance_cycles(gb, gb->pending_cycles - 2); - uint8_t old_value = GB_read_memory(gb, addr); - GB_write_memory(gb, addr, value | old_value); - GB_advance_cycles(gb, 1); - GB_write_memory(gb, addr, value); - gb->pending_cycles = 5; - return; - } - case GB_CONFLICT_WRITE_CPU: GB_advance_cycles(gb, gb->pending_cycles + 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; return; - + + /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ + case GB_CONFLICT_STAT_DMG: + GB_advance_cycles(gb, gb->pending_cycles); + /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird. + The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite + the timing not making much sense for that. + This is a hack to simulate this effect */ + if (gb->display_state == 7 && (gb->io_registers[GB_IO_STAT] & 0x28) == 0x08) { + GB_write_memory(gb, addr, ~0x20); + } + else { + GB_write_memory(gb, addr, 0xFF); + } + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + return; + case GB_CONFLICT_STAT_CGB: { /* The LYC bit behaves differently */ uint8_t old_value = GB_read_memory(gb, addr); @@ -115,23 +127,27 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ - case GB_CONFLICT_STAT_DMG: - GB_advance_cycles(gb, gb->pending_cycles); - /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird. - The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite - the timing not making much sense for that. - This is a hack to simulate this effect */ - if (gb->display_state == 7 && (gb->io_registers[GB_IO_STAT] & 0x28) == 0x08) { - GB_write_memory(gb, addr, ~0x20); - } - else { - GB_write_memory(gb, addr, 0xFF); - } + /* There is some "time travel" going on with these two values, as it appears + that there's some off-by-1-T-cycle timing issue in the PPU implementation. + + This is should be accurate for every measureable scenario, though. */ + + case GB_CONFLICT_PALETTE_DMG: { + GB_advance_cycles(gb, gb->pending_cycles - 2); + uint8_t old_value = GB_read_memory(gb, addr); + GB_write_memory(gb, addr, value | old_value); GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); - gb->pending_cycles = 3; + gb->pending_cycles = 5; return; + } + + case GB_CONFLICT_PALETTE_CGB: { + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 6; + return; + } } } From f64da1864f5e3d710c0f9d1c531d1f59bf80b3d2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jun 2018 15:11:20 +0300 Subject: [PATCH 0668/1216] APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode), the first DIV/APU event is skipped. --- Core/apu.c | 9 +++++++++ Core/apu.h | 2 ++ Core/gb.h | 2 +- Core/timing.c | 2 ++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 703355ac..cb462d76 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -181,6 +181,10 @@ static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) void GB_apu_div_event(GB_gameboy_t *gb) { if (!gb->apu.global_enable) return; + if (gb->apu.skip_div_event) { + gb->apu.skip_div_event = false; + return; + } gb->apu.div_divider++; if ((gb->apu.div_divider & 7) == 0) { @@ -426,6 +430,11 @@ void GB_apu_init(GB_gameboy_t *gb) { memset(&gb->apu, 0, sizeof(gb->apu)); gb->apu.lf_div = 1; + /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode), the + first DIV/APU event is skipped. */ + if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { + gb->apu.skip_div_event = true; + } } uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) diff --git a/Core/apu.h b/Core/apu.h index f75d25e8..59d0e360 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -108,6 +108,8 @@ typedef struct } noise_channel; + bool skip_div_event; + } GB_apu_t; typedef enum { diff --git a/Core/gb.h b/Core/gb.h index aa16ede3..afd5c275 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -350,7 +350,7 @@ struct GB_gameboy_internal_s { GB_SECTION(timing, GB_UNIT(display); GB_UNIT(div); - uint32_t div_counter; + uint16_t div_counter; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; uint16_t serial_length; diff --git a/Core/timing.c b/Core/timing.c index 6132f40d..58177d5c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -130,6 +130,8 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) counter_overflow_check(gb->div_counter, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); } + + /* TODO: Can switching to double speed mode trigger an event? */ if (counter_overflow_check(gb->div_counter, value, gb->cgb_double_speed? 0x4000 : 0x2000)) { GB_apu_run(gb); GB_apu_div_event(gb); From 38c0cb3323a2b0581deb9fc8f9a7e8ad5e7956de Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jun 2018 15:12:42 +0300 Subject: [PATCH 0669/1216] Typo --- Core/apu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index cb462d76..4f8f5c6b 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -430,8 +430,8 @@ void GB_apu_init(GB_gameboy_t *gb) { memset(&gb->apu, 0, sizeof(gb->apu)); gb->apu.lf_div = 1; - /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode), the - first DIV/APU event is skipped. */ + /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, + the first DIV/APU event is skipped. */ if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { gb->apu.skip_div_event = true; } From d95ad1ca54c6d40fd7a0868056b2412bab38dbcb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jun 2018 15:39:40 +0300 Subject: [PATCH 0670/1216] SWAP was incorrectly disassembled as RLC --- Core/z80_disassembler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/z80_disassembler.c b/Core/z80_disassembler.c index d45cbe70..96aec000 100644 --- a/Core/z80_disassembler.c +++ b/Core/z80_disassembler.c @@ -662,7 +662,7 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) static void swap_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; - GB_log(gb, "RLC %s\n", get_src_name(opcode)); + GB_log(gb, "SWAP %s\n", get_src_name(opcode)); } static void bit_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) From 9a3d53ae510d48b9573fb26180becc757188c3dd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 11 Jun 2018 20:23:51 +0300 Subject: [PATCH 0671/1216] Remove OpenGL specific code from GBView --- Cocoa/Document.xib | 4 +- Cocoa/{GBShader.h => GBGLShader.h} | 2 +- Cocoa/{GBShader.m => GBGLShader.m} | 4 +- Cocoa/GBOpenGLView.h | 6 +++ Cocoa/GBOpenGLView.m | 42 ++++++++++++++++ Cocoa/GBView.h | 8 +-- Cocoa/GBView.m | 80 +++++++++++------------------- Cocoa/GBViewGL.h | 5 ++ Cocoa/GBViewGL.m | 26 ++++++++++ 9 files changed, 119 insertions(+), 58 deletions(-) rename Cocoa/{GBShader.h => GBGLShader.h} (85%) rename Cocoa/{GBShader.m => GBGLShader.m} (99%) create mode 100644 Cocoa/GBOpenGLView.h create mode 100644 Cocoa/GBOpenGLView.m create mode 100644 Cocoa/GBViewGL.h create mode 100644 Cocoa/GBViewGL.m diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 18114a39..88c52757 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -54,10 +54,10 @@ - + - + diff --git a/Cocoa/GBShader.h b/Cocoa/GBGLShader.h similarity index 85% rename from Cocoa/GBShader.h rename to Cocoa/GBGLShader.h index d2c44773..76291fb2 100644 --- a/Cocoa/GBShader.h +++ b/Cocoa/GBGLShader.h @@ -1,6 +1,6 @@ #import -@interface GBShader : NSObject +@interface GBGLShader : NSObject - (instancetype)initWithName:(NSString *) shaderName; - (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale; @end diff --git a/Cocoa/GBShader.m b/Cocoa/GBGLShader.m similarity index 99% rename from Cocoa/GBShader.m rename to Cocoa/GBGLShader.m index 63bfaac0..1fa2ba36 100644 --- a/Cocoa/GBShader.m +++ b/Cocoa/GBGLShader.m @@ -1,4 +1,4 @@ -#import "GBShader.h" +#import "GBGLShader.h" #import /* @@ -16,7 +16,7 @@ void main(void) {\n\ }\n\ "; -@implementation GBShader +@implementation GBGLShader { GLuint resolution_uniform; GLuint texture_uniform; diff --git a/Cocoa/GBOpenGLView.h b/Cocoa/GBOpenGLView.h new file mode 100644 index 00000000..5f875ab2 --- /dev/null +++ b/Cocoa/GBOpenGLView.h @@ -0,0 +1,6 @@ +#import +#import "GBGLShader.h" + +@interface GBOpenGLView : NSOpenGLView +@property GBGLShader *shader; +@end diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m new file mode 100644 index 00000000..505f5db5 --- /dev/null +++ b/Cocoa/GBOpenGLView.m @@ -0,0 +1,42 @@ +#import "GBOpenGLView.h" +#import "GBView.h" +#include + +@implementation GBOpenGLView + +- (void)drawRect:(NSRect)dirtyRect { + if (!self.shader) { + self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; + } + + GBView *gbview = (GBView *)self.superview; + double scale = self.window.backingScaleFactor; + glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); + + if (gbview.shouldBlendFrameWithPrevious) { + [self.shader renderBitmap:gbview.currentBuffer + previous:gbview.previousBuffer + inSize:self.bounds.size + scale:scale]; + } + else { + [self.shader renderBitmap:gbview.currentBuffer + previous:NULL + inSize:self.bounds.size + scale:scale]; + } + glFlush(); +} + +- (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; + return [super initWithFrame:frameRect pixelFormat:format]; +} + +- (void) filterChanged +{ + self.shader = nil; + [self setNeedsDisplay:YES]; +} +@end diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index ffd6dc03..c054e09f 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,14 +1,16 @@ #import #include #import "GBJoystickListener.h" -#import "GBShader.h" -@interface GBView : NSOpenGLView +@interface GBView : NSView - (void) flip; - (uint32_t *) pixels; @property GB_gameboy_t *gb; @property (nonatomic) BOOL shouldBlendFrameWithPrevious; -@property GBShader *shader; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property bool isRewinding; +@property NSView *internalView; +- (void) createInternalView; +- (uint32_t *)currentBuffer; +- (uint32_t *)previousBuffer; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index d329328e..92d0d5c5 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -1,6 +1,6 @@ -#import #import #import "GBView.h" +#import "GBViewGL.h" #import "GBButtons.h" #import "NSString+StringForKey.h" @@ -17,29 +17,26 @@ NSEventModifierFlags previousModifiers; } -- (void) awakeFromNib ++ (instancetype)alloc { - NSOpenGLPixelFormatAttribute attrs[] = - { - NSOpenGLPFAOpenGLProfile, - NSOpenGLProfileVersion3_2Core, - 0 - }; - - NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs] ; - - if (!pf) - { - NSLog(@"No OpenGL pixel format"); + if (self == [GBView class]) { + return [GBViewGL alloc]; } - - NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil] ; - - [self setPixelFormat:pf]; - - [self setOpenGLContext:context]; + return [super alloc]; } ++ (instancetype)allocWithZone:(struct _NSZone *)zone +{ + if (self == [GBView class]) { + return [GBViewGL allocWithZone: zone]; + } + return [super allocWithZone:zone]; +} + +- (void) createInternalView +{ + assert(false && "createInternalView must not be inherited"); +} - (void) _init { @@ -47,7 +44,6 @@ image_buffers[1] = malloc(160 * 144 * 4); image_buffers[2] = malloc(160 * 144 * 4); _shouldBlendFrameWithPrevious = 1; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect @@ -55,12 +51,9 @@ userInfo:nil]; [self addTrackingArea:tracking_area]; clockMultiplier = 1.0; -} - -- (void) filterChanged -{ - [self setNeedsDisplay:YES]; - self.shader = nil; + [self createInternalView]; + [self addSubview:self.internalView]; + self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; } - (void) ratioKeepingChanged @@ -132,29 +125,6 @@ [super setFrame:frame]; } -- (void)drawRect:(NSRect)dirtyRect { - if (!self.shader) { - self.shader = [[GBShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; - } - - double scale = self.window.backingScaleFactor; - glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); - - if (_shouldBlendFrameWithPrevious) { - [self.shader renderBitmap:image_buffers[current_buffer] - previous:image_buffers[(current_buffer + 2) % self.numberOfBuffers] - inSize:self.bounds.size - scale:scale]; - } - else { - [self.shader renderBitmap:image_buffers[current_buffer] - previous:NULL - inSize:self.bounds.size - scale:scale]; - } - glFlush(); -} - - (void) flip { if (underclockKeyDown && clockMultiplier > 0.5) { @@ -362,4 +332,14 @@ previousModifiers = event.modifierFlags; } +- (uint32_t *)currentBuffer +{ + return image_buffers[current_buffer]; +} + +- (uint32_t *)previousBuffer +{ + return image_buffers[(current_buffer + 2) % self.numberOfBuffers]; +} + @end diff --git a/Cocoa/GBViewGL.h b/Cocoa/GBViewGL.h new file mode 100644 index 00000000..28db4848 --- /dev/null +++ b/Cocoa/GBViewGL.h @@ -0,0 +1,5 @@ +#import "GBView.h" + +@interface GBViewGL : GBView + +@end diff --git a/Cocoa/GBViewGL.m b/Cocoa/GBViewGL.m new file mode 100644 index 00000000..d678cfe2 --- /dev/null +++ b/Cocoa/GBViewGL.m @@ -0,0 +1,26 @@ +#import "GBViewGL.h" +#import "GBOpenGLView.h" + +@implementation GBViewGL + +- (void)createInternalView +{ + NSOpenGLPixelFormatAttribute attrs[] = + { + NSOpenGLPFAOpenGLProfile, + NSOpenGLProfileVersion3_2Core, + 0 + }; + + NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + + assert(pf); + + NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; + + self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf]; + ((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = YES; + ((GBOpenGLView *)self.internalView).openGLContext = context; +} + +@end From 5b39cacc8a92274ab139342c9f5b9e63512a4212 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 12:58:33 +0300 Subject: [PATCH 0672/1216] Basic Metal support. No OpenGL fallback, no shaders, no blending. --- Cocoa/GBView.m | 8 +-- Cocoa/GBViewMetal.h | 7 ++ Cocoa/GBViewMetal.m | 131 +++++++++++++++++++++++++++++++++++++ Makefile | 8 +-- Shaders/MasterShader.metal | 45 +++++++++++++ 5 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 Cocoa/GBViewMetal.h create mode 100644 Cocoa/GBViewMetal.m create mode 100644 Shaders/MasterShader.metal diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 92d0d5c5..1504cdde 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -1,6 +1,7 @@ #import #import "GBView.h" #import "GBViewGL.h" +#import "GBViewMetal.h" #import "GBButtons.h" #import "NSString+StringForKey.h" @@ -19,16 +20,13 @@ + (instancetype)alloc { - if (self == [GBView class]) { - return [GBViewGL alloc]; - } - return [super alloc]; + return [self allocWithZone:NULL]; } + (instancetype)allocWithZone:(struct _NSZone *)zone { if (self == [GBView class]) { - return [GBViewGL allocWithZone: zone]; + return [GBViewMetal allocWithZone: zone]; } return [super allocWithZone:zone]; } diff --git a/Cocoa/GBViewMetal.h b/Cocoa/GBViewMetal.h new file mode 100644 index 00000000..a28a3431 --- /dev/null +++ b/Cocoa/GBViewMetal.h @@ -0,0 +1,7 @@ +#import +#import +#import "GBView.h" + +@interface GBViewMetal : GBView + +@end diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m new file mode 100644 index 00000000..157ddf37 --- /dev/null +++ b/Cocoa/GBViewMetal.m @@ -0,0 +1,131 @@ +#import "GBViewMetal.h" + +#define WIDTH 160 +#define HEIGHT 144 +#define PITCH (160 * 4) + +static const MTLRegion region = { + {0, 0, 0}, // MTLOrigin + {WIDTH, HEIGHT, 1} // MTLSize +}; + +static const vector_float2 rect[] = +{ + {-1, -1}, + { 1, -1}, + {-1, 1}, + { 1, 1}, +}; + +@implementation GBViewMetal +{ + id device; + id texture, previous_texture; + id vertices; + id pipeline_state; + id command_queue; +} + +- (void)createInternalView +{ + MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; + view.delegate = self; + self.internalView = view; + + MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; + + texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; + + texture_descriptor.width = WIDTH; + texture_descriptor.height = HEIGHT; + + texture = [device newTextureWithDescriptor:texture_descriptor]; + previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + + vertices = [device newBufferWithBytes:rect + length:sizeof(rect) + options:MTLResourceStorageModeShared]; + + [self loadShader]; +} + +- (void) loadShader +{ + NSError *error = nil; + NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader" + ofType:@"metal" + inDirectory:@"Shaders"] + encoding:NSUTF8StringEncoding + error:nil]; + id library = [device newLibraryWithSource:shader_source + options:nil + error:&error]; + if (error) { + NSLog(@"Error: %@", error); + } + + id vertex_function = [library newFunctionWithName:@"vertex_shader"]; + id fragment_function = [library newFunctionWithName:@"fragment_shader"]; + + // Set up a descriptor for creating a pipeline state object + MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipeline_state_descriptor.vertexFunction = vertex_function; + pipeline_state_descriptor.fragmentFunction = fragment_function; + pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat; + + error = nil; + pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor + error:&error]; + if (error) { + NSLog(@"Failed to created pipeline state, error %@", error); + } + + command_queue = [device newCommandQueue]; +} + +- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size +{ +} + +- (void)drawInMTKView:(nonnull MTKView *)view +{ + [texture replaceRegion:region + mipmapLevel:0 + withBytes:[self currentBuffer] + bytesPerRow:PITCH]; + + MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; + id command_buffer = [command_queue commandBuffer]; + + if(render_pass_descriptor != nil) + { + id render_encoder = + [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; + + [render_encoder setViewport:(MTLViewport){0.0, 0.0, + view.bounds.size.width * view.window.backingScaleFactor, + view.bounds.size.height * view.window.backingScaleFactor, + -1.0, 1.0}]; + + [render_encoder setRenderPipelineState:pipeline_state]; + + [render_encoder setVertexBuffer:vertices + offset:0 + atIndex:0]; + + [render_encoder setFragmentTexture:texture + atIndex:0]; + + [render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip + vertexStart:0 + vertexCount:4]; + + [render_encoder endEncoding]; + + [command_buffer presentDrawable:view.currentDrawable]; + } + + + [command_buffer commit]; +} +@end diff --git a/Makefile b/Makefile index f32e9310..afd56b65 100755 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ ifeq ($(PLATFORM),Darwin) SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 -LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore +LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -framework Metal -framework MetalKit SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations @@ -160,8 +160,6 @@ $(OBJ)/%.m.o: %.m # Cocoa Port -Shaders:$(shell ls Shaders/*.fsh) - $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ $(shell ls Cocoa/*.icns) \ Cocoa/License.html \ @@ -178,7 +176,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ 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 + cp Shaders/*.fsh Shaders/*.metal $(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/ @@ -289,7 +287,7 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp $(BIN)/SDL/Shaders: Shaders -@$(MKDIR) -p $(dir $@) - cp -rf $^ $@ + cp -rf Shaders/*.fsh $@ # Boot ROMs diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal new file mode 100644 index 00000000..a5157642 --- /dev/null +++ b/Shaders/MasterShader.metal @@ -0,0 +1,45 @@ +#include +#include +#include + +using namespace metal; + +/* For GLSL compatibility */ +typedef float2 vec2; +typedef float3 vec3; +typedef float4 vec4; + +typedef struct { + float4 position [[position]]; + float2 texcoords; +} rasterizer_data; + +// Vertex Function +vertex rasterizer_data vertex_shader(uint index [[ vertex_id ]], + constant vector_float2 *vertices [[ buffer(0) ]]) + +{ + rasterizer_data out; + + out.position.xy = vertices[index].xy; + out.position.z = 0.0; + out.position.w = 1.0; + out.texcoords = (vertices[index].xy + float2(1, 1)) / 2.0; + + return out; +} + + +static inline float4 texture(texture2d texture, float2 pos) +{ + constexpr sampler texture_sampler; + return float4(texture.sample(texture_sampler, pos)); +} + +fragment float4 fragment_shader(rasterizer_data in [[stage_in]], + texture2d image [[ texture(0) ]]) +{ + in.texcoords.y = 1 - in.texcoords.y; + return texture(image, in.texcoords); +} + From c1fcd1a0c0b8cc79f1e921282876134ed67517ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 13:58:50 +0300 Subject: [PATCH 0673/1216] Added frame blending --- Cocoa/GBViewMetal.m | 21 +++++++++++++++++++++ Shaders/MasterShader.metal | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 157ddf37..89534929 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -24,6 +24,7 @@ static const vector_float2 rect[] = id vertices; id pipeline_state; id command_queue; + id mix_previous_buffer; } - (void)createInternalView @@ -46,6 +47,12 @@ static const vector_float2 rect[] = length:sizeof(rect) options:MTLResourceStorageModeShared]; + + static const bool default_mix_value = false; + mix_previous_buffer = [device newBufferWithBytes:&default_mix_value + length:sizeof(default_mix_value) + options:MTLResourceStorageModeShared]; + [self loadShader]; } @@ -93,12 +100,19 @@ static const vector_float2 rect[] = mipmapLevel:0 withBytes:[self currentBuffer] bytesPerRow:PITCH]; + if ([self shouldBlendFrameWithPrevious]) { + [previous_texture replaceRegion:region + mipmapLevel:0 + withBytes:[self previousBuffer] + bytesPerRow:PITCH]; + } MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; id command_buffer = [command_queue commandBuffer]; if(render_pass_descriptor != nil) { + *(bool *)[mix_previous_buffer contents] = [self shouldBlendFrameWithPrevious]; id render_encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; @@ -113,9 +127,16 @@ static const vector_float2 rect[] = offset:0 atIndex:0]; + [render_encoder setFragmentBuffer:mix_previous_buffer + offset:0 + atIndex:0]; + [render_encoder setFragmentTexture:texture atIndex:0]; + [render_encoder setFragmentTexture:previous_texture + atIndex:1]; + [render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index a5157642..928c4c75 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -37,9 +37,14 @@ static inline float4 texture(texture2d texture, float2 pos) } fragment float4 fragment_shader(rasterizer_data in [[stage_in]], - texture2d image [[ texture(0) ]]) + texture2d image [[ texture(0) ]], + texture2d previous_image [[ texture(1) ]], + constant bool *mix_previous [[ buffer(0) ]]) { in.texcoords.y = 1 - in.texcoords.y; + if (*mix_previous) { + return mix(texture(image, in.texcoords), texture(previous_image, in.texcoords), 0.5); + } return texture(image, in.texcoords); } From c6dba26d02caeb015cc82667063d6773c1e5b828 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 18:08:54 +0300 Subject: [PATCH 0674/1216] Simplify shaders --- Cocoa/GBGLShader.m | 6 ++-- Makefile | 2 +- SDL/shader.c | 8 ++--- Shaders/AAOmniScaleLegacy.fsh | 29 +++++++++-------- Shaders/AAScale2x.fsh | 35 ++++++++++----------- Shaders/AAScale4x.fsh | 57 +++++++++++++++++---------------- Shaders/Bilinear.fsh | 16 +++++----- Shaders/HQ2x.fsh | 29 +++++++++-------- Shaders/LCD.fsh | 26 +++++++-------- Shaders/MasterShader.fsh | 27 ++++++++-------- Shaders/MasterShader.metal | 2 +- Shaders/NearestNeighbor.fsh | 8 ++--- Shaders/OmniScale.fsh | 59 +++++++++++++++++------------------ Shaders/OmniScaleLegacy.fsh | 18 +++++------ Shaders/Scale2x.fsh | 29 +++++++++-------- Shaders/Scale4x.fsh | 53 +++++++++++++++---------------- Shaders/SmoothBilinear.fsh | 16 +++++----- 17 files changed, 200 insertions(+), 220 deletions(-) diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index 1fa2ba36..444ee894 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -50,7 +50,7 @@ void main(void) {\n\ // Attributes position_attribute = glGetAttribLocation(program, "aPosition"); // Uniforms - resolution_uniform = glGetUniformLocation(program, "uResolution"); + resolution_uniform = glGetUniformLocation(program, "output_resolution"); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); @@ -68,9 +68,9 @@ void main(void) {\n\ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); - previous_texture_uniform = glGetUniformLocation(program, "previousImage"); + previous_texture_uniform = glGetUniformLocation(program, "previous_image"); - mix_previous_uniform = glGetUniformLocation(program, "uMixPrevious"); + mix_previous_uniform = glGetUniformLocation(program, "mix_previous"); // Configure OpenGL [self configureOpenGL]; diff --git a/Makefile b/Makefile index afd56b65..85a92bd2 100755 --- a/Makefile +++ b/Makefile @@ -286,7 +286,7 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp cp -f $^ $@ $(BIN)/SDL/Shaders: Shaders - -@$(MKDIR) -p $(dir $@) + -@$(MKDIR) -p $@ cp -rf Shaders/*.fsh $@ # Boot ROMs diff --git a/SDL/shader.c b/SDL/shader.c index a34866e7..5b41b2a8 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -109,8 +109,8 @@ bool init_shader_with_name(shader_t *shader, const char *name) // Attributes shader->position_attribute = glGetAttribLocation(shader->program, "aPosition"); // Uniforms - shader->resolution_uniform = glGetUniformLocation(shader->program, "uResolution"); - shader->origin_uniform = glGetUniformLocation(shader->program, "uOrigin"); + shader->resolution_uniform = glGetUniformLocation(shader->program, "output_resolution"); + shader->origin_uniform = glGetUniformLocation(shader->program, "origin"); glGenTextures(1, &shader->texture); glBindTexture(GL_TEXTURE_2D, shader->texture); @@ -128,9 +128,9 @@ bool init_shader_with_name(shader_t *shader, const char *name) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); - shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previousImage"); + shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image"); - shader->mix_previous_uniform = glGetUniformLocation(shader->program, "uMixPrevious"); + shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous"); // Program diff --git a/Shaders/AAOmniScaleLegacy.fsh b/Shaders/AAOmniScaleLegacy.fsh index d667edfe..6aff04a1 100644 --- a/Shaders/AAOmniScaleLegacy.fsh +++ b/Shaders/AAOmniScaleLegacy.fsh @@ -4,14 +4,14 @@ float quickDistance(vec4 a, vec4 b) return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 omniScale(sampler2D image, vec2 texCoord) +vec4 omniScale(sampler2D image, vec2 position) { - vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); - vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); - vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); - vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); - vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); vec2 pos = fract(pixel); @@ -25,7 +25,7 @@ vec4 omniScale(sampler2D image, vec2 texCoord) int diagonalBias = 0; for (float y = -1.0; y < 3.0; y++) { for (float x = -1.0; x < 3.0; x++) { - vec4 color = texture(image, (pixel + vec2(x, y)) / textureDimensions); + vec4 color = texture(image, (pixel + vec2(x, y)) / input_resolution); if (color == q11) diagonalBias++; if (color == q12) diagonalBias--; } @@ -104,16 +104,15 @@ vec4 omniScale(sampler2D image, vec2 texCoord) return q22; } -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - vec2 pixel = vec2(1.0, 1.0) / uResolution; + vec2 pixel = vec2(1.0, 1.0) / output_resolution; // 4-pixel super sampling - vec4 q11 = omniScale(image, texCoord + pixel * vec2(-0.25, -0.25)); - vec4 q21 = omniScale(image, texCoord + pixel * vec2(+0.25, -0.25)); - vec4 q12 = omniScale(image, texCoord + pixel * vec2(-0.25, +0.25)); - vec4 q22 = omniScale(image, texCoord + pixel * vec2(+0.25, +0.25)); + vec4 q11 = omniScale(image, position + pixel * vec2(-0.25, -0.25)); + vec4 q21 = omniScale(image, position + pixel * vec2(+0.25, -0.25)); + vec4 q12 = omniScale(image, position + pixel * vec2(-0.25, +0.25)); + vec4 q22 = omniScale(image, position + pixel * vec2(+0.25, +0.25)); return (q11 + q21 + q12 + q22) / 4.0; -} \ No newline at end of file +} diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh index bdd84037..4f5d415f 100644 --- a/Shaders/AAScale2x.fsh +++ b/Shaders/AAScale2x.fsh @@ -1,23 +1,22 @@ -vec4 scale2x(sampler2D image) +vec4 scale2x(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / textureDimensions; - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - + vec2 o = 1.0 / input_resolution; + // texel arrangement // A B C // D E F // G H I - vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture(image, texCoord + vec2( 0, o.y)); - vec4 C = texture(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture(image, texCoord + vec2( 0, 0)); - vec4 F = texture(image, texCoord + vec2( o.x, 0)); - vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); - vec2 p = texCoord * textureDimensions; + vec4 A = texture(image, position + vec2( -o.x, o.y)); + vec4 B = texture(image, position + vec2( 0, o.y)); + vec4 C = texture(image, position + vec2( o.x, o.y)); + vec4 D = texture(image, position + vec2( -o.x, 0)); + vec4 E = texture(image, position + vec2( 0, 0)); + vec4 F = texture(image, position + vec2( o.x, 0)); + vec4 G = texture(image, position + vec2( -o.x, -o.y)); + vec4 H = texture(image, position + vec2( 0, -o.y)); + vec4 I = texture(image, position + vec2( o.x, -o.y)); + vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); if (p.x > .5) { @@ -39,9 +38,7 @@ vec4 scale2x(sampler2D image) } } -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - - return mix(texture(image, texCoord), scale2x(image), 0.5); -} \ No newline at end of file + return mix(texture(image, position), scale2x(image, position), 0.5); +} diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh index 83380d13..b08fd93a 100644 --- a/Shaders/AAScale4x.fsh +++ b/Shaders/AAScale4x.fsh @@ -1,21 +1,21 @@ -vec4 scale2x(sampler2D image, vec2 texCoord) +vec4 scale2x(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / textureDimensions; + vec2 o = 1.0 / input_resolution; // texel arrangement // A B C // D E F // G H I - vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture(image, texCoord + vec2( 0, o.y)); - vec4 C = texture(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture(image, texCoord + vec2( 0, 0)); - vec4 F = texture(image, texCoord + vec2( o.x, 0)); - vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); - vec2 p = texCoord * textureDimensions; + vec4 A = texture(image, position + vec2( -o.x, o.y)); + vec4 B = texture(image, position + vec2( 0, o.y)); + vec4 C = texture(image, position + vec2( o.x, o.y)); + vec4 D = texture(image, position + vec2( -o.x, 0)); + vec4 E = texture(image, position + vec2( 0, 0)); + vec4 F = texture(image, position + vec2( o.x, 0)); + vec4 G = texture(image, position + vec2( -o.x, -o.y)); + vec4 H = texture(image, position + vec2( 0, -o.y)); + vec4 I = texture(image, position + vec2( o.x, -o.y)); + vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); if (p.x > .5) { @@ -37,32 +37,31 @@ vec4 scale2x(sampler2D image, vec2 texCoord) } } -vec4 aaScale2x(sampler2D image, vec2 texCoord) +vec4 aaScale2x(sampler2D image, vec2 position) { - return mix(texture(image, texCoord), scale2x(image, texCoord), 0.5); + return mix(texture(image, position), scale2x(image, position), 0.5); } -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / (textureDimensions * 2.); - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - + vec2 o = 1.0 / (input_resolution * 2.); + // texel arrangement // A B C // D E F // G H I - vec4 A = aaScale2x(image, texCoord + vec2( -o.x, o.y)); - vec4 B = aaScale2x(image, texCoord + vec2( 0, o.y)); - vec4 C = aaScale2x(image, texCoord + vec2( o.x, o.y)); - vec4 D = aaScale2x(image, texCoord + vec2( -o.x, 0)); - vec4 E = aaScale2x(image, texCoord + vec2( 0, 0)); - vec4 F = aaScale2x(image, texCoord + vec2( o.x, 0)); - vec4 G = aaScale2x(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = aaScale2x(image, texCoord + vec2( 0, -o.y)); - vec4 I = aaScale2x(image, texCoord + vec2( o.x, -o.y)); + vec4 A = aaScale2x(image, position + vec2( -o.x, o.y)); + vec4 B = aaScale2x(image, position + vec2( 0, o.y)); + vec4 C = aaScale2x(image, position + vec2( o.x, o.y)); + vec4 D = aaScale2x(image, position + vec2( -o.x, 0)); + vec4 E = aaScale2x(image, position + vec2( 0, 0)); + vec4 F = aaScale2x(image, position + vec2( o.x, 0)); + vec4 G = aaScale2x(image, position + vec2( -o.x, -o.y)); + vec4 H = aaScale2x(image, position + vec2( 0, -o.y)); + vec4 I = aaScale2x(image, position + vec2( o.x, -o.y)); vec4 R; - vec2 p = texCoord * textureDimensions * 2.; + vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] p = fract(p); if (p.x > .5) { @@ -84,4 +83,4 @@ vec4 scale(sampler2D image) } return mix(R, E, 0.5); -} \ No newline at end of file +} diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh index 6fa5e21f..7e8d259c 100644 --- a/Shaders/Bilinear.fsh +++ b/Shaders/Bilinear.fsh @@ -1,16 +1,14 @@ -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); - vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - - vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); - vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); - vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); - vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); vec4 r1 = mix(q11, q21, fract(pixel.x)); vec4 r2 = mix(q12, q22, fract(pixel.x)); return mix (r1, r2, fract(pixel.y)); -} \ No newline at end of file +} diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index 7a4fb1ea..a815a602 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -27,31 +27,30 @@ vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); } -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / textureDimensions; - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - + vec2 o = 1.0 / input_resolution; + /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ // p = the position within a pixel [0...1] - vec2 p = fract(texCoord * textureDimensions); + vec2 p = fract(position * input_resolution); if (p.x > 0.5) o.x = -o.x; if (p.y > 0.5) o.y = -o.y; - vec4 w0 = texture(image, texCoord + vec2( -o.x, -o.y)); - vec4 w1 = texture(image, texCoord + vec2( 0, -o.y)); - vec4 w2 = texture(image, texCoord + vec2( o.x, -o.y)); - vec4 w3 = texture(image, texCoord + vec2( -o.x, 0)); - vec4 w4 = texture(image, texCoord + vec2( 0, 0)); - vec4 w5 = texture(image, texCoord + vec2( o.x, 0)); - vec4 w6 = texture(image, texCoord + vec2( -o.x, o.y)); - vec4 w7 = texture(image, texCoord + vec2( 0, o.y)); - vec4 w8 = texture(image, texCoord + vec2( o.x, o.y)); + vec4 w0 = texture(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture(image, position + vec2( 0, -o.y)); + vec4 w2 = texture(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture(image, position + vec2( -o.x, 0)); + vec4 w4 = texture(image, position + vec2( 0, 0)); + vec4 w5 = texture(image, position + vec2( o.x, 0)); + vec4 w6 = texture(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture(image, position + vec2( 0, o.y)); + vec4 w8 = texture(image, position + vec2( o.x, o.y)); int pattern = 0; if (is_different(w0, w4)) pattern |= 1; @@ -99,4 +98,4 @@ vec4 scale(sampler2D image) return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); -} \ No newline at end of file +} diff --git a/Shaders/LCD.fsh b/Shaders/LCD.fsh index 980f3bdd..f41cf5b3 100644 --- a/Shaders/LCD.fsh +++ b/Shaders/LCD.fsh @@ -2,29 +2,27 @@ #define COLOR_HIGH 1.0 #define SCANLINE_DEPTH 0.1 -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + vec2 pos = fract(position * input_resolution); + vec2 sub_pos = fract(position * input_resolution * 6); - vec2 pos = fract(texCoord * textureDimensions); - vec2 sub_pos = fract(texCoord * textureDimensions * 6); - - vec4 center = texture(image, texCoord); - vec4 left = texture(image, texCoord - vec2(1.0 / textureDimensions.x, 0)); - vec4 right = texture(image, texCoord + vec2(1.0 / textureDimensions.x, 0)); + vec4 center = texture(image, position); + vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0)); + vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0)); if (pos.y < 1.0 / 6.0) { - center = mix(center, texture(image, texCoord + vec2(0, -1.0 / textureDimensions.y)), 0.5 - sub_pos.y / 2.0); - left = mix(left, texture(image, texCoord + vec2(-1.0 / textureDimensions.x, -1.0 / textureDimensions.y)), 0.5 - sub_pos.y / 2.0); - right = mix(right, texture(image, texCoord + vec2( 1.0 / textureDimensions.x, -1.0 / textureDimensions.y)), 0.5 - sub_pos.y / 2.0); + center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); center *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); left *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); right *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); } else if (pos.y > 5.0 / 6.0) { - center = mix(center, texture(image, texCoord + vec2(0, 1.0 / textureDimensions.y)), sub_pos.y / 2.0); - left = mix(left, texture(image, texCoord + vec2(-1.0 / textureDimensions.x, 1.0 / textureDimensions.y)), sub_pos.y / 2.0); - right = mix(right, texture(image, texCoord + vec2( 1.0 / textureDimensions.x, 1.0 / textureDimensions.y)), sub_pos.y / 2.0); + center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 41030565..9df2c1a8 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -1,27 +1,28 @@ #version 150 uniform sampler2D image; -uniform sampler2D previousImage; -uniform bool uMixPrevious; +uniform sampler2D previous_image; +uniform bool mix_previous; -uniform vec2 uResolution; -uniform vec2 uOrigin; -const vec2 textureDimensions = vec2(160, 144); +uniform vec2 output_resolution; +uniform vec2 origin; +const vec2 input_resolution = vec2(160, 144); out vec4 frag_color; vec4 modified_frag_cord; -#define gl_FragCoord modified_frag_cord #line 1 {filter} -#undef gl_FragCoord -void main() { - modified_frag_cord = gl_FragCoord - vec4(uOrigin.x, uOrigin.y, 0, 0); +void main() +{ + vec2 position = gl_FragCoord.xy - origin; + position /= output_resolution; + position.y = 1 - position.y; - if (uMixPrevious) { - frag_color = mix(scale(image), scale(previousImage), 0.5); + if (mix_previous) { + frag_color = mix(scale(image, position), scale(previous_image, position), 0.5); } else { - frag_color = scale(image); + frag_color = scale(image, position); } -} \ No newline at end of file +} diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 928c4c75..3c227364 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -3,6 +3,7 @@ #include using namespace metal; +const float4 input_resolution = float4(160, 144); /* For GLSL compatibility */ typedef float2 vec2; @@ -17,7 +18,6 @@ typedef struct { // Vertex Function vertex rasterizer_data vertex_shader(uint index [[ vertex_id ]], constant vector_float2 *vertices [[ buffer(0) ]]) - { rasterizer_data out; diff --git a/Shaders/NearestNeighbor.fsh b/Shaders/NearestNeighbor.fsh index 661e5fb3..e8b0759e 100644 --- a/Shaders/NearestNeighbor.fsh +++ b/Shaders/NearestNeighbor.fsh @@ -1,6 +1,4 @@ -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - - return texture(image, texCoord); -} \ No newline at end of file + return texture(image, position); +} diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index 7e8b6cbe..384fdd36 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -24,16 +24,15 @@ bool is_different(vec4 a, vec4 b) #define P(m, r) ((pattern & (m)) == (r)) -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / textureDimensions; - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - + vec2 o = 1.0 / input_resolution; + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ // p = the position within a pixel [0...1] - vec2 p = fract(texCoord * textureDimensions); + vec2 p = fract(position * input_resolution); if (p.x > 0.5) { o.x = -o.x; @@ -44,17 +43,15 @@ vec4 scale(sampler2D image) p.y = 1.0 - p.y; } - - - vec4 w0 = texture(image, texCoord + vec2( -o.x, -o.y)); - vec4 w1 = texture(image, texCoord + vec2( 0, -o.y)); - vec4 w2 = texture(image, texCoord + vec2( o.x, -o.y)); - vec4 w3 = texture(image, texCoord + vec2( -o.x, 0)); - vec4 w4 = texture(image, texCoord + vec2( 0, 0)); - vec4 w5 = texture(image, texCoord + vec2( o.x, 0)); - vec4 w6 = texture(image, texCoord + vec2( -o.x, o.y)); - vec4 w7 = texture(image, texCoord + vec2( 0, o.y)); - vec4 w8 = texture(image, texCoord + vec2( o.x, o.y)); + vec4 w0 = texture(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture(image, position + vec2( 0, -o.y)); + vec4 w2 = texture(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture(image, position + vec2( -o.x, 0)); + vec4 w4 = texture(image, position + vec2( 0, 0)); + vec4 w5 = texture(image, position + vec2( o.x, 0)); + vec4 w6 = texture(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture(image, position + vec2( 0, o.y)); + vec4 w8 = texture(image, position + vec2( o.x, o.y)); int pattern = 0; if (is_different(w0, w4)) pattern |= 1 << 0; @@ -83,7 +80,7 @@ vec4 scale(sampler2D image) return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); if (P(0x2f,0x2f)) { float dist = length(p - vec2(0.5)); - float pixel_size = length(1.0 / (uResolution / textureDimensions)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); if (dist < 0.5 - pixel_size / 2) { return w4; } @@ -102,7 +99,7 @@ vec4 scale(sampler2D image) } if (P(0xbf,0x37) || P(0xdb,0x13)) { float dist = p.x - 2.0 * p.y; - float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); if (dist > pixel_size / 2) { return w1; } @@ -114,7 +111,7 @@ vec4 scale(sampler2D image) } if (P(0xdb,0x49) || P(0xef,0x6d)) { float dist = p.y - 2.0 * p.x; - float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); if (p.y - 2.0 * p.x > pixel_size / 2) { return w3; } @@ -126,7 +123,7 @@ vec4 scale(sampler2D image) } if (P(0xbf,0x8f) || P(0x7e,0x0e)) { float dist = p.x + 2.0 * p.y; - float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); if (dist > 1.0 + pixel_size / 2) { return w4; @@ -150,7 +147,7 @@ vec4 scale(sampler2D image) if (P(0x7e,0x2a) || P(0xef,0xab)) { float dist = p.y + 2.0 * p.x; - float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { return w4; @@ -186,7 +183,7 @@ vec4 scale(sampler2D image) P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || P(0x3b,0x1b)) { float dist = p.x + p.y; - float pixel_size = length(1.0 / (uResolution / textureDimensions)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); if (dist > 0.5 + pixel_size / 2) { return w4; @@ -214,19 +211,19 @@ vec4 scale(sampler2D image) return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); float dist = p.x + p.y; - float pixel_size = length(1.0 / (uResolution / textureDimensions)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); if (dist > 0.5 + pixel_size / 2) return w4; /* We need more samples to "solve" this diagonal */ - vec4 x0 = texture(image, texCoord + vec2( -o.x * 2.0, -o.y * 2.0)); - vec4 x1 = texture(image, texCoord + vec2( -o.x , -o.y * 2.0)); - vec4 x2 = texture(image, texCoord + vec2( 0.0 , -o.y * 2.0)); - vec4 x3 = texture(image, texCoord + vec2( o.x , -o.y * 2.0)); - vec4 x4 = texture(image, texCoord + vec2( -o.x * 2.0, -o.y )); - vec4 x5 = texture(image, texCoord + vec2( -o.x * 2.0, 0.0 )); - vec4 x6 = texture(image, texCoord + vec2( -o.x * 2.0, o.y )); + vec4 x0 = texture(image, position + vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture(image, position + vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture(image, position + vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture(image, position + vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture(image, position + vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture(image, position + vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture(image, position + vec2( -o.x * 2.0, o.y )); if (is_different(x0, w4)) pattern |= 1 << 8; if (is_different(x1, w4)) pattern |= 1 << 9; @@ -251,4 +248,4 @@ vec4 scale(sampler2D image) } return w4; -} \ No newline at end of file +} diff --git a/Shaders/OmniScaleLegacy.fsh b/Shaders/OmniScaleLegacy.fsh index f4bdea9a..2d38a2f7 100644 --- a/Shaders/OmniScaleLegacy.fsh +++ b/Shaders/OmniScaleLegacy.fsh @@ -4,16 +4,14 @@ float quickDistance(vec4 a, vec4 b) return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); - vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - - vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); - vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); - vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); - vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); vec2 pos = fract(pixel); @@ -27,7 +25,7 @@ vec4 scale(sampler2D image) int diagonalBias = 0; for (float y = -1.0; y < 3.0; y++) { for (float x = -1.0; x < 3.0; x++) { - vec4 color = texture(image, (pixel + vec2(x, y)) / textureDimensions); + vec4 color = texture(image, (pixel + vec2(x, y)) / input_resolution); if (color == q11) diagonalBias++; if (color == q12) diagonalBias--; } @@ -104,4 +102,4 @@ vec4 scale(sampler2D image) } return q22; -} \ No newline at end of file +} diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh index 3968057f..ffc68f5c 100644 --- a/Shaders/Scale2x.fsh +++ b/Shaders/Scale2x.fsh @@ -1,25 +1,24 @@ /* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */ -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / textureDimensions; - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - + vec2 o = 1.0 / input_resolution; + // texel arrangement // A B C // D E F // G H I - vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture(image, texCoord + vec2( 0, o.y)); - vec4 C = texture(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture(image, texCoord + vec2( 0, 0)); - vec4 F = texture(image, texCoord + vec2( o.x, 0)); - vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); - vec2 p = texCoord * textureDimensions; + vec4 A = texture(image, position + vec2( -o.x, o.y)); + vec4 B = texture(image, position + vec2( 0, o.y)); + vec4 C = texture(image, position + vec2( o.x, o.y)); + vec4 D = texture(image, position + vec2( -o.x, 0)); + vec4 E = texture(image, position + vec2( 0, 0)); + vec4 F = texture(image, position + vec2( o.x, 0)); + vec4 G = texture(image, position + vec2( -o.x, -o.y)); + vec4 H = texture(image, position + vec2( 0, -o.y)); + vec4 I = texture(image, position + vec2( o.x, -o.y)); + vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); if (p.x > .5) { @@ -39,4 +38,4 @@ vec4 scale(sampler2D image) return D == H && D != B && H != F ? D : E; } } -} \ No newline at end of file +} diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh index 1c763fc4..e6be367f 100644 --- a/Shaders/Scale4x.fsh +++ b/Shaders/Scale4x.fsh @@ -1,21 +1,21 @@ -vec4 scale2x(sampler2D image, vec2 texCoord) +vec4 scale2x(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / textureDimensions; + vec2 o = 1.0 / input_resolution; // texel arrangement // A B C // D E F // G H I - vec4 A = texture(image, texCoord + vec2( -o.x, o.y)); - vec4 B = texture(image, texCoord + vec2( 0, o.y)); - vec4 C = texture(image, texCoord + vec2( o.x, o.y)); - vec4 D = texture(image, texCoord + vec2( -o.x, 0)); - vec4 E = texture(image, texCoord + vec2( 0, 0)); - vec4 F = texture(image, texCoord + vec2( o.x, 0)); - vec4 G = texture(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = texture(image, texCoord + vec2( 0, -o.y)); - vec4 I = texture(image, texCoord + vec2( o.x, -o.y)); - vec2 p = texCoord * textureDimensions; + vec4 A = texture(image, position + vec2( -o.x, o.y)); + vec4 B = texture(image, position + vec2( 0, o.y)); + vec4 C = texture(image, position + vec2( o.x, o.y)); + vec4 D = texture(image, position + vec2( -o.x, 0)); + vec4 E = texture(image, position + vec2( 0, 0)); + vec4 F = texture(image, position + vec2( o.x, 0)); + vec4 G = texture(image, position + vec2( -o.x, -o.y)); + vec4 H = texture(image, position + vec2( 0, -o.y)); + vec4 I = texture(image, position + vec2( o.x, -o.y)); + vec2 p = position * input_resolution; // p = the position within a pixel [0...1] vec4 R; p = fract(p); @@ -38,26 +38,25 @@ vec4 scale2x(sampler2D image, vec2 texCoord) } } -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { // o = offset, the width of a pixel - vec2 o = 1.0 / (textureDimensions * 2.); - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; - + vec2 o = 1.0 / (input_resolution * 2.); + // texel arrangement // A B C // D E F // G H I - vec4 A = scale2x(image, texCoord + vec2( -o.x, o.y)); - vec4 B = scale2x(image, texCoord + vec2( 0, o.y)); - vec4 C = scale2x(image, texCoord + vec2( o.x, o.y)); - vec4 D = scale2x(image, texCoord + vec2( -o.x, 0)); - vec4 E = scale2x(image, texCoord + vec2( 0, 0)); - vec4 F = scale2x(image, texCoord + vec2( o.x, 0)); - vec4 G = scale2x(image, texCoord + vec2( -o.x, -o.y)); - vec4 H = scale2x(image, texCoord + vec2( 0, -o.y)); - vec4 I = scale2x(image, texCoord + vec2( o.x, -o.y)); - vec2 p = texCoord * textureDimensions * 2.; + vec4 A = scale2x(image, position + vec2( -o.x, o.y)); + vec4 B = scale2x(image, position + vec2( 0, o.y)); + vec4 C = scale2x(image, position + vec2( o.x, o.y)); + vec4 D = scale2x(image, position + vec2( -o.x, 0)); + vec4 E = scale2x(image, position + vec2( 0, 0)); + vec4 F = scale2x(image, position + vec2( o.x, 0)); + vec4 G = scale2x(image, position + vec2( -o.x, -o.y)); + vec4 H = scale2x(image, position + vec2( 0, -o.y)); + vec4 I = scale2x(image, position + vec2( o.x, -o.y)); + vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] p = fract(p); if (p.x > .5) { @@ -77,4 +76,4 @@ vec4 scale(sampler2D image) return D == H && D != B && H != F ? D : E; } } -} \ No newline at end of file +} diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index b796d88a..0e400432 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -1,13 +1,11 @@ -vec4 scale(sampler2D image) +vec4 scale(sampler2D image, vec2 position) { - vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); - vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5); - - vec4 q11 = texture(image, (floor(pixel) + 0.5) / textureDimensions); - vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / textureDimensions); - vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / textureDimensions); - vec4 q22 = texture(image, (ceil(pixel) + 0.5) / textureDimensions); + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); vec2 s = smoothstep(0., 1., fract(pixel)); @@ -15,4 +13,4 @@ vec4 scale(sampler2D image) vec4 r2 = mix(q12, q22, s.x); return mix (r1, r2, s.y); -} \ No newline at end of file +} From da7c32cb10485f4c38fdaf235c6de34d1f898c3c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 18:22:09 +0300 Subject: [PATCH 0675/1216] No more globals in shaders --- Shaders/AAOmniScaleLegacy.fsh | 12 ++++++------ Shaders/AAScale2x.fsh | 6 +++--- Shaders/AAScale4x.fsh | 26 +++++++++++++------------- Shaders/Bilinear.fsh | 2 +- Shaders/HQ2x.fsh | 2 +- Shaders/LCD.fsh | 2 +- Shaders/MasterShader.fsh | 6 +++--- Shaders/MasterShader.metal | 2 +- Shaders/NearestNeighbor.fsh | 2 +- Shaders/OmniScale.fsh | 2 +- Shaders/OmniScaleLegacy.fsh | 2 +- Shaders/Scale2x.fsh | 2 +- Shaders/Scale4x.fsh | 22 +++++++++++----------- Shaders/SmoothBilinear.fsh | 2 +- 14 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Shaders/AAOmniScaleLegacy.fsh b/Shaders/AAOmniScaleLegacy.fsh index 6aff04a1..f8d50d27 100644 --- a/Shaders/AAOmniScaleLegacy.fsh +++ b/Shaders/AAOmniScaleLegacy.fsh @@ -4,7 +4,7 @@ float quickDistance(vec4 a, vec4 b) return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 omniScale(sampler2D image, vec2 position) +vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); @@ -104,15 +104,15 @@ vec4 omniScale(sampler2D image, vec2 position) return q22; } -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = vec2(1.0, 1.0) / output_resolution; // 4-pixel super sampling - vec4 q11 = omniScale(image, position + pixel * vec2(-0.25, -0.25)); - vec4 q21 = omniScale(image, position + pixel * vec2(+0.25, -0.25)); - vec4 q12 = omniScale(image, position + pixel * vec2(-0.25, +0.25)); - vec4 q22 = omniScale(image, position + pixel * vec2(+0.25, +0.25)); + vec4 q11 = omniScale(image, position + pixel * vec2(-0.25, -0.25), input_resolution, output_resolution); + vec4 q21 = omniScale(image, position + pixel * vec2(+0.25, -0.25), input_resolution, output_resolution); + vec4 q12 = omniScale(image, position + pixel * vec2(-0.25, +0.25), input_resolution, output_resolution); + vec4 q22 = omniScale(image, position + pixel * vec2(+0.25, +0.25), input_resolution, output_resolution); return (q11 + q21 + q12 + q22) / 4.0; } diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh index 4f5d415f..1801805e 100644 --- a/Shaders/AAScale2x.fsh +++ b/Shaders/AAScale2x.fsh @@ -1,4 +1,4 @@ -vec4 scale2x(sampler2D image, vec2 position) +vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -38,7 +38,7 @@ vec4 scale2x(sampler2D image, vec2 position) } } -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - return mix(texture(image, position), scale2x(image, position), 0.5); + return mix(texture(image, position), scale2x(image, position, input_resolution, output_resolution), 0.5); } diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh index b08fd93a..7e9ab728 100644 --- a/Shaders/AAScale4x.fsh +++ b/Shaders/AAScale4x.fsh @@ -1,4 +1,4 @@ -vec4 scale2x(sampler2D image, vec2 position) +vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -37,12 +37,12 @@ vec4 scale2x(sampler2D image, vec2 position) } } -vec4 aaScale2x(sampler2D image, vec2 position) +vec4 aaScale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - return mix(texture(image, position), scale2x(image, position), 0.5); + return mix(texture(image, position), scale2x(image, position, input_resolution, output_resolution), 0.5); } -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / (input_resolution * 2.); @@ -51,15 +51,15 @@ vec4 scale(sampler2D image, vec2 position) // A B C // D E F // G H I - vec4 A = aaScale2x(image, position + vec2( -o.x, o.y)); - vec4 B = aaScale2x(image, position + vec2( 0, o.y)); - vec4 C = aaScale2x(image, position + vec2( o.x, o.y)); - vec4 D = aaScale2x(image, position + vec2( -o.x, 0)); - vec4 E = aaScale2x(image, position + vec2( 0, 0)); - vec4 F = aaScale2x(image, position + vec2( o.x, 0)); - vec4 G = aaScale2x(image, position + vec2( -o.x, -o.y)); - vec4 H = aaScale2x(image, position + vec2( 0, -o.y)); - vec4 I = aaScale2x(image, position + vec2( o.x, -o.y)); + vec4 A = aaScale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); + vec4 B = aaScale2x(image, position + vec2( 0, o.y), input_resolution, output_resolution); + vec4 C = aaScale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); + vec4 D = aaScale2x(image, position + vec2( -o.x, 0), input_resolution, output_resolution); + vec4 E = aaScale2x(image, position + vec2( 0, 0), input_resolution, output_resolution); + vec4 F = aaScale2x(image, position + vec2( o.x, 0), input_resolution, output_resolution); + vec4 G = aaScale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); + vec4 H = aaScale2x(image, position + vec2( 0, -o.y), input_resolution, output_resolution); + vec4 I = aaScale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); vec4 R; vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh index 7e8d259c..8283bdf0 100644 --- a/Shaders/Bilinear.fsh +++ b/Shaders/Bilinear.fsh @@ -1,4 +1,4 @@ -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index a815a602..a9832a02 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -27,7 +27,7 @@ vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); } -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; diff --git a/Shaders/LCD.fsh b/Shaders/LCD.fsh index f41cf5b3..236456fb 100644 --- a/Shaders/LCD.fsh +++ b/Shaders/LCD.fsh @@ -2,7 +2,7 @@ #define COLOR_HIGH 1.0 #define SCANLINE_DEPTH 0.1 -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pos = fract(position * input_resolution); vec2 sub_pos = fract(position * input_resolution * 6); diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 9df2c1a8..90c6b1a0 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -9,7 +9,6 @@ const vec2 input_resolution = vec2(160, 144); out vec4 frag_color; -vec4 modified_frag_cord; #line 1 {filter} @@ -20,9 +19,10 @@ void main() position.y = 1 - position.y; if (mix_previous) { - frag_color = mix(scale(image, position), scale(previous_image, position), 0.5); + frag_color = mix(scale(image, position, input_resolution, output_resolution), + scale(previous_image, position, input_resolution, output_resolution), 0.5); } else { - frag_color = scale(image, position); + frag_color = scale(image, position, input_resolution, output_resolution); } } diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 3c227364..76318e58 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -3,7 +3,7 @@ #include using namespace metal; -const float4 input_resolution = float4(160, 144); +constant float2 input_resolution = float2(160, 144); /* For GLSL compatibility */ typedef float2 vec2; diff --git a/Shaders/NearestNeighbor.fsh b/Shaders/NearestNeighbor.fsh index e8b0759e..24fd66b9 100644 --- a/Shaders/NearestNeighbor.fsh +++ b/Shaders/NearestNeighbor.fsh @@ -1,4 +1,4 @@ -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { return texture(image, position); } diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index 384fdd36..21a5c1d6 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -24,7 +24,7 @@ bool is_different(vec4 a, vec4 b) #define P(m, r) ((pattern & (m)) == (r)) -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; diff --git a/Shaders/OmniScaleLegacy.fsh b/Shaders/OmniScaleLegacy.fsh index 2d38a2f7..dfeb3a01 100644 --- a/Shaders/OmniScaleLegacy.fsh +++ b/Shaders/OmniScaleLegacy.fsh @@ -4,7 +4,7 @@ float quickDistance(vec4 a, vec4 b) return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh index ffc68f5c..439b1fae 100644 --- a/Shaders/Scale2x.fsh +++ b/Shaders/Scale2x.fsh @@ -1,6 +1,6 @@ /* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */ -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh index e6be367f..4e3b3f10 100644 --- a/Shaders/Scale4x.fsh +++ b/Shaders/Scale4x.fsh @@ -1,4 +1,4 @@ -vec4 scale2x(sampler2D image, vec2 position) +vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -38,7 +38,7 @@ vec4 scale2x(sampler2D image, vec2 position) } } -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / (input_resolution * 2.); @@ -47,15 +47,15 @@ vec4 scale(sampler2D image, vec2 position) // A B C // D E F // G H I - vec4 A = scale2x(image, position + vec2( -o.x, o.y)); - vec4 B = scale2x(image, position + vec2( 0, o.y)); - vec4 C = scale2x(image, position + vec2( o.x, o.y)); - vec4 D = scale2x(image, position + vec2( -o.x, 0)); - vec4 E = scale2x(image, position + vec2( 0, 0)); - vec4 F = scale2x(image, position + vec2( o.x, 0)); - vec4 G = scale2x(image, position + vec2( -o.x, -o.y)); - vec4 H = scale2x(image, position + vec2( 0, -o.y)); - vec4 I = scale2x(image, position + vec2( o.x, -o.y)); + vec4 A = scale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); + vec4 B = scale2x(image, position + vec2( 0, o.y), input_resolution, output_resolution); + vec4 C = scale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); + vec4 D = scale2x(image, position + vec2( -o.x, 0), input_resolution, output_resolution); + vec4 E = scale2x(image, position + vec2( 0, 0), input_resolution, output_resolution); + vec4 F = scale2x(image, position + vec2( o.x, 0), input_resolution, output_resolution); + vec4 G = scale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); + vec4 H = scale2x(image, position + vec2( 0, -o.y), input_resolution, output_resolution); + vec4 I = scale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] p = fract(p); diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index 0e400432..2082cd2f 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -1,4 +1,4 @@ -vec4 scale(sampler2D image, vec2 position) +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); From 4466a55de6705a34499c131bf9aeae2cc2aa81ed Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 18:44:22 +0300 Subject: [PATCH 0676/1216] Output resolution parameter --- Cocoa/GBViewMetal.m | 18 +++++++++++++++--- Shaders/MasterShader.metal | 4 +++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 89534929..a4bca885 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -25,6 +25,8 @@ static const vector_float2 rect[] = id pipeline_state; id command_queue; id mix_previous_buffer; + id output_resolution_buffer; + vector_float2 output_resolution; } - (void)createInternalView @@ -46,13 +48,16 @@ static const vector_float2 rect[] = vertices = [device newBufferWithBytes:rect length:sizeof(rect) options:MTLResourceStorageModeShared]; - static const bool default_mix_value = false; mix_previous_buffer = [device newBufferWithBytes:&default_mix_value length:sizeof(default_mix_value) options:MTLResourceStorageModeShared]; + output_resolution_buffer = [device newBufferWithBytes:&default_mix_value + length:sizeof(default_mix_value) + options:MTLResourceStorageModeShared]; + [self loadShader]; } @@ -92,6 +97,7 @@ static const vector_float2 rect[] = - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { + output_resolution = (vector_float2){size.width, size.height}; } - (void)drawInMTKView:(nonnull MTKView *)view @@ -113,12 +119,14 @@ static const vector_float2 rect[] = if(render_pass_descriptor != nil) { *(bool *)[mix_previous_buffer contents] = [self shouldBlendFrameWithPrevious]; + *(vector_float2 *)[output_resolution_buffer contents] = output_resolution; + id render_encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; [render_encoder setViewport:(MTLViewport){0.0, 0.0, - view.bounds.size.width * view.window.backingScaleFactor, - view.bounds.size.height * view.window.backingScaleFactor, + output_resolution.x, + output_resolution.y, -1.0, 1.0}]; [render_encoder setRenderPipelineState:pipeline_state]; @@ -131,6 +139,10 @@ static const vector_float2 rect[] = offset:0 atIndex:0]; + [render_encoder setFragmentBuffer:output_resolution_buffer + offset:0 + atIndex:1]; + [render_encoder setFragmentTexture:texture atIndex:0]; diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 76318e58..5c2b8d9f 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -36,10 +36,12 @@ static inline float4 texture(texture2d texture, float2 pos) return float4(texture.sample(texture_sampler, pos)); } + fragment float4 fragment_shader(rasterizer_data in [[stage_in]], texture2d image [[ texture(0) ]], texture2d previous_image [[ texture(1) ]], - constant bool *mix_previous [[ buffer(0) ]]) + constant bool *mix_previous [[ buffer(0) ]], + constant float2 *output_resolution [[ buffer(1) ]]) { in.texcoords.y = 1 - in.texcoords.y; if (*mix_previous) { From cd045fde1541b425f24ba847b09fbdcde3199612 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 19:11:06 +0300 Subject: [PATCH 0677/1216] Scaling filters in Metal --- Cocoa/GBViewMetal.m | 20 ++++++++++++++++++-- Shaders/AAOmniScaleLegacy.fsh | 18 +++++++++--------- Shaders/AAScale2x.fsh | 8 ++++---- Shaders/AAScale4x.fsh | 16 ++++++++-------- Shaders/MasterShader.fsh | 3 +++ Shaders/MasterShader.metal | 10 ++++++++-- Shaders/OmniScale.fsh | 8 ++++---- Shaders/OmniScaleLegacy.fsh | 18 +++++++++--------- Shaders/Scale2x.fsh | 8 ++++---- Shaders/Scale4x.fsh | 16 ++++++++-------- 10 files changed, 75 insertions(+), 50 deletions(-) diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index a4bca885..2bd69fe1 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -54,10 +54,11 @@ static const vector_float2 rect[] = length:sizeof(default_mix_value) options:MTLResourceStorageModeShared]; - output_resolution_buffer = [device newBufferWithBytes:&default_mix_value - length:sizeof(default_mix_value) + output_resolution_buffer = [device newBufferWithBytes:&output_resolution + length:sizeof(output_resolution) options:MTLResourceStorageModeShared]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil]; [self loadShader]; } @@ -69,11 +70,25 @@ static const vector_float2 rect[] = inDirectory:@"Shaders"] encoding:NSUTF8StringEncoding error:nil]; + + NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]; + NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name + ofType:@"fsh" + inDirectory:@"Shaders"] + encoding:NSUTF8StringEncoding + error:nil]; + + shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}" + withString:scaler_source]; + id library = [device newLibraryWithSource:shader_source options:nil error:&error]; if (error) { NSLog(@"Error: %@", error); + if (!library) { + return; + } } id vertex_function = [library newFunctionWithName:@"vertex_shader"]; @@ -90,6 +105,7 @@ static const vector_float2 rect[] = error:&error]; if (error) { NSLog(@"Failed to created pipeline state, error %@", error); + return; } command_queue = [device newCommandQueue]; diff --git a/Shaders/AAOmniScaleLegacy.fsh b/Shaders/AAOmniScaleLegacy.fsh index f8d50d27..5bfddb91 100644 --- a/Shaders/AAOmniScaleLegacy.fsh +++ b/Shaders/AAOmniScaleLegacy.fsh @@ -18,16 +18,16 @@ vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 outpu /* Special handling for diaonals */ bool hasDownDiagonal = false; bool hasUpDiagonal = false; - if (q12 == q21 && q11 != q22) hasUpDiagonal = true; - else if (q12 != q21 && q11 == q22) hasDownDiagonal = true; - else if (q12 == q21 && q11 == q22) { - if (q11 == q12) return q11; + if (equal(q12, q21) && inequal(q11, q22)) hasUpDiagonal = true; + else if (inequal(q12, q21) && equal(q11, q22)) hasDownDiagonal = true; + else if (equal(q12, q21) && equal(q11, q22)) { + if (equal(q11, q12)) return q11; int diagonalBias = 0; for (float y = -1.0; y < 3.0; y++) { for (float x = -1.0; x < 3.0; x++) { vec4 color = texture(image, (pixel + vec2(x, y)) / input_resolution); - if (color == q11) diagonalBias++; - if (color == q12) diagonalBias--; + if (equal(color, q11)) diagonalBias++; + if (equal(color, q12)) diagonalBias--; } } if (diagonalBias <= 0) { @@ -89,15 +89,15 @@ vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 outpu min(q12d, q22d))); - if (q11d == best) { + if (equal(q11d, best)) { return q11; } - if (q21d == best) { + if (equal(q21d, best)) { return q21; } - if (q12d == best) { + if (equal(q12d, best)) { return q12; } diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh index 1801805e..220e14b3 100644 --- a/Shaders/AAScale2x.fsh +++ b/Shaders/AAScale2x.fsh @@ -22,18 +22,18 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ if (p.x > .5) { if (p.y > .5) { // Top Right - return B == F && B != D && F != H ? F : E; + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; } else { // Bottom Right - return H == F && D != H && B != F ? F : E; + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; } } else { if (p.y > .5) { // Top Left - return D == B && B != F && D != H ? D : E; + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; } else { // Bottom Left - return D == H && D != B && H != F ? D : E; + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; } } } diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh index 7e9ab728..9f378626 100644 --- a/Shaders/AAScale4x.fsh +++ b/Shaders/AAScale4x.fsh @@ -21,18 +21,18 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ if (p.x > .5) { if (p.y > .5) { // Top Right - return B == F && B != D && F != H ? F : E; + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; } else { // Bottom Right - return H == F && D != H && B != F ? F : E; + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; } } else { if (p.y > .5) { // Top Left - return D == B && B != F && D != H ? D : E; + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; } else { // Bottom Left - return D == H && D != B && H != F ? D : E; + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; } } } @@ -67,18 +67,18 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re if (p.x > .5) { if (p.y > .5) { // Top Right - R = B == F && B != D && F != H ? F : E; + R = equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; } else { // Bottom Right - R = H == F && D != H && B != F ? F : E; + R = equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; } } else { if (p.y > .5) { // Top Left - R = D == B && B != F && D != H ? D : E; + R = equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; } else { // Bottom Left - R = D == H && D != B && H != F ? D : E; + R = equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; } } diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 90c6b1a0..61a08db5 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -7,6 +7,9 @@ uniform vec2 output_resolution; uniform vec2 origin; const vec2 input_resolution = vec2(160, 144); +#define equal(x, y) ((x) == (y)) +#define inequal(x, y) ((x) != (y)) + out vec4 frag_color; #line 1 diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 5c2b8d9f..83530945 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -9,6 +9,9 @@ constant float2 input_resolution = float2(160, 144); typedef float2 vec2; typedef float3 vec3; typedef float4 vec4; +typedef texture2d sampler2D; +#define equal(x, y) all((x) == (y)) +#define inequal(x, y) any((x) != (y)) typedef struct { float4 position [[position]]; @@ -36,6 +39,8 @@ static inline float4 texture(texture2d texture, float2 pos) return float4(texture.sample(texture_sampler, pos)); } +#line 1 +{filter} fragment float4 fragment_shader(rasterizer_data in [[stage_in]], texture2d image [[ texture(0) ]], @@ -45,8 +50,9 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]], { in.texcoords.y = 1 - in.texcoords.y; if (*mix_previous) { - return mix(texture(image, in.texcoords), texture(previous_image, in.texcoords), 0.5); + return mix(scale(image, in.texcoords, input_resolution, *output_resolution), + scale(previous_image, in.texcoords, input_resolution, *output_resolution), 0.5); } - return texture(image, in.texcoords); + return scale(image, in.texcoords, input_resolution, *output_resolution); } diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index 21a5c1d6..985fc98b 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -99,7 +99,7 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re } if (P(0xbf,0x37) || P(0xdb,0x13)) { float dist = p.x - 2.0 * p.y; - float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); if (dist > pixel_size / 2) { return w1; } @@ -111,7 +111,7 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re } if (P(0xdb,0x49) || P(0xef,0x6d)) { float dist = p.y - 2.0 * p.x; - float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); if (p.y - 2.0 * p.x > pixel_size / 2) { return w3; } @@ -123,7 +123,7 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re } if (P(0xbf,0x8f) || P(0x7e,0x0e)) { float dist = p.x + 2.0 * p.y; - float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); if (dist > 1.0 + pixel_size / 2) { return w4; @@ -147,7 +147,7 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re if (P(0x7e,0x2a) || P(0xef,0xab)) { float dist = p.y + 2.0 * p.x; - float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5); + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { return w4; diff --git a/Shaders/OmniScaleLegacy.fsh b/Shaders/OmniScaleLegacy.fsh index dfeb3a01..90ace69a 100644 --- a/Shaders/OmniScaleLegacy.fsh +++ b/Shaders/OmniScaleLegacy.fsh @@ -18,16 +18,16 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re /* Special handling for diaonals */ bool hasDownDiagonal = false; bool hasUpDiagonal = false; - if (q12 == q21 && q11 != q22) hasUpDiagonal = true; - else if (q12 != q21 && q11 == q22) hasDownDiagonal = true; - else if (q12 == q21 && q11 == q22) { - if (q11 == q12) return q11; + if (equal(q12, q21) && inequal(q11, q22)) hasUpDiagonal = true; + else if (inequal(q12, q21) && equal(q11, q22)) hasDownDiagonal = true; + else if (equal(q12, q21) && equal(q11, q22)) { + if (equal(q11, q12)) return q11; int diagonalBias = 0; for (float y = -1.0; y < 3.0; y++) { for (float x = -1.0; x < 3.0; x++) { vec4 color = texture(image, (pixel + vec2(x, y)) / input_resolution); - if (color == q11) diagonalBias++; - if (color == q12) diagonalBias--; + if (equal(color, q11)) diagonalBias++; + if (equal(color, q12)) diagonalBias--; } } if (diagonalBias <= 0) { @@ -89,15 +89,15 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re min(q12d, q22d))); - if (q11d == best) { + if (equal(q11d, best)) { return q11; } - if (q21d == best) { + if (equal(q21d, best)) { return q21; } - if (q12d == best) { + if (equal(q12d, best)) { return q12; } diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh index 439b1fae..ff774403 100644 --- a/Shaders/Scale2x.fsh +++ b/Shaders/Scale2x.fsh @@ -24,18 +24,18 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re if (p.x > .5) { if (p.y > .5) { // Top Right - return B == F && B != D && F != H ? F : E; + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; } else { // Bottom Right - return H == F && D != H && B != F ? F : E; + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; } } else { if (p.y > .5) { // Top Left - return D == B && B != F && D != H ? D : E; + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; } else { // Bottom Left - return D == H && D != B && H != F ? D : E; + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; } } } diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh index 4e3b3f10..1edf8006 100644 --- a/Shaders/Scale4x.fsh +++ b/Shaders/Scale4x.fsh @@ -22,18 +22,18 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ if (p.x > .5) { if (p.y > .5) { // Top Right - return B == F && B != D && F != H ? F : E; + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; } else { // Bottom Right - return H == F && D != H && B != F ? F : E; + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; } } else { if (p.y > .5) { // Top Left - return D == B && B != F && D != H ? D : E; + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; } else { // Bottom Left - return D == H && D != B && H != F ? D : E; + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; } } } @@ -62,18 +62,18 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re if (p.x > .5) { if (p.y > .5) { // Top Right - return B == F && B != D && F != H ? F : E; + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; } else { // Bottom Right - return H == F && D != H && B != F ? F : E; + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; } } else { if (p.y > .5) { // Top Left - return D == B && B != F && D != H ? D : E; + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; } else { // Bottom Left - return D == H && D != B && H != F ? D : E; + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; } } } From a068b7b09f127257c8dada5bc83066e750e83f48 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 19:18:30 +0300 Subject: [PATCH 0678/1216] Fixed Metal warnings, made everything static for performance --- Shaders/AAOmniScaleLegacy.fsh | 6 +++--- Shaders/AAScale2x.fsh | 12 ++++++------ Shaders/AAScale4x.fsh | 22 +++++++++++----------- Shaders/Bilinear.fsh | 2 +- Shaders/HQ2x.fsh | 10 +++++----- Shaders/LCD.fsh | 2 +- Shaders/MasterShader.fsh | 1 + Shaders/MasterShader.metal | 1 + Shaders/NearestNeighbor.fsh | 2 +- Shaders/OmniScale.fsh | 6 +++--- Shaders/OmniScaleLegacy.fsh | 5 ++--- Shaders/Scale2x.fsh | 10 +++++----- Shaders/Scale4x.fsh | 20 ++++++++++---------- Shaders/SmoothBilinear.fsh | 2 +- 14 files changed, 51 insertions(+), 50 deletions(-) diff --git a/Shaders/AAOmniScaleLegacy.fsh b/Shaders/AAOmniScaleLegacy.fsh index 5bfddb91..b84e2ced 100644 --- a/Shaders/AAOmniScaleLegacy.fsh +++ b/Shaders/AAOmniScaleLegacy.fsh @@ -1,10 +1,10 @@ -float quickDistance(vec4 a, vec4 b) +STATIC float quickDistance(vec4 a, vec4 b) { return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); @@ -104,7 +104,7 @@ vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 outpu return q22; } -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = vec2(1.0, 1.0) / output_resolution; // 4-pixel super sampling diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh index 220e14b3..d51a9a6a 100644 --- a/Shaders/AAScale2x.fsh +++ b/Shaders/AAScale2x.fsh @@ -1,4 +1,4 @@ -vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -7,15 +7,15 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ // A B C // D E F // G H I - vec4 A = texture(image, position + vec2( -o.x, o.y)); + // vec4 A = texture(image, position + vec2( -o.x, o.y)); vec4 B = texture(image, position + vec2( 0, o.y)); - vec4 C = texture(image, position + vec2( o.x, o.y)); + // vec4 C = texture(image, position + vec2( o.x, o.y)); vec4 D = texture(image, position + vec2( -o.x, 0)); vec4 E = texture(image, position + vec2( 0, 0)); vec4 F = texture(image, position + vec2( o.x, 0)); - vec4 G = texture(image, position + vec2( -o.x, -o.y)); + // vec4 G = texture(image, position + vec2( -o.x, -o.y)); vec4 H = texture(image, position + vec2( 0, -o.y)); - vec4 I = texture(image, position + vec2( o.x, -o.y)); + // vec4 I = texture(image, position + vec2( o.x, -o.y)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); @@ -38,7 +38,7 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ } } -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { return mix(texture(image, position), scale2x(image, position, input_resolution, output_resolution), 0.5); } diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh index 9f378626..b59b80e9 100644 --- a/Shaders/AAScale4x.fsh +++ b/Shaders/AAScale4x.fsh @@ -1,4 +1,4 @@ -vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -6,15 +6,15 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ // A B C // D E F // G H I - vec4 A = texture(image, position + vec2( -o.x, o.y)); + // vec4 A = texture(image, position + vec2( -o.x, o.y)); vec4 B = texture(image, position + vec2( 0, o.y)); - vec4 C = texture(image, position + vec2( o.x, o.y)); + // vec4 C = texture(image, position + vec2( o.x, o.y)); vec4 D = texture(image, position + vec2( -o.x, 0)); vec4 E = texture(image, position + vec2( 0, 0)); vec4 F = texture(image, position + vec2( o.x, 0)); - vec4 G = texture(image, position + vec2( -o.x, -o.y)); + // vec4 G = texture(image, position + vec2( -o.x, -o.y)); vec4 H = texture(image, position + vec2( 0, -o.y)); - vec4 I = texture(image, position + vec2( o.x, -o.y)); + // vec4 I = texture(image, position + vec2( o.x, -o.y)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); @@ -37,12 +37,12 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ } } -vec4 aaScale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 aaScale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { return mix(texture(image, position), scale2x(image, position, input_resolution, output_resolution), 0.5); } -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / (input_resolution * 2.); @@ -51,15 +51,15 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re // A B C // D E F // G H I - vec4 A = aaScale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); + // vec4 A = aaScale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); vec4 B = aaScale2x(image, position + vec2( 0, o.y), input_resolution, output_resolution); - vec4 C = aaScale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); + // vec4 C = aaScale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); vec4 D = aaScale2x(image, position + vec2( -o.x, 0), input_resolution, output_resolution); vec4 E = aaScale2x(image, position + vec2( 0, 0), input_resolution, output_resolution); vec4 F = aaScale2x(image, position + vec2( o.x, 0), input_resolution, output_resolution); - vec4 G = aaScale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); + // vec4 G = aaScale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); vec4 H = aaScale2x(image, position + vec2( 0, -o.y), input_resolution, output_resolution); - vec4 I = aaScale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); + // vec4 I = aaScale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); vec4 R; vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh index 8283bdf0..e68e1d19 100644 --- a/Shaders/Bilinear.fsh +++ b/Shaders/Bilinear.fsh @@ -1,4 +1,4 @@ -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index a9832a02..3871db92 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -2,14 +2,14 @@ /* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ -vec3 rgb_to_hq_colospace(vec4 rgb) +STATIC vec3 rgb_to_hq_colospace(vec4 rgb) { return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); } -bool is_different(vec4 a, vec4 b) +STATIC bool is_different(vec4 a, vec4 b) { vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; @@ -17,17 +17,17 @@ bool is_different(vec4 a, vec4 b) #define P(m, r) ((pattern & (m)) == (r)) -vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) +STATIC vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) { return (c1 * w1 + c2 * w2) / (w1 + w2); } -vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) +STATIC vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) { return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); } -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; diff --git a/Shaders/LCD.fsh b/Shaders/LCD.fsh index 236456fb..d20a7c93 100644 --- a/Shaders/LCD.fsh +++ b/Shaders/LCD.fsh @@ -2,7 +2,7 @@ #define COLOR_HIGH 1.0 #define SCANLINE_DEPTH 0.1 -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pos = fract(position * input_resolution); vec2 sub_pos = fract(position * input_resolution * 6); diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 61a08db5..55cdf49f 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -9,6 +9,7 @@ const vec2 input_resolution = vec2(160, 144); #define equal(x, y) ((x) == (y)) #define inequal(x, y) ((x) != (y)) +#define STATIC out vec4 frag_color; diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 83530945..589c4aca 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -12,6 +12,7 @@ typedef float4 vec4; typedef texture2d sampler2D; #define equal(x, y) all((x) == (y)) #define inequal(x, y) any((x) != (y)) +#define STATIC static typedef struct { float4 position [[position]]; diff --git a/Shaders/NearestNeighbor.fsh b/Shaders/NearestNeighbor.fsh index 24fd66b9..7f37024c 100644 --- a/Shaders/NearestNeighbor.fsh +++ b/Shaders/NearestNeighbor.fsh @@ -1,4 +1,4 @@ -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { return texture(image, position); } diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index 985fc98b..bb2b7d65 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -8,7 +8,7 @@ */ /* We use the same colorspace as the HQ algorithms. */ -vec3 rgb_to_hq_colospace(vec4 rgb) +STATIC vec3 rgb_to_hq_colospace(vec4 rgb) { return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, @@ -16,7 +16,7 @@ vec3 rgb_to_hq_colospace(vec4 rgb) } -bool is_different(vec4 a, vec4 b) +STATIC bool is_different(vec4 a, vec4 b) { vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); return diff.x > 0.125 || diff.y > 0.027 || diff.z > 0.031; @@ -24,7 +24,7 @@ bool is_different(vec4 a, vec4 b) #define P(m, r) ((pattern & (m)) == (r)) -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; diff --git a/Shaders/OmniScaleLegacy.fsh b/Shaders/OmniScaleLegacy.fsh index 90ace69a..06849fdd 100644 --- a/Shaders/OmniScaleLegacy.fsh +++ b/Shaders/OmniScaleLegacy.fsh @@ -1,10 +1,9 @@ - -float quickDistance(vec4 a, vec4 b) +STATIC float quickDistance(vec4 a, vec4 b) { return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh index ff774403..17b6edb8 100644 --- a/Shaders/Scale2x.fsh +++ b/Shaders/Scale2x.fsh @@ -1,6 +1,6 @@ /* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */ -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -9,15 +9,15 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re // A B C // D E F // G H I - vec4 A = texture(image, position + vec2( -o.x, o.y)); + // vec4 A = texture(image, position + vec2( -o.x, o.y)); vec4 B = texture(image, position + vec2( 0, o.y)); - vec4 C = texture(image, position + vec2( o.x, o.y)); + // vec4 C = texture(image, position + vec2( o.x, o.y)); vec4 D = texture(image, position + vec2( -o.x, 0)); vec4 E = texture(image, position + vec2( 0, 0)); vec4 F = texture(image, position + vec2( o.x, 0)); - vec4 G = texture(image, position + vec2( -o.x, -o.y)); + // vec4 G = texture(image, position + vec2( -o.x, -o.y)); vec4 H = texture(image, position + vec2( 0, -o.y)); - vec4 I = texture(image, position + vec2( o.x, -o.y)); + // vec4 I = texture(image, position + vec2( o.x, -o.y)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh index 1edf8006..da1ff148 100644 --- a/Shaders/Scale4x.fsh +++ b/Shaders/Scale4x.fsh @@ -1,4 +1,4 @@ -vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; @@ -6,15 +6,15 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ // A B C // D E F // G H I - vec4 A = texture(image, position + vec2( -o.x, o.y)); + // vec4 A = texture(image, position + vec2( -o.x, o.y)); vec4 B = texture(image, position + vec2( 0, o.y)); - vec4 C = texture(image, position + vec2( o.x, o.y)); + // vec4 C = texture(image, position + vec2( o.x, o.y)); vec4 D = texture(image, position + vec2( -o.x, 0)); vec4 E = texture(image, position + vec2( 0, 0)); vec4 F = texture(image, position + vec2( o.x, 0)); - vec4 G = texture(image, position + vec2( -o.x, -o.y)); + // vec4 G = texture(image, position + vec2( -o.x, -o.y)); vec4 H = texture(image, position + vec2( 0, -o.y)); - vec4 I = texture(image, position + vec2( o.x, -o.y)); + // vec4 I = texture(image, position + vec2( o.x, -o.y)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] vec4 R; @@ -38,7 +38,7 @@ vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_ } } -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel vec2 o = 1.0 / (input_resolution * 2.); @@ -47,15 +47,15 @@ vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_re // A B C // D E F // G H I - vec4 A = scale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); + // vec4 A = scale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); vec4 B = scale2x(image, position + vec2( 0, o.y), input_resolution, output_resolution); - vec4 C = scale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); + // vec4 C = scale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); vec4 D = scale2x(image, position + vec2( -o.x, 0), input_resolution, output_resolution); vec4 E = scale2x(image, position + vec2( 0, 0), input_resolution, output_resolution); vec4 F = scale2x(image, position + vec2( o.x, 0), input_resolution, output_resolution); - vec4 G = scale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); + // vec4 G = scale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); vec4 H = scale2x(image, position + vec2( 0, -o.y), input_resolution, output_resolution); - vec4 I = scale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); + // vec4 I = scale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] p = fract(p); diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh index 2082cd2f..d5082772 100644 --- a/Shaders/SmoothBilinear.fsh +++ b/Shaders/SmoothBilinear.fsh @@ -1,4 +1,4 @@ -vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pixel = position * input_resolution - vec2(0.5, 0.5); From 1c1cddb53e86f3e3476410f4c53f488dda7fe99d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Jun 2018 20:03:59 +0300 Subject: [PATCH 0679/1216] OpenGL fallback --- Cocoa/GBView.m | 5 ++++- Cocoa/GBViewMetal.h | 2 +- Cocoa/GBViewMetal.m | 8 ++++++++ Makefile | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 1504cdde..a3c5a75b 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -26,7 +26,10 @@ + (instancetype)allocWithZone:(struct _NSZone *)zone { if (self == [GBView class]) { - return [GBViewMetal allocWithZone: zone]; + if ([GBViewMetal isSupported]) { + return [GBViewMetal allocWithZone: zone]; + } + return [GBViewGL allocWithZone: zone]; } return [super allocWithZone:zone]; } diff --git a/Cocoa/GBViewMetal.h b/Cocoa/GBViewMetal.h index a28a3431..521c3c72 100644 --- a/Cocoa/GBViewMetal.h +++ b/Cocoa/GBViewMetal.h @@ -3,5 +3,5 @@ #import "GBView.h" @interface GBViewMetal : GBView - ++ (bool) isSupported; @end diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 2bd69fe1..4d2bac35 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -29,6 +29,14 @@ static const vector_float2 rect[] = vector_float2 output_resolution; } ++ (bool)isSupported +{ + if (MTLCopyAllDevices) { + return [MTLCopyAllDevices() count]; + } + return false; +} + - (void)createInternalView { MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; diff --git a/Makefile b/Makefile index 85a92bd2..9156b225 100755 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ ifeq ($(PLATFORM),Darwin) SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 -LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -framework Metal -framework MetalKit +LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations From 968ff4879a4e7dd98e998de4d04e3a7029f4fe60 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jun 2018 00:08:24 +0300 Subject: [PATCH 0680/1216] Enable fast math --- Cocoa/GBViewMetal.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 4d2bac35..0678d9f8 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -89,8 +89,10 @@ static const vector_float2 rect[] = shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}" withString:scaler_source]; + MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; + options.fastMathEnabled = YES; id library = [device newLibraryWithSource:shader_source - options:nil + options:options error:&error]; if (error) { NSLog(@"Error: %@", error); From c2862036406e60096bd9ccdae7e8b2130b1f4a0f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jun 2018 12:46:57 +0300 Subject: [PATCH 0681/1216] Fixed potential black screen on Metal --- Cocoa/GBViewMetal.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 0678d9f8..34cd50bd 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -66,6 +66,7 @@ static const vector_float2 rect[] = length:sizeof(output_resolution) options:MTLResourceStorageModeShared]; + output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height}; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil]; [self loadShader]; } From 45c73e0175ed41d79cd733fc71e159bd7933dff6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jun 2018 13:59:33 +0300 Subject: [PATCH 0682/1216] Replaced the is_cgb bool with a more future compatible model enum. Removed the GB_init_cgb API and replaced it with an extended GB_init and GB_switch_model_and_reset APIs that now receive a model parameter. Increased the struct version. --- Cocoa/Document.m | 14 +++++++-- Cocoa/GBMemoryByteArray.m | 8 +++--- Core/apu.c | 10 +++---- Core/debugger.c | 6 ++-- Core/display.c | 54 +++++++++++++++++------------------ Core/gb.c | 47 ++++++++++++++---------------- Core/gb.h | 37 ++++++++++++++++++++---- Core/memory.c | 40 +++++++++++++------------- Core/z80_cpu.c | 8 +++--- QuickLook/get_image_for_rom.c | 2 +- SDL/main.c | 22 ++++++++------ Tester/main.c | 4 +-- libretro/libretro.c | 21 ++++++++------ 13 files changed, 155 insertions(+), 118 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 40b68477..78dfd776 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -18,6 +18,13 @@ enum model { MODEL_AGB, }; +static const GB_model_t cocoa_to_internal_model[] = +{ + [MODEL_DMG] = GB_MODEL_DMG_B, + [MODEL_CGB] = GB_MODEL_CGB_E, + [MODEL_AGB] = GB_MODEL_AGB +}; + @interface Document () { /* NSTextViews freeze the entire app if they're modified too often and too quickly. @@ -137,21 +144,22 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) initDMG { current_model = MODEL_DMG; - GB_init(&gb); + GB_init(&gb, cocoa_to_internal_model[current_model]); [self initCommon]; GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]); } - (void) initCGB { - GB_init_cgb(&gb); [self initCommon]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]) { current_model = MODEL_AGB; + GB_init(&gb, cocoa_to_internal_model[current_model]); GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"agb_boot" ofType:@"bin"] UTF8String]); } else { current_model = MODEL_CGB; + GB_init(&gb, cocoa_to_internal_model[current_model]); GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]); } } @@ -250,7 +258,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } else { current_model = (enum model)[sender tag]; - GB_switch_model_and_reset(&gb, current_model != MODEL_DMG); + GB_switch_model_and_reset(&gb, cocoa_to_internal_model[current_model]); static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot"}; GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:boot_names[current_model - 1] ofType:@"bin"] UTF8String]); } diff --git a/Cocoa/GBMemoryByteArray.m b/Cocoa/GBMemoryByteArray.m index 26555791..32526ade 100644 --- a/Cocoa/GBMemoryByteArray.m +++ b/Cocoa/GBMemoryByteArray.m @@ -54,7 +54,7 @@ break; case GBMemoryVRAM: bank_backup = gb->cgb_vram_bank; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { gb->cgb_vram_bank = self.selectedBank; } addr += 0x8000; @@ -66,7 +66,7 @@ break; case GBMemoryRAM: bank_backup = gb->cgb_ram_bank; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { gb->cgb_ram_bank = self.selectedBank; } addr += 0xC000; @@ -127,7 +127,7 @@ break; case GBMemoryVRAM: bank_backup = gb->cgb_vram_bank; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { gb->cgb_vram_bank = self.selectedBank; } addr += 0x8000; @@ -139,7 +139,7 @@ break; case GBMemoryRAM: bank_backup = gb->cgb_ram_bank; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { gb->cgb_ram_bank = self.selectedBank; } addr += 0xC000; diff --git a/Core/apu.c b/Core/apu.c index 4f8f5c6b..b5574c9f 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -468,7 +468,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) }; if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { - if (!gb->is_cgb && !gb->apu.wave_channel.wave_form_just_read) { + if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { return 0xFF; } reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; @@ -479,7 +479,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { - if (!gb->apu.global_enable && reg != GB_IO_NR52 && (gb->is_cgb || + if (!gb->apu.global_enable && reg != GB_IO_NR52 && (GB_is_cgb(gb) || ( reg != GB_IO_NR11 && reg != GB_IO_NR21 && @@ -491,7 +491,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { - if (!gb->is_cgb && !gb->apu.wave_channel.wave_form_just_read) { + if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { return; } reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; @@ -533,7 +533,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.global_enable = false; } - if (!gb->is_cgb && (value & 0x80)) { + if (!GB_is_cgb(gb) && (value & 0x80)) { GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]); GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]); GB_apu_write(gb, GB_IO_NR31, old_nrx1[2]); @@ -694,7 +694,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((value & 0x80)) { /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU reads from it. */ - if (!gb->is_cgb && + if (!GB_is_cgb(gb) && gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.enable) { diff --git a/Core/debugger.c b/Core/debugger.c index a843f9bc..3382ef33 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -112,7 +112,7 @@ static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) gb->mbc_rom_bank = bank; gb->mbc_ram_bank = bank; gb->mbc_ram_enable = true; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { gb->cgb_ram_bank = bank & 7; gb->cgb_vram_bank = bank & 1; if (gb->cgb_ram_bank == 0) { @@ -1462,7 +1462,7 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const d return true; } - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { GB_log(gb, "Not available on a DMG.\n"); return true; } @@ -1495,7 +1495,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } GB_log(gb, "LCDC:\n"); GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); - GB_log(gb, " %s: %s\n", gb->is_cgb? (gb->cgb_mode? "Sprite priority flags" : "Background and Window") : "Background", + GB_log(gb, " %s: %s\n", GB_is_cgb(gb)? (gb->cgb_mode? "Sprite priority flags" : "Background and Window") : "Background", (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); diff --git a/Core/display.c b/Core/display.c index 20580dee..0fa6e7ba 100644 --- a/Core/display.c +++ b/Core/display.c @@ -113,7 +113,7 @@ typedef struct __attribute__((packed)) { static bool window_enabled(GB_gameboy_t *gb) { if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { - if (!gb->cgb_mode && gb->is_cgb) { + if (!gb->cgb_mode && GB_is_cgb(gb)) { return false; } } @@ -203,7 +203,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index) { - if (!gb->rgb_encode_callback || !gb->is_cgb) return; + if (!gb->rgb_encode_callback || !GB_is_cgb(gb)) return; uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); @@ -213,7 +213,7 @@ void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode) { gb->color_correction_mode = mode; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, false, i * 2); GB_palette_changed(gb, true, i * 2); @@ -236,7 +236,7 @@ void GB_STAT_update(GB_gameboy_t *gb) bool previous_interrupt_line = gb->stat_interrupt_line; /* Set LY=LYC bit */ - if (gb->ly_for_comparison != (uint16_t)-1 || !gb->is_cgb) { + if (gb->ly_for_comparison != (uint16_t)-1 || !GB_is_cgb(gb)) { if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { gb->lyc_interrupt_line = true; gb->io_registers[GB_IO_STAT] |= 4; @@ -362,7 +362,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bg_enabled = false; } } - if (!gb->is_cgb && gb->in_window) { + if (!GB_is_cgb(gb) && gb->in_window) { bg_enabled = true; } @@ -434,7 +434,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { /* This value is cached on the CGB, so it cannot be used to mix tiles together */ /* Todo: This is NOT true on CGB-B! This is likely the case for all CGBs prior to D. Currently, SameBoy is emulating CGB-E, but if other revisions are added in the future @@ -442,7 +442,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_y = y; } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. This probably means the CGB has a 16-bit data bus for the VRAM. */ gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; @@ -456,7 +456,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) case GB_FETCHER_GET_TILE_DATA_LOWER: { uint8_t y_flip = 0; uint16_t tile_address = 0; - uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); + uint8_t y = GB_is_cgb(gb)? gb->fetcher_y : fetcher_y(gb); /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ if (gb->io_registers[GB_IO_LCDC] & 0x10) { @@ -483,7 +483,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) bit mid-fetching causes a glitched mixing of the two, in comparison to the more logical DMG version. */ uint16_t tile_address = 0; - uint8_t y = gb->is_cgb? gb->fetcher_y : fetcher_y(gb); + uint8_t y = GB_is_cgb(gb)? gb->fetcher_y : fetcher_y(gb); if (gb->io_registers[GB_IO_LCDC] & 0x10) { tile_address = gb->current_tile * 0x10; @@ -571,7 +571,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) return; } - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { GB_SLEEP(gb, display, 23, 1); } @@ -593,9 +593,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] |= 3; gb->mode_for_interrupt = 3; gb->oam_read_blocked = true; - gb->vram_read_blocked = !gb->is_cgb; + gb->vram_read_blocked = !GB_is_cgb(gb); gb->oam_write_blocked = true; - gb->vram_write_blocked = !gb->is_cgb; + gb->vram_write_blocked = !GB_is_cgb(gb); GB_STAT_update(gb); gb->cycles_for_line += 2; @@ -634,7 +634,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { - gb->oam_write_blocked = gb->is_cgb; + gb->oam_write_blocked = GB_is_cgb(gb); gb->accessed_oam_row = 0; GB_SLEEP(gb, display, 6, 3); gb->io_registers[GB_IO_LY] = gb->current_line; @@ -647,7 +647,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->mode_for_interrupt = 2; gb->io_registers[GB_IO_STAT] &= ~3; } - else if (!gb->is_cgb) { + else if (!GB_is_cgb(gb)) { gb->io_registers[GB_IO_STAT] &= ~3; } GB_STAT_update(gb); @@ -665,19 +665,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->n_visible_objs = 0; for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { add_object_from_index(gb, gb->oam_search_index); /* The CGB does not care about the accessed OAM row as there's no OAM bug*/ } GB_SLEEP(gb, display, 8, 2); - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { add_object_from_index(gb, gb->oam_search_index); gb->accessed_oam_row = (gb->oam_search_index & ~1) * 4 + 8; } if (gb->oam_search_index == 37) { - gb->vram_read_blocked = !gb->is_cgb; + gb->vram_read_blocked = !GB_is_cgb(gb); gb->vram_write_blocked = false; - gb->oam_write_blocked = gb->is_cgb; + gb->oam_write_blocked = GB_is_cgb(gb); GB_STAT_update(gb); } } @@ -719,7 +719,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->n_visible_objs--; } while (gb->n_visible_objs != 0 && - (gb->io_registers[GB_IO_LCDC] & 2 || gb->is_cgb) && + (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { while (gb->fetcher_state < 5) { @@ -864,17 +864,17 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_LY] = 153; gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 14, gb->is_cgb? 4: 6); + GB_SLEEP(gb, display, 14, GB_is_cgb(gb)? 4: 6); - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { gb->io_registers[GB_IO_LY] = 0; } gb->ly_for_comparison = 153; GB_STAT_update(gb); - GB_SLEEP(gb, display, 15, gb->is_cgb? 4: 2); + GB_SLEEP(gb, display, 15, GB_is_cgb(gb)? 4: 2); gb->io_registers[GB_IO_LY] = 0; - gb->ly_for_comparison = gb->is_cgb? 153 : -1; + gb->ly_for_comparison = GB_is_cgb(gb)? 153 : -1; GB_STAT_update(gb); GB_SLEEP(gb, display, 16, 4); @@ -896,7 +896,7 @@ void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette uint32_t none_palette[4]; uint32_t *palette = NULL; - switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) { default: case GB_PALETTE_NONE: none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); @@ -915,7 +915,7 @@ void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette for (unsigned y = 0; y < 192; y++) { for (unsigned x = 0; x < 256; x++) { - if (x >= 128 && !gb->is_cgb) { + if (x >= 128 && !GB_is_cgb(gb)) { *(dest++) = gb->background_palettes_rgb[0]; continue; } @@ -947,7 +947,7 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette uint32_t *palette = NULL; uint16_t map = 0x1800; - switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) { case GB_PALETTE_NONE: none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); @@ -1048,7 +1048,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h uint16_t vram_address = dest[i].tile * 0x10; uint8_t flags = dest[i].flags; uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0); - if (gb->is_cgb && (flags & 0x8)) { + if (GB_is_cgb(gb) && (flags & 0x8)) { vram_address += 0x2000; } diff --git a/Core/gb.c b/Core/gb.c index da1e9419..1f02a3fe 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -90,11 +90,18 @@ static char *default_async_input_callback(GB_gameboy_t *gb) } #endif -void GB_init(GB_gameboy_t *gb) +void GB_init(GB_gameboy_t *gb, GB_model_t model) { memset(gb, 0, sizeof(*gb)); - gb->ram = malloc(gb->ram_size = 0x2000); - gb->vram = malloc(gb->vram_size = 0x2000); + gb->model = model; + if (GB_is_cgb(gb)) { + gb->ram = malloc(gb->ram_size = 0x2000 * 8); + gb->vram = malloc(gb->vram_size = 0x2000 * 2); + } + else { + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + } #ifndef DISABLE_DEBUGGER gb->input_callback = default_input_callback; @@ -106,21 +113,9 @@ void GB_init(GB_gameboy_t *gb) GB_reset(gb); } -void GB_init_cgb(GB_gameboy_t *gb) +GB_model_t GB_get_model(GB_gameboy_t *gb) { - memset(gb, 0, sizeof(*gb)); - gb->ram = malloc(gb->ram_size = 0x2000 * 8); - gb->vram = malloc(gb->vram_size = 0x2000 * 2); - gb->is_cgb = true; - -#ifndef DISABLE_DEBUGGER - gb->input_callback = default_input_callback; - gb->async_input_callback = default_async_input_callback; -#endif - gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type - gb->clock_multiplier = 1.0; - - GB_reset(gb); + return gb->model; } void GB_free(GB_gameboy_t *gb) @@ -339,7 +334,7 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) { - if (!gb->rgb_encode_callback && !gb->is_cgb) { + if (!gb->rgb_encode_callback && !GB_is_cgb(gb)) { gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = callback(gb, 0xFF, 0xFF, 0xFF); gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = @@ -424,7 +419,7 @@ bool GB_is_inited(GB_gameboy_t *gb) bool GB_is_cgb(GB_gameboy_t *gb) { - return gb->is_cgb; + return ((gb->model) & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; } void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip) @@ -451,7 +446,7 @@ void GB_set_user_data(GB_gameboy_t *gb, void *data) void GB_reset(GB_gameboy_t *gb) { uint32_t mbc_ram_size = gb->mbc_ram_size; - bool cgb = gb->is_cgb; + bool cgb = GB_is_cgb(gb); memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); gb->version = GB_STRUCT_VERSION; @@ -466,7 +461,7 @@ void GB_reset(GB_gameboy_t *gb) gb->vram_size = 0x2000 * 2; memset(gb->vram, 0, gb->vram_size); - gb->is_cgb = true; + gb->model = GB_MODEL_CGB_E; gb->cgb_mode = true; } else { @@ -491,16 +486,17 @@ void GB_reset(GB_gameboy_t *gb) gb->io_registers[GB_IO_SC] = 0x7E; /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */ - gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = gb->is_cgb? 0x00 : 0xFF; + gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF; gb->accessed_oam_row = -1; gb->magic = (uintptr_t)'SAME'; } -void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb) +void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) { - if (is_cgb) { + gb->model = model; + if (GB_is_cgb(gb)) { gb->ram = realloc(gb->ram, gb->ram_size = 0x2000 * 8); gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2); } @@ -508,7 +504,6 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb) gb->ram = realloc(gb->ram, gb->ram_size = 0x2000); gb->vram = realloc(gb->vram, gb->vram_size = 0x2000); } - gb->is_cgb = is_cgb; GB_rewind_free(gb); GB_reset(gb); } @@ -553,7 +548,7 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * *bank = 0; return &gb->io_registers; case GB_DIRECT_ACCESS_BOOTROM: - *size = gb->is_cgb? sizeof(gb->boot_rom) : 0x100; + *size = GB_is_cgb(gb)? sizeof(gb->boot_rom) : 0x100; *bank = 0; return &gb->boot_rom; case GB_DIRECT_ACCESS_OAM: diff --git a/Core/gb.h b/Core/gb.h index afd5c275..31840d94 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -20,7 +20,34 @@ #include "z80_cpu.h" #include "symbol_hash.h" -#define GB_STRUCT_VERSION 12 +#define GB_STRUCT_VERSION 13 + +typedef enum { +#ifdef GB_INTERNAL + GB_MODEL_FAMILY_MASK = 0xF00, + GB_MODEL_DMG_FAMILY = 0x000, +#endif + // GB_MODEL_DMG_0 = 0x000, + // GB_MODEL_DMG_A = 0x001, + GB_MODEL_DMG_B = 0x002, + // GB_MODEL_DMG_C = 0x003, + // GB_MODEL_SGB = 0x004, +#ifdef GB_INTERNAL + GB_MODEL_MGB_FAMILY = 0x100, +#endif + // GB_MODEL_MGB = 0x100, + // GB_MODEL_SGB2 = 0x101, +#ifdef GB_INTERNAL + GB_MODEL_CGB_FAMILY = 0x200, +#endif + // GB_MODEL_CGB_0 = 0x200, + // GB_MODEL_CGB_A = 0x201, + // GB_MODEL_CGB_B = 0x202, + // GB_MODEL_CGB_C = 0x203, + // GB_MODEL_CGB_D = 0x204, + GB_MODEL_CGB_E = 0x205, + GB_MODEL_AGB = 0x206, +} GB_model_t; enum { GB_REGISTER_AF, @@ -261,8 +288,8 @@ struct GB_gameboy_internal_s { uint8_t cgb_ram_bank; /* CPU and General Hardware Flags*/ + GB_model_t model; bool cgb_mode; - bool is_cgb; bool cgb_double_speed; bool halted; bool stopped; @@ -547,13 +574,13 @@ struct GB_gameboy_s { __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #endif -void GB_init(GB_gameboy_t *gb); -void GB_init_cgb(GB_gameboy_t *gb); +void GB_init(GB_gameboy_t *gb, GB_model_t model); bool GB_is_inited(GB_gameboy_t *gb); bool GB_is_cgb(GB_gameboy_t *gb); +GB_model_t GB_get_model(GB_gameboy_t *gb); void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); -void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb); +void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model); /* Returns the time passed, in 4MHz ticks. */ uint8_t GB_run(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index f280c94a..43cb0494 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -24,7 +24,7 @@ static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) return GB_BUS_MAIN; } if (addr < 0xFE00) { - return gb->is_cgb? GB_BUS_RAM : GB_BUS_MAIN; + return GB_is_cgb(gb)? GB_BUS_RAM : GB_BUS_MAIN; } return GB_BUS_INTERNAL; } @@ -46,7 +46,7 @@ static uint8_t bitwise_glitch_read_increase(uint8_t a, uint8_t b, uint8_t c, uin void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) { - if (gb->is_cgb) return; + if (GB_is_cgb(gb)) return; if (address >= 0xFE00 && address < 0xFF00) { if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) { @@ -65,7 +65,7 @@ void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) { - if (gb->is_cgb) return; + if (GB_is_cgb(gb)) return; if (address >= 0xFE00 && address < 0xFF00) { if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) { @@ -86,7 +86,7 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address) { - if (gb->is_cgb) return; + if (GB_is_cgb(gb)) return; if (address >= 0xFE00 && address < 0xFF00) { if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 0x20 && gb->accessed_oam_row < 0x98) { @@ -119,7 +119,7 @@ static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) return gb->boot_rom[addr]; } - if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->boot_rom_finished) { + if (addr >= 0x200 && addr < 0x900 && GB_is_cgb(gb) && !gb->boot_rom_finished) { return gb->boot_rom[addr]; } @@ -208,7 +208,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (gb->oam_read_blocked) { - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { if (addr < 0xFEA0) { if (gb->accessed_oam_row == 0) { gb->oam[(addr & 0xf8)] = @@ -249,7 +249,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */ return 0xFF; } - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { return (addr & 0xF0) | ((addr >> 4) & 0xF); } } @@ -275,11 +275,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE; case GB_IO_PCM_12: - if (!gb->is_cgb) return 0xFF; + if (!GB_is_cgb(gb)) return 0xFF; return (gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0); case GB_IO_PCM_34: - if (!gb->is_cgb) return 0xFF; + if (!GB_is_cgb(gb)) return 0xFF; return (gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0); case GB_IO_JOYP: @@ -314,7 +314,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return gb->cgb_ram_bank | ~0x7; case GB_IO_VBK: - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { return 0xFF; } return gb->cgb_vram_bank | ~0x1; @@ -322,7 +322,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) /* Todo: It seems that a CGB in DMG mode can access BGPI and OBPI, but not BGPD and OBPD? */ case GB_IO_BGPI: case GB_IO_OBPI: - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { return 0xFF; } return gb->io_registers[addr & 0xFF] | 0x40; @@ -357,11 +357,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } case GB_IO_UNKNOWN2: case GB_IO_UNKNOWN3: - return gb->is_cgb? gb->io_registers[addr & 0xFF] : 0xFF; + return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] : 0xFF; case GB_IO_UNKNOWN4: return gb->cgb_mode? gb->io_registers[addr & 0xFF] : 0xFF; case GB_IO_UNKNOWN5: - return gb->is_cgb? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF; + return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF; default: if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { return GB_apu_read(gb, addr & 0xFF); @@ -531,7 +531,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { if (addr < 0xFEA0) { gb->oam[addr & 0xFF] = value; } @@ -599,7 +599,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* TODO: Probably completely wrong in double speed mode */ /* TODO: This hack is disgusting */ - if (gb->display_state == 29 && gb->is_cgb) { + if (gb->display_state == 29 && GB_is_cgb(gb)) { gb->ly_for_comparison = 153; GB_STAT_update(gb); gb->ly_for_comparison = 0; @@ -609,14 +609,14 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* These are the states when LY changes, let the display routine call GB_STAT_update for use so it correctly handles T-cycle accurate LYC writes */ - if (!gb->is_cgb || ( + if (!GB_is_cgb(gb) || ( gb->display_state != 6 && gb->display_state != 26 && gb->display_state != 15 && gb->display_state != 16)) { /* More hacks to make LYC write conflicts work */ - if (gb->display_state == 14 && gb->is_cgb) { + if (gb->display_state == 14 && GB_is_cgb(gb)) { gb->ly_for_comparison = 153; GB_STAT_update(gb); gb->ly_for_comparison = -1; @@ -691,7 +691,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DMG_EMULATION: - if (gb->is_cgb && !gb->boot_rom_finished) { + if (GB_is_cgb(gb) && !gb->boot_rom_finished) { gb->cgb_mode = value != 4; /* The real "contents" of this register aren't quite known yet. */ } return; @@ -728,7 +728,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_BGPI: case GB_IO_OBPI: - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { return; } gb->io_registers[addr & 0xFF] = value; @@ -823,7 +823,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_RP: { - if (!gb->is_cgb) { + if (!GB_is_cgb(gb)) { return; } if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) { diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 721008fb..26cadd65 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -77,7 +77,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) assert(gb->pending_cycles); GB_conflict_t conflict = GB_CONFLICT_READ_OLD; if ((addr & 0xFF80) == 0xFF00) { - conflict = (gb->is_cgb? cgb_conflict_map : dmg_conflict_map)[addr & 0x7F]; + conflict = (GB_is_cgb(gb)? cgb_conflict_map : dmg_conflict_map)[addr & 0x7F]; } switch (conflict) { case GB_CONFLICT_READ_OLD: @@ -158,7 +158,7 @@ static void cycle_no_access(GB_gameboy_t *gb) static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) { - if (gb->is_cgb) { + if (GB_is_cgb(gb)) { /* Slight optimization */ gb->pending_cycles += 4; return; @@ -1381,14 +1381,14 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } - if (gb->halted && !gb->is_cgb && !gb->just_halted) { + if (gb->halted && !GB_is_cgb(gb) && !gb->just_halted) { GB_advance_cycles(gb, 2); } uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; if (gb->halted) { - GB_advance_cycles(gb, (gb->is_cgb || gb->just_halted) ? 4 : 2); + GB_advance_cycles(gb, (GB_is_cgb(gb) || gb->just_halted) ? 4 : 2); } gb->just_halted = false; diff --git a/QuickLook/get_image_for_rom.c b/QuickLook/get_image_for_rom.c index 25f8e2bf..e0482ec7 100755 --- a/QuickLook/get_image_for_rom.c +++ b/QuickLook/get_image_for_rom.c @@ -47,7 +47,7 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) int get_image_for_rom(const char *filename, const char *boot_path, uint32_t *output, uint8_t *cgb_flag) { GB_gameboy_t gb; - GB_init_cgb(&gb); + GB_init(&gb, GB_MODEL_CGB_E); if (GB_load_boot_rom(&gb, boot_path)) { GB_free(&gb); return 1; diff --git a/SDL/main.c b/SDL/main.c index f3ee39e7..9a1c3d61 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -26,13 +26,19 @@ static bool paused = false; static uint32_t pixel_buffer_1[160*144], pixel_buffer_2[160*144]; static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; - static char *filename = NULL; static bool should_free_filename = false; static char *battery_save_path_ptr; SDL_AudioDeviceID device_id; +static const GB_model_t sdl_to_internal_model[] = +{ + [MODEL_DMG] = GB_MODEL_DMG_B, + [MODEL_CGB] = GB_MODEL_CGB_E, + [MODEL_AGB] = GB_MODEL_AGB +}; + void set_filename(const char *new_filename, bool new_should_free) { if (filename && should_free_filename) { @@ -364,15 +370,10 @@ static void run(void) pending_command = GB_SDL_NO_COMMAND; restart: if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, configuration.model != MODEL_DMG); + GB_switch_model_and_reset(&gb, sdl_to_internal_model[configuration.model]); } else { - if (configuration.model == MODEL_DMG) { - GB_init(&gb); - } - else { - GB_init_cgb(&gb); - } + GB_init(&gb, sdl_to_internal_model[configuration.model]); GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_pixels_output(&gb, active_pixel_buffer); @@ -454,7 +455,7 @@ int main(int argc, char **argv) signal(SIGINT, debugger_interrupt); - SDL_Init( SDL_INIT_EVERYTHING ); + SDL_Init(SDL_INIT_EVERYTHING); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); @@ -527,6 +528,9 @@ int main(int argc, char **argv) fread(&configuration, 1, sizeof(configuration), prefs_file); fclose(prefs_file); } + if (configuration.model >= MODEL_MAX) { + configuration.model = MODEL_CGB; + } atexit(save_configuration); diff --git a/Tester/main.c b/Tester/main.c index 8c33e049..be5f9e83 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -295,14 +295,14 @@ int main(int argc, char **argv) fprintf(stderr, "Testing ROM %s\n", filename); if (dmg) { - GB_init(&gb); + GB_init(&gb, GB_MODEL_DMG_B); if (GB_load_boot_rom(&gb, boot_rom_path? boot_rom_path : executable_relative_path("dmg_boot.bin"))) { perror("Failed to load boot ROM"); exit(1); } } else { - GB_init_cgb(&gb); + GB_init(&gb, GB_MODEL_CGB_E); if (GB_load_boot_rom(&gb, boot_rom_path? boot_rom_path : executable_relative_path("cgb_boot.bin"))) { perror("Failed to load boot ROM"); exit(1); diff --git a/libretro/libretro.c b/libretro/libretro.c index 275df1d9..c09de575 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -52,6 +52,13 @@ enum model { MODEL_AUTO }; +static const GB_model_t libretro_to_internal_model[] = +{ + [MODEL_DMG] = GB_MODEL_DMG_B, + [MODEL_CGB] = GB_MODEL_CGB_E, + [MODEL_AGB] = GB_MODEL_AGB +}; + enum screen_layout { LAYOUT_TOP_DOWN, LAYOUT_LEFT_RIGHT @@ -317,15 +324,11 @@ static void init_for_current_model(void) for (i = 0; i < emulated_devices; i++) { - if (GB_is_inited(&gameboy[i])) - GB_switch_model_and_reset(&gameboy[i], effective_model[i] != MODEL_DMG); - - else - { - if (effective_model[i] == MODEL_DMG) - GB_init(&gameboy[i]); - else - GB_init_cgb(&gameboy[i]); + if (GB_is_inited(&gameboy[i])) { + GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model[i]]); + } + else { + GB_init(&gameboy[i], libretro_to_internal_model[effective_model[i]]); } const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model[i]]; const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model[i]]; From bc876ec30c8cd387a210b1a156aa733d26ebe343 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jun 2018 14:36:06 +0300 Subject: [PATCH 0683/1216] Whoops --- Cocoa/Document.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 78dfd776..c1cc7640 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -141,17 +141,17 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return self; } +/* Todo: Unify the 3 init functions */ - (void) initDMG { current_model = MODEL_DMG; GB_init(&gb, cocoa_to_internal_model[current_model]); - [self initCommon]; GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]); + [self initCommon]; } - (void) initCGB { - [self initCommon]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]) { current_model = MODEL_AGB; GB_init(&gb, cocoa_to_internal_model[current_model]); @@ -162,6 +162,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, GB_init(&gb, cocoa_to_internal_model[current_model]); GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]); } + [self initCommon]; } - (void) initCommon From ca9249d4db27cd4af1612543d0967c71f8aebabe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jun 2018 14:46:16 +0300 Subject: [PATCH 0684/1216] Prevent memory viewer errors on reset --- Cocoa/Document.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c1cc7640..5fe30c1b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -278,7 +278,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (hex_controller) { /* Verify bank sanity, especially when switching models. */ [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:0]; - [self hexUpdateBank:self.memoryBankInput]; + [self hexUpdateBank:self.memoryBankInput ignoreErrors:true]; } } @@ -911,7 +911,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } } -- (IBAction)hexUpdateBank:(NSControl *)sender +- (void)hexUpdateBank:(NSControl *)sender ignoreErrors: (bool)ignore_errors { NSString *error = [self captureOutputForBlock:^{ uint16_t addr, bank; @@ -954,12 +954,17 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [hex_controller reloadData]; }]; - if (error) { + if (error && !ignore_errors) { NSBeep(); [GBWarningPopover popoverWithContents:error onView:sender]; } } +- (IBAction)hexUpdateBank:(NSControl *)sender +{ + [self hexUpdateBank:sender ignoreErrors:false]; +} + - (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender { self.memoryBankItem.enabled = [sender indexOfSelectedItem] != GBMemoryEntireSpace; From d6879c4f8ad67b4042c0d3af934fda0c89deb2c7 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 16 Jun 2018 11:05:07 -0500 Subject: [PATCH 0685/1216] libretro: allow changing model at runtime again, also do not reset both gameboys on model change in dual mode --- libretro/libretro.c | 85 ++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index c09de575..44ebbb96 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -233,7 +233,7 @@ static const struct retro_variable vars_single[] = { { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated model (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model", "Emulated model; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { NULL } }; @@ -244,8 +244,8 @@ static const struct retro_variable vars_single_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -259,8 +259,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2 (reload); Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -310,40 +310,37 @@ static void set_link_cable_state(bool state) } } -static void init_for_current_model(void) +static void init_for_current_model(unsigned id) { - unsigned i = 0; - enum model effective_model[2]; - for (i=0; i < emulated_devices; i++) - { - effective_model[i] = model[i]; - if (effective_model[i] == MODEL_AUTO) { - effective_model[i] = auto_model; - } + unsigned i = id; + enum model effective_model; + + effective_model = model[i]; + if (effective_model == MODEL_AUTO) { + effective_model = auto_model; } - for (i = 0; i < emulated_devices; i++) - { - if (GB_is_inited(&gameboy[i])) { - GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model[i]]); - } - else { - GB_init(&gameboy[i], libretro_to_internal_model[effective_model[i]]); - } - const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model[i]]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model[i]]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model[i]]; - char buf[256]; - snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); - log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); - if (GB_load_boot_rom(&gameboy[i], buf)) - GB_load_boot_rom_from_buffer(&gameboy[i], boot_code, boot_length); - GB_set_user_data(&gameboy[i], (void*)NULL); - GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); - GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); - GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); + if (GB_is_inited(&gameboy[i])) { + GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]); } + else { + GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); + } + const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model]; + + char buf[256]; + snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); + log_cb(RETRO_LOG_INFO, "Initializing as model: %s\n", model_name); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + if (GB_load_boot_rom(&gameboy[i], buf)) + GB_load_boot_rom_from_buffer(&gameboy[i], boot_code, boot_length); + GB_set_user_data(&gameboy[i], (void*)NULL); + GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); + GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); + GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); @@ -401,7 +398,6 @@ static void init_for_current_model(void) static void check_variables(bool link) { struct retro_variable var = {0}; - if (!link) { var.key = "sameboy_color_correction_mode"; @@ -445,6 +441,7 @@ static void check_variables(bool link) new_model = MODEL_AUTO; model[0] = new_model; + init_for_current_model(0); } } else @@ -515,7 +512,11 @@ static void check_variables(bool link) else new_model = MODEL_AUTO; - model[0] = new_model; + if (model[0] != new_model) + { + model[0] = new_model; + init_for_current_model(0); + } } var.key = "sameboy_model_2"; @@ -532,7 +533,11 @@ static void check_variables(bool link) else new_model = MODEL_AUTO; - model[1] = new_model; + if (model[1] != new_model) + { + model[1] = new_model; + init_for_current_model(1); + } } } @@ -790,6 +795,7 @@ bool retro_load_game(const struct retro_game_info *info) { environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); check_variables(false); + if (sameboy_dual) { emulated_devices = 2; @@ -816,10 +822,9 @@ bool retro_load_game(const struct retro_game_info *info) auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - init_for_current_model(); - for (int i = 0; i < emulated_devices; i++) { + init_for_current_model(i); if (GB_load_rom(&gameboy[i],info->path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM at %s\n", info->path); @@ -905,9 +910,9 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - init_for_current_model(); for (int i = 0; i < emulated_devices; i++) { + init_for_current_model(i); if (GB_load_rom(&gameboy[i], info[i].path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); From d81c23cb16711e3daf05fd3c933b73f8fd1a1a8e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jun 2018 23:52:24 +0300 Subject: [PATCH 0686/1216] Fixed HDMA regression --- Core/memory.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 43cb0494..ee9ad188 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -906,15 +906,17 @@ void GB_dma_run(GB_gameboy_t *gb) void GB_hdma_run(GB_gameboy_t *gb) { if (!gb->hdma_on) return; + while (gb->hdma_cycles >= 0x4) { gb->hdma_cycles -= 0x4; GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); if ((gb->hdma_current_dest & 0xf) == 0) { - if(--gb->hdma_steps_left == 0){ + if (--gb->hdma_steps_left == 0) { gb->hdma_on = false; gb->hdma_on_hblank = false; + gb->hdma_starting = false; gb->io_registers[GB_IO_HDMA5] &= 0x7F; break; } From a4bfb026a867da3587b5d4241b9cd2d65f6948cc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 17 Jun 2018 23:16:34 +0300 Subject: [PATCH 0687/1216] Fixed rounded corners in Metal --- Cocoa/Document.m | 2 ++ Cocoa/Document.xib | 10 ++-------- Cocoa/GBBorderView.h | 5 ----- Cocoa/GBBorderView.m | 11 ----------- Cocoa/GBViewMetal.m | 7 +++++++ 5 files changed, 11 insertions(+), 24 deletions(-) delete mode 100644 Cocoa/GBBorderView.h delete mode 100644 Cocoa/GBBorderView.m diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 5fe30c1b..f4abb5be 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -314,6 +314,8 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init]; [paragraph_style setLineSpacing:2]; + self.mainWindow.backgroundColor = [NSColor blackColor]; + self.debuggerSideViewInput.font = [NSFont userFixedPitchFontOfSize:12]; self.debuggerSideViewInput.textColor = [NSColor whiteColor]; self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 88c52757..5694ecda 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -50,16 +50,10 @@ - + - - - - - - - + diff --git a/Cocoa/GBBorderView.h b/Cocoa/GBBorderView.h deleted file mode 100644 index 477add17..00000000 --- a/Cocoa/GBBorderView.h +++ /dev/null @@ -1,5 +0,0 @@ -#import - -@interface GBBorderView : NSView - -@end diff --git a/Cocoa/GBBorderView.m b/Cocoa/GBBorderView.m deleted file mode 100644 index c81adb4a..00000000 --- a/Cocoa/GBBorderView.m +++ /dev/null @@ -1,11 +0,0 @@ -#import "GBBorderView.h" - -@implementation GBBorderView - -- (void)drawRect:(NSRect)dirtyRect { - [[NSColor blackColor] setFill]; - NSRectFill(dirtyRect); - [super drawRect:dirtyRect]; -} - -@end diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 34cd50bd..1c0a86f3 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -71,6 +71,13 @@ static const vector_float2 rect[] = [self loadShader]; } +- (void)addSubview:(NSView *)view +{ + /* Fixes rounded corners */ + [super addSubview:view]; + [self setWantsLayer:YES]; +} + - (void) loadShader { NSError *error = nil; From 70d68a500e5a14b22f35a4cd579738612e5adcc8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 17 Jun 2018 23:24:02 +0300 Subject: [PATCH 0688/1216] Stop annoying exceptions in the preferences window --- Cocoa/GBPreferencesWindow.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 66d8e4c7..051ac2f1 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -132,7 +132,7 @@ -(void)keyDown:(NSEvent *)theEvent { if (!is_button_being_modified) { - if (self.firstResponder != self.controlsTableView) { + if (self.firstResponder != self.controlsTableView && [theEvent type] != NSEventTypeFlagsChanged) { [super keyDown:theEvent]; } return; @@ -152,9 +152,6 @@ if (event.modifierFlags > previousModifiers) { [self keyDown:event]; } - else { - [self keyUp:event]; - } previousModifiers = event.modifierFlags; } From 6a7c084177da6ea481b7e4451fe336bc114724df Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 18 Jun 2018 21:57:01 +0300 Subject: [PATCH 0689/1216] Fixed window regression --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 0fa6e7ba..a2d3c18f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1075,7 +1075,7 @@ void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value) gb->io_registers[addr] = value; bool after = window_enabled(gb); - if (before != after && gb->current_line >= LINES) { + if (before != after && gb->current_line < LINES) { /* Window was disabled or enabled outside of vblank */ if (gb->current_line >= gb->io_registers[GB_IO_WY]) { if (after) { From 30f13bd28c4508f7e36b70fdd70c42e67c890e37 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 19 Jun 2018 23:59:16 +0300 Subject: [PATCH 0690/1216] More accurate CGB initial register values. Closes #80 --- BootROMs/cgb_boot.asm | 57 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 2682dc5d..77a25bc9 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -10,6 +10,8 @@ Start: xor a ; Clear chosen input palette ldh [InputPalette], a +; Clear title checksum + ldh [TitleChecksum], a ; Clear memory VRAM ld hl, $8000 call ClearMemoryPage @@ -723,39 +725,38 @@ Preboot: cpl ldh [$00], a call ClearVRAMViaHDMA - + + ; Final values for CGB mode + ld de, $ff56 + ld l, $0d + ld a, [$143] bit 7, a - jr nz, .cgbGame - call EmulateDMG - -.cgbGame + call z, EmulateDMG + ldh a, [TitleChecksum] + ld b, a + ldh [$4C], a ; One day, I will know what this switch does and how it differs from FF6C ldh a, [InputPalette] and a jr nz, .emulateDMGForCGBGame IF DEF(AGB) ; Set registers to match the original AGB-CGB boot - ld bc, $1100 - push bc - pop af + ; AF = $1100, C = 0 + xor a + ld c, a + add a, $11 ld h, c ld b, 1 - ld c, c - ld e, $08 - ld l, $7c ELSE ; Set registers to match the original CGB boot - ld bc, $1180 - push bc - pop af - ld c, 0 + ; AF = $1180, C = 0 + xor a + ld c, a + ld a, $11 ld h, c - ld b, c - ld c, c - ld e, $08 - ld l, $7c + ; B is set to the title checksum ENDC ret @@ -788,25 +789,31 @@ EmulateDMG: call WaitFrame call LoadPalettesFromIndex ld a, 4 + ; Set the final values for DMG mode + ld d, 0 + ld e, $8 + ld l, $7c ret GetPaletteIndex: - ld a, [$14B] ; Old Licensee + ld hl, $14B + ld a, [hl] ; Old Licensee cp $33 jr z, .newLicensee cp 1 ; Nintendo jr nz, .notNintendo jr .doChecksum .newLicensee - ld a, [$144] + ld l, $44 + ld a, [hli] cp "0" jr nz, .notNintendo - ld a, [$145] + ld a, [hl] cp "1" jr nz, .notNintendo .doChecksum - ld hl, $134 + ld l, $34 ld c, $10 ld b, 0 @@ -848,6 +855,8 @@ GetPaletteIndex: ld a, l add PalettePerChecksum - TitleChecksums - 1; -1 since hl was incremented ld l, a + ld a, b + ldh [TitleChecksum], a ld a, [hl] ret @@ -1140,6 +1149,8 @@ SECTION "ROMMax", ROM0[$900] ds 1 SECTION "HRAM", HRAM[$FF80] +TitleChecksum: + ds 1 BgPalettes: ds 8 * 4 * 2 InputPalette: From 0ffb936885dcff8655de8ecbd0e262d48c904164 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 21 Jun 2018 00:48:02 +0300 Subject: [PATCH 0691/1216] The Cocoa port now allows selecting a folder containing boot ROM files --- Cocoa/Document.m | 31 ++++++++-- Cocoa/GBPreferencesWindow.h | 2 + Cocoa/GBPreferencesWindow.m | 45 +++++++++++++++ Cocoa/Preferences.xib | 111 ++++++++++++++++++++++++------------ 4 files changed, 145 insertions(+), 44 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index f4abb5be..f79e141b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -141,12 +141,27 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return self; } +- (NSString *)bootROMPathForName:(NSString *)name +{ + NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]; + if (url) { + NSString *path = [url path]; + path = [path stringByAppendingPathComponent:name]; + path = [path stringByAppendingPathExtension:@"bin"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + return path; + } + } + + return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"]; +} + /* Todo: Unify the 3 init functions */ - (void) initDMG { current_model = MODEL_DMG; GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]); + GB_load_boot_rom(&gb, [[self bootROMPathForName:@"dmg_boot"] UTF8String]); [self initCommon]; } @@ -155,12 +170,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]) { current_model = MODEL_AGB; GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"agb_boot" ofType:@"bin"] UTF8String]); + GB_load_boot_rom(&gb, [[self bootROMPathForName:@"agb_boot"] UTF8String]); } else { current_model = MODEL_CGB; GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]); + GB_load_boot_rom(&gb, [[self bootROMPathForName:@"cgb_boot"] UTF8String]); } [self initCommon]; } @@ -254,14 +269,18 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, { [self stop]; + if ([sender tag] != MODEL_NONE) { + current_model = (enum model)[sender tag]; + } + + static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot"}; + GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); + if ([sender tag] == MODEL_NONE) { GB_reset(&gb); } else { - current_model = (enum model)[sender tag]; GB_switch_model_and_reset(&gb, cocoa_to_internal_model[current_model]); - static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot"}; - GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:boot_names[current_model - 1] ofType:@"bin"] UTF8String]); } if ([sender tag] != 0) { diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 1334d489..14d3a33f 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -10,4 +10,6 @@ @property (strong) IBOutlet NSPopUpButton *rewindPopupButton; @property (strong) IBOutlet NSButton *configureJoypadButton; @property (strong) IBOutlet NSButton *skipButton; +@property (strong) IBOutlet NSMenuItem *bootROMsFolderItem; +@property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 051ac2f1..73406d6a 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -308,6 +308,7 @@ - (void)awakeFromNib { [super awakeFromNib]; + [self updateBootROMFolderButton]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil]; } @@ -316,4 +317,48 @@ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView]; } +- (IBAction)selectOtherBootROMFolder:(id)sender +{ + NSOpenPanel *panel = [[NSOpenPanel alloc] init]; + [panel setCanChooseDirectories:YES]; + [panel setCanChooseFiles:NO]; + [panel setPrompt:@"Select"]; + [panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]]; + [panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) { + if (result == NSModalResponseOK) { + NSURL *url = [[panel URLs] firstObject]; + [[NSUserDefaults standardUserDefaults] setURL:url forKey:@"GBBootROMsFolder"]; + } + [self updateBootROMFolderButton]; + }]; + +} + +- (void) updateBootROMFolderButton +{ + NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]; + BOOL is_dir = false; + [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&is_dir]; + if (!is_dir) url = nil; + + if (url) { + [self.bootROMsFolderItem setTitle:[url lastPathComponent]]; + NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]]; + [icon setSize:NSMakeSize(16, 16)]; + [self.bootROMsFolderItem setHidden:NO]; + [self.bootROMsFolderItem setImage:icon]; + [self.bootROMsButton selectItemAtIndex:1]; + } + else { + [self.bootROMsFolderItem setHidden:YES]; + [self.bootROMsButton selectItemAtIndex:0]; + } +} + +- (IBAction)useBuiltinBootROMs:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setURL:nil forKey:@"GBBootROMsFolder"]; + [self updateBootROMFolderButton]; +} + @end diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index da853241..747a6824 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -17,14 +17,14 @@ - + - + - + @@ -33,14 +33,14 @@ - + - + - + @@ -68,7 +68,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -98,7 +98,7 @@ - + @@ -118,7 +118,7 @@ - + @@ -142,7 +142,7 @@ - + + + + - + - + - + @@ -228,7 +215,7 @@ - + @@ -259,10 +246,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -272,7 +307,7 @@ - + From 6b2c25475ffb2945e01ee99a9b712b0bc784af8e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 21 Jun 2018 21:23:57 +0300 Subject: [PATCH 0692/1216] Removed limitations around debugger output in the Cocoa frontend --- Cocoa/Document.m | 131 +++++++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index f79e141b..3da5c6fa 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -27,11 +27,11 @@ static const GB_model_t cocoa_to_internal_model[] = @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; + + NSMutableAttributedString *pending_console_output; + NSRecursiveLock *console_output_lock; + NSTimer *console_output_timer; + bool fullScreen; bool in_sync_input; HFController *hex_controller; @@ -137,6 +137,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (self) { has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; debugger_input_queue = [[NSMutableArray alloc] init]; + console_output_lock = [[NSRecursiveLock alloc] init]; } return self; } @@ -572,6 +573,30 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return rect; } +- (void) appendPendingOutput +{ + [console_output_lock lock]; + if (shouldClearSideView) { + shouldClearSideView = false; + [self.debuggerSideView setString:@""]; + } + if (pending_console_output) { + NSTextView *textView = logToSideView? self.debuggerSideView : self.consoleOutput; + + [hex_controller reloadData]; + [self reloadVRAMData: nil]; + + [textView.textStorage appendAttributedString:pending_console_output]; + [textView scrollToEndOfDocument:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { + [self.consoleWindow orderBack:nil]; + } + pending_console_output = nil; +} + [console_output_lock unlock]; + +} + - (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes { NSString *nsstring = @(string); // For ref-counting @@ -580,57 +605,45 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return; } - NSTextView *textView = logToSideView? self.debuggerSideView : self.consoleOutput; - if (!logToSideView && pendingLogLines > 128) { - /* The ROM causes so many errors in such a short time, and we can't handle it. */ - tooMuchLogs = true; - return; + NSFont *font = [NSFont userFixedPitchFontOfSize:12]; + NSUnderlineStyle underline = NSUnderlineStyleNone; + if (attributes & GB_LOG_BOLD) { + font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask]; } - pendingLogLines++; + + 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]; + NSMutableAttributedString *attributed = + [[NSMutableAttributedString alloc] initWithString:nsstring + attributes:@{NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor whiteColor], + NSUnderlineStyleAttributeName: @(underline), + NSParagraphStyleAttributeName: paragraph_style}]; + + [console_output_lock lock]; + if (!pending_console_output) { + pending_console_output = attributed; + } + else { + [pending_console_output appendAttributedString:attributed]; + } + + if ([console_output_timer isValid]) { + console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 repeats:NO block:^(NSTimer * _Nonnull timer) { + [self appendPendingOutput]; + }]; + [[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode]; + } + + [console_output_lock unlock]; /* Make sure mouse is not hidden while debugging */ self.view.mouseHidingEnabled = NO; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (shouldClearSideView) { - shouldClearSideView = false; - [self.debuggerSideView setString:@""]; - } - [hex_controller reloadData]; - [self reloadVRAMData: nil]; - - 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}]; - [textView.textStorage appendAttributedString:attributed]; - if (pendingLogLines == 1) { - if (tooMuchLogs) { - tooMuchLogs = false; - [self log:"[...]\n"]; - } - [textView scrollToEndOfDocument:nil]; - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { - [self.consoleWindow orderBack:nil]; - } - } - pendingLogLines--; - }); } - (IBAction)showConsoleWindow:(id)sender @@ -674,8 +687,20 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (!GB_debugger_is_stopped(&gb)) { return; } + + if (![NSThread isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self updateSideView]; + }); + return; + } + + [console_output_lock lock]; shouldClearSideView = true; + [self appendPendingOutput]; logToSideView = true; + [console_output_lock unlock]; + for (NSString *line in [self.debuggerSideViewInput.string componentsSeparatedByString:@"\n"]) { NSString *stripped = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if ([stripped length]) { @@ -686,7 +711,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, free(dupped); } } + + [console_output_lock lock]; + [self appendPendingOutput]; logToSideView = false; + [console_output_lock unlock]; } - (char *) getDebuggerInput From 2791775c5d1c87763a6742d3b9d76cac87d823f3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 22 Jun 2018 18:38:54 +0300 Subject: [PATCH 0693/1216] Improvements to the `lcd` debugger command --- Core/debugger.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 3382ef33..2cc49abe 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1514,8 +1514,25 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); - GB_log(gb, "\nCycles until next event: %d\n", -gb->display_cycles / 2); - GB_log(gb, "Current line: %d\n", gb->current_line); + + GB_log(gb, "\nCurrent line: %d\n", gb->current_line); + GB_log(gb, "Current state: "); + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + GB_log(gb, "Off\n"); + } + else if (gb->display_state == 7 || gb->display_state == 8) { + GB_log(gb, "Reading OAM data (%d/40)\n", gb->display_state == 8? gb->oam_search_index : 0); + } + else if (gb->display_state <= 3 || gb->display_state == 24) { + GB_log(gb, "Glitched line 0 (%d cycles to next event)\n", -gb->display_cycles / 2); + } + else if (gb->mode_for_interrupt == 3) { + signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line; + GB_log(gb, "Rendering pixel (%d/160)\n", pixel); + } + else { + GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2); + } GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); From 1915365b1affbfefad8a7c7c64b8e04205e4335d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Jun 2018 00:10:28 +0300 Subject: [PATCH 0694/1216] Added rewind and underclocking support to the SDL port. Joystick controls to be done. --- SDL/gui.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++---- SDL/gui.h | 5 +++ SDL/main.c | 44 ++++++++++++++++++++---- 3 files changed, 135 insertions(+), 12 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 3e0151b6..365f0597 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -62,10 +62,15 @@ configuration_t configuration = SDL_SCANCODE_RETURN, SDL_SCANCODE_SPACE }, + .keys_2 = { + SDL_SCANCODE_TAB, + SDL_SCANCODE_LSHIFT, + }, .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, .highpass_mode = GB_HIGHPASS_ACCURATE, .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, .blend_frames = true, + .rewind_length = 60 * 2, .model = MODEL_CGB }; @@ -282,8 +287,55 @@ const char *current_model_string(unsigned index) [configuration.model]; } +static const uint32_t rewind_lengths[] = {0, 10, 30, 60, 60 * 2, 60 * 5, 60 * 10}; +static const char *rewind_strings[] = {"Disabled", + "10 Seconds", + "30 Seconds", + "1 Minute", + "2 Minutes", + "5 Minutes", + "10 Minutes", +}; + +static void cycle_rewind(unsigned index) +{ + for (unsigned i = 0; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]) - 1; i++) { + if (configuration.rewind_length == rewind_lengths[i]) { + configuration.rewind_length = rewind_lengths[i + 1]; + GB_set_rewind_length(&gb, configuration.rewind_length); + return; + } + } + configuration.rewind_length = rewind_lengths[0]; + GB_set_rewind_length(&gb, configuration.rewind_length); +} + +static void cycle_rewind_backwards(unsigned index) +{ + for (unsigned i = 1; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]); i++) { + if (configuration.rewind_length == rewind_lengths[i]) { + configuration.rewind_length = rewind_lengths[i - 1]; + GB_set_rewind_length(&gb, configuration.rewind_length); + return; + } + } + configuration.rewind_length = rewind_lengths[sizeof(rewind_lengths) / sizeof(rewind_lengths[0]) - 1]; + GB_set_rewind_length(&gb, configuration.rewind_length); +} + +const char *current_rewind_string(unsigned index) +{ + for (unsigned i = 0; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]); i++) { + if (configuration.rewind_length == rewind_lengths[i]) { + return rewind_strings[i]; + } + } + return "Custom"; +} + static const struct menu_item emulation_menu[] = { {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, + {"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards}, {"Back", return_to_root_menu}, {NULL,} }; @@ -486,16 +538,15 @@ static void enter_audio_menu(unsigned index) current_menu = audio_menu; current_selection = 0; } -static const char *key_name(unsigned index) -{ - return SDL_GetScancodeName(configuration.keys[index]); -} static void modify_key(unsigned index) { gui_state = WAITING_FOR_KEY; } +static void enter_controls_menu_2(unsigned index); +static const char *key_name(unsigned index); + static const struct menu_item controls_menu[] = { {"Right:", modify_key, key_name,}, {"Left:", modify_key, key_name,}, @@ -505,17 +556,42 @@ static const struct menu_item controls_menu[] = { {"B:", modify_key, key_name,}, {"Select:", modify_key, key_name,}, {"Start:", modify_key, key_name,}, - {"Turbo:", modify_key, key_name,}, + {"Next Page", enter_controls_menu_2}, {"Back", return_to_root_menu}, {NULL,} }; +static const struct menu_item controls_menu_2[] = { + {"Turbo:", modify_key, key_name,}, + {"Rewind:", modify_key, key_name,}, + {"Slow-Motion:", modify_key, key_name,}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static const char *key_name(unsigned index) +{ + if (current_menu == controls_menu_2) { + if (index == 0) { + return SDL_GetScancodeName(configuration.keys[8]); + } + return SDL_GetScancodeName(configuration.keys_2[index - 1]); + } + return SDL_GetScancodeName(configuration.keys[index]); +} + static void enter_controls_menu(unsigned index) { current_menu = controls_menu; current_selection = 0; } +static void enter_controls_menu_2(unsigned index) +{ + current_menu = controls_menu_2; + current_selection = 0; +} + static unsigned joypad_index = 0; SDL_Joystick *joystick = NULL; SDL_GameController *controller = NULL; @@ -906,7 +982,17 @@ void run_gui(bool is_running) should_render = true; } else if (gui_state == WAITING_FOR_KEY) { - configuration.keys[current_selection] = event.key.keysym.scancode; + if (current_menu == controls_menu_2) { + if (current_selection == 0) { + configuration.keys[8] = event.key.keysym.scancode; + } + else { + configuration.keys_2[current_selection - 1] = event.key.keysym.scancode; + } + } + else { + configuration.keys[current_selection] = event.key.keysym.scancode; + } gui_state = SHOWING_MENU; should_render = true; } diff --git a/SDL/gui.h b/SDL/gui.h index 692c7666..e3f53c9a 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -6,6 +6,7 @@ #include #include "shader.h" +extern GB_gameboy_t gb; extern SDL_Window *window; extern SDL_Renderer *renderer; @@ -51,6 +52,10 @@ typedef struct { MODEL_AGB, MODEL_MAX, } model; + + /* v0.11 */ + uint32_t rewind_length; + SDL_Scancode keys_2[2]; /* Rewind and underclock */ } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 9a1c3d61..ff279f44 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include "utils.h" @@ -25,6 +26,8 @@ GB_gameboy_t gb; static bool paused = false; static uint32_t pixel_buffer_1[160*144], pixel_buffer_2[160*144]; static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; +static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; +static double clock_mutliplier = 1.0; static char *filename = NULL; static bool should_free_filename = false; @@ -143,7 +146,8 @@ static void handle_events(GB_gameboy_t *gb) GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_JOYBUTTONDOWN); } else if (event.jbutton.button & 1) { - GB_set_turbo_mode(gb, event.type == SDL_JOYBUTTONDOWN, false); + turbo_down = event.type == SDL_JOYBUTTONDOWN; + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); } else { @@ -152,9 +156,6 @@ static void handle_events(GB_gameboy_t *gb) SDL_PauseAudioDevice(device_id, 1); } run_gui(true); - if (!audio_playing) { - SDL_PauseAudioDevice(device_id, 0); - } GB_set_color_correction_mode(gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(gb, configuration.highpass_mode); } @@ -268,7 +269,18 @@ static void handle_events(GB_gameboy_t *gb) } case SDL_KEYUP: // Fallthrough if (event.key.keysym.scancode == configuration.keys[8]) { - GB_set_turbo_mode(gb, event.type == SDL_KEYDOWN, false); + turbo_down = event.type == SDL_KEYDOWN; + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (event.key.keysym.scancode == configuration.keys_2[0]) { + rewind_down = event.type == SDL_KEYDOWN; + if (event.type == SDL_KEYUP) { + rewind_paused = false; + } + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (event.key.keysym.scancode == configuration.keys_2[1]) { + underclock_down = event.type == SDL_KEYDOWN; } else { for (unsigned i = 0; i < GB_KEY_MAX; i++) { @@ -286,6 +298,14 @@ static void handle_events(GB_gameboy_t *gb) static void vblank(GB_gameboy_t *gb) { + if (underclock_down && clock_mutliplier > 0.5) { + clock_mutliplier -= 0.1; + GB_set_clock_multiplier(gb, clock_mutliplier); + } + else if (!underclock_down && clock_mutliplier < 1.0) { + clock_mutliplier += 0.1; + GB_set_clock_multiplier(gb, clock_mutliplier); + } if (configuration.blend_frames) { render_texture(active_pixel_buffer, previous_pixel_buffer); uint32_t *temp = active_pixel_buffer; @@ -296,6 +316,7 @@ static void vblank(GB_gameboy_t *gb) else { render_texture(active_pixel_buffer, NULL); } + do_rewind = rewind_down; handle_events(gb); } @@ -381,6 +402,7 @@ restart: GB_set_sample_rate(&gb, have_aspec.freq); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); + GB_set_rewind_length(&gb, configuration.rewind_length); } bool error = false; @@ -410,11 +432,21 @@ restart: /* Run emulation */ while (true) { - if (paused) { + if (paused || rewind_paused) { SDL_WaitEvent(NULL); handle_events(&gb); } else { + if (do_rewind) { + GB_rewind_pop(&gb); + if (turbo_down) { + GB_rewind_pop(&gb); + } + if (!GB_rewind_pop(&gb)) { + rewind_paused = true; + } + do_rewind = false; + } GB_run(&gb); } From 74abf61a08f22295420bc22ba7f96def69844a90 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Jun 2018 11:50:08 +0300 Subject: [PATCH 0695/1216] Fixed a crash that happened when changing the rewind length in Cocoa --- Cocoa/Document.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 3da5c6fa..7cb0eeac 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1411,9 +1411,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) updateRewindLength { - if (GB_is_inited(&gb)) { - GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); - } + [self performAtomicBlock:^{ + if (GB_is_inited(&gb)) { + GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); + } + }]; } @end From 82436ad8388fb11fa722e17c7877b9663e6690f7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Jun 2018 22:27:05 +0300 Subject: [PATCH 0696/1216] Added proper joypad configuration in SDL (Closes #33), can now use Rewind/Slow-Motion from the joypad. --- SDL/gui.c | 237 +++++++++++++++++++++++++++-------------------------- SDL/gui.h | 39 +++++++-- SDL/main.c | 85 +++++++++---------- 3 files changed, 194 insertions(+), 167 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 365f0597..be075e48 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -66,6 +66,24 @@ configuration_t configuration = SDL_SCANCODE_TAB, SDL_SCANCODE_LSHIFT, }, + .joypad_configuration = { + 13, + 14, + 11, + 12, + 0, + 1, + 9, + 8, + 10, + 4, + -1, + 5, + }, + .joypad_axises = { + 0, + 1, + }, .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, .highpass_mode = GB_HIGHPASS_ACCURATE, .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, @@ -221,8 +239,8 @@ static enum { WAITING_FOR_JBUTTON, } gui_state; -unsigned auto_detect_progress = 0; -unsigned auto_detect_inputs[3]; +static unsigned joypad_configuration_progress = 0; +static uint8_t joypad_axis_temp; static void item_exit(unsigned index) { @@ -593,8 +611,9 @@ static void enter_controls_menu_2(unsigned index) } static unsigned joypad_index = 0; -SDL_Joystick *joystick = NULL; -SDL_GameController *controller = NULL; +static SDL_Joystick *joystick = NULL; +static SDL_GameController *controller = NULL; + const char *current_joypad_name(unsigned index) { static char name[23] = {0,}; @@ -661,80 +680,16 @@ static void cycle_joypads_backwards(unsigned index) } } -unsigned fix_joypad_axis(unsigned axis) -{ - if (controller) { - /* Convert to the mapping used by generic Xbox-style controllers */ - for (SDL_GameControllerAxis i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) { - if (SDL_GameControllerGetBindForAxis(controller, i).value.axis == axis) { - if (i == SDL_CONTROLLER_AXIS_LEFTX || i == SDL_CONTROLLER_AXIS_RIGHTX) return 0; - if (i == SDL_CONTROLLER_AXIS_LEFTY || i == SDL_CONTROLLER_AXIS_RIGHTY) return 1; - return i; - } - } - return -1; - } - - - if (configuration.div_joystick) { - axis >>= 1; - } - - return axis & 1; -} - -unsigned fix_joypad_button(unsigned button) -{ - if (controller) { - /* Convert to the mapping used by generic Xbox-style controllers */ - for (SDL_GameControllerButton i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) { - if (SDL_GameControllerGetBindForButton(controller, i).value.button == button) { - if (i == SDL_CONTROLLER_BUTTON_START) { - return 9; - } - if (i == 9) { - return SDL_CONTROLLER_BUTTON_START; - } - - if (i == SDL_CONTROLLER_BUTTON_BACK) { - return 8; - } - if (i == 8) { - return SDL_CONTROLLER_BUTTON_BACK; - } - return i; - } - } - return -1; - } - - - if (configuration.div_joystick) { - button >>= 1; - } - - if (button < 4) { - if (configuration.swap_joysticks_bits_1_and_2) { - button = (int[]){0, 2, 1, 3}[button]; - } - - if (configuration.flip_joystick_bit_1) { - button ^= 1; - } - } - - return button; -} - static void detect_joypad_layout(unsigned index) { gui_state = WAITING_FOR_JBUTTON; - auto_detect_progress = 0; + joypad_configuration_progress = 0; + joypad_axis_temp = -1; } static const struct menu_item joypad_menu[] = { {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, - {"Detect layout", detect_joypad_layout}, + {"Configure layout", detect_joypad_layout}, {"Back", return_to_root_menu}, {NULL,} }; @@ -745,6 +700,27 @@ static void enter_joypad_menu(unsigned index) current_selection = 0; } +joypad_button_t get_joypad_button(uint8_t physical_button) +{ + for (unsigned i = 0; i < JOYPAD_BUTTONS_MAX; i++) { + if (configuration.joypad_configuration[i] == physical_button) { + return i; + } + } + return JOYPAD_BUTTONS_MAX; +} + +joypad_axis_t get_joypad_axis(uint8_t physical_axis) +{ + for (unsigned i = 0; i < JOYPAD_AXISES_MAX; i++) { + if (configuration.joypad_axises[i] == physical_axis) { + return i; + } + } + return JOYPAD_AXISES_MAX; +} + + extern void set_filename(const char *new_filename, bool new_should_free); void run_gui(bool is_running) { @@ -793,17 +769,17 @@ void run_gui(bool is_running) switch (event.type) { case SDL_JOYBUTTONDOWN: event.type = SDL_KEYDOWN; - event.jbutton.button = fix_joypad_button(event.jbutton.button); - if (event.jbutton.button < 4) { - event.key.keysym.scancode = (event.jbutton.button & 1) ? SDL_SCANCODE_RETURN : SDL_SCANCODE_ESCAPE; + joypad_button_t button = get_joypad_button(event.jbutton.button); + if (button == JOYPAD_BUTTON_A) { + event.key.keysym.scancode = SDL_SCANCODE_RETURN; } - else if (event.jbutton.button == 8 || event.jbutton.button == 9) { + else if (button == JOYPAD_BUTTON_START || button == JOYPAD_BUTTON_B || button == JOYPAD_BUTTON_MENU) { event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; } - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) event.key.keysym.scancode = SDL_SCANCODE_UP; - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) event.key.keysym.scancode = SDL_SCANCODE_DOWN; - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) event.key.keysym.scancode = SDL_SCANCODE_LEFT; - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; + else if (button == JOYPAD_BUTTON_UP) event.key.keysym.scancode = SDL_SCANCODE_UP; + else if (button == JOYPAD_BUTTON_DOWN) event.key.keysym.scancode = SDL_SCANCODE_DOWN; + else if (button == JOYPAD_BUTTON_LEFT) event.key.keysym.scancode = SDL_SCANCODE_LEFT; + else if (button == JOYPAD_BUTTON_RIGHT) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; break; case SDL_JOYHATMOTION: { @@ -825,8 +801,9 @@ void run_gui(bool is_running) case SDL_JOYAXISMOTION: { static bool axis_active[2] = {false, false}; - event.jaxis.axis = fix_joypad_axis(event.jaxis.axis); - if (event.jaxis.axis == 1) { + + joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); + if (axis == JOYPAD_AXISES_Y) { if (event.jaxis.value > 0x4000) { if (!axis_active[1]) { event.type = SDL_KEYDOWN; @@ -845,7 +822,7 @@ void run_gui(bool is_running) axis_active[1] = false; } } - else if (event.jaxis.axis == 0) { + else if (axis == JOYPAD_AXISES_X) { if (event.jaxis.value > 0x4000) { if (!axis_active[0]) { event.type = SDL_KEYDOWN; @@ -892,36 +869,54 @@ void run_gui(bool is_running) } case SDL_JOYBUTTONDOWN: { - if (gui_state == WAITING_FOR_JBUTTON) { + if (gui_state == WAITING_FOR_JBUTTON && joypad_configuration_progress != JOYPAD_BUTTONS_MAX) { should_render = true; - auto_detect_inputs[auto_detect_progress++] = event.jbutton.button; - if (auto_detect_progress == 3) { + configuration.joypad_configuration[joypad_configuration_progress++] = event.jbutton.button; + } + break; + } + + case SDL_JOYAXISMOTION: { + if (gui_state == WAITING_FOR_JBUTTON && + joypad_configuration_progress == JOYPAD_BUTTONS_MAX && + abs(event.jaxis.value) >= 0x4000) { + if (joypad_axis_temp == (uint8_t)-1) { + joypad_axis_temp = event.jaxis.axis; + } + else if (joypad_axis_temp != event.jaxis.axis) { + if (joypad_axis_temp < event.jaxis.axis) { + configuration.joypad_axises[JOYPAD_AXISES_X] = joypad_axis_temp; + configuration.joypad_axises[JOYPAD_AXISES_Y] = event.jaxis.axis; + } + else { + configuration.joypad_axises[JOYPAD_AXISES_Y] = joypad_axis_temp; + configuration.joypad_axises[JOYPAD_AXISES_X] = event.jaxis.axis; + } + gui_state = SHOWING_MENU; - - configuration.div_joystick = - ((auto_detect_inputs[0] | auto_detect_inputs[1] | auto_detect_inputs[2]) & 1) == 0 && - auto_detect_inputs[0] > 9; - - if (configuration.div_joystick) { - auto_detect_inputs[0] >>= 1; - auto_detect_inputs[1] >>= 1; - auto_detect_inputs[2] >>= 1; - } - - configuration.swap_joysticks_bits_1_and_2 = - (auto_detect_inputs[1] & 1) == (auto_detect_inputs[2] & 1); - - if (configuration.swap_joysticks_bits_1_and_2) { - auto_detect_inputs[1] = (int[]){0, 2, 1, 3}[auto_detect_inputs[1]]; - auto_detect_inputs[2] = (int[]){0, 2, 1, 3}[auto_detect_inputs[2]]; - } - - configuration.flip_joystick_bit_1 = auto_detect_inputs[2] & 1; + should_render = true; } } + break; } + case SDL_KEYDOWN: - if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { + if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && gui_state == WAITING_FOR_JBUTTON) { + should_render = true; + if (joypad_configuration_progress != JOYPAD_BUTTONS_MAX) { + configuration.joypad_configuration[joypad_configuration_progress] = -1; + } + else { + configuration.joypad_axises[0] = -1; + configuration.joypad_axises[1] = -1; + } + joypad_configuration_progress++; + + if (joypad_configuration_progress > JOYPAD_BUTTONS_MAX) { + gui_state = SHOWING_MENU; + } + } + else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { if (is_running) { return; } @@ -937,8 +932,7 @@ void run_gui(bool is_running) should_render = true; } } - - if (gui_state == SHOWING_MENU) { + else if (gui_state == SHOWING_MENU) { if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) { current_selection++; should_render = true; @@ -1040,13 +1034,28 @@ void run_gui(bool is_running) draw_text_centered(pixels, 68, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); break; case WAITING_FOR_JBUTTON: - draw_text_centered(pixels, 68, (const char *[]) - { - "Press button for Start", - "Press button for A", - "Press button for B", - } [auto_detect_progress], + draw_text_centered(pixels, 68, + joypad_configuration_progress != JOYPAD_BUTTONS_MAX ? "Press button for" : "Move the Analog Stick", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + draw_text_centered(pixels, 80, + (const char *[]) + { + "Right", + "Left", + "Up", + "Down", + "A", + "B", + "Select", + "Start", + "Open Menu", + "Turbo", + "Rewind", + "Slow-Motion", + "", + } [joypad_configuration_progress], + gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + draw_text_centered(pixels, 104, "Press Enter to skip", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); break; } diff --git a/SDL/gui.h b/SDL/gui.h index e3f53c9a..7042357d 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -31,8 +31,32 @@ enum pending_command { GB_SDL_QUIT_COMMAND, }; + extern enum pending_command pending_command; extern unsigned command_parameter; + +typedef enum { + JOYPAD_BUTTON_LEFT, + JOYPAD_BUTTON_RIGHT, + JOYPAD_BUTTON_UP, + JOYPAD_BUTTON_DOWN, + JOYPAD_BUTTON_A, + JOYPAD_BUTTON_B, + JOYPAD_BUTTON_SELECT, + JOYPAD_BUTTON_START, + JOYPAD_BUTTON_MENU, + JOYPAD_BUTTON_TURBO, + JOYPAD_BUTTON_REWIND, + JOYPAD_BUTTON_SLOW_MOTION, + JOYPAD_BUTTONS_MAX +} joypad_button_t; + +typedef enum { + JOYPAD_AXISES_X, + JOYPAD_AXISES_Y, + JOYPAD_AXISES_MAX +} joypad_axis_t; + typedef struct { SDL_Scancode keys[9]; GB_color_correction_mode_t color_correction_mode; @@ -41,9 +65,9 @@ typedef struct { GB_highpass_mode_t highpass_mode; - bool div_joystick; - bool flip_joystick_bit_1; - bool swap_joysticks_bits_1_and_2; + bool _deprecated_div_joystick; + bool _deprecated_flip_joystick_bit_1; + bool _deprecated_swap_joysticks_bits_1_and_2; char filter[32]; enum { @@ -55,15 +79,18 @@ typedef struct { /* v0.11 */ uint32_t rewind_length; - SDL_Scancode keys_2[2]; /* Rewind and underclock */ + SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */ + uint8_t joypad_configuration[32]; /* 12 Keys + padding for the future*/; + uint8_t joypad_axises[JOYPAD_AXISES_MAX]; } configuration_t; extern configuration_t configuration; void update_viewport(void); void run_gui(bool is_running); -unsigned fix_joypad_button(unsigned button); -unsigned fix_joypad_axis(unsigned axis); void render_texture(void *pixels, void *previous); +joypad_button_t get_joypad_button(uint8_t physical_button); +joypad_axis_t get_joypad_axis(uint8_t physical_axis); + #endif diff --git a/SDL/main.c b/SDL/main.c index ff279f44..5aabfe85 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -92,6 +92,20 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit) return captured_log; } +static void open_menu(void) +{ + bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; + if (audio_playing) { + SDL_PauseAudioDevice(device_id, 1); + } + run_gui(true); + if (audio_playing) { + SDL_PauseAudioDevice(device_id, 0); + } + GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); +} + static void handle_events(GB_gameboy_t *gb) { #ifdef __APPLE__ @@ -121,56 +135,42 @@ static void handle_events(GB_gameboy_t *gb) } case SDL_JOYBUTTONUP: - case SDL_JOYBUTTONDOWN: - event.jbutton.button = fix_joypad_button(event.jbutton.button); - if (event.jbutton.button < 4) { - GB_set_key_state(gb, (event.jbutton.button & 1) ? GB_KEY_A : GB_KEY_B, - event.type == SDL_JOYBUTTONDOWN); + case SDL_JOYBUTTONDOWN: { + joypad_button_t button = get_joypad_button(event.jbutton.button); + if ((GB_key_t) button < GB_KEY_MAX) { + GB_set_key_state(gb, (GB_key_t) button, event.type == SDL_JOYBUTTONDOWN); } - else if (event.jbutton.button == 8) { - GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_JOYBUTTONDOWN); - } - else if (event.jbutton.button == 9) { - GB_set_key_state(gb, GB_KEY_START, event.type == SDL_JOYBUTTONDOWN); - } - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) { - GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_JOYBUTTONDOWN); - } - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { - GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_JOYBUTTONDOWN); - } - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { - GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_JOYBUTTONDOWN); - } - else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { - GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_JOYBUTTONDOWN); - } - else if (event.jbutton.button & 1) { + else if (button == JOYPAD_BUTTON_TURBO) { turbo_down = event.type == SDL_JOYBUTTONDOWN; GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); } - - else { - bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; - if (audio_playing) { - SDL_PauseAudioDevice(device_id, 1); - } - run_gui(true); - GB_set_color_correction_mode(gb, configuration.color_correction_mode); - GB_set_highpass_filter_mode(gb, configuration.highpass_mode); + else if (button == JOYPAD_BUTTON_SLOW_MOTION) { + underclock_down = event.type == SDL_JOYBUTTONDOWN; } + else if (button == JOYPAD_BUTTON_REWIND) { + rewind_down = event.type == SDL_JOYBUTTONDOWN; + if (event.type == SDL_JOYBUTTONUP) { + rewind_paused = false; + } + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) { + open_menu(); + } + } break; - case SDL_JOYAXISMOTION: - event.jaxis.axis = fix_joypad_axis(event.jaxis.axis); - if (event.jaxis.axis == 1) { + case SDL_JOYAXISMOTION: { + joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); + if (axis == JOYPAD_AXISES_Y) { GB_set_key_state(gb, GB_KEY_DOWN, event.jaxis.value > 0x4000); GB_set_key_state(gb, GB_KEY_UP, event.jaxis.value < -0x4000); } - else if (event.jaxis.axis == 0) { + else if (axis == JOYPAD_AXISES_X) { GB_set_key_state(gb, GB_KEY_RIGHT, event.jaxis.value > 0x4000); GB_set_key_state(gb, GB_KEY_LEFT, event.jaxis.value < -0x4000); } + } break; case SDL_JOYHATMOTION: @@ -191,16 +191,7 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYDOWN: switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: { - bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; - if (audio_playing) { - SDL_PauseAudioDevice(device_id, 1); - } - run_gui(true); - if (!audio_playing) { - SDL_PauseAudioDevice(device_id, 0); - } - GB_set_color_correction_mode(gb, configuration.color_correction_mode); - GB_set_highpass_filter_mode(gb, configuration.highpass_mode); + open_menu(); break; } case SDL_SCANCODE_C: From 3a4ed6fd40b0e53e3345f605c72f0f0c226cdf25 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Jun 2018 22:36:29 +0300 Subject: [PATCH 0697/1216] Made SDL menus more consistent between keyboards and joypads (Closes #35) --- SDL/gui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index be075e48..f77f0647 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -773,7 +773,7 @@ void run_gui(bool is_running) if (button == JOYPAD_BUTTON_A) { event.key.keysym.scancode = SDL_SCANCODE_RETURN; } - else if (button == JOYPAD_BUTTON_START || button == JOYPAD_BUTTON_B || button == JOYPAD_BUTTON_MENU) { + else if (button == JOYPAD_BUTTON_MENU) { event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; } else if (button == JOYPAD_BUTTON_UP) event.key.keysym.scancode = SDL_SCANCODE_UP; From 32443a9675c0b0681d3cc738c405812d799706a6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 26 Jun 2018 19:36:14 +0300 Subject: [PATCH 0698/1216] Improved analog stick controls in both ports. Affects #34? --- Cocoa/GBView.m | 51 +++++++++++++++++++++++++++++++++-------------- SDL/gui.c | 54 +++++++++++++++++++++----------------------------- SDL/gui.h | 3 +++ SDL/main.c | 40 +++++++++++++++++++++++++++++++------ 4 files changed, 96 insertions(+), 52 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index a3c5a75b..10f1bca7 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -5,6 +5,9 @@ #import "GBButtons.h" #import "NSString+StringForKey.h" +#define JOYSTICK_HIGH 0x4000 +#define JOYSTICK_LOW 0x3800 + @implementation GBView { uint32_t *image_buffers[3]; @@ -12,7 +15,7 @@ BOOL mouse_hidden; NSTrackingArea *tracking_area; BOOL _mouseHidingEnabled; - bool enableAnalog; + bool axisActive[2]; bool underclockKeyDown; double clockMultiplier; NSEventModifierFlags previousModifiers; @@ -241,9 +244,6 @@ break; default: - if (i < GB_KEY_A) { - enableAnalog = false; - } GB_set_key_state(_gb, (GB_key_t)i, state); break; } @@ -258,18 +258,39 @@ NSNumber *x_axis = [mapping objectForKey:@"XAxis"]; NSNumber *y_axis = [mapping objectForKey:@"YAxis"]; - if (value > 0x4000 || value < -0x4000) { - enableAnalog = true; + if (axis == [x_axis integerValue]) { + if (value > JOYSTICK_HIGH) { + axisActive[0] = true; + GB_set_key_state(_gb, GB_KEY_RIGHT, true); + GB_set_key_state(_gb, GB_KEY_LEFT, false); + } + else if (value < -JOYSTICK_HIGH) { + axisActive[0] = true; + GB_set_key_state(_gb, GB_KEY_RIGHT, false); + GB_set_key_state(_gb, GB_KEY_LEFT, true); + } + else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { + axisActive[0] = false; + GB_set_key_state(_gb, GB_KEY_RIGHT, false); + GB_set_key_state(_gb, GB_KEY_LEFT, false); + } } - if (!enableAnalog) return; - - if (x_axis && [x_axis integerValue] == axis) { - GB_set_key_state(_gb, GB_KEY_LEFT, value < -0x4000); - GB_set_key_state(_gb, GB_KEY_RIGHT, value > 0x4000); - } - else if (y_axis && [y_axis integerValue] == axis) { - GB_set_key_state(_gb, GB_KEY_UP, value < -0x4000); - GB_set_key_state(_gb, GB_KEY_DOWN, value > 0x4000); + else if (axis == [y_axis integerValue]) { + if (value > JOYSTICK_HIGH) { + axisActive[1] = true; + GB_set_key_state(_gb, GB_KEY_DOWN, true); + GB_set_key_state(_gb, GB_KEY_UP, false); + } + else if (value < -JOYSTICK_HIGH) { + axisActive[1] = true; + GB_set_key_state(_gb, GB_KEY_DOWN, false); + GB_set_key_state(_gb, GB_KEY_UP, true); + } + else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { + axisActive[1] = false; + GB_set_key_state(_gb, GB_KEY_DOWN, false); + GB_set_key_state(_gb, GB_KEY_UP, false); + } } } diff --git a/SDL/gui.c b/SDL/gui.c index f77f0647..d43d939a 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -801,44 +801,36 @@ void run_gui(bool is_running) case SDL_JOYAXISMOTION: { static bool axis_active[2] = {false, false}; - joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); - if (axis == JOYPAD_AXISES_Y) { - if (event.jaxis.value > 0x4000) { - if (!axis_active[1]) { - event.type = SDL_KEYDOWN; - event.key.keysym.scancode = SDL_SCANCODE_DOWN; - } - axis_active[1] = true; + if (axis == JOYPAD_AXISES_X) { + if (!axis_active[0] && event.jaxis.value > JOYSTICK_HIGH) { + axis_active[0] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_RIGHT; } - else if (event.jaxis.value < -0x4000) { - if (!axis_active[0]) { - event.type = SDL_KEYDOWN; - event.key.keysym.scancode = SDL_SCANCODE_UP; - } - axis_active[1] = true; + else if (!axis_active[0] && event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[0] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_LEFT; + } - else { - axis_active[1] = false; + else if (axis_active[0] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[0] = false; } } - else if (axis == JOYPAD_AXISES_X) { - if (event.jaxis.value > 0x4000) { - if (!axis_active[0]) { - event.type = SDL_KEYDOWN; - event.key.keysym.scancode = SDL_SCANCODE_RIGHT; - } - axis_active[0] = true; + else if (axis == JOYPAD_AXISES_Y) { + if (!axis_active[1] && event.jaxis.value > JOYSTICK_HIGH) { + axis_active[1] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_DOWN; } - else if (event.jaxis.value < -0x4000) { - if (!axis_active[0]) { - event.type = SDL_KEYDOWN; - event.key.keysym.scancode = SDL_SCANCODE_LEFT; - } - axis_active[0] = true; + else if (!axis_active[1] && event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[1] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_UP; } - else { - axis_active[0] = false; + else if (axis_active[1] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[1] = false; } } } diff --git a/SDL/gui.h b/SDL/gui.h index 7042357d..9893eb61 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -6,6 +6,9 @@ #include #include "shader.h" +#define JOYSTICK_HIGH 0x4000 +#define JOYSTICK_LOW 0x3800 + extern GB_gameboy_t gb; extern SDL_Window *window; diff --git a/SDL/main.c b/SDL/main.c index 5aabfe85..058f57fb 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -8,6 +8,7 @@ #include "gui.h" #include "shader.h" + #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 #else @@ -161,14 +162,41 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_JOYAXISMOTION: { + static bool axis_active[2] = {false, false}; joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); - if (axis == JOYPAD_AXISES_Y) { - GB_set_key_state(gb, GB_KEY_DOWN, event.jaxis.value > 0x4000); - GB_set_key_state(gb, GB_KEY_UP, event.jaxis.value < -0x4000); + if (axis == JOYPAD_AXISES_X) { + if (event.jaxis.value > JOYSTICK_HIGH) { + axis_active[0] = true; + GB_set_key_state(gb, GB_KEY_RIGHT, true); + GB_set_key_state(gb, GB_KEY_LEFT, false); + } + else if (event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[0] = true; + GB_set_key_state(gb, GB_KEY_RIGHT, false); + GB_set_key_state(gb, GB_KEY_LEFT, true); + } + else if (axis_active[0] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[0] = false; + GB_set_key_state(gb, GB_KEY_RIGHT, false); + GB_set_key_state(gb, GB_KEY_LEFT, false); + } } - else if (axis == JOYPAD_AXISES_X) { - GB_set_key_state(gb, GB_KEY_RIGHT, event.jaxis.value > 0x4000); - GB_set_key_state(gb, GB_KEY_LEFT, event.jaxis.value < -0x4000); + else if (axis == JOYPAD_AXISES_Y) { + if (event.jaxis.value > JOYSTICK_HIGH) { + axis_active[1] = true; + GB_set_key_state(gb, GB_KEY_DOWN, true); + GB_set_key_state(gb, GB_KEY_UP, false); + } + else if (event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[1] = true; + GB_set_key_state(gb, GB_KEY_DOWN, false); + GB_set_key_state(gb, GB_KEY_UP, true); + } + else if (axis_active[1] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[1] = false; + GB_set_key_state(gb, GB_KEY_DOWN, false); + GB_set_key_state(gb, GB_KEY_UP, false); + } } } break; From 045c8631178fdddd52b3e2e9dea7e0ac51172b63 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 Jun 2018 13:05:08 +0300 Subject: [PATCH 0699/1216] Fixed Windows build --- SDL/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index 058f57fb..0cf1c79d 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include "utils.h" From 81662e954bb97fce326365b6edbba4644d368c4a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 Jun 2018 13:17:27 +0300 Subject: [PATCH 0700/1216] Convert the Windows .rc file to UTF-16 (Seems like UTF-8 support is broken in rc) --- Windows/resources.rc | Bin 605 -> 1210 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Windows/resources.rc b/Windows/resources.rc index 7a6964f2fddf59804d94165818e1d627716e6205..ffa8b3b93a3b02e11c7283a4c4764b7e27b9321f 100644 GIT binary patch literal 1210 zcmb`H%}xSA5QOW@Q*1aZKZ=QZ<3}S(fC#SfWKa=FkR`Ar65|8;lIqvHjO+$Hh-T8u z%rsL|UDMs4A4Pfcb)rjkR98z)zD=Dn%kZtMfv<(nQ$a5(C3d^IQ5$dUDdKs>yyd8H zvZbmnSZ6sOYO0~e8h|>~NPV!!dQ^aSbt;Ll8L0q=f{w5fcb8J9-HA;o&gZKPv#W@A z7Jh-AHBk>P3*zpv>Vvrxv2MULfVZbJJR|=1#Cp8(&dvBP)*7fY-A5Ws(86#HC%&h% zytl&cVheO1^eOmB)byLK6m~q_Z(5HANP{R=Q)U7id$j>^hu>`1J)YjtXpc83>Oi~8 z*&%y>SFjC(Ko6RuUbyx`+bWkv{XhM#Wy8IzsTQK*OvlPI251?g3l2JY~92jju&weZ~4lQjHu8Of~BmUwHSN(*OVf literal 605 zcma)4%}#_c5We#i4QE}-iivvTH&z?iEGxv5sff@7C@I}!iP;D8HGDE#sO~0a)gC6D z%r`UN%r`d3!gY$07{yUyL`5)Gk|;^PW*ddrh?P1ifB`!?vUUd!1q2RO^ zRAOXKDsw$Ho`h#5A7-eYbRw*3s;7D2vLcPo`jOMB7OL#G13uot655yN(p-T*8Ll{c z*%sn!qTkqQ=)VZ&d9GO1LiEFpWmQzv+vi2WS_`8)bg{I4G}=#d&Q>dz5q?TGy@w4a t-Ov!dTewp)$_r3R>T-L-KmmF}eiGY2SrDMqch`aIVJWDlv7m*#j88%&o0tFq From d9dfbcd199949e8ca28751150a91d68768d88273 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 Jun 2018 13:18:05 +0300 Subject: [PATCH 0701/1216] Updated version to 0.11, updated README --- Makefile | 2 +- README.md | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 9156b225..45e3b29f 100755 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.10.1 +VERSION := 0.11 export VERSION CONF ?= debug diff --git a/README.md b/README.md index e80a92db..b1a2011d 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,37 @@ # SameBoy -SameBoy is an open source Gameboy (DMG) and Gameboy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for OS X, an experimental SDL frontend for other operating systems, and a libretro core. It also includes a text-based debugger with an expression evaluator. Visit [the website](https://sameboy.github.io/). +SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for macOS, an SDL frontend for other operating systems, and a libretro core. It also includes a text-based debugger with an expression evaluator. Visit [the website](https://sameboy.github.io/). ## Features Features common to both Cocoa and SDL versions: - * Supports Gameboy (DMG) and Gameboy Color (CGB) emulation + * Supports Game Boy (DMG) and Game Boy Color (CGB) emulation * Lets you choose the model you want to emulate regardless of ROM * High quality 96KHz audio * Battery save support * Save states * Includes open source DMG and CGB boot ROMs: - * Complete support for (and documentation of) *all* game-specific palettes in the CGB boot ROM, for accurate emulation of Gameboy games on a Gameboy Color + * Complete support for (and documentation of) *all* game-specific palettes in the CGB boot ROM, for accurate emulation of Game Boy games on a Game Boy Color * Supports manual palette selection with key combinations, with 4 additional new palettes (A + B + direction) * Supports palette selection in a CGB game, forcing it to run in 'paletted' DMG mode, if ROM allows doing so. * Support for games with a non-Nintendo logo in the header * No long animation in the DMG boot * Advanced text-based debugger with an expression evaluator, disassembler, conditional breakpoints, conditional watchpoints, backtracing and other features + * Extremely high accuracy * Emulates [PCM_12 and PCM_34 registers](https://github.com/LIJI32/GBVisualizer) - * Emulates LCD timing effects, supporting the Demotronic trick, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos - * Extermely high accuracy + * T-cycle accurate emulation of LCD timing effects, supporting the Demotronic trick, Prehistorik Man, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos * Real time clock emulation * Retina/High DPI display support, allowing a wider range of scaling factors without artifacts * Optional frame blending (Requires OpenGL 3.2 or later) - * Several [scaling algorithms](https://sameboy.github.io/scaling/) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x; Requires OpenGL 3.2 or later) + * Several [scaling algorithms](https://sameboy.github.io/scaling/) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x; Requires OpenGL 3.2 or later or Metal) Features currently supported only with the Cocoa version: * Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars - * GameBoy Camera support + * Game Boy Camera support [Read more](https://sameboy.github.io/features/). ## Compatibility -SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as 77 out of 78 of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) tests (Some tests require the original boot ROMs). SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](https://sameboy.github.io/automation/). +SameBoy passes all of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), all of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) tests (Some tests require the original boot ROMs), and all of [Wilbert Pol's tests](https://github.com/wilbertpol/mooneye-gb/tree/master/tests/acceptance). SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](https://sameboy.github.io/automation/). ## Compilation SameBoy requires the following tools and libraries to build: @@ -44,8 +44,8 @@ SameBoy requires the following tools and libraries to build: On Windows, SameBoy also requires: * Visual Studio (For headers, etc.) * [GnuWin](http://gnuwin32.sourceforge.net/) - * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively. + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. -To compile, simply run `make`. The targets are cocoa (Default for OS X), sdl (Default for everything else), libretro, bootroms and tester. You may also specify CONF=debug (default), CONF=release or CONF=native_release to control optimization and symbols. native_release is faster than release, but is optimized to the host's CPU and therefore is not portable. You may set BOOTROMS_DIR=... to a directory containing precompiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs. +To compile, simply run `make`. The targets are cocoa (Default for macOS), sdl (Default for everything else), libretro, bootroms and tester. You may also specify CONF=debug (default), CONF=release or CONF=native_release to control optimization and symbols. native_release is faster than release, but is optimized to the host's CPU and therefore is not portable. You may set BOOTROMS_DIR=... to a directory containing precompiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs. SameBoy was compiled and tested on macOS, Ubuntu and 32-bit Windows 7. From 47a74cb6c389ea15682116923b8bcbd4b0389fd1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Jun 2018 16:53:28 +0300 Subject: [PATCH 0702/1216] Randomize initial RAM values. Closes #82 --- Core/gb.c | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 1f02a3fe..2c3dfbb7 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -443,11 +443,45 @@ void GB_set_user_data(GB_gameboy_t *gb, void *data) gb->user_data = data; } +static void reset_ram(GB_gameboy_t *gb) +{ + switch (gb->model) { + case GB_MODEL_CGB_E: + default: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = (random() & 0xFF); + } + break; + + case GB_MODEL_DMG_B: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = (random() & 0xFF); + if (i & 0x100) { + gb->ram[i] &= random(); + } + else { + gb->ram[i] |= random(); + } + } + break; +#if 0 + /* Not emulated yet, for documentation only*/ + case GB_MODEL_SGB2: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = 0x55; + gb->ram[i] ^= random() & random() & random(); + } + break; +#endif + } +} + void GB_reset(GB_gameboy_t *gb) { uint32_t mbc_ram_size = gb->mbc_ram_size; - bool cgb = GB_is_cgb(gb); + GB_model_t model = gb->model; memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); + gb->model = model; gb->version = GB_STRUCT_VERSION; gb->mbc_rom_bank = 1; @@ -455,18 +489,14 @@ void GB_reset(GB_gameboy_t *gb) gb->cgb_ram_bank = 1; gb->io_registers[GB_IO_JOYP] = 0xF; gb->mbc_ram_size = mbc_ram_size; - if (cgb) { + if (GB_is_cgb(gb)) { gb->ram_size = 0x2000 * 8; - memset(gb->ram, 0, gb->ram_size); gb->vram_size = 0x2000 * 2; memset(gb->vram, 0, gb->vram_size); - - gb->model = GB_MODEL_CGB_E; gb->cgb_mode = true; } else { gb->ram_size = 0x2000; - memset(gb->ram, 0, gb->ram_size); gb->vram_size = 0x2000; memset(gb->vram, 0, gb->vram_size); @@ -481,6 +511,8 @@ void GB_reset(GB_gameboy_t *gb) gb->rgb_encode_callback(gb, 0, 0, 0); } } + reset_ram(gb); + /* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */ gb->serial_cycles = 0x100-0xF7; gb->io_registers[GB_IO_SC] = 0x7E; From a7aabca6180b7d60bf49023dfb88526951bc2b39 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Jul 2018 21:43:46 +0300 Subject: [PATCH 0703/1216] Starting to add CGB-C support --- Core/gb.c | 15 +++++++++++++++ Core/gb.h | 3 ++- Core/memory.c | 27 +++++++++++++++++++++------ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 2c3dfbb7..c3fa4cc8 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -473,6 +473,21 @@ static void reset_ram(GB_gameboy_t *gb) } break; #endif + + case GB_MODEL_CGB_C: + for (unsigned i = 0; i < gb->ram_size; i++) { + if ((i & 0x808) == 0x800 || (i & 0x808) == 0x008) { + gb->ram[i] = 0; + } + else { + gb->ram[i] = (random() | random() | random() | random()) & 0xFF; + } + } + break; + } + + for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) { + gb->extra_oam[i] = (random() & 0xFF); } } diff --git a/Core/gb.h b/Core/gb.h index 31840d94..32546247 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -43,7 +43,7 @@ typedef enum { // GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, // GB_MODEL_CGB_B = 0x202, - // GB_MODEL_CGB_C = 0x203, + GB_MODEL_CGB_C = 0x203, // GB_MODEL_CGB_D = 0x204, GB_MODEL_CGB_E = 0x205, GB_MODEL_AGB = 0x206, @@ -301,6 +301,7 @@ struct GB_gameboy_internal_s { /* Misc state */ bool infrared_input; GB_printer_t printer; + uint8_t extra_oam[0xff00 - 0xfea0]; ); /* DMA and HDMA */ diff --git a/Core/memory.c b/Core/memory.c index ee9ad188..5f6a89af 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -244,13 +244,21 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->oam[addr & 0xFF]; } - /* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */ - /* Also, writes to this area are not emulated */ - if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */ + if (gb->oam_read_blocked) { return 0xFF; } - if (GB_is_cgb(gb)) { - return (addr & 0xF0) | ((addr >> 4) & 0xF); + + switch (gb->model) { + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + return (addr & 0xF0) | ((addr >> 4) & 0xF); + + case GB_MODEL_CGB_C: + addr &= ~0x18; + return gb->extra_oam[addr - 0xfea0]; + + default: + ; } } @@ -535,10 +543,17 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (addr < 0xFEA0) { gb->oam[addr & 0xFF] = value; } + switch (gb->model) { + case GB_MODEL_CGB_C: + addr &= ~0x18; + gb->extra_oam[addr - 0xfea0] = value; + break; + default: + break; + } return; } - /* Todo: This is writable, but glitchy, on CGB-B and CGB-D. */ if (addr < 0xFEA0) { if (gb->accessed_oam_row == 0xa0) { for (unsigned i = 0; i < 8; i++) { From 18ae18a95cd56b2dd566958cb3401f35d594fcfe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Jul 2018 21:56:32 +0300 Subject: [PATCH 0704/1216] LYC bit on CGB-C --- Core/display.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index a2d3c18f..9d05fe7f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -236,7 +236,8 @@ void GB_STAT_update(GB_gameboy_t *gb) bool previous_interrupt_line = gb->stat_interrupt_line; /* Set LY=LYC bit */ - if (gb->ly_for_comparison != (uint16_t)-1 || !GB_is_cgb(gb)) { + /* TODO: This behavior might not be correct for CGB revisions other than C and E */ + if (gb->ly_for_comparison != (uint16_t)-1 || gb->model <= GB_MODEL_CGB_C) { if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { gb->lyc_interrupt_line = true; gb->io_registers[GB_IO_STAT] |= 4; @@ -748,6 +749,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (object->flags & 0x40) { /* Flip Y */ tile_y ^= height_16? 0xF : 7; } + /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; @@ -864,17 +866,17 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_LY] = 153; gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 14, GB_is_cgb(gb)? 4: 6); + GB_SLEEP(gb, display, 14, (gb->model > GB_MODEL_CGB_C)? 4: 6); if (!GB_is_cgb(gb)) { gb->io_registers[GB_IO_LY] = 0; } gb->ly_for_comparison = 153; GB_STAT_update(gb); - GB_SLEEP(gb, display, 15, GB_is_cgb(gb)? 4: 2); + GB_SLEEP(gb, display, 15, (gb->model > GB_MODEL_CGB_C)? 4: 2); gb->io_registers[GB_IO_LY] = 0; - gb->ly_for_comparison = GB_is_cgb(gb)? 153 : -1; + gb->ly_for_comparison = (gb->model > GB_MODEL_CGB_C)? 153 : -1; GB_STAT_update(gb); GB_SLEEP(gb, display, 16, 4); From 0a78f735d3c4501aa874eb9a48e258609bcdb52e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Jul 2018 22:14:53 +0300 Subject: [PATCH 0705/1216] Fetcher Y is not cached on CGB-C --- Core/display.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index 9d05fe7f..682b45b5 100644 --- a/Core/display.c +++ b/Core/display.c @@ -435,11 +435,8 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); - if (GB_is_cgb(gb)) { + if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB, so it cannot be used to mix tiles together */ - /* Todo: This is NOT true on CGB-B! This is likely the case for all CGBs prior to D. - Currently, SameBoy is emulating CGB-E, but if other revisions are added in the future - this should be taken care of */ gb->fetcher_y = y; } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; @@ -457,7 +454,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) case GB_FETCHER_GET_TILE_DATA_LOWER: { uint8_t y_flip = 0; uint16_t tile_address = 0; - uint8_t y = GB_is_cgb(gb)? gb->fetcher_y : fetcher_y(gb); + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ if (gb->io_registers[GB_IO_LCDC] & 0x10) { @@ -484,7 +481,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) bit mid-fetching causes a glitched mixing of the two, in comparison to the more logical DMG version. */ uint16_t tile_address = 0; - uint8_t y = GB_is_cgb(gb)? gb->fetcher_y : fetcher_y(gb); + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); if (gb->io_registers[GB_IO_LCDC] & 0x10) { tile_address = gb->current_tile * 0x10; From b7b35c9b59c56561e3cb5bee109c4c00405404ba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Jul 2018 22:25:09 +0300 Subject: [PATCH 0706/1216] CGB-C timing --- Core/display.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 682b45b5..d88978ed 100644 --- a/Core/display.c +++ b/Core/display.c @@ -436,7 +436,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); if (gb->model > GB_MODEL_CGB_C) { - /* This value is cached on the CGB, so it cannot be used to mix tiles together */ + /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; @@ -559,6 +559,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 27); GB_STATE(gb, display, 28); GB_STATE(gb, display, 29); + GB_STATE(gb, display, 30); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -698,8 +699,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); - gb->cycles_for_line += 5; - GB_SLEEP(gb, display, 10, 5); + uint8_t idle_cycles = 5; + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + idle_cycles = 4; + } + gb->cycles_for_line += idle_cycles; + GB_SLEEP(gb, display, 10, idle_cycles); /* The actual rendering cycle */ gb->fetcher_state = 0; @@ -790,6 +795,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); } + + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 30, 1); + } + if (!gb->cgb_double_speed) { gb->io_registers[GB_IO_STAT] &= ~3; gb->mode_for_interrupt = 0; From f3437d7cc051bd7468864a701ae9f6e26a0cadd7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Jul 2018 23:47:50 +0300 Subject: [PATCH 0707/1216] Added todo --- Core/z80_cpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 26cadd65..cc8f0de9 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -117,6 +117,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_CONFLICT_STAT_CGB: { + /* Todo: Verify this with SCX adjustments */ /* The LYC bit behaves differently */ uint8_t old_value = GB_read_memory(gb, addr); GB_advance_cycles(gb, gb->pending_cycles); From dc5cb71c22352b85cd4fe6993ed2b0e42e9db175 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 4 Jul 2018 21:55:12 +0300 Subject: [PATCH 0708/1216] =?UTF-8?q?Emulate=20CGB-C=E2=80=99s=20quirky=20?= =?UTF-8?q?LFSR=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 18 ++++++++++++++---- Core/apu.h | 3 ++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index b5574c9f..2ba69697 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -365,7 +365,7 @@ void GB_apu_run(GB_gameboy_t *gb) /* Step LFSR */ unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; - // This formula is different on a GBA! + /* Todo: is this formula is different on a GBA? */ bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; gb->apu.noise_channel.lfsr >>= 1; @@ -377,8 +377,17 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.noise_channel.lfsr &= ~high_bit_mask; } + gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + if (gb->model == GB_MODEL_CGB_C) { + /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. + Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, + I'll assume these devices are innocent until proven guilty. */ + gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; + } + gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, 0); } @@ -776,7 +785,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) else if (gb->apu.is_active[GB_NOISE]){ nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, 0); } @@ -821,11 +830,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) cases. */ if (gb->apu.is_active[GB_NOISE]) { update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, 0); } gb->apu.noise_channel.lfsr = 0; + gb->apu.current_lfsr_sample = false; gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { diff --git a/Core/apu.h b/Core/apu.h index 59d0e360..10f667d2 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -109,7 +109,8 @@ typedef struct } noise_channel; bool skip_div_event; - + bool current_lfsr_sample; + bool previous_lfsr_sample; } GB_apu_t; typedef enum { From afcb7b8579da645608dc97ef3476edda44db5146 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 10 Jul 2018 20:28:36 +0300 Subject: [PATCH 0709/1216] Fixed a regression where DMG mode on CGB would not activate on most DMG games. Fixes #98 --- BootROMs/cgb_boot.asm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 77a25bc9..ba139135 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -732,12 +732,11 @@ Preboot: ld a, [$143] bit 7, a - call z, EmulateDMG + ldh [$4C], a ldh a, [TitleChecksum] ld b, a - ldh [$4C], a ; One day, I will know what this switch does and how it differs from FF6C ldh a, [InputPalette] and a jr nz, .emulateDMGForCGBGame From dc4c23c0da1f86f04bc6e8ce62c9732de4675a70 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 10 Jul 2018 20:35:07 +0300 Subject: [PATCH 0710/1216] Worked around a macOS bug where fullscreen-mode SameBoy would render garbage on High Sierra. Fixed titlebar color when using the ugly Yosemite theme. --- Cocoa/Document.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 7cb0eeac..c9205ce0 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -334,7 +334,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init]; [paragraph_style setLineSpacing:2]; - self.mainWindow.backgroundColor = [NSColor blackColor]; + self.mainWindow.contentView.layer.backgroundColor = [[NSColor blackColor] CGColor]; self.debuggerSideViewInput.font = [NSFont userFixedPitchFontOfSize:12]; self.debuggerSideViewInput.textColor = [NSColor whiteColor]; From 5f58323c017fbbec292bcb5e922e8bfe950a2bd7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 10 Jul 2018 21:33:03 +0300 Subject: [PATCH 0711/1216] Attempt to improve audio quality on frontend with big audio buffers --- Core/apu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index b5574c9f..dee4c006 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -417,6 +417,14 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) dest[gb->apu_output.buffer_position + i] = output; } + if (gb->apu_output.buffer_position) { + if (gb->apu_output.buffer_size + (count - gb->apu_output.buffer_position) < count * 3) { + gb->apu_output.buffer_size += count - gb->apu_output.buffer_position; + gb->apu_output.buffer = realloc(gb->apu_output.buffer, + gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer)); + gb->apu_output.stream_started = false; + } + } count = gb->apu_output.buffer_position; } memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer)); From e5f4495ca0a57c438391308752d9354cef9472f8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 11 Jul 2018 19:46:27 +0300 Subject: [PATCH 0712/1216] Turns out the last Cocoa fix was neither reliable or actually working. --- Cocoa/Document.m | 4 +--- Cocoa/Document.xib | 10 ++++++++-- Cocoa/GBBorderView.h | 5 +++++ Cocoa/GBBorderView.m | 26 ++++++++++++++++++++++++++ Cocoa/GBViewMetal.m | 7 ------- 5 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 Cocoa/GBBorderView.h create mode 100644 Cocoa/GBBorderView.m diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c9205ce0..98660642 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -333,9 +333,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init]; [paragraph_style setLineSpacing:2]; - - self.mainWindow.contentView.layer.backgroundColor = [[NSColor blackColor] CGColor]; - + self.debuggerSideViewInput.font = [NSFont userFixedPitchFontOfSize:12]; self.debuggerSideViewInput.textColor = [NSColor whiteColor]; self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 5694ecda..88c52757 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -50,10 +50,16 @@ - + - + + + + + + + diff --git a/Cocoa/GBBorderView.h b/Cocoa/GBBorderView.h new file mode 100644 index 00000000..477add17 --- /dev/null +++ b/Cocoa/GBBorderView.h @@ -0,0 +1,5 @@ +#import + +@interface GBBorderView : NSView + +@end diff --git a/Cocoa/GBBorderView.m b/Cocoa/GBBorderView.m new file mode 100644 index 00000000..a5f5e817 --- /dev/null +++ b/Cocoa/GBBorderView.m @@ -0,0 +1,26 @@ +#import "GBBorderView.h" + +@implementation GBBorderView + + +- (void)awakeFromNib +{ + self.wantsLayer = YES; +} + +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (void)updateLayer +{ + /* Wonderful, wonderful windowserver(?) bug. Using 0,0,0 here would cause it to render garbage + on fullscreen windows on some High Sierra machines. Any other value, including the one used + here (which is rendered exactly the same due to rounding) works around this bug. */ + self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0 + green:0 + blue:1.0 / 1024.0 + alpha:1.0].CGColor; +} +@end diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 1c0a86f3..34cd50bd 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -71,13 +71,6 @@ static const vector_float2 rect[] = [self loadShader]; } -- (void)addSubview:(NSView *)view -{ - /* Fixes rounded corners */ - [super addSubview:view]; - [self setWantsLayer:YES]; -} - - (void) loadShader { NSError *error = nil; From 0783f131b85b96220ab1b211eda600e229ace6d7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 11 Jul 2018 20:07:54 +0300 Subject: [PATCH 0713/1216] Update version to v0.11.1 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 45e3b29f..d81c7746 100755 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.11 +VERSION := 0.11.1 export VERSION CONF ?= debug @@ -306,4 +306,4 @@ libretro: clean: rm -rf build -.PHONY: libretro \ No newline at end of file +.PHONY: libretro From 2e9e3424ecd6b2701ef8f479c939bbd4297ed77b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Jul 2018 21:52:54 +0300 Subject: [PATCH 0714/1216] Document some revision differences --- Core/apu.c | 5 ++++- Core/gb.c | 2 +- Core/gb.h | 16 +++++++--------- Core/memory.c | 34 +++++++++++++++++++++++++++++++--- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 2da43463..260947df 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -381,7 +381,10 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->model == GB_MODEL_CGB_C) { /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, - I'll assume these devices are innocent until proven guilty. */ + I'll assume these devices are innocent until proven guilty. + + Also happens on CGB-B, but not on CGB-D. + */ gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; } gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; diff --git a/Core/gb.c b/Core/gb.c index c3fa4cc8..5f7b4f48 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -447,7 +447,7 @@ static void reset_ram(GB_gameboy_t *gb) { switch (gb->model) { case GB_MODEL_CGB_E: - default: + case GB_MODEL_AGB: /* Unverified */ for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = (random() & 0xFF); } diff --git a/Core/gb.h b/Core/gb.h index 32546247..8cf487d3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -22,24 +22,22 @@ #define GB_STRUCT_VERSION 13 -typedef enum { #ifdef GB_INTERNAL - GB_MODEL_FAMILY_MASK = 0xF00, - GB_MODEL_DMG_FAMILY = 0x000, +#define GB_MODEL_FAMILY_MASK 0xF00 +#define GB_MODEL_DMG_FAMILY 0x000 +#define GB_MODEL_MGB_FAMILY 0x100 +#define GB_MODEL_CGB_FAMILY 0x200 #endif + +typedef enum { + // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, GB_MODEL_DMG_B = 0x002, // GB_MODEL_DMG_C = 0x003, // GB_MODEL_SGB = 0x004, -#ifdef GB_INTERNAL - GB_MODEL_MGB_FAMILY = 0x100, -#endif // GB_MODEL_MGB = 0x100, // GB_MODEL_SGB2 = 0x101, -#ifdef GB_INTERNAL - GB_MODEL_CGB_FAMILY = 0x200, -#endif // GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, // GB_MODEL_CGB_B = 0x202, diff --git a/Core/memory.c b/Core/memory.c index 5f6a89af..b658fd58 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -252,12 +252,25 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_MODEL_CGB_E: case GB_MODEL_AGB: return (addr & 0xF0) | ((addr >> 4) & 0xF); - + + /* + case GB_MODEL_CGB_D: + if (addr > 0xfec0) { + addr |= 0xf0; + } + return gb->extra_oam[addr - 0xfea0]; + */ + case GB_MODEL_CGB_C: + /* + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + */ addr &= ~0x18; return gb->extra_oam[addr - 0xfea0]; - default: + case GB_MODEL_DMG_B: ; } } @@ -544,11 +557,26 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->oam[addr & 0xFF] = value; } switch (gb->model) { + /* + case GB_MODEL_CGB_D: + if (addr > 0xfec0) { + addr |= 0xf0; + } + gb->extra_oam[addr - 0xfea0] = value; + break; + */ case GB_MODEL_CGB_C: + /* + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + */ addr &= ~0x18; gb->extra_oam[addr - 0xfea0] = value; break; - default: + case GB_MODEL_DMG_B: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: break; } return; From c66b6fbafc3308c7aeedd2ab7e32b1cc7c21a836 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 16 Jul 2018 23:08:25 +0300 Subject: [PATCH 0715/1216] Fixed an edge case with DAC discharge emulation --- Core/apu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 260947df..a77f4aaf 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -28,7 +28,6 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign } else { gb->apu.samples[index] = value; - gb->apu_output.dac_discharge[index] = 1.0; } if (gb->apu_output.sample_rate) { @@ -887,6 +886,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } gb->io_registers[reg] = value; + for (unsigned i = 0; i < GB_N_CHANNELS; i++) { + if (gb->apu.is_active[i]) { + gb->apu_output.dac_discharge[i] = 1.0; + } + } } size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb) From b1cc55b786657c5e959d8590ec5f264810c4f53f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 19 Jul 2018 20:38:11 +0300 Subject: [PATCH 0716/1216] Turns out Left/Right audio channels were flipped --- Core/apu.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index a77f4aaf..a7170ec8 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -31,13 +31,13 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign } if (gb->apu_output.sample_rate) { - unsigned left_volume = 0; - if (gb->io_registers[GB_IO_NR51] & (1 << index)) { - left_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; - } unsigned right_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; + } + unsigned left_volume = 0; if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { - right_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; } GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume}; if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { From ce80acc818bbd6129df1f4a0902539264f07522a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Jul 2018 12:34:52 +0300 Subject: [PATCH 0717/1216] Fixed HDMA timing )But still not verified) --- Core/display.c | 4 ++-- Core/gb.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index d88978ed..2759146e 100644 --- a/Core/display.c +++ b/Core/display.c @@ -823,8 +823,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: Measure this value */ - gb->cycles_for_line += 16; - GB_SLEEP(gb, display, 25, 16); + gb->cycles_for_line += 12; + GB_SLEEP(gb, display, 25, 12); if (gb->hdma_on_hblank) { gb->hdma_starting = true; diff --git a/Core/gb.c b/Core/gb.c index 5f7b4f48..8ce8326d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -465,7 +465,7 @@ static void reset_ram(GB_gameboy_t *gb) } break; #if 0 - /* Not emulated yet, for documentation only*/ + /* Not emulated yet, for documentation only */ case GB_MODEL_SGB2: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = 0x55; From 538038e49c7fc81e0500b83cae195c7768e16a3c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Jul 2018 12:35:15 +0300 Subject: [PATCH 0718/1216] Disable randomness in the automation for consistency --- Tester/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tester/main.c b/Tester/main.c index be5f9e83..db72edac 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -15,6 +15,12 @@ #include +/* Disable all randomness during automatic tests */ +long random(void) +{ + return 0; +} + static bool running = false; static char *filename; static char *bmp_filename; From b7426f93c0b1082be51e920a2db6a81fd139752d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Jul 2018 23:23:47 +0300 Subject: [PATCH 0719/1216] Randomize object palettes. Slightly more accurate emulation of FF4C. --- Core/gb.c | 17 +++++++++++++++++ Core/memory.c | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index 8ce8326d..ff5851eb 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -344,7 +344,13 @@ void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callb gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = callback(gb, 0, 0, 0); } + gb->rgb_encode_callback = callback; + + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, true, i * 2); + GB_palette_changed(gb, false, i * 2); + } } void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) @@ -489,6 +495,17 @@ static void reset_ram(GB_gameboy_t *gb) for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) { gb->extra_oam[i] = (random() & 0xFF); } + + if (GB_is_cgb(gb)) { + for (unsigned i = 0; i < 64; i++) { + gb->background_palettes_data[i] = random() & 0xFF; /* Doesn't really matter as the boot ROM overrides it anyway*/ + gb->sprite_palettes_data[i] = random() & 0xFF; + } + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, true, i * 2); + GB_palette_changed(gb, false, i * 2); + } + } } void GB_reset(GB_gameboy_t *gb) diff --git a/Core/memory.c b/Core/memory.c index b658fd58..412af34f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -734,8 +734,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DMG_EMULATION: + GB_log(gb, "4c = %x\n", value); if (GB_is_cgb(gb) && !gb->boot_rom_finished) { - gb->cgb_mode = value != 4; /* The real "contents" of this register aren't quite known yet. */ + gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */ } return; From f7b882f0e875d903c30e2bdb9fac8c0a7f82cf94 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 22 Jul 2018 02:10:26 +0300 Subject: [PATCH 0720/1216] Whoops --- Core/memory.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 412af34f..af2d3e98 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -734,7 +734,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DMG_EMULATION: - GB_log(gb, "4c = %x\n", value); if (GB_is_cgb(gb) && !gb->boot_rom_finished) { gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */ } From a36dd791eca965be580d9b6f965f99996156d860 Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Sun, 22 Jul 2018 18:36:24 +0200 Subject: [PATCH 0721/1216] Rewrite the DAA instruction --- Core/z80_cpu.c | 59 +++++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index cc8f0de9..e187c3e4 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -464,54 +464,39 @@ static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) static void daa(GB_gameboy_t *gb, uint8_t opcode) { - /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ - gb->registers[GB_REGISTER_AF] &= ~GB_ZERO_FLAG; + int16_t result = gb->registers[GB_REGISTER_AF] >> 8; + + gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG); + 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; - } + result = (result - 0x06) & 0xFF; } - else if(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { - gb->registers[GB_REGISTER_AF] += 0xA000; + + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + result -= 0x60; } } else { - if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { - uint16_t 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; + if ((gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) || (result & 0x0F) > 0x09) { + result += 0x06; } - else { - uint16_t 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] & GB_CARRY_FLAG) || result > 0x9F) { + result += 0x60; } } - if ((gb->registers[GB_REGISTER_AF] & 0xFF00) == 0) { + + if ((result & 0xFF) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } + + if ((result & 0x100) == 0x100) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + + gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] |= result << 8; } static void cpl(GB_gameboy_t *gb, uint8_t opcode) From 83ab8efbd7164f862acdf13ad42d15b6a7a52c86 Mon Sep 17 00:00:00 2001 From: Alvaro Burnett Date: Sun, 19 Aug 2018 22:07:16 +0200 Subject: [PATCH 0722/1216] Improve compatibility with the Unix tools included in Git for Windows. Previously the Makefile, when run on Windows, expected the uname command to report either "MINGW" or "windows32". This was unfortunate because the uname included in Git for Windows reports "MSYS". With this change, the Makefile will work properly with any uname, whether it comes from MinGW, GnuWin32 or Git for Windows. --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index d81c7746..1b414747 100755 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ PLATFORM := windows32 USE_WINDRES := true endif +ifneq ($(findstring MSYS,$(PLATFORM)),) +PLATFORM := windows32 +endif + ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa else From 3151821e6d51aae8e6521a785a408d0e8fab0e77 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 9 Sep 2018 12:50:55 +0300 Subject: [PATCH 0723/1216] Fixed minor APU regression (Channels 1 and 2 were given no delay under certain circumstances) --- Core/apu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index a7170ec8..a93ac664 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -644,7 +644,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { gb->apu.is_active[index] = true; - update_square_sample(gb, index); + update_sample(gb, index, 0, 0); } if (gb->apu.square_channels[index].pulse_length == 0) { gb->apu.square_channels[index].pulse_length = 0x40; From ec0a879a93bdee91de7a890a4ab661d727015d10 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 10 Sep 2018 16:59:59 +0300 Subject: [PATCH 0724/1216] =?UTF-8?q?Correct=20emulation=20of=20enabling?= =?UTF-8?q?=20and=20disabling=20the=20volume=20envelope.=20Correct=20emula?= =?UTF-8?q?tion=20of=20a=20glitch=20where=20the=20volume=20envelope=20tick?= =?UTF-8?q?s=20when=20it=20usually=20wouldn=E2=80=99t.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 110 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index a93ac664..2dcfeb40 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -177,6 +177,55 @@ static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) (*volume) &= 0xF; } +static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) +{ + uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + + if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) { + if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { + if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { + gb->apu.square_channels[index].current_volume++; + } + + else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { + gb->apu.square_channels[index].current_volume--; + } + + gb->apu.square_channels[index].volume_countdown = nrx2 & 7; + + if (gb->apu.is_active[index]) { + update_square_sample(gb, index); + } + } + } +} + +static void tick_noise_envelope(GB_gameboy_t *gb) +{ + uint8_t nr42 = gb->io_registers[GB_IO_NR42]; + + if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) { + if (!--gb->apu.noise_channel.volume_countdown) { + if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { + gb->apu.noise_channel.current_volume++; + } + + else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { + gb->apu.noise_channel.current_volume--; + } + + gb->apu.noise_channel.volume_countdown = nr42 & 7; + + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + } + } +} + void GB_apu_div_event(GB_gameboy_t *gb) { if (!gb->apu.global_enable) return; @@ -186,53 +235,27 @@ void GB_apu_div_event(GB_gameboy_t *gb) } gb->apu.div_divider++; - if ((gb->apu.div_divider & 7) == 0) { + if ((gb->apu.div_divider & 1) == 0) { for (unsigned i = GB_SQUARE_2 + 1; i--;) { uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - - if (gb->apu.square_channels[i].volume_countdown) { - if (!--gb->apu.square_channels[i].volume_countdown) { - if ((nrx2 & 8) && gb->apu.square_channels[i].current_volume < 0xF) { - gb->apu.square_channels[i].current_volume++; - } - - else if (!(nrx2 & 8) && gb->apu.square_channels[i].current_volume > 0) { - gb->apu.square_channels[i].current_volume--; - } - - gb->apu.square_channels[i].volume_countdown = nrx2 & 7; - - if (gb->apu.is_active[i]) { - update_square_sample(gb, i); - } - } + if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0 && (nrx2 & 7)) { + tick_square_envelope(gb, i); } } - uint8_t nr42 = gb->io_registers[GB_IO_NR42]; - - if (gb->apu.noise_channel.volume_countdown) { - if (!--gb->apu.noise_channel.volume_countdown) { - if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { - gb->apu.noise_channel.current_volume++; - } - - else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { - gb->apu.noise_channel.current_volume--; - } - - gb->apu.noise_channel.volume_countdown = nr42 & 7; - - if (gb->apu.is_active[GB_NOISE]) { - update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? - gb->apu.noise_channel.current_volume : 0, - 0); - } - } + if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown && (gb->io_registers[GB_IO_NR42] & 7)) { + tick_noise_envelope(gb); } } + if ((gb->apu.div_divider & 7) == 0) { + for (unsigned i = GB_SQUARE_2 + 1; i--;) { + tick_square_envelope(gb, i); + } + + tick_noise_envelope(gb); + } + if ((gb->apu.div_divider & 1) == 1) { for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.square_channels[i].length_enabled) { @@ -589,6 +612,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR12: case GB_IO_NR22: { unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; + if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { + /* Envelope disabled */ + gb->apu.square_channels[index].volume_countdown = 0; + } if ((value & 0xF8) == 0) { /* According to Blargg's test ROM this should disable the channel instantly TODO: verify how "instant" the change is using PCM12 */ @@ -785,7 +812,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } case GB_IO_NR42: { - + if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { + /* Envelope disabled */ + gb->apu.noise_channel.volume_countdown = 0; + } if ((value & 0xF8) == 0) { /* According to Blargg's test ROM this should disable the channel instantly TODO: verify how "instant" the change is using PCM12 */ From 629550c30bdb67a73ca1bb08965ec2cf22a5ee49 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 10 Sep 2018 19:02:45 +0300 Subject: [PATCH 0725/1216] Update .gitattributes --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index 43c8926b..255c40c2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,5 @@ HexFiend/* linguist-vendored +Core/*.h linguist-language=C +SDL/*.h linguist-language=C +Windows/*.h linguist-language=C +Cocoa/*.h linguist-language=Objective-C From dbc338a887a14c5a206b53708f44189dd43a3502 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Sep 2018 00:47:26 +0300 Subject: [PATCH 0726/1216] =?UTF-8?q?Compensate=20for=20prefetch=20in=20DI?= =?UTF-8?q?V=E2=80=99s=20initial=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/gb.c | 3 +++ Core/timing.c | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index ff5851eb..73edb115 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -553,6 +553,9 @@ void GB_reset(GB_gameboy_t *gb) gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF; gb->accessed_oam_row = -1; + + /* Todo: Ugly, fixme, see comment in the timer state machine */ + gb->div_state = 3; gb->magic = (uintptr_t)'SAME'; } diff --git a/Core/timing.c b/Core/timing.c index 58177d5c..65382e4a 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -144,9 +144,11 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE_MACHINE(gb, div, cycles, 1) { GB_STATE(gb, div, 1); GB_STATE(gb, div, 2); + GB_STATE(gb, div, 3); } GB_set_internal_div_counter(gb, 0); +main: GB_SLEEP(gb, div, 1, 3); while (true) { advance_tima_state_machine(gb); @@ -154,6 +156,14 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; GB_SLEEP(gb, div, 2, 4); } + + /* Todo: This is ugly to allow compatibility with 0.11 save states. Fix me when breaking save compatibility */ + { + div3: + /* Compensate for lack of prefetch emulation, as well as DIV's internal initial value */ + GB_set_internal_div_counter(gb, 8); + goto main; + } } void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) From 11c148c85113713160ec19b1402f2a60286c3f2e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Sep 2018 00:48:31 +0300 Subject: [PATCH 0727/1216] Starting G/HDMA directly takes one more M-cycle (More research required) --- Core/memory.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index af2d3e98..4053530e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -833,7 +833,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->hdma_on_hblank = (value & 0x80) != 0; if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) { gb->hdma_on = true; - gb->hdma_cycles = -8; } gb->io_registers[GB_IO_HDMA5] = value; gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; @@ -841,7 +840,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) { gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4; } - gb->hdma_cycles = -8; + gb->hdma_cycles = -12; return; /* Todo: what happens when starting a transfer during a transfer? From 1b049b8f75a3fd53b13e09c3b7f8ea6a565559f6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Sep 2018 17:57:59 +0300 Subject: [PATCH 0728/1216] Fixing UI bugs in the Cocoa debugger. Console windows now display the ROM file name --- Cocoa/Document.m | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 98660642..cf2a7f91 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -356,6 +356,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [self.feedSaveButton removeFromSuperview]; + + self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[self.fileURL path] lastPathComponent]]; + /* contentView.superview.subviews.lastObject is the titlebar view */ NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject; [titleView addSubview: self.feedSaveButton]; @@ -587,7 +590,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [textView.textStorage appendAttributedString:pending_console_output]; [textView scrollToEndOfDocument:nil]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { - [self.consoleWindow orderBack:nil]; + [self.consoleWindow orderFront:nil]; } pending_console_output = nil; } @@ -631,7 +634,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [pending_console_output appendAttributedString:attributed]; } - if ([console_output_timer isValid]) { + if (![console_output_timer isValid]) { console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 repeats:NO block:^(NSTimer * _Nonnull timer) { [self appendPendingOutput]; }]; @@ -1416,4 +1419,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, }]; } +- (void)setFileURL:(NSURL *)fileURL +{ + [super setFileURL:fileURL]; + self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[fileURL path] lastPathComponent]]; + +} + @end From 9080a2391312a6eab0e5d23b3489340996d7ca82 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 11 Oct 2018 18:43:31 +0300 Subject: [PATCH 0729/1216] Improved idle performance in the Cocoa port when using Metal --- Cocoa/GBView.m | 3 --- Cocoa/GBViewGL.m | 8 ++++++++ Cocoa/GBViewMetal.m | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 10f1bca7..a2dbe72f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -140,9 +140,6 @@ GB_set_clock_multiplier(_gb, clockMultiplier); } current_buffer = (current_buffer + 1) % self.numberOfBuffers; - dispatch_async(dispatch_get_main_queue(), ^{ - [self setNeedsDisplay:YES]; - }); } - (uint32_t *) pixels diff --git a/Cocoa/GBViewGL.m b/Cocoa/GBViewGL.m index d678cfe2..15aed088 100644 --- a/Cocoa/GBViewGL.m +++ b/Cocoa/GBViewGL.m @@ -23,4 +23,12 @@ ((GBOpenGLView *)self.internalView).openGLContext = context; } +- (void)flip +{ + [super flip]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self setNeedsDisplay:YES]; + }); +} + @end diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 34cd50bd..4867d513 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -42,6 +42,7 @@ static const vector_float2 rect[] = MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; view.delegate = self; self.internalView = view; + view.paused = YES; MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; @@ -125,10 +126,14 @@ static const vector_float2 rect[] = - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { output_resolution = (vector_float2){size.width, size.height}; + dispatch_async(dispatch_get_main_queue(), ^{ + [(MTKView *)self.internalView draw]; + }); } - (void)drawInMTKView:(nonnull MTKView *)view { + if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; [texture replaceRegion:region mipmapLevel:0 withBytes:[self currentBuffer] @@ -188,4 +193,13 @@ static const vector_float2 rect[] = [command_buffer commit]; } + +- (void)flip +{ + [super flip]; + dispatch_async(dispatch_get_main_queue(), ^{ + [(MTKView *)self.internalView draw]; + }); +} + @end From 78546869030992e5076b20323d16f275f546e1ed Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Thu, 11 Oct 2018 21:54:04 -0400 Subject: [PATCH 0730/1216] Remove redefinition of CC Redefining CC isn't a standard thing to do in Makefiles. See more information about this over at: https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 1b414747..b098cdec 100755 --- a/Makefile +++ b/Makefile @@ -34,8 +34,6 @@ OBJ := build/obj BOOTROMS_DIR ?= $(BIN)/BootROMs # Set tools - -CC := clang ifeq ($(PLATFORM),windows32) # To force use of the Unix version instead of the Windows version MKDIR := $(shell which mkdir) From f4ee044347c0b4e9e399aac78d5599858298bfbe Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Thu, 11 Oct 2018 22:17:16 -0400 Subject: [PATCH 0731/1216] Fix control reaches end of non-void function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes it so that there is a return value when a kind doens't match. Allows -Werror=return-type to pass. ``` Core/debugger.c: In function ‘read_lvalue’: Core/debugger.c:239:1: error: control reaches end of non-void function [-Werror=return-type] } ``` --- Core/debugger.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 2cc49abe..c125d606 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -226,15 +226,15 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); - - case LVALUE_REG16: - return VALUE_16(*lvalue.register_address); - case LVALUE_REG_L: return VALUE_16(*lvalue.register_address & 0x00FF); case LVALUE_REG_H: return VALUE_16(*lvalue.register_address >> 8); + + case LVALUE_REG16: + default: + return VALUE_16(*lvalue.register_address); } } From b8825127fdca3f492ba4f57bfa0752fd4dabb5a7 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Thu, 11 Oct 2018 22:37:26 -0400 Subject: [PATCH 0732/1216] =?UTF-8?q?Fix=20parentheses=20around=20?= =?UTF-8?q?=E2=80=98+=E2=80=99=20in=20operand=20of=20=E2=80=98&=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` Core/z80_cpu.c: In function ‘add_hl_rr’: Core/z80_cpu.c:341:31: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses] if ( ((unsigned long) hl) + ((unsigned long) rr) & 0x10000) { ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ ``` --- Core/z80_cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index e187c3e4..705fefeb 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -338,7 +338,7 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if ( ((unsigned long) hl) + ((unsigned long) rr) & 0x10000) { + if ( ((unsigned long) hl) + (((unsigned long) rr) & 0x10000)) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } From ec41e87cf1e23dac87b6a8bb25e01fedc3551d23 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Thu, 11 Oct 2018 22:39:43 -0400 Subject: [PATCH 0733/1216] Update logic behind params --- Core/z80_cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 705fefeb..4584c534 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -338,7 +338,7 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if ( ((unsigned long) hl) + (((unsigned long) rr) & 0x10000)) { + if ( ((unsigned long) hl + (unsigned long) rr) & 0x10000) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } From e834d32b8e59026ec86af9a729a4ceddc8eb7915 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Fri, 12 Oct 2018 09:41:25 -0400 Subject: [PATCH 0734/1216] Update switch to just return at the end instead --- Core/debugger.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index c125d606..acf1a558 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -226,16 +226,17 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); + case LVALUE_REG16: + return VALUE_16(*lvalue.register_address); + case LVALUE_REG_L: return VALUE_16(*lvalue.register_address & 0x00FF); case LVALUE_REG_H: return VALUE_16(*lvalue.register_address >> 8); - - case LVALUE_REG16: - default: - return VALUE_16(*lvalue.register_address); } + + return VALUE_16(*lvalue.register_address); } static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) From 2da7a3f7fb29058b9531c468771a0f0ebcbf9d2e Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Fri, 12 Oct 2018 10:09:30 -0400 Subject: [PATCH 0735/1216] Fix the return value of read_lvalue to be 0 --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index acf1a558..fd292b78 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -236,7 +236,7 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) return VALUE_16(*lvalue.register_address >> 8); } - return VALUE_16(*lvalue.register_address); + return VALUE_16(0); } static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) From 86005bcfb85a44caa28045d8471d329a4980f6a4 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Fri, 12 Oct 2018 20:14:34 -0400 Subject: [PATCH 0736/1216] Update Makefile to use clang if it's available --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index b098cdec..58064d24 100755 --- a/Makefile +++ b/Makefile @@ -34,6 +34,12 @@ OBJ := build/obj BOOTROMS_DIR ?= $(BIN)/BootROMs # Set tools + +# Use clang if it's available. +ifneq (, $(shell which clang)) +CC := clang +endif + ifeq ($(PLATFORM),windows32) # To force use of the Unix version instead of the Windows version MKDIR := $(shell which mkdir) From 7a6d6c385096448031d566ba78ca0de2c57a1dfe Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 13 Oct 2018 17:19:05 -0400 Subject: [PATCH 0737/1216] Ignore multichar and int-in-bool-context warnings --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 58064d24..e81b8685 100755 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ endif # Set compilation and linkage flags based on target, platform and configuration -CFLAGS += -Werror -Wall -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -Werror -Wall -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows From 839de39c1fdefd3d084f992c23b5999621e83fe1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 14 Oct 2018 14:57:09 +0300 Subject: [PATCH 0738/1216] Fix clang build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e81b8685..330f2c81 100755 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ endif # Set compilation and linkage flags based on target, platform and configuration -CFLAGS += -Werror -Wall -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -Werror -Wall -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows From 324201f336a23bfbe71f19c5dbbd281dafef1fae Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 17 Oct 2018 20:35:29 +0300 Subject: [PATCH 0739/1216] Correct emulation of switching the DACs on and off. Fixes #100 and #87 --- Core/apu.c | 33 ++++++++++++++++++++++++++------- Core/apu.h | 3 ++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 2dcfeb40..82a65161 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -21,9 +21,28 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; } +static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index) +{ + switch (index) { + case GB_SQUARE_1: + return gb->io_registers[GB_IO_NR12] & 0xF8; + + case GB_SQUARE_2: + return gb->io_registers[GB_IO_NR22] & 0xF8; + + case GB_WAVE: + return gb->apu.wave_channel.enable; + + case GB_NOISE: + return gb->io_registers[GB_IO_NR42] & 0xF8; + } + + return 0; +} + static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) { - if (!gb->apu.is_active[index]) { + if (!is_DAC_enabled(gb, index)) { value = gb->apu.samples[index]; } else { @@ -52,7 +71,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) GB_sample_t output = {0,0}; for (unsigned i = GB_N_CHANNELS; i--;) { double multiplier = CH_STEP; - if (!gb->apu.is_active[i]) { + if (!is_DAC_enabled(gb, i)) { gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; if (gb->apu_output.dac_discharge[i] < 0) { multiplier = 0; @@ -617,8 +636,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].volume_countdown = 0; } if ((value & 0xF8) == 0) { - /* According to Blargg's test ROM this should disable the channel instantly - TODO: verify how "instant" the change is using PCM12 */ + /* This disables the DAC */ + gb->io_registers[reg] = value; gb->apu.is_active[index] = false; update_sample(gb, index, 0, 0); } @@ -817,8 +836,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.volume_countdown = 0; } if ((value & 0xF8) == 0) { - /* According to Blargg's test ROM this should disable the channel instantly - TODO: verify how "instant" the change is using PCM12 */ + /* This disables the DAC */ + gb->io_registers[reg] = value; gb->apu.is_active[GB_NOISE] = false; update_sample(gb, GB_NOISE, 0, 0); } @@ -917,7 +936,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } gb->io_registers[reg] = value; for (unsigned i = 0; i < GB_N_CHANNELS; i++) { - if (gb->apu.is_active[i]) { + if (is_DAC_enabled(gb, i)) { gb->apu_output.dac_discharge[i] = 1.0; } } diff --git a/Core/apu.h b/Core/apu.h index 10f667d2..f6c456aa 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -8,7 +8,8 @@ #ifdef GB_INTERNAL /* Speed = 1 / Length (in seconds) */ -#define DAC_DECAY_SPEED 500.0 +/* Todo: Measure this and find the actual curve shape*/ +#define DAC_DECAY_SPEED 50 /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ #ifdef WIIU From 4276549acdf8bbb124b7116a6380bfff1f38499d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Oct 2018 01:02:27 +0300 Subject: [PATCH 0740/1216] Research notes TODOs --- Core/apu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index 82a65161..6041c25e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -797,6 +797,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.length_enabled = false; } /* Note that we don't change the sample just yet! This was verified on hardware. */ + /* Todo: The first sample *is not* skipped on the DMG, this is a bug introduced + on the CGB. It appears that the bug was fixed on the AGB, but it's not reflected + by PCM434. */ + /* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */ } /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ From 3035f43428b15dbc75dff150646dd97425f91ec4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Oct 2018 23:53:01 +0300 Subject: [PATCH 0741/1216] Emulation of DAC charging, Fixes #46, #85, #88 and #89 --- Core/apu.c | 24 +++++++++++++++--------- Core/apu.h | 9 +++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 6041c25e..1bdc2750 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -75,9 +75,19 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; if (gb->apu_output.dac_discharge[i] < 0) { multiplier = 0; + gb->apu_output.dac_discharge[i] = 0; } else { - multiplier *= pow(0.05, 1 - gb->apu_output.dac_discharge[i]) * (gb->apu_output.dac_discharge[i]); + multiplier *= gb->apu_output.dac_discharge[i]; + } + } + else { + gb->apu_output.dac_discharge[i] += ((double) DAC_ATTACK_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] > 1) { + gb->apu_output.dac_discharge[i] = 1; + } + else { + multiplier *= gb->apu_output.dac_discharge[i]; } } @@ -797,9 +807,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.length_enabled = false; } /* Note that we don't change the sample just yet! This was verified on hardware. */ - /* Todo: The first sample *is not* skipped on the DMG, this is a bug introduced - on the CGB. It appears that the bug was fixed on the AGB, but it's not reflected - by PCM434. */ + /* Todo: The first sample might *not* beskipped on the DMG, this could be a bug + introduced on the CGB. It appears that the bug was fixed on the AGB, but it's + not reflected by PCM34. This should be probably verified as this could just + mean differences in the DACs. */ /* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */ } @@ -939,11 +950,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } gb->io_registers[reg] = value; - for (unsigned i = 0; i < GB_N_CHANNELS; i++) { - if (is_DAC_enabled(gb, i)) { - gb->apu_output.dac_discharge[i] = 1.0; - } - } } size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb) diff --git a/Core/apu.h b/Core/apu.h index f6c456aa..baa56a43 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -8,8 +8,13 @@ #ifdef GB_INTERNAL /* Speed = 1 / Length (in seconds) */ -/* Todo: Measure this and find the actual curve shape*/ -#define DAC_DECAY_SPEED 50 +/* Todo: Measure these and find the actual curve shapes. + They are known to be incorrect (Some analog test ROM sound different), + but are good enough approximations to fix Cannon Fodder's terrible audio. + It also varies by model. */ +#define DAC_DECAY_SPEED (1000000) +#define DAC_ATTACK_SPEED 1000 + /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ #ifdef WIIU From 9ffeef88d50ea8a25a5d9d7fc4f43ac7a4e1324b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Oct 2018 17:19:53 +0300 Subject: [PATCH 0742/1216] Trying to fix libretro achievements (#48) --- libretro/libretro.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 44ebbb96..2073a8ca 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -351,7 +351,7 @@ static void init_for_current_model(unsigned id) set_link_cable_state(true); } - struct retro_memory_descriptor descs[7]; + struct retro_memory_descriptor descs[8]; size_t size; uint16_t bank; @@ -370,7 +370,7 @@ static void init_for_current_model(unsigned id) descs[2].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank); descs[2].start = 0xC000; - descs[2].len = 0x2000; + descs[2].len = 0x1000; descs[3].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); descs[3].start = 0xA000; @@ -388,6 +388,10 @@ static void init_for_current_model(unsigned id) descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); descs[6].start = 0xFE00; descs[6].len = 0x00A0; + + descs[7].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank) + 0x1000; + descs[7].start = 0xD000; + descs[7].len = 0x1000; struct retro_memory_map mmaps; mmaps.descriptors = descs; From 64922fff4b0200c17b4933f3b5f73ed73fd79f22 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 29 Oct 2018 00:44:43 +0200 Subject: [PATCH 0743/1216] Fixed a bug where channels 1 and 2 would start playing earlier than they should have if NRx4 was written to twice. Fixes #86. --- Core/apu.c | 4 ++++ Core/apu.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 1bdc2750..ff1cb5cb 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -172,6 +172,8 @@ static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb) static void update_square_sample(GB_gameboy_t *gb, unsigned index) { + if (gb->apu.square_channels[index].current_sample_index & 0x80) return; + uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; update_sample(gb, index, duties[gb->apu.square_channels[index].current_sample_index + duty * 8]? @@ -701,6 +703,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { gb->apu.is_active[index] = true; update_sample(gb, index, 0, 0); + /* We use the highest bit in current_sample_index to mark this sample is not actually playing yet, */ + gb->apu.square_channels[index].current_sample_index |= 0x80; } if (gb->apu.square_channels[index].pulse_length == 0) { gb->apu.square_channels[index].pulse_length = 0x40; diff --git a/Core/apu.h b/Core/apu.h index baa56a43..01ca1321 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -75,7 +75,9 @@ typedef struct uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks uint8_t current_volume; // Reloaded from NRX2 uint8_t volume_countdown; // Reloaded from NRX2 - uint8_t current_sample_index; + uint8_t current_sample_index; /* For save state compatibility, + highest bit is reused (See NR14/NR24's + write code)*/ uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_length; // From NRX3, NRX4, in APU ticks From c9d6a1381f5f71318a6d23a749fd54f9a02f3baa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Nov 2018 01:31:14 +0200 Subject: [PATCH 0744/1216] Cross emulator compatibility with RTC saves --- Core/gb.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++------- Core/gb.h | 38 ++++++++++++-------- 2 files changed, 116 insertions(+), 27 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 73edb115..edc0a560 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -195,6 +195,36 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +typedef struct { + uint8_t seconds; + uint8_t padding1[3]; + uint8_t minutes; + uint8_t padding2[3]; + uint8_t hours; + uint8_t padding3[3]; + uint8_t days; + uint8_t padding4[3]; + uint8_t high; + uint8_t padding5[3]; +} GB_vba_rtc_time_t; + +typedef union { + struct __attribute__((packed)) { + GB_rtc_time_t rtc_real; + time_t last_rtc_second; /* Platform specific endianess and size */ + } sameboy_legacy; + struct { + /* Used by VBA versions with 32-bit timestamp*/ + GB_vba_rtc_time_t rtc_real, rtc_latched; + uint32_t last_rtc_second; /* Always little endian */ + } vba32; + struct { + /* Used by BGB and VBA versions with 64-bit timestamp*/ + GB_vba_rtc_time_t rtc_real, rtc_latched; + uint64_t last_rtc_second; /* Always little endian */ + } vba64; +} GB_rtc_save_t; + int GB_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. @@ -210,15 +240,27 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return EIO; } if (gb->cartridge_type->has_rtc) { - if (fwrite(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) { + GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; + rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; + rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; + rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; + rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; +#ifdef GB_BIG_ENDIAN + rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); +#else + rtc_save.vba64.last_rtc_second = gb->last_rtc_second; +#endif + if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) { 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; @@ -238,14 +280,53 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) goto reset_rtc; } - if (fread(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) { - goto reset_rtc; + GB_rtc_save_t rtc_save; + switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) { + case sizeof(rtc_save.sameboy_legacy): + memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba32.last_rtc_second; +#endif + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba64.last_rtc_second; +#endif + break; + + default: + 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; diff --git a/Core/gb.h b/Core/gb.h index 8cf487d3..d20ef2b7 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -29,6 +29,25 @@ #define GB_MODEL_CGB_FAMILY 0x200 #endif +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define GB_BIG_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define GB_LITTLE_ENDIAN +#else +#error Unable to detect endianess +#endif + +typedef union { + struct { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t days; + uint8_t high; + }; + uint8_t data[5]; +} GB_rtc_time_t; + typedef enum { // GB_MODEL_DMG_0 = 0x000, @@ -265,18 +284,16 @@ struct GB_gameboy_internal_s { sp; }; struct { -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#ifdef GB_BIG_ENDIAN uint8_t a, f, b, c, d, e, h, l; -#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#else uint8_t f, a, c, b, e, d, l, h; -#else -#error Unable to detect endianess #endif }; @@ -389,17 +406,8 @@ struct GB_gameboy_internal_s { /* RTC */ GB_SECTION(rtc, - union { - struct { - uint8_t seconds; - uint8_t minutes; - uint8_t hours; - uint8_t days; - uint8_t high; - }; - uint8_t data[5]; - } rtc_real, rtc_latched; - time_t last_rtc_second; + GB_rtc_time_t rtc_real, rtc_latched; + uint64_t last_rtc_second; bool rtc_latch; ); From 96e9ea2d1e280cd3dd2866188773f9e251a17aad Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Nov 2018 13:18:08 +0200 Subject: [PATCH 0745/1216] Fix libretro RTC support. Fixes #41 --- libretro/libretro.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 2073a8ca..aa715915 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -994,7 +994,7 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { - void* data = NULL; + void *data = NULL; switch(mode) { case MODE_SINGLE_GAME: @@ -1016,7 +1016,7 @@ void *retro_get_memory_data(unsigned type) break; case RETRO_MEMORY_RTC: if(gameboy[0].cartridge_type->has_battery) - data = &gameboy[0].rtc_real; + data = GB_GET_SECTION(&gameboy[0], rtc); else data = NULL; break; @@ -1043,13 +1043,13 @@ void *retro_get_memory_data(unsigned type) break; case RETRO_MEMORY_GAMEBOY_1_RTC: if(gameboy[0].cartridge_type->has_battery) - data = &gameboy[0].rtc_real; + data = GB_GET_SECTION(&gameboy[0], rtc); else data = NULL; break; case RETRO_MEMORY_GAMEBOY_2_RTC: if(gameboy[1].cartridge_type->has_battery) - data = &gameboy[1].rtc_real; + data = GB_GET_SECTION(&gameboy[1], rtc); else data = NULL; break; @@ -1088,7 +1088,7 @@ size_t retro_get_memory_size(unsigned type) break; case RETRO_MEMORY_RTC: if(gameboy[0].cartridge_type->has_battery) - size = sizeof (gameboy[0].rtc_real); + size = GB_SECTION_SIZE(rtc); else size = 0; break; @@ -1115,11 +1115,11 @@ size_t retro_get_memory_size(unsigned type) break; case RETRO_MEMORY_GAMEBOY_1_RTC: if(gameboy[0].cartridge_type->has_battery) - size = sizeof (gameboy[0].rtc_real); + size = GB_SECTION_SIZE(rtc); break; case RETRO_MEMORY_GAMEBOY_2_RTC: if(gameboy[1].cartridge_type->has_battery) - size = sizeof (gameboy[1].rtc_real); + size = GB_SECTION_SIZE(rtc); break; default: break; From a39b314378f67ce56eff97d606b7e8b9ea93708e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Nov 2018 14:33:12 +0200 Subject: [PATCH 0746/1216] Fixed silly APU regression (Noise volume envelope ran too fast) Fixes #121 --- Core/apu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index ff1cb5cb..f9aabd7f 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -274,7 +274,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } } - if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown && (gb->io_registers[GB_IO_NR42] & 7)) { + if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) { tick_noise_envelope(gb); } } From 792b45d5dd27ebff162ef9cb79f07ee6b6cf5ffa Mon Sep 17 00:00:00 2001 From: radius Date: Mon, 5 Nov 2018 18:59:13 -0500 Subject: [PATCH 0747/1216] don't reinit on audio filter change or color correction --- libretro/libretro.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index aa715915..7d50c52a 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -444,8 +444,11 @@ static void check_variables(bool link) else new_model = MODEL_AUTO; - model[0] = new_model; - init_for_current_model(0); + if (new_model != model[0]) + { + model[0] = new_model; + init_for_current_model(0); + } } } else From d05ee826d45dcbd5dff661491bc3b1227159a9c7 Mon Sep 17 00:00:00 2001 From: NieDzejkob Date: Fri, 9 Nov 2018 22:20:35 +0100 Subject: [PATCH 0748/1216] Fix building the tester binary on Linux --- Tester/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tester/main.c b/Tester/main.c index db72edac..2ea76285 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -11,6 +11,8 @@ #include #include #define snprintf _snprintf +#else +#include #endif #include From 91904df5e8cc7370776c37bdb46467a81887a6b4 Mon Sep 17 00:00:00 2001 From: NieDzejkob Date: Fri, 9 Nov 2018 23:20:57 +0100 Subject: [PATCH 0749/1216] Add a build-time option to change the resources directory. Normally, SameBoy would use executable-relative paths for any resource files, which posed problems for packaging the software by distributions, which usually prefer FHS-compliant file locations. This commit makes it possible to specify an alternative base directory with a compile-time environment variable. --- Makefile | 4 ++++ README.md | 4 +++- SDL/gui.c | 2 +- SDL/main.c | 4 ++-- SDL/shader.c | 4 ++-- SDL/utils.c | 16 ++++++++++------ SDL/utils.h | 4 ++-- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 330f2c81..7038ee98 100755 --- a/Makefile +++ b/Makefile @@ -33,6 +33,10 @@ BIN := build/bin OBJ := build/obj BOOTROMS_DIR ?= $(BIN)/BootROMs +ifdef DATA_DIR +CFLAGS += -DDATA_DIR="\"$(DATA_DIR)\"" +endif + # Set tools # Use clang if it's available. diff --git a/README.md b/README.md index b1a2011d..91e0bf6c 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ On Windows, SameBoy also requires: * [GnuWin](http://gnuwin32.sourceforge.net/) * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. -To compile, simply run `make`. The targets are cocoa (Default for macOS), sdl (Default for everything else), libretro, bootroms and tester. You may also specify CONF=debug (default), CONF=release or CONF=native_release to control optimization and symbols. native_release is faster than release, but is optimized to the host's CPU and therefore is not portable. You may set BOOTROMS_DIR=... to a directory containing precompiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs. +To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release` or `CONF=native_release` to control optimization and symbols. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. You may set `BOOTROMS_DIR=...` to a directory containing precompiled `dmg_boot.bin` and `cgb_boot.bin` files, otherwise the build system will compile and use SameBoy's own boot ROMs. + +By default, the SDL port will look for resource files with a path relative to executable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character. SameBoy was compiled and tested on macOS, Ubuntu and 32-bit Windows 7. diff --git a/SDL/gui.c b/SDL/gui.c index d43d939a..c32eec4d 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -746,7 +746,7 @@ void run_gui(bool is_running) /* Draw the background screen */ static SDL_Surface *converted_background = NULL; if (!converted_background) { - SDL_Surface *background = SDL_LoadBMP(executable_relative_path("background.bmp")); + SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4); converted_background = SDL_ConvertSurface(background, pixel_format, 0); SDL_LockSurface(converted_background); diff --git a/SDL/main.c b/SDL/main.c index 0cf1c79d..34e2f80d 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -426,7 +426,7 @@ restart: bool error = false; start_capturing_logs(); const char * const boot_roms[] = {"dmg_boot.bin", "cgb_boot.bin", "agb_boot.bin"}; - error = GB_load_boot_rom(&gb, executable_relative_path(boot_roms[configuration.model])); + error = GB_load_boot_rom(&gb, resource_path(boot_roms[configuration.model])); end_capturing_logs(true, error); start_capturing_logs(); @@ -442,7 +442,7 @@ restart: GB_load_battery(&gb, battery_save_path); /* Configure symbols */ - GB_debugger_load_symbol_file(&gb, executable_relative_path("registers.sym")); + GB_debugger_load_symbol_file(&gb, resource_path("registers.sym")); char symbols_path[path_length + 5]; replace_extension(filename, path_length, symbols_path, ".sym"); diff --git a/SDL/shader.c b/SDL/shader.c index 5b41b2a8..ed45c42f 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -78,7 +78,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) static signed long filter_token_location = 0; if (!master_shader_code[0]) { - FILE *master_shader_f = fopen(executable_relative_path("Shaders/MasterShader.fsh"), "r"); + FILE *master_shader_f = fopen(resource_path("Shaders/MasterShader.fsh"), "r"); if (!master_shader_f) return false; fread(master_shader_code, 1, sizeof(master_shader_code) - 1, master_shader_f); fclose(master_shader_f); @@ -92,7 +92,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) char shader_path[1024]; sprintf(shader_path, "Shaders/%s.fsh", name); - FILE *shader_f = fopen(executable_relative_path(shader_path), "r"); + FILE *shader_f = fopen(resource_path(shader_path), "r"); if (!shader_f) return false; memset(shader_code, 0, sizeof(shader_code)); fread(shader_code, 1, sizeof(shader_code) - 1, shader_f); diff --git a/SDL/utils.c b/SDL/utils.c index 539e3518..eee6ce65 100644 --- a/SDL/utils.c +++ b/SDL/utils.c @@ -3,8 +3,11 @@ #include #include "utils.h" -const char *executable_folder(void) +const char *resource_folder(void) { +#ifdef DATA_DIR + return DATA_DIR; +#else static const char *ret = NULL; if (!ret) { ret = SDL_GetBasePath(); @@ -13,21 +16,22 @@ const char *executable_folder(void) } } return ret; +#endif } -char *executable_relative_path(const char *filename) +char *resource_path(const char *filename) { static char path[1024]; - snprintf(path, sizeof(path), "%s%s", executable_folder(), filename); + snprintf(path, sizeof(path), "%s%s", resource_folder(), filename); return path; } - + void replace_extension(const char *src, size_t length, char *dest, const char *ext) { memcpy(dest, src, length); dest[length] = 0; - + /* Remove extension */ for (size_t i = length; i--;) { if (dest[i] == '/') break; @@ -36,7 +40,7 @@ void replace_extension(const char *src, size_t length, char *dest, const char *e break; } } - + /* Add new extension */ strcat(dest, ext); } diff --git a/SDL/utils.h b/SDL/utils.h index 7995da90..216e723e 100644 --- a/SDL/utils.h +++ b/SDL/utils.h @@ -2,8 +2,8 @@ #define utils_h #include -const char *executable_folder(void); -char *executable_relative_path(const char *filename); +const char *resource_folder(void); +char *resource_path(const char *filename); void replace_extension(const char *src, size_t length, char *dest, const char *ext); #endif /* utils_h */ From bc48c9bc26e992e4214bb6240c71aa96c8141173 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Nov 2018 16:32:45 +0200 Subject: [PATCH 0750/1216] Added command line fullscreen flag to the SDL port, closes #126 --- SDL/main.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index 0cf1c79d..5f5c76ae 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -488,14 +488,28 @@ static void save_configuration(void) } } +static bool get_arg_flag(const char *flag, int *argc, char **argv) +{ + for (unsigned i = 1; i < *argc; i++) { + if (strcmp(argv[i], flag) == 0) { + (*argc)--; + argv[i] = argv[*argc]; + return true; + } + } + return false; +} + int main(int argc, char **argv) { #define str(x) #x #define xstr(x) str(x) fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); + + bool fullscreen = get_arg_flag("--fullscreen", &argc, argv); if (argc > 2) { - fprintf(stderr, "Usage: %s [rom]\n", argv[0]); + fprintf(stderr, "Usage: %s [--fullscreen] [rom]\n", argv[0]); exit(1); } @@ -515,6 +529,10 @@ int main(int argc, char **argv) 160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_SetWindowMinimumSize(window, 160, 144); + if (fullscreen) { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + SDL_GLContext gl_context = SDL_GL_CreateContext(window); GLint major = 0, minor = 0; From 453673a2a653a45d8ee378ba5335f98df5e22efa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Nov 2018 18:58:22 +0200 Subject: [PATCH 0751/1216] Apply the SDL 2.0.6 audio workaround to everything except Windows, check the linked version instead of the headers version. Fixes #130 --- SDL/main.c | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index c21a96f9..7db59be4 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -559,25 +559,31 @@ int main(int argc, char **argv) want_aspec.freq = AUDIO_FREQUENCY; want_aspec.format = AUDIO_S16SYS; want_aspec.channels = 2; -#if SDL_COMPILEDVERSION >= 2005 && defined(__APPLE__) - /* SDL 2.0.5 on macOS introduced a bug where certain combinations of buffer lengths and frequencies - fail to produce audio correctly. */ - want_aspec.samples = 2048; -#else want_aspec.samples = 512; + + SDL_version _sdl_version; + SDL_GetVersion(&_sdl_version); + unsigned sdl_version = _sdl_version.major * 1000 + _sdl_version.minor * 100 + _sdl_version.patch; + +#ifndef _WIN32 + /* SDL 2.0.5 on macOS and Linux introduced a bug where certain combinations of buffer lengths and frequencies + fail to produce audio correctly. */ + if (sdl_version >= 2005) { + want_aspec.samples = 2048; + } +#else + if (sdl_version >= 2006) { + /* SDL 2.0.6 offers WASAPI support which allows for much lower audio buffer lengths which at least + theoretically reduces lagging. */ + want_aspec.samples = 32; + } + else { + /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency + to 44100 because otherwise we would get garbled audio output.*/ + want_aspec.freq = 44100; + } #endif - -#if SDL_COMPILEDVERSION >= 2006 && defined(_WIN32) - /* SDL 2.0.6 offers WASAPI support which allows for much lower audio buffer lengths which at least - theoretically reduces lagging. */ - want_aspec.samples = 32; -#endif - -#if SDL_COMPILEDVERSION <= 2005 && defined(_WIN32) - /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency - to 44100 because otherwise we would get garbled audio output.*/ - want_aspec.freq = 44100; -#endif + want_aspec.callback = audio_callback; want_aspec.userdata = &gb; From 94136f5741b7484189af048830126c4bad04ac11 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Nov 2018 19:14:18 +0200 Subject: [PATCH 0752/1216] =?UTF-8?q?Adjust=20DAC=20attributes=20to=20fix?= =?UTF-8?q?=20LADX=E2=80=99s=20crackling=20audio=20(Fixes=20#125)=20while?= =?UTF-8?q?=20keeping=20Cannon=20Fodder=E2=80=99s=20buzzing=20reasonable?= =?UTF-8?q?=20(Proper=20audio=20measurements=20still=20required)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.h b/Core/apu.h index 01ca1321..ab42055b 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -12,7 +12,7 @@ They are known to be incorrect (Some analog test ROM sound different), but are good enough approximations to fix Cannon Fodder's terrible audio. It also varies by model. */ -#define DAC_DECAY_SPEED (1000000) +#define DAC_DECAY_SPEED 50000 #define DAC_ATTACK_SPEED 1000 From 7ffed9c43cd533c4ab16ab9d8b0879a39a00a02b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Nov 2018 19:39:57 +0200 Subject: [PATCH 0753/1216] Reconnect the joypad when SameBoy starts directly to a ROM (fixes #131) --- SDL/gui.c | 10 ++++++++-- SDL/gui.h | 1 + SDL/main.c | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index c32eec4d..ed9f3573 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -721,8 +721,7 @@ joypad_axis_t get_joypad_axis(uint8_t physical_axis) } -extern void set_filename(const char *new_filename, bool new_should_free); -void run_gui(bool is_running) +void connect_joypad(void) { if (joystick && !SDL_NumJoysticks()) { if (controller) { @@ -743,6 +742,13 @@ void run_gui(bool is_running) joystick = SDL_JoystickOpen(0); } } +} + +extern void set_filename(const char *new_filename, bool new_should_free); +void run_gui(bool is_running) +{ + connect_joypad(); + /* Draw the background screen */ static SDL_Surface *converted_background = NULL; if (!converted_background) { diff --git a/SDL/gui.h b/SDL/gui.h index 9893eb61..4d106143 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -92,6 +92,7 @@ extern configuration_t configuration; void update_viewport(void); void run_gui(bool is_running); void render_texture(void *pixels, void *previous); +void connect_joypad(void); joypad_button_t get_joypad_button(uint8_t physical_button); joypad_axis_t get_joypad_axis(uint8_t physical_axis); diff --git a/SDL/main.c b/SDL/main.c index 7db59be4..99facf8d 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -616,6 +616,9 @@ int main(int argc, char **argv) if (filename == NULL) { run_gui(false); } + else { + connect_joypad(); + } SDL_PauseAudioDevice(device_id, 0); run(); // Never returns return 0; From a47e3cc62c56f4c46e96e2a9354937fca4aa2e61 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Nov 2018 21:10:09 +0200 Subject: [PATCH 0754/1216] Remove code duplication in the QL fast boot ROM, should fix some glitched thumbnails --- BootROMs/cgb_boot.asm | 22 +- BootROMs/cgb_boot_fast.asm | 784 +------------------------------------ 2 files changed, 21 insertions(+), 785 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index ba139135..31fbeabc 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -1,7 +1,5 @@ ; 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 @@ -142,11 +140,22 @@ Start: ld hl, BgPalettes xor a .expandPalettesLoop: +IF !DEF(FAST) cpl +ENDC ; One white ldi [hl], a ldi [hl], a +IF DEF(FAST) + ; 3 more whites + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a + ldi [hl], a +ELSE ; The actual color ld a, [de] inc de @@ -161,6 +170,7 @@ Start: ldi [hl], a ldi [hl], a ldi [hl], a +ENDC dec c jr nz, .expandPalettesLoop @@ -174,6 +184,7 @@ Start: ld a, $91 ldh [$40], a +IF !DEF(FAST) call DoIntroAnimation ; Wait ~0.75 seconds @@ -199,6 +210,10 @@ Start: ld hl, WaitLoopCounter dec [hl] jr nz, .waitLoop +ELSE + ld a, $c1 + call PlaySound +ENDC call Preboot ; Will be filled with NOPs @@ -716,8 +731,9 @@ DoIntroAnimation: ret Preboot: - +IF !DEF(FAST) call FadeOut +ENDC call ClearVRAMViaHDMA ; Select the first bank xor a diff --git a/BootROMs/cgb_boot_fast.asm b/BootROMs/cgb_boot_fast.asm index b7351b9d..cddb4750 100644 --- a/BootROMs/cgb_boot_fast.asm +++ b/BootROMs/cgb_boot_fast.asm @@ -1,782 +1,2 @@ -; 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 - - 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 +FAST EQU 1 +include "cgb_boot.asm" \ No newline at end of file From 44891d5c4a4e69fa8b37f94630faff58dd7d3d44 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Nov 2018 01:16:32 +0200 Subject: [PATCH 0755/1216] =?UTF-8?q?Initial=20code=20to=20support=20SGB,?= =?UTF-8?q?=20command=20=E2=80=9Cparsing=E2=80=9D,=20replacement=20SGB=20b?= =?UTF-8?q?oot=20ROM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BootROMs/sgb_boot.asm | 209 ++++++++++++++++++++++++++++++++++++++++++ Cocoa/Document.m | 22 ++++- Cocoa/MainMenu.xib | 15 ++- Core/gb.c | 19 +++- Core/gb.h | 20 +++- Core/joypad.c | 4 +- Core/memory.c | 7 ++ Core/sgb.c | 51 +++++++++++ Core/sgb.h | 9 ++ Makefile | 9 +- 10 files changed, 346 insertions(+), 19 deletions(-) create mode 100644 BootROMs/sgb_boot.asm create mode 100644 Core/sgb.c create mode 100644 Core/sgb.h diff --git a/BootROMs/sgb_boot.asm b/BootROMs/sgb_boot.asm new file mode 100644 index 00000000..c4d537f7 --- /dev/null +++ b/BootROMs/sgb_boot.asm @@ -0,0 +1,209 @@ +; 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 to white + ld a, $0 + ldh [$47], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $34 ; End of logo + jr nz, .loadLogoLoop + +; Load trademark symbol + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + +; Set up tilemap + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + + ; Turn on LCD + ld a, $91 + ldh [$40], a + + ld a, $f1 ; Packet magic, increases by 2 for every packet + ldh [$80], a + ld hl, $104 ; Header start + + xor a + ld c, a ; JOYP + +.sendCommand + xor a + ldh [c], a + ld a, $30 + ldh [c], a + + ldh a, [$80] + call SendByte + push hl + ld b, $e + ld d, 0 + +.checksumLoop + call ReadHeaderByte + add d + ld d, a + dec b + jr nz, .checksumLoop + + ; Send checksum + call SendByte + pop hl + + ld b, $e +.sendLoop + call ReadHeaderByte + call SendByte + dec b + jr nz, .sendLoop + + ; Done bit + ld a, $20 + ldh [c], a + ld a, $30 + ldh [c], a + + ; Update command + ldh a, [$80] + add 2 + ldh [$80], a + + ld a, $58 + cp l + jr nz, .sendCommand + + ; Write to sound registers for DMG compatibility + ld c, $13 + ld a, $c1 + ldh [c], a + inc c + ld a, 7 + ldh [c], a + + ; Init BG palette + ld a, $fc + ldh [$47], a + +; Set registers to match the original SGB boot + ld a, 1 + ld hl, $c060 + +; Boot the game + jp BootGame + +ReadHeaderByte: + ld a, $4F + cp l + jr c, .zero + ld a, [hli] + ret +.zero: + inc hl + xor a + ret + +SendByte: + ld e, a + ld d, 8 +.loop + ld a, $10 + rr e + jr c, .zeroBit + add a ; 10 -> 20 +.zeroBit + ldh [c], a + ld a, $30 + ldh [c], a + dec d + ret z + jr .loop + +DoubleBitsAndWriteRow: +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +TrademarkSymbol: +db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [$50], a \ No newline at end of file diff --git a/Cocoa/Document.m b/Cocoa/Document.m index cf2a7f91..05b3b459 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -16,13 +16,15 @@ enum model { MODEL_DMG, MODEL_CGB, MODEL_AGB, + MODEL_SGB, }; static const GB_model_t cocoa_to_internal_model[] = { [MODEL_DMG] = GB_MODEL_DMG_B, [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB + [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_SGB] = GB_MODEL_SGB, }; @interface Document () @@ -157,7 +159,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"]; } -/* Todo: Unify the 3 init functions */ +/* Todo: Unify the 4 init functions */ - (void) initDMG { current_model = MODEL_DMG; @@ -166,6 +168,14 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [self initCommon]; } +- (void) initSGB +{ + current_model = MODEL_SGB; + GB_init(&gb, cocoa_to_internal_model[current_model]); + GB_load_boot_rom(&gb, [[self bootROMPathForName:@"sgb_boot"] UTF8String]); + [self initCommon]; +} + - (void) initCGB { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]) { @@ -274,7 +284,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, current_model = (enum model)[sender tag]; } - static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot"}; + static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"}; GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); if ([sender tag] == MODEL_NONE) { @@ -287,7 +297,8 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if ([sender tag] != 0) { /* User explictly selected a model, save the preference */ [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_DMG forKey:@"EmulateDMG"]; - [[NSUserDefaults standardUserDefaults] setBool:current_model== MODEL_AGB forKey:@"EmulateAGB"]; + [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_SGB forKey:@"EmulateSGB"]; + [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_AGB forKey:@"EmulateAGB"]; } /* Reload the ROM, SAV and SYM files */ @@ -382,6 +393,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { [self initDMG]; } + else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateSGB"]) { + [self initSGB]; + } else { [self initCGB]; } diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 3658f83d..844aa0c9 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -1,7 +1,8 @@ - - + + - + + @@ -311,7 +312,13 @@ - + + + + + + + diff --git a/Core/gb.c b/Core/gb.c index edc0a560..00c4e7b5 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -506,7 +506,12 @@ bool GB_is_inited(GB_gameboy_t *gb) bool GB_is_cgb(GB_gameboy_t *gb) { - return ((gb->model) & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; + return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; +} + +bool GB_is_sgb(GB_gameboy_t *gb) +{ + return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2; } void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip) @@ -541,6 +546,8 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = (random() & 0xFF); if (i & 0x100) { @@ -551,15 +558,13 @@ static void reset_ram(GB_gameboy_t *gb) } } break; -#if 0 - /* Not emulated yet, for documentation only */ + case GB_MODEL_SGB2: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = 0x55; gb->ram[i] ^= random() & random() & random(); } break; -#endif case GB_MODEL_CGB_C: for (unsigned i = 0; i < gb->ram_size; i++) { @@ -729,5 +734,11 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) uint32_t GB_get_clock_rate(GB_gameboy_t *gb) { + if (gb->model == GB_MODEL_SGB_NTSC) { + return SGB_NTSC_FREQUENCY * gb->clock_multiplier; + } + if (gb->model == GB_MODEL_SGB_PAL) { + return SGB_PAL_FREQUENCY * gb->clock_multiplier; + } return CPU_FREQUENCY * gb->clock_multiplier; } diff --git a/Core/gb.h b/Core/gb.h index d20ef2b7..97c7dd4b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -19,6 +19,7 @@ #include "rewind.h" #include "z80_cpu.h" #include "symbol_hash.h" +#include "sgb.h" #define GB_STRUCT_VERSION 13 @@ -27,6 +28,7 @@ #define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_CGB_FAMILY 0x200 +#define GB_MODEL_PAL_BIT 0x1000 #endif #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ @@ -48,15 +50,18 @@ typedef union { uint8_t data[5]; } GB_rtc_time_t; + typedef enum { // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, GB_MODEL_DMG_B = 0x002, // GB_MODEL_DMG_C = 0x003, - // GB_MODEL_SGB = 0x004, + GB_MODEL_SGB = 0x004, + GB_MODEL_SGB_NTSC = GB_MODEL_SGB, + GB_MODEL_SGB_PAL = 0x1004, // GB_MODEL_MGB = 0x100, - // GB_MODEL_SGB2 = 0x101, + GB_MODEL_SGB2 = 0x101, // GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, // GB_MODEL_CGB_B = 0x202, @@ -203,6 +208,8 @@ typedef enum { #ifdef GB_INTERNAL #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 +#define SGB_NTSC_FREQUENCY (21477272 / 5) +#define SGB_PAL_FREQUENCY (21281370 / 5) #define DIV_CYCLES (0x100) #define INTERNAL_DIV_CYCLES (0x40000) @@ -459,6 +466,14 @@ struct GB_gameboy_internal_s { uint8_t mode_for_interrupt; bool lyc_interrupt_line; ); + + /* Super Game Boy state, only dumped/loaded for relevant models */ + GB_SECTION(sgb, + uint8_t sgb_command[16]; + uint8_t sgb_command_write_index; + bool sgb_ready_for_pulse; + bool sgb_ready_for_write; + ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* This data is reserved on reset and must come last in the struct */ @@ -584,6 +599,7 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) void GB_init(GB_gameboy_t *gb, GB_model_t model); bool GB_is_inited(GB_gameboy_t *gb); bool GB_is_cgb(GB_gameboy_t *gb); +bool GB_is_sgb(GB_gameboy_t *gb); GB_model_t GB_get_model(GB_gameboy_t *gb); void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); diff --git a/Core/joypad.c b/Core/joypad.c index 8b7a8da3..edee8c32 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -47,12 +47,14 @@ void GB_update_joyp(GB_gameboy_t *gb) default: break; } + if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ gb->io_registers[GB_IO_IF] |= 0x10; gb->stopped = false; } - gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support + + gb->io_registers[GB_IO_JOYP] |= 0xC0; } void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) diff --git a/Core/memory.c b/Core/memory.c index 4053530e..cf5a0c03 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -271,6 +271,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->extra_oam[addr - 0xfea0]; case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB2: ; } } @@ -575,6 +578,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->extra_oam[addr - 0xfea0] = value; break; case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB2: case GB_MODEL_CGB_E: case GB_MODEL_AGB: break; @@ -727,6 +733,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_JOYP] &= 0x0F; gb->io_registers[GB_IO_JOYP] |= value & 0xF0; GB_update_joyp(gb); + GB_sgb_write(gb, value); return; case GB_IO_BIOS: diff --git a/Core/sgb.c b/Core/sgb.c new file mode 100644 index 00000000..9dce2d88 --- /dev/null +++ b/Core/sgb.c @@ -0,0 +1,51 @@ +#include "sgb.h" + +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) +{ + if (!GB_is_sgb(gb)) return; + switch ((value >> 4) & 3 ) { + case 3: + gb->sgb_ready_for_pulse = true; + break; + + case 2: // Zero + if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; + if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { + GB_log(gb, "Got SGB command: "); + for (unsigned i = 0; i < 16; i++) { + GB_log(gb, "%02x ", gb->sgb_command[i]); + } + GB_log(gb, "\n"); + gb->sgb_ready_for_pulse = false; + gb->sgb_ready_for_write = false; + } + else { + gb->sgb_command_write_index++; + gb->sgb_ready_for_pulse = false; + } + break; + case 1: // One + if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; + if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { + GB_log(gb, "Corrupt SGB command.\n"); + gb->sgb_ready_for_pulse = false; + gb->sgb_ready_for_write = false; + } + else { + gb->sgb_command[gb->sgb_command_write_index / 8] |= 1 << (gb->sgb_command_write_index & 7); + gb->sgb_command_write_index++; + gb->sgb_ready_for_pulse = false; + } + break; + + case 0: + gb->sgb_ready_for_pulse = false; + gb->sgb_ready_for_write = true; + gb->sgb_command_write_index = 0; + memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + break; + + default: + break; + } +} diff --git a/Core/sgb.h b/Core/sgb.h new file mode 100644 index 00000000..e3b52bd0 --- /dev/null +++ b/Core/sgb.h @@ -0,0 +1,9 @@ +#ifndef sgb_h +#define sgb_h +#include "gb.h" + +#ifdef GB_INTERNAL +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +#endif + +#endif diff --git a/Makefile b/Makefile index 7038ee98..9eeefacf 100755 --- a/Makefile +++ b/Makefile @@ -106,9 +106,9 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders -bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin -tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin +tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets @@ -180,6 +180,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/agb_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/sgb_boot.bin \ $(patsubst %.xib,%.nib,$(addprefix $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/,$(shell cd Cocoa;ls *.xib))) \ $(BIN)/SameBoy.qlgenerator \ Shaders @@ -307,7 +308,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm -@$(MKDIR) -p $(dir $@) cd BootROMs && rgbasm -o ../$@.tmp ../$< rgblink -o $@.tmp2 $@.tmp - head -c $(if $(findstring dmg,$@), 256, 2304) $@.tmp2 > $@ + head -c $(if $(findstring dmg,$@)$(findstring sgb,$@), 256, 2304) $@.tmp2 > $@ @rm $@.tmp $@.tmp2 # Libretro Core (uses its own build system) From 5c581651cecc1de51d1df44114d71f72b23a16ed Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Nov 2018 22:50:00 +0200 Subject: [PATCH 0756/1216] Handle the SGB header commands, disable SGB functions if needed. --- Core/gb.h | 1 + Core/sgb.c | 40 +++++++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 97c7dd4b..a422d5ed 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -473,6 +473,7 @@ struct GB_gameboy_internal_s { uint8_t sgb_command_write_index; bool sgb_ready_for_pulse; bool sgb_ready_for_write; + bool sgb_disable_commands; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/sgb.c b/Core/sgb.c index 9dce2d88..71fccfee 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,8 +1,42 @@ #include "sgb.h" +static void command_ready(GB_gameboy_t *gb) +{ + /* SGB header commands are used to send the contents of the header to the SNES CPU. + A header command looks like this: + Command ID: 0b1111xxx1, where xxx is the packet index. (e.g. F1 for [0x104, 0x112), F3 for [0x112, 0x120)) + Checksum: Simple one byte sum for the following content bytes + 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ + + if (gb->sgb_command[0] >= 0xF0) { + uint8_t checksum = 0; + for (unsigned i = 2; i < 0x10; i++) { + checksum += gb->sgb_command[i]; + } + if (checksum != gb->sgb_command[1]) { + GB_log(gb, "Failed checksum for SGB header command, disabling SGB features\n"); + gb->sgb_disable_commands = true; + return; + } + } + if (gb->sgb_command[0] == 0xf9) { + if (gb->sgb_command[0xc] != 3) { // SGB Flag + GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); + gb->sgb_disable_commands = true; + } + } + else if (gb->sgb_command[0] == 0xfb) { + if (gb->sgb_command[0x3] != 0x33) { // Old licensee code + GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); + gb->sgb_disable_commands = true; + } + } +} + void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { if (!GB_is_sgb(gb)) return; + if (gb->sgb_disable_commands) return; switch ((value >> 4) & 3 ) { case 3: gb->sgb_ready_for_pulse = true; @@ -11,11 +45,7 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) case 2: // Zero if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { - GB_log(gb, "Got SGB command: "); - for (unsigned i = 0; i < 16; i++) { - GB_log(gb, "%02x ", gb->sgb_command[i]); - } - GB_log(gb, "\n"); + command_ready(gb); gb->sgb_ready_for_pulse = false; gb->sgb_ready_for_write = false; } From 7735d638c69a1b950fc45ecc29324a1f776319d6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Nov 2018 00:37:06 +0200 Subject: [PATCH 0757/1216] Multiplayer SGB APIs/SGB detection --- Core/gb.c | 2 ++ Core/gb.h | 4 +++- Core/joypad.c | 24 ++++++++++++++++++------ Core/joypad.h | 1 + Core/memory.c | 2 +- Core/sgb.c | 18 +++++++++++++++++- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 00c4e7b5..401eba87 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -640,6 +640,8 @@ void GB_reset(GB_gameboy_t *gb) gb->accessed_oam_row = -1; + gb->sgb_player_count = 1; + /* Todo: Ugly, fixme, see comment in the timer state machine */ gb->div_state = 3; diff --git a/Core/gb.h b/Core/gb.h index a422d5ed..65b377a2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -474,6 +474,8 @@ struct GB_gameboy_internal_s { bool sgb_ready_for_pulse; bool sgb_ready_for_write; bool sgb_disable_commands; + /* Multiplayer Input */ + uint8_t sgb_player_count, sgb_current_player; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ @@ -500,7 +502,7 @@ struct GB_gameboy_internal_s { uint32_t background_palettes_rgb[0x20]; uint32_t sprite_palettes_rgb[0x20]; GB_color_correction_mode_t color_correction_mode; - bool keys[GB_KEY_MAX]; + bool keys[4][GB_KEY_MAX]; /* Timing */ uint64_t last_sync; diff --git a/Core/joypad.c b/Core/joypad.c index edee8c32..b4e0f762 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -13,14 +13,19 @@ void GB_update_joyp(GB_gameboy_t *gb) gb->io_registers[GB_IO_JOYP] &= 0xF0; switch (key_selection) { case 3: - /* Nothing is wired, all up */ - gb->io_registers[GB_IO_JOYP] |= 0x0F; + if (gb->sgb_player_count > 1) { + gb->io_registers[GB_IO_JOYP] |= 0xF - gb->sgb_current_player; + } + else { + /* Nothing is wired, all up */ + gb->io_registers[GB_IO_JOYP] |= 0x0F; + } break; case 2: /* Direction keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[gb->sgb_current_player][i]) << i; } /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ if (!(gb->io_registers[GB_IO_JOYP] & 1)) { @@ -34,13 +39,13 @@ void GB_update_joyp(GB_gameboy_t *gb) case 1: /* Other keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[gb->sgb_current_player][i + 4]) << i; } break; case 0: for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[i] || gb->keys[i + 4])) << i; + gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[gb->sgb_current_player][i] || gb->keys[gb->sgb_current_player][i + 4])) << i; } break; @@ -60,5 +65,12 @@ void GB_update_joyp(GB_gameboy_t *gb) void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) { assert(index >= 0 && index < GB_KEY_MAX); - gb->keys[index] = pressed; + gb->keys[0][index] = pressed; +} + +void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed) +{ + assert(index >= 0 && index < GB_KEY_MAX); + assert(player < 4); + gb->keys[player][index] = pressed; } diff --git a/Core/joypad.h b/Core/joypad.h index 83d3734f..768d685a 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -16,6 +16,7 @@ typedef enum { } GB_key_t; void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); +void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed); #ifdef GB_INTERNAL void GB_update_joyp(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index cf5a0c03..3a646aec 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -730,10 +730,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_JOYP: + GB_sgb_write(gb, value); gb->io_registers[GB_IO_JOYP] &= 0x0F; gb->io_registers[GB_IO_JOYP] |= value & 0xF0; GB_update_joyp(gb); - GB_sgb_write(gb, value); return; case GB_IO_BIOS: diff --git a/Core/sgb.c b/Core/sgb.c index 71fccfee..a755016a 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,5 +1,9 @@ #include "sgb.h" +enum { + MLT_REQ = 0x11, +}; + static void command_ready(GB_gameboy_t *gb) { /* SGB header commands are used to send the contents of the header to the SNES CPU. @@ -31,13 +35,21 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb_disable_commands = true; } } + + switch (gb->sgb_command[0] >> 3) { + case MLT_REQ: + gb->sgb_player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb_command[1] & 3]; + gb->sgb_current_player = gb->sgb_player_count - 1; + break; + } } void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { if (!GB_is_sgb(gb)) return; if (gb->sgb_disable_commands) return; - switch ((value >> 4) & 3 ) { + + switch ((value >> 4) & 3) { case 3: gb->sgb_ready_for_pulse = true; break; @@ -73,6 +85,10 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) gb->sgb_ready_for_write = true; gb->sgb_command_write_index = 0; memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + if (gb->sgb_player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { + gb->sgb_current_player++; + gb->sgb_current_player &= gb->sgb_player_count - 1; + } break; default: From 6ba5cfbeefd3b6ef19c5c37fad48de63564ae741 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Nov 2018 23:45:26 +0200 Subject: [PATCH 0758/1216] Support for multi-packet SGB commands --- Core/gb.h | 5 +++-- Core/sgb.c | 64 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 65b377a2..ec8c7e8c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -469,10 +469,11 @@ struct GB_gameboy_internal_s { /* Super Game Boy state, only dumped/loaded for relevant models */ GB_SECTION(sgb, - uint8_t sgb_command[16]; - uint8_t sgb_command_write_index; + uint8_t sgb_command[16 * 7]; + uint16_t sgb_command_write_index; bool sgb_ready_for_pulse; bool sgb_ready_for_write; + bool sgb_ready_for_stop; bool sgb_disable_commands; /* Multiplayer Input */ uint8_t sgb_player_count, sgb_current_player; diff --git a/Core/sgb.c b/Core/sgb.c index a755016a..755fea2a 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -4,6 +4,8 @@ enum { MLT_REQ = 0x11, }; +#define SGB_PACKET_SIZE 16 + static void command_ready(GB_gameboy_t *gb) { /* SGB header commands are used to send the contents of the header to the SNES CPU. @@ -12,7 +14,8 @@ static void command_ready(GB_gameboy_t *gb) Checksum: Simple one byte sum for the following content bytes 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ - if (gb->sgb_command[0] >= 0xF0) { + + if ((gb->sgb_command[0] & 0xF1) == 0xF1) { uint8_t checksum = 0; for (unsigned i = 2; i < 0x10; i++) { checksum += gb->sgb_command[i]; @@ -22,25 +25,35 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb_disable_commands = true; return; } - } - if (gb->sgb_command[0] == 0xf9) { - if (gb->sgb_command[0xc] != 3) { // SGB Flag - GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); - gb->sgb_disable_commands = true; + if (gb->sgb_command[0] == 0xf9) { + if (gb->sgb_command[0xc] != 3) { // SGB Flag + GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); + gb->sgb_disable_commands = true; + } } - } - else if (gb->sgb_command[0] == 0xfb) { - if (gb->sgb_command[0x3] != 0x33) { // Old licensee code - GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); - gb->sgb_disable_commands = true; + else if (gb->sgb_command[0] == 0xfb) { + if (gb->sgb_command[0x3] != 0x33) { // Old licensee code + GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); + gb->sgb_disable_commands = true; + } } + return; } + switch (gb->sgb_command[0] >> 3) { case MLT_REQ: gb->sgb_player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb_command[1] & 3]; gb->sgb_current_player = gb->sgb_player_count - 1; break; + default: + GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb_command[0] >> 3); + for (unsigned i = 0; i < gb->sgb_command_write_index / 8; i++) { + GB_log(gb, "%02x ", gb->sgb_command[i]); + } + GB_log(gb, "\n"); + ; + } } @@ -48,6 +61,12 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { if (!GB_is_sgb(gb)) return; if (gb->sgb_disable_commands) return; + if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) return; + + uint16_t command_size = (gb->sgb_command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; + if ((gb->sgb_command[0] & 0xF1) == 0xF1) { + command_size = SGB_PACKET_SIZE * 8; + } switch ((value >> 4) & 3) { case 3: @@ -56,35 +75,48 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) case 2: // Zero if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; - if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { - command_ready(gb); + if (gb->sgb_ready_for_stop) { + if (gb->sgb_command_write_index == command_size) { + command_ready(gb); + gb->sgb_command_write_index = 0; + memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + } gb->sgb_ready_for_pulse = false; gb->sgb_ready_for_write = false; + gb->sgb_ready_for_stop = false; } else { gb->sgb_command_write_index++; gb->sgb_ready_for_pulse = false; + if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb_ready_for_stop = true; + } } break; case 1: // One if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; - if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { + if (gb->sgb_ready_for_stop) { GB_log(gb, "Corrupt SGB command.\n"); gb->sgb_ready_for_pulse = false; gb->sgb_ready_for_write = false; + gb->sgb_command_write_index = 0; + memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); } else { gb->sgb_command[gb->sgb_command_write_index / 8] |= 1 << (gb->sgb_command_write_index & 7); gb->sgb_command_write_index++; gb->sgb_ready_for_pulse = false; + if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb_ready_for_stop = true; + } } break; case 0: + if (!gb->sgb_ready_for_pulse) return; gb->sgb_ready_for_pulse = false; gb->sgb_ready_for_write = true; - gb->sgb_command_write_index = 0; - memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + gb->sgb_ready_for_pulse = false; if (gb->sgb_player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { gb->sgb_current_player++; gb->sgb_current_player &= gb->sgb_player_count - 1; From 634a54c04665207ac9c40fcc382d2a3711ffd029 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 15 Nov 2018 00:21:21 +0200 Subject: [PATCH 0759/1216] SGB resolution support (Cocoa only so far) --- Cocoa/Document.m | 18 ++++++++----- Cocoa/GBGLShader.h | 2 +- Cocoa/GBGLShader.m | 8 +++--- Cocoa/GBOpenGLView.m | 17 ++++--------- Cocoa/GBView.h | 1 + Cocoa/GBView.m | 31 ++++++++++++++++++----- Cocoa/GBViewMetal.m | 52 +++++++++++++++++++++++--------------- Core/display.c | 35 ++++++++++++++++++++----- Core/gb.c | 25 ++++++++++++++++++ Core/gb.h | 8 +++++- Core/sgb.c | 26 +++++++++++++++++++ Core/sgb.h | 1 + Shaders/MasterShader.fsh | 3 ++- Shaders/MasterShader.metal | 3 ++- 14 files changed, 170 insertions(+), 60 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 05b3b459..24e99a32 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -225,8 +225,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) run { running = true; - GB_set_pixels_output(&gb, self.view.pixels); self.view.gb = &gb; + [self.view screenSizeChanged]; + GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { GB_apu_copy_buffer(&gb, buffer, nFrames); @@ -565,21 +566,24 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (fullScreen) { return newFrame; } + size_t width = GB_get_screen_width(&gb), + height = GB_get_screen_height(&gb); + NSRect rect = window.contentView.frame; int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; - int step = 160 / [[window screen] backingScaleFactor]; + int step = width / [[window screen] backingScaleFactor]; rect.size.width = floor(rect.size.width / step) * step + step; - rect.size.height = rect.size.width / 10 * 9 + titlebarSize; + rect.size.height = rect.size.width * height / width + titlebarSize; if (rect.size.width > newFrame.size.width) { - rect.size.width = 160; - rect.size.height = 144 + titlebarSize; + rect.size.width = width; + rect.size.height = height + titlebarSize; } else if (rect.size.height > newFrame.size.height) { - rect.size.width = 160; - rect.size.height = 144 + titlebarSize; + rect.size.width = width; + rect.size.height = height + titlebarSize; } rect.origin = window.frame.origin; diff --git a/Cocoa/GBGLShader.h b/Cocoa/GBGLShader.h index 76291fb2..1a126177 100644 --- a/Cocoa/GBGLShader.h +++ b/Cocoa/GBGLShader.h @@ -2,5 +2,5 @@ @interface GBGLShader : NSObject - (instancetype)initWithName:(NSString *) shaderName; -- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale; @end diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index 444ee894..fe636f87 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -79,19 +79,19 @@ void main(void) {\n\ return self; } -- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale { glUseProgram(program); - glUniform2f(resolution_uniform, size.width * scale, size.height * scale); + glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); glUniform1i(texture_uniform, 0); glUniform1i(mix_previous_uniform, previous != NULL); if (previous) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, previous_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); glUniform1i(previous_texture_uniform, 1); } glBindFragDataLocation(program, 0, "frag_color"); diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 505f5db5..67a9f8d0 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -13,18 +13,11 @@ double scale = self.window.backingScaleFactor; glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); - if (gbview.shouldBlendFrameWithPrevious) { - [self.shader renderBitmap:gbview.currentBuffer - previous:gbview.previousBuffer - inSize:self.bounds.size - scale:scale]; - } - else { - [self.shader renderBitmap:gbview.currentBuffer - previous:NULL - inSize:self.bounds.size - scale:scale]; - } + [self.shader renderBitmap:gbview.currentBuffer + previous:gbview.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL + sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) + inSize:self.bounds.size + scale:scale]; glFlush(); } diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index c054e09f..f4c5e449 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -13,4 +13,5 @@ - (void) createInternalView; - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; +- (void)screenSizeChanged; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index a2dbe72f..debac87f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -44,9 +44,8 @@ - (void) _init { - image_buffers[0] = malloc(160 * 144 * 4); - image_buffers[1] = malloc(160 * 144 * 4); - image_buffers[2] = malloc(160 * 144 * 4); + [self screenSizeChanged]; + _shouldBlendFrameWithPrevious = 1; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} @@ -60,6 +59,24 @@ self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; } +- (void)screenSizeChanged +{ + if (!_gb) return; + if (image_buffers[0]) free(image_buffers[0]); + if (image_buffers[1]) free(image_buffers[1]); + if (image_buffers[2]) free(image_buffers[2]); + + size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb); + + image_buffers[0] = malloc(buffer_size); + image_buffers[1] = malloc(buffer_size); + image_buffers[2] = malloc(buffer_size); + + dispatch_async(dispatch_get_main_queue(), ^{ + [self setFrame:self.superview.frame]; + }); +} + - (void) ratioKeepingChanged { [self setFrame:self.superview.frame]; @@ -112,14 +129,16 @@ frame = self.superview.frame; if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { double ratio = frame.size.width / frame.size.height; - if (ratio >= 160.0/144.0) { - double new_width = round(frame.size.height / 144.0 * 160.0); + double width = GB_get_screen_width(_gb); + double height = GB_get_screen_height(_gb); + if (ratio >= width / height) { + double new_width = round(frame.size.height / height * width); frame.origin.x = floor((frame.size.width - new_width) / 2); frame.size.width = new_width; frame.origin.y = 0; } else { - double new_height = round(frame.size.width / 160.0 * 144.0); + double new_height = round(frame.size.width / width * height); frame.origin.y = floor((frame.size.height - new_height) / 2); frame.size.height = new_height; frame.origin.x = 0; diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 4867d513..124db778 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -1,14 +1,5 @@ #import "GBViewMetal.h" -#define WIDTH 160 -#define HEIGHT 144 -#define PITCH (160 * 4) - -static const MTLRegion region = { - {0, 0, 0}, // MTLOrigin - {WIDTH, HEIGHT, 1} // MTLSize -}; - static const vector_float2 rect[] = { {-1, -1}, @@ -37,6 +28,29 @@ static const vector_float2 rect[] = return false; } +- (void) allocateTextures +{ + if (!device) return; + if (!self.gb) return; + + MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; + + texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; + + texture_descriptor.width = GB_get_screen_width(self.gb); + texture_descriptor.height = GB_get_screen_height(self.gb); + + texture = [device newTextureWithDescriptor:texture_descriptor]; + previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + +} + +- (void)screenSizeChanged +{ + [super screenSizeChanged]; + [self allocateTextures]; +} + - (void)createInternalView { MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; @@ -44,15 +58,7 @@ static const vector_float2 rect[] = self.internalView = view; view.paused = YES; - MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; - - texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; - - texture_descriptor.width = WIDTH; - texture_descriptor.height = HEIGHT; - - texture = [device newTextureWithDescriptor:texture_descriptor]; - previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + [self allocateTextures]; vertices = [device newBufferWithBytes:rect length:sizeof(rect) @@ -134,15 +140,21 @@ static const vector_float2 rect[] = - (void)drawInMTKView:(nonnull MTKView *)view { if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; + + MTLRegion region = { + {0, 0, 0}, // MTLOrigin + {texture.width, texture.height, 1} // MTLSize + }; + [texture replaceRegion:region mipmapLevel:0 withBytes:[self currentBuffer] - bytesPerRow:PITCH]; + bytesPerRow:texture.width * 4]; if ([self shouldBlendFrameWithPrevious]) { [previous_texture replaceRegion:region mipmapLevel:0 withBytes:[self previousBuffer] - bytesPerRow:PITCH]; + bytesPerRow:texture.width * 4]; } MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; diff --git a/Core/display.c b/Core/display.c index 2759146e..9c6e9cb9 100644 --- a/Core/display.c +++ b/Core/display.c @@ -132,14 +132,25 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ - uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? - gb->rgb_encode_callback(gb, 0, 0, 0) : - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); - for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb ->screen[i] = color; + if (gb->sgb_screen_buffer) { + uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0 : 0xFF; + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->sgb_screen_buffer[i] = color; + } + } + else { + uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + gb->rgb_encode_callback(gb, 0, 0, 0) : + gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->screen[i] = color; + } } } + if (GB_is_sgb(gb)) { + GB_sgb_render(gb); + } gb->vblank_callback(gb); GB_timing_sync(gb); } @@ -375,7 +386,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + if (gb->sgb_screen_buffer) { + gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; + } + else { + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + } } if (draw_oam) { @@ -384,7 +400,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Todo: Verify access timings */ pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); } - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + if (gb->sgb_screen_buffer) { + gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; + } + else { + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + } } gb->position_in_line++; diff --git a/Core/gb.c b/Core/gb.c index 401eba87..29d6df05 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -139,6 +139,9 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } + if (gb->sgb_screen_buffer) { + free(gb->sgb_screen_buffer); + } #ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); #endif @@ -642,6 +645,18 @@ void GB_reset(GB_gameboy_t *gb) gb->sgb_player_count = 1; + if (GB_is_sgb(gb)) { + if (!gb->sgb_screen_buffer) { + gb->sgb_screen_buffer = malloc(160 * 144); + } + } + else { + if (gb->sgb_screen_buffer) { + free(gb->sgb_screen_buffer); + gb->sgb_screen_buffer = NULL; + } + } + /* Todo: Ugly, fixme, see comment in the timer state machine */ gb->div_state = 3; @@ -744,3 +759,13 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb) } return CPU_FREQUENCY * gb->clock_multiplier; } + +size_t GB_get_screen_width(GB_gameboy_t *gb) +{ + return (gb && GB_is_sgb(gb))? 256 : 160; +} + +size_t GB_get_screen_height(GB_gameboy_t *gb) +{ + return (gb && GB_is_sgb(gb))? 224 : 144; +} diff --git a/Core/gb.h b/Core/gb.h index ec8c7e8c..83f880cd 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -475,6 +475,9 @@ struct GB_gameboy_internal_s { bool sgb_ready_for_write; bool sgb_ready_for_stop; bool sgb_disable_commands; + + /* Screen buffer */ + uint8_t *sgb_screen_buffer; /* Multiplayer Input */ uint8_t sgb_player_count, sgb_current_player; ); @@ -677,5 +680,8 @@ void GB_disconnect_serial(GB_gameboy_t *gb); uint32_t GB_get_clock_rate(GB_gameboy_t *gb); #endif void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); - + +size_t GB_get_screen_width(GB_gameboy_t *gb); +size_t GB_get_screen_height(GB_gameboy_t *gb); + #endif /* GB_h */ diff --git a/Core/sgb.c b/Core/sgb.c index 755fea2a..e3f71adc 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -127,3 +127,29 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) break; } } + +void GB_sgb_render(GB_gameboy_t *gb) +{ + if (!gb->screen || !gb->rgb_encode_callback) return; + + uint32_t border = gb->rgb_encode_callback(gb, 0x9c, 0x9c, 0xa5); + uint32_t colors[] = { + gb->rgb_encode_callback(gb, 0xf7, 0xe7, 0xc6), + gb->rgb_encode_callback(gb, 0xd6, 0x8e, 0x49), + gb->rgb_encode_callback(gb, 0xa6, 0x37, 0x25), + gb->rgb_encode_callback(gb, 0x33, 0x1e, 0x50) + }; + + for (unsigned i = 0; i < 256 * 224; i++) { + gb->screen[i] = border; + } + + uint32_t *output = &gb->screen[48 + 39 * 256]; + uint8_t *input = gb->sgb_screen_buffer; + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = colors[*(input++) & 3]; + } + output += 256 - 160; + } +} diff --git a/Core/sgb.h b/Core/sgb.h index e3b52bd0..c4541fac 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -4,6 +4,7 @@ #ifdef GB_INTERNAL void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +void GB_sgb_render(GB_gameboy_t *gb); #endif #endif diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 55cdf49f..a489cf76 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -21,7 +21,8 @@ void main() vec2 position = gl_FragCoord.xy - origin; position /= output_resolution; position.y = 1 - position.y; - + vec2 input_resolution = textureSize(image, 0); + if (mix_previous) { frag_color = mix(scale(image, position, input_resolution, output_resolution), scale(previous_image, position, input_resolution, output_resolution), 0.5); diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 589c4aca..4cae3ae0 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -3,7 +3,6 @@ #include using namespace metal; -constant float2 input_resolution = float2(160, 144); /* For GLSL compatibility */ typedef float2 vec2; @@ -49,6 +48,8 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]], constant bool *mix_previous [[ buffer(0) ]], constant float2 *output_resolution [[ buffer(1) ]]) { + float2 input_resolution = float2(image.get_width(), image.get_height()); + in.texcoords.y = 1 - in.texcoords.y; if (*mix_previous) { return mix(scale(image, in.texcoords, input_resolution, *output_resolution), From 2f2b792edf2c103cd73d4a324acaedf61fd04db0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 01:53:01 +0200 Subject: [PATCH 0760/1216] SGB save states --- Core/display.c | 12 +++--- Core/gb.c | 17 ++++---- Core/gb.h | 19 ++------- Core/joypad.c | 11 ++--- Core/save_state.c | 32 +++++++++++--- Core/sgb.c | 106 +++++++++++++++++++++++----------------------- Core/sgb.h | 19 ++++++++- 7 files changed, 122 insertions(+), 94 deletions(-) diff --git a/Core/display.c b/Core/display.c index 9c6e9cb9..f5ba685d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -132,10 +132,10 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ - if (gb->sgb_screen_buffer) { + if (gb->sgb) { uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0 : 0xFF; for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb ->sgb_screen_buffer[i] = color; + gb->sgb->screen_buffer[i] = color; } } else { @@ -386,8 +386,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } - if (gb->sgb_screen_buffer) { - gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; + if (gb->sgb) { + gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; @@ -400,8 +400,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Todo: Verify access timings */ pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); } - if (gb->sgb_screen_buffer) { - gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; + if (gb->sgb) { + gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; diff --git a/Core/gb.c b/Core/gb.c index 29d6df05..b38715c7 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -139,8 +139,8 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } - if (gb->sgb_screen_buffer) { - free(gb->sgb_screen_buffer); + if (gb->sgb) { + free(gb->sgb); } #ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); @@ -643,17 +643,18 @@ void GB_reset(GB_gameboy_t *gb) gb->accessed_oam_row = -1; - gb->sgb_player_count = 1; if (GB_is_sgb(gb)) { - if (!gb->sgb_screen_buffer) { - gb->sgb_screen_buffer = malloc(160 * 144); + if (!gb->sgb) { + gb->sgb = malloc(sizeof(*gb->sgb)); } + gb->sgb->player_count = 1; + } else { - if (gb->sgb_screen_buffer) { - free(gb->sgb_screen_buffer); - gb->sgb_screen_buffer = NULL; + if (gb->sgb) { + free(gb->sgb); + gb->sgb = NULL; } } diff --git a/Core/gb.h b/Core/gb.h index 83f880cd..e9f13870 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -52,7 +52,6 @@ typedef union { typedef enum { - // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, GB_MODEL_DMG_B = 0x002, @@ -466,21 +465,6 @@ struct GB_gameboy_internal_s { uint8_t mode_for_interrupt; bool lyc_interrupt_line; ); - - /* Super Game Boy state, only dumped/loaded for relevant models */ - GB_SECTION(sgb, - uint8_t sgb_command[16 * 7]; - uint16_t sgb_command_write_index; - bool sgb_ready_for_pulse; - bool sgb_ready_for_write; - bool sgb_ready_for_stop; - bool sgb_disable_commands; - - /* Screen buffer */ - uint8_t *sgb_screen_buffer; - /* Multiplayer Input */ - uint8_t sgb_player_count, sgb_current_player; - ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* This data is reserved on reset and must come last in the struct */ @@ -577,6 +561,9 @@ struct GB_gameboy_internal_s { unsigned pos; } *rewind_sequences; // lasts about 4 seconds size_t rewind_pos; + + /* SGB - saved and allocated optionally */ + GB_sgb_t *sgb; /* Misc */ bool turbo; diff --git a/Core/joypad.c b/Core/joypad.c index b4e0f762..053ef2f5 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -11,10 +11,11 @@ void GB_update_joyp(GB_gameboy_t *gb) 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; + uint8_t current_player = gb->sgb? gb->sgb->current_player : 0; switch (key_selection) { case 3: - if (gb->sgb_player_count > 1) { - gb->io_registers[GB_IO_JOYP] |= 0xF - gb->sgb_current_player; + if (gb->sgb && gb->sgb->player_count > 1) { + gb->io_registers[GB_IO_JOYP] |= 0xF - current_player; } else { /* Nothing is wired, all up */ @@ -25,7 +26,7 @@ void GB_update_joyp(GB_gameboy_t *gb) case 2: /* Direction keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[gb->sgb_current_player][i]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i; } /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ if (!(gb->io_registers[GB_IO_JOYP] & 1)) { @@ -39,13 +40,13 @@ void GB_update_joyp(GB_gameboy_t *gb) case 1: /* Other keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[gb->sgb_current_player][i + 4]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i + 4]) << i; } break; case 0: for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[gb->sgb_current_player][i] || gb->keys[gb->sgb_current_player][i + 4])) << i; + gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[current_player][i] || gb->keys[current_player][i + 4])) << i; } break; diff --git a/Core/save_state.c b/Core/save_state.c index 22788534..26c9b522 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -36,6 +36,10 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) if (!DUMP_SECTION(gb, f, rtc )) goto error; if (!DUMP_SECTION(gb, f, video )) goto error; + if (GB_is_sgb(gb)) { + if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; + } + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { goto error; @@ -69,6 +73,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + GB_SECTION_SIZE(apu ) + sizeof(uint32_t) + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t) + GB_SECTION_SIZE(video ) + sizeof(uint32_t) + + (GB_is_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + gb->mbc_ram_size + gb->ram_size + gb->vram_size; @@ -100,6 +105,10 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) DUMP_SECTION(gb, buffer, rtc ); DUMP_SECTION(gb, buffer, video ); + if (GB_is_sgb(gb)) { + buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb)); + } + buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer); buffer_write(gb->ram, gb->ram_size, &buffer); @@ -133,27 +142,32 @@ static bool read_section(FILE *f, void *dest, uint32_t size) static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) { if (gb->magic != save->magic) { - GB_log(gb, "File is not a save state, or is from an incompatible operating system.\n"); + GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); return false; } if (gb->version != save->version) { - GB_log(gb, "Save state is for a different version of SameBoy.\n"); + GB_log(gb, "The save state is for a different version of SameBoy.\n"); return false; } if (gb->mbc_ram_size < save->mbc_ram_size) { - GB_log(gb, "Save state has non-matching MBC RAM size.\n"); + GB_log(gb, "The save state has non-matching MBC RAM size.\n"); return false; } if (gb->ram_size != save->ram_size) { - GB_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n"); + GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); return false; } if (gb->vram_size != save->vram_size) { - GB_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n"); + GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n"); + return false; + } + + if (GB_is_sgb(gb) != GB_is_sgb(save)) { + GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_sgb(save)? "" : "not "); return false; } @@ -190,6 +204,10 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) goto error; } + if (GB_is_sgb(gb)) { + if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; + } + memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) { fclose(f); @@ -289,6 +307,10 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return -1; } + if (GB_is_sgb(gb)) { + if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1; + } + memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) { return -1; diff --git a/Core/sgb.c b/Core/sgb.c index e3f71adc..f23abfab 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,4 +1,4 @@ -#include "sgb.h" +#include "gb.h" enum { MLT_REQ = 0x11, @@ -15,41 +15,41 @@ static void command_ready(GB_gameboy_t *gb) 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ - if ((gb->sgb_command[0] & 0xF1) == 0xF1) { + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { uint8_t checksum = 0; for (unsigned i = 2; i < 0x10; i++) { - checksum += gb->sgb_command[i]; + checksum += gb->sgb->command[i]; } - if (checksum != gb->sgb_command[1]) { + if (checksum != gb->sgb->command[1]) { GB_log(gb, "Failed checksum for SGB header command, disabling SGB features\n"); - gb->sgb_disable_commands = true; + gb->sgb->disable_commands = true; return; } - if (gb->sgb_command[0] == 0xf9) { - if (gb->sgb_command[0xc] != 3) { // SGB Flag + if (gb->sgb->command[0] == 0xf9) { + if (gb->sgb->command[0xc] != 3) { // SGB Flag GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); - gb->sgb_disable_commands = true; + gb->sgb->disable_commands = true; } } - else if (gb->sgb_command[0] == 0xfb) { - if (gb->sgb_command[0x3] != 0x33) { // Old licensee code + else if (gb->sgb->command[0] == 0xfb) { + if (gb->sgb->command[0x3] != 0x33) { // Old licensee code GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); - gb->sgb_disable_commands = true; + gb->sgb->disable_commands = true; } } return; } - switch (gb->sgb_command[0] >> 3) { + switch (gb->sgb->command[0] >> 3) { case MLT_REQ: - gb->sgb_player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb_command[1] & 3]; - gb->sgb_current_player = gb->sgb_player_count - 1; + gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; + gb->sgb->current_player = gb->sgb->player_count - 1; break; default: - GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb_command[0] >> 3); - for (unsigned i = 0; i < gb->sgb_command_write_index / 8; i++) { - GB_log(gb, "%02x ", gb->sgb_command[i]); + GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); + for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { + GB_log(gb, "%02x ", gb->sgb->command[i]); } GB_log(gb, "\n"); ; @@ -60,66 +60,66 @@ static void command_ready(GB_gameboy_t *gb) void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { if (!GB_is_sgb(gb)) return; - if (gb->sgb_disable_commands) return; - if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) return; + if (gb->sgb->disable_commands) return; + if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return; - uint16_t command_size = (gb->sgb_command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; - if ((gb->sgb_command[0] & 0xF1) == 0xF1) { + uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { command_size = SGB_PACKET_SIZE * 8; } switch ((value >> 4) & 3) { case 3: - gb->sgb_ready_for_pulse = true; + gb->sgb->ready_for_pulse = true; break; case 2: // Zero - if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; - if (gb->sgb_ready_for_stop) { - if (gb->sgb_command_write_index == command_size) { + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { + if (gb->sgb->command_write_index == command_size) { command_ready(gb); - gb->sgb_command_write_index = 0; - memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); } - gb->sgb_ready_for_pulse = false; - gb->sgb_ready_for_write = false; - gb->sgb_ready_for_stop = false; + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->ready_for_stop = false; } else { - gb->sgb_command_write_index++; - gb->sgb_ready_for_pulse = false; - if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { - gb->sgb_ready_for_stop = true; + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; } } break; case 1: // One - if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; - if (gb->sgb_ready_for_stop) { + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { GB_log(gb, "Corrupt SGB command.\n"); - gb->sgb_ready_for_pulse = false; - gb->sgb_ready_for_write = false; - gb->sgb_command_write_index = 0; - memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); } else { - gb->sgb_command[gb->sgb_command_write_index / 8] |= 1 << (gb->sgb_command_write_index & 7); - gb->sgb_command_write_index++; - gb->sgb_ready_for_pulse = false; - if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { - gb->sgb_ready_for_stop = true; + gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; } } break; case 0: - if (!gb->sgb_ready_for_pulse) return; - gb->sgb_ready_for_pulse = false; - gb->sgb_ready_for_write = true; - gb->sgb_ready_for_pulse = false; - if (gb->sgb_player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { - gb->sgb_current_player++; - gb->sgb_current_player &= gb->sgb_player_count - 1; + if (!gb->sgb->ready_for_pulse) return; + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = true; + gb->sgb->ready_for_pulse = false; + if (gb->sgb->player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { + gb->sgb->current_player++; + gb->sgb->current_player &= gb->sgb->player_count - 1; } break; @@ -145,7 +145,7 @@ void GB_sgb_render(GB_gameboy_t *gb) } uint32_t *output = &gb->screen[48 + 39 * 256]; - uint8_t *input = gb->sgb_screen_buffer; + uint8_t *input = gb->sgb->screen_buffer; for (unsigned y = 0; y < 144; y++) { for (unsigned x = 0; x < 160; x++) { *(output++) = colors[*(input++) & 3]; diff --git a/Core/sgb.h b/Core/sgb.h index c4541fac..50747373 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -1,10 +1,27 @@ #ifndef sgb_h #define sgb_h -#include "gb.h" +#include "gb_struct_def.h" +#include +#include #ifdef GB_INTERNAL +typedef struct { + uint8_t command[16 * 7]; + uint16_t command_write_index; + bool ready_for_pulse; + bool ready_for_write; + bool ready_for_stop; + bool disable_commands; + + /* Screen buffer */ + uint8_t screen_buffer[160 * 144]; + /* Multiplayer Input */ + uint8_t player_count, current_player; +} GB_sgb_t; + void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); + #endif #endif From 382d9f8898313ef9a4abdf3b54aedf0355cbc9bc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 12:42:52 +0200 Subject: [PATCH 0761/1216] MASK_EN support --- Core/gb.c | 1 + Core/sgb.c | 33 ++++++++++++++++++++++++++++++++- Core/sgb.h | 7 ++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index b38715c7..99d6c2cb 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -648,6 +648,7 @@ void GB_reset(GB_gameboy_t *gb) if (!gb->sgb) { gb->sgb = malloc(sizeof(*gb->sgb)); } + memset(gb->sgb, 0, sizeof(*gb->sgb)); gb->sgb->player_count = 1; } diff --git a/Core/sgb.c b/Core/sgb.c index f23abfab..c60afff1 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -2,8 +2,16 @@ enum { MLT_REQ = 0x11, + MASK_EN = 0x17, }; +typedef enum { + MASK_DISABLED, + MASK_FREEZE, + MASK_COLOR_3, + MASK_COLOR_0, +} mask_mode_t; + #define SGB_PACKET_SIZE 16 static void command_ready(GB_gameboy_t *gb) @@ -46,6 +54,9 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; gb->sgb->current_player = gb->sgb->player_count - 1; break; + case MASK_EN: + gb->sgb->mask_mode = gb->sgb->command[1] & 3; + break; default: GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { @@ -144,8 +155,28 @@ void GB_sgb_render(GB_gameboy_t *gb) gb->screen[i] = border; } + switch ((mask_mode_t) gb->sgb->mask_mode) { + case MASK_DISABLED: + memcpy(gb->sgb->effective_screen_buffer, + gb->sgb->screen_buffer, + sizeof(gb->sgb->effective_screen_buffer)); + break; + case MASK_FREEZE: + break; + + case MASK_COLOR_3: + memset(gb->sgb->effective_screen_buffer, + 3, + sizeof(gb->sgb->effective_screen_buffer)); + break; + case MASK_COLOR_0: + memset(gb->sgb->effective_screen_buffer, + 0, + sizeof(gb->sgb->effective_screen_buffer)); + } + uint32_t *output = &gb->screen[48 + 39 * 256]; - uint8_t *input = gb->sgb->screen_buffer; + uint8_t *input = gb->sgb->effective_screen_buffer; for (unsigned y = 0; y < 144; y++) { for (unsigned x = 0; x < 160; x++) { *(output++) = colors[*(input++) & 3]; diff --git a/Core/sgb.h b/Core/sgb.h index 50747373..bafde350 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -14,9 +14,14 @@ typedef struct { bool disable_commands; /* Screen buffer */ - uint8_t screen_buffer[160 * 144]; + uint8_t screen_buffer[160 * 144]; // Live image from the Game Boy + uint8_t effective_screen_buffer[160 * 144]; // Image actually rendered to the screen + /* Multiplayer Input */ uint8_t player_count, current_player; + + /* Mask */ + uint8_t mask_mode; } GB_sgb_t; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From 398148f7ea7d9ef4d18726fd87a98df6475d301c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 16:04:40 +0200 Subject: [PATCH 0762/1216] Basic SGB border support --- Core/display.c | 12 ++++--- Core/sgb.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++--- Core/sgb.h | 13 +++++++ 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/Core/display.c b/Core/display.c index f5ba685d..d13a344f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -123,7 +123,12 @@ static bool window_enabled(GB_gameboy_t *gb) static void display_vblank(GB_gameboy_t *gb) { gb->vblank_just_occured = true; - + + /* TODO: Slow in trubo mode! */ + if (GB_is_sgb(gb)) { + GB_sgb_render(gb); + } + if (gb->turbo) { if (GB_timing_sync_turbo(gb)) { return; @@ -133,7 +138,7 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (gb->sgb) { - uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0 : 0xFF; + uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0x3 : 0x0; for (unsigned i = 0; i < WIDTH * LINES; i++) { gb->sgb->screen_buffer[i] = color; } @@ -148,9 +153,6 @@ static void display_vblank(GB_gameboy_t *gb) } } - if (GB_is_sgb(gb)) { - GB_sgb_render(gb); - } gb->vblank_callback(gb); GB_timing_sync(gb); } diff --git a/Core/sgb.c b/Core/sgb.c index c60afff1..ce3016c2 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -2,6 +2,8 @@ enum { MLT_REQ = 0x11, + CHR_TRN = 0x13, + PCT_TRN = 0x14, MASK_EN = 0x17, }; @@ -57,6 +59,17 @@ static void command_ready(GB_gameboy_t *gb) case MASK_EN: gb->sgb->mask_mode = gb->sgb->command[1] & 3; break; + case CHR_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->tile_transfer = true; + gb->sgb->data_transfer = false; + gb->sgb->tile_transfer_high = gb->sgb->command[1] & 1; + break; + case PCT_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->tile_transfer = false; + gb->sgb->data_transfer = true; + break; default: GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { @@ -139,11 +152,28 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) } } +static inline uint8_t scale_channel(uint8_t x) +{ + return (x << 3) | (x >> 2); +} + +static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color) +{ + uint8_t r = (color) & 0x1F; + uint8_t g = (color >> 5) & 0x1F; + uint8_t b = (color >> 10) & 0x1F; + + r = scale_channel(r); + g = scale_channel(g); + b = scale_channel(b); + + return gb->rgb_encode_callback(gb, r, g, b); +} + void GB_sgb_render(GB_gameboy_t *gb) { if (!gb->screen || !gb->rgb_encode_callback) return; - uint32_t border = gb->rgb_encode_callback(gb, 0x9c, 0x9c, 0xa5); uint32_t colors[] = { gb->rgb_encode_callback(gb, 0xf7, 0xe7, 0xc6), gb->rgb_encode_callback(gb, 0xd6, 0x8e, 0x49), @@ -151,8 +181,9 @@ void GB_sgb_render(GB_gameboy_t *gb) gb->rgb_encode_callback(gb, 0x33, 0x1e, 0x50) }; - for (unsigned i = 0; i < 256 * 224; i++) { - gb->screen[i] = border; + uint32_t border_colors[16 * 4]; + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15(gb, gb->sgb->border_palette[i]); } switch ((mask_mode_t) gb->sgb->mask_mode) { @@ -175,7 +206,40 @@ void GB_sgb_render(GB_gameboy_t *gb) sizeof(gb->sgb->effective_screen_buffer)); } - uint32_t *output = &gb->screen[48 + 39 * 256]; + if (gb->sgb->vram_transfer_countdown) { + if (--gb->sgb->vram_transfer_countdown == 0) { + if (gb->sgb->tile_transfer) { + uint8_t *base = &gb->sgb->tiles[gb->sgb->tile_transfer_high? 0x80 * 8 * 8 : 0]; + for (unsigned tile = 0; tile < 0x80; tile++) { + unsigned tile_x = (tile % 10) * 16; + unsigned tile_y = (tile / 10) * 8; + for (unsigned y = 0; y < 0x8; y++) { + for (unsigned x = 0; x < 0x8; x++) { + base[tile * 8 * 8 + y * 8 + x] = gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] + + gb->sgb->screen_buffer[(tile_x + x + 8) + (tile_y + y) * 160] * 4; + } + } + } + + } + else if (gb->sgb->data_transfer) { + for (unsigned tile = 0; tile < 0x88; tile++) { + unsigned tile_x = (tile % 20) * 8; + unsigned tile_y = (tile / 20) * 8; + for (unsigned y = 0; y < 0x8; y++) { + static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080}; + uint16_t *data = &gb->sgb->raw_pct_data[tile * 8 + y]; + *data = 0; + for (unsigned x = 0; x < 8; x++) { + *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; + } + } + } + } + } + } + + uint32_t *output = &gb->screen[48 + 40 * 256]; uint8_t *input = gb->sgb->effective_screen_buffer; for (unsigned y = 0; y < 144; y++) { for (unsigned x = 0; x < 160; x++) { @@ -183,4 +247,25 @@ void GB_sgb_render(GB_gameboy_t *gb) } output += 256 - 160; } + + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { + for (unsigned tile_x = 0; tile_x < 32; tile_x++) { + bool gb_area = false; + if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { + gb_area = true; + } + uint16_t tile = gb->sgb->map[tile_x + tile_y * 32]; + uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; + uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; + uint8_t palette = (tile >> 10) & 3; + for (unsigned y = 0; y < 8; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = gb->sgb->tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + if (color == 0 && gb_area) continue; + gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = + border_colors[palette * 16 + color]; + } + } + } + } } diff --git a/Core/sgb.h b/Core/sgb.h index bafde350..d9111257 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -22,6 +22,19 @@ typedef struct { /* Mask */ uint8_t mask_mode; + + /* Border */ + uint8_t vram_transfer_countdown; + bool tile_transfer, tile_transfer_high, data_transfer; + uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ + union { + struct { + uint16_t map[32 * 32]; + uint16_t border_palette[16 * 4]; + }; + uint16_t raw_pct_data[0x440]; + }; + } GB_sgb_t; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From 69ac36cca1b14da2cbddb0a33c076df636ef29fd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 17:22:57 +0200 Subject: [PATCH 0763/1216] Add default SGB border --- Core/gb.c | 1 + Core/sgb.c | 692 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Core/sgb.h | 1 + 3 files changed, 694 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 99d6c2cb..d1846642 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -650,6 +650,7 @@ void GB_reset(GB_gameboy_t *gb) } memset(gb->sgb, 0, sizeof(*gb->sgb)); gb->sgb->player_count = 1; + GB_sgb_load_default_border(gb); } else { diff --git a/Core/sgb.c b/Core/sgb.c index ce3016c2..d7030bfd 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -269,3 +269,695 @@ void GB_sgb_render(GB_gameboy_t *gb) } } } + +void GB_sgb_load_default_border(GB_gameboy_t *gb) +{ + static const uint16_t palette[] = { + 0x5273, 0x14A5, 0x043C, 0x2529, 0x35AD, 0x2954, 0x739C, 0x5AD5, + 0x41A8, 0x3D44, 0x4100, 0x20E7, 0x358C, 0x3DCE, 0x4A31, 0x1C75}; + static const uint16_t tilemap[] = { + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1001, 0x1002, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, + 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, + 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, + 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x5002, 0x5001, 0x1000, + 0x1000, 0x1004, 0x1005, 0x1005, 0x1005, 0x1006, 0x1007, 0x1008, + 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, + 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, + 0x1019, 0x101A, 0x101B, 0x1005, 0x1005, 0x1005, 0x5004, 0x1000, + 0x1000, 0x101C, 0x101D, 0x101D, 0x101D, 0x101E, 0x101F, 0x1020, + 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, + 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, + 0x1031, 0x1032, 0x1033, 0x101D, 0x101D, 0x101D, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1036, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1037, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1038, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1039, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x103A, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x103B, 0x103C, 0x1000, + 0x1000, 0x5038, 0x1034, 0x1034, 0x1034, 0x103D, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x503D, 0x1034, 0x103E, 0x103F, 0x1000, 0x1000, + 0x1000, 0x1040, 0x1041, 0x1034, 0x1034, 0x1042, 0x1043, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1049, 0x104A, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x104B, 0x104C, 0x104D, 0x104E, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x104F, 0x1050, + 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, + 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, 0x1060, + 0x1061, 0x1062, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1063, 0x1064, + 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, + 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, + 0x1075, 0x1076, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1077, 0x1078, + 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, 0x1080, + 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, 0x1088, + 0x1089, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + }; + const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x05, 0x01, 0x0F, 0x07, + 0x1E, 0x0E, 0x1D, 0x3D, 0x1B, 0x1B, 0x37, 0x77, + 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x08, 0x0B, + 0x11, 0x16, 0x22, 0x28, 0x24, 0x39, 0x48, 0x73, + 0x3F, 0xC0, 0x7F, 0x7F, 0xFF, 0xFF, 0x80, 0x80, + 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x9F, 0x00, 0xFF, 0x7F, 0xC3, + 0x80, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x37, 0x37, 0x2F, 0x2F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x48, 0x2B, 0x50, 0x67, 0x10, 0x27, 0x10, 0x6F, + 0x10, 0x6F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, + 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, + 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF8, 0x0C, 0xFF, + 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3B, 0x3F, 0xD3, 0x53, + 0xCF, 0xAF, 0xD7, 0xAF, 0xCF, 0xAF, 0xD3, 0x53, + 0x00, 0xFF, 0x00, 0xFF, 0x47, 0x74, 0xE0, 0xB1, + 0x78, 0xE3, 0x78, 0xFF, 0x78, 0xE3, 0xE0, 0xB1, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0x19, 0x39, + 0xF9, 0xF9, 0xE9, 0xE9, 0xF9, 0xF9, 0x19, 0x39, + 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x70, 0xA4, 0xBD, + 0x24, 0x8D, 0x34, 0xDD, 0x24, 0x8D, 0xA4, 0xBD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB5, 0xF5, 0xF5, + 0xF1, 0xF2, 0xF2, 0xF1, 0xF3, 0xF3, 0xF3, 0xF2, + 0x00, 0xFF, 0x00, 0xFF, 0xCE, 0x7B, 0x0C, 0xF9, + 0x0B, 0xFB, 0x0B, 0xFB, 0x09, 0xFA, 0x09, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xDE, 0xD7, + 0x8F, 0x97, 0xAF, 0xD4, 0xEF, 0x77, 0xED, 0xB3, + 0x00, 0xFF, 0x00, 0xFF, 0x38, 0xEE, 0x19, 0xCF, + 0x59, 0xDC, 0x7B, 0xFF, 0xDB, 0x9C, 0xDE, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xBF, 0x3D, 0x7B, + 0x9D, 0x9B, 0xDD, 0xBB, 0xFD, 0xFB, 0xDD, 0xEB, + 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x30, 0x46, 0x7F, + 0x26, 0x3F, 0x66, 0xFF, 0xE6, 0x0F, 0x36, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xBA, 0x9A, 0x98, + 0x9A, 0x98, 0xBA, 0xBE, 0x9A, 0x9A, 0x9A, 0x92, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0x41, 0x43, 0xDB, + 0x43, 0xDB, 0x7D, 0xC5, 0x4D, 0xC7, 0x49, 0xD9, + 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0x4F, 0x75, 0x71, + 0x75, 0x65, 0x7B, 0x7F, 0x6B, 0x71, 0x4A, 0x4A, + 0x00, 0xFF, 0x00, 0xFF, 0x13, 0x1F, 0x1C, 0x4C, + 0x16, 0x73, 0x0C, 0x67, 0x1A, 0x7B, 0x27, 0x45, + 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0xDD, 0xE7, 0xE5, + 0xFD, 0xE6, 0xE7, 0xE7, 0xFD, 0xFB, 0xF7, 0xFD, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0xE6, 0x0B, 0xE6, + 0x1B, 0xFF, 0x12, 0xE1, 0x0E, 0xF7, 0x0E, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x65, 0x7B, 0xFD, + 0xEB, 0xDD, 0x1B, 0x2D, 0xFB, 0x3D, 0x5B, 0x5D, + 0x00, 0xFF, 0x00, 0xFF, 0x8F, 0x6E, 0x96, 0xC7, + 0xB6, 0x7F, 0x76, 0x7F, 0xE6, 0xCF, 0xC6, 0x9F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFB, 0xBD, 0xDB, + 0xBD, 0xDB, 0xBF, 0xDB, 0xBD, 0xDB, 0xBD, 0xDB, + 0x00, 0xFF, 0x00, 0xFF, 0xFE, 0x0F, 0x66, 0xFF, + 0x66, 0xFF, 0x67, 0xFC, 0x66, 0xFF, 0x66, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCE, 0xCF, 0xEF, 0xEF, 0xCF, 0xCF, 0xCE, 0xCF, + 0x00, 0xFF, 0x00, 0xFF, 0x11, 0xDF, 0x10, 0xDE, + 0x11, 0xDF, 0xF0, 0x1F, 0x10, 0xDF, 0x11, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x6F, 0xFE, 0x4D, + 0xFE, 0x7D, 0x9E, 0x7D, 0xCE, 0xCD, 0xFE, 0x5D, + 0x00, 0xFF, 0x00, 0xFF, 0x7F, 0x18, 0xB3, 0xFF, + 0x83, 0xFF, 0xE3, 0xEF, 0x23, 0xCF, 0xB3, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xDE, 0xCF, 0xCF, + 0xCF, 0xCF, 0xDE, 0xDE, 0xCF, 0xCF, 0xCF, 0xCF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x21, 0x20, 0xEF, + 0x20, 0xEF, 0x3F, 0xE1, 0x20, 0xEF, 0x20, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x75, 0x75, 0x34, 0x30, + 0x34, 0x30, 0x75, 0x7D, 0x34, 0x34, 0x34, 0x24, + 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x82, 0x86, 0xB6, + 0x86, 0xB6, 0xFB, 0x8A, 0x9A, 0x8E, 0x92, 0xB2, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xEB, 0xFD, 0xFF, + 0xFB, 0xF7, 0xE7, 0xEB, 0xFB, 0xF7, 0xFD, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xF7, 0x1E, 0x06, 0xF3, + 0x0C, 0xFF, 0xFC, 0x1F, 0x0C, 0xFF, 0x06, 0xF3, + 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x9F, 0xFF, 0xAE, + 0xEF, 0xCE, 0xDF, 0xDF, 0xEF, 0xCF, 0xFF, 0xAE, + 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0x3E, 0x71, 0xDF, + 0x21, 0xEF, 0x30, 0xEF, 0x20, 0xEF, 0x71, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x6C, 0x5B, 0x4A, + 0x7B, 0xF9, 0x8D, 0x6D, 0xFB, 0xF9, 0x53, 0xD2, + 0x00, 0xFF, 0x00, 0xFF, 0x72, 0x1E, 0x37, 0x79, + 0x82, 0xFB, 0xF6, 0xFB, 0x12, 0xC3, 0xBF, 0xE9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, 0xC9, 0xDD, 0xF9, + 0xED, 0xF1, 0xF5, 0xE9, 0xEB, 0xF3, 0xDF, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0xD4, 0x1D, 0x34, 0xE5, + 0x1C, 0xFD, 0x1C, 0xFD, 0x1E, 0xFD, 0x33, 0xE1, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xC5, 0xD7, 0xC7, + 0xD2, 0xC0, 0xD2, 0xC3, 0xC3, 0xC3, 0xF3, 0xB3, + 0x00, 0xFF, 0x00, 0xFF, 0x1E, 0xDB, 0x1E, 0xD8, + 0x1A, 0xDA, 0x19, 0xDB, 0x18, 0xCA, 0x68, 0xCB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, + 0xF7, 0xFB, 0xF7, 0x7B, 0xB7, 0xBB, 0x77, 0x7B, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0x98, 0x2C, 0x9F, + 0x2C, 0x9F, 0xAC, 0x9F, 0xEC, 0x1F, 0xEC, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0xDF, 0x5F, + 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0x5F, + 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x7F, 0xE0, 0xBF, + 0x60, 0xFF, 0x60, 0xFF, 0x60, 0xFF, 0xE0, 0xBF, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, + 0x3B, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x47, 0x74, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xF9, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xC4, 0x7D, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xF3, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x08, 0xFA, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xE9, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x9A, 0x39, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xCD, 0xCB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x26, 0xC7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x98, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x47, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x2E, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x72, 0x5E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x0C, 0x79, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xFB, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x66, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xBD, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x66, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xCF, 0xCE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x11, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xEE, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xF3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xDE, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x3F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x31, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x8F, 0xBE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xE9, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xF7, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xBF, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xA1, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x7E, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x72, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xCF, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xD3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xF3, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xE8, 0x3B, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x6F, 0xD8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, + 0x40, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, + 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, + 0x3C, 0xFF, 0x00, 0x81, 0x81, 0x81, 0x00, 0x00, + 0x00, 0xFF, 0x81, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x81, 0x81, 0x66, 0xE7, 0x3C, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, + 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF4, 0xF4, + 0xF4, 0xF4, 0xF6, 0xF4, 0xE4, 0xE6, 0xEC, 0xEC, + 0x08, 0xF6, 0x08, 0xF4, 0x08, 0xF4, 0x0A, 0xE6, + 0x0A, 0xE4, 0x0A, 0xEE, 0x1A, 0xFE, 0x10, 0xF4, + 0xEE, 0xEE, 0xE8, 0xE8, 0xE8, 0xEC, 0xD8, 0xD8, + 0xDC, 0xDC, 0xD0, 0xD8, 0xB8, 0xB8, 0xA8, 0xA8, + 0x12, 0xCC, 0x14, 0xCC, 0x14, 0xDC, 0x20, 0xE8, + 0x24, 0x90, 0x28, 0xB8, 0x48, 0x30, 0x58, 0x30, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFD, 0xFD, 0xF9, 0xF9, 0xFB, 0xFB, 0xF7, 0xF7, + 0x00, 0xFE, 0x00, 0xFE, 0x01, 0xFC, 0x01, 0xFC, + 0x02, 0xF9, 0x06, 0xF7, 0x04, 0xF2, 0x09, 0xE6, + 0x60, 0x60, 0x70, 0x70, 0xC0, 0xC0, 0xA0, 0xA0, + 0xC0, 0xC0, 0xC0, 0x40, 0x80, 0x80, 0x00, 0x00, + 0x80, 0xA0, 0x90, 0x40, 0x00, 0x40, 0x60, 0xC0, + 0x40, 0x80, 0xC0, 0x80, 0x80, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFE, 0xFC, 0xFC, 0xFB, 0xFB, 0xE6, 0xE6, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, + 0x01, 0xFD, 0x03, 0xF9, 0x04, 0xF1, 0x19, 0xD3, + 0xED, 0xEF, 0xCA, 0xCA, 0x98, 0x9C, 0x38, 0x38, + 0x78, 0x78, 0xF0, 0xF0, 0xE0, 0xE0, 0x40, 0xC0, + 0x13, 0xC6, 0x36, 0xBC, 0x64, 0x70, 0xC8, 0xE0, + 0x98, 0xC0, 0x30, 0x80, 0x60, 0x00, 0xC0, 0x00, + 0x6F, 0x6F, 0x6F, 0x6F, 0x2F, 0x2F, 0x37, 0x77, + 0x77, 0x77, 0x1B, 0x3B, 0x2D, 0x2D, 0x06, 0x16, + 0x10, 0x6F, 0x10, 0x27, 0x50, 0x27, 0x48, 0x6B, + 0x48, 0x13, 0x24, 0x39, 0x32, 0x08, 0x19, 0x0E, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x80, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF8, 0x07, 0x07, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xE4, 0xF8, 0xCB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xF9, 0xF9, + 0xE7, 0xE7, 0x1F, 0x1F, 0xF5, 0xF7, 0xF4, 0xDC, + 0x00, 0xFF, 0x00, 0xFE, 0x01, 0xF8, 0x06, 0xE4, + 0x18, 0x83, 0xE1, 0xBC, 0x0F, 0x78, 0x3C, 0xA0, + 0xCC, 0xCD, 0x38, 0x3E, 0x7C, 0x7C, 0xC8, 0xF8, + 0x60, 0x60, 0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x33, 0xAC, 0xC6, 0x9C, 0x8C, 0x60, 0x38, 0xE0, + 0xE0, 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x0B, 0x03, 0x07, 0x02, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x07, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0xFF, 0xFF, 0xDF, 0x9F, 0x80, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0xC3, 0x00, 0xFF, 0xE0, 0x6F, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xFA, 0xF9, 0x01, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x81, 0x00, 0xFF, 0x07, 0xC7, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xE4, 0xEF, 0xCC, 0xFC, 0xC0, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x3F, 0x1F, 0xF8, 0xFC, 0x00, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0xF0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, + 0x0F, 0x16, 0x3F, 0x1D, 0x7F, 0xF0, 0xE9, 0xE7, + 0xD3, 0xCB, 0xE7, 0x93, 0xC6, 0x02, 0x87, 0x40, + 0x1F, 0x16, 0x3F, 0x1D, 0xFF, 0xF6, 0xFF, 0xEF, + 0xFB, 0xDB, 0xF7, 0xF3, 0xC7, 0x42, 0xC7, 0xC1, + 0x00, 0x00, 0x80, 0x00, 0x80, 0x40, 0xC0, 0x00, + 0xC0, 0x00, 0x80, 0x40, 0xB0, 0x01, 0x31, 0xE9, + 0x00, 0x00, 0x80, 0x00, 0xC0, 0x40, 0xC0, 0x40, + 0xC0, 0x40, 0xC0, 0xC0, 0xB1, 0x81, 0xF9, 0xE9, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x84, 0x85, 0xC6, 0xCE, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x84, 0x84, 0xC7, 0xC6, 0xCF, 0xC7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0xC1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0xC3, 0xE3, 0xC7, 0xF7, 0x23, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x02, 0xC3, 0xC3, 0xE7, 0xC7, 0xF7, 0x63, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x40, 0xF0, 0x20, 0xF0, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0xC0, 0xC0, 0xF0, 0xA0, 0xF8, 0xF8, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x07, + 0x01, 0x1E, 0x11, 0x0E, 0x00, 0x0E, 0x00, 0x1F, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x07, 0x00, 0x0F, + 0x10, 0x1F, 0x11, 0x1F, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x10, 0x08, 0xF4, 0x00, 0xF8, + 0x24, 0x12, 0x98, 0x64, 0x00, 0x00, 0x80, 0x40, + 0x00, 0x00, 0x10, 0xF0, 0x04, 0xFC, 0x00, 0xFC, + 0x06, 0xFE, 0xFC, 0xFC, 0x00, 0x00, 0x40, 0xC0, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x1F, + 0x10, 0x0F, 0x00, 0x1F, 0x00, 0x7B, 0x4C, 0x33, + 0x00, 0x00, 0x07, 0x07, 0x00, 0x0F, 0x10, 0x1F, + 0x00, 0x1F, 0x00, 0x3F, 0x40, 0x7F, 0x04, 0x7F, + 0x00, 0x00, 0x00, 0x01, 0x82, 0x01, 0x00, 0x41, + 0x80, 0x41, 0x82, 0x41, 0x40, 0x83, 0x00, 0x87, + 0x00, 0x00, 0x01, 0x01, 0x82, 0x83, 0x40, 0xC3, + 0x40, 0xC3, 0x40, 0xC3, 0x40, 0xC3, 0x44, 0xC7, + 0x00, 0x00, 0x00, 0xF0, 0x10, 0xE0, 0x18, 0xE0, + 0x08, 0xF0, 0x00, 0xF0, 0x40, 0xB4, 0x49, 0xB4, + 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xF0, 0x08, 0xF8, + 0x08, 0xF8, 0x08, 0xF8, 0x04, 0xFD, 0x04, 0xFD, + 0x00, 0x00, 0x00, 0x3E, 0x40, 0x3F, 0x43, 0x3C, + 0x83, 0x7C, 0x80, 0x7D, 0x20, 0xDD, 0x02, 0xDD, + 0x00, 0x00, 0x3E, 0x3E, 0x41, 0x7F, 0x01, 0x7F, + 0x81, 0xFF, 0x01, 0xFF, 0x03, 0xFD, 0x03, 0xFF, + 0x00, 0x00, 0x00, 0x3F, 0x40, 0x3F, 0x00, 0x3F, + 0x07, 0x38, 0x47, 0x30, 0x00, 0x74, 0x08, 0xF0, + 0x00, 0x00, 0x3F, 0x3F, 0x40, 0x7F, 0x00, 0x7F, + 0x00, 0x7F, 0x07, 0x7F, 0x0C, 0x7C, 0x80, 0xFF, + 0x00, 0x00, 0x00, 0xE7, 0x24, 0xD3, 0x30, 0xC7, + 0xC0, 0x1F, 0xE8, 0x17, 0x01, 0x06, 0x49, 0x06, + 0x00, 0x00, 0xE7, 0xE7, 0x10, 0xF7, 0x10, 0xF7, + 0x18, 0xFF, 0xF8, 0xFF, 0x00, 0x0F, 0x40, 0xCF, + 0x00, 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x02, 0xFC, + 0xE0, 0x1F, 0x81, 0x6E, 0x83, 0x0C, 0x07, 0x18, + 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xFC, 0x00, 0xFE, + 0x01, 0xFF, 0xE1, 0xFF, 0x81, 0x9F, 0x03, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x18, 0x07, + 0x01, 0x3E, 0x21, 0x1C, 0x02, 0x39, 0x4C, 0x32, + 0x00, 0x00, 0x01, 0x01, 0x08, 0x0F, 0x10, 0x1F, + 0x20, 0x3F, 0x01, 0x3F, 0x03, 0x7F, 0x06, 0x7E, + 0x00, 0x00, 0x20, 0x10, 0x0C, 0xF0, 0x00, 0xFC, + 0xE1, 0x1E, 0x81, 0x6E, 0x09, 0x06, 0x00, 0x07, + 0x00, 0x00, 0x30, 0xF0, 0x04, 0xFC, 0x00, 0xFE, + 0x01, 0xFF, 0xE0, 0xFF, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF0, 0x04, 0x38, + 0x00, 0x78, 0x25, 0x9A, 0x00, 0xBD, 0x90, 0x0F, + 0x00, 0x00, 0xF8, 0xF8, 0x80, 0xF8, 0x04, 0x7C, + 0x40, 0x7C, 0x83, 0xBF, 0xA0, 0xBF, 0x80, 0x9F, + 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7E, 0x42, 0x38, + 0x04, 0x72, 0x18, 0xE4, 0x30, 0xC8, 0x60, 0x90, + 0x00, 0x00, 0x3E, 0x3E, 0x42, 0x7E, 0x06, 0x7E, + 0x06, 0xFE, 0x0C, 0xFC, 0x18, 0xF8, 0x30, 0xF0, + 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x28, 0x1C, 0x3A, 0x0F, 0x1E, 0x07, 0x0B, + 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x38, 0x28, 0x3E, 0x3A, 0x1F, 0x1E, 0x0F, 0x0B, + 0x03, 0x00, 0x80, 0xC0, 0xC0, 0xE0, 0xF1, 0x68, + 0x79, 0x3D, 0x19, 0x2B, 0x1D, 0x03, 0xFD, 0xA1, + 0x83, 0x03, 0xC0, 0xC0, 0xE0, 0xE0, 0xF9, 0x68, + 0x7D, 0x7D, 0x3F, 0x2B, 0x1F, 0x07, 0xFD, 0xAD, + 0x63, 0x69, 0xF3, 0x4F, 0xD7, 0xC2, 0xE7, 0x9E, + 0x8F, 0xAC, 0xDE, 0x2D, 0xFE, 0x05, 0xEF, 0x95, + 0x7B, 0x69, 0xFF, 0x5F, 0xF7, 0xD2, 0xFF, 0xBF, + 0xEF, 0xAD, 0xFF, 0x6F, 0xFF, 0x47, 0xFF, 0x95, + 0xCD, 0x1C, 0x1E, 0x49, 0xBC, 0x5A, 0x34, 0xB1, + 0x79, 0x34, 0x79, 0xF3, 0xFB, 0xD7, 0xFF, 0xCE, + 0xDF, 0x5D, 0xDF, 0x4B, 0xFE, 0xDE, 0xBD, 0xB5, + 0x7D, 0x3C, 0xFB, 0xFB, 0xFF, 0xDF, 0xFF, 0xFE, + 0x67, 0xD2, 0xE7, 0x5E, 0xEF, 0x54, 0xEF, 0xDD, + 0xFF, 0x88, 0xFE, 0x98, 0xFC, 0x0A, 0xF7, 0x4C, + 0xF7, 0xD2, 0xFF, 0x5F, 0xFF, 0x55, 0xFF, 0xFD, + 0xFF, 0xAB, 0xFE, 0xFA, 0xFE, 0x5A, 0xFF, 0x5C, + 0xF3, 0x2A, 0xE7, 0x28, 0xF5, 0x4E, 0xFF, 0x80, + 0x69, 0x17, 0x15, 0xE9, 0x29, 0x51, 0xD1, 0x28, + 0xFB, 0xEA, 0xFF, 0xA9, 0xFF, 0x5F, 0xFF, 0xB2, + 0xFF, 0x77, 0xFD, 0xED, 0x79, 0x59, 0xF9, 0x38, + 0x78, 0x20, 0xF8, 0x60, 0xD0, 0xC0, 0xE0, 0x90, + 0xC8, 0xA0, 0xCC, 0x18, 0xD4, 0x18, 0xF8, 0x84, + 0xF8, 0x28, 0xF8, 0xF8, 0xF0, 0xD0, 0xF0, 0xB0, + 0xE8, 0xE0, 0xDC, 0x5C, 0xDC, 0x5C, 0xFC, 0x8C, + 0x00, 0x07, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x08, 0x00, 0x24, 0x58, 0x00, 0x7F, 0x10, 0x2F, + 0x00, 0x0F, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, + 0x08, 0x18, 0x40, 0x7F, 0x40, 0x7F, 0x20, 0x3F, + 0x20, 0xD0, 0x00, 0xF9, 0x81, 0x70, 0x0A, 0xF5, + 0x06, 0xF1, 0x80, 0x73, 0x10, 0xE7, 0x58, 0x87, + 0x10, 0xF0, 0x09, 0xF9, 0x00, 0xF9, 0x86, 0xFF, + 0x84, 0xFF, 0x08, 0xF7, 0x08, 0xFF, 0x10, 0xFF, + 0x0A, 0x75, 0x0A, 0xE1, 0x12, 0xE1, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x81, 0xC0, 0x3F, 0x81, 0x40, + 0x0C, 0xFF, 0x08, 0xFB, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x3C, 0xFF, 0x7E, 0xFF, 0xC0, 0xC1, + 0x04, 0xA3, 0x40, 0xA3, 0x44, 0xA3, 0x24, 0xC3, + 0x20, 0xC7, 0x00, 0xDF, 0x21, 0xD6, 0x21, 0xD6, + 0x24, 0xE7, 0x20, 0xE7, 0x20, 0xE7, 0x20, 0xE7, + 0x20, 0xE7, 0x18, 0xFF, 0x10, 0xFF, 0x10, 0xFF, + 0x88, 0x35, 0xC4, 0x3B, 0x04, 0x7B, 0x20, 0x5F, + 0x80, 0x5E, 0xA3, 0x1C, 0x81, 0x3C, 0x86, 0x19, + 0x04, 0xFF, 0x44, 0xFF, 0x40, 0xFF, 0x40, 0xFF, + 0xC0, 0xFF, 0xA1, 0xBF, 0xA1, 0xBF, 0x83, 0x9F, + 0x46, 0x98, 0x66, 0x98, 0xE0, 0x1A, 0x80, 0x7A, + 0x84, 0x7A, 0x44, 0xB9, 0x0C, 0x30, 0x4C, 0x30, + 0x02, 0xFE, 0x42, 0xFE, 0x42, 0xFE, 0xC2, 0xFE, + 0xC6, 0xFE, 0xC5, 0xFD, 0x04, 0x7D, 0x04, 0x7D, + 0x80, 0x7F, 0x00, 0x7F, 0x9F, 0x60, 0x80, 0x68, + 0x00, 0xE8, 0x07, 0xF8, 0x00, 0xFF, 0x00, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x0F, 0xFF, 0x08, 0xF8, + 0x18, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x08, 0xA7, 0x00, 0xAF, 0xC1, 0x3E, 0x13, 0x0C, + 0x03, 0x0C, 0xD1, 0x0E, 0x10, 0xAF, 0x40, 0xBF, + 0x20, 0xEF, 0x20, 0xEF, 0xF1, 0xFF, 0x11, 0x1F, + 0x01, 0x1F, 0x40, 0xDF, 0x60, 0xFF, 0x60, 0xFF, + 0x0E, 0xF0, 0x00, 0xF8, 0xC2, 0x1C, 0x02, 0x1D, + 0x20, 0x1D, 0xC2, 0x3D, 0x02, 0xF8, 0x3C, 0xC2, + 0x06, 0xFE, 0x00, 0xFC, 0xC2, 0xFE, 0x01, 0x1F, + 0x21, 0x3F, 0x03, 0xFF, 0x02, 0xFE, 0x0E, 0xFE, + 0x4C, 0x30, 0x00, 0xF4, 0x00, 0xF4, 0x08, 0x74, + 0x40, 0x38, 0x01, 0x7E, 0x00, 0x1F, 0x18, 0x07, + 0x04, 0x7C, 0x84, 0xFC, 0x84, 0xFC, 0x04, 0x7C, + 0x00, 0x7C, 0x40, 0x7F, 0x00, 0x3F, 0x10, 0x1F, + 0x00, 0x07, 0x09, 0x06, 0x01, 0x1E, 0x00, 0x0E, + 0x03, 0x5C, 0x82, 0x7D, 0x0E, 0xF0, 0x2C, 0xC0, + 0x00, 0x0F, 0x00, 0x0F, 0x10, 0x1F, 0x01, 0x1F, + 0x41, 0x7F, 0x03, 0xFF, 0x06, 0xFE, 0x0C, 0xFC, + 0x90, 0x0F, 0x80, 0x0F, 0x89, 0x06, 0x00, 0x8E, + 0x00, 0x9E, 0x11, 0x0E, 0x01, 0x0E, 0x13, 0x0C, + 0x90, 0x9F, 0x80, 0x8F, 0x80, 0x8F, 0x80, 0x8F, + 0x91, 0x9F, 0x11, 0x1F, 0x01, 0x1F, 0x01, 0x1F, + 0x60, 0x80, 0x40, 0x00, 0x80, 0x40, 0x00, 0x80, + 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x60, 0xE0, 0xC0, 0x40, 0xC0, 0xC0, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0x05, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0x3D, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x10, 0x01, 0x63, 0x01, 0x03, 0x03, 0x01, + 0x03, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, + 0xF7, 0x74, 0x63, 0x63, 0x03, 0x03, 0x03, 0x01, + 0x03, 0x00, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, + 0xE7, 0x88, 0xC0, 0xA3, 0xC0, 0x00, 0x80, 0x40, + 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, + 0xEF, 0xA8, 0xE3, 0xE3, 0xC0, 0x40, 0xC0, 0xC0, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, + 0xA1, 0x54, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0xF5, 0xC1, 0xC1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xA0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE0, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xD0, 0x28, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xB8, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x1F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x10, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x77, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8E, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xCF, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x0F, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x08, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xF8, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + memcpy(gb->sgb->map, tilemap, sizeof(tilemap)); + memcpy(gb->sgb->border_palette, palette, sizeof(palette)); + + + /* Expend tileset */ + for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { + for (unsigned y = 0; y < 8; y++) { + for (unsigned x = 0; x < 8; x++) { + gb->sgb->tiles[tile * 8 * 8 + y * 8 + x] = + (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) | + (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) | + (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) | + (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0); + } + } + } +} diff --git a/Core/sgb.h b/Core/sgb.h index d9111257..43de81c1 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -39,6 +39,7 @@ typedef struct { void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); +void GB_sgb_load_default_border(GB_gameboy_t *gb); #endif From 595907cae284471eafc381350c7a9af09caf21ca Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 17:36:21 +0200 Subject: [PATCH 0764/1216] Border fade animation --- Core/sgb.c | 60 +++++++++++++++++++++++++++++++++++++++++++----------- Core/sgb.h | 17 +++++++++------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index d7030bfd..edc33230 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -170,6 +170,23 @@ static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color) return gb->rgb_encode_callback(gb, r, g, b); } +static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade) +{ + uint8_t r = ((color) & 0x1F) - fade; + uint8_t g = ((color >> 5) & 0x1F) - fade; + uint8_t b = ((color >> 10) & 0x1F) - fade; + + if (r >= 0x20) r = 0; + if (g >= 0x20) g = 0; + if (b >= 0x20) b = 0; + + r = scale_channel(r); + g = scale_channel(g); + b = scale_channel(b); + + return gb->rgb_encode_callback(gb, r, g, b); +} + void GB_sgb_render(GB_gameboy_t *gb) { if (!gb->screen || !gb->rgb_encode_callback) return; @@ -181,11 +198,6 @@ void GB_sgb_render(GB_gameboy_t *gb) gb->rgb_encode_callback(gb, 0x33, 0x1e, 0x50) }; - uint32_t border_colors[16 * 4]; - for (unsigned i = 0; i < 16 * 4; i++) { - border_colors[i] = convert_rgb15(gb, gb->sgb->border_palette[i]); - } - switch ((mask_mode_t) gb->sgb->mask_mode) { case MASK_DISABLED: memcpy(gb->sgb->effective_screen_buffer, @@ -209,7 +221,7 @@ void GB_sgb_render(GB_gameboy_t *gb) if (gb->sgb->vram_transfer_countdown) { if (--gb->sgb->vram_transfer_countdown == 0) { if (gb->sgb->tile_transfer) { - uint8_t *base = &gb->sgb->tiles[gb->sgb->tile_transfer_high? 0x80 * 8 * 8 : 0]; + uint8_t *base = &gb->sgb->pending_border.tiles[gb->sgb->tile_transfer_high? 0x80 * 8 * 8 : 0]; for (unsigned tile = 0; tile < 0x80; tile++) { unsigned tile_x = (tile % 10) * 16; unsigned tile_y = (tile / 10) * 8; @@ -228,13 +240,14 @@ void GB_sgb_render(GB_gameboy_t *gb) unsigned tile_y = (tile / 20) * 8; for (unsigned y = 0; y < 0x8; y++) { static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080}; - uint16_t *data = &gb->sgb->raw_pct_data[tile * 8 + y]; + uint16_t *data = &gb->sgb->pending_border.raw_data[tile * 8 + y]; *data = 0; for (unsigned x = 0; x < 8; x++) { *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; } } } + gb->sgb->border_animation = 64; } } } @@ -248,19 +261,42 @@ void GB_sgb_render(GB_gameboy_t *gb) output += 256 - 160; } + uint32_t border_colors[16 * 4]; + if (gb->sgb->border_animation == 0) { + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15(gb, gb->sgb->border.palette[i]); + } + } + else if (gb->sgb->border_animation > 32) { + gb->sgb->border_animation--; + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], 64 - gb->sgb->border_animation); + } + } + else { + gb->sgb->border_animation--; + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], gb->sgb->border_animation); + } + } + + if (gb->sgb->border_animation == 32) { + memcpy(&gb->sgb->border, &gb->sgb->pending_border, sizeof(gb->sgb->border)); + } + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { for (unsigned tile_x = 0; tile_x < 32; tile_x++) { bool gb_area = false; if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { gb_area = true; } - uint16_t tile = gb->sgb->map[tile_x + tile_y * 32]; + uint16_t tile = gb->sgb->border.map[tile_x + tile_y * 32]; uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; uint8_t palette = (tile >> 10) & 3; for (unsigned y = 0; y < 8; y++) { for (unsigned x = 0; x < 8; x++) { - uint8_t color = gb->sgb->tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; if (color == 0 && gb_area) continue; gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[palette * 16 + color]; @@ -944,15 +980,15 @@ void GB_sgb_load_default_border(GB_gameboy_t *gb) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - memcpy(gb->sgb->map, tilemap, sizeof(tilemap)); - memcpy(gb->sgb->border_palette, palette, sizeof(palette)); + memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); + memcpy(gb->sgb->border.palette, palette, sizeof(palette)); /* Expend tileset */ for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { for (unsigned y = 0; y < 8; y++) { for (unsigned x = 0; x < 8; x++) { - gb->sgb->tiles[tile * 8 * 8 + y * 8 + x] = + gb->sgb->border.tiles[tile * 8 * 8 + y * 8 + x] = (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) | (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) | (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) | diff --git a/Core/sgb.h b/Core/sgb.h index 43de81c1..de188413 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -26,14 +26,17 @@ typedef struct { /* Border */ uint8_t vram_transfer_countdown; bool tile_transfer, tile_transfer_high, data_transfer; - uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ - union { - struct { - uint16_t map[32 * 32]; - uint16_t border_palette[16 * 4]; + struct { + uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ + union { + struct { + uint16_t map[32 * 32]; + uint16_t palette[16 * 4]; + }; + uint16_t raw_data[0x440]; }; - uint16_t raw_pct_data[0x440]; - }; + } border, pending_border; + uint8_t border_animation; } GB_sgb_t; From 2710939e1ebbee227734736227111cb4c15b2331 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 17:38:30 +0200 Subject: [PATCH 0765/1216] That should be an inc file --- Core/sgb.c | 673 +------------------------------------------- Core/sgb_border.inc | 671 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 673 insertions(+), 671 deletions(-) create mode 100644 Core/sgb_border.inc diff --git a/Core/sgb.c b/Core/sgb.c index edc33230..548e9346 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -308,677 +308,8 @@ void GB_sgb_render(GB_gameboy_t *gb) void GB_sgb_load_default_border(GB_gameboy_t *gb) { - static const uint16_t palette[] = { - 0x5273, 0x14A5, 0x043C, 0x2529, 0x35AD, 0x2954, 0x739C, 0x5AD5, - 0x41A8, 0x3D44, 0x4100, 0x20E7, 0x358C, 0x3DCE, 0x4A31, 0x1C75}; - static const uint16_t tilemap[] = { - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1001, 0x1002, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, - 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, - 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, - 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x5002, 0x5001, 0x1000, - 0x1000, 0x1004, 0x1005, 0x1005, 0x1005, 0x1006, 0x1007, 0x1008, - 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, - 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, - 0x1019, 0x101A, 0x101B, 0x1005, 0x1005, 0x1005, 0x5004, 0x1000, - 0x1000, 0x101C, 0x101D, 0x101D, 0x101D, 0x101E, 0x101F, 0x1020, - 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, - 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, - 0x1031, 0x1032, 0x1033, 0x101D, 0x101D, 0x101D, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1036, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1037, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1038, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1039, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x103A, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x103B, 0x103C, 0x1000, - 0x1000, 0x5038, 0x1034, 0x1034, 0x1034, 0x103D, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x503D, 0x1034, 0x103E, 0x103F, 0x1000, 0x1000, - 0x1000, 0x1040, 0x1041, 0x1034, 0x1034, 0x1042, 0x1043, 0x1044, - 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, - 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, - 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1049, 0x104A, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104C, 0x104D, 0x104E, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x104F, 0x1050, - 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, - 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, 0x1060, - 0x1061, 0x1062, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1063, 0x1064, - 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, - 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, - 0x1075, 0x1076, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1077, 0x1078, - 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, 0x1080, - 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, 0x1088, - 0x1089, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - }; - const uint8_t tiles[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x02, 0x05, 0x01, 0x0F, 0x07, - 0x1E, 0x0E, 0x1D, 0x3D, 0x1B, 0x1B, 0x37, 0x77, - 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x08, 0x0B, - 0x11, 0x16, 0x22, 0x28, 0x24, 0x39, 0x48, 0x73, - 0x3F, 0xC0, 0x7F, 0x7F, 0xFF, 0xFF, 0x80, 0x80, - 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x9F, 0x00, 0xFF, 0x7F, 0xC3, - 0x80, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x37, 0x37, 0x2F, 0x2F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x48, 0x2B, 0x50, 0x67, 0x10, 0x27, 0x10, 0x6F, - 0x10, 0x6F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, - 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, - 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF8, 0x0C, 0xFF, - 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x3B, 0x3F, 0xD3, 0x53, - 0xCF, 0xAF, 0xD7, 0xAF, 0xCF, 0xAF, 0xD3, 0x53, - 0x00, 0xFF, 0x00, 0xFF, 0x47, 0x74, 0xE0, 0xB1, - 0x78, 0xE3, 0x78, 0xFF, 0x78, 0xE3, 0xE0, 0xB1, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0x19, 0x39, - 0xF9, 0xF9, 0xE9, 0xE9, 0xF9, 0xF9, 0x19, 0x39, - 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x70, 0xA4, 0xBD, - 0x24, 0x8D, 0x34, 0xDD, 0x24, 0x8D, 0xA4, 0xBD, - 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB5, 0xF5, 0xF5, - 0xF1, 0xF2, 0xF2, 0xF1, 0xF3, 0xF3, 0xF3, 0xF2, - 0x00, 0xFF, 0x00, 0xFF, 0xCE, 0x7B, 0x0C, 0xF9, - 0x0B, 0xFB, 0x0B, 0xFB, 0x09, 0xFA, 0x09, 0xFB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xDE, 0xD7, - 0x8F, 0x97, 0xAF, 0xD4, 0xEF, 0x77, 0xED, 0xB3, - 0x00, 0xFF, 0x00, 0xFF, 0x38, 0xEE, 0x19, 0xCF, - 0x59, 0xDC, 0x7B, 0xFF, 0xDB, 0x9C, 0xDE, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xBF, 0x3D, 0x7B, - 0x9D, 0x9B, 0xDD, 0xBB, 0xFD, 0xFB, 0xDD, 0xEB, - 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x30, 0x46, 0x7F, - 0x26, 0x3F, 0x66, 0xFF, 0xE6, 0x0F, 0x36, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xBA, 0x9A, 0x98, - 0x9A, 0x98, 0xBA, 0xBE, 0x9A, 0x9A, 0x9A, 0x92, - 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0x41, 0x43, 0xDB, - 0x43, 0xDB, 0x7D, 0xC5, 0x4D, 0xC7, 0x49, 0xD9, - 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0x4F, 0x75, 0x71, - 0x75, 0x65, 0x7B, 0x7F, 0x6B, 0x71, 0x4A, 0x4A, - 0x00, 0xFF, 0x00, 0xFF, 0x13, 0x1F, 0x1C, 0x4C, - 0x16, 0x73, 0x0C, 0x67, 0x1A, 0x7B, 0x27, 0x45, - 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0xDD, 0xE7, 0xE5, - 0xFD, 0xE6, 0xE7, 0xE7, 0xFD, 0xFB, 0xF7, 0xFD, - 0x00, 0xFF, 0x00, 0xFF, 0x33, 0xE6, 0x0B, 0xE6, - 0x1B, 0xFF, 0x12, 0xE1, 0x0E, 0xF7, 0x0E, 0xFB, - 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x65, 0x7B, 0xFD, - 0xEB, 0xDD, 0x1B, 0x2D, 0xFB, 0x3D, 0x5B, 0x5D, - 0x00, 0xFF, 0x00, 0xFF, 0x8F, 0x6E, 0x96, 0xC7, - 0xB6, 0x7F, 0x76, 0x7F, 0xE6, 0xCF, 0xC6, 0x9F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFB, 0xBD, 0xDB, - 0xBD, 0xDB, 0xBF, 0xDB, 0xBD, 0xDB, 0xBD, 0xDB, - 0x00, 0xFF, 0x00, 0xFF, 0xFE, 0x0F, 0x66, 0xFF, - 0x66, 0xFF, 0x67, 0xFC, 0x66, 0xFF, 0x66, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0xCF, 0xEF, 0xEF, 0xCF, 0xCF, 0xCE, 0xCF, - 0x00, 0xFF, 0x00, 0xFF, 0x11, 0xDF, 0x10, 0xDE, - 0x11, 0xDF, 0xF0, 0x1F, 0x10, 0xDF, 0x11, 0xDF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x6F, 0xFE, 0x4D, - 0xFE, 0x7D, 0x9E, 0x7D, 0xCE, 0xCD, 0xFE, 0x5D, - 0x00, 0xFF, 0x00, 0xFF, 0x7F, 0x18, 0xB3, 0xFF, - 0x83, 0xFF, 0xE3, 0xEF, 0x23, 0xCF, 0xB3, 0xEF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xDE, 0xCF, 0xCF, - 0xCF, 0xCF, 0xDE, 0xDE, 0xCF, 0xCF, 0xCF, 0xCF, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x21, 0x20, 0xEF, - 0x20, 0xEF, 0x3F, 0xE1, 0x20, 0xEF, 0x20, 0xEF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x75, 0x75, 0x34, 0x30, - 0x34, 0x30, 0x75, 0x7D, 0x34, 0x34, 0x34, 0x24, - 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x82, 0x86, 0xB6, - 0x86, 0xB6, 0xFB, 0x8A, 0x9A, 0x8E, 0x92, 0xB2, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xEB, 0xFD, 0xFF, - 0xFB, 0xF7, 0xE7, 0xEB, 0xFB, 0xF7, 0xFD, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0xF7, 0x1E, 0x06, 0xF3, - 0x0C, 0xFF, 0xFC, 0x1F, 0x0C, 0xFF, 0x06, 0xF3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x9F, 0xFF, 0xAE, - 0xEF, 0xCE, 0xDF, 0xDF, 0xEF, 0xCF, 0xFF, 0xAE, - 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0x3E, 0x71, 0xDF, - 0x21, 0xEF, 0x30, 0xEF, 0x20, 0xEF, 0x71, 0xDF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x6C, 0x5B, 0x4A, - 0x7B, 0xF9, 0x8D, 0x6D, 0xFB, 0xF9, 0x53, 0xD2, - 0x00, 0xFF, 0x00, 0xFF, 0x72, 0x1E, 0x37, 0x79, - 0x82, 0xFB, 0xF6, 0xFB, 0x12, 0xC3, 0xBF, 0xE9, - 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, 0xC9, 0xDD, 0xF9, - 0xED, 0xF1, 0xF5, 0xE9, 0xEB, 0xF3, 0xDF, 0xFE, - 0x00, 0xFF, 0x00, 0xFF, 0xD4, 0x1D, 0x34, 0xE5, - 0x1C, 0xFD, 0x1C, 0xFD, 0x1E, 0xFD, 0x33, 0xE1, - 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xC5, 0xD7, 0xC7, - 0xD2, 0xC0, 0xD2, 0xC3, 0xC3, 0xC3, 0xF3, 0xB3, - 0x00, 0xFF, 0x00, 0xFF, 0x1E, 0xDB, 0x1E, 0xD8, - 0x1A, 0xDA, 0x19, 0xDB, 0x18, 0xCA, 0x68, 0xCB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, - 0xF7, 0xFB, 0xF7, 0x7B, 0xB7, 0xBB, 0x77, 0x7B, - 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0x98, 0x2C, 0x9F, - 0x2C, 0x9F, 0xAC, 0x9F, 0xEC, 0x1F, 0xEC, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0xDF, 0x5F, - 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0x5F, - 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x7F, 0xE0, 0xBF, - 0x60, 0xFF, 0x60, 0xFF, 0x60, 0xFF, 0xE0, 0xBF, - 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, - 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, - 0x3B, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x47, 0x74, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF9, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xC4, 0x7D, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF3, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x08, 0xFA, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xE9, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x9A, 0x39, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCD, 0xCB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x26, 0xC7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x98, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x47, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x2E, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x72, 0x5E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x0C, 0x79, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xFB, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x66, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xBD, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x66, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCF, 0xCE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x11, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xEE, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xF3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xDE, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x3F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x31, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x8F, 0xBE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xE9, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xF7, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xBF, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xA1, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x7E, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x72, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCF, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xD3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF3, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xE8, 0x3B, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x6F, 0xD8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, - 0x40, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, - 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, - 0x3C, 0xFF, 0x00, 0x81, 0x81, 0x81, 0x00, 0x00, - 0x00, 0xFF, 0x81, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x81, 0x81, 0x66, 0xE7, 0x3C, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, - 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, - 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, - 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xF6, - 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF4, 0xF4, - 0xF4, 0xF4, 0xF6, 0xF4, 0xE4, 0xE6, 0xEC, 0xEC, - 0x08, 0xF6, 0x08, 0xF4, 0x08, 0xF4, 0x0A, 0xE6, - 0x0A, 0xE4, 0x0A, 0xEE, 0x1A, 0xFE, 0x10, 0xF4, - 0xEE, 0xEE, 0xE8, 0xE8, 0xE8, 0xEC, 0xD8, 0xD8, - 0xDC, 0xDC, 0xD0, 0xD8, 0xB8, 0xB8, 0xA8, 0xA8, - 0x12, 0xCC, 0x14, 0xCC, 0x14, 0xDC, 0x20, 0xE8, - 0x24, 0x90, 0x28, 0xB8, 0x48, 0x30, 0x58, 0x30, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, - 0xFD, 0xFD, 0xF9, 0xF9, 0xFB, 0xFB, 0xF7, 0xF7, - 0x00, 0xFE, 0x00, 0xFE, 0x01, 0xFC, 0x01, 0xFC, - 0x02, 0xF9, 0x06, 0xF7, 0x04, 0xF2, 0x09, 0xE6, - 0x60, 0x60, 0x70, 0x70, 0xC0, 0xC0, 0xA0, 0xA0, - 0xC0, 0xC0, 0xC0, 0x40, 0x80, 0x80, 0x00, 0x00, - 0x80, 0xA0, 0x90, 0x40, 0x00, 0x40, 0x60, 0xC0, - 0x40, 0x80, 0xC0, 0x80, 0x80, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0xFE, 0xFC, 0xFC, 0xFB, 0xFB, 0xE6, 0xE6, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, - 0x01, 0xFD, 0x03, 0xF9, 0x04, 0xF1, 0x19, 0xD3, - 0xED, 0xEF, 0xCA, 0xCA, 0x98, 0x9C, 0x38, 0x38, - 0x78, 0x78, 0xF0, 0xF0, 0xE0, 0xE0, 0x40, 0xC0, - 0x13, 0xC6, 0x36, 0xBC, 0x64, 0x70, 0xC8, 0xE0, - 0x98, 0xC0, 0x30, 0x80, 0x60, 0x00, 0xC0, 0x00, - 0x6F, 0x6F, 0x6F, 0x6F, 0x2F, 0x2F, 0x37, 0x77, - 0x77, 0x77, 0x1B, 0x3B, 0x2D, 0x2D, 0x06, 0x16, - 0x10, 0x6F, 0x10, 0x27, 0x50, 0x27, 0x48, 0x6B, - 0x48, 0x13, 0x24, 0x39, 0x32, 0x08, 0x19, 0x0E, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x80, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF8, 0x07, 0x07, - 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xE4, 0xF8, 0xCB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xF9, 0xF9, - 0xE7, 0xE7, 0x1F, 0x1F, 0xF5, 0xF7, 0xF4, 0xDC, - 0x00, 0xFF, 0x00, 0xFE, 0x01, 0xF8, 0x06, 0xE4, - 0x18, 0x83, 0xE1, 0xBC, 0x0F, 0x78, 0x3C, 0xA0, - 0xCC, 0xCD, 0x38, 0x3E, 0x7C, 0x7C, 0xC8, 0xF8, - 0x60, 0x60, 0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x33, 0xAC, 0xC6, 0x9C, 0x8C, 0x60, 0x38, 0xE0, - 0xE0, 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x0B, 0x03, 0x07, 0x02, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0C, 0x07, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x80, 0xFF, 0xFF, 0xDF, 0x9F, 0x80, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xC3, 0x00, 0xFF, 0xE0, 0x6F, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFA, 0xF9, 0x01, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0x81, 0x00, 0xFF, 0x07, 0xC7, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0xE4, 0xEF, 0xCC, 0xFC, 0xC0, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x3F, 0x1F, 0xF8, 0xFC, 0x00, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x90, 0xF0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, - 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, - 0x0F, 0x16, 0x3F, 0x1D, 0x7F, 0xF0, 0xE9, 0xE7, - 0xD3, 0xCB, 0xE7, 0x93, 0xC6, 0x02, 0x87, 0x40, - 0x1F, 0x16, 0x3F, 0x1D, 0xFF, 0xF6, 0xFF, 0xEF, - 0xFB, 0xDB, 0xF7, 0xF3, 0xC7, 0x42, 0xC7, 0xC1, - 0x00, 0x00, 0x80, 0x00, 0x80, 0x40, 0xC0, 0x00, - 0xC0, 0x00, 0x80, 0x40, 0xB0, 0x01, 0x31, 0xE9, - 0x00, 0x00, 0x80, 0x00, 0xC0, 0x40, 0xC0, 0x40, - 0xC0, 0x40, 0xC0, 0xC0, 0xB1, 0x81, 0xF9, 0xE9, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x84, 0x85, 0xC6, 0xCE, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x84, 0x84, 0xC7, 0xC6, 0xCF, 0xC7, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0x41, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0xC1, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0xC3, 0xE3, 0xC7, 0xF7, 0x23, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x02, 0xC3, 0xC3, 0xE7, 0xC7, 0xF7, 0x63, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x80, 0x80, 0x40, 0xF0, 0x20, 0xF0, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x80, 0xC0, 0xC0, 0xF0, 0xA0, 0xF8, 0xF8, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x07, - 0x01, 0x1E, 0x11, 0x0E, 0x00, 0x0E, 0x00, 0x1F, - 0x00, 0x00, 0x01, 0x01, 0x00, 0x07, 0x00, 0x0F, - 0x10, 0x1F, 0x11, 0x1F, 0x00, 0x1F, 0x10, 0x1F, - 0x00, 0x00, 0x00, 0x10, 0x08, 0xF4, 0x00, 0xF8, - 0x24, 0x12, 0x98, 0x64, 0x00, 0x00, 0x80, 0x40, - 0x00, 0x00, 0x10, 0xF0, 0x04, 0xFC, 0x00, 0xFC, - 0x06, 0xFE, 0xFC, 0xFC, 0x00, 0x00, 0x40, 0xC0, - 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x1F, - 0x10, 0x0F, 0x00, 0x1F, 0x00, 0x7B, 0x4C, 0x33, - 0x00, 0x00, 0x07, 0x07, 0x00, 0x0F, 0x10, 0x1F, - 0x00, 0x1F, 0x00, 0x3F, 0x40, 0x7F, 0x04, 0x7F, - 0x00, 0x00, 0x00, 0x01, 0x82, 0x01, 0x00, 0x41, - 0x80, 0x41, 0x82, 0x41, 0x40, 0x83, 0x00, 0x87, - 0x00, 0x00, 0x01, 0x01, 0x82, 0x83, 0x40, 0xC3, - 0x40, 0xC3, 0x40, 0xC3, 0x40, 0xC3, 0x44, 0xC7, - 0x00, 0x00, 0x00, 0xF0, 0x10, 0xE0, 0x18, 0xE0, - 0x08, 0xF0, 0x00, 0xF0, 0x40, 0xB4, 0x49, 0xB4, - 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xF0, 0x08, 0xF8, - 0x08, 0xF8, 0x08, 0xF8, 0x04, 0xFD, 0x04, 0xFD, - 0x00, 0x00, 0x00, 0x3E, 0x40, 0x3F, 0x43, 0x3C, - 0x83, 0x7C, 0x80, 0x7D, 0x20, 0xDD, 0x02, 0xDD, - 0x00, 0x00, 0x3E, 0x3E, 0x41, 0x7F, 0x01, 0x7F, - 0x81, 0xFF, 0x01, 0xFF, 0x03, 0xFD, 0x03, 0xFF, - 0x00, 0x00, 0x00, 0x3F, 0x40, 0x3F, 0x00, 0x3F, - 0x07, 0x38, 0x47, 0x30, 0x00, 0x74, 0x08, 0xF0, - 0x00, 0x00, 0x3F, 0x3F, 0x40, 0x7F, 0x00, 0x7F, - 0x00, 0x7F, 0x07, 0x7F, 0x0C, 0x7C, 0x80, 0xFF, - 0x00, 0x00, 0x00, 0xE7, 0x24, 0xD3, 0x30, 0xC7, - 0xC0, 0x1F, 0xE8, 0x17, 0x01, 0x06, 0x49, 0x06, - 0x00, 0x00, 0xE7, 0xE7, 0x10, 0xF7, 0x10, 0xF7, - 0x18, 0xFF, 0xF8, 0xFF, 0x00, 0x0F, 0x40, 0xCF, - 0x00, 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x02, 0xFC, - 0xE0, 0x1F, 0x81, 0x6E, 0x83, 0x0C, 0x07, 0x18, - 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xFC, 0x00, 0xFE, - 0x01, 0xFF, 0xE1, 0xFF, 0x81, 0x9F, 0x03, 0xFF, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x18, 0x07, - 0x01, 0x3E, 0x21, 0x1C, 0x02, 0x39, 0x4C, 0x32, - 0x00, 0x00, 0x01, 0x01, 0x08, 0x0F, 0x10, 0x1F, - 0x20, 0x3F, 0x01, 0x3F, 0x03, 0x7F, 0x06, 0x7E, - 0x00, 0x00, 0x20, 0x10, 0x0C, 0xF0, 0x00, 0xFC, - 0xE1, 0x1E, 0x81, 0x6E, 0x09, 0x06, 0x00, 0x07, - 0x00, 0x00, 0x30, 0xF0, 0x04, 0xFC, 0x00, 0xFE, - 0x01, 0xFF, 0xE0, 0xFF, 0x00, 0x0F, 0x00, 0x0F, - 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF0, 0x04, 0x38, - 0x00, 0x78, 0x25, 0x9A, 0x00, 0xBD, 0x90, 0x0F, - 0x00, 0x00, 0xF8, 0xF8, 0x80, 0xF8, 0x04, 0x7C, - 0x40, 0x7C, 0x83, 0xBF, 0xA0, 0xBF, 0x80, 0x9F, - 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7E, 0x42, 0x38, - 0x04, 0x72, 0x18, 0xE4, 0x30, 0xC8, 0x60, 0x90, - 0x00, 0x00, 0x3E, 0x3E, 0x42, 0x7E, 0x06, 0x7E, - 0x06, 0xFE, 0x0C, 0xFC, 0x18, 0xF8, 0x30, 0xF0, - 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x10, 0x28, 0x1C, 0x3A, 0x0F, 0x1E, 0x07, 0x0B, - 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x38, 0x28, 0x3E, 0x3A, 0x1F, 0x1E, 0x0F, 0x0B, - 0x03, 0x00, 0x80, 0xC0, 0xC0, 0xE0, 0xF1, 0x68, - 0x79, 0x3D, 0x19, 0x2B, 0x1D, 0x03, 0xFD, 0xA1, - 0x83, 0x03, 0xC0, 0xC0, 0xE0, 0xE0, 0xF9, 0x68, - 0x7D, 0x7D, 0x3F, 0x2B, 0x1F, 0x07, 0xFD, 0xAD, - 0x63, 0x69, 0xF3, 0x4F, 0xD7, 0xC2, 0xE7, 0x9E, - 0x8F, 0xAC, 0xDE, 0x2D, 0xFE, 0x05, 0xEF, 0x95, - 0x7B, 0x69, 0xFF, 0x5F, 0xF7, 0xD2, 0xFF, 0xBF, - 0xEF, 0xAD, 0xFF, 0x6F, 0xFF, 0x47, 0xFF, 0x95, - 0xCD, 0x1C, 0x1E, 0x49, 0xBC, 0x5A, 0x34, 0xB1, - 0x79, 0x34, 0x79, 0xF3, 0xFB, 0xD7, 0xFF, 0xCE, - 0xDF, 0x5D, 0xDF, 0x4B, 0xFE, 0xDE, 0xBD, 0xB5, - 0x7D, 0x3C, 0xFB, 0xFB, 0xFF, 0xDF, 0xFF, 0xFE, - 0x67, 0xD2, 0xE7, 0x5E, 0xEF, 0x54, 0xEF, 0xDD, - 0xFF, 0x88, 0xFE, 0x98, 0xFC, 0x0A, 0xF7, 0x4C, - 0xF7, 0xD2, 0xFF, 0x5F, 0xFF, 0x55, 0xFF, 0xFD, - 0xFF, 0xAB, 0xFE, 0xFA, 0xFE, 0x5A, 0xFF, 0x5C, - 0xF3, 0x2A, 0xE7, 0x28, 0xF5, 0x4E, 0xFF, 0x80, - 0x69, 0x17, 0x15, 0xE9, 0x29, 0x51, 0xD1, 0x28, - 0xFB, 0xEA, 0xFF, 0xA9, 0xFF, 0x5F, 0xFF, 0xB2, - 0xFF, 0x77, 0xFD, 0xED, 0x79, 0x59, 0xF9, 0x38, - 0x78, 0x20, 0xF8, 0x60, 0xD0, 0xC0, 0xE0, 0x90, - 0xC8, 0xA0, 0xCC, 0x18, 0xD4, 0x18, 0xF8, 0x84, - 0xF8, 0x28, 0xF8, 0xF8, 0xF0, 0xD0, 0xF0, 0xB0, - 0xE8, 0xE0, 0xDC, 0x5C, 0xDC, 0x5C, 0xFC, 0x8C, - 0x00, 0x07, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, - 0x08, 0x00, 0x24, 0x58, 0x00, 0x7F, 0x10, 0x2F, - 0x00, 0x0F, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, - 0x08, 0x18, 0x40, 0x7F, 0x40, 0x7F, 0x20, 0x3F, - 0x20, 0xD0, 0x00, 0xF9, 0x81, 0x70, 0x0A, 0xF5, - 0x06, 0xF1, 0x80, 0x73, 0x10, 0xE7, 0x58, 0x87, - 0x10, 0xF0, 0x09, 0xF9, 0x00, 0xF9, 0x86, 0xFF, - 0x84, 0xFF, 0x08, 0xF7, 0x08, 0xFF, 0x10, 0xFF, - 0x0A, 0x75, 0x0A, 0xE1, 0x12, 0xE1, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0x81, 0xC0, 0x3F, 0x81, 0x40, - 0x0C, 0xFF, 0x08, 0xFB, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x3C, 0xFF, 0x7E, 0xFF, 0xC0, 0xC1, - 0x04, 0xA3, 0x40, 0xA3, 0x44, 0xA3, 0x24, 0xC3, - 0x20, 0xC7, 0x00, 0xDF, 0x21, 0xD6, 0x21, 0xD6, - 0x24, 0xE7, 0x20, 0xE7, 0x20, 0xE7, 0x20, 0xE7, - 0x20, 0xE7, 0x18, 0xFF, 0x10, 0xFF, 0x10, 0xFF, - 0x88, 0x35, 0xC4, 0x3B, 0x04, 0x7B, 0x20, 0x5F, - 0x80, 0x5E, 0xA3, 0x1C, 0x81, 0x3C, 0x86, 0x19, - 0x04, 0xFF, 0x44, 0xFF, 0x40, 0xFF, 0x40, 0xFF, - 0xC0, 0xFF, 0xA1, 0xBF, 0xA1, 0xBF, 0x83, 0x9F, - 0x46, 0x98, 0x66, 0x98, 0xE0, 0x1A, 0x80, 0x7A, - 0x84, 0x7A, 0x44, 0xB9, 0x0C, 0x30, 0x4C, 0x30, - 0x02, 0xFE, 0x42, 0xFE, 0x42, 0xFE, 0xC2, 0xFE, - 0xC6, 0xFE, 0xC5, 0xFD, 0x04, 0x7D, 0x04, 0x7D, - 0x80, 0x7F, 0x00, 0x7F, 0x9F, 0x60, 0x80, 0x68, - 0x00, 0xE8, 0x07, 0xF8, 0x00, 0xFF, 0x00, 0xFF, - 0x80, 0xFF, 0x00, 0xFF, 0x0F, 0xFF, 0x08, 0xF8, - 0x18, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x08, 0xA7, 0x00, 0xAF, 0xC1, 0x3E, 0x13, 0x0C, - 0x03, 0x0C, 0xD1, 0x0E, 0x10, 0xAF, 0x40, 0xBF, - 0x20, 0xEF, 0x20, 0xEF, 0xF1, 0xFF, 0x11, 0x1F, - 0x01, 0x1F, 0x40, 0xDF, 0x60, 0xFF, 0x60, 0xFF, - 0x0E, 0xF0, 0x00, 0xF8, 0xC2, 0x1C, 0x02, 0x1D, - 0x20, 0x1D, 0xC2, 0x3D, 0x02, 0xF8, 0x3C, 0xC2, - 0x06, 0xFE, 0x00, 0xFC, 0xC2, 0xFE, 0x01, 0x1F, - 0x21, 0x3F, 0x03, 0xFF, 0x02, 0xFE, 0x0E, 0xFE, - 0x4C, 0x30, 0x00, 0xF4, 0x00, 0xF4, 0x08, 0x74, - 0x40, 0x38, 0x01, 0x7E, 0x00, 0x1F, 0x18, 0x07, - 0x04, 0x7C, 0x84, 0xFC, 0x84, 0xFC, 0x04, 0x7C, - 0x00, 0x7C, 0x40, 0x7F, 0x00, 0x3F, 0x10, 0x1F, - 0x00, 0x07, 0x09, 0x06, 0x01, 0x1E, 0x00, 0x0E, - 0x03, 0x5C, 0x82, 0x7D, 0x0E, 0xF0, 0x2C, 0xC0, - 0x00, 0x0F, 0x00, 0x0F, 0x10, 0x1F, 0x01, 0x1F, - 0x41, 0x7F, 0x03, 0xFF, 0x06, 0xFE, 0x0C, 0xFC, - 0x90, 0x0F, 0x80, 0x0F, 0x89, 0x06, 0x00, 0x8E, - 0x00, 0x9E, 0x11, 0x0E, 0x01, 0x0E, 0x13, 0x0C, - 0x90, 0x9F, 0x80, 0x8F, 0x80, 0x8F, 0x80, 0x8F, - 0x91, 0x9F, 0x11, 0x1F, 0x01, 0x1F, 0x01, 0x1F, - 0x60, 0x80, 0x40, 0x00, 0x80, 0x40, 0x00, 0x80, - 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x60, 0xE0, 0xC0, 0x40, 0xC0, 0xC0, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0x05, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFD, 0x3D, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x10, 0x01, 0x63, 0x01, 0x03, 0x03, 0x01, - 0x03, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, - 0xF7, 0x74, 0x63, 0x63, 0x03, 0x03, 0x03, 0x01, - 0x03, 0x00, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, - 0xE7, 0x88, 0xC0, 0xA3, 0xC0, 0x00, 0x80, 0x40, - 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, - 0xEF, 0xA8, 0xE3, 0xE3, 0xC0, 0x40, 0xC0, 0xC0, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, - 0xA1, 0x54, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF7, 0xF5, 0xC1, 0xC1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x40, 0xA0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xE0, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xD0, 0x28, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0xB8, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x1F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x10, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF7, 0x77, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF3, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF7, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0E, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x8E, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xCF, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0E, 0x0F, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x70, 0x08, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0xF8, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; + +#include "sgb_border.inc" memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); memcpy(gb->sgb->border.palette, palette, sizeof(palette)); diff --git a/Core/sgb_border.inc b/Core/sgb_border.inc new file mode 100644 index 00000000..9ddd12be --- /dev/null +++ b/Core/sgb_border.inc @@ -0,0 +1,671 @@ +static const uint16_t palette[] = { + 0x5273, 0x14A5, 0x043C, 0x2529, 0x35AD, 0x2954, 0x739C, 0x5AD5, + 0x41A8, 0x3D44, 0x4100, 0x20E7, 0x358C, 0x3DCE, 0x4A31, 0x1C75}; +static const uint16_t tilemap[] = { + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1001, 0x1002, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, + 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, + 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, + 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x5002, 0x5001, 0x1000, + 0x1000, 0x1004, 0x1005, 0x1005, 0x1005, 0x1006, 0x1007, 0x1008, + 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, + 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, + 0x1019, 0x101A, 0x101B, 0x1005, 0x1005, 0x1005, 0x5004, 0x1000, + 0x1000, 0x101C, 0x101D, 0x101D, 0x101D, 0x101E, 0x101F, 0x1020, + 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, + 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, + 0x1031, 0x1032, 0x1033, 0x101D, 0x101D, 0x101D, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1036, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1037, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1038, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1039, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x103A, 0x1000, + 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x103B, 0x103C, 0x1000, + 0x1000, 0x5038, 0x1034, 0x1034, 0x1034, 0x103D, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x503D, 0x1034, 0x103E, 0x103F, 0x1000, 0x1000, + 0x1000, 0x1040, 0x1041, 0x1034, 0x1034, 0x1042, 0x1043, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1049, 0x104A, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x104B, 0x104C, 0x104D, 0x104E, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x104F, 0x1050, + 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, + 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, 0x1060, + 0x1061, 0x1062, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1063, 0x1064, + 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, + 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, + 0x1075, 0x1076, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1077, 0x1078, + 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, 0x1080, + 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, 0x1088, + 0x1089, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, +}; +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x05, 0x01, 0x0F, 0x07, + 0x1E, 0x0E, 0x1D, 0x3D, 0x1B, 0x1B, 0x37, 0x77, + 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x08, 0x0B, + 0x11, 0x16, 0x22, 0x28, 0x24, 0x39, 0x48, 0x73, + 0x3F, 0xC0, 0x7F, 0x7F, 0xFF, 0xFF, 0x80, 0x80, + 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x9F, 0x00, 0xFF, 0x7F, 0xC3, + 0x80, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x37, 0x37, 0x2F, 0x2F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x48, 0x2B, 0x50, 0x67, 0x10, 0x27, 0x10, 0x6F, + 0x10, 0x6F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, + 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, + 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF8, 0x0C, 0xFF, + 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3B, 0x3F, 0xD3, 0x53, + 0xCF, 0xAF, 0xD7, 0xAF, 0xCF, 0xAF, 0xD3, 0x53, + 0x00, 0xFF, 0x00, 0xFF, 0x47, 0x74, 0xE0, 0xB1, + 0x78, 0xE3, 0x78, 0xFF, 0x78, 0xE3, 0xE0, 0xB1, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0x19, 0x39, + 0xF9, 0xF9, 0xE9, 0xE9, 0xF9, 0xF9, 0x19, 0x39, + 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x70, 0xA4, 0xBD, + 0x24, 0x8D, 0x34, 0xDD, 0x24, 0x8D, 0xA4, 0xBD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB5, 0xF5, 0xF5, + 0xF1, 0xF2, 0xF2, 0xF1, 0xF3, 0xF3, 0xF3, 0xF2, + 0x00, 0xFF, 0x00, 0xFF, 0xCE, 0x7B, 0x0C, 0xF9, + 0x0B, 0xFB, 0x0B, 0xFB, 0x09, 0xFA, 0x09, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xDE, 0xD7, + 0x8F, 0x97, 0xAF, 0xD4, 0xEF, 0x77, 0xED, 0xB3, + 0x00, 0xFF, 0x00, 0xFF, 0x38, 0xEE, 0x19, 0xCF, + 0x59, 0xDC, 0x7B, 0xFF, 0xDB, 0x9C, 0xDE, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xBF, 0x3D, 0x7B, + 0x9D, 0x9B, 0xDD, 0xBB, 0xFD, 0xFB, 0xDD, 0xEB, + 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x30, 0x46, 0x7F, + 0x26, 0x3F, 0x66, 0xFF, 0xE6, 0x0F, 0x36, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xBA, 0x9A, 0x98, + 0x9A, 0x98, 0xBA, 0xBE, 0x9A, 0x9A, 0x9A, 0x92, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0x41, 0x43, 0xDB, + 0x43, 0xDB, 0x7D, 0xC5, 0x4D, 0xC7, 0x49, 0xD9, + 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0x4F, 0x75, 0x71, + 0x75, 0x65, 0x7B, 0x7F, 0x6B, 0x71, 0x4A, 0x4A, + 0x00, 0xFF, 0x00, 0xFF, 0x13, 0x1F, 0x1C, 0x4C, + 0x16, 0x73, 0x0C, 0x67, 0x1A, 0x7B, 0x27, 0x45, + 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0xDD, 0xE7, 0xE5, + 0xFD, 0xE6, 0xE7, 0xE7, 0xFD, 0xFB, 0xF7, 0xFD, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0xE6, 0x0B, 0xE6, + 0x1B, 0xFF, 0x12, 0xE1, 0x0E, 0xF7, 0x0E, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x65, 0x7B, 0xFD, + 0xEB, 0xDD, 0x1B, 0x2D, 0xFB, 0x3D, 0x5B, 0x5D, + 0x00, 0xFF, 0x00, 0xFF, 0x8F, 0x6E, 0x96, 0xC7, + 0xB6, 0x7F, 0x76, 0x7F, 0xE6, 0xCF, 0xC6, 0x9F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFB, 0xBD, 0xDB, + 0xBD, 0xDB, 0xBF, 0xDB, 0xBD, 0xDB, 0xBD, 0xDB, + 0x00, 0xFF, 0x00, 0xFF, 0xFE, 0x0F, 0x66, 0xFF, + 0x66, 0xFF, 0x67, 0xFC, 0x66, 0xFF, 0x66, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCE, 0xCF, 0xEF, 0xEF, 0xCF, 0xCF, 0xCE, 0xCF, + 0x00, 0xFF, 0x00, 0xFF, 0x11, 0xDF, 0x10, 0xDE, + 0x11, 0xDF, 0xF0, 0x1F, 0x10, 0xDF, 0x11, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x6F, 0xFE, 0x4D, + 0xFE, 0x7D, 0x9E, 0x7D, 0xCE, 0xCD, 0xFE, 0x5D, + 0x00, 0xFF, 0x00, 0xFF, 0x7F, 0x18, 0xB3, 0xFF, + 0x83, 0xFF, 0xE3, 0xEF, 0x23, 0xCF, 0xB3, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xDE, 0xCF, 0xCF, + 0xCF, 0xCF, 0xDE, 0xDE, 0xCF, 0xCF, 0xCF, 0xCF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x21, 0x20, 0xEF, + 0x20, 0xEF, 0x3F, 0xE1, 0x20, 0xEF, 0x20, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x75, 0x75, 0x34, 0x30, + 0x34, 0x30, 0x75, 0x7D, 0x34, 0x34, 0x34, 0x24, + 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x82, 0x86, 0xB6, + 0x86, 0xB6, 0xFB, 0x8A, 0x9A, 0x8E, 0x92, 0xB2, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xEB, 0xFD, 0xFF, + 0xFB, 0xF7, 0xE7, 0xEB, 0xFB, 0xF7, 0xFD, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xF7, 0x1E, 0x06, 0xF3, + 0x0C, 0xFF, 0xFC, 0x1F, 0x0C, 0xFF, 0x06, 0xF3, + 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x9F, 0xFF, 0xAE, + 0xEF, 0xCE, 0xDF, 0xDF, 0xEF, 0xCF, 0xFF, 0xAE, + 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0x3E, 0x71, 0xDF, + 0x21, 0xEF, 0x30, 0xEF, 0x20, 0xEF, 0x71, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x6C, 0x5B, 0x4A, + 0x7B, 0xF9, 0x8D, 0x6D, 0xFB, 0xF9, 0x53, 0xD2, + 0x00, 0xFF, 0x00, 0xFF, 0x72, 0x1E, 0x37, 0x79, + 0x82, 0xFB, 0xF6, 0xFB, 0x12, 0xC3, 0xBF, 0xE9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, 0xC9, 0xDD, 0xF9, + 0xED, 0xF1, 0xF5, 0xE9, 0xEB, 0xF3, 0xDF, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0xD4, 0x1D, 0x34, 0xE5, + 0x1C, 0xFD, 0x1C, 0xFD, 0x1E, 0xFD, 0x33, 0xE1, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xC5, 0xD7, 0xC7, + 0xD2, 0xC0, 0xD2, 0xC3, 0xC3, 0xC3, 0xF3, 0xB3, + 0x00, 0xFF, 0x00, 0xFF, 0x1E, 0xDB, 0x1E, 0xD8, + 0x1A, 0xDA, 0x19, 0xDB, 0x18, 0xCA, 0x68, 0xCB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, + 0xF7, 0xFB, 0xF7, 0x7B, 0xB7, 0xBB, 0x77, 0x7B, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0x98, 0x2C, 0x9F, + 0x2C, 0x9F, 0xAC, 0x9F, 0xEC, 0x1F, 0xEC, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0xDF, 0x5F, + 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0x5F, + 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x7F, 0xE0, 0xBF, + 0x60, 0xFF, 0x60, 0xFF, 0x60, 0xFF, 0xE0, 0xBF, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, + 0x3B, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x47, 0x74, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xF9, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xC4, 0x7D, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xF3, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x08, 0xFA, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xE9, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x9A, 0x39, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xCD, 0xCB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x26, 0xC7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x98, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x47, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x2E, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x72, 0x5E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x0C, 0x79, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xFB, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x66, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xBD, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x66, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xCF, 0xCE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x11, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xEE, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xF3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xDE, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x3F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x31, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x8F, 0xBE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xE9, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xF7, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xBF, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xA1, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x7E, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x72, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xCF, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xD3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xF3, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xE8, 0x3B, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x6F, 0xD8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, + 0x40, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, + 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, + 0x3C, 0xFF, 0x00, 0x81, 0x81, 0x81, 0x00, 0x00, + 0x00, 0xFF, 0x81, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x81, 0x81, 0x66, 0xE7, 0x3C, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, + 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF4, 0xF4, + 0xF4, 0xF4, 0xF6, 0xF4, 0xE4, 0xE6, 0xEC, 0xEC, + 0x08, 0xF6, 0x08, 0xF4, 0x08, 0xF4, 0x0A, 0xE6, + 0x0A, 0xE4, 0x0A, 0xEE, 0x1A, 0xFE, 0x10, 0xF4, + 0xEE, 0xEE, 0xE8, 0xE8, 0xE8, 0xEC, 0xD8, 0xD8, + 0xDC, 0xDC, 0xD0, 0xD8, 0xB8, 0xB8, 0xA8, 0xA8, + 0x12, 0xCC, 0x14, 0xCC, 0x14, 0xDC, 0x20, 0xE8, + 0x24, 0x90, 0x28, 0xB8, 0x48, 0x30, 0x58, 0x30, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFD, 0xFD, 0xF9, 0xF9, 0xFB, 0xFB, 0xF7, 0xF7, + 0x00, 0xFE, 0x00, 0xFE, 0x01, 0xFC, 0x01, 0xFC, + 0x02, 0xF9, 0x06, 0xF7, 0x04, 0xF2, 0x09, 0xE6, + 0x60, 0x60, 0x70, 0x70, 0xC0, 0xC0, 0xA0, 0xA0, + 0xC0, 0xC0, 0xC0, 0x40, 0x80, 0x80, 0x00, 0x00, + 0x80, 0xA0, 0x90, 0x40, 0x00, 0x40, 0x60, 0xC0, + 0x40, 0x80, 0xC0, 0x80, 0x80, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFE, 0xFC, 0xFC, 0xFB, 0xFB, 0xE6, 0xE6, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, + 0x01, 0xFD, 0x03, 0xF9, 0x04, 0xF1, 0x19, 0xD3, + 0xED, 0xEF, 0xCA, 0xCA, 0x98, 0x9C, 0x38, 0x38, + 0x78, 0x78, 0xF0, 0xF0, 0xE0, 0xE0, 0x40, 0xC0, + 0x13, 0xC6, 0x36, 0xBC, 0x64, 0x70, 0xC8, 0xE0, + 0x98, 0xC0, 0x30, 0x80, 0x60, 0x00, 0xC0, 0x00, + 0x6F, 0x6F, 0x6F, 0x6F, 0x2F, 0x2F, 0x37, 0x77, + 0x77, 0x77, 0x1B, 0x3B, 0x2D, 0x2D, 0x06, 0x16, + 0x10, 0x6F, 0x10, 0x27, 0x50, 0x27, 0x48, 0x6B, + 0x48, 0x13, 0x24, 0x39, 0x32, 0x08, 0x19, 0x0E, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x80, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF8, 0x07, 0x07, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xE4, 0xF8, 0xCB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xF9, 0xF9, + 0xE7, 0xE7, 0x1F, 0x1F, 0xF5, 0xF7, 0xF4, 0xDC, + 0x00, 0xFF, 0x00, 0xFE, 0x01, 0xF8, 0x06, 0xE4, + 0x18, 0x83, 0xE1, 0xBC, 0x0F, 0x78, 0x3C, 0xA0, + 0xCC, 0xCD, 0x38, 0x3E, 0x7C, 0x7C, 0xC8, 0xF8, + 0x60, 0x60, 0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x33, 0xAC, 0xC6, 0x9C, 0x8C, 0x60, 0x38, 0xE0, + 0xE0, 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x0B, 0x03, 0x07, 0x02, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x07, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0xFF, 0xFF, 0xDF, 0x9F, 0x80, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0xC3, 0x00, 0xFF, 0xE0, 0x6F, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xFA, 0xF9, 0x01, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x81, 0x00, 0xFF, 0x07, 0xC7, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xE4, 0xEF, 0xCC, 0xFC, 0xC0, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x3F, 0x1F, 0xF8, 0xFC, 0x00, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0xF0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, + 0x0F, 0x16, 0x3F, 0x1D, 0x7F, 0xF0, 0xE9, 0xE7, + 0xD3, 0xCB, 0xE7, 0x93, 0xC6, 0x02, 0x87, 0x40, + 0x1F, 0x16, 0x3F, 0x1D, 0xFF, 0xF6, 0xFF, 0xEF, + 0xFB, 0xDB, 0xF7, 0xF3, 0xC7, 0x42, 0xC7, 0xC1, + 0x00, 0x00, 0x80, 0x00, 0x80, 0x40, 0xC0, 0x00, + 0xC0, 0x00, 0x80, 0x40, 0xB0, 0x01, 0x31, 0xE9, + 0x00, 0x00, 0x80, 0x00, 0xC0, 0x40, 0xC0, 0x40, + 0xC0, 0x40, 0xC0, 0xC0, 0xB1, 0x81, 0xF9, 0xE9, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x84, 0x85, 0xC6, 0xCE, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x84, 0x84, 0xC7, 0xC6, 0xCF, 0xC7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0xC1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0xC3, 0xE3, 0xC7, 0xF7, 0x23, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x02, 0xC3, 0xC3, 0xE7, 0xC7, 0xF7, 0x63, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x40, 0xF0, 0x20, 0xF0, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0xC0, 0xC0, 0xF0, 0xA0, 0xF8, 0xF8, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x07, + 0x01, 0x1E, 0x11, 0x0E, 0x00, 0x0E, 0x00, 0x1F, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x07, 0x00, 0x0F, + 0x10, 0x1F, 0x11, 0x1F, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x10, 0x08, 0xF4, 0x00, 0xF8, + 0x24, 0x12, 0x98, 0x64, 0x00, 0x00, 0x80, 0x40, + 0x00, 0x00, 0x10, 0xF0, 0x04, 0xFC, 0x00, 0xFC, + 0x06, 0xFE, 0xFC, 0xFC, 0x00, 0x00, 0x40, 0xC0, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x1F, + 0x10, 0x0F, 0x00, 0x1F, 0x00, 0x7B, 0x4C, 0x33, + 0x00, 0x00, 0x07, 0x07, 0x00, 0x0F, 0x10, 0x1F, + 0x00, 0x1F, 0x00, 0x3F, 0x40, 0x7F, 0x04, 0x7F, + 0x00, 0x00, 0x00, 0x01, 0x82, 0x01, 0x00, 0x41, + 0x80, 0x41, 0x82, 0x41, 0x40, 0x83, 0x00, 0x87, + 0x00, 0x00, 0x01, 0x01, 0x82, 0x83, 0x40, 0xC3, + 0x40, 0xC3, 0x40, 0xC3, 0x40, 0xC3, 0x44, 0xC7, + 0x00, 0x00, 0x00, 0xF0, 0x10, 0xE0, 0x18, 0xE0, + 0x08, 0xF0, 0x00, 0xF0, 0x40, 0xB4, 0x49, 0xB4, + 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xF0, 0x08, 0xF8, + 0x08, 0xF8, 0x08, 0xF8, 0x04, 0xFD, 0x04, 0xFD, + 0x00, 0x00, 0x00, 0x3E, 0x40, 0x3F, 0x43, 0x3C, + 0x83, 0x7C, 0x80, 0x7D, 0x20, 0xDD, 0x02, 0xDD, + 0x00, 0x00, 0x3E, 0x3E, 0x41, 0x7F, 0x01, 0x7F, + 0x81, 0xFF, 0x01, 0xFF, 0x03, 0xFD, 0x03, 0xFF, + 0x00, 0x00, 0x00, 0x3F, 0x40, 0x3F, 0x00, 0x3F, + 0x07, 0x38, 0x47, 0x30, 0x00, 0x74, 0x08, 0xF0, + 0x00, 0x00, 0x3F, 0x3F, 0x40, 0x7F, 0x00, 0x7F, + 0x00, 0x7F, 0x07, 0x7F, 0x0C, 0x7C, 0x80, 0xFF, + 0x00, 0x00, 0x00, 0xE7, 0x24, 0xD3, 0x30, 0xC7, + 0xC0, 0x1F, 0xE8, 0x17, 0x01, 0x06, 0x49, 0x06, + 0x00, 0x00, 0xE7, 0xE7, 0x10, 0xF7, 0x10, 0xF7, + 0x18, 0xFF, 0xF8, 0xFF, 0x00, 0x0F, 0x40, 0xCF, + 0x00, 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x02, 0xFC, + 0xE0, 0x1F, 0x81, 0x6E, 0x83, 0x0C, 0x07, 0x18, + 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xFC, 0x00, 0xFE, + 0x01, 0xFF, 0xE1, 0xFF, 0x81, 0x9F, 0x03, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x18, 0x07, + 0x01, 0x3E, 0x21, 0x1C, 0x02, 0x39, 0x4C, 0x32, + 0x00, 0x00, 0x01, 0x01, 0x08, 0x0F, 0x10, 0x1F, + 0x20, 0x3F, 0x01, 0x3F, 0x03, 0x7F, 0x06, 0x7E, + 0x00, 0x00, 0x20, 0x10, 0x0C, 0xF0, 0x00, 0xFC, + 0xE1, 0x1E, 0x81, 0x6E, 0x09, 0x06, 0x00, 0x07, + 0x00, 0x00, 0x30, 0xF0, 0x04, 0xFC, 0x00, 0xFE, + 0x01, 0xFF, 0xE0, 0xFF, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF0, 0x04, 0x38, + 0x00, 0x78, 0x25, 0x9A, 0x00, 0xBD, 0x90, 0x0F, + 0x00, 0x00, 0xF8, 0xF8, 0x80, 0xF8, 0x04, 0x7C, + 0x40, 0x7C, 0x83, 0xBF, 0xA0, 0xBF, 0x80, 0x9F, + 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7E, 0x42, 0x38, + 0x04, 0x72, 0x18, 0xE4, 0x30, 0xC8, 0x60, 0x90, + 0x00, 0x00, 0x3E, 0x3E, 0x42, 0x7E, 0x06, 0x7E, + 0x06, 0xFE, 0x0C, 0xFC, 0x18, 0xF8, 0x30, 0xF0, + 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x28, 0x1C, 0x3A, 0x0F, 0x1E, 0x07, 0x0B, + 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x38, 0x28, 0x3E, 0x3A, 0x1F, 0x1E, 0x0F, 0x0B, + 0x03, 0x00, 0x80, 0xC0, 0xC0, 0xE0, 0xF1, 0x68, + 0x79, 0x3D, 0x19, 0x2B, 0x1D, 0x03, 0xFD, 0xA1, + 0x83, 0x03, 0xC0, 0xC0, 0xE0, 0xE0, 0xF9, 0x68, + 0x7D, 0x7D, 0x3F, 0x2B, 0x1F, 0x07, 0xFD, 0xAD, + 0x63, 0x69, 0xF3, 0x4F, 0xD7, 0xC2, 0xE7, 0x9E, + 0x8F, 0xAC, 0xDE, 0x2D, 0xFE, 0x05, 0xEF, 0x95, + 0x7B, 0x69, 0xFF, 0x5F, 0xF7, 0xD2, 0xFF, 0xBF, + 0xEF, 0xAD, 0xFF, 0x6F, 0xFF, 0x47, 0xFF, 0x95, + 0xCD, 0x1C, 0x1E, 0x49, 0xBC, 0x5A, 0x34, 0xB1, + 0x79, 0x34, 0x79, 0xF3, 0xFB, 0xD7, 0xFF, 0xCE, + 0xDF, 0x5D, 0xDF, 0x4B, 0xFE, 0xDE, 0xBD, 0xB5, + 0x7D, 0x3C, 0xFB, 0xFB, 0xFF, 0xDF, 0xFF, 0xFE, + 0x67, 0xD2, 0xE7, 0x5E, 0xEF, 0x54, 0xEF, 0xDD, + 0xFF, 0x88, 0xFE, 0x98, 0xFC, 0x0A, 0xF7, 0x4C, + 0xF7, 0xD2, 0xFF, 0x5F, 0xFF, 0x55, 0xFF, 0xFD, + 0xFF, 0xAB, 0xFE, 0xFA, 0xFE, 0x5A, 0xFF, 0x5C, + 0xF3, 0x2A, 0xE7, 0x28, 0xF5, 0x4E, 0xFF, 0x80, + 0x69, 0x17, 0x15, 0xE9, 0x29, 0x51, 0xD1, 0x28, + 0xFB, 0xEA, 0xFF, 0xA9, 0xFF, 0x5F, 0xFF, 0xB2, + 0xFF, 0x77, 0xFD, 0xED, 0x79, 0x59, 0xF9, 0x38, + 0x78, 0x20, 0xF8, 0x60, 0xD0, 0xC0, 0xE0, 0x90, + 0xC8, 0xA0, 0xCC, 0x18, 0xD4, 0x18, 0xF8, 0x84, + 0xF8, 0x28, 0xF8, 0xF8, 0xF0, 0xD0, 0xF0, 0xB0, + 0xE8, 0xE0, 0xDC, 0x5C, 0xDC, 0x5C, 0xFC, 0x8C, + 0x00, 0x07, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x08, 0x00, 0x24, 0x58, 0x00, 0x7F, 0x10, 0x2F, + 0x00, 0x0F, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, + 0x08, 0x18, 0x40, 0x7F, 0x40, 0x7F, 0x20, 0x3F, + 0x20, 0xD0, 0x00, 0xF9, 0x81, 0x70, 0x0A, 0xF5, + 0x06, 0xF1, 0x80, 0x73, 0x10, 0xE7, 0x58, 0x87, + 0x10, 0xF0, 0x09, 0xF9, 0x00, 0xF9, 0x86, 0xFF, + 0x84, 0xFF, 0x08, 0xF7, 0x08, 0xFF, 0x10, 0xFF, + 0x0A, 0x75, 0x0A, 0xE1, 0x12, 0xE1, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x81, 0xC0, 0x3F, 0x81, 0x40, + 0x0C, 0xFF, 0x08, 0xFB, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x3C, 0xFF, 0x7E, 0xFF, 0xC0, 0xC1, + 0x04, 0xA3, 0x40, 0xA3, 0x44, 0xA3, 0x24, 0xC3, + 0x20, 0xC7, 0x00, 0xDF, 0x21, 0xD6, 0x21, 0xD6, + 0x24, 0xE7, 0x20, 0xE7, 0x20, 0xE7, 0x20, 0xE7, + 0x20, 0xE7, 0x18, 0xFF, 0x10, 0xFF, 0x10, 0xFF, + 0x88, 0x35, 0xC4, 0x3B, 0x04, 0x7B, 0x20, 0x5F, + 0x80, 0x5E, 0xA3, 0x1C, 0x81, 0x3C, 0x86, 0x19, + 0x04, 0xFF, 0x44, 0xFF, 0x40, 0xFF, 0x40, 0xFF, + 0xC0, 0xFF, 0xA1, 0xBF, 0xA1, 0xBF, 0x83, 0x9F, + 0x46, 0x98, 0x66, 0x98, 0xE0, 0x1A, 0x80, 0x7A, + 0x84, 0x7A, 0x44, 0xB9, 0x0C, 0x30, 0x4C, 0x30, + 0x02, 0xFE, 0x42, 0xFE, 0x42, 0xFE, 0xC2, 0xFE, + 0xC6, 0xFE, 0xC5, 0xFD, 0x04, 0x7D, 0x04, 0x7D, + 0x80, 0x7F, 0x00, 0x7F, 0x9F, 0x60, 0x80, 0x68, + 0x00, 0xE8, 0x07, 0xF8, 0x00, 0xFF, 0x00, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x0F, 0xFF, 0x08, 0xF8, + 0x18, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x08, 0xA7, 0x00, 0xAF, 0xC1, 0x3E, 0x13, 0x0C, + 0x03, 0x0C, 0xD1, 0x0E, 0x10, 0xAF, 0x40, 0xBF, + 0x20, 0xEF, 0x20, 0xEF, 0xF1, 0xFF, 0x11, 0x1F, + 0x01, 0x1F, 0x40, 0xDF, 0x60, 0xFF, 0x60, 0xFF, + 0x0E, 0xF0, 0x00, 0xF8, 0xC2, 0x1C, 0x02, 0x1D, + 0x20, 0x1D, 0xC2, 0x3D, 0x02, 0xF8, 0x3C, 0xC2, + 0x06, 0xFE, 0x00, 0xFC, 0xC2, 0xFE, 0x01, 0x1F, + 0x21, 0x3F, 0x03, 0xFF, 0x02, 0xFE, 0x0E, 0xFE, + 0x4C, 0x30, 0x00, 0xF4, 0x00, 0xF4, 0x08, 0x74, + 0x40, 0x38, 0x01, 0x7E, 0x00, 0x1F, 0x18, 0x07, + 0x04, 0x7C, 0x84, 0xFC, 0x84, 0xFC, 0x04, 0x7C, + 0x00, 0x7C, 0x40, 0x7F, 0x00, 0x3F, 0x10, 0x1F, + 0x00, 0x07, 0x09, 0x06, 0x01, 0x1E, 0x00, 0x0E, + 0x03, 0x5C, 0x82, 0x7D, 0x0E, 0xF0, 0x2C, 0xC0, + 0x00, 0x0F, 0x00, 0x0F, 0x10, 0x1F, 0x01, 0x1F, + 0x41, 0x7F, 0x03, 0xFF, 0x06, 0xFE, 0x0C, 0xFC, + 0x90, 0x0F, 0x80, 0x0F, 0x89, 0x06, 0x00, 0x8E, + 0x00, 0x9E, 0x11, 0x0E, 0x01, 0x0E, 0x13, 0x0C, + 0x90, 0x9F, 0x80, 0x8F, 0x80, 0x8F, 0x80, 0x8F, + 0x91, 0x9F, 0x11, 0x1F, 0x01, 0x1F, 0x01, 0x1F, + 0x60, 0x80, 0x40, 0x00, 0x80, 0x40, 0x00, 0x80, + 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x60, 0xE0, 0xC0, 0x40, 0xC0, 0xC0, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0x05, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0x3D, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x10, 0x01, 0x63, 0x01, 0x03, 0x03, 0x01, + 0x03, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, + 0xF7, 0x74, 0x63, 0x63, 0x03, 0x03, 0x03, 0x01, + 0x03, 0x00, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, + 0xE7, 0x88, 0xC0, 0xA3, 0xC0, 0x00, 0x80, 0x40, + 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, + 0xEF, 0xA8, 0xE3, 0xE3, 0xC0, 0x40, 0xC0, 0xC0, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, + 0xA1, 0x54, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0xF5, 0xC1, 0xC1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xA0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE0, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xD0, 0x28, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xB8, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x1F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x10, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x77, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8E, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xCF, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x0F, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x08, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xF8, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; From 91a3091b2e8a2b4218c1247624438fde0e9dfa88 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 17:41:23 +0200 Subject: [PATCH 0766/1216] Build fix --- Core/sgb.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/sgb.h b/Core/sgb.h index de188413..dd771572 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -4,8 +4,9 @@ #include #include +typedef struct GB_sgb_s GB_sgb_t; #ifdef GB_INTERNAL -typedef struct { +struct GB_sgb_s { uint8_t command[16 * 7]; uint16_t command_write_index; bool ready_for_pulse; @@ -38,7 +39,7 @@ typedef struct { } border, pending_border; uint8_t border_animation; -} GB_sgb_t; +}; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); From d42cbcdee272e9b68f9749f62851e3c017f88d03 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 19:11:21 +0200 Subject: [PATCH 0767/1216] Palette transfer functions --- Core/gb.c | 2 +- Core/sgb.c | 100 ++++++++++++++++++++++++++++++++++++++++++++--------- Core/sgb.h | 13 ++++--- 3 files changed, 93 insertions(+), 22 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index d1846642..0d19fc83 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -650,7 +650,7 @@ void GB_reset(GB_gameboy_t *gb) } memset(gb->sgb, 0, sizeof(*gb->sgb)); gb->sgb->player_count = 1; - GB_sgb_load_default_border(gb); + GB_sgb_load_default_data(gb); } else { diff --git a/Core/sgb.c b/Core/sgb.c index 548e9346..458fd28b 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,10 +1,17 @@ #include "gb.h" enum { - MLT_REQ = 0x11, - CHR_TRN = 0x13, - PCT_TRN = 0x14, - MASK_EN = 0x17, + PAL01 = 0x00, + PAL23 = 0x01, + PAL03 = 0x02, + PAL12 = 0x03, + PAL_SET = 0x0A, + PAL_TRN = 0x0B, + DATA_SND = 0x0f, + MLT_REQ = 0x11, + CHR_TRN = 0x13, + PCT_TRN = 0x14, + MASK_EN = 0x17, }; typedef enum { @@ -15,6 +22,20 @@ typedef enum { } mask_mode_t; #define SGB_PACKET_SIZE 16 +static inline void pal_command(GB_gameboy_t *gb, unsigned first, unsigned second) +{ + gb->sgb->effective_palettes[0] = gb->sgb->effective_palettes[4] = + gb->sgb->effective_palettes[8] = gb->sgb->effective_palettes[12] = + gb->sgb->command[1] | (gb->sgb->command[2] << 8); + + for (unsigned i = 0; i < 3; i++) { + gb->sgb->effective_palettes[first * 4 + i + 1] = gb->sgb->command[3 + i * 2] | (gb->sgb->command[4 + i * 2] << 8); + } + + for (unsigned i = 0; i < 3; i++) { + gb->sgb->effective_palettes[second * 4 + i + 1] = gb->sgb->command[9 + i * 2] | (gb->sgb->command[10 + i * 2] << 8); + } +} static void command_ready(GB_gameboy_t *gb) { @@ -50,8 +71,46 @@ static void command_ready(GB_gameboy_t *gb) return; } - switch (gb->sgb->command[0] >> 3) { + case PAL01: + pal_command(gb, 0, 1); + break; + case PAL23: + pal_command(gb, 2, 3); + break; + case PAL03: + pal_command(gb, 0, 3); + break; + case PAL12: + pal_command(gb, 1, 2); + break; + case PAL_SET: + memcpy(&gb->sgb->effective_palettes[0], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[1] + (gb->sgb->command[2] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[4], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[3] + (gb->sgb->command[4] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[8], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[5] + (gb->sgb->command[6] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[12], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[7] + (gb->sgb->command[8] & 1) * 0x100)], + 8); + + if (gb->sgb->command[9] & 0x40) { + gb->sgb->mask_mode = MASK_DISABLED; + } + break; + case PAL_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->tile_transfer = false; + gb->sgb->data_transfer = true; + gb->sgb->palette_transfer = true; + break; + case DATA_SND: + // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this + break; case MLT_REQ: gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; gb->sgb->current_player = gb->sgb->player_count - 1; @@ -69,6 +128,7 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb->vram_transfer_countdown = 2; gb->sgb->tile_transfer = false; gb->sgb->data_transfer = true; + gb->sgb->palette_transfer = false; break; default: GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); @@ -77,7 +137,6 @@ static void command_ready(GB_gameboy_t *gb) } GB_log(gb, "\n"); ; - } } @@ -192,10 +251,10 @@ void GB_sgb_render(GB_gameboy_t *gb) if (!gb->screen || !gb->rgb_encode_callback) return; uint32_t colors[] = { - gb->rgb_encode_callback(gb, 0xf7, 0xe7, 0xc6), - gb->rgb_encode_callback(gb, 0xd6, 0x8e, 0x49), - gb->rgb_encode_callback(gb, 0xa6, 0x37, 0x25), - gb->rgb_encode_callback(gb, 0x33, 0x1e, 0x50) + convert_rgb15(gb, gb->sgb->effective_palettes[0]), + convert_rgb15(gb, gb->sgb->effective_palettes[1]), + convert_rgb15(gb, gb->sgb->effective_palettes[2]), + convert_rgb15(gb, gb->sgb->effective_palettes[3]), }; switch ((mask_mode_t) gb->sgb->mask_mode) { @@ -235,19 +294,23 @@ void GB_sgb_render(GB_gameboy_t *gb) } else if (gb->sgb->data_transfer) { - for (unsigned tile = 0; tile < 0x88; tile++) { + unsigned size = gb->sgb->palette_transfer? 0x100 : 0x88; + uint16_t *data = gb->sgb->palette_transfer? gb->sgb->ram_palettes : gb->sgb->pending_border.raw_data; + for (unsigned tile = 0; tile < size; tile++) { unsigned tile_x = (tile % 20) * 8; unsigned tile_y = (tile / 20) * 8; for (unsigned y = 0; y < 0x8; y++) { static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080}; - uint16_t *data = &gb->sgb->pending_border.raw_data[tile * 8 + y]; *data = 0; for (unsigned x = 0; x < 8; x++) { *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; } + data++; } } - gb->sgb->border_animation = 64; + if (!gb->sgb->palette_transfer) { + gb->sgb->border_animation = 64; + } } } } @@ -298,15 +361,14 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned x = 0; x < 8; x++) { uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; if (color == 0 && gb_area) continue; - gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = - border_colors[palette * 16 + color]; + gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[palette * 16 + color]; } } } } } -void GB_sgb_load_default_border(GB_gameboy_t *gb) +void GB_sgb_load_default_data(GB_gameboy_t *gb) { #include "sgb_border.inc" @@ -314,7 +376,6 @@ void GB_sgb_load_default_border(GB_gameboy_t *gb) memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); memcpy(gb->sgb->border.palette, palette, sizeof(palette)); - /* Expend tileset */ for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { for (unsigned y = 0; y < 8; y++) { @@ -327,4 +388,9 @@ void GB_sgb_load_default_border(GB_gameboy_t *gb) } } } + + gb->sgb->effective_palettes[0] = 0x639E; + gb->sgb->effective_palettes[1] = 0x263A; + gb->sgb->effective_palettes[2] = 0x10D4; + gb->sgb->effective_palettes[3] = 0x2866; } diff --git a/Core/sgb.h b/Core/sgb.h index dd771572..0dcac882 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -24,9 +24,11 @@ struct GB_sgb_s { /* Mask */ uint8_t mask_mode; - /* Border */ + /* Data Transfer */ uint8_t vram_transfer_countdown; - bool tile_transfer, tile_transfer_high, data_transfer; + bool tile_transfer, tile_transfer_high, data_transfer, palette_transfer; + + /* Border */ struct { uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ union { @@ -38,12 +40,15 @@ struct GB_sgb_s { }; } border, pending_border; uint8_t border_animation; - + + /* Colorization */ + uint16_t effective_palettes[4 * 4]; + uint16_t ram_palettes[4 * 512]; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); -void GB_sgb_load_default_border(GB_gameboy_t *gb); +void GB_sgb_load_default_data(GB_gameboy_t *gb); #endif From e5e7ce82031345b5fbdd531763c9c32fa7d2cd08 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 22:05:35 +0200 Subject: [PATCH 0768/1216] Basic colorization --- Core/sgb.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++------- Core/sgb.h | 1 + 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index 458fd28b..10889ca6 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -5,6 +5,7 @@ enum { PAL23 = 0x01, PAL03 = 0x02, PAL12 = 0x03, + ATTR_BLK = 0x04, PAL_SET = 0x0A, PAL_TRN = 0x0B, DATA_SND = 0x0f, @@ -84,6 +85,56 @@ static void command_ready(GB_gameboy_t *gb) case PAL12: pal_command(gb, 1, 2); break; + case ATTR_BLK: { + struct { + uint8_t count; + struct { + uint8_t control; + uint8_t palettes; + uint8_t left, top, right, bottom; + } data[]; + } *command = (void *)(gb->sgb->command + 1); + if (command->count > 0x12) return; + + for (unsigned i = 0; i < command->count; i++) { + bool inside = command->data[i].control & 1; + bool middle = command->data[i].control & 2; + bool outside = command->data[i].control & 4; + uint8_t inside_palette = command->data[i].palettes & 0x3; + uint8_t middle_palette = (command->data[i].palettes >> 2) & 0x3; + uint8_t outside_palette = (command->data[i].palettes >> 4) & 0x3; + + if (inside && !middle && !outside) { + middle = true; + middle_palette = inside_palette; + } + else if (outside && !middle && !inside) { + middle = true; + middle_palette = outside_palette; + } + + for (unsigned y = 0; y < 18; y++) { + for (unsigned x = 0; x < 20; x++) { + if (x < command->data[i].left || x > command->data[i].right || + y < command->data[i].top || y > command->data[i].bottom) { + if (outside) { + gb->sgb->attribute_map[x + 20 * y] = outside_palette; + } + } + else if (x > command->data[i].left && x < command->data[i].right && + y > command->data[i].top && y < command->data[i].bottom) { + if (inside) { + gb->sgb->attribute_map[x + 20 * y] = inside_palette; + } + } + else if(middle) { + gb->sgb->attribute_map[x + 20 * y] = middle_palette; + } + } + } + } + break; + } case PAL_SET: memcpy(&gb->sgb->effective_palettes[0], &gb->sgb->ram_palettes[4 * (gb->sgb->command[1] + (gb->sgb->command[2] & 1) * 0x100)], @@ -250,13 +301,6 @@ void GB_sgb_render(GB_gameboy_t *gb) { if (!gb->screen || !gb->rgb_encode_callback) return; - uint32_t colors[] = { - convert_rgb15(gb, gb->sgb->effective_palettes[0]), - convert_rgb15(gb, gb->sgb->effective_palettes[1]), - convert_rgb15(gb, gb->sgb->effective_palettes[2]), - convert_rgb15(gb, gb->sgb->effective_palettes[3]), - }; - switch ((mask_mode_t) gb->sgb->mask_mode) { case MASK_DISABLED: memcpy(gb->sgb->effective_screen_buffer, @@ -315,11 +359,17 @@ void GB_sgb_render(GB_gameboy_t *gb) } } + uint32_t colors[4 * 4]; + for (unsigned i = 0; i < 4 * 4; i++) { + colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]); + } + uint32_t *output = &gb->screen[48 + 40 * 256]; uint8_t *input = gb->sgb->effective_screen_buffer; for (unsigned y = 0; y < 144; y++) { for (unsigned x = 0; x < 160; x++) { - *(output++) = colors[*(input++) & 3]; + uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; + *(output++) = colors[(*(input++) & 3) + palette * 4]; } output += 256 - 160; } diff --git a/Core/sgb.h b/Core/sgb.h index 0dcac882..531fb60e 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -44,6 +44,7 @@ struct GB_sgb_s { /* Colorization */ uint16_t effective_palettes[4 * 4]; uint16_t ram_palettes[4 * 512]; + uint8_t attribute_map[20 * 18]; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From b698ce5d95060e0d8b9feaa2f2136f78640115e5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 22:13:29 +0200 Subject: [PATCH 0769/1216] Correctly emulate SGB border color 0 --- Core/sgb.c | 10 +- Core/sgb_border.inc | 1173 ++++++++++++++++++++++--------------------- 2 files changed, 595 insertions(+), 588 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index 10889ca6..45ae7494 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -393,6 +393,7 @@ void GB_sgb_render(GB_gameboy_t *gb) } } + if (gb->sgb->border_animation == 32) { memcpy(&gb->sgb->border, &gb->sgb->pending_border, sizeof(gb->sgb->border)); } @@ -410,8 +411,13 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned y = 0; y < 8; y++) { for (unsigned x = 0; x < 8; x++) { uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; - if (color == 0 && gb_area) continue; - gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[palette * 16 + color]; + if (color == 0) { + if (gb_area) continue; + gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = colors[0]; + } + else { + gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[color + palette * 16]; + } } } } diff --git a/Core/sgb_border.inc b/Core/sgb_border.inc index 9ddd12be..b4b22253 100644 --- a/Core/sgb_border.inc +++ b/Core/sgb_border.inc @@ -1,671 +1,672 @@ static const uint16_t palette[] = { - 0x5273, 0x14A5, 0x043C, 0x2529, 0x35AD, 0x2954, 0x739C, 0x5AD5, - 0x41A8, 0x3D44, 0x4100, 0x20E7, 0x358C, 0x3DCE, 0x4A31, 0x1C75}; + 0x0000, 0x18C6, 0x043C, 0x2529, 0x2954, 0x739C, 0x5AD5, 0x4100, + 0x4166, 0x20E7, 0x316B, 0x358C, 0x3DCE, 0x4A31, 0x5273, 0x1C75}; + static const uint16_t tilemap[] = { + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1002, 0x1003, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, + 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, + 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, + 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x5003, 0x5002, 0x1001, + 0x1001, 0x1005, 0x1006, 0x1006, 0x1006, 0x1007, 0x1008, 0x1009, + 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, + 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, 0x1019, + 0x101A, 0x101B, 0x101C, 0x1006, 0x1006, 0x1006, 0x5005, 0x1001, + 0x1001, 0x101D, 0x101E, 0x101E, 0x101E, 0x101F, 0x1020, 0x1021, + 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, 0x1029, + 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, 0x1031, + 0x1032, 0x1033, 0x1034, 0x101E, 0x101E, 0x101E, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1001, 0x1002, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, - 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, - 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, - 0x1003, 0x1003, 0x1003, 0x1003, 0x1003, 0x5002, 0x5001, 0x1000, - 0x1000, 0x1004, 0x1005, 0x1005, 0x1005, 0x1006, 0x1007, 0x1008, - 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, - 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, - 0x1019, 0x101A, 0x101B, 0x1005, 0x1005, 0x1005, 0x5004, 0x1000, - 0x1000, 0x101C, 0x101D, 0x101D, 0x101D, 0x101E, 0x101F, 0x1020, - 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, - 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, - 0x1031, 0x1032, 0x1033, 0x101D, 0x101D, 0x101D, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1037, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1038, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1036, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1037, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0x1039, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0x103A, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0xC01C, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x103B, 0x103C, 0x1001, + 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x103D, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1038, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x1039, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x1034, 0x103A, 0x1000, - 0x1000, 0x101C, 0x1034, 0x1034, 0x1034, 0x1035, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC035, 0x1034, 0x1034, 0x103B, 0x103C, 0x1000, - 0x1000, 0x5038, 0x1034, 0x1034, 0x1034, 0x103D, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x503D, 0x1034, 0x103E, 0x103F, 0x1000, 0x1000, - 0x1000, 0x1040, 0x1041, 0x1034, 0x1034, 0x1042, 0x1043, 0x1044, + 0x1000, 0x1000, 0x503D, 0x1035, 0x103E, 0x103F, 0x1001, 0x1001, + 0x1001, 0x1040, 0x1041, 0x1035, 0x1035, 0x1042, 0x1043, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, - 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1049, 0x104A, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, + 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1049, 0x104A, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104C, 0x104D, 0x104E, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x104F, 0x1050, + 0x104B, 0x104C, 0x104D, 0x104E, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x104F, 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, 0x1060, - 0x1061, 0x1062, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1063, 0x1064, + 0x1061, 0x1062, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, - 0x1075, 0x1076, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1077, 0x1078, + 0x1075, 0x1076, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1077, 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, 0x1088, - 0x1089, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1089, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001 }; const uint8_t tiles[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x02, 0x05, 0x01, 0x0F, 0x07, - 0x1E, 0x0E, 0x1D, 0x3D, 0x1B, 0x1B, 0x37, 0x77, - 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x08, 0x0B, - 0x11, 0x16, 0x22, 0x28, 0x24, 0x39, 0x48, 0x73, - 0x3F, 0xC0, 0x7F, 0x7F, 0xFF, 0xFF, 0x80, 0x80, - 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x9F, 0x00, 0xFF, 0x7F, 0xC3, - 0x80, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x37, 0x37, 0x2F, 0x2F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x48, 0x2B, 0x50, 0x67, 0x10, 0x27, 0x10, 0x6F, - 0x10, 0x6F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x02, 0xFC, 0x01, 0xFB, 0x07, 0xF4, + 0x0F, 0xE9, 0x3E, 0xD7, 0x1F, 0xE6, 0x7F, 0x8C, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE, 0xF8, 0xFB, + 0xF0, 0xF7, 0xE0, 0xEB, 0xC0, 0xFD, 0xC0, 0xFB, + 0xC0, 0x00, 0xFF, 0xE0, 0xFF, 0x00, 0xBF, 0x7F, + 0xBF, 0xE0, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xDF, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, - 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, 0xF7, 0xFB, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x77, 0xDC, 0x3F, 0xD8, 0x7F, 0xD8, 0x7F, 0x90, + 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, + 0x80, 0xEB, 0x80, 0xF7, 0x80, 0xB7, 0x80, 0xFF, + 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x07, 0xFB, 0x00, + 0xFB, 0x00, 0xFB, 0x00, 0xFB, 0x00, 0xFB, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF8, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x3B, 0x3F, 0xD3, 0x53, - 0xCF, 0xAF, 0xD7, 0xAF, 0xCF, 0xAF, 0xD3, 0x53, - 0x00, 0xFF, 0x00, 0xFF, 0x47, 0x74, 0xE0, 0xB1, - 0x78, 0xE3, 0x78, 0xFF, 0x78, 0xE3, 0xE0, 0xB1, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0x19, 0x39, - 0xF9, 0xF9, 0xE9, 0xE9, 0xF9, 0xF9, 0x19, 0x39, - 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x70, 0xA4, 0xBD, - 0x24, 0x8D, 0x34, 0xDD, 0x24, 0x8D, 0xA4, 0xBD, - 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB5, 0xF5, 0xF5, - 0xF1, 0xF2, 0xF2, 0xF1, 0xF3, 0xF3, 0xF3, 0xF2, - 0x00, 0xFF, 0x00, 0xFF, 0xCE, 0x7B, 0x0C, 0xF9, - 0x0B, 0xFB, 0x0B, 0xFB, 0x09, 0xFA, 0x09, 0xFB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xDE, 0xD7, - 0x8F, 0x97, 0xAF, 0xD4, 0xEF, 0x77, 0xED, 0xB3, - 0x00, 0xFF, 0x00, 0xFF, 0x38, 0xEE, 0x19, 0xCF, - 0x59, 0xDC, 0x7B, 0xFF, 0xDB, 0x9C, 0xDE, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xBF, 0x3D, 0x7B, - 0x9D, 0x9B, 0xDD, 0xBB, 0xFD, 0xFB, 0xDD, 0xEB, - 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x30, 0x46, 0x7F, - 0x26, 0x3F, 0x66, 0xFF, 0xE6, 0x0F, 0x36, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xBA, 0x9A, 0x98, - 0x9A, 0x98, 0xBA, 0xBE, 0x9A, 0x9A, 0x9A, 0x92, - 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0x41, 0x43, 0xDB, - 0x43, 0xDB, 0x7D, 0xC5, 0x4D, 0xC7, 0x49, 0xD9, - 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0x4F, 0x75, 0x71, - 0x75, 0x65, 0x7B, 0x7F, 0x6B, 0x71, 0x4A, 0x4A, - 0x00, 0xFF, 0x00, 0xFF, 0x13, 0x1F, 0x1C, 0x4C, - 0x16, 0x73, 0x0C, 0x67, 0x1A, 0x7B, 0x27, 0x45, - 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0xDD, 0xE7, 0xE5, - 0xFD, 0xE6, 0xE7, 0xE7, 0xFD, 0xFB, 0xF7, 0xFD, - 0x00, 0xFF, 0x00, 0xFF, 0x33, 0xE6, 0x0B, 0xE6, - 0x1B, 0xFF, 0x12, 0xE1, 0x0E, 0xF7, 0x0E, 0xFB, - 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x65, 0x7B, 0xFD, - 0xEB, 0xDD, 0x1B, 0x2D, 0xFB, 0x3D, 0x5B, 0x5D, - 0x00, 0xFF, 0x00, 0xFF, 0x8F, 0x6E, 0x96, 0xC7, - 0xB6, 0x7F, 0x76, 0x7F, 0xE6, 0xCF, 0xC6, 0x9F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFB, 0xBD, 0xDB, - 0xBD, 0xDB, 0xBF, 0xDB, 0xBD, 0xDB, 0xBD, 0xDB, + 0xFF, 0x00, 0xFF, 0x00, 0x3C, 0xCB, 0x13, 0x6E, + 0xB7, 0x1C, 0xAF, 0x00, 0xB7, 0x1C, 0x13, 0x6E, + 0x00, 0xFF, 0x00, 0xFF, 0x87, 0xF4, 0xCC, 0xBD, + 0x68, 0xF3, 0x78, 0xFF, 0x68, 0xF3, 0xCC, 0xBD, + 0xFF, 0x00, 0xFF, 0x00, 0x20, 0x8F, 0x39, 0xC6, + 0xD9, 0x76, 0xC9, 0x36, 0xD9, 0x76, 0x39, 0xC6, + 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x70, 0x62, 0xFF, + 0x22, 0x8F, 0x22, 0xDF, 0x22, 0x8F, 0x62, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x31, 0xCC, 0xF1, 0x0E, + 0xF2, 0x0C, 0xF1, 0x0C, 0xF2, 0x0D, 0xF2, 0x0C, + 0x00, 0xFF, 0x00, 0xFF, 0x86, 0x7B, 0x06, 0xFB, + 0x07, 0xFF, 0x07, 0xFF, 0x05, 0xFE, 0x05, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xC7, 0x11, 0xC7, 0x30, + 0x96, 0x63, 0xD4, 0x00, 0x34, 0x63, 0x33, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x38, 0xEE, 0x39, 0xEF, + 0x39, 0xFC, 0x7B, 0xFF, 0xDB, 0x9C, 0xDE, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x70, 0xCF, 0x7B, 0x80, + 0x9B, 0xE0, 0xBB, 0x00, 0x1B, 0xF0, 0xEB, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x9F, 0x70, 0xC6, 0xFF, + 0x46, 0x7F, 0x66, 0xFF, 0xE6, 0x0F, 0x36, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x02, 0xFF, 0x98, 0x65, + 0x98, 0x65, 0x86, 0x7B, 0x92, 0x7D, 0x92, 0x67, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0x45, 0x26, 0xFF, + 0x26, 0xFF, 0x3C, 0xC5, 0x28, 0xE7, 0x2C, 0xFD, + 0xFF, 0x00, 0xFF, 0x00, 0x4F, 0xE0, 0x61, 0xBB, + 0x61, 0x8E, 0x77, 0x98, 0x71, 0x84, 0x68, 0xBF, + 0x00, 0xFF, 0x00, 0xFF, 0xB3, 0xBF, 0x96, 0xCE, + 0x9C, 0xFB, 0x8C, 0xE7, 0x9E, 0xFF, 0x92, 0xF5, + 0xFF, 0x00, 0xFF, 0x00, 0xCC, 0x3B, 0xEC, 0x19, + 0xE6, 0x00, 0xF5, 0x1E, 0xF3, 0x08, 0xF9, 0x04, + 0x00, 0xFF, 0x00, 0xFF, 0x11, 0xE6, 0x13, 0xFE, + 0x1B, 0xFF, 0x0A, 0xF9, 0x0E, 0xF7, 0x0E, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xE4, 0x91, 0xED, 0x38, + 0x5D, 0x80, 0x2D, 0xC0, 0x1D, 0x30, 0x1D, 0xE0, + 0x00, 0xFF, 0x00, 0xFF, 0x1F, 0xFE, 0x96, 0xC7, + 0xB6, 0x7F, 0xB6, 0xFF, 0xE6, 0xCF, 0x66, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0x0B, 0xF0, 0xDB, 0x00, + 0xDB, 0x00, 0xD8, 0x03, 0xDB, 0x00, 0xDB, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFE, 0x0F, 0x66, 0xFF, 0x66, 0xFF, 0x67, 0xFC, 0x66, 0xFF, 0x66, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xCE, 0xCE, 0xCE, - 0xCE, 0xCF, 0xEF, 0xEF, 0xCF, 0xCF, 0xCE, 0xCF, - 0x00, 0xFF, 0x00, 0xFF, 0x11, 0xDF, 0x10, 0xDE, - 0x11, 0xDF, 0xF0, 0x1F, 0x10, 0xDF, 0x11, 0xDF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x6F, 0xFE, 0x4D, - 0xFE, 0x7D, 0x9E, 0x7D, 0xCE, 0xCD, 0xFE, 0x5D, - 0x00, 0xFF, 0x00, 0xFF, 0x7F, 0x18, 0xB3, 0xFF, - 0x83, 0xFF, 0xE3, 0xEF, 0x23, 0xCF, 0xB3, 0xEF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xDE, 0xCF, 0xCF, - 0xCF, 0xCF, 0xDE, 0xDE, 0xCF, 0xCF, 0xCF, 0xCF, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x21, 0x20, 0xEF, - 0x20, 0xEF, 0x3F, 0xE1, 0x20, 0xEF, 0x20, 0xEF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x75, 0x75, 0x34, 0x30, - 0x34, 0x30, 0x75, 0x7D, 0x34, 0x34, 0x34, 0x24, - 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x82, 0x86, 0xB6, - 0x86, 0xB6, 0xFB, 0x8A, 0x9A, 0x8E, 0x92, 0xB2, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xEB, 0xFD, 0xFF, - 0xFB, 0xF7, 0xE7, 0xEB, 0xFB, 0xF7, 0xFD, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0xF7, 0x1E, 0x06, 0xF3, - 0x0C, 0xFF, 0xFC, 0x1F, 0x0C, 0xFF, 0x06, 0xF3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x9F, 0xFF, 0xAE, - 0xEF, 0xCE, 0xDF, 0xDF, 0xEF, 0xCF, 0xFF, 0xAE, - 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0x3E, 0x71, 0xDF, - 0x21, 0xEF, 0x30, 0xEF, 0x20, 0xEF, 0x71, 0xDF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x6C, 0x5B, 0x4A, - 0x7B, 0xF9, 0x8D, 0x6D, 0xFB, 0xF9, 0x53, 0xD2, - 0x00, 0xFF, 0x00, 0xFF, 0x72, 0x1E, 0x37, 0x79, - 0x82, 0xFB, 0xF6, 0xFB, 0x12, 0xC3, 0xBF, 0xE9, - 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, 0xC9, 0xDD, 0xF9, - 0xED, 0xF1, 0xF5, 0xE9, 0xEB, 0xF3, 0xDF, 0xFE, - 0x00, 0xFF, 0x00, 0xFF, 0xD4, 0x1D, 0x34, 0xE5, - 0x1C, 0xFD, 0x1C, 0xFD, 0x1E, 0xFD, 0x33, 0xE1, - 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0xC5, 0xD7, 0xC7, - 0xD2, 0xC0, 0xD2, 0xC3, 0xC3, 0xC3, 0xF3, 0xB3, - 0x00, 0xFF, 0x00, 0xFF, 0x1E, 0xDB, 0x1E, 0xD8, - 0x1A, 0xDA, 0x19, 0xDB, 0x18, 0xCA, 0x68, 0xCB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF7, 0xFB, - 0xF7, 0xFB, 0xF7, 0x7B, 0xB7, 0xBB, 0x77, 0x7B, + 0xFF, 0x00, 0xFF, 0x00, 0xCE, 0x31, 0xCE, 0x31, + 0xCF, 0x30, 0x0F, 0xF0, 0xCF, 0x30, 0xCF, 0x30, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x21, 0xFF, + 0x21, 0xFF, 0xE0, 0x1F, 0x20, 0xFF, 0x21, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x08, 0xF7, 0x4D, 0x00, + 0x7D, 0x00, 0x7D, 0x10, 0xED, 0x30, 0x4D, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0x98, 0xB3, 0xFF, + 0x83, 0xFF, 0xE3, 0xEF, 0x13, 0xFF, 0xB3, 0xEF, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0xCF, 0x30, + 0xCF, 0x30, 0xC0, 0x3F, 0xCF, 0x30, 0xCF, 0x30, + 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x21, 0x10, 0xFF, + 0x10, 0xFF, 0x1E, 0xE1, 0x10, 0xFF, 0x10, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x04, 0xFF, 0x30, 0xCB, + 0x30, 0xCB, 0x0C, 0xF7, 0x24, 0xFB, 0x24, 0xCF, + 0x00, 0xFF, 0x00, 0xFF, 0x79, 0x8A, 0x4D, 0xFF, + 0x4D, 0xFF, 0x79, 0x8A, 0x51, 0xCF, 0x59, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0x0A, 0xE5, 0xFB, 0x0C, + 0xF7, 0x00, 0x0B, 0xF0, 0xF7, 0x00, 0xFB, 0x0C, + 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x1E, 0x06, 0xF3, + 0x0C, 0xFF, 0xEC, 0x1F, 0x0C, 0xFF, 0x06, 0xF3, + 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0xC1, 0x8E, 0x20, + 0xCE, 0x10, 0xCF, 0x30, 0xCF, 0x10, 0x8E, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xE0, 0x7E, 0x71, 0xDF, + 0x31, 0xFF, 0x10, 0xEF, 0x30, 0xFF, 0x71, 0xDF, + 0xFF, 0x00, 0xFF, 0x00, 0x0C, 0xE1, 0x4C, 0xA6, + 0xF9, 0x04, 0x69, 0x16, 0xE9, 0x3C, 0xC4, 0x3E, + 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x9F, 0x93, 0xFD, + 0x86, 0xFF, 0xE4, 0xFB, 0x16, 0xC7, 0x93, 0xED, + 0xFF, 0x00, 0xFF, 0x00, 0x09, 0xF2, 0xE9, 0x1A, + 0xF1, 0x02, 0xE9, 0x02, 0xF1, 0x06, 0xEC, 0x1E, + 0x00, 0xFF, 0x00, 0xFF, 0xE6, 0x3F, 0x36, 0xE7, + 0x1E, 0xFF, 0x1E, 0xFF, 0x1A, 0xFD, 0x33, 0xE1, + 0xFF, 0x00, 0xFF, 0x00, 0xC1, 0x2C, 0xC1, 0x2F, + 0xC0, 0x2D, 0xC3, 0x2C, 0xD3, 0x3D, 0x93, 0x3C, + 0x00, 0xFF, 0x00, 0xFF, 0x36, 0xFB, 0x36, 0xF8, + 0x37, 0xFF, 0x35, 0xFF, 0x24, 0xFE, 0x64, 0xCF, + 0xFF, 0x00, 0xFF, 0x00, 0xD8, 0x67, 0xDB, 0x60, + 0xDB, 0x60, 0x5B, 0x60, 0x5B, 0xE0, 0x9B, 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0x98, 0x2C, 0x9F, - 0x2C, 0x9F, 0xAC, 0x9F, 0xEC, 0x1F, 0xEC, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0xDF, 0x5F, - 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0xBF, 0xDF, 0x5F, - 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x7F, 0xE0, 0xBF, - 0x60, 0xFF, 0x60, 0xFF, 0x60, 0xFF, 0xE0, 0xBF, - 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, - 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, - 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, 0x10, 0x7F, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2C, 0x9F, 0xAC, 0x9F, 0xAC, 0x5F, 0x6C, 0x9F, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xC0, 0x1F, 0x60, + 0xBF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x1F, 0x60, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0xC0, 0xBF, + 0x60, 0xFF, 0x60, 0xFF, 0x60, 0xFF, 0xC0, 0xBF, + 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, + 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, + 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, + 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xF8, 0x07, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, - 0x3B, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x47, 0x74, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF9, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xC4, 0x7D, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF3, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x08, 0xFA, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xE9, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x9A, 0x39, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCD, 0xCB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x26, 0xC7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x98, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x47, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x2E, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x72, 0x5E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x3C, 0xCB, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x87, 0xF4, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x39, 0x86, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC2, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0x0D, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x73, 0xC6, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9C, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xEB, 0x38, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x16, 0xF7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x9E, 0x61, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x26, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0xF1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA3, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0x86, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x79, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xFB, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x9D, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xBD, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xDB, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCF, 0xCE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x11, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xEE, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xF3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xDE, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x3F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x31, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x8F, 0xBE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xE9, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xF7, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xBF, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xA1, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x7E, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x72, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCF, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xD3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF3, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xE8, 0x3B, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xCE, 0x30, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0xF0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xC1, 0x3E, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x3C, 0xC3, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x4D, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x1A, 0xE5, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x1E, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE1, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0xE1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0x9F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0xF1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE3, 0x3E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x13, 0xCC, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE4, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xD8, 0x27, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xD8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, - 0x40, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, - 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, - 0x3C, 0xFF, 0x00, 0x81, 0x81, 0x81, 0x00, 0x00, - 0x00, 0xFF, 0x81, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x81, 0x81, 0x66, 0xE7, 0x3C, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x3C, 0xC3, 0x7E, 0x81, 0xFF, 0x81, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x81, + 0x3C, 0x3C, 0x42, 0x42, 0x81, 0x81, 0x81, 0x81, + 0x81, 0xFF, 0xC3, 0xFF, 0xFF, 0x7E, 0xFF, 0x3C, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x81, 0x81, 0xC3, 0xC3, 0x7E, 0xFF, 0x3C, 0xBD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, - 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, - 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, - 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xFE, 0x08, 0xF6, - 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF4, 0xF4, - 0xF4, 0xF4, 0xF6, 0xF4, 0xE4, 0xE6, 0xEC, 0xEC, - 0x08, 0xF6, 0x08, 0xF4, 0x08, 0xF4, 0x0A, 0xE6, - 0x0A, 0xE4, 0x0A, 0xEE, 0x1A, 0xFE, 0x10, 0xF4, - 0xEE, 0xEE, 0xE8, 0xE8, 0xE8, 0xEC, 0xD8, 0xD8, - 0xDC, 0xDC, 0xD0, 0xD8, 0xB8, 0xB8, 0xA8, 0xA8, - 0x12, 0xCC, 0x14, 0xCC, 0x14, 0xDC, 0x20, 0xE8, - 0x24, 0x90, 0x28, 0xB8, 0x48, 0x30, 0x58, 0x30, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, - 0xFD, 0xFD, 0xF9, 0xF9, 0xFB, 0xFB, 0xF7, 0xF7, - 0x00, 0xFE, 0x00, 0xFE, 0x01, 0xFC, 0x01, 0xFC, - 0x02, 0xF9, 0x06, 0xF7, 0x04, 0xF2, 0x09, 0xE6, - 0x60, 0x60, 0x70, 0x70, 0xC0, 0xC0, 0xA0, 0xA0, - 0xC0, 0xC0, 0xC0, 0x40, 0x80, 0x80, 0x00, 0x00, - 0x80, 0xA0, 0x90, 0x40, 0x00, 0x40, 0x60, 0xC0, - 0x40, 0x80, 0xC0, 0x80, 0x80, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, - 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x09, 0xFE, 0x0B, 0xFE, 0x0B, 0xFC, 0x1B, + 0xFE, 0x1B, 0xFC, 0x19, 0xE6, 0x19, 0xEC, 0x1B, + 0x01, 0xFF, 0x01, 0xFD, 0x01, 0xFD, 0x01, 0xEF, + 0x01, 0xEF, 0x03, 0xEF, 0x03, 0xFF, 0x03, 0xF7, + 0xFC, 0x33, 0xF8, 0x37, 0xCC, 0x33, 0xF8, 0x37, + 0xF8, 0x6F, 0x98, 0x67, 0xF0, 0xCF, 0xF0, 0xDF, + 0x03, 0xDD, 0x03, 0xDF, 0x07, 0xFF, 0x07, 0xEF, + 0x07, 0xB3, 0x0F, 0xFF, 0x0F, 0x77, 0x0F, 0x77, + 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x03, 0xFD, 0x03, + 0xFF, 0x06, 0xF9, 0x0E, 0xF7, 0x0D, 0xFE, 0x19, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFF, + 0x00, 0xFB, 0x00, 0xF7, 0x00, 0xFE, 0x01, 0xEE, + 0xE0, 0xDF, 0xC0, 0xBF, 0xC0, 0xBF, 0x80, 0x7F, + 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0xBF, 0x1F, 0xEF, 0x3F, 0x7F, 0x3F, 0xDF, + 0x7F, 0xBF, 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0xFE, 0xFC, 0xFC, 0xFB, 0xFB, 0xE6, 0xE6, + 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, + 0xFE, 0x03, 0xFE, 0x07, 0xFF, 0x0E, 0xEE, 0x3D, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, - 0x01, 0xFD, 0x03, 0xF9, 0x04, 0xF1, 0x19, 0xD3, - 0xED, 0xEF, 0xCA, 0xCA, 0x98, 0x9C, 0x38, 0x38, - 0x78, 0x78, 0xF0, 0xF0, 0xE0, 0xE0, 0x40, 0xC0, - 0x13, 0xC6, 0x36, 0xBC, 0x64, 0x70, 0xC8, 0xE0, - 0x98, 0xC0, 0x30, 0x80, 0x60, 0x00, 0xC0, 0x00, - 0x6F, 0x6F, 0x6F, 0x6F, 0x2F, 0x2F, 0x37, 0x77, - 0x77, 0x77, 0x1B, 0x3B, 0x2D, 0x2D, 0x06, 0x16, - 0x10, 0x6F, 0x10, 0x27, 0x50, 0x27, 0x48, 0x6B, - 0x48, 0x13, 0x24, 0x39, 0x32, 0x08, 0x19, 0x0E, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, + 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF5, 0x00, 0xDB, + 0xFE, 0x39, 0xE8, 0x77, 0x9C, 0xEB, 0x30, 0xDF, + 0x60, 0xBF, 0xC0, 0x7F, 0x80, 0xFF, 0x80, 0x7F, + 0x03, 0xD6, 0x03, 0xBD, 0x07, 0x73, 0x0F, 0xE7, + 0x1F, 0xC7, 0x3F, 0x8F, 0x7F, 0x1F, 0xFF, 0x3F, + 0x7F, 0x90, 0x7F, 0xD8, 0x7F, 0xD8, 0x77, 0x9C, + 0x3F, 0xEC, 0x3F, 0xC6, 0x1E, 0xF7, 0x17, 0xE9, + 0x80, 0xFF, 0x80, 0xB7, 0x80, 0xF7, 0xC0, 0xEB, + 0xC0, 0x9B, 0xE0, 0xFD, 0xE0, 0xDB, 0xF0, 0xEF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xBF, 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x80, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xDF, + 0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x0F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF8, 0x07, 0x07, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x03, 0xFB, 0x1F, 0x3F, 0xFC, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xE4, 0xF8, 0xCB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xF9, 0xF9, - 0xE7, 0xE7, 0x1F, 0x1F, 0xF5, 0xF7, 0xF4, 0xDC, - 0x00, 0xFF, 0x00, 0xFE, 0x01, 0xF8, 0x06, 0xE4, - 0x18, 0x83, 0xE1, 0xBC, 0x0F, 0x78, 0x3C, 0xA0, - 0xCC, 0xCD, 0x38, 0x3E, 0x7C, 0x7C, 0xC8, 0xF8, - 0x60, 0x60, 0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x33, 0xAC, 0xC6, 0x9C, 0x8C, 0x60, 0x38, 0xE0, - 0xE0, 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x0B, 0x03, 0x07, 0x02, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0C, 0x07, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x80, 0xFF, 0xFF, 0xDF, 0x9F, 0x80, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xC3, 0x00, 0xFF, 0xE0, 0x6F, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFA, 0xF9, 0x01, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0x81, 0x00, 0xFF, 0x07, 0xC7, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0xE4, 0xEF, 0xCC, 0xFC, 0xC0, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x3F, 0x1F, 0xF8, 0xFC, 0x00, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x90, 0xF0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, - 0x01, 0x00, 0x03, 0x01, 0x03, 0x01, 0x03, 0x03, - 0x0F, 0x16, 0x3F, 0x1D, 0x7F, 0xF0, 0xE9, 0xE7, - 0xD3, 0xCB, 0xE7, 0x93, 0xC6, 0x02, 0x87, 0x40, - 0x1F, 0x16, 0x3F, 0x1D, 0xFF, 0xF6, 0xFF, 0xEF, - 0xFB, 0xDB, 0xF7, 0xF3, 0xC7, 0x42, 0xC7, 0xC1, - 0x00, 0x00, 0x80, 0x00, 0x80, 0x40, 0xC0, 0x00, - 0xC0, 0x00, 0x80, 0x40, 0xB0, 0x01, 0x31, 0xE9, - 0x00, 0x00, 0x80, 0x00, 0xC0, 0x40, 0xC0, 0x40, - 0xC0, 0x40, 0xC0, 0xC0, 0xB1, 0x81, 0xF9, 0xE9, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x84, 0x85, 0xC6, 0xCE, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x84, 0x84, 0xC7, 0xC6, 0xCF, 0xC7, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0x41, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xE3, 0xC1, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0xC3, 0xE3, 0xC7, 0xF7, 0x23, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x02, 0xC3, 0xC3, 0xE7, 0xC7, 0xF7, 0x63, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x80, 0x80, 0x40, 0xF0, 0x20, 0xF0, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x80, 0xC0, 0xC0, 0xF0, 0xA0, 0xF8, 0xF8, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x07, - 0x01, 0x1E, 0x11, 0x0E, 0x00, 0x0E, 0x00, 0x1F, - 0x00, 0x00, 0x01, 0x01, 0x00, 0x07, 0x00, 0x0F, - 0x10, 0x1F, 0x11, 0x1F, 0x00, 0x1F, 0x10, 0x1F, - 0x00, 0x00, 0x00, 0x10, 0x08, 0xF4, 0x00, 0xF8, - 0x24, 0x12, 0x98, 0x64, 0x00, 0x00, 0x80, 0x40, - 0x00, 0x00, 0x10, 0xF0, 0x04, 0xFC, 0x00, 0xFC, - 0x06, 0xFE, 0xFC, 0xFC, 0x00, 0x00, 0x40, 0xC0, - 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x1F, - 0x10, 0x0F, 0x00, 0x1F, 0x00, 0x7B, 0x4C, 0x33, - 0x00, 0x00, 0x07, 0x07, 0x00, 0x0F, 0x10, 0x1F, - 0x00, 0x1F, 0x00, 0x3F, 0x40, 0x7F, 0x04, 0x7F, - 0x00, 0x00, 0x00, 0x01, 0x82, 0x01, 0x00, 0x41, - 0x80, 0x41, 0x82, 0x41, 0x40, 0x83, 0x00, 0x87, - 0x00, 0x00, 0x01, 0x01, 0x82, 0x83, 0x40, 0xC3, - 0x40, 0xC3, 0x40, 0xC3, 0x40, 0xC3, 0x44, 0xC7, - 0x00, 0x00, 0x00, 0xF0, 0x10, 0xE0, 0x18, 0xE0, - 0x08, 0xF0, 0x00, 0xF0, 0x40, 0xB4, 0x49, 0xB4, - 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xF0, 0x08, 0xF8, - 0x08, 0xF8, 0x08, 0xF8, 0x04, 0xFD, 0x04, 0xFD, - 0x00, 0x00, 0x00, 0x3E, 0x40, 0x3F, 0x43, 0x3C, - 0x83, 0x7C, 0x80, 0x7D, 0x20, 0xDD, 0x02, 0xDD, - 0x00, 0x00, 0x3E, 0x3E, 0x41, 0x7F, 0x01, 0x7F, - 0x81, 0xFF, 0x01, 0xFF, 0x03, 0xFD, 0x03, 0xFF, - 0x00, 0x00, 0x00, 0x3F, 0x40, 0x3F, 0x00, 0x3F, - 0x07, 0x38, 0x47, 0x30, 0x00, 0x74, 0x08, 0xF0, - 0x00, 0x00, 0x3F, 0x3F, 0x40, 0x7F, 0x00, 0x7F, - 0x00, 0x7F, 0x07, 0x7F, 0x0C, 0x7C, 0x80, 0xFF, - 0x00, 0x00, 0x00, 0xE7, 0x24, 0xD3, 0x30, 0xC7, - 0xC0, 0x1F, 0xE8, 0x17, 0x01, 0x06, 0x49, 0x06, - 0x00, 0x00, 0xE7, 0xE7, 0x10, 0xF7, 0x10, 0xF7, - 0x18, 0xFF, 0xF8, 0xFF, 0x00, 0x0F, 0x40, 0xCF, - 0x00, 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x02, 0xFC, - 0xE0, 0x1F, 0x81, 0x6E, 0x83, 0x0C, 0x07, 0x18, - 0x00, 0x00, 0xF0, 0xF0, 0x00, 0xFC, 0x00, 0xFE, - 0x01, 0xFF, 0xE1, 0xFF, 0x81, 0x9F, 0x03, 0xFF, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x18, 0x07, - 0x01, 0x3E, 0x21, 0x1C, 0x02, 0x39, 0x4C, 0x32, - 0x00, 0x00, 0x01, 0x01, 0x08, 0x0F, 0x10, 0x1F, - 0x20, 0x3F, 0x01, 0x3F, 0x03, 0x7F, 0x06, 0x7E, - 0x00, 0x00, 0x20, 0x10, 0x0C, 0xF0, 0x00, 0xFC, - 0xE1, 0x1E, 0x81, 0x6E, 0x09, 0x06, 0x00, 0x07, - 0x00, 0x00, 0x30, 0xF0, 0x04, 0xFC, 0x00, 0xFE, - 0x01, 0xFF, 0xE0, 0xFF, 0x00, 0x0F, 0x00, 0x0F, - 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF0, 0x04, 0x38, - 0x00, 0x78, 0x25, 0x9A, 0x00, 0xBD, 0x90, 0x0F, - 0x00, 0x00, 0xF8, 0xF8, 0x80, 0xF8, 0x04, 0x7C, - 0x40, 0x7C, 0x83, 0xBF, 0xA0, 0xBF, 0x80, 0x9F, - 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7E, 0x42, 0x38, - 0x04, 0x72, 0x18, 0xE4, 0x30, 0xC8, 0x60, 0x90, - 0x00, 0x00, 0x3E, 0x3E, 0x42, 0x7E, 0x06, 0x7E, - 0x06, 0xFE, 0x0C, 0xFC, 0x18, 0xF8, 0x30, 0xF0, - 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x10, 0x28, 0x1C, 0x3A, 0x0F, 0x1E, 0x07, 0x0B, - 0x03, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x38, 0x28, 0x3E, 0x3A, 0x1F, 0x1E, 0x0F, 0x0B, - 0x03, 0x00, 0x80, 0xC0, 0xC0, 0xE0, 0xF1, 0x68, - 0x79, 0x3D, 0x19, 0x2B, 0x1D, 0x03, 0xFD, 0xA1, - 0x83, 0x03, 0xC0, 0xC0, 0xE0, 0xE0, 0xF9, 0x68, - 0x7D, 0x7D, 0x3F, 0x2B, 0x1F, 0x07, 0xFD, 0xAD, - 0x63, 0x69, 0xF3, 0x4F, 0xD7, 0xC2, 0xE7, 0x9E, - 0x8F, 0xAC, 0xDE, 0x2D, 0xFE, 0x05, 0xEF, 0x95, - 0x7B, 0x69, 0xFF, 0x5F, 0xF7, 0xD2, 0xFF, 0xBF, - 0xEF, 0xAD, 0xFF, 0x6F, 0xFF, 0x47, 0xFF, 0x95, - 0xCD, 0x1C, 0x1E, 0x49, 0xBC, 0x5A, 0x34, 0xB1, - 0x79, 0x34, 0x79, 0xF3, 0xFB, 0xD7, 0xFF, 0xCE, - 0xDF, 0x5D, 0xDF, 0x4B, 0xFE, 0xDE, 0xBD, 0xB5, - 0x7D, 0x3C, 0xFB, 0xFB, 0xFF, 0xDF, 0xFF, 0xFE, - 0x67, 0xD2, 0xE7, 0x5E, 0xEF, 0x54, 0xEF, 0xDD, - 0xFF, 0x88, 0xFE, 0x98, 0xFC, 0x0A, 0xF7, 0x4C, - 0xF7, 0xD2, 0xFF, 0x5F, 0xFF, 0x55, 0xFF, 0xFD, - 0xFF, 0xAB, 0xFE, 0xFA, 0xFE, 0x5A, 0xFF, 0x5C, - 0xF3, 0x2A, 0xE7, 0x28, 0xF5, 0x4E, 0xFF, 0x80, - 0x69, 0x17, 0x15, 0xE9, 0x29, 0x51, 0xD1, 0x28, - 0xFB, 0xEA, 0xFF, 0xA9, 0xFF, 0x5F, 0xFF, 0xB2, - 0xFF, 0x77, 0xFD, 0xED, 0x79, 0x59, 0xF9, 0x38, - 0x78, 0x20, 0xF8, 0x60, 0xD0, 0xC0, 0xE0, 0x90, - 0xC8, 0xA0, 0xCC, 0x18, 0xD4, 0x18, 0xF8, 0x84, - 0xF8, 0x28, 0xF8, 0xF8, 0xF0, 0xD0, 0xF0, 0xB0, - 0xE8, 0xE0, 0xDC, 0x5C, 0xDC, 0x5C, 0xFC, 0x8C, - 0x00, 0x07, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, - 0x08, 0x00, 0x24, 0x58, 0x00, 0x7F, 0x10, 0x2F, - 0x00, 0x0F, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, - 0x08, 0x18, 0x40, 0x7F, 0x40, 0x7F, 0x20, 0x3F, - 0x20, 0xD0, 0x00, 0xF9, 0x81, 0x70, 0x0A, 0xF5, - 0x06, 0xF1, 0x80, 0x73, 0x10, 0xE7, 0x58, 0x87, - 0x10, 0xF0, 0x09, 0xF9, 0x00, 0xF9, 0x86, 0xFF, - 0x84, 0xFF, 0x08, 0xF7, 0x08, 0xFF, 0x10, 0xFF, - 0x0A, 0x75, 0x0A, 0xE1, 0x12, 0xE1, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0x81, 0xC0, 0x3F, 0x81, 0x40, - 0x0C, 0xFF, 0x08, 0xFB, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x3C, 0xFF, 0x7E, 0xFF, 0xC0, 0xC1, - 0x04, 0xA3, 0x40, 0xA3, 0x44, 0xA3, 0x24, 0xC3, - 0x20, 0xC7, 0x00, 0xDF, 0x21, 0xD6, 0x21, 0xD6, - 0x24, 0xE7, 0x20, 0xE7, 0x20, 0xE7, 0x20, 0xE7, - 0x20, 0xE7, 0x18, 0xFF, 0x10, 0xFF, 0x10, 0xFF, - 0x88, 0x35, 0xC4, 0x3B, 0x04, 0x7B, 0x20, 0x5F, - 0x80, 0x5E, 0xA3, 0x1C, 0x81, 0x3C, 0x86, 0x19, - 0x04, 0xFF, 0x44, 0xFF, 0x40, 0xFF, 0x40, 0xFF, - 0xC0, 0xFF, 0xA1, 0xBF, 0xA1, 0xBF, 0x83, 0x9F, - 0x46, 0x98, 0x66, 0x98, 0xE0, 0x1A, 0x80, 0x7A, - 0x84, 0x7A, 0x44, 0xB9, 0x0C, 0x30, 0x4C, 0x30, - 0x02, 0xFE, 0x42, 0xFE, 0x42, 0xFE, 0xC2, 0xFE, - 0xC6, 0xFE, 0xC5, 0xFD, 0x04, 0x7D, 0x04, 0x7D, - 0x80, 0x7F, 0x00, 0x7F, 0x9F, 0x60, 0x80, 0x68, - 0x00, 0xE8, 0x07, 0xF8, 0x00, 0xFF, 0x00, 0xFF, - 0x80, 0xFF, 0x00, 0xFF, 0x0F, 0xFF, 0x08, 0xF8, - 0x18, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x08, 0xA7, 0x00, 0xAF, 0xC1, 0x3E, 0x13, 0x0C, - 0x03, 0x0C, 0xD1, 0x0E, 0x10, 0xAF, 0x40, 0xBF, - 0x20, 0xEF, 0x20, 0xEF, 0xF1, 0xFF, 0x11, 0x1F, - 0x01, 0x1F, 0x40, 0xDF, 0x60, 0xFF, 0x60, 0xFF, - 0x0E, 0xF0, 0x00, 0xF8, 0xC2, 0x1C, 0x02, 0x1D, - 0x20, 0x1D, 0xC2, 0x3D, 0x02, 0xF8, 0x3C, 0xC2, - 0x06, 0xFE, 0x00, 0xFC, 0xC2, 0xFE, 0x01, 0x1F, - 0x21, 0x3F, 0x03, 0xFF, 0x02, 0xFE, 0x0E, 0xFE, - 0x4C, 0x30, 0x00, 0xF4, 0x00, 0xF4, 0x08, 0x74, - 0x40, 0x38, 0x01, 0x7E, 0x00, 0x1F, 0x18, 0x07, - 0x04, 0x7C, 0x84, 0xFC, 0x84, 0xFC, 0x04, 0x7C, - 0x00, 0x7C, 0x40, 0x7F, 0x00, 0x3F, 0x10, 0x1F, - 0x00, 0x07, 0x09, 0x06, 0x01, 0x1E, 0x00, 0x0E, - 0x03, 0x5C, 0x82, 0x7D, 0x0E, 0xF0, 0x2C, 0xC0, - 0x00, 0x0F, 0x00, 0x0F, 0x10, 0x1F, 0x01, 0x1F, - 0x41, 0x7F, 0x03, 0xFF, 0x06, 0xFE, 0x0C, 0xFC, - 0x90, 0x0F, 0x80, 0x0F, 0x89, 0x06, 0x00, 0x8E, - 0x00, 0x9E, 0x11, 0x0E, 0x01, 0x0E, 0x13, 0x0C, - 0x90, 0x9F, 0x80, 0x8F, 0x80, 0x8F, 0x80, 0x8F, - 0x91, 0x9F, 0x11, 0x1F, 0x01, 0x1F, 0x01, 0x1F, - 0x60, 0x80, 0x40, 0x00, 0x80, 0x40, 0x00, 0x80, - 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x60, 0xE0, 0xC0, 0x40, 0xC0, 0xC0, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0x05, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFD, 0x3D, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x10, 0x01, 0x63, 0x01, 0x03, 0x03, 0x01, - 0x03, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, - 0xF7, 0x74, 0x63, 0x63, 0x03, 0x03, 0x03, 0x01, - 0x03, 0x00, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, - 0xE7, 0x88, 0xC0, 0xA3, 0xC0, 0x00, 0x80, 0x40, - 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, - 0xEF, 0xA8, 0xE3, 0xE3, 0xC0, 0x40, 0xC0, 0xC0, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, - 0xA1, 0x54, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF7, 0xF5, 0xC1, 0xC1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x40, 0xA0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xE0, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xD0, 0x28, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0xB8, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x1F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x10, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF7, 0x77, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF3, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF7, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0E, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x8E, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xCF, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0E, 0x0F, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x70, 0x08, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xF8, 0xF8, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE7, 0x00, 0xFB, + 0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x07, 0xFF, 0x1F, + 0xDF, 0x7C, 0x7C, 0xE3, 0xF2, 0x8D, 0xC8, 0x57, + 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF9, 0x00, 0xE6, + 0x00, 0xBB, 0x01, 0xFE, 0x07, 0x78, 0x3F, 0xA3, + 0xDF, 0x72, 0x5E, 0xE1, 0xF0, 0x9F, 0xF0, 0x0F, + 0x00, 0xFF, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xBE, 0x07, 0xFD, 0x0F, 0xE3, 0x3F, 0xE7, + 0x7F, 0x9F, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0B, 0xF0, 0x05, 0xFB, 0x01, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0xF7, 0xFE, 0xF8, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0x7F, 0xFF, 0x00, 0x1F, 0xB0, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0x6F, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0xF5, 0x3C, 0xFE, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xCF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7E, 0xC0, 0xEB, 0x14, 0x30, 0xCF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xBF, 0x0F, 0xF8, 0xFF, 0x03, 0xFF, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x60, 0x9F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x0F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFE, + 0x00, 0xFE, 0x01, 0xFD, 0x01, 0xFD, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, + 0x16, 0xE6, 0x1D, 0xDD, 0xF0, 0x70, 0xE7, 0xE1, + 0xCB, 0xC7, 0x93, 0x8B, 0x03, 0x3B, 0x40, 0x38, + 0xFF, 0xF6, 0xFF, 0xDD, 0xFF, 0xF6, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFB, 0xFE, 0x7B, 0xFF, 0xF9, + 0x00, 0xFF, 0x00, 0x7F, 0x40, 0x3F, 0x00, 0x3F, + 0x00, 0x3F, 0x40, 0x3F, 0x01, 0x4E, 0xE9, 0x27, + 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xEF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x84, 0x7B, 0xC6, 0xBC, 0x87, 0xB6, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xF7, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xBE, 0x41, 0x5D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0xFF, 0xDD, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x02, 0xFC, 0xC3, 0x3F, 0xC7, 0xDB, 0x23, 0x2B, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0x6B, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x80, 0x7F, 0x40, 0x3F, 0x20, 0x2F, 0xF8, 0xF7, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFE, 0x03, 0xFB, 0x07, 0xF7, + 0x1E, 0xEE, 0x0E, 0xEE, 0x0F, 0xEF, 0x1F, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF8, + 0xFE, 0xF1, 0xFF, 0xF1, 0xFE, 0xF1, 0xFF, 0xF0, + 0x00, 0xFF, 0x10, 0x0F, 0xF4, 0xF3, 0xF8, 0xFB, + 0xD2, 0xD1, 0x64, 0x03, 0x00, 0xFF, 0x40, 0x3F, + 0xFF, 0xFF, 0xBF, 0xFF, 0xF7, 0x0F, 0xFB, 0x07, + 0x17, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0x00, 0xFF, 0x07, 0xF8, 0x07, 0xF7, 0x1F, 0xEF, + 0x0F, 0xEF, 0x1F, 0xDF, 0x7F, 0xBF, 0x33, 0xB3, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xF8, 0xFF, 0xF0, + 0xEF, 0xF0, 0xFF, 0xE0, 0xFB, 0xC4, 0xB7, 0xCC, + 0x00, 0xFF, 0x01, 0xFE, 0x01, 0x7D, 0x41, 0x3D, + 0x41, 0x3D, 0xC1, 0xBD, 0x83, 0xBF, 0xC7, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFE, + 0x7D, 0xFE, 0xFD, 0x7E, 0xFF, 0x7C, 0xBF, 0x7C, + 0x00, 0xFF, 0xF0, 0x0F, 0xE0, 0xEF, 0xF0, 0xF7, + 0xF0, 0xF7, 0xF8, 0xFF, 0xB4, 0xB2, 0xB5, 0xB3, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x1F, 0xFF, 0x0F, + 0xFF, 0x0F, 0xF7, 0x0F, 0xB7, 0x4F, 0xB7, 0x4E, + 0x00, 0xFF, 0x3E, 0xC1, 0x3F, 0xBE, 0x7C, 0xFC, + 0x7C, 0x7C, 0xFD, 0xFC, 0xDF, 0xDE, 0xDD, 0xDC, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFD, 0x83, + 0xFD, 0x83, 0xFD, 0x03, 0xDD, 0x23, 0xDF, 0x23, + 0x00, 0xFF, 0x3F, 0xC0, 0x3F, 0xBF, 0x3F, 0xBF, + 0x38, 0xB8, 0x30, 0xB0, 0x7C, 0xFB, 0xF0, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xC0, + 0xB8, 0xC7, 0xB7, 0xCF, 0xF7, 0x8F, 0xF0, 0x8F, + 0x00, 0xFF, 0xE7, 0x18, 0xD3, 0xCB, 0xC7, 0xCF, + 0x1F, 0x07, 0x17, 0x07, 0x06, 0xF6, 0x07, 0x37, + 0xFF, 0xFF, 0xFF, 0xFF, 0xDB, 0x3C, 0xDF, 0x38, + 0x1F, 0xF8, 0xFF, 0xF8, 0xFE, 0xF9, 0x77, 0xF8, + 0x00, 0xFF, 0xF0, 0x0F, 0xF8, 0xFB, 0xFC, 0xFD, + 0x1F, 0x1E, 0x6E, 0x0E, 0x0C, 0x6C, 0x18, 0x18, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x07, 0xFD, 0x03, + 0x1F, 0xE1, 0xEF, 0xF1, 0xFD, 0xF3, 0x5B, 0xE7, + 0x00, 0xFF, 0x01, 0xFE, 0x0B, 0xF3, 0x0F, 0xEF, + 0x3F, 0xDF, 0x1C, 0xDC, 0x39, 0xB8, 0x3A, 0xB9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFC, 0xFF, 0xF0, + 0xFF, 0xE0, 0xDD, 0xE3, 0xFB, 0xC7, 0xBF, 0xC7, + 0x00, 0xFF, 0x10, 0x0F, 0xF0, 0xF3, 0xFC, 0xFD, + 0x3E, 0x3E, 0x6E, 0x0E, 0x07, 0xF7, 0x07, 0xF7, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x0F, 0xFF, 0x03, + 0x3F, 0xC1, 0xEE, 0xF1, 0xF7, 0xF8, 0xF7, 0xF8, + 0x00, 0xFF, 0xF8, 0x07, 0xF0, 0x77, 0x38, 0xBB, + 0x78, 0xBB, 0x9E, 0x5C, 0xBF, 0x5F, 0x0F, 0x6F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x8F, 0xBF, 0xC7, + 0xFB, 0xC7, 0xDF, 0xE3, 0xFD, 0xE2, 0xEF, 0xF0, + 0x00, 0xFF, 0x3E, 0xC1, 0x7E, 0xBD, 0x3C, 0xBD, + 0x72, 0x71, 0xE4, 0xE3, 0xC8, 0xC7, 0x90, 0x8F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xBB, 0xC7, + 0x77, 0x8F, 0xEF, 0x1F, 0xDF, 0x3F, 0xBF, 0x7F, + 0x03, 0xFF, 0x01, 0xFD, 0x00, 0xFE, 0x00, 0xFF, + 0x28, 0xC7, 0x3A, 0xD9, 0x1E, 0xEE, 0x0B, 0xF3, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xEF, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFB, + 0x00, 0x7C, 0xC0, 0xBF, 0xE0, 0xDF, 0x68, 0x66, + 0x3D, 0xBB, 0x2B, 0xC9, 0x03, 0xE1, 0xA1, 0xA3, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, + 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xE7, 0xFF, 0xAF, + 0x69, 0xE5, 0x4F, 0x43, 0xC2, 0xCA, 0x9E, 0x86, + 0xAC, 0x9C, 0x2D, 0x0C, 0x05, 0x04, 0x95, 0x85, + 0xFF, 0xFD, 0xFF, 0x5F, 0xFF, 0xFA, 0xFF, 0xBF, + 0xFF, 0xFD, 0xFF, 0x6F, 0xFF, 0x47, 0xFF, 0x95, + 0x1C, 0x2C, 0x49, 0x28, 0x5A, 0x19, 0xB9, 0x7A, + 0x34, 0xB2, 0xF3, 0x75, 0xD7, 0xD3, 0xCE, 0xCE, + 0xFF, 0x7F, 0xFF, 0xEB, 0xFF, 0xDF, 0xF7, 0xFF, + 0xFF, 0xBE, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xEE, + 0xD2, 0x4A, 0x5E, 0x46, 0x54, 0x44, 0xDD, 0xCD, + 0x88, 0x88, 0x98, 0x99, 0x0A, 0x09, 0x4C, 0x44, + 0xFF, 0xDA, 0xFF, 0x5F, 0xFF, 0x55, 0xFF, 0xFD, + 0xFF, 0xAB, 0xFF, 0xFB, 0xFF, 0x5B, 0xFF, 0x5C, + 0x2A, 0x26, 0x38, 0x30, 0x4E, 0x44, 0x80, 0x80, + 0x17, 0x01, 0xE9, 0x03, 0x51, 0x87, 0x28, 0x06, + 0xFF, 0xEE, 0xEF, 0xB9, 0xFF, 0x5F, 0xFF, 0xB2, + 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xDF, 0xFF, 0x3E, + 0xA0, 0xA7, 0x60, 0x67, 0xC0, 0xCF, 0x90, 0x8F, + 0xA0, 0x97, 0x18, 0x2B, 0x18, 0x33, 0x84, 0x83, + 0x7F, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, + 0xFF, 0xF7, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x8F, + 0x07, 0xF7, 0x03, 0xFB, 0x03, 0xFD, 0x00, 0xFF, + 0x00, 0xE7, 0x58, 0x98, 0x7F, 0xBF, 0x2F, 0xCF, + 0xF7, 0xF8, 0xFF, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xDB, 0xE7, 0xFF, 0xC0, 0xEF, 0xF0, + 0xD0, 0xCF, 0xF9, 0xF6, 0xF0, 0xF6, 0xF5, 0x71, + 0xF3, 0x73, 0xFB, 0xFB, 0xEF, 0xEF, 0xCF, 0xCF, + 0xDF, 0x3F, 0xFF, 0x0F, 0xF6, 0x0F, 0xF7, 0x8E, + 0xF7, 0x8C, 0xF3, 0x0C, 0xE7, 0x18, 0xDF, 0x30, + 0x77, 0x73, 0xE1, 0xE5, 0xE1, 0xE1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x01, 0x40, 0x3E, + 0xFF, 0x8C, 0xED, 0x1E, 0xE1, 0x1E, 0xFF, 0x00, + 0xFF, 0x00, 0x81, 0x7E, 0x7F, 0xFE, 0xFE, 0xFF, + 0xA3, 0x9B, 0xA3, 0x9B, 0xA3, 0x9B, 0xC7, 0xDF, + 0xC7, 0xDF, 0xDF, 0xC7, 0xD6, 0xC6, 0xD6, 0xC6, + 0xBF, 0x7C, 0xBF, 0x7C, 0xBB, 0x7C, 0xFF, 0x38, + 0xFF, 0x38, 0xDF, 0x38, 0xDE, 0x39, 0xD6, 0x39, + 0x75, 0x71, 0x3B, 0x3B, 0x7B, 0x3B, 0xDF, 0x9F, + 0x5E, 0x1E, 0x1E, 0x5E, 0x3E, 0x5E, 0x19, 0x78, + 0x37, 0xCE, 0x7F, 0xC4, 0x7B, 0xC4, 0x5F, 0xE0, + 0xDE, 0xE1, 0xFF, 0xE1, 0xFD, 0xE3, 0xFB, 0xE7, + 0x9C, 0x9D, 0x98, 0x99, 0x1A, 0x19, 0x7E, 0x3D, + 0x7A, 0x39, 0xB9, 0x3A, 0x30, 0xB2, 0x30, 0xB2, + 0x9F, 0x63, 0xDB, 0x67, 0x5B, 0xE7, 0xFB, 0xC7, + 0xFF, 0xC7, 0xFF, 0xC7, 0xB7, 0xCF, 0xB6, 0xCF, + 0x7F, 0x7F, 0x7F, 0x7F, 0x60, 0x60, 0xE8, 0xE7, + 0xF8, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0xFF, 0x80, 0x6F, 0x9F, 0xEF, 0x1F, + 0xEF, 0x1F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xA7, 0x97, 0xEF, 0xDF, 0x3E, 0x0E, 0x0E, 0xEE, + 0x0C, 0xEC, 0x8E, 0xAE, 0xFF, 0xDF, 0xBF, 0x9F, + 0xB7, 0x78, 0xBF, 0x70, 0xFF, 0xF1, 0xFF, 0xF1, + 0xED, 0xF3, 0xEE, 0x71, 0xBF, 0x60, 0xFF, 0x60, + 0xF0, 0xF1, 0xF8, 0xFB, 0x1C, 0x1D, 0x1D, 0xFC, + 0x1D, 0xDC, 0x3D, 0x3C, 0xFC, 0xFD, 0xE2, 0xE1, + 0xF7, 0x0F, 0xFB, 0x07, 0xDF, 0xE3, 0xFD, 0xE3, + 0xFD, 0xE3, 0x3F, 0xC3, 0xFB, 0x07, 0xEF, 0x1F, + 0x70, 0xF3, 0xF4, 0x73, 0xF4, 0x73, 0x74, 0xF3, + 0x38, 0xBB, 0x7E, 0xBE, 0x1F, 0xDF, 0x07, 0xE7, + 0xF7, 0x8F, 0xF7, 0x8F, 0xF7, 0x8F, 0xF7, 0x8F, + 0xBB, 0xC7, 0xFE, 0xC1, 0xFF, 0xE0, 0xF7, 0xF8, + 0x07, 0xF7, 0x06, 0xF6, 0x1E, 0xEE, 0x0F, 0xEF, + 0x5C, 0x9C, 0x7D, 0x7C, 0xF0, 0xF1, 0xE0, 0xE3, + 0xF7, 0xF8, 0xF6, 0xF9, 0xFE, 0xF1, 0xEE, 0xF1, + 0xDD, 0xE3, 0x7F, 0x83, 0xF7, 0x0F, 0xEF, 0x1F, + 0x0F, 0x6F, 0x0F, 0x7F, 0x06, 0x76, 0x8E, 0x7E, + 0x9F, 0x6F, 0x0E, 0xEE, 0x0E, 0xEE, 0x0C, 0xEC, + 0xFF, 0xF0, 0xFF, 0xF0, 0xF6, 0xF9, 0xFE, 0xF1, + 0xFE, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xED, 0xF3, + 0x80, 0x9F, 0x80, 0xBF, 0x40, 0x3F, 0x80, 0x7F, + 0x80, 0x7F, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x03, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x05, 0x02, 0xF0, 0x0F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x90, 0x88, 0x63, 0x9D, 0x03, 0xFD, 0x01, 0xFD, + 0x00, 0xFC, 0x00, 0xFC, 0x01, 0xFE, 0x00, 0xFF, + 0x7F, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, + 0x88, 0x90, 0xA3, 0x9C, 0x00, 0x3F, 0x40, 0x3F, + 0x00, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x00, 0xFF, + 0xFF, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x54, 0x08, 0xC1, 0x3E, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xA0, 0x1F, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x28, 0x07, 0x70, 0x8F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x15, 0xE5, 0x03, 0xFC, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x90, 0x88, 0xC0, 0x3F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x04, 0x08, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x80, 0x71, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x04, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x80, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x30, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x08, 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0A, 0xF2, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x88, 0x87, 0xC0, 0x3F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; From deb5e218425d551cd042565c2c97b88e57ff1203 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Nov 2018 00:44:18 +0200 Subject: [PATCH 0770/1216] More colorization commands --- Core/sgb.c | 98 +++++++++++++++++++++++++++++++++++++++++++----------- Core/sgb.h | 4 +-- 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index 45ae7494..4edd08f7 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -12,6 +12,8 @@ enum { MLT_REQ = 0x11, CHR_TRN = 0x13, PCT_TRN = 0x14, + ATTR_TRN = 0x15, + ATTR_SET = 0x16, MASK_EN = 0x17, }; @@ -22,6 +24,14 @@ typedef enum { MASK_COLOR_0, } mask_mode_t; +typedef enum { + TRANSFER_LOW_TILES, + TRANSFER_HIGH_TILES, + TRANSFER_BORDER_DATA, + TRANSFER_PALETTES, + TRANSFER_ATTRIBUTES, +} transfer_dest_t; + #define SGB_PACKET_SIZE 16 static inline void pal_command(GB_gameboy_t *gb, unsigned first, unsigned second) { @@ -38,6 +48,19 @@ static inline void pal_command(GB_gameboy_t *gb, unsigned first, unsigned second } } +static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index) +{ + if (file_index > 0x2C) return; + uint8_t *output = gb->sgb->attribute_map; + for (unsigned i = 0; i < 90; i++) { + uint8_t byte = gb->sgb->attribute_files[file_index * 90 + i]; + for (unsigned j = 4; j--;) { + *(output++) = byte >> 6; + byte <<= 2; + } + } +} + static void command_ready(GB_gameboy_t *gb) { /* SGB header commands are used to send the contents of the header to the SNES CPU. @@ -149,15 +172,17 @@ static void command_ready(GB_gameboy_t *gb) &gb->sgb->ram_palettes[4 * (gb->sgb->command[7] + (gb->sgb->command[8] & 1) * 0x100)], 8); + if (gb->sgb->command[9] & 0x80) { + load_attribute_file(gb, gb->sgb->command[9] & 0x3F); + } + if (gb->sgb->command[9] & 0x40) { gb->sgb->mask_mode = MASK_DISABLED; } break; case PAL_TRN: gb->sgb->vram_transfer_countdown = 2; - gb->sgb->tile_transfer = false; - gb->sgb->data_transfer = true; - gb->sgb->palette_transfer = true; + gb->sgb->transfer_dest = TRANSFER_PALETTES; break; case DATA_SND: // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this @@ -166,28 +191,40 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; gb->sgb->current_player = gb->sgb->player_count - 1; break; - case MASK_EN: - gb->sgb->mask_mode = gb->sgb->command[1] & 3; - break; case CHR_TRN: gb->sgb->vram_transfer_countdown = 2; - gb->sgb->tile_transfer = true; - gb->sgb->data_transfer = false; - gb->sgb->tile_transfer_high = gb->sgb->command[1] & 1; + gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES; break; case PCT_TRN: gb->sgb->vram_transfer_countdown = 2; - gb->sgb->tile_transfer = false; - gb->sgb->data_transfer = true; - gb->sgb->palette_transfer = false; + gb->sgb->transfer_dest = TRANSFER_BORDER_DATA; + break; + case ATTR_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES; + break; + case ATTR_SET: + load_attribute_file(gb, gb->sgb->command[0] & 0x3F); + + if (gb->sgb->command[0] & 0x40) { + gb->sgb->mask_mode = MASK_DISABLED; + } + break; + case MASK_EN: + gb->sgb->mask_mode = gb->sgb->command[1] & 3; break; default: + if ((gb->sgb->command[0] >> 3) == 8 && + (gb->sgb->command[1] & ~0x80) == 0 && + (gb->sgb->command[2] & ~0x80) == 0) { + /* Mute/dummy sound commands, ignore this command as it's used by many games at startup */ + break; + } GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { GB_log(gb, "%02x ", gb->sgb->command[i]); } GB_log(gb, "\n"); - ; } } @@ -323,8 +360,8 @@ void GB_sgb_render(GB_gameboy_t *gb) if (gb->sgb->vram_transfer_countdown) { if (--gb->sgb->vram_transfer_countdown == 0) { - if (gb->sgb->tile_transfer) { - uint8_t *base = &gb->sgb->pending_border.tiles[gb->sgb->tile_transfer_high? 0x80 * 8 * 8 : 0]; + if (gb->sgb->transfer_dest == TRANSFER_LOW_TILES || gb->sgb->transfer_dest == TRANSFER_HIGH_TILES) { + uint8_t *base = &gb->sgb->pending_border.tiles[gb->sgb->transfer_dest == TRANSFER_HIGH_TILES ? 0x80 * 8 * 8 : 0]; for (unsigned tile = 0; tile < 0x80; tile++) { unsigned tile_x = (tile % 10) * 16; unsigned tile_y = (tile / 10) * 8; @@ -337,9 +374,27 @@ void GB_sgb_render(GB_gameboy_t *gb) } } - else if (gb->sgb->data_transfer) { - unsigned size = gb->sgb->palette_transfer? 0x100 : 0x88; - uint16_t *data = gb->sgb->palette_transfer? gb->sgb->ram_palettes : gb->sgb->pending_border.raw_data; + else { + unsigned size = 0; + uint16_t *data = NULL; + + switch (gb->sgb->transfer_dest) { + case TRANSFER_PALETTES: + size = 0x100; + data = gb->sgb->ram_palettes; + break; + case TRANSFER_BORDER_DATA: + size = 0x88; + data = gb->sgb->pending_border.raw_data; + break; + case TRANSFER_ATTRIBUTES: + size = 0xFE; + data = (uint16_t *)gb->sgb->attribute_files; + break; + default: + return; // Corrupt state? + } + for (unsigned tile = 0; tile < size; tile++) { unsigned tile_x = (tile % 20) * 8; unsigned tile_y = (tile / 20) * 8; @@ -349,10 +404,15 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned x = 0; x < 8; x++) { *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; } +#ifdef GB_BIG_ENDIAN + if (gb->sgb->transfer_dest == TRANSFER_ATTRIBUTES) { + *data = __builtin_bswap16(*data); + } +#endif data++; } } - if (!gb->sgb->palette_transfer) { + if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { gb->sgb->border_animation = 64; } } diff --git a/Core/sgb.h b/Core/sgb.h index 531fb60e..7957201c 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -25,8 +25,7 @@ struct GB_sgb_s { uint8_t mask_mode; /* Data Transfer */ - uint8_t vram_transfer_countdown; - bool tile_transfer, tile_transfer_high, data_transfer, palette_transfer; + uint8_t vram_transfer_countdown, transfer_dest; /* Border */ struct { @@ -45,6 +44,7 @@ struct GB_sgb_s { uint16_t effective_palettes[4 * 4]; uint16_t ram_palettes[4 * 512]; uint8_t attribute_map[20 * 18]; + uint8_t attribute_files[0xFE0]; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From af5ef50cf91a73d0103738d204fd195b99770138 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Nov 2018 01:39:38 +0200 Subject: [PATCH 0771/1216] Fixing default border --- Core/sgb_border.inc | 266 ++++++++++++++++++++++---------------------- 1 file changed, 131 insertions(+), 135 deletions(-) diff --git a/Core/sgb_border.inc b/Core/sgb_border.inc index b4b22253..f3e1bf19 100644 --- a/Core/sgb_border.inc +++ b/Core/sgb_border.inc @@ -7,114 +7,114 @@ static const uint16_t tilemap[] = { 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1002, 0x1003, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, - 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, - 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, - 0x1004, 0x1004, 0x1004, 0x1004, 0x1004, 0x5003, 0x5002, 0x1001, - 0x1001, 0x1005, 0x1006, 0x1006, 0x1006, 0x1007, 0x1008, 0x1009, - 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, - 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, 0x1019, - 0x101A, 0x101B, 0x101C, 0x1006, 0x1006, 0x1006, 0x5005, 0x1001, - 0x1001, 0x101D, 0x101E, 0x101E, 0x101E, 0x101F, 0x1020, 0x1021, - 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, 0x1029, - 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, 0x1031, - 0x1032, 0x1033, 0x1034, 0x101E, 0x101E, 0x101E, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1001, 0x1003, 0x1004, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x5004, 0x5003, 0x1001, + 0x1001, 0x1006, 0x1007, 0x1007, 0x1007, 0x1008, 0x1009, 0x100A, + 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, 0x1012, + 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, 0x1019, 0x101A, + 0x101B, 0x101C, 0x101D, 0x1007, 0x1007, 0x1007, 0x5006, 0x1001, + 0x1001, 0x101E, 0x101F, 0x101F, 0x101F, 0x1020, 0x1021, 0x1022, + 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, 0x1029, 0x102A, + 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, 0x1031, 0x1032, + 0x1033, 0x1034, 0x1035, 0x101F, 0x101F, 0x101F, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1037, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1038, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1038, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1039, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0xC01D, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0x1039, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0x103A, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x1035, 0x103A, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x1036, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0x103B, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC036, 0x1035, 0x1035, 0x103B, 0x103C, 0x1001, - 0x1001, 0x101D, 0x1035, 0x1035, 0x1035, 0x103D, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x103C, 0x103D, 0x1001, + 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0x503D, 0x1035, 0x103E, 0x103F, 0x1001, 0x1001, - 0x1001, 0x1040, 0x1041, 0x1035, 0x1035, 0x1042, 0x1043, 0x1044, - 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, - 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, - 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1049, 0x104A, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, 0x104B, - 0x104B, 0x104C, 0x104D, 0x104E, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x104F, 0x1050, - 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, - 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, 0x1060, - 0x1061, 0x1062, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1063, 0x1064, - 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, - 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, - 0x1075, 0x1076, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1077, 0x1078, - 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, 0x1080, - 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, 0x1088, - 0x1089, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001 + 0x1000, 0x1000, 0xC037, 0x1036, 0x103E, 0x103F, 0x1001, 0x1001, + 0x1001, 0x1040, 0x1041, 0x1036, 0x1036, 0x1042, 0x1043, 0x1043, + 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, + 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, + 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1048, 0x1049, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, + 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, + 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, + 0x104A, 0x104B, 0x104C, 0x104D, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x104E, 0x104F, + 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, + 0x1058, 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, + 0x1060, 0x1061, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1062, 0x1063, + 0x1064, 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, + 0x106C, 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, + 0x1074, 0x1075, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1076, 0x1077, + 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, + 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, + 0x1088, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, }; const uint8_t tiles[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -125,21 +125,25 @@ const uint8_t tiles[] = { 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x02, 0xFC, 0x01, 0xFB, 0x07, 0xF4, - 0x0F, 0xE9, 0x3E, 0xD7, 0x1F, 0xE6, 0x7F, 0x8C, + 0x0F, 0xE9, 0x3F, 0xD7, 0x1F, 0xE6, 0x7F, 0x8C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE, 0xF8, 0xFB, - 0xF0, 0xF7, 0xE0, 0xEB, 0xC0, 0xFD, 0xC0, 0xFB, - 0xC0, 0x00, 0xFF, 0xE0, 0xFF, 0x00, 0xBF, 0x7F, - 0xBF, 0xE0, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0xFF, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xDF, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0xF7, 0xE0, 0xEA, 0xC0, 0xFD, 0xC0, 0xFB, + 0xC0, 0x00, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0xFF, + 0x7F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x77, 0xDC, 0x3F, 0xD8, 0x7F, 0xD8, 0x7F, 0x90, + 0x77, 0xD8, 0x3F, 0xD8, 0x7F, 0xD0, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, - 0x80, 0xEB, 0x80, 0xF7, 0x80, 0xB7, 0x80, 0xFF, + 0x80, 0xEF, 0x80, 0xF7, 0x80, 0xBF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, @@ -246,85 +250,85 @@ const uint8_t tiles[] = { 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x3C, 0xCB, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x87, 0xF4, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x39, 0x86, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC2, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xF3, 0x0D, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x04, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x73, 0xC6, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x9C, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xEB, 0x38, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x16, 0xF7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x9E, 0x61, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x26, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x0C, 0xF1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xA3, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xF3, 0x86, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x0C, 0x79, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x9D, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x66, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xDB, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x66, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xCE, 0x30, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x21, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x0D, 0xF0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xE3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xC1, 0x3E, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x3C, 0xC3, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x4D, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1A, 0xE5, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1E, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xE1, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x0C, 0xE1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF3, 0x9F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x0C, 0xF1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xE3, 0x3E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x13, 0xCC, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xE4, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xD8, 0x27, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x6F, 0xD8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, @@ -338,12 +342,12 @@ const uint8_t tiles[] = { 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x3C, 0xC3, 0x7E, 0x81, 0xFF, 0x81, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x81, - 0x3C, 0x3C, 0x42, 0x42, 0x81, 0x81, 0x81, 0x81, + 0xFF, 0x3C, 0xC3, 0x7E, 0x99, 0xE7, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, + 0x3C, 0xBD, 0x42, 0x42, 0x99, 0x81, 0x00, 0x00, 0x81, 0xFF, 0xC3, 0xFF, 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x81, 0x81, 0xC3, 0xC3, 0x7E, 0xFF, 0x3C, 0xBD, + 0x81, 0x81, 0xC3, 0xC3, 0x7E, 0x7E, 0x3C, 0xBD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFE, 0x09, 0xFE, 0x0B, 0xFE, 0x0B, 0xFC, 0x1B, 0xFE, 0x1B, 0xFC, 0x19, 0xE6, 0x19, 0xEC, 0x1B, @@ -361,10 +365,6 @@ const uint8_t tiles[] = { 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x1F, 0xBF, 0x1F, 0xEF, 0x3F, 0x7F, 0x3F, 0xDF, 0x7F, 0xBF, 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFE, 0x03, 0xFE, 0x07, 0xFF, 0x0E, 0xEE, 0x3D, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, @@ -381,23 +381,19 @@ const uint8_t tiles[] = { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xBF, 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xDF, - 0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFE, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF0, - 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFB, 0x1F, 0x3F, 0xFC, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE7, 0x00, 0xFB, From 67d52b78b6ca7d0d59cfe5ebe62984ecd917ef54 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Nov 2018 12:26:07 +0200 Subject: [PATCH 0772/1216] Handling interrupted SGB commands --- Core/sgb.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index 4edd08f7..9ee0a5d2 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -69,7 +69,6 @@ static void command_ready(GB_gameboy_t *gb) Checksum: Simple one byte sum for the following content bytes 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ - if ((gb->sgb->command[0] & 0xF1) == 0xF1) { uint8_t checksum = 0; for (unsigned i = 2; i < 0x10; i++) { @@ -285,9 +284,15 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) case 0: if (!gb->sgb->ready_for_pulse) return; - gb->sgb->ready_for_pulse = false; gb->sgb->ready_for_write = true; gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) != 0 || + gb->sgb->command_write_index == 0 || + gb->sgb->ready_for_stop) { + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + gb->sgb->ready_for_stop = false; + } if (gb->sgb->player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { gb->sgb->current_player++; gb->sgb->current_player &= gb->sgb->player_count - 1; From 6160f513aa3b18caa05fdd75f7520f673a5e7333 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Nov 2018 18:52:11 +0200 Subject: [PATCH 0773/1216] CRT filter --- Cocoa/GBPreferencesWindow.m | 1 + Cocoa/Preferences.xib | 1 + SDL/gui.c | 1 + Shaders/CRT.fsh | 163 ++++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 Shaders/CRT.fsh diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 73406d6a..97f37d74 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -29,6 +29,7 @@ @"Bilinear", @"SmoothBilinear", @"LCD", + @"CRT", @"Scale2x", @"Scale4x", @"AAScale2x", diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 747a6824..f712a243 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -46,6 +46,7 @@ + diff --git a/SDL/gui.c b/SDL/gui.c index ed9f3573..4de76f51 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -427,6 +427,7 @@ struct shader_name { {"Bilinear", "Bilinear"}, {"SmoothBilinear", "Smooth Bilinear"}, {"LCD", "LCD Display"}, + {"CRT", "CRT Display"}, {"Scale2x", "Scale2x"}, {"Scale4x", "Scale4x"}, {"AAScale2x", "Anti-aliased Scale2x"}, diff --git a/Shaders/CRT.fsh b/Shaders/CRT.fsh new file mode 100644 index 00000000..cbb15283 --- /dev/null +++ b/Shaders/CRT.fsh @@ -0,0 +1,163 @@ +#define COLOR_LOW 0.7 +#define COLOR_HIGH 1.0 +#define VERTICAL_BORDER_DEPTH 0.6 +#define SCANLINE_DEPTH 0.3 +#define CURVENESS 0.3 + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + /* Curve and pixel ratio */ + float y_curve = cos(position.x - 0.5) * CURVENESS + (1 - CURVENESS); + float y_multiplier = 8.0 / 7.0 / y_curve; + position.y *= y_multiplier; + position.y -= (y_multiplier - 1) / 2; + if (position.y < 0.0) return vec4(0,0,0,0); + if (position.y > 1.0) return vec4(0,0,0,0); + + float x_curve = cos(position.y - 0.5) * CURVENESS + (1 - CURVENESS); + float x_multiplier = 1/x_curve; + position.x *= x_multiplier; + position.x -= (x_multiplier - 1) / 2; + if (position.x < 0.0) return vec4(0,0,0,0); + if (position.x > 1.0) return vec4(0,0,0,0); + + /* Setting up common vars */ + vec2 pos = fract(position * input_resolution); + vec2 sub_pos = fract(position * input_resolution * 6); + + vec4 center = texture(image, position); + vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0)); + vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0)); + + /* Vertical blurring */ + if (pos.y < 1.0 / 6.0) { + center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + } + else if (pos.y > 5.0 / 6.0) { + center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + } + + /* Scanlines */ + float scanline_multiplier; + if (pos.y < 0.5) { + scanline_multiplier = (pos.y * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else { + scanline_multiplier = ((1 - pos.y) * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + center *= scanline_multiplier; + left *= scanline_multiplier; + right *= scanline_multiplier; + + /* Vertical seperator for shadow masks */ + bool odd = (int)(position * input_resolution).x & 1; + if (odd) { + pos.y += 0.5; + pos.y = fract(pos.y); + } + + if (pos.y < 1.0 / 3.0) { + float gradient_position = pos.y * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + else if (pos.y > 2.0 / 3.0) { + float gradient_position = (1 - pos.y) * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + + /* Blur the edges of the separators of adjacent columns */ + if (pos.x < 1.0 / 6.0 || pos.x > 5.0 / 6.0) { + pos.y += 0.5; + pos.y = fract(pos.y); + + if (pos.y < 1.0 / 3.0) { + float gradient_position = pos.y * 3.0; + if (pos.x < 0.5) { + gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + } + else { + gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + } + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + else if (pos.y > 2.0 / 3.0) { + float gradient_position = (1 - pos.y) * 3.0; + if (pos.x < 0.5) { + gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + } + else { + gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + } + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + } + + + /* Subpixel blurring, like LCD filter*/ + + vec4 midleft = mix(left, center, 0.5); + vec4 midright = mix(right, center, 0.5); + + vec4 ret; + if (pos.x < 1.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + sub_pos.x); + } + else if (pos.x < 2.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + sub_pos.x); + } + else if (pos.x < 3.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1), + sub_pos.x); + } + else if (pos.x < 4.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else if (pos.x < 5.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1), + sub_pos.x); + } + + /* Anti alias the curve */ + vec2 pixel_position = position * output_resolution; + if (pixel_position.x < 1) { + ret *= pixel_position.x; + } + else if (pixel_position.x > output_resolution.x - 1) { + ret *= output_resolution.x - pixel_position.x; + } + if (pixel_position.y < 1) { + ret *= pixel_position.y; + } + else if (pixel_position.y > output_resolution.y - 1) { + ret *= output_resolution.y - pixel_position.y; + } + + + return ret; +} From a7d4c091a0525fa8b137e2d9a6e12161b897cdd3 Mon Sep 17 00:00:00 2001 From: Anders Conbere Date: Tue, 20 Nov 2018 10:04:12 -0800 Subject: [PATCH 0774/1216] add FAQ file --- FAQ.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 00000000..b2246e45 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,7 @@ +# Attempt to build Mac binary fails with NSInternalInconsistencyException + +When building on the darwin platform SameBoy will attempt to make a native executable and UI. In this case the environment expects a bunch of stuff to be set up by XCode. Fix this issue by starting XCode and letting it install components and set up the environment. After it's done building SameBoy should work (wehther or not XCode continues to run). + +# Attempt to build SDL binaris on Mac fails on linking + +Sameboy expects you to have installed the SDL2 framework you can find the binaries on the [SLD homepage](https://www.libsdl.org/download-2.0.php). Mount the DMG and copy SDL2.framework to `/Library/Frameworks/`. From aaafd482ccd3776203832b10db8ce4336a3bb1eb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 22 Nov 2018 22:59:29 +0200 Subject: [PATCH 0775/1216] Fixed Cocoa OpenGL regression, it was completely broken --- Cocoa/GBViewGL.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Cocoa/GBViewGL.m b/Cocoa/GBViewGL.m index 15aed088..b80973e4 100644 --- a/Cocoa/GBViewGL.m +++ b/Cocoa/GBViewGL.m @@ -27,6 +27,7 @@ { [super flip]; dispatch_async(dispatch_get_main_queue(), ^{ + [self.internalView setNeedsDisplay:YES]; [self setNeedsDisplay:YES]; }); } From 3ba1364d609740289e7b5ffdbf21de1a992b3e36 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Nov 2018 01:09:14 +0200 Subject: [PATCH 0776/1216] Fixed a boot ROM bug that made a few Nintendo games use incorrect object palettes --- BootROMs/cgb_boot.asm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 31fbeabc..fa826f5f 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -429,8 +429,11 @@ Dups4thLetterArray: PaletteCombinations: palette_comb: MACRO ; Obj0, Obj1, Bg - db \1 * 8, \2 * 8, \3 *8 - ENDM + db (\1) * 8, (\2) * 8, (\3) *8 +ENDM +raw_palette_comb: MACRO ; Obj0, Obj1, Bg + db (\1) * 2, (\2) * 2, (\3) * 2 +ENDM palette_comb 4, 4, 29 palette_comb 18, 18, 18 palette_comb 20, 20, 20 @@ -453,7 +456,7 @@ palette_comb: MACRO ; Obj0, Obj1, Bg palette_comb 4, 4, 18 palette_comb 4, 4, 20 palette_comb 19, 19, 9 - palette_comb 3, 3, 11 + raw_palette_comb 4 * 4 - 1, 4 * 4 - 1, 11 * 4 palette_comb 17, 17, 2 palette_comb 4, 4, 2 palette_comb 4, 4, 3 @@ -465,8 +468,8 @@ palette_comb: MACRO ; Obj0, Obj1, Bg 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 + raw_palette_comb 28 * 4 - 1, 0 * 4, 14 * 4 + raw_palette_comb 28 * 4 - 1, 4 * 4, 15 * 4 palette_comb 19, 22, 9 palette_comb 16, 28, 10 palette_comb 4, 23, 28 From 558f9b9bae46a9cce9dd5e0e59fb3d9d422549ac Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Nov 2018 14:16:48 +0200 Subject: [PATCH 0777/1216] =?UTF-8?q?Updated=20SGB=20border,=20add=20?= =?UTF-8?q?=E2=80=9C2=E2=80=9D=20to=20SGB2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/sgb.c | 10 + Core/sgb_border.inc | 1152 +++++++++++++++++++++---------------------- 2 files changed, 581 insertions(+), 581 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index 9ee0a5d2..5bd603ca 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -510,6 +510,16 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb) } } + if (gb->model != GB_MODEL_SGB2) { + /* Delete the "2" */ + gb->sgb->border.map[25 * 32 + 25] = gb->sgb->border.map[25 * 32 + 26] = + gb->sgb->border.map[26 * 32 + 25] = gb->sgb->border.map[26 * 32 + 26] = + gb->sgb->border.map[27 * 32 + 25] = gb->sgb->border.map[27 * 32 + 26] = + gb->sgb->border.map[0]; + + /* Re-center */ + memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0])); + } gb->sgb->effective_palettes[0] = 0x639E; gb->sgb->effective_palettes[1] = 0x263A; gb->sgb->effective_palettes[2] = 0x10D4; diff --git a/Core/sgb_border.inc b/Core/sgb_border.inc index f3e1bf19..d7d0a5c9 100644 --- a/Core/sgb_border.inc +++ b/Core/sgb_border.inc @@ -1,6 +1,7 @@ static const uint16_t palette[] = { - 0x0000, 0x18C6, 0x043C, 0x2529, 0x2954, 0x739C, 0x5AD5, 0x4100, - 0x4166, 0x20E7, 0x316B, 0x358C, 0x3DCE, 0x4A31, 0x5273, 0x1C75}; + 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6, + 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4631, 0x2033, 0x20EC, 0x18B7 +}; static const uint16_t tilemap[] = { 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, @@ -17,105 +18,106 @@ static const uint16_t tilemap[] = { 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x5004, 0x5003, 0x1001, 0x1001, 0x1006, 0x1007, 0x1007, 0x1007, 0x1008, 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, 0x1012, - 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, 0x1019, 0x101A, - 0x101B, 0x101C, 0x101D, 0x1007, 0x1007, 0x1007, 0x5006, 0x1001, - 0x1001, 0x101E, 0x101F, 0x101F, 0x101F, 0x1020, 0x1021, 0x1022, - 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, 0x1028, 0x1029, 0x102A, - 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, 0x1030, 0x1031, 0x1032, - 0x1033, 0x1034, 0x1035, 0x101F, 0x101F, 0x101F, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1013, 0x1014, 0x1015, 0x100E, 0x1016, 0x1017, 0x1018, 0x1019, + 0x101A, 0x101B, 0x101C, 0x1007, 0x1007, 0x1007, 0x5006, 0x1001, + 0x1001, 0x101D, 0x101E, 0x101E, 0x101E, 0x101F, 0x1020, 0x1021, + 0x1022, 0x1023, 0x1024, 0x1025, 0x5024, 0x1026, 0x1025, 0x1025, + 0x1027, 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, + 0x102F, 0x1030, 0x1031, 0x101E, 0x101E, 0x101E, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1038, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1034, 0x1035, 0x5034, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1039, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x8034, 0x1036, 0xC034, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0xC01E, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0x103A, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x1036, 0x103B, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1037, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x1036, 0x103C, 0x103D, 0x1001, - 0x1001, 0x101E, 0x1036, 0x1036, 0x1036, 0x1037, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1038, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, - 0x1000, 0x1000, 0xC037, 0x1036, 0x103E, 0x103F, 0x1001, 0x1001, - 0x1001, 0x1040, 0x1041, 0x1036, 0x1036, 0x1042, 0x1043, 0x1043, - 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, - 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, 0x1043, - 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1048, 0x1049, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, - 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, - 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, 0x104A, - 0x104A, 0x104B, 0x104C, 0x104D, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x104E, 0x104F, - 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, - 0x1058, 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, - 0x1060, 0x1061, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1062, 0x1063, - 0x1064, 0x1065, 0x1066, 0x1067, 0x1068, 0x1069, 0x106A, 0x106B, - 0x106C, 0x106D, 0x106E, 0x106F, 0x1070, 0x1071, 0x1072, 0x1073, - 0x1074, 0x1075, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, - 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1076, 0x1077, - 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, - 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, - 0x1088, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1039, 0x103A, 0x1001, + 0x1001, 0x103B, 0x103C, 0x1032, 0x1032, 0xC03C, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103E, 0x103F, 0x1040, 0x1041, 0x1001, 0x1001, + 0x1001, 0x1042, 0x1043, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1045, 0x1046, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1047, 0x1048, 0x1049, + 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F, 0x1050, 0x1051, + 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, 0x1059, + 0x105A, 0x105B, 0x105C, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x105D, 0x105E, 0x105F, + 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067, + 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F, + 0x1070, 0x1071, 0x1072, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1073, 0x1074, 0x1075, + 0x1076, 0x1077, 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, + 0x107E, 0x107F, 0x1080, 0x1081, 0x1082, 0x1083, 0x507A, 0x1084, + 0x1001, 0x1085, 0x507A, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, }; + const uint8_t tiles[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -123,546 +125,534 @@ const uint8_t tiles[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0x02, 0xFC, 0x01, 0xFB, 0x07, 0xF4, - 0x0F, 0xE9, 0x3F, 0xD7, 0x1F, 0xE6, 0x7F, 0x8C, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE, 0xF8, 0xFB, - 0xF0, 0xF7, 0xE0, 0xEA, 0xC0, 0xFD, 0xC0, 0xFB, - 0xC0, 0x00, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0xFF, - 0x7F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0xFF, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0x7F, - 0x00, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x77, 0xD8, 0x3F, 0xD8, 0x7F, 0xD0, 0x7F, 0x90, - 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, - 0x80, 0xEF, 0x80, 0xF7, 0x80, 0xBF, 0x80, 0xFF, - 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x07, 0xFB, 0x00, - 0xFB, 0x00, 0xFB, 0x00, 0xFB, 0x00, 0xFB, 0x00, - 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF8, 0x0C, 0xFF, - 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, 0x0C, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0x3C, 0xCB, 0x13, 0x6E, - 0xB7, 0x1C, 0xAF, 0x00, 0xB7, 0x1C, 0x13, 0x6E, - 0x00, 0xFF, 0x00, 0xFF, 0x87, 0xF4, 0xCC, 0xBD, - 0x68, 0xF3, 0x78, 0xFF, 0x68, 0xF3, 0xCC, 0xBD, - 0xFF, 0x00, 0xFF, 0x00, 0x20, 0x8F, 0x39, 0xC6, - 0xD9, 0x76, 0xC9, 0x36, 0xD9, 0x76, 0x39, 0xC6, - 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x70, 0x62, 0xFF, - 0x22, 0x8F, 0x22, 0xDF, 0x22, 0x8F, 0x62, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0x31, 0xCC, 0xF1, 0x0E, - 0xF2, 0x0C, 0xF1, 0x0C, 0xF2, 0x0D, 0xF2, 0x0C, - 0x00, 0xFF, 0x00, 0xFF, 0x86, 0x7B, 0x06, 0xFB, - 0x07, 0xFF, 0x07, 0xFF, 0x05, 0xFE, 0x05, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0xC7, 0x11, 0xC7, 0x30, - 0x96, 0x63, 0xD4, 0x00, 0x34, 0x63, 0x33, 0x80, - 0x00, 0xFF, 0x00, 0xFF, 0x38, 0xEE, 0x39, 0xEF, - 0x39, 0xFC, 0x7B, 0xFF, 0xDB, 0x9C, 0xDE, 0x7F, - 0xFF, 0x00, 0xFF, 0x00, 0x70, 0xCF, 0x7B, 0x80, - 0x9B, 0xE0, 0xBB, 0x00, 0x1B, 0xF0, 0xEB, 0x00, - 0x00, 0xFF, 0x00, 0xFF, 0x9F, 0x70, 0xC6, 0xFF, - 0x46, 0x7F, 0x66, 0xFF, 0xE6, 0x0F, 0x36, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0x02, 0xFF, 0x98, 0x65, - 0x98, 0x65, 0x86, 0x7B, 0x92, 0x7D, 0x92, 0x67, - 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0x45, 0x26, 0xFF, - 0x26, 0xFF, 0x3C, 0xC5, 0x28, 0xE7, 0x2C, 0xFD, - 0xFF, 0x00, 0xFF, 0x00, 0x4F, 0xE0, 0x61, 0xBB, - 0x61, 0x8E, 0x77, 0x98, 0x71, 0x84, 0x68, 0xBF, - 0x00, 0xFF, 0x00, 0xFF, 0xB3, 0xBF, 0x96, 0xCE, - 0x9C, 0xFB, 0x8C, 0xE7, 0x9E, 0xFF, 0x92, 0xF5, - 0xFF, 0x00, 0xFF, 0x00, 0xCC, 0x3B, 0xEC, 0x19, - 0xE6, 0x00, 0xF5, 0x1E, 0xF3, 0x08, 0xF9, 0x04, - 0x00, 0xFF, 0x00, 0xFF, 0x11, 0xE6, 0x13, 0xFE, - 0x1B, 0xFF, 0x0A, 0xF9, 0x0E, 0xF7, 0x0E, 0xFB, - 0xFF, 0x00, 0xFF, 0x00, 0xE4, 0x91, 0xED, 0x38, - 0x5D, 0x80, 0x2D, 0xC0, 0x1D, 0x30, 0x1D, 0xE0, - 0x00, 0xFF, 0x00, 0xFF, 0x1F, 0xFE, 0x96, 0xC7, - 0xB6, 0x7F, 0xB6, 0xFF, 0xE6, 0xCF, 0x66, 0xBF, - 0xFF, 0x00, 0xFF, 0x00, 0x0B, 0xF0, 0xDB, 0x00, - 0xDB, 0x00, 0xD8, 0x03, 0xDB, 0x00, 0xDB, 0x00, - 0x00, 0xFF, 0x00, 0xFF, 0xFE, 0x0F, 0x66, 0xFF, - 0x66, 0xFF, 0x67, 0xFC, 0x66, 0xFF, 0x66, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0xCE, 0x31, 0xCE, 0x31, - 0xCF, 0x30, 0x0F, 0xF0, 0xCF, 0x30, 0xCF, 0x30, - 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x21, 0xFF, - 0x21, 0xFF, 0xE0, 0x1F, 0x20, 0xFF, 0x21, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0x08, 0xF7, 0x4D, 0x00, - 0x7D, 0x00, 0x7D, 0x10, 0xED, 0x30, 0x4D, 0x10, - 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0x98, 0xB3, 0xFF, - 0x83, 0xFF, 0xE3, 0xEF, 0x13, 0xFF, 0xB3, 0xEF, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0xCF, 0x30, - 0xCF, 0x30, 0xC0, 0x3F, 0xCF, 0x30, 0xCF, 0x30, - 0x00, 0xFF, 0x00, 0xFF, 0xDF, 0x21, 0x10, 0xFF, - 0x10, 0xFF, 0x1E, 0xE1, 0x10, 0xFF, 0x10, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0x04, 0xFF, 0x30, 0xCB, - 0x30, 0xCB, 0x0C, 0xF7, 0x24, 0xFB, 0x24, 0xCF, - 0x00, 0xFF, 0x00, 0xFF, 0x79, 0x8A, 0x4D, 0xFF, - 0x4D, 0xFF, 0x79, 0x8A, 0x51, 0xCF, 0x59, 0xFB, - 0xFF, 0x00, 0xFF, 0x00, 0x0A, 0xE5, 0xFB, 0x0C, - 0xF7, 0x00, 0x0B, 0xF0, 0xF7, 0x00, 0xFB, 0x0C, - 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x1E, 0x06, 0xF3, - 0x0C, 0xFF, 0xEC, 0x1F, 0x0C, 0xFF, 0x06, 0xF3, - 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0xC1, 0x8E, 0x20, - 0xCE, 0x10, 0xCF, 0x30, 0xCF, 0x10, 0x8E, 0x20, - 0x00, 0xFF, 0x00, 0xFF, 0xE0, 0x7E, 0x71, 0xDF, - 0x31, 0xFF, 0x10, 0xEF, 0x30, 0xFF, 0x71, 0xDF, - 0xFF, 0x00, 0xFF, 0x00, 0x0C, 0xE1, 0x4C, 0xA6, - 0xF9, 0x04, 0x69, 0x16, 0xE9, 0x3C, 0xC4, 0x3E, - 0x00, 0xFF, 0x00, 0xFF, 0xF3, 0x9F, 0x93, 0xFD, - 0x86, 0xFF, 0xE4, 0xFB, 0x16, 0xC7, 0x93, 0xED, - 0xFF, 0x00, 0xFF, 0x00, 0x09, 0xF2, 0xE9, 0x1A, - 0xF1, 0x02, 0xE9, 0x02, 0xF1, 0x06, 0xEC, 0x1E, - 0x00, 0xFF, 0x00, 0xFF, 0xE6, 0x3F, 0x36, 0xE7, - 0x1E, 0xFF, 0x1E, 0xFF, 0x1A, 0xFD, 0x33, 0xE1, - 0xFF, 0x00, 0xFF, 0x00, 0xC1, 0x2C, 0xC1, 0x2F, - 0xC0, 0x2D, 0xC3, 0x2C, 0xD3, 0x3D, 0x93, 0x3C, - 0x00, 0xFF, 0x00, 0xFF, 0x36, 0xFB, 0x36, 0xF8, - 0x37, 0xFF, 0x35, 0xFF, 0x24, 0xFE, 0x64, 0xCF, - 0xFF, 0x00, 0xFF, 0x00, 0xD8, 0x67, 0xDB, 0x60, - 0xDB, 0x60, 0x5B, 0x60, 0x5B, 0xE0, 0x9B, 0xE0, - 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0x98, 0x2C, 0x9F, - 0x2C, 0x9F, 0xAC, 0x9F, 0xAC, 0x5F, 0x6C, 0x9F, - 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xC0, 0x1F, 0x60, - 0xBF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x1F, 0x60, - 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0xC0, 0xBF, - 0x60, 0xFF, 0x60, 0xFF, 0x60, 0xFF, 0xC0, 0xBF, - 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, - 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, 0x7F, 0x90, - 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, - 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x80, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xF8, 0x07, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, - 0x3C, 0xCB, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x87, 0xF4, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x39, 0x86, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xC2, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF3, 0x0D, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x04, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x73, 0xC6, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x9C, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xEB, 0x38, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x16, 0xF7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x9E, 0x61, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x26, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x0C, 0xF1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xA3, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xF3, 0x86, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x0C, 0x79, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x9D, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x66, 0xBF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xDB, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x66, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xCE, 0x30, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x21, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x0D, 0xF0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xE3, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xC1, 0x3E, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x1F, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x3C, 0xC3, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x4D, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x1A, 0xE5, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xF3, 0x1E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x1E, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xE1, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x0C, 0xE1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xF3, 0x9F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x0C, 0xF1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xE3, 0x3E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x13, 0xCC, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xE4, 0x3F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0xD8, 0x27, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x6F, 0xD8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x3F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7, + 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00, + 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00, + 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF, + 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9, + 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9, + 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22, + 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22, + 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF, + 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF, + 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00, + 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF, + 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, + 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41, + 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F, + 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7, + 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0, + 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB, + 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00, + 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF, + 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00, + 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72, + 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08, + 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27, + 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88, + 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00, + 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77, + 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88, + 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF, + 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00, + 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9, + 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02, + 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74, + 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89, + 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89, + 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7, + 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4, + 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10, + 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12, + 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF, + 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00, + 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF, + 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00, + 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F, + 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80, + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF, + 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, + 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81, + 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, + 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, + 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02, + 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, + 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F, + 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04, + 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7, + 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08, + 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7, + 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40, + 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF, + 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F, + 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0, + 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xFF, 0x01, 0xFD, 0x03, 0xFF, 0x03, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x01, 0xFE, 0x02, 0xFE, 0x02, 0xFC, 0x00, + 0x0E, 0xEE, 0x3F, 0xFF, 0x75, 0x71, 0xFB, 0xE7, + 0xE3, 0xCB, 0xC7, 0x9F, 0x07, 0x3E, 0x84, 0x7C, + 0xFB, 0x1B, 0xE6, 0x26, 0x8E, 0x82, 0x3E, 0x22, + 0x7C, 0x54, 0x7D, 0x25, 0xF9, 0x40, 0xFB, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0xFF, + 0x00, 0x7F, 0x80, 0x4F, 0x31, 0x7F, 0x71, 0xFD, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, + 0xFF, 0x00, 0xFF, 0x30, 0xFF, 0xB1, 0xDE, 0x52, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x7B, 0x87, 0xFF, 0x8E, 0xFE, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x3C, 0xC3, 0x7E, 0x99, 0xE7, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, - 0x3C, 0xBD, 0x42, 0x42, 0x99, 0x81, 0x00, 0x00, - 0x81, 0xFF, 0xC3, 0xFF, 0xFF, 0x7E, 0xFF, 0x3C, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x81, 0x81, 0xC3, 0xC3, 0x7E, 0x7E, 0x3C, 0xBD, + 0xFF, 0x00, 0xFF, 0x84, 0xFA, 0x82, 0xF9, 0x88, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFE, 0x09, 0xFE, 0x0B, 0xFE, 0x0B, 0xFC, 0x1B, - 0xFE, 0x1B, 0xFC, 0x19, 0xE6, 0x19, 0xEC, 0x1B, - 0x01, 0xFF, 0x01, 0xFD, 0x01, 0xFD, 0x01, 0xEF, - 0x01, 0xEF, 0x03, 0xEF, 0x03, 0xFF, 0x03, 0xF7, - 0xFC, 0x33, 0xF8, 0x37, 0xCC, 0x33, 0xF8, 0x37, - 0xF8, 0x6F, 0x98, 0x67, 0xF0, 0xCF, 0xF0, 0xDF, - 0x03, 0xDD, 0x03, 0xDF, 0x07, 0xFF, 0x07, 0xEF, - 0x07, 0xB3, 0x0F, 0xFF, 0x0F, 0x77, 0x0F, 0x77, - 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x03, 0xFD, 0x03, - 0xFF, 0x06, 0xF9, 0x0E, 0xF7, 0x0D, 0xFE, 0x19, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFF, - 0x00, 0xFB, 0x00, 0xF7, 0x00, 0xFE, 0x01, 0xEE, - 0xE0, 0xDF, 0xC0, 0xBF, 0xC0, 0xBF, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xFD, 0xE3, 0x7B, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xC3, 0xBC, 0x24, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xFB, 0xF7, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x01, 0xFE, 0x02, 0x7C, 0x64, 0xFC, 0xB4, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x7F, 0x80, 0xFF, 0xA0, 0x2F, 0xF0, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x70, 0x8F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFD, 0x00, 0xF7, + 0x00, 0xFF, 0x11, 0xEE, 0x11, 0xEE, 0x10, 0xEF, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x03, 0xF8, 0x0F, + 0xF0, 0x0F, 0xE0, 0x1F, 0xE1, 0x1E, 0xE0, 0x1F, + 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xE7, 0x00, 0xFB, + 0xC4, 0x3B, 0x98, 0x03, 0x00, 0xEF, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF8, 0x07, 0xFC, + 0x07, 0xF8, 0xEF, 0x74, 0xFF, 0x10, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xEF, 0x00, 0xFF, 0x22, 0xDD, 0x06, 0xB9, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x07, 0xF0, 0x0F, + 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, 0xC4, 0x7B, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7D, 0x02, 0xFD, + 0x02, 0xBD, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0x7E, 0x83, 0x7C, 0x83, + 0x7C, 0xC3, 0x7C, 0x83, 0x3C, 0xC3, 0x3C, 0xC3, + 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xEF, 0x00, 0xFF, + 0x00, 0xF7, 0x00, 0xF7, 0x48, 0xB6, 0x48, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF8, 0x0F, 0xF8, 0x07, 0xF9, 0x06, 0xF9, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x02, 0xFC, + 0x02, 0x7D, 0x02, 0xFD, 0x02, 0xFD, 0x20, 0xDD, + 0xFF, 0x00, 0xFF, 0x00, 0xC1, 0x7E, 0x81, 0x7F, + 0x81, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF, + 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0F, 0xF0, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F, + 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB, + 0xE4, 0x1B, 0x00, 0x1F, 0x00, 0xFF, 0x80, 0x3F, + 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7, + 0x18, 0xE7, 0xF8, 0xE7, 0xF8, 0x07, 0x78, 0xC7, + 0x00, 0xFF, 0x00, 0xFF, 0x04, 0xF9, 0x00, 0xFF, + 0x71, 0x8E, 0x89, 0x06, 0x81, 0x7E, 0xE1, 0x1E, + 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFE, 0x01, 0xFE, + 0x00, 0xFF, 0x70, 0xFF, 0x70, 0x8F, 0x01, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xF9, 0x00, 0xFF, + 0x00, 0xFF, 0x03, 0xFC, 0x06, 0xB9, 0x44, 0xBB, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF0, 0x0F, + 0xE0, 0x1F, 0xC1, 0x3E, 0xC3, 0x7C, 0x87, 0x78, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFD, + 0xC0, 0x3F, 0x11, 0x0E, 0x00, 0xFF, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x03, 0xFE, + 0x01, 0xFE, 0xE0, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x77, 0x40, 0xBF, + 0x04, 0xBB, 0x00, 0xFE, 0x00, 0xDD, 0x00, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x87, 0xF8, 0x87, 0x78, + 0xC3, 0x7C, 0xC3, 0x3D, 0xE2, 0x3F, 0xE0, 0x9F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFD, 0x06, 0xF9, + 0x0C, 0x73, 0x08, 0xF7, 0x10, 0xE7, 0x20, 0xCF, + 0xFF, 0x00, 0xFF, 0x00, 0xC3, 0x3E, 0x83, 0x7C, + 0x87, 0xF8, 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF7, 0x00, 0xFF, + 0x01, 0xDE, 0x06, 0xF8, 0x1C, 0xC3, 0x00, 0xF3, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x0F, 0xE0, 0x1F, + 0xE0, 0x3F, 0xC3, 0x3D, 0xE7, 0x38, 0xFF, 0x0C, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x00, 0xFF, + 0x00, 0xF7, 0x08, 0x77, 0x08, 0xF7, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x0F, 0xF0, + 0x0F, 0xF8, 0x87, 0xF8, 0x87, 0x78, 0x07, 0xF8, + 0x03, 0xFF, 0x03, 0xFF, 0x01, 0xFF, 0x00, 0xFE, + 0x18, 0xDF, 0x1C, 0xFD, 0x0F, 0xEF, 0x07, 0xF7, + 0xFC, 0x00, 0xFE, 0x02, 0xFF, 0x01, 0xFF, 0x01, + 0xFF, 0x38, 0xF3, 0x12, 0xF9, 0x19, 0xFC, 0x0C, + 0x02, 0x79, 0x80, 0xFD, 0xC0, 0xDF, 0xF0, 0xFE, + 0x79, 0x3F, 0x19, 0xDB, 0x19, 0xFB, 0xF9, 0xF7, + 0xFF, 0x84, 0xFF, 0x82, 0x7F, 0x60, 0x9F, 0x91, + 0xEF, 0xA9, 0xF6, 0x34, 0xFE, 0x1C, 0x1F, 0x11, + 0x63, 0xEF, 0xF3, 0xEB, 0xC6, 0xCE, 0xEF, 0xDE, + 0x8C, 0x9C, 0xDE, 0xBD, 0x9C, 0x9D, 0xFF, 0xEF, + 0x9E, 0x02, 0xBC, 0xA4, 0x3D, 0x14, 0x7B, 0x4A, + 0x73, 0x21, 0xF7, 0x94, 0xF7, 0xF6, 0xFE, 0xEE, + 0x8D, 0xEC, 0x9E, 0x7D, 0x1C, 0x5B, 0x38, 0xFA, + 0x79, 0xF7, 0x71, 0x75, 0xF3, 0xF3, 0xEF, 0xCF, + 0xF3, 0x90, 0xF7, 0x14, 0xEF, 0xA8, 0xEF, 0x2D, + 0xCF, 0x41, 0x8E, 0x8A, 0x3C, 0x3C, 0x39, 0x19, + 0x67, 0xFF, 0xEF, 0xFE, 0xEC, 0xDC, 0xCF, 0xCF, + 0xDD, 0xDC, 0xDC, 0x9F, 0x2C, 0x2F, 0xD7, 0xC7, + 0xB9, 0x21, 0xBB, 0xAA, 0xB3, 0x81, 0x76, 0x76, + 0x77, 0x76, 0xE7, 0xA4, 0xD7, 0x44, 0xFB, 0xCB, + 0xB3, 0x37, 0x73, 0x72, 0xF4, 0xEC, 0xEF, 0xCD, + 0xCD, 0x09, 0x11, 0xF3, 0x29, 0xA7, 0xF1, 0xCF, + 0xCD, 0x49, 0xDF, 0xDE, 0xBF, 0xA5, 0x7F, 0x5D, + 0xF6, 0x32, 0xFE, 0x14, 0xFE, 0x70, 0xFF, 0xC1, + 0xF0, 0x77, 0xF0, 0x67, 0xE0, 0xCF, 0x80, 0x97, + 0xC8, 0xBB, 0x98, 0xBB, 0x90, 0xD3, 0xE8, 0xE7, + 0xDF, 0x58, 0xBF, 0x28, 0x7F, 0x50, 0x7F, 0x28, + 0xF7, 0x84, 0xFF, 0xDC, 0xEF, 0xA4, 0xDF, 0xC0, + 0x00, 0xFF, 0x04, 0xF3, 0x03, 0xF8, 0x00, 0xFF, + 0x08, 0xF7, 0x03, 0xFC, 0x00, 0xBF, 0x18, 0xC7, + 0xF0, 0x0F, 0xF8, 0x0F, 0xFE, 0x05, 0xFF, 0x00, + 0xE7, 0x18, 0xC0, 0x3F, 0xC0, 0x7F, 0xE0, 0x3F, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF6, 0x08, 0x77, + 0x08, 0xF5, 0x08, 0xF7, 0x10, 0xE7, 0x70, 0x87, + 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF9, 0x86, 0xF9, + 0x86, 0x7B, 0x0C, 0xF3, 0x08, 0xFF, 0x38, 0xCF, + 0x0A, 0xF1, 0x88, 0x77, 0x0E, 0xF1, 0x00, 0xFF, + 0x00, 0xFF, 0x7F, 0x80, 0x41, 0xBE, 0x81, 0x3E, + 0x84, 0x7F, 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x3E, 0xC1, 0x7E, 0x81, 0x7E, 0xC1, + 0x04, 0xFB, 0x04, 0xDB, 0x24, 0xDB, 0x20, 0xDF, + 0x20, 0xDF, 0x00, 0xFF, 0x08, 0xE7, 0x19, 0xE6, + 0x38, 0xC7, 0x38, 0xE7, 0x38, 0xC7, 0x18, 0xE7, + 0x18, 0xE7, 0x18, 0xE7, 0x10, 0xFF, 0x10, 0xEF, + 0x48, 0xB5, 0x80, 0x3F, 0x84, 0x7B, 0x80, 0x7F, + 0xA1, 0x5E, 0x21, 0x5E, 0x02, 0x7C, 0x02, 0x7D, + 0x46, 0xBB, 0x44, 0xFB, 0x40, 0xBF, 0x40, 0xBF, + 0xC0, 0x3F, 0xC1, 0xBE, 0xE1, 0x9F, 0xE3, 0x9C, + 0x60, 0x9D, 0x64, 0x99, 0x84, 0x3B, 0x84, 0x7B, + 0x04, 0x7B, 0x40, 0xBB, 0x41, 0xBA, 0x09, 0xF2, + 0x03, 0xFE, 0x43, 0xBE, 0x43, 0xFC, 0xC3, 0x3C, + 0xC7, 0xB8, 0x87, 0x7C, 0x86, 0x7D, 0x86, 0x7D, + 0x80, 0x7F, 0x80, 0x7F, 0x8F, 0x70, 0x10, 0xEF, + 0x10, 0xEF, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x48, 0xB7, 0x48, 0xB7, 0xC0, 0x3F, 0x01, 0xFE, + 0x01, 0xFE, 0x81, 0x2E, 0x50, 0xAF, 0x50, 0xAF, + 0x30, 0xCF, 0x30, 0xCF, 0xF0, 0x0F, 0xF0, 0x0F, + 0xF0, 0x0F, 0x70, 0xDF, 0x20, 0xDF, 0x60, 0x9F, + 0x06, 0xF8, 0x00, 0xFD, 0xF0, 0x0F, 0x00, 0x7E, + 0x00, 0xEE, 0xE2, 0x1C, 0x02, 0xFD, 0x0C, 0xF1, + 0x03, 0xFD, 0x03, 0xFE, 0xE1, 0x1E, 0xF1, 0x8F, + 0xF1, 0x1F, 0x01, 0xFF, 0x03, 0xFC, 0x07, 0xFA, + 0x08, 0xF3, 0x08, 0xF7, 0x08, 0xF7, 0x00, 0xFF, + 0x40, 0xBB, 0x01, 0xFE, 0x20, 0xDF, 0x18, 0xE7, + 0x87, 0x7C, 0x87, 0x78, 0x87, 0x78, 0x87, 0x78, + 0x87, 0x7C, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, + 0x08, 0xF7, 0x08, 0xF7, 0x01, 0xFE, 0x11, 0xEE, + 0x01, 0xDE, 0x82, 0x7C, 0x04, 0xF9, 0x38, 0xC3, + 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xE0, 0x1F, + 0xE1, 0x3E, 0x03, 0xFD, 0x07, 0xFA, 0x0F, 0xF4, + 0x10, 0x6F, 0x00, 0x7F, 0x01, 0x7E, 0x01, 0xFE, + 0x01, 0xFE, 0x11, 0xEE, 0x10, 0xEE, 0x12, 0xEC, + 0xE0, 0x9F, 0xF0, 0x8F, 0xF0, 0x8F, 0xF0, 0x0F, + 0xF0, 0x0F, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1F, + 0x40, 0x9F, 0x80, 0x3F, 0x80, 0x7F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0xA0, 0x7F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFB, 0x08, 0xF7, 0x00, 0xFF, + 0x01, 0xBE, 0x03, 0xFC, 0x00, 0x7F, 0x80, 0x7F, + 0xFE, 0x01, 0xFC, 0x07, 0xF0, 0x0F, 0xE0, 0x1F, + 0xC1, 0x7E, 0x80, 0x7F, 0x80, 0xFF, 0x00, 0xFF, + 0x08, 0xF7, 0x10, 0xE7, 0x60, 0x8F, 0xC0, 0x3F, + 0x80, 0x7F, 0xE0, 0x0F, 0x00, 0xEF, 0x00, 0xEF, + 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, 0x7F, 0x80, + 0xFF, 0x00, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, 0xF0, + 0x02, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xD0, 0xC6, 0x00, 0x1F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xC9, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xE7, 0x86, 0x01, 0x39, 0x01, 0xFF, 0x03, 0xFF, + 0x03, 0xFF, 0x00, 0xFC, 0x00, 0xFE, 0x00, 0xFF, + 0xFF, 0x9E, 0xFF, 0xC7, 0xFE, 0x00, 0xFE, 0x02, + 0xFF, 0x03, 0xFF, 0x02, 0xFF, 0x01, 0xFF, 0x00, + 0xC3, 0xD3, 0xC0, 0xBC, 0x80, 0xBF, 0x00, 0x7F, 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, - 0x1F, 0xBF, 0x1F, 0xEF, 0x3F, 0x7F, 0x3F, 0xDF, - 0x7F, 0xBF, 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0xFF, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, - 0xFE, 0x03, 0xFE, 0x07, 0xFF, 0x0E, 0xEE, 0x3D, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, - 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF5, 0x00, 0xDB, - 0xFE, 0x39, 0xE8, 0x77, 0x9C, 0xEB, 0x30, 0xDF, - 0x60, 0xBF, 0xC0, 0x7F, 0x80, 0xFF, 0x80, 0x7F, - 0x03, 0xD6, 0x03, 0xBD, 0x07, 0x73, 0x0F, 0xE7, - 0x1F, 0xC7, 0x3F, 0x8F, 0x7F, 0x1F, 0xFF, 0x3F, - 0x7F, 0x90, 0x7F, 0xD8, 0x7F, 0xD8, 0x77, 0x9C, - 0x3F, 0xEC, 0x3F, 0xC6, 0x1E, 0xF7, 0x17, 0xE9, - 0x80, 0xFF, 0x80, 0xB7, 0x80, 0xF7, 0xC0, 0xEB, - 0xC0, 0x9B, 0xE0, 0xFD, 0xE0, 0xDB, 0xF0, 0xEF, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xBF, 0xE0, + 0x7F, 0x6B, 0x7F, 0x03, 0xFF, 0xC0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0x1B, 0x00, 0x7C, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x23, 0xFF, 0x83, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC0, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x20, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x50, 0x4F, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x60, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xDF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF7, 0x08, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x0F, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF0, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x03, 0xFB, 0x1F, 0x3F, 0xFC, - 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE7, 0x00, 0xFB, - 0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x07, 0xFF, 0x1F, - 0xDF, 0x7C, 0x7C, 0xE3, 0xF2, 0x8D, 0xC8, 0x57, - 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF9, 0x00, 0xE6, - 0x00, 0xBB, 0x01, 0xFE, 0x07, 0x78, 0x3F, 0xA3, - 0xDF, 0x72, 0x5E, 0xE1, 0xF0, 0x9F, 0xF0, 0x0F, - 0x00, 0xFF, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, - 0x01, 0xBE, 0x07, 0xFD, 0x0F, 0xE3, 0x3F, 0xE7, - 0x7F, 0x9F, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x0B, 0xF0, 0x05, 0xFB, 0x01, 0xFE, 0x00, 0xFF, + 0x0C, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFC, 0xF7, 0xFE, 0xF8, 0xFF, 0xFC, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xBF, 0x7F, 0xFF, 0x00, 0x1F, 0xB0, 0x7F, 0x80, + 0xFF, 0x12, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x38, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0x6F, 0xFF, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0xFF, 0xFF, 0x00, 0xF5, 0x3C, 0xFE, 0x01, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x8F, 0x30, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xCF, 0xFF, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x7E, 0xC0, 0xEB, 0x14, 0x30, 0xCF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF0, 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x01, 0xBF, 0x0F, 0xF8, 0xFF, 0x03, 0xFF, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x60, 0x9F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x03, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0x0F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFE, - 0x00, 0xFE, 0x01, 0xFD, 0x01, 0xFD, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, - 0x16, 0xE6, 0x1D, 0xDD, 0xF0, 0x70, 0xE7, 0xE1, - 0xCB, 0xC7, 0x93, 0x8B, 0x03, 0x3B, 0x40, 0x38, - 0xFF, 0xF6, 0xFF, 0xDD, 0xFF, 0xF6, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFB, 0xFE, 0x7B, 0xFF, 0xF9, - 0x00, 0xFF, 0x00, 0x7F, 0x40, 0x3F, 0x00, 0x3F, - 0x00, 0x3F, 0x40, 0x3F, 0x01, 0x4E, 0xE9, 0x27, - 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, - 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xEF, + 0xFF, 0x0C, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x84, 0x7B, 0xC6, 0xBC, 0x87, 0xB6, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x7F, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xBE, 0x41, 0x5D, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0xFF, 0xDD, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x02, 0xFC, 0xC3, 0x3F, 0xC7, 0xDB, 0x23, 0x2B, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0x6B, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x80, 0x7F, 0x40, 0x3F, 0x20, 0x2F, 0xF8, 0xF7, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFE, 0x03, 0xFB, 0x07, 0xF7, - 0x1E, 0xEE, 0x0E, 0xEE, 0x0F, 0xEF, 0x1F, 0xEF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF8, - 0xFE, 0xF1, 0xFF, 0xF1, 0xFE, 0xF1, 0xFF, 0xF0, - 0x00, 0xFF, 0x10, 0x0F, 0xF4, 0xF3, 0xF8, 0xFB, - 0xD2, 0xD1, 0x64, 0x03, 0x00, 0xFF, 0x40, 0x3F, - 0xFF, 0xFF, 0xBF, 0xFF, 0xF7, 0x0F, 0xFB, 0x07, - 0x17, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, - 0x00, 0xFF, 0x07, 0xF8, 0x07, 0xF7, 0x1F, 0xEF, - 0x0F, 0xEF, 0x1F, 0xDF, 0x7F, 0xBF, 0x33, 0xB3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xF8, 0xFF, 0xF0, - 0xEF, 0xF0, 0xFF, 0xE0, 0xFB, 0xC4, 0xB7, 0xCC, - 0x00, 0xFF, 0x01, 0xFE, 0x01, 0x7D, 0x41, 0x3D, - 0x41, 0x3D, 0xC1, 0xBD, 0x83, 0xBF, 0xC7, 0xFB, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFE, - 0x7D, 0xFE, 0xFD, 0x7E, 0xFF, 0x7C, 0xBF, 0x7C, - 0x00, 0xFF, 0xF0, 0x0F, 0xE0, 0xEF, 0xF0, 0xF7, - 0xF0, 0xF7, 0xF8, 0xFF, 0xB4, 0xB2, 0xB5, 0xB3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x1F, 0xFF, 0x0F, - 0xFF, 0x0F, 0xF7, 0x0F, 0xB7, 0x4F, 0xB7, 0x4E, - 0x00, 0xFF, 0x3E, 0xC1, 0x3F, 0xBE, 0x7C, 0xFC, - 0x7C, 0x7C, 0xFD, 0xFC, 0xDF, 0xDE, 0xDD, 0xDC, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFD, 0x83, - 0xFD, 0x83, 0xFD, 0x03, 0xDD, 0x23, 0xDF, 0x23, - 0x00, 0xFF, 0x3F, 0xC0, 0x3F, 0xBF, 0x3F, 0xBF, - 0x38, 0xB8, 0x30, 0xB0, 0x7C, 0xFB, 0xF0, 0x70, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xC0, - 0xB8, 0xC7, 0xB7, 0xCF, 0xF7, 0x8F, 0xF0, 0x8F, - 0x00, 0xFF, 0xE7, 0x18, 0xD3, 0xCB, 0xC7, 0xCF, - 0x1F, 0x07, 0x17, 0x07, 0x06, 0xF6, 0x07, 0x37, - 0xFF, 0xFF, 0xFF, 0xFF, 0xDB, 0x3C, 0xDF, 0x38, - 0x1F, 0xF8, 0xFF, 0xF8, 0xFE, 0xF9, 0x77, 0xF8, - 0x00, 0xFF, 0xF0, 0x0F, 0xF8, 0xFB, 0xFC, 0xFD, - 0x1F, 0x1E, 0x6E, 0x0E, 0x0C, 0x6C, 0x18, 0x18, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x07, 0xFD, 0x03, - 0x1F, 0xE1, 0xEF, 0xF1, 0xFD, 0xF3, 0x5B, 0xE7, - 0x00, 0xFF, 0x01, 0xFE, 0x0B, 0xF3, 0x0F, 0xEF, - 0x3F, 0xDF, 0x1C, 0xDC, 0x39, 0xB8, 0x3A, 0xB9, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFC, 0xFF, 0xF0, - 0xFF, 0xE0, 0xDD, 0xE3, 0xFB, 0xC7, 0xBF, 0xC7, - 0x00, 0xFF, 0x10, 0x0F, 0xF0, 0xF3, 0xFC, 0xFD, - 0x3E, 0x3E, 0x6E, 0x0E, 0x07, 0xF7, 0x07, 0xF7, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x0F, 0xFF, 0x03, - 0x3F, 0xC1, 0xEE, 0xF1, 0xF7, 0xF8, 0xF7, 0xF8, - 0x00, 0xFF, 0xF8, 0x07, 0xF0, 0x77, 0x38, 0xBB, - 0x78, 0xBB, 0x9E, 0x5C, 0xBF, 0x5F, 0x0F, 0x6F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x8F, 0xBF, 0xC7, - 0xFB, 0xC7, 0xDF, 0xE3, 0xFD, 0xE2, 0xEF, 0xF0, - 0x00, 0xFF, 0x3E, 0xC1, 0x7E, 0xBD, 0x3C, 0xBD, - 0x72, 0x71, 0xE4, 0xE3, 0xC8, 0xC7, 0x90, 0x8F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xBB, 0xC7, - 0x77, 0x8F, 0xEF, 0x1F, 0xDF, 0x3F, 0xBF, 0x7F, - 0x03, 0xFF, 0x01, 0xFD, 0x00, 0xFE, 0x00, 0xFF, - 0x28, 0xC7, 0x3A, 0xD9, 0x1E, 0xEE, 0x0B, 0xF3, - 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, - 0xFF, 0xEF, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFB, - 0x00, 0x7C, 0xC0, 0xBF, 0xE0, 0xDF, 0x68, 0x66, - 0x3D, 0xBB, 0x2B, 0xC9, 0x03, 0xE1, 0xA1, 0xA3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, - 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xE7, 0xFF, 0xAF, - 0x69, 0xE5, 0x4F, 0x43, 0xC2, 0xCA, 0x9E, 0x86, - 0xAC, 0x9C, 0x2D, 0x0C, 0x05, 0x04, 0x95, 0x85, - 0xFF, 0xFD, 0xFF, 0x5F, 0xFF, 0xFA, 0xFF, 0xBF, - 0xFF, 0xFD, 0xFF, 0x6F, 0xFF, 0x47, 0xFF, 0x95, - 0x1C, 0x2C, 0x49, 0x28, 0x5A, 0x19, 0xB9, 0x7A, - 0x34, 0xB2, 0xF3, 0x75, 0xD7, 0xD3, 0xCE, 0xCE, - 0xFF, 0x7F, 0xFF, 0xEB, 0xFF, 0xDF, 0xF7, 0xFF, - 0xFF, 0xBE, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xEE, - 0xD2, 0x4A, 0x5E, 0x46, 0x54, 0x44, 0xDD, 0xCD, - 0x88, 0x88, 0x98, 0x99, 0x0A, 0x09, 0x4C, 0x44, - 0xFF, 0xDA, 0xFF, 0x5F, 0xFF, 0x55, 0xFF, 0xFD, - 0xFF, 0xAB, 0xFF, 0xFB, 0xFF, 0x5B, 0xFF, 0x5C, - 0x2A, 0x26, 0x38, 0x30, 0x4E, 0x44, 0x80, 0x80, - 0x17, 0x01, 0xE9, 0x03, 0x51, 0x87, 0x28, 0x06, - 0xFF, 0xEE, 0xEF, 0xB9, 0xFF, 0x5F, 0xFF, 0xB2, - 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xDF, 0xFF, 0x3E, - 0xA0, 0xA7, 0x60, 0x67, 0xC0, 0xCF, 0x90, 0x8F, - 0xA0, 0x97, 0x18, 0x2B, 0x18, 0x33, 0x84, 0x83, - 0x7F, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, - 0xFF, 0xF7, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x8F, - 0x07, 0xF7, 0x03, 0xFB, 0x03, 0xFD, 0x00, 0xFF, - 0x00, 0xE7, 0x58, 0x98, 0x7F, 0xBF, 0x2F, 0xCF, - 0xF7, 0xF8, 0xFF, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xDB, 0xE7, 0xFF, 0xC0, 0xEF, 0xF0, - 0xD0, 0xCF, 0xF9, 0xF6, 0xF0, 0xF6, 0xF5, 0x71, - 0xF3, 0x73, 0xFB, 0xFB, 0xEF, 0xEF, 0xCF, 0xCF, - 0xDF, 0x3F, 0xFF, 0x0F, 0xF6, 0x0F, 0xF7, 0x8E, - 0xF7, 0x8C, 0xF3, 0x0C, 0xE7, 0x18, 0xDF, 0x30, - 0x77, 0x73, 0xE1, 0xE5, 0xE1, 0xE1, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x01, 0x40, 0x3E, - 0xFF, 0x8C, 0xED, 0x1E, 0xE1, 0x1E, 0xFF, 0x00, - 0xFF, 0x00, 0x81, 0x7E, 0x7F, 0xFE, 0xFE, 0xFF, - 0xA3, 0x9B, 0xA3, 0x9B, 0xA3, 0x9B, 0xC7, 0xDF, - 0xC7, 0xDF, 0xDF, 0xC7, 0xD6, 0xC6, 0xD6, 0xC6, - 0xBF, 0x7C, 0xBF, 0x7C, 0xBB, 0x7C, 0xFF, 0x38, - 0xFF, 0x38, 0xDF, 0x38, 0xDE, 0x39, 0xD6, 0x39, - 0x75, 0x71, 0x3B, 0x3B, 0x7B, 0x3B, 0xDF, 0x9F, - 0x5E, 0x1E, 0x1E, 0x5E, 0x3E, 0x5E, 0x19, 0x78, - 0x37, 0xCE, 0x7F, 0xC4, 0x7B, 0xC4, 0x5F, 0xE0, - 0xDE, 0xE1, 0xFF, 0xE1, 0xFD, 0xE3, 0xFB, 0xE7, - 0x9C, 0x9D, 0x98, 0x99, 0x1A, 0x19, 0x7E, 0x3D, - 0x7A, 0x39, 0xB9, 0x3A, 0x30, 0xB2, 0x30, 0xB2, - 0x9F, 0x63, 0xDB, 0x67, 0x5B, 0xE7, 0xFB, 0xC7, - 0xFF, 0xC7, 0xFF, 0xC7, 0xB7, 0xCF, 0xB6, 0xCF, - 0x7F, 0x7F, 0x7F, 0x7F, 0x60, 0x60, 0xE8, 0xE7, - 0xF8, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0xFF, 0x80, 0x6F, 0x9F, 0xEF, 0x1F, - 0xEF, 0x1F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xA7, 0x97, 0xEF, 0xDF, 0x3E, 0x0E, 0x0E, 0xEE, - 0x0C, 0xEC, 0x8E, 0xAE, 0xFF, 0xDF, 0xBF, 0x9F, - 0xB7, 0x78, 0xBF, 0x70, 0xFF, 0xF1, 0xFF, 0xF1, - 0xED, 0xF3, 0xEE, 0x71, 0xBF, 0x60, 0xFF, 0x60, - 0xF0, 0xF1, 0xF8, 0xFB, 0x1C, 0x1D, 0x1D, 0xFC, - 0x1D, 0xDC, 0x3D, 0x3C, 0xFC, 0xFD, 0xE2, 0xE1, - 0xF7, 0x0F, 0xFB, 0x07, 0xDF, 0xE3, 0xFD, 0xE3, - 0xFD, 0xE3, 0x3F, 0xC3, 0xFB, 0x07, 0xEF, 0x1F, - 0x70, 0xF3, 0xF4, 0x73, 0xF4, 0x73, 0x74, 0xF3, - 0x38, 0xBB, 0x7E, 0xBE, 0x1F, 0xDF, 0x07, 0xE7, - 0xF7, 0x8F, 0xF7, 0x8F, 0xF7, 0x8F, 0xF7, 0x8F, - 0xBB, 0xC7, 0xFE, 0xC1, 0xFF, 0xE0, 0xF7, 0xF8, - 0x07, 0xF7, 0x06, 0xF6, 0x1E, 0xEE, 0x0F, 0xEF, - 0x5C, 0x9C, 0x7D, 0x7C, 0xF0, 0xF1, 0xE0, 0xE3, - 0xF7, 0xF8, 0xF6, 0xF9, 0xFE, 0xF1, 0xEE, 0xF1, - 0xDD, 0xE3, 0x7F, 0x83, 0xF7, 0x0F, 0xEF, 0x1F, - 0x0F, 0x6F, 0x0F, 0x7F, 0x06, 0x76, 0x8E, 0x7E, - 0x9F, 0x6F, 0x0E, 0xEE, 0x0E, 0xEE, 0x0C, 0xEC, - 0xFF, 0xF0, 0xFF, 0xF0, 0xF6, 0xF9, 0xFE, 0xF1, - 0xFE, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xED, 0xF3, - 0x80, 0x9F, 0x80, 0xBF, 0x40, 0x3F, 0x80, 0x7F, - 0x80, 0x7F, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x03, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x05, 0x02, 0xF0, 0x0F, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x90, 0x88, 0x63, 0x9D, 0x03, 0xFD, 0x01, 0xFD, - 0x00, 0xFC, 0x00, 0xFC, 0x01, 0xFE, 0x00, 0xFF, - 0x7F, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, - 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, - 0x88, 0x90, 0xA3, 0x9C, 0x00, 0x3F, 0x40, 0x3F, - 0x00, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x00, 0xFF, - 0xFF, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x54, 0x08, 0xC1, 0x3E, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xA0, 0x1F, 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x28, 0x07, 0x70, 0x8F, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x15, 0xE5, 0x03, 0xFC, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x90, 0x88, 0xC0, 0x3F, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x04, 0x08, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x71, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x04, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x30, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x08, 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x0A, 0xF2, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x88, 0x87, 0xC0, 0x3F, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 }; From b3166135eb1fe88963a14c9b4c0f62ec06b514e9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Nov 2018 19:59:15 +0200 Subject: [PATCH 0778/1216] Clean up that crashing mess I made --- Cocoa/Document.m | 65 +++++++++++++++------------------------------ Cocoa/GBView.m | 7 ++--- Cocoa/GBViewMetal.m | 13 +++------ Core/gb.c | 4 +-- 4 files changed, 30 insertions(+), 59 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 24e99a32..c65b69e5 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -159,40 +159,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"]; } -/* Todo: Unify the 4 init functions */ -- (void) initDMG -{ - current_model = MODEL_DMG; - GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[self bootROMPathForName:@"dmg_boot"] UTF8String]); - [self initCommon]; -} - -- (void) initSGB -{ - current_model = MODEL_SGB; - GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[self bootROMPathForName:@"sgb_boot"] UTF8String]); - [self initCommon]; -} - -- (void) initCGB -{ - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]) { - current_model = MODEL_AGB; - GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[self bootROMPathForName:@"agb_boot"] UTF8String]); - } - else { - current_model = MODEL_CGB; - GB_init(&gb, cocoa_to_internal_model[current_model]); - GB_load_boot_rom(&gb, [[self bootROMPathForName:@"cgb_boot"] UTF8String]); - } - [self initCommon]; -} - - (void) initCommon { + GB_init(&gb, cocoa_to_internal_model[current_model]); GB_set_user_data(&gb, (__bridge void *)(self)); GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog); @@ -204,7 +173,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); - [self loadROM]; } - (void) vblank @@ -225,8 +193,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) run { running = true; - self.view.gb = &gb; - [self.view screenSizeChanged]; GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { @@ -277,23 +243,32 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, GB_debugger_set_disabled(&gb, false); } +- (void) loadBootROM +{ + static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"}; + GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); +} + - (IBAction)reset:(id)sender { [self stop]; - + size_t old_width = GB_get_screen_width(&gb); + [self loadBootROM]; + if ([sender tag] != MODEL_NONE) { current_model = (enum model)[sender tag]; } - static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"}; - GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); - if ([sender tag] == MODEL_NONE) { GB_reset(&gb); } else { GB_switch_model_and_reset(&gb, cocoa_to_internal_model[current_model]); } + + if (old_width != GB_get_screen_width(&gb)) { + [self.view screenSizeChanged]; + } if ([sender tag] != 0) { /* User explictly selected a model, save the preference */ @@ -392,16 +367,20 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, object:nil]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { - [self initDMG]; + current_model = MODEL_DMG; } else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateSGB"]) { - [self initSGB]; + current_model = MODEL_SGB; } else { - [self initCGB]; + current_model = [[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]? MODEL_AGB : MODEL_CGB; } - [self start]; + [self initCommon]; + self.view.gb = &gb; + [self.view screenSizeChanged]; + [self loadROM]; + [self reset:nil]; } diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index debac87f..194ab5a7 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -43,9 +43,7 @@ } - (void) _init -{ - [self screenSizeChanged]; - +{ _shouldBlendFrameWithPrevious = 1; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} @@ -61,7 +59,6 @@ - (void)screenSizeChanged { - if (!_gb) return; if (image_buffers[0]) free(image_buffers[0]); if (image_buffers[1]) free(image_buffers[1]); if (image_buffers[2]) free(image_buffers[2]); @@ -127,7 +124,7 @@ - (void)setFrame:(NSRect)frame { frame = self.superview.frame; - if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { + if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { double ratio = frame.size.width / frame.size.height; double width = GB_get_screen_width(_gb); double height = GB_get_screen_height(_gb); diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 124db778..9acb11e9 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -31,7 +31,6 @@ static const vector_float2 rect[] = - (void) allocateTextures { if (!device) return; - if (!self.gb) return; MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; @@ -45,12 +44,6 @@ static const vector_float2 rect[] = } -- (void)screenSizeChanged -{ - [super screenSizeChanged]; - [self allocateTextures]; -} - - (void)createInternalView { MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; @@ -58,8 +51,6 @@ static const vector_float2 rect[] = self.internalView = view; view.paused = YES; - [self allocateTextures]; - vertices = [device newBufferWithBytes:rect length:sizeof(rect) options:MTLResourceStorageModeShared]; @@ -140,6 +131,10 @@ static const vector_float2 rect[] = - (void)drawInMTKView:(nonnull MTKView *)view { if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; + if (texture.width != GB_get_screen_width(self.gb) || + texture.height != GB_get_screen_height(self.gb)) { + [self allocateTextures]; + } MTLRegion region = { {0, 0, 0}, // MTLOrigin diff --git a/Core/gb.c b/Core/gb.c index 0d19fc83..17864209 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -765,10 +765,10 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb) size_t GB_get_screen_width(GB_gameboy_t *gb) { - return (gb && GB_is_sgb(gb))? 256 : 160; + return GB_is_sgb(gb)? 256 : 160; } size_t GB_get_screen_height(GB_gameboy_t *gb) { - return (gb && GB_is_sgb(gb))? 224 : 144; + return GB_is_sgb(gb)? 224 : 144; } From 2d6d1e63251769632102926f327246f86d3f4b63 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Nov 2018 13:21:00 +0200 Subject: [PATCH 0779/1216] SGB boot animation --- Core/apu.c | 3 + Core/gb.c | 3 + Core/gb.h | 3 + Core/sgb.c | 138 ++++++++- Core/sgb.h | 5 + Core/sgb_animation_logo.inc | 563 ++++++++++++++++++++++++++++++++++++ 6 files changed, 707 insertions(+), 8 deletions(-) create mode 100644 Core/sgb_animation_logo.inc diff --git a/Core/apu.c b/Core/apu.c index f9aabd7f..8c515bbe 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -465,6 +465,9 @@ void GB_apu_run(GB_gameboy_t *gb) void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) { + if (gb->sgb) { + if (GB_sgb_render_jingle(gb, dest, count)) return; + } gb->apu_output.copy_in_progress = true; if (!gb->apu_output.stream_started) { diff --git a/Core/gb.c b/Core/gb.c index 17864209..5bf96b33 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -649,6 +649,9 @@ void GB_reset(GB_gameboy_t *gb) gb->sgb = malloc(sizeof(*gb->sgb)); } memset(gb->sgb, 0, sizeof(*gb->sgb)); + memset(gb->sgb_intro_jingle_phases, 0, sizeof(gb->sgb_intro_jingle_phases)); + gb->sgb_intro_sweep_phase = 0; + gb->sgb->player_count = 1; GB_sgb_load_default_data(gb); diff --git a/Core/gb.h b/Core/gb.h index e9f13870..c1f85b59 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -564,6 +564,9 @@ struct GB_gameboy_internal_s { /* SGB - saved and allocated optionally */ GB_sgb_t *sgb; + + double sgb_intro_jingle_phases[7]; + double sgb_intro_sweep_phase; /* Misc */ bool turbo; diff --git a/Core/sgb.c b/Core/sgb.c index 5bd603ca..c75d9cb9 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,4 +1,7 @@ #include "gb.h" +#include + +#define INTRO_ANIMATION_LENGTH 180 enum { PAL01 = 0x00, @@ -339,8 +342,66 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_ return gb->rgb_encode_callback(gb, r, g, b); } +#include +static void render_boot_animation (GB_gameboy_t *gb) +{ +#include "sgb_animation_logo.inc" + uint32_t *output = &gb->screen[48 + 40 * 256]; + uint8_t *input = animation_logo; + unsigned fade_blue = 0; + unsigned fade_red = 0; + if (gb->sgb->intro_animation < 32) { + fade_blue = 32; + } + else if (gb->sgb->intro_animation < 64) { + fade_blue = 64 - gb->sgb->intro_animation; + } + else if (gb->sgb->intro_animation > INTRO_ANIMATION_LENGTH - 32) { + fade_red = fade_blue = gb->sgb->intro_animation - INTRO_ANIMATION_LENGTH + 32; + } + uint32_t colors[] = { + convert_rgb15(gb, 0), + convert_rgb15_with_fade(gb, 0x14A5, fade_blue), + convert_rgb15_with_fade(gb, 0x54E0, fade_blue), + convert_rgb15_with_fade(gb, 0x0019, fade_red), + convert_rgb15(gb, 0x0011), + convert_rgb15(gb, 0x0009), + }; + unsigned y_min = (144 - animation_logo_height) / 2; + unsigned y_max = y_min + animation_logo_height; + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + if (y < y_min || y >= y_max) { + *(output++) = colors[0]; + } + else { + uint8_t color = *input; + if (color >= 3) { + if (color == gb->sgb->intro_animation / 2 - 3) { + color = 5; + } + else if (color == gb->sgb->intro_animation / 2 - 4) { + color = 4; + } + else if (color < gb->sgb->intro_animation / 2 - 4) { + color = 3; + } + else { + color = 0; + } + } + *(output++) = colors[color]; + input++; + } + } + output += 256 - 160; + } +} + void GB_sgb_render(GB_gameboy_t *gb) { + if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; + if (!gb->screen || !gb->rgb_encode_callback) return; switch ((mask_mode_t) gb->sgb->mask_mode) { @@ -429,18 +490,23 @@ void GB_sgb_render(GB_gameboy_t *gb) colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]); } - uint32_t *output = &gb->screen[48 + 40 * 256]; - uint8_t *input = gb->sgb->effective_screen_buffer; - for (unsigned y = 0; y < 144; y++) { - for (unsigned x = 0; x < 160; x++) { - uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; - *(output++) = colors[(*(input++) & 3) + palette * 4]; + if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { + render_boot_animation(gb); + } + else { + uint32_t *output = &gb->screen[48 + 40 * 256]; + uint8_t *input = gb->sgb->effective_screen_buffer; + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; + *(output++) = colors[(*(input++) & 3) + palette * 4]; + } + output += 256 - 160; } - output += 256 - 160; } uint32_t border_colors[16 * 4]; - if (gb->sgb->border_animation == 0) { + if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { for (unsigned i = 0; i < 16 * 4; i++) { border_colors[i] = convert_rgb15(gb, gb->sgb->border.palette[i]); } @@ -525,3 +591,59 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb) gb->sgb->effective_palettes[2] = 0x10D4; gb->sgb->effective_palettes[3] = 0x2866; } + +static double fm_synth(double phase) +{ + return (sin(phase * M_PI * 2) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 2)) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 3)) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 4))) / 4; +} + +static double fm_sweep(double phase) +{ + double ret = 0; + for (unsigned i = 0; i < 8; i++) { + ret += fm_synth((phase) * pow(1.17, i)) * (8 - i) / 36; + } + return ret; +} + +bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) +{ + const double frequencies[7] = { + 466.16, // Bb4 + 587.33, // D5 + 698.46, // F5 + 830.61, // Ab5 + 1046.50, // C6 + 1244.51, // Eb6 + 1567.98, // G6 + }; + + if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return false; + + signed jingle_stage = (gb->sgb->intro_animation - 60) / 3; + for (unsigned i = 0; i < count; i++) { + double sample = 0; + for (signed f = 0; f < 7 && f < jingle_stage; f++) { + sample += fm_synth(gb->sgb_intro_jingle_phases[f]) * + (0.75 * pow(0.5, jingle_stage - f) + 0.25) / 5.0; + gb->sgb_intro_jingle_phases[f] += frequencies[f] / gb->apu_output.sample_rate; + } + if (gb->sgb->intro_animation > 100) { + sample *= pow((INTRO_ANIMATION_LENGTH - gb->sgb->intro_animation) / (INTRO_ANIMATION_LENGTH - 100.0), 3); + } + + if (gb->sgb->intro_animation < 120) { + sample += fm_sweep(gb->sgb_intro_sweep_phase) / 2.0 * pow((120 - gb->sgb->intro_animation) / 120.0, 2); + gb->sgb_intro_sweep_phase += (1000.0 * pow(2, gb->sgb->intro_animation / 40.0)) / gb->apu_output.sample_rate; + } + + dest->left = dest->right = sample * 0x7000; + dest++; + } + + return true; +} + diff --git a/Core/sgb.h b/Core/sgb.h index 7957201c..dc231fc6 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -5,6 +5,7 @@ #include typedef struct GB_sgb_s GB_sgb_t; + #ifdef GB_INTERNAL struct GB_sgb_s { uint8_t command[16 * 7]; @@ -45,11 +46,15 @@ struct GB_sgb_s { uint16_t ram_palettes[4 * 512]; uint8_t attribute_map[20 * 18]; uint8_t attribute_files[0xFE0]; + + /* Intro */ + uint16_t intro_animation; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); void GB_sgb_load_default_data(GB_gameboy_t *gb); +bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count); #endif diff --git a/Core/sgb_animation_logo.inc b/Core/sgb_animation_logo.inc new file mode 100644 index 00000000..75075f49 --- /dev/null +++ b/Core/sgb_animation_logo.inc @@ -0,0 +1,563 @@ +static uint8_t animation_logo[] = { + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0xE, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, + 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x1, + 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1, + 0x0, 0x0, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0x7, 0x7, 0x9, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1, + 0x1, 0xE, 0xE, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x7, 0x7, 0x7, 0x9, 0x1, 0x1, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC, + 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0xC, 0xC, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE, + 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1, 0x1, 0x0, 0x1, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, + 0x7, 0x7, 0x9, 0x1, 0x1, 0x0, 0x0, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0xC, 0xC, + 0xC, 0xC, 0x1, 0x1, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE, + 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x5, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, + 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x1, 0x9, 0x9, 0x9, 0x0, 0x0, 0x0, 0x1, 0xC, 0xC, + 0xC, 0x1, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0x1, 0xD, 0x1, 0x1, 0x1, + 0x1, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, + 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0xC, 0xC, 0xC, + 0xC, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, + 0xE, 0xE, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, + 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, + 0x1, 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0xF, + 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x5, 0x5, 0x5, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, + 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x1, 0xF, + 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x1, 0xC, 0xC, 0xB, 0xB, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0xF, 0xF, + 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, + 0x0, 0x0, 0x1, 0x6, 0x6, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x1, 0x0, 0xC, 0xC, 0xB, 0xB, 0x1, + 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0x1, 0xF, 0xF, + 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x4, 0x1, 0x0, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, + 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0x1, + 0x0, 0x0, 0x0, 0x1, 0x9, 0x9, 0xA, 0x1, 0x1, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, + 0x1, 0x6, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0xA, 0x1, + 0x0, 0x0, 0x0, 0x9, 0x9, 0xA, 0xA, 0x1, 0x0, 0x1, 0xB, 0xB, 0xB, 0xD, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x1, 0x1, 0x0, 0x1, + 0x6, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0xA, 0xA, 0x1, + 0x0, 0x0, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0x0, 0xB, 0xB, 0x1, 0xD, 0xD, 0xD, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x0, 0x6, 0x6, 0x1, 0x0, 0x1, 0x6, + 0x1, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0x1, 0x1, 0xA, 0xA, + 0x1, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0xD, 0xD, 0x1, + 0x0, 0x0, 0x0, 0x1, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x1, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x1, + 0x1, 0x0, 0x1, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x1, 0x0, 0x1, 0xA, + 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x0, 0x0, 0xD, 0xD, 0xD, + 0xD, 0xD, 0xD, 0xD, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, + 0xF, 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x1, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, + 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0xD, + 0xD, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xF, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 +}; +static const unsigned animation_logo_height = sizeof(animation_logo) / 160; From 389d8ae04598399ff555b6cb08265ca20099a91f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Nov 2018 13:34:07 +0200 Subject: [PATCH 0780/1216] Halt the CPU while the SGB animation is playing --- Core/gb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 5bf96b33..2432666a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -351,6 +351,18 @@ exit: uint8_t GB_run(GB_gameboy_t *gb) { + if (gb->sgb && gb->sgb->intro_animation < 140) { + /* On the SGB, the GB is halted after finishing the boot ROM. + Then, after the boot animation is almost done, it's reset. + Since the SGB HLE does not perform any header validity checks, + we just halt the CPU (with hacky code) until the correct time. + This ensures the Nintendo logo doesn't flash on screen, and + the game does "run in background" while the animation is playing. */ + GB_display_run(gb, 228); + gb->cycles_since_last_sync += 228; + return 228; + } + GB_debugger_run(gb); gb->cycles_since_run = 0; GB_cpu_run(gb); From 4b93f897634bfae3527a34144e48098b1f4bfc8f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Nov 2018 15:42:54 +0200 Subject: [PATCH 0781/1216] Correct emulation of color masks --- Core/sgb.c | 61 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index c75d9cb9..ed6bffd4 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -23,7 +23,7 @@ enum { typedef enum { MASK_DISABLED, MASK_FREEZE, - MASK_COLOR_3, + MASK_BLACK, MASK_COLOR_0, } mask_mode_t; @@ -404,24 +404,10 @@ void GB_sgb_render(GB_gameboy_t *gb) if (!gb->screen || !gb->rgb_encode_callback) return; - switch ((mask_mode_t) gb->sgb->mask_mode) { - case MASK_DISABLED: - memcpy(gb->sgb->effective_screen_buffer, - gb->sgb->screen_buffer, - sizeof(gb->sgb->effective_screen_buffer)); - break; - case MASK_FREEZE: - break; - - case MASK_COLOR_3: - memset(gb->sgb->effective_screen_buffer, - 3, - sizeof(gb->sgb->effective_screen_buffer)); - break; - case MASK_COLOR_0: - memset(gb->sgb->effective_screen_buffer, - 0, - sizeof(gb->sgb->effective_screen_buffer)); + if (gb->sgb->mask_mode != MASK_FREEZE) { + memcpy(gb->sgb->effective_screen_buffer, + gb->sgb->screen_buffer, + sizeof(gb->sgb->effective_screen_buffer)); } if (gb->sgb->vram_transfer_countdown) { @@ -496,12 +482,39 @@ void GB_sgb_render(GB_gameboy_t *gb) else { uint32_t *output = &gb->screen[48 + 40 * 256]; uint8_t *input = gb->sgb->effective_screen_buffer; - for (unsigned y = 0; y < 144; y++) { - for (unsigned x = 0; x < 160; x++) { - uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; - *(output++) = colors[(*(input++) & 3) + palette * 4]; + switch ((mask_mode_t) gb->sgb->mask_mode) { + case MASK_DISABLED: + case MASK_FREEZE: { + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; + *(output++) = colors[(*(input++) & 3) + palette * 4]; + } + output += 256 - 160; + } + break; + } + case MASK_BLACK: + { + uint32_t black = convert_rgb15(gb, 0); + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = black; + } + output += 256 - 160; + } + break; + } + case MASK_COLOR_0: + { + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = colors[0]; + } + output += 256 - 160; + } + break; } - output += 256 - 160; } } From 6d28e74667b2a10206581b90d2413a68731a9189 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 24 Nov 2018 15:53:53 +0200 Subject: [PATCH 0782/1216] Color 0 must always be shared --- Core/sgb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/sgb.c b/Core/sgb.c index ed6bffd4..19f70bd6 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -174,6 +174,9 @@ static void command_ready(GB_gameboy_t *gb) &gb->sgb->ram_palettes[4 * (gb->sgb->command[7] + (gb->sgb->command[8] & 1) * 0x100)], 8); + gb->sgb->effective_palettes[12] = gb->sgb->effective_palettes[8] = + gb->sgb->effective_palettes[4] = gb->sgb->effective_palettes[0]; + if (gb->sgb->command[9] & 0x80) { load_attribute_file(gb, gb->sgb->command[9] & 0x3F); } From b1a2e4516844a215380d5bf1db56bfa2a316a09c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Dec 2018 13:39:43 +0200 Subject: [PATCH 0783/1216] Improvements to the SGB animation --- Core/gb.c | 2 ++ Core/gb.h | 1 + Core/sgb.c | 38 ++++++++++++++++++++++++++++++-------- Core/sgb.h | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 2432666a..93ecb08d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -663,6 +663,8 @@ void GB_reset(GB_gameboy_t *gb) memset(gb->sgb, 0, sizeof(*gb->sgb)); memset(gb->sgb_intro_jingle_phases, 0, sizeof(gb->sgb_intro_jingle_phases)); gb->sgb_intro_sweep_phase = 0; + gb->sgb_intro_sweep_previous_sample = 0; + gb->sgb->intro_animation = -10; gb->sgb->player_count = 1; GB_sgb_load_default_data(gb); diff --git a/Core/gb.h b/Core/gb.h index c1f85b59..2808677f 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -567,6 +567,7 @@ struct GB_gameboy_internal_s { double sgb_intro_jingle_phases[7]; double sgb_intro_sweep_phase; + double sgb_intro_sweep_previous_sample; /* Misc */ bool turbo; diff --git a/Core/sgb.c b/Core/sgb.c index 19f70bd6..bd3edc27 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,7 +1,7 @@ #include "gb.h" #include -#define INTRO_ANIMATION_LENGTH 180 +#define INTRO_ANIMATION_LENGTH 200 enum { PAL01 = 0x00, @@ -353,11 +353,11 @@ static void render_boot_animation (GB_gameboy_t *gb) uint8_t *input = animation_logo; unsigned fade_blue = 0; unsigned fade_red = 0; - if (gb->sgb->intro_animation < 32) { + if (gb->sgb->intro_animation < 80 - 32) { fade_blue = 32; } - else if (gb->sgb->intro_animation < 64) { - fade_blue = 64 - gb->sgb->intro_animation; + else if (gb->sgb->intro_animation < 80) { + fade_blue = 80 - gb->sgb->intro_animation; } else if (gb->sgb->intro_animation > INTRO_ANIMATION_LENGTH - 32) { fade_red = fade_blue = gb->sgb->intro_animation - INTRO_ANIMATION_LENGTH + 32; @@ -620,10 +620,14 @@ static double fm_sweep(double phase) { double ret = 0; for (unsigned i = 0; i < 8; i++) { - ret += fm_synth((phase) * pow(1.17, i)) * (8 - i) / 36; + ret += sin((phase * M_PI * 2 + sin(phase * M_PI * 8) / 4) * pow(1.25, i)) * (8 - i) / 36; } return ret; } +static double random_double(void) +{ + return ((random() % 0x10001) - 0x8000) / (double) 0x8000; +} bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) { @@ -637,9 +641,23 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) 1567.98, // G6 }; + if (gb->sgb->intro_animation < 0) { + for (unsigned i = 0; i < count; i++) { + dest->left = dest->right = 0; + dest++; + } + return true; + } + if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return false; - signed jingle_stage = (gb->sgb->intro_animation - 60) / 3; + signed jingle_stage = (gb->sgb->intro_animation - 64) / 3; + double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate; + double sweep_phase_shift = 1000.0 * pow(2, gb->sgb->intro_animation / 40.0) / gb->apu_output.sample_rate; + if (sweep_cutoff_ratio > 1) { + sweep_cutoff_ratio = 1; + } + for (unsigned i = 0; i < count; i++) { double sample = 0; for (signed f = 0; f < 7 && f < jingle_stage; f++) { @@ -652,8 +670,12 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) } if (gb->sgb->intro_animation < 120) { - sample += fm_sweep(gb->sgb_intro_sweep_phase) / 2.0 * pow((120 - gb->sgb->intro_animation) / 120.0, 2); - gb->sgb_intro_sweep_phase += (1000.0 * pow(2, gb->sgb->intro_animation / 40.0)) / gb->apu_output.sample_rate; + double next = fm_sweep(gb->sgb_intro_sweep_phase) * 0.3 + random_double() * 0.7; + gb->sgb_intro_sweep_phase += sweep_phase_shift; + + gb->sgb_intro_sweep_previous_sample = next * (sweep_cutoff_ratio) + + gb->sgb_intro_sweep_previous_sample * (1 - sweep_cutoff_ratio); + sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8; } dest->left = dest->right = sample * 0x7000; diff --git a/Core/sgb.h b/Core/sgb.h index dc231fc6..7e361988 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -48,7 +48,7 @@ struct GB_sgb_s { uint8_t attribute_files[0xFE0]; /* Intro */ - uint16_t intro_animation; + int16_t intro_animation; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From 51b7780d834e6781e32a6b61696f3aade9bdb1fe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Dec 2018 16:08:59 +0200 Subject: [PATCH 0784/1216] Redesign the preferences window --- Cocoa/AppDelegate.h | 5 + Cocoa/AppDelegate.m | 22 +- Cocoa/CPU.png | Bin 0 -> 18330 bytes Cocoa/CPU@2x.png | Bin 0 -> 19184 bytes Cocoa/Display.png | Bin 0 -> 19795 bytes Cocoa/Display@2x.png | Bin 0 -> 20722 bytes Cocoa/Joypad.png | Bin 0 -> 19966 bytes Cocoa/Joypad@2x.png | Bin 0 -> 20863 bytes Cocoa/Preferences.xib | 622 +++++++++++++++++++++++------------------- Cocoa/Speaker.png | Bin 0 -> 18543 bytes Cocoa/Speaker@2x.png | Bin 0 -> 19746 bytes Makefile | 4 +- 12 files changed, 372 insertions(+), 281 deletions(-) create mode 100644 Cocoa/CPU.png create mode 100644 Cocoa/CPU@2x.png create mode 100644 Cocoa/Display.png create mode 100644 Cocoa/Display@2x.png create mode 100644 Cocoa/Joypad.png create mode 100644 Cocoa/Joypad@2x.png create mode 100644 Cocoa/Speaker.png create mode 100644 Cocoa/Speaker@2x.png diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index d4ffe7ce..608a50ce 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -3,8 +3,13 @@ @interface AppDelegate : NSObject @property IBOutlet NSWindow *preferencesWindow; +@property (strong) IBOutlet NSView *graphicsTab; +@property (strong) IBOutlet NSView *emulationTab; +@property (strong) IBOutlet NSView *audioTab; +@property (strong) IBOutlet NSView *controlsTab; - (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; +- (IBAction)switchPreferencesTab:(id)sender; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 44f56835..a9c5a9e2 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -6,6 +6,7 @@ @implementation AppDelegate { NSWindow *preferences_window; + NSArray *preferences_tabs; } - (void) applicationDidFinishLaunching:(NSNotification *)notification @@ -44,10 +45,24 @@ [defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"]; } +- (IBAction)switchPreferencesTab:(id)sender +{ + for (NSView *view in preferences_tabs) { + [view removeFromSuperview]; + } + NSView *tab = preferences_tabs[[sender tag]]; + NSRect old = [_preferencesWindow frame]; + NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame]; + new.origin.x = old.origin.x; + new.origin.y = old.origin.y + (old.size.height - new.size.height); + [_preferencesWindow setFrame:new display:YES animate:_preferencesWindow.visible]; + [_preferencesWindow.contentView addSubview:tab]; +} + - (BOOL)validateMenuItem:(NSMenuItem *)anItem { if ([anItem action] == @selector(toggleDeveloperMode:)) { - [(NSMenuItem*)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; + [(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; } return true; @@ -58,6 +73,11 @@ NSArray *objects; if (!_preferencesWindow) { [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; + NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject]; + _preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier]; + preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab]; + [self switchPreferencesTab:first_toolbar_item]; + [_preferencesWindow center]; } [_preferencesWindow makeKeyAndOrderFront:self]; } diff --git a/Cocoa/CPU.png b/Cocoa/CPU.png new file mode 100644 index 0000000000000000000000000000000000000000..7f13621351f0de516706036db2704d63de8e7abd GIT binary patch literal 18330 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Joypad.png b/Cocoa/Joypad.png new file mode 100644 index 0000000000000000000000000000000000000000..f30d8f99f08ffdf38d4b1b0c31b327f0edff93fd GIT binary patch literal 19966 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Joypad@2x.png b/Cocoa/Joypad@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d91ee30b9ddefe4c041bbb3ecd95059c298bf9bf GIT binary patch literal 20863 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index f712a243..ff0df586 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -8,297 +8,59 @@ + + + + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -308,7 +70,311 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/Speaker.png b/Cocoa/Speaker.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6b556a5a085459bff39898b411e4a5e177c89d GIT binary patch literal 18543 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 P0xk9n6dm$iz2ScV#@DwE literal 0 HcmV?d00001 diff --git a/Cocoa/Speaker@2x.png b/Cocoa/Speaker@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..41c46ffdb97577786ed67a1e0972b698af41a54c GIT binary patch literal 19746 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 $(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 From cbbe3fe2072d372bfd3035c64129bf44d984d36d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Dec 2018 17:16:50 +0200 Subject: [PATCH 0785/1216] Revision selection --- Cocoa/AppDelegate.m | 6 +- Cocoa/Document.m | 78 ++++++++++++++++++++++---- Cocoa/GBPreferencesWindow.h | 5 ++ Cocoa/GBPreferencesWindow.m | 57 +++++++++++++++++++ Cocoa/Preferences.xib | 106 +++++++++++++++++++++++++++++++++--- 5 files changed, 232 insertions(+), 20 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index a9c5a9e2..96331a6e 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -35,7 +35,11 @@ @"GBFilter": @"NearestNeighbor", @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), - @"GBRewindLength": @(10) + @"GBRewindLength": @(10), + + @"GBDMGModel": @(GB_MODEL_DMG_B), + @"GBCGBModel": @(GB_MODEL_CGB_E), + @"GBSGBModel": @(GB_MODEL_SGB2), }]; } diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c65b69e5..30284ba9 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -19,14 +19,6 @@ enum model { MODEL_SGB, }; -static const GB_model_t cocoa_to_internal_model[] = -{ - [MODEL_DMG] = GB_MODEL_DMG_B, - [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB, - [MODEL_SGB] = GB_MODEL_SGB, -}; - @interface Document () { @@ -62,6 +54,7 @@ static const GB_model_t cocoa_to_internal_model[] = enum model current_model; bool rewind; + bool modelsChanging; } @property GBAudioClient *audioClient; @@ -159,9 +152,27 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"]; } +- (GB_model_t)internalModel +{ + switch (current_model) { + case MODEL_DMG: + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]; + + case MODEL_NONE: + case MODEL_CGB: + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]; + + case MODEL_SGB: + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]; + + case MODEL_AGB: + return GB_MODEL_AGB; + } +} + - (void) initCommon { - GB_init(&gb, cocoa_to_internal_model[current_model]); + GB_init(&gb, [self internalModel]); GB_set_user_data(&gb, (__bridge void *)(self)); GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog); @@ -253,17 +264,18 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, { [self stop]; size_t old_width = GB_get_screen_width(&gb); - [self loadBootROM]; if ([sender tag] != MODEL_NONE) { current_model = (enum model)[sender tag]; } - if ([sender tag] == MODEL_NONE) { + [self loadBootROM]; + + if (!modelsChanging && [sender tag] == MODEL_NONE) { GB_reset(&gb); } else { - GB_switch_model_and_reset(&gb, cocoa_to_internal_model[current_model]); + GB_switch_model_and_reset(&gb, [self internalModel]); } if (old_width != GB_get_screen_width(&gb)) { @@ -366,6 +378,21 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, name:@"GBRewindLengthChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(dmgModelChanged) + name:@"GBDMGModelChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sgbModelChanged) + name:@"GBSGBModelChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(cgbModelChanged) + name:@"GBCGBModelChanged" + object:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { current_model = MODEL_DMG; } @@ -1416,6 +1443,33 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, }]; } +- (void)dmgModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_DMG) { + [self reset:nil]; + } + modelsChanging = false; +} + +- (void)sgbModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_SGB) { + [self reset:nil]; + } + modelsChanging = false; +} + +- (void)cgbModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_CGB) { + [self reset:nil]; + } + modelsChanging = false; +} + - (void)setFileURL:(NSURL *)fileURL { [super setFileURL:fileURL]; diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 14d3a33f..b1284851 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -12,4 +12,9 @@ @property (strong) IBOutlet NSButton *skipButton; @property (strong) IBOutlet NSMenuItem *bootROMsFolderItem; @property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton; + +@property (weak) IBOutlet NSPopUpButton *dmgPopupButton; +@property (weak) IBOutlet NSPopUpButton *sgbPopupButton; +@property (weak) IBOutlet NSPopUpButton *cgbPopupButton; + @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 97f37d74..ae4b59fc 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -17,6 +17,8 @@ NSPopUpButton *_rewindPopupButton; NSButton *_aspectRatioCheckbox; NSEventModifierFlags previousModifiers; + + NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; } + (NSArray *)filterList @@ -362,4 +364,59 @@ [self updateBootROMFolderButton]; } +- (void)setDmgPopupButton:(NSPopUpButton *)dmgPopupButton +{ + _dmgPopupButton = dmgPopupButton; + [_dmgPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]]; +} + +- (NSPopUpButton *)dmgPopupButton +{ + return _dmgPopupButton; +} + +- (void)setSgbPopupButton:(NSPopUpButton *)sgbPopupButton +{ + _sgbPopupButton = sgbPopupButton; + [_sgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]]; +} + +- (NSPopUpButton *)sgbPopupButton +{ + return _sgbPopupButton; +} + +- (void)setCgbPopupButton:(NSPopUpButton *)cgbPopupButton +{ + _cgbPopupButton = cgbPopupButton; + [_cgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]]; +} + +- (NSPopUpButton *)cgbPopupButton +{ + return _cgbPopupButton; +} + +- (IBAction)dmgModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBDMGModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBDMGModelChanged" object:nil]; + +} + +- (IBAction)sgbModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBSGBModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBSGBModelChanged" object:nil]; +} + +- (IBAction)cgbModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBCGBModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil]; +} + @end diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index ff0df586..245bca88 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -61,13 +61,16 @@ + + + @@ -166,11 +169,11 @@ - + - + @@ -194,7 +197,7 @@ - + @@ -203,7 +206,7 @@ - + @@ -212,7 +215,7 @@ - + @@ -235,8 +238,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -292,7 +384,7 @@ - + From a1c39173dd423ce028af8092f14bf698a6002057 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Dec 2018 17:28:57 +0200 Subject: [PATCH 0786/1216] SGB2 boot ROM --- BootROMs/sgb2_boot.asm | 2 ++ BootROMs/sgb_boot.asm | 4 ++++ Cocoa/Document.m | 7 ++++++- Makefile | 7 ++++--- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 BootROMs/sgb2_boot.asm diff --git a/BootROMs/sgb2_boot.asm b/BootROMs/sgb2_boot.asm new file mode 100644 index 00000000..1c3d8584 --- /dev/null +++ b/BootROMs/sgb2_boot.asm @@ -0,0 +1,2 @@ +SGB2 EQU 1 +include "sgb_boot.asm" \ No newline at end of file diff --git a/BootROMs/sgb_boot.asm b/BootROMs/sgb_boot.asm index c4d537f7..0574d29c 100644 --- a/BootROMs/sgb_boot.asm +++ b/BootROMs/sgb_boot.asm @@ -138,7 +138,11 @@ Start: ldh [$47], a ; Set registers to match the original SGB boot +IF DEF(SGB2) + ld a, $FF +ELSE ld a, 1 +ENDC ld hl, $c060 ; Boot the game diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 30284ba9..cd2e9690 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -257,7 +257,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) loadBootROM { static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"}; - GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); + if ([self internalModel] == GB_MODEL_SGB2) { + GB_load_boot_rom(&gb, [[self bootROMPathForName:@"sgb2_boot"] UTF8String]); + } + else { + GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); + } } - (IBAction)reset:(id)sender diff --git a/Makefile b/Makefile index adffbdf8..8ae09e90 100755 --- a/Makefile +++ b/Makefile @@ -106,9 +106,9 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders -bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin -tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb_boot2.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb_boot2.bin +tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets @@ -181,6 +181,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/agb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/sgb_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/sgb2_boot.bin \ $(patsubst %.xib,%.nib,$(addprefix $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/,$(shell cd Cocoa;ls *.xib))) \ $(BIN)/SameBoy.qlgenerator \ Shaders From b852add7731ed1dee6a0fea5440ea2d380049d30 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Dec 2018 22:43:35 +0200 Subject: [PATCH 0787/1216] Minor refinements to the icon --- Cocoa/AppIcon.icns | Bin 261383 -> 327033 bytes Windows/sameboy.ico | Bin 114427 -> 115816 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Cocoa/AppIcon.icns b/Cocoa/AppIcon.icns index 05a241c99cf657fc92e594c6ad2fcf48f23e464b..0fe800598b875028a395987c4c141f45b24b449c 100644 GIT binary patch literal 327033 zcmeFZ1yoe+*Ef7-7`j0aq(fAsyF*e13F$_pyBmgX>F!24Bm|^YQd$J0k?wB30dM_# z-|zFRcdhUFo_D>^`u?*R=Ir0z``U5#b>>_#$5_w87JytjE69kD0{~!TY|F|50N^ZJ zE^|`=03jLcF*5@I(xI{04UULtc8f!9a2Sn|ZLG({2>@h6Fq#zro=?MQuAiUGYyi;Q z0He8oeljxw;PiLD+6S(K0q8Xt05W{*z!?DS7!Cm9xxG2yxa0q#5`?{8K%_WsNi+aR zO;&s=A|~{kP7r?aOj7dsPZ|PK=1_y`K-9Q?smJqqaX`3Tf2qe8ghCY*par+|jd*Sb zG#-+b|BD}^r=M3)kP{PgV;yEL?$xXOg8Z0ri@)gjvXX*=w;m}`H#E%ec<%y8R_?7d zjPm#Kh4YrbVFd%A>+9dq<@h#$b8cW*bV+>P+;DtJHx$f%61Owd72?c&OT+9X5!+cA z80-Cu=C-piGqd?g-^vF;BOpP%xAHfw33_ou_&k2A7bnL!JI56NZoVK7nguB+`o&Lp zZBSTTT<|kHH|F$S7Zn$ARk_}{b3<2E6c?8SXT{ynuz-@hiy`@MZ<7O~f_(zu{1k3j ze-E#~w;5b;1H&9j6Y}PV6H2?GVD6^zt3uTws{FS!to<}n6{Qz)&;Ld9t12ldsQjdF z<&B|M5M#kx`J2{6y!atPUcc4L3k%ZH3d(P3m_unW_sYtCagz;}8#UtIruUA(^`!Oee>*VoOkX8s4!myI|6|BmNs*8GpOTK8*g=^MhnxNT8$K zLxDl)PbEcRMGlbPypa%K|7~4d4qzW3TX~5mKI(p8FbKK= z0QO7(u%-h5JP81B*D9r6jt|B_wvtq}1pxf}H*e5WdFmYifJhlDsMx7UOY!JgnltFW zwtS_};ACzElLi1jCmz_LxxSq)xs$n>g)NU0Kjp0i5A65`W~3y)6|pnrr&N)aB^R-@ z(I;nTU}IpS6hI*-C+D+yZNMWhD*l%`jN+#>va_?|VPtf4bYyU3Ww5j{WMt;%=4NDK zVPs*Uhe^=eI$PN3I?-F$QvGb?S39Emwt6b|nHx8tXzyfO6q_0vx~ zV}pNLvatOtELebyHylQ01}4V;NKN0#_&-s*;ryp+upIpk>u<)Kbgh0W-6rFwk9;>C z@W|`iTAJD4graC+Y$w3Nck9?c;eSQ;Q%mJvY66_Ubp8bYsUTxxtPe}(jU55zzij-8 z{Zn4`KQizq_)i5{BTG9=SS8pP>piuw)3>p*fmOw=xnJ)6=MsOkb(3BmSz{-CGZj%| zbA1cj8;4n8P5g(9e=3StnpxVsw9?hn7vNxLXJ!9O?GN~$8n>oY|1(p6F#f4126I$N z-{#goCKeW0PH#Q@6aGU5Rv0`YHu}1D`l7J601Fc{J3TWqJrkz_3kwem2M;?ZALBn{ z{-G^x`P$gP`Ij~aJrgTEE0Y2f6Aud)4>K%3H`;&5{6qWJwxx}NrKOpG@J-Jqzdaka zMwV9OA|m84f7r+$O6%$wTi99J8a?{MnZK0&YLAccroa8s|9*DvTNKv21yEp`zpfMl zDEtQ_Phr;;jHjZ)3M$Ce%Fiv;#qL~f9X@t=GNIC8{5SyN&NEVktU?niB|d63>JA*m zVk1rkI@$_9c+^4%W$FmoQ%pt0ip=C^KjiR&xPl=#8TcP9GFqPoX6~*F^l^_hg}Jjj zF41J^g>9#>F7NgwjuDnjS%@gGN%vq0O4uh9GDHQn*gjpN7-ioZ#_IYk*2}hy`f(Hx3Q^A>R6_jq*NAHy=W8 zwFW~Nc9+~8d_rKVhECz@*WDknkB!)TvG3f|H=v1bbzT1AWiE@H`h*ZrItbq8JGLRM zCpGDbw%5FCT6-0U#)SiAz{o*FjFhBylRv5Ep~623N_Yk|9(-y@dSpW?lPl_Q)cX=l zKslVR)Sc#|E!(n`rWu463wWCxuHpB_A;;Gwn5$TsA)42Q9ate43=289u@z9}#sVfe z9(ot$hSbv6HTuzd2rY(Eh7Iq{KW8KQpKMHM%7JHXZE{U*qngfbsN^y8bS2MyQup}r zVm#0e^xz_b>(paj4)CtHj(Kj~&)vMcyp(h4VVHyTx=K}B>+}cJNwu-)LcxP+OoBF}=H@$R=!%jifx2s>ZDUr7L0 z;o{UlipsYdc+Fw#{Bxn$6sxV4+N@`95Hlbx$M5wMAI8U3ysLv|e}1#jIU-eH-Gp8g z-|YWG<(ubK4ohSt+^kff$8pd7!X8LR+nJglSIfwO*$moFpn6H3BlXxO!U?`Kb+kJR zN~^?`L|AUsb>XbQ2nF=cMsee(`>8VVn<>2X8|4_+^$Qb~3=1RgOjVw20IZ5veibs46& ze@@px(Nw@F@#48(`Z@2IB5@_O=laMQ4XYdJWRHPS@(q1#dR(v;dQGB1exvN&5Wy^^ z$yagj9#WAHSzx|!+353>&Jll;pc_!9!RN8oD~G2VQ%(UQ6cz=pfYJThj7 ztifc*)Qm@N>Z_M`aNe0BWXPe+WNidJdtjF)emzs;AUKnK5rNq7?Y)tQbCH1rLW>Ny z%h2ExJ2^W1SZJ5u~HaZ(} z*8`VR9;8crkwK>F?e|>K^f?b&SgO6buXrl0-!f?y7a*-!E|ILQDG1R>|@p9m}w@nHqv+fZ+gy$YaefIjT)@AU`zO9Jtx zx!la`e7^$t{M?%nR=_n949iLLl<~^DqwjCxa5`7`Y|trYqj(7*P8;rW3zeg!}l^bbd2LpN8~0#{dP0erMW%I~PW3(#F$;=Enrk^|k`-2Z?97w;@YcPEd4 z;6K4E?*c^s@Tj8lH#ec&q$RKb@m*YXUYyEbfLn+!#9aZF&Hz|Bd0+o7s<^NQ@NJy1 z#3z-86vLImhH1A&l~gPe#0?R-8OGffRZ=lGx3ixsf-^Vw-!NalAOzokU_pKn2tj|t zh>8D(!D=U|7>0Q|+L;?0+ME3rzGZN82f0|8o0}NeIQ?e83h2gzzqRte#e{x(dJ|i5 zFgL{NCke|!Qt|TuZUhn6x}OLvV@YAf;M-7dj=hSZZh#N$o#6Ec`bz?Kij#`p=H?ao z6~h<3y&2cumQPZCrk9LQkxwx~K08Zh;&0UZ)iZM+uVSRataN7P{NH%d&!Q?u%3)`% ztuFc9N=cv*y}lPrzKDgTrp)E`b;uo$s6p{_7V^)CP#5&(fJ!{1PUE2_k>X7FvCu*9cThLpoq!iLAUMU_@A5yTIX zxEVa(7FAj~KfkJUkWFQ=~X zTlkj2&u^@)tf=@>Mpf%K16Dvc9{jD9|1Bo^)6<*S%7ghKc0Wm2QKgm31n?tBcr^S( zU>QpbD+k|(dUNbm4$Xjd{tV!)*B|IF3D_x4D=#T1Eb}XeFDtp3Pq;0rw4xjz8NV{0 za)hE+nz^u9gIhGU(D&6d3qP-Nq|)4M6_ujjc=YKHlX>mG$Wb<)!~RE9Dgxe&s+Z^bbe9%OURW51)|mKfzM(a>S76xU!nqn^11j5?GE{pV&~Es{R+? z9TE)jQT<(1zh?Pvz8V2QG3T#&kl!CUia$9&|K}Y3o(TEt;4tS8{vY!szvY@?PW*cP zoBum4zYqSMpI_DSw|TXH=jUez&Z_>Eo4)Fg6bATLd|HtM4|8DC4+BrZj zdS(`SCgz(NL{?@VmcM2Y|0{EVoc|4Tfb|)6Bf;gEs&=c$-&2K#e{&LjA%>76ym+YE&x}9`!L~4$ajQU%K%J09UUQT8+C`TQA$O4^z`&6^IKE?=S+Uko_NLw z^tvjk2ath%$vQ%j{yX5MmpKOOCy^_5~TTt||zS=Y<3(k=7 z5jn(jtAjAK;gi)?`iH`e55w+tHb<4T$CBJ$+S|?kk(e3}i)ayh5PzgG2X!hjeS|xa z_qb5#PkBSdX)!;|zCJk6_{3|c?<8iFaF__Gu(4ia^ueRv2Ou<}$!%rmDt)i^@#$fw zZ9-OFRQj1-Fk%kM5B@0!7q@4s_J{T2APta9$6g*@pf9BfQu*}B4ng)JmWo_aQP zE6?Mmp`)1}%xtd`Wd)0&Sg%7&}>kCmr=zI=Ic0@s@RIA5;)4w%$=QJ0v{De!#X z*e=mI_Ts_~%Sl3l*Ti&P3Qk5EcG76}xNB7(D*L&#jO<~&KAAE^!9INZHdeu|NLGW3ENE8@cD`>3GLCzItAMbl^d zR<<18d9?Z9=S)ctMq+42U!|G_6ypkE%}l8_JbXhE5@%4$^uSigfr22~f(N95wcDUs ziZA|YHu)V!1`gkS91OqL%y;BwPXj9_qtib-MYV?ty`q{ckFt!Hv2+Rb|c3ZB*AkRCe@P7U=xtxB!-&EuWC|EXK zFm(F(L3z`AI|?)7A4HVUd=cko2M+|@#YqAv2J$^FMA)sZ_s+(v1eH4m3Yn`pRMZ|M z_c7>AI(}Ahtiv_A3Vbk+;zo@V%N4wZN~#dM%0tSPA1Dl7tji!)#=BNph>a#M=?n^I z-i}VDuGH&rd0qz~zfK;hAT6YQqTr7`Mlfh3)%B%HeRpvSud_fE2vZJDe1iBgbQoOpe=o@F$RdHYdx+=B`=t3^WPnQ}zgL>0O>8E#X# zHvO0LV>g`V>0P6wJ2_>7gdvGV(AHZ=1on`d6UM~UjhcRGs11)rTW%$`+0HhE!Wx-I*y zEt0ELtDej9>IoEXuZ^8$qph<5)qGPumN;ARQ~n?u+lpryroFxWiQ2Njo%pfY3n=24 zgqzAkLB*WUQ~@Q6m-9Q?m~&~0MYE)2%8Zh44-dXCcfgTet!X*^s6JU7m*bn-RC2j8 z)}|7;e~QNW$q3*Jh(>g+wkposnN>RBJSuv zh-SZ%56+3Jbu|09uKBs#ec8w4lco8(P~-;dPPKQD^cQTOu`=D2v+6-yOS^0Fi5?~6 zCE+qe-;akl(YUyAI2EBsU*nyRLuw0(iV(F=CiL6(Pu*;sN7r{YLJ>>nbSo}qS5{x< zryV%)!BIY1Xzr_Y`~Z>C+GyAb?)fpHSEXt`P6xI*9NrzV2BEZi9v6NsOipBr3-R3{vgz{hCrozm^^?z)P`VU+G69%bFtURn7|MiV3{~d$W zyzwVA0B~>huR*G&vz^AM^Yy#FypI$2QPpc+t82}}o0)~~BGk0%pb;Q}o+QTs==@KU zZ0l2^^B(YbJ(anlc$A6GZ9M#r-%rR@z6C^t_G#A}{nN)vy0Xe@wR6erNVbN!)#LND zG?shnN0Ou;jt$t9msk5-x2MmyE}LeKr(g$D=TY9Rp?nm}WaFEdmEYhUsn+g#T&$f33_+U22^;PBpv)zgNd;5>mT<1QW``?81es<5G`$p7u^STN30wX85g{(W`N z-T3P!yYb5NN?V(PMl%f6-X&vnAbm#0{xEEDV>Pp|@a1;XnG9od3!m{A3ZP~R-DtdM z40~&A9Fv|d;o%8wBxMBo4wo=`J76X;a^Dv-nzMOdP+o5Jct(7fpcY`V&MkSQVXW}2 zudkWOGs!^=1Q0_7jldI3`?^%d`0u^~PmT7yTXpwb+_MX{dkiF5yTx}XnaG5(y>%-P zPvl=_tC#|k`lHVl_)>jPE_SNo*ar29b&^aj*7p<{lUBgcb$QBN*_S9r(-7Y=jJ5vh z@uv>154clbY!RO1&?}6-a=K9YAnqE@j9E4QWPE&FK~`20-p?=FAwuhLhy>urK&-B< zm9sx2Tw7bqg56+$Fv{$5)!kfPULLO0fBuM_J=SJ_kIi}8iE)qw&?<{JF)=B~&em&W z`84Vd5ZkjSOUJ~=m$^>a)q(~PEiA{(zKb||7r_gqhA~~_@&Zxs&CwtT3iVRStZSk5 z>m+uT;HW4X|JLGI_*YVIeIB~t%DM}NDxujNroJnxwQx!^nzBroqo!QJfCj~J_r1=F zd8a@sl1^YnbCwqLiH!7KUZz)T5X~^ZM=uEIm0{17e`XTA)qzUiGN5C2r1c5|h;U;f zt1=Qt9ROG0hvrJ?g}GSEPD6lsXb|U1KL}vq?_wRnv1A6=U5~U&9&o-&q|B6g0w?+; zm+MV5AVkc{8C7^6z(S6l=XwDKtb4yp78*zb8M0Pk?62o2GfmJwG82a}3)ZBCb5?;5 zmfJ;MH|{nO%v(Uj*ogtzjItVzm%9=VR;E#xJMo3@|!YOU_a6pS zD36fIW*()>G%voLA?mBJcjQ@F@xmRM8W*v@OXP=afA?O&+4Yxb9udb?4SvN`uE+hQ zBeg@T{rz&!^@vh=6-6#TFxVVA8(CS&mY%0DoLyMm_sNbgZ03@Q{oIeUMRK~rRm~I; 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
  1. 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

    k%MuEiIP*A}5UBQ22H;i*enXrajP2o1I$ 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 literal 114427 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 From ea09dfc88817a8fd07a1ef8a65b812fc24aeae7f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 4 Dec 2018 23:46:30 +0200 Subject: [PATCH 0788/1216] Fixed multiplayer SGB mode --- Core/joypad.c | 1 - Core/memory.c | 3 +-- Core/sgb.c | 11 +++++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Core/joypad.c b/Core/joypad.c index 053ef2f5..7713cbdf 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -1,4 +1,3 @@ -#include #include "gb.h" #include diff --git a/Core/memory.c b/Core/memory.c index 3a646aec..dc394fad 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -731,8 +731,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_JOYP: GB_sgb_write(gb, value); - gb->io_registers[GB_IO_JOYP] &= 0x0F; - gb->io_registers[GB_IO_JOYP] |= value & 0xF0; + gb->io_registers[GB_IO_JOYP] = value & 0xF0; GB_update_joyp(gb); return; diff --git a/Core/sgb.c b/Core/sgb.c index bd3edc27..2fd134bb 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -84,13 +84,11 @@ static void command_ready(GB_gameboy_t *gb) } if (gb->sgb->command[0] == 0xf9) { if (gb->sgb->command[0xc] != 3) { // SGB Flag - GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); gb->sgb->disable_commands = true; } } else if (gb->sgb->command[0] == 0xfb) { if (gb->sgb->command[0x3] != 0x33) { // Old licensee code - GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); gb->sgb->disable_commands = true; } } @@ -247,6 +245,11 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) switch ((value >> 4) & 3) { case 3: gb->sgb->ready_for_pulse = true; + /* TODO: This is the logic used by BGB which *should* work for most/all games, but a proper test ROM is needed */ + if (gb->sgb->player_count > 1 && (gb->io_registers[GB_IO_JOYP] & 0x30) == 0x10) { + gb->sgb->current_player++; + gb->sgb->current_player &= gb->sgb->player_count - 1; + } break; case 2: // Zero @@ -299,10 +302,6 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); gb->sgb->ready_for_stop = false; } - if (gb->sgb->player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { - gb->sgb->current_player++; - gb->sgb->current_player &= gb->sgb->player_count - 1; - } break; default: From e8dfc18d11292a6ddf5b9d15ee9ded75813348ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 5 Dec 2018 00:00:16 +0200 Subject: [PATCH 0789/1216] Multiplayer SGB support in Cocoa (Keyboard only) --- Cocoa/AppDelegate.m | 4 +- Cocoa/GBButtons.h | 8 ++- Cocoa/GBPreferencesWindow.h | 2 + Cocoa/GBPreferencesWindow.m | 23 +++++-- Cocoa/GBView.m | 91 ++++++++++++++------------ Cocoa/Preferences.xib | 126 ++++++++++++++++++++++++++---------- 6 files changed, 171 insertions(+), 83 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 96331a6e..bbaa3aee 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -13,8 +13,8 @@ { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; for (unsigned i = 0; i < GBButtonCount; i++) { - if ([[defaults objectForKey:button_to_preference_name(i)] isKindOfClass:[NSString class]]) { - [defaults removeObjectForKey:button_to_preference_name(i)]; + if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) { + [defaults removeObjectForKey:button_to_preference_name(i, 0)]; } } [[NSUserDefaults standardUserDefaults] registerDefaults:@{ diff --git a/Cocoa/GBButtons.h b/Cocoa/GBButtons.h index 314a930a..7b2ea5d9 100644 --- a/Cocoa/GBButtons.h +++ b/Cocoa/GBButtons.h @@ -13,13 +13,17 @@ typedef enum : NSUInteger { GBTurbo, GBRewind, GBUnderclock, - GBButtonCount + GBButtonCount, + GBGameBoyButtonCount = GBStart + 1, } GBButton; extern NSString const *GBButtonNames[GBButtonCount]; -static inline NSString *button_to_preference_name(GBButton button) +static inline NSString *button_to_preference_name(GBButton button, unsigned player) { + if (player) { + return [NSString stringWithFormat:@"GBPlayer%d%@", player + 1, GBButtonNames[button]]; + } return [NSString stringWithFormat:@"GB%@", GBButtonNames[button]]; } diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index b1284851..90eee545 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -16,5 +16,7 @@ @property (weak) IBOutlet NSPopUpButton *dmgPopupButton; @property (weak) IBOutlet NSPopUpButton *sgbPopupButton; @property (weak) IBOutlet NSPopUpButton *cgbPopupButton; +@property (weak) IBOutlet NSPopUpButton *preferredJoypadButton; +@property (weak) IBOutlet NSPopUpButton *playerListButton; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index ae4b59fc..ac63080c 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -103,7 +103,10 @@ - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { - return GBButtonCount; + if (self.playerListButton.selectedTag == 0) { + return GBButtonCount; + } + return GBGameBoyButtonCount; } - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row @@ -115,17 +118,23 @@ if (is_button_being_modified && button_being_modified == row) { return @"Select a new key..."; } + + NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)]; + if (key) { + return [NSString displayStringForKeyCode: [key unsignedIntegerValue]]; + } - return [NSString displayStringForKeyCode:[[NSUserDefaults standardUserDefaults] integerForKey: - button_to_preference_name(row)]]; + return @""; } - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { + dispatch_async(dispatch_get_main_queue(), ^{ is_button_being_modified = true; button_being_modified = row; tableView.enabled = NO; + self.playerListButton.enabled = NO; [tableView reloadData]; [self makeFirstResponder:self]; }); @@ -144,8 +153,9 @@ is_button_being_modified = false; [[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode - forKey:button_to_preference_name(button_being_modified)]; + forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)]; self.controlsTableView.enabled = YES; + self.playerListButton.enabled = YES; [self.controlsTableView reloadData]; [self makeFirstResponder:self.controlsTableView]; } @@ -419,4 +429,9 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil]; } +- (IBAction)reloadButtonsData:(id)sender +{ + [self.controlsTableView reloadData]; +} + @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 194ab5a7..b9548399 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -169,26 +169,32 @@ bool handled = false; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - for (GBButton i = 0; i < GBButtonCount; i++) { - if ([defaults integerForKey:button_to_preference_name(i)] == keyCode) { - handled = true; - switch (i) { - case GBTurbo: - GB_set_turbo_mode(_gb, true, self.isRewinding); - break; - - case GBRewind: - self.isRewinding = true; - GB_set_turbo_mode(_gb, false, false); - break; - - case GBUnderclock: - underclockKeyDown = true; - break; - - default: - GB_set_key_state(_gb, (GB_key_t)i, true); - break; + unsigned player_count = GB_is_sgb(_gb)? 4: 1; + for (unsigned player = 0; player < player_count; player++) { + for (GBButton button = 0; button < GBButtonCount; button++) { + NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; + if (!key) continue; + + if (key.unsignedShortValue == keyCode) { + handled = true; + switch (button) { + case GBTurbo: + GB_set_turbo_mode(_gb, true, self.isRewinding); + break; + + case GBRewind: + self.isRewinding = true; + GB_set_turbo_mode(_gb, false, false); + break; + + case GBUnderclock: + underclockKeyDown = true; + break; + + default: + GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true); + break; + } } } } @@ -204,29 +210,34 @@ bool handled = false; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - for (GBButton i = 0; i < GBButtonCount; i++) { - if ([defaults integerForKey:button_to_preference_name(i)] == keyCode) { - handled = true; - switch (i) { - case GBTurbo: - GB_set_turbo_mode(_gb, false, false); - break; - - case GBRewind: - self.isRewinding = false; - break; - - case GBUnderclock: - underclockKeyDown = false; - break; - - default: - GB_set_key_state(_gb, (GB_key_t)i, false); - break; + unsigned player_count = GB_is_sgb(_gb)? 4: 1; + for (unsigned player = 0; player < player_count; player++) { + for (GBButton button = 0; button < GBButtonCount; button++) { + NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; + if (!key) continue; + + if (key.unsignedShortValue == keyCode) { + handled = true; + switch (button) { + case GBTurbo: + GB_set_turbo_mode(_gb, false, false); + break; + + case GBRewind: + self.isRewinding = false; + break; + + case GBUnderclock: + underclockKeyDown = false; + break; + + default: + GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false); + break; + } } } } - if (!handled && [theEvent type] != NSEventTypeFlagsChanged) { [super keyUp:theEvent]; } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 245bca88..fe6241ea 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -69,6 +69,8 @@ + + @@ -89,7 +91,7 @@ - + @@ -134,7 +136,7 @@ - + @@ -173,7 +175,7 @@ - + @@ -215,7 +217,7 @@ - + @@ -248,7 +250,7 @@ - + @@ -276,7 +278,7 @@ - + @@ -305,8 +307,12 @@ + + + + - + @@ -323,10 +329,6 @@ - - - - @@ -335,7 +337,7 @@ - + @@ -367,24 +369,46 @@ - + - - + + + + + + - + - + @@ -437,30 +461,62 @@ - - + - + From 612cd07fb3203aa61e5c2ce36d68973e41612533 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 12 Dec 2018 23:44:00 +0200 Subject: [PATCH 0790/1216] Fixed emulation of echo RAM --- Core/memory.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 4053530e..9466ab1a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -193,7 +193,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFE00) { - return gb->ram[addr & 0x0FFF]; + return read_banked_ram(gb, addr); } if (addr < 0xFF00) { @@ -409,7 +409,7 @@ static GB_read_function_t * const read_map[] = 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 */ + read_ram, read_high_memory, /* EXXX FXXX */ }; uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) @@ -537,7 +537,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (addr < 0xFE00) { GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); - gb->ram[addr & 0x0FFF] = value; + write_banked_ram(gb, addr, value); return; } @@ -907,7 +907,7 @@ static GB_write_function_t * const write_map[] = 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 */ + write_ram, write_high_memory, /* EXXX FXXX */ }; void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) From 7b02b3cb89d40c6266b43dd54153e8657eececb1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Dec 2018 18:01:30 +0200 Subject: [PATCH 0791/1216] Fix automation --- Tester/main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 2ea76285..643c39f9 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -69,28 +69,28 @@ static void vblank(GB_gameboy_t *gb) push_a_twice? frames / 4: frames) % combo_length + (start_is_bad? 20 : 0) ) { case 0: - gb->keys[push_right? 0 : 7] = true; // Start (Or right) down + gb->keys[0][push_right? 0 : 7] = true; // Start (Or right) down break; case 10: - gb->keys[push_right? 0 : 7] = false; // Start (Or right) up + gb->keys[0][push_right? 0 : 7] = false; // Start (Or right) up break; case 20: - gb->keys[b_is_confirm? 5: 4] = true; // A down (or B) + gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) break; case 30: - gb->keys[b_is_confirm? 5: 4] = false; // A up (or B) + gb->keys[0][b_is_confirm? 5: 4] = false; // A up (or B) break; case 40: if (push_a_twice) { - gb->keys[b_is_confirm? 5: 4] = true; // A down (or B) + gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) } else if (gb->boot_rom_finished) { - gb->keys[3] = true; // D-Pad Down down + gb->keys[0][3] = true; // D-Pad Down down } break; case 50: - gb->keys[b_is_confirm? 5: 4] = false; // A down (or B) - gb->keys[3] = false; // D-Pad Down up + gb->keys[0][b_is_confirm? 5: 4] = false; // A down (or B) + gb->keys[0][3] = false; // D-Pad Down up break; } } From 21eb96a2f5deb5e8f23e282e3dd12b0a303db906 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Dec 2018 18:55:41 +0200 Subject: [PATCH 0792/1216] Joypad multiplayer support (Cocoa) --- Cocoa/GBPreferencesWindow.m | 49 +++++++++++++ Cocoa/GBView.m | 141 +++++++++++++++++++++--------------- Cocoa/Preferences.xib | 31 ++++---- Core/gb.c | 5 ++ Core/gb.h | 2 + 5 files changed, 155 insertions(+), 73 deletions(-) diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index ac63080c..982aa450 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -19,6 +19,7 @@ NSEventModifierFlags previousModifiers; NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; + NSPopUpButton *_preferredJoypadButton; } + (NSArray *)filterList @@ -264,6 +265,7 @@ all_mappings[joystick_name] = mapping; [[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; + [self refreshJoypadMenu:nil]; [self advanceConfigurationStateMachine]; } @@ -434,4 +436,51 @@ [self.controlsTableView reloadData]; } +- (void)setPreferredJoypadButton:(NSPopUpButton *)preferredJoypadButton +{ + _preferredJoypadButton = preferredJoypadButton; + [self refreshJoypadMenu:nil]; +} + +- (NSPopUpButton *)preferredJoypadButton +{ + return _preferredJoypadButton; +} + +- (IBAction)refreshJoypadMenu:(id)sender +{ + NSArray *joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] allKeys]; + for (NSString *joypad in joypads) { + if ([self.preferredJoypadButton indexOfItemWithTitle:joypad] == -1) { + [self.preferredJoypadButton addItemWithTitle:joypad]; + } + } + + NSString *player_string = [NSString stringWithFormat: @"%ld", (long)self.playerListButton.selectedTag]; + NSString *selected_joypad = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"][player_string]; + if (selected_joypad && [self.preferredJoypadButton indexOfItemWithTitle:selected_joypad] != -1) { + [self.preferredJoypadButton selectItemWithTitle:selected_joypad]; + } + else { + [self.preferredJoypadButton selectItemWithTitle:@"None"]; + } + [self.controlsTableView reloadData]; +} + +- (IBAction)changeDefaultJoypad:(id)sender +{ + NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] mutableCopy]; + if (!default_joypads) { + default_joypads = [[NSMutableDictionary alloc] init]; + } + + NSString *player_string = [NSString stringWithFormat: @"%ld", self.playerListButton.selectedTag]; + if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) { + [default_joypads removeObjectForKey:player_string]; + } + else { + default_joypads[player_string] = [sender titleOfSelectedItem]; + } + [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"GBDefaultJoypads"]; +} @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index b9548399..7e425c54 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -169,7 +169,7 @@ bool handled = false; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - unsigned player_count = GB_is_sgb(_gb)? 4: 1; + unsigned player_count = GB_get_player_count(_gb); for (unsigned player = 0; player < player_count; player++) { for (GBButton button = 0; button < GBButtonCount; button++) { NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; @@ -210,7 +210,7 @@ bool handled = false; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - unsigned player_count = GB_is_sgb(_gb)? 4: 1; + unsigned player_count = GB_get_player_count(_gb); for (unsigned player = 0; player < player_count; player++) { for (GBButton button = 0; button < GBButtonCount; button++) { NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; @@ -245,31 +245,42 @@ - (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state { + unsigned player_count = GB_get_player_count(_gb); + UpdateSystemActivity(UsrActivity); - NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; - - for (GBButton i = 0; i < GBButtonCount; i++) { - NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]]; - if (mapped_button && [mapped_button integerValue] == button) { - switch (i) { - case GBTurbo: - GB_set_turbo_mode(_gb, state, state && self.isRewinding); - break; + for (unsigned player = 0; player < player_count; player++) { + NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] + objectForKey:[NSString stringWithFormat:@"%u", player]]; + if (player_count != 1 && // Single player, accpet inputs from all joypads + !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads + ![preferred_joypad isEqualToString:joystick_name]) { + continue; + } + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; + + for (GBButton i = 0; i < GBButtonCount; i++) { + NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]]; + if (mapped_button && [mapped_button integerValue] == button) { + switch (i) { + case GBTurbo: + GB_set_turbo_mode(_gb, state, state && self.isRewinding); + break; + + case GBRewind: + self.isRewinding = state; + if (state) { + GB_set_turbo_mode(_gb, false, false); + } + break; - case GBRewind: - self.isRewinding = state; - if (state) { - GB_set_turbo_mode(_gb, false, false); - } - break; - - case GBUnderclock: - underclockKeyDown = state; - break; - - default: - GB_set_key_state(_gb, (GB_key_t)i, state); - break; + case GBUnderclock: + underclockKeyDown = state; + break; + + default: + GB_set_key_state_for_player(_gb, (GB_key_t)i, player, state); + break; + } } } } @@ -277,43 +288,55 @@ - (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value { + unsigned player_count = GB_get_player_count(_gb); + UpdateSystemActivity(UsrActivity); - NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; - NSNumber *x_axis = [mapping objectForKey:@"XAxis"]; - NSNumber *y_axis = [mapping objectForKey:@"YAxis"]; - - if (axis == [x_axis integerValue]) { - if (value > JOYSTICK_HIGH) { - axisActive[0] = true; - GB_set_key_state(_gb, GB_KEY_RIGHT, true); - GB_set_key_state(_gb, GB_KEY_LEFT, false); + for (unsigned player = 0; player < player_count; player++) { + NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] + objectForKey:[NSString stringWithFormat:@"%u", player]]; + if (player_count != 1 && // Single player, accpet inputs from all joypads + !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads + ![preferred_joypad isEqualToString:joystick_name]) { + continue; } - else if (value < -JOYSTICK_HIGH) { - axisActive[0] = true; - GB_set_key_state(_gb, GB_KEY_RIGHT, false); - GB_set_key_state(_gb, GB_KEY_LEFT, true); + + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; + NSNumber *x_axis = [mapping objectForKey:@"XAxis"]; + NSNumber *y_axis = [mapping objectForKey:@"YAxis"]; + + if (axis == [x_axis integerValue]) { + if (value > JOYSTICK_HIGH) { + axisActive[0] = true; + GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, true); + GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false); + } + else if (value < -JOYSTICK_HIGH) { + axisActive[0] = true; + GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false); + GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, true); + } + else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { + axisActive[0] = false; + GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false); + GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false); + } } - else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { - axisActive[0] = false; - GB_set_key_state(_gb, GB_KEY_RIGHT, false); - GB_set_key_state(_gb, GB_KEY_LEFT, false); - } - } - else if (axis == [y_axis integerValue]) { - if (value > JOYSTICK_HIGH) { - axisActive[1] = true; - GB_set_key_state(_gb, GB_KEY_DOWN, true); - GB_set_key_state(_gb, GB_KEY_UP, false); - } - else if (value < -JOYSTICK_HIGH) { - axisActive[1] = true; - GB_set_key_state(_gb, GB_KEY_DOWN, false); - GB_set_key_state(_gb, GB_KEY_UP, true); - } - else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { - axisActive[1] = false; - GB_set_key_state(_gb, GB_KEY_DOWN, false); - GB_set_key_state(_gb, GB_KEY_UP, false); + else if (axis == [y_axis integerValue]) { + if (value > JOYSTICK_HIGH) { + axisActive[1] = true; + GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, true); + GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false); + } + else if (value < -JOYSTICK_HIGH) { + axisActive[1] = true; + GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false); + GB_set_key_state_for_player(_gb, GB_KEY_UP, player, true); + } + else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { + axisActive[1] = false; + GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false); + GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false); + } } } } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index fe6241ea..e754199b 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -373,7 +373,7 @@ - @@ -471,7 +460,7 @@ - + @@ -482,6 +471,9 @@

    v~{qotKc(^CSL{nFjA-R#r)k#@5h+4P3H3 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 =#bdPl^da2PW@5H*+2h*Br+#2o29&8dCU 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�$H?BAfMwe=apQ zIr#WQ{`6rradc!LZ6bqQMNug3z1ic_*f zuoD`!Z~@kmwnD*4Iq>uUgLi$73_hzD9*u$`!HQXU4M9_m6WX=3jixFz- z0-~bY%#phq#`B5P!WgAG`(PvX;IXm$YH3R1cV3~Kmo7rzD2PdB-@cq#Qj+-XyZf!P zzZqT{3uCB*`-N_L!T$XD10TnDC};so7F0bKDDv|1#H=@C7uo|wM)#i7!a1Vda&gwr zVx+s@ynO+j+M9rjh9xWE!%Ldm8LqnsQEF?-6K!A@kE6|kY$r}yP=z`9`Oxg}L)8nq=7dpqqr zNs?w)NJuW;8sWJ=3EC{KrOW`nFWEtOPd*M zR|l&hXYGQ%fi@qdXeyx-tN!GPO}~mHyd}H1tNfJ#eeXPHn$v;VPt^-V*

  2. + + + @@ -512,9 +504,20 @@
    - + + diff --git a/Core/gb.c b/Core/gb.c index 93ecb08d..d9963c18 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -789,3 +789,8 @@ size_t GB_get_screen_height(GB_gameboy_t *gb) { return GB_is_sgb(gb)? 224 : 144; } + +unsigned GB_get_player_count(GB_gameboy_t *gb) +{ + return GB_is_sgb(gb)? gb->sgb->player_count : 1; +} diff --git a/Core/gb.h b/Core/gb.h index 2808677f..39f4162b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -675,4 +675,6 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); size_t GB_get_screen_width(GB_gameboy_t *gb); size_t GB_get_screen_height(GB_gameboy_t *gb); +unsigned GB_get_player_count(GB_gameboy_t *gb); + #endif /* GB_h */ From af0430dbc5652175d2433c4c80920f3407518a85 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 31 Dec 2018 22:06:20 +0200 Subject: [PATCH 0793/1216] Unroll some loops in PPU code, more efficient timer handling --- Core/display.c | 4 ++++ Core/timing.c | 21 ++++++++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Core/display.c b/Core/display.c index 2759146e..aae3a476 100644 --- a/Core/display.c +++ b/Core/display.c @@ -27,6 +27,7 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) { if (!flip_x) { + #pragma unroll for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower >> 7) | ((upper >> 7) << 1), @@ -42,6 +43,7 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint } } else { + #pragma unroll for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower & 1) | ((upper & 1) << 1), @@ -68,6 +70,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe uint8_t flip_xor = flip_x? 0: 0x7; + #pragma unroll for (unsigned i = 8; i--;) { uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)]; @@ -1063,6 +1066,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } for (unsigned y = 0; y < *sprite_height; y++) { + #pragma unroll for (unsigned x = 0; x < 8; x++) { uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); diff --git a/Core/timing.c b/Core/timing.c index 65382e4a..3ab4c305 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -8,7 +8,7 @@ #include #endif -static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; +static const unsigned int GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; #ifndef DISABLE_TIMEKEEPING static int64_t get_nanoseconds(void) @@ -108,11 +108,6 @@ static void advance_tima_state_machine(GB_gameboy_t *gb) } } -static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) -{ - return (old & (max >> 1)) && !(new & (max >> 1)); -} - static void increase_tima(GB_gameboy_t *gb) { gb->io_registers[GB_IO_TIMA]++; @@ -126,13 +121,13 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) { /* TIMA increases when a specific high-bit becomes a low-bit. */ value &= INTERNAL_DIV_CYCLES - 1; - if ((gb->io_registers[GB_IO_TAC] & 4) && - counter_overflow_check(gb->div_counter, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { + uint32_t triggers = gb->div_counter & ~value; + if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); } /* TODO: Can switching to double speed mode trigger an event? */ - if (counter_overflow_check(gb->div_counter, value, gb->cgb_double_speed? 0x4000 : 0x2000)) { + if (triggers & (gb->cgb_double_speed? 0x2000 : 0x1000)) { GB_apu_run(gb); GB_apu_div_event(gb); } @@ -221,13 +216,13 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) /* Glitch only happens when old_tac is enabled. */ if (!(old_tac & 4)) return; - unsigned int old_clocks = GB_TAC_RATIOS[old_tac & 3]; - unsigned int new_clocks = GB_TAC_RATIOS[new_tac & 3]; + unsigned int old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; + unsigned int new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; /* The bit used for overflow testing must have been 1 */ - if (gb->div_counter & (old_clocks >> 1)) { + if (gb->div_counter & old_clocks) { /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ - if (!(new_tac & 4) || gb->div_counter & (new_clocks >> 1)) { + if (!(new_tac & 4) || gb->div_counter & new_clocks) { increase_tima(gb); } } From 9d947c7ce639e93f42a86d1bf42135ff78f0375a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 31 Dec 2018 23:09:56 +0200 Subject: [PATCH 0794/1216] Unroll some APU loops --- Core/apu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index f9aabd7f..127d34c6 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -69,6 +69,7 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) { GB_sample_t output = {0,0}; + #pragma unroll for (unsigned i = GB_N_CHANNELS; i--;) { double multiplier = CH_STEP; if (!is_DAC_enabled(gb, i)) { @@ -125,6 +126,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) unsigned mask = gb->io_registers[GB_IO_NR51]; unsigned left_volume = 0; unsigned right_volume = 0; + #pragma unroll for (unsigned i = GB_N_CHANNELS; i--;) { if (gb->apu.is_active[i]) { if (mask & 1) { @@ -372,6 +374,7 @@ void GB_apu_run(GB_gameboy_t *gb) } } + #pragma unroll for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.is_active[i]) { uint8_t cycles_left = cycles; From 4051f190a5c611c76db4c353d25ac326799c4a43 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 1 Jan 2019 00:42:40 +0200 Subject: [PATCH 0795/1216] Cache cycles_per_sample to avoid FP arithmetic --- Core/apu.c | 13 ++++++++++--- Core/apu.h | 2 ++ Core/gb.c | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 127d34c6..147ea631 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -457,10 +457,9 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - double cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ - if (gb->apu_output.sample_cycles > cycles_per_sample) { - gb->apu_output.sample_cycles -= cycles_per_sample; + if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) { + gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; render(gb, false, NULL); } } @@ -976,9 +975,17 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) if (sample_rate) { gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); } + GB_apu_update_cycles_per_sample(gb); } void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) { gb->apu_output.highpass_mode = mode; } + +void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb) +{ + if (gb->apu_output.sample_rate) { + gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ + } +} diff --git a/Core/apu.h b/Core/apu.h index ab42055b..bfa35984 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -140,6 +140,7 @@ typedef struct { volatile bool lock; double sample_cycles; // In 8 MHz units + double cycles_per_sample; // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! unsigned cycles_since_render; @@ -164,6 +165,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); void GB_apu_div_event(GB_gameboy_t *gb); void GB_apu_init(GB_gameboy_t *gb); void GB_apu_run(GB_gameboy_t *gb); +void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); #endif #endif /* apu_h */ diff --git a/Core/gb.c b/Core/gb.c index edc0a560..fc3bdc42 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -638,6 +638,8 @@ void GB_reset(GB_gameboy_t *gb) /* Todo: Ugly, fixme, see comment in the timer state machine */ gb->div_state = 3; + GB_apu_update_cycles_per_sample(gb); + gb->magic = (uintptr_t)'SAME'; } @@ -725,6 +727,7 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) { gb->clock_multiplier = multiplier; + GB_apu_update_cycles_per_sample(gb); } uint32_t GB_get_clock_rate(GB_gameboy_t *gb) From ae959cd8787d6ff81427d932578368afad956efa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 5 Jan 2019 18:59:50 +0200 Subject: [PATCH 0796/1216] ATTR_LIN and ATTR_DIV --- Core/sgb.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/Core/sgb.c b/Core/sgb.c index 2fd134bb..9e7e3822 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -9,9 +9,11 @@ enum { PAL03 = 0x02, PAL12 = 0x03, ATTR_BLK = 0x04, + ATTR_LIN = 0x05, + ATTR_DIV = 0x06, PAL_SET = 0x0A, PAL_TRN = 0x0B, - DATA_SND = 0x0f, + DATA_SND = 0x0F, MLT_REQ = 0x11, CHR_TRN = 0x13, PCT_TRN = 0x14, @@ -136,6 +138,11 @@ static void command_ready(GB_gameboy_t *gb) middle_palette = outside_palette; } + command->data[i].left &= 0x1F; + command->data[i].top &= 0x1F; + command->data[i].right &= 0x1F; + command->data[i].bottom &= 0x1F; + for (unsigned y = 0; y < 18; y++) { for (unsigned x = 0; x < 20; x++) { if (x < command->data[i].left || x > command->data[i].right || @@ -158,6 +165,56 @@ static void command_ready(GB_gameboy_t *gb) } break; } + case ATTR_LIN: { + struct { + uint8_t count; + uint8_t data[]; + } *command = (void *)(gb->sgb->command + 1); + if (command->count > sizeof(gb->sgb->command) - 2) return; + + for (unsigned i = 0; i < command->count; i++) { + bool horizontal = command->data[i] & 0x80; + uint8_t palette = (command->data[i] >> 5) & 0x3; + uint8_t line = (command->data[i]) & 0x1F; + + if (horizontal) { + if (line > 18) continue; + for (unsigned x = 0; x < 20; x++) { + gb->sgb->attribute_map[x + 20 * line] = palette; + } + } + else { + if (line > 20) continue; + for (unsigned y = 0; y < 18; y++) { + gb->sgb->attribute_map[line + 20 * y] = palette; + } + } + } + break; + } + case ATTR_DIV: { + uint8_t high_palette = gb->sgb->command[1] & 3; + uint8_t low_palette = (gb->sgb->command[1] >> 2) & 3; + uint8_t middle_palette = (gb->sgb->command[1] >> 4) & 3; + bool horizontal = gb->sgb->command[1] & 0x40; + uint8_t line = gb->sgb->command[2] & 0x1F; + + for (unsigned y = 0; y < 18; y++) { + for (unsigned x = 0; x < 20; x++) { + if ((horizontal? y : x) < line) { + gb->sgb->attribute_map[x + 20 * y] = low_palette; + } + else if ((horizontal? y : x) == line) { + gb->sgb->attribute_map[x + 20 * y] = middle_palette; + } + else { + gb->sgb->attribute_map[x + 20 * y] = high_palette; + } + } + } + + break; + } case PAL_SET: memcpy(&gb->sgb->effective_palettes[0], &gb->sgb->ram_palettes[4 * (gb->sgb->command[1] + (gb->sgb->command[2] & 1) * 0x100)], From 112a174f4ab047e45080a34da9530b41f9ad7165 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 5 Jan 2019 23:58:18 +0200 Subject: [PATCH 0797/1216] Proper window minimum size handling --- Cocoa/Document.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index cd2e9690..c94f77b3 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -286,7 +286,13 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (old_width != GB_get_screen_width(&gb)) { [self.view screenSizeChanged]; } - + + self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) || + self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) { + [self.mainWindow zoom:nil]; + } + if ([sender tag] != 0) { /* User explictly selected a model, save the preference */ [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_DMG forKey:@"EmulateDMG"]; From de3718c718e04df60ec0b340a6dee591cbd643f0 Mon Sep 17 00:00:00 2001 From: orbea Date: Sat, 5 Jan 2019 17:53:48 -0800 Subject: [PATCH 0798/1216] Makefile: Fix typo. Fixes: make: *** No rule to make target 'build/bin/BootROMs/sgb_boot2.bin', needed by 'bootroms'. Stop. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8ae09e90..5f46008e 100755 --- a/Makefile +++ b/Makefile @@ -106,8 +106,8 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb_boot2.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders -bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb_boot2.bin +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin all: cocoa sdl tester libretro From 653c7fc1e649d6091071bd0c0b1237f32746c175 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 6 Jan 2019 20:45:15 +0200 Subject: [PATCH 0799/1216] Update and rename FAQ.md to build-faq.md --- FAQ.md | 7 ------- build-faq.md | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 FAQ.md create mode 100644 build-faq.md diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index b2246e45..00000000 --- a/FAQ.md +++ /dev/null @@ -1,7 +0,0 @@ -# Attempt to build Mac binary fails with NSInternalInconsistencyException - -When building on the darwin platform SameBoy will attempt to make a native executable and UI. In this case the environment expects a bunch of stuff to be set up by XCode. Fix this issue by starting XCode and letting it install components and set up the environment. After it's done building SameBoy should work (wehther or not XCode continues to run). - -# Attempt to build SDL binaris on Mac fails on linking - -Sameboy expects you to have installed the SDL2 framework you can find the binaries on the [SLD homepage](https://www.libsdl.org/download-2.0.php). Mount the DMG and copy SDL2.framework to `/Library/Frameworks/`. diff --git a/build-faq.md b/build-faq.md new file mode 100644 index 00000000..e2192f57 --- /dev/null +++ b/build-faq.md @@ -0,0 +1,7 @@ +# Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException + +When building on macOS, the build system will make a native Cocoa app by default. In this case, the build system uses the Xcode `ibtool` command to build user interface files. If this command fails, you can fix this issue by starting Xcode and letting it install components. After this is done, you should be able to close Xcode and build successfully. + +# Attempting to build the SDL frontend on macOS fails on linking + +SameBoy on macOS expects you to have the SDL2 framework installed as a framework. You can find the SDL2 binaries on the [SDL homepage](https://www.libsdl.org/download-2.0.php). Mount the DMG and copy SDL2.framework to `/Library/Frameworks/`. From 4e2b5bb3367241378a85f6123b98cbd249dac1df Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 11 Jan 2019 21:53:21 +0200 Subject: [PATCH 0800/1216] Fix the GB_run_frame API in SGB mode --- Core/gb.c | 2 ++ Core/z80_cpu.c | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 54265e67..0d957eef 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -351,6 +351,8 @@ exit: uint8_t GB_run(GB_gameboy_t *gb) { + gb->vblank_just_occured = false; + if (gb->sgb && gb->sgb->intro_animation < 140) { /* On the SGB, the GB is halted after finishing the boot ROM. Then, after the boot animation is almost done, it's reset. diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 4584c534..55e13352 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -1356,8 +1356,6 @@ static GB_opcode_t *opcodes[256] = { }; void GB_cpu_run(GB_gameboy_t *gb) { - gb->vblank_just_occured = false; - if (gb->hdma_on) { GB_advance_cycles(gb, 4); return; From 21d2a59a5ff0cb380024a46d19ca433489e5ebf7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Jan 2019 00:39:24 +0200 Subject: [PATCH 0801/1216] Fixed a very rare edge case where an interrupt occurs when SP=FF11 --- Core/z80_cpu.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Core/z80_cpu.c b/Core/z80_cpu.c index 55e13352..baf2c848 100644 --- a/Core/z80_cpu.c +++ b/Core/z80_cpu.c @@ -72,6 +72,21 @@ static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr) return ret; } +/* A special case for IF during ISR, returns the old value of IF. */ +/* TODO: Verify the timing, it might be wrong in cases where, in the same M cycle, IF + is both read be the CPU, modified by the ISR, and modified by an actual interrupt. + If this timing proves incorrect, the ISR emulation must be updated so IF reads are + timed correctly. */ +static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value) +{ + assert(gb->pending_cycles); + GB_advance_cycles(gb, gb->pending_cycles); + uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F; + GB_write_memory(gb, 0xFF00 + GB_IO_IF, value); + gb->pending_cycles = 4; + return old; +} + static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { assert(gb->pending_cycles); @@ -1399,8 +1414,15 @@ void GB_cpu_run(GB_gameboy_t *gb) cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); interrupt_queue = gb->interrupt_enable; - cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); - interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; + + if (gb->registers[GB_REGISTER_SP] == GB_IO_IF + 0xFF00 + 1) { + gb->registers[GB_REGISTER_SP]--; + interrupt_queue &= cycle_write_if(gb, (gb->pc) & 0xFF); + } + else { + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; + } if (interrupt_queue) { uint8_t interrupt_bit = 0; From c74b39e7129194d2fed7bb23dfec1f7fffbaa072 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Jan 2019 00:42:16 +0200 Subject: [PATCH 0802/1216] The CPU core of the Game Boy is (most likely) called SM83 --- Core/gb.h | 2 +- Core/{z80_cpu.c => sm83_cpu.c} | 0 Core/{z80_cpu.h => sm83_cpu.h} | 6 +++--- Core/{z80_disassembler.c => sm83_disassembler.c} | 0 libretro/Makefile.common | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename Core/{z80_cpu.c => sm83_cpu.c} (100%) rename Core/{z80_cpu.h => sm83_cpu.h} (74%) rename Core/{z80_disassembler.c => sm83_disassembler.c} (100%) diff --git a/Core/gb.h b/Core/gb.h index 39f4162b..c45a22db 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -17,7 +17,7 @@ #include "printer.h" #include "timing.h" #include "rewind.h" -#include "z80_cpu.h" +#include "sm83_cpu.h" #include "symbol_hash.h" #include "sgb.h" diff --git a/Core/z80_cpu.c b/Core/sm83_cpu.c similarity index 100% rename from Core/z80_cpu.c rename to Core/sm83_cpu.c diff --git a/Core/z80_cpu.h b/Core/sm83_cpu.h similarity index 74% rename from Core/z80_cpu.h rename to Core/sm83_cpu.h index 3541fa81..49fa80b5 100644 --- a/Core/z80_cpu.h +++ b/Core/sm83_cpu.h @@ -1,5 +1,5 @@ -#ifndef z80_cpu_h -#define z80_cpu_h +#ifndef sm83_cpu_h +#define sm83_cpu_h #include "gb_struct_def.h" #include @@ -8,4 +8,4 @@ void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); void GB_cpu_run(GB_gameboy_t *gb); #endif -#endif /* z80_cpu_h */ +#endif /* sm83_cpu_h */ diff --git a/Core/z80_disassembler.c b/Core/sm83_disassembler.c similarity index 100% rename from Core/z80_disassembler.c rename to Core/sm83_disassembler.c diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 889e972c..dfedccfb 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -8,7 +8,7 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/Core/display.c \ $(CORE_DIR)/Core/symbol_hash.c \ $(CORE_DIR)/Core/camera.c \ - $(CORE_DIR)/Core/z80_cpu.c \ + $(CORE_DIR)/Core/sm83_cpu.c \ $(CORE_DIR)/Core/joypad.c \ $(CORE_DIR)/Core/save_state.c \ $(CORE_DIR)/libretro/agb_boot.c \ From ba5c07bed9533011a22c7ae14633b0a2c6038fea Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 13 Jan 2019 01:09:41 +0200 Subject: [PATCH 0803/1216] Correctly emulate speed switch timing --- Core/gb.h | 1 + Core/sm83_cpu.c | 23 ++++++++++++++++++----- Core/timing.c | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index c45a22db..e37a1592 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -403,6 +403,7 @@ struct GB_gameboy_internal_s { uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; uint16_t serial_length; + uint8_t double_speed_alignment; ); /* APU */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index baf2c848..fdf357ce 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -211,14 +211,27 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode) static void stop(GB_gameboy_t *gb, uint8_t opcode) { if (gb->io_registers[GB_IO_KEY1] & 0x1) { - /* Make sure we don't leave display_cycles not divisble by 8 in single speed mode */ - if (gb->display_cycles % 8 == 4) { - cycle_no_access(gb); - } + flush_pending_cycles(gb); + bool needs_alignment = false; - /* Todo: the switch is not instant. We should emulate this. */ + GB_advance_cycles(gb, 0x4); + /* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */ + if (gb->double_speed_alignment & 7) { + GB_advance_cycles(gb, 0x4); + needs_alignment = true; + } + gb->cgb_double_speed ^= true; gb->io_registers[GB_IO_KEY1] = 0; + + for (unsigned i = 0x800; i--;) { + GB_advance_cycles(gb, 0x40); + } + + if (!needs_alignment) { + GB_advance_cycles(gb, 0x4); + } + } else { gb->stopped = true; diff --git a/Core/timing.c b/Core/timing.c index 3ab4c305..06c603a4 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -191,8 +191,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) if (!gb->cgb_double_speed) { cycles <<= 1; } - + // Not affected by speed boost + gb->double_speed_alignment += cycles; gb->hdma_cycles += cycles; gb->apu_output.sample_cycles += cycles; gb->cycles_since_ir_change += cycles; From 879d3b607d4c70d034eff1bcbe55a6a0e7f73141 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 14 Jan 2019 20:32:52 +0200 Subject: [PATCH 0804/1216] Removed verified TODO --- Core/memory.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index e0e862ac..ce25b040 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -515,8 +515,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return; if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { - /* RTC read */ - gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; /* Todo: does it really write both? */ + gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; } if (!gb->mbc_ram) { From 312478e5090714414450ab41e77d958462e16a27 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 14 Jan 2019 22:22:46 +0200 Subject: [PATCH 0805/1216] CGB palettes are not accessible during Mode 3, closes #84 --- Core/memory.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index ce25b040..a3010b06 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -357,6 +357,10 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (!gb->cgb_mode && gb->boot_rom_finished) { return 0xFF; } + /* TODO: Verify actual access timing */ + if (gb->vram_read_blocked) { + return 0xFF; + } uint8_t index_reg = (addr & 0xFF) - 1; return ((addr & 0xFF) == GB_IO_BGPD? gb->background_palettes_data : @@ -788,6 +792,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) is required. */ return; } + /* TODO: Verify actual access timing */ + if (gb->vram_write_blocked) { + return; + } uint8_t index_reg = (addr & 0xFF) - 1; ((addr & 0xFF) == GB_IO_BGPD? gb->background_palettes_data : From 9fa6a2fe929994864a37980d7161357f96cd6547 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Fri, 4 Jan 2019 20:07:41 +0100 Subject: [PATCH 0806/1216] Add MSVC2017 target - doesn't work yet --- libretro/Makefile | 127 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 3 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index cde75a05..75ddfc6c 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -6,6 +6,15 @@ ifneq ($(GIT_VERSION)," unknown") CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" endif +SPACE := +SPACE := $(SPACE) $(SPACE) +BACKSLASH := +BACKSLASH := \$(BACKSLASH) +filter_out1 = $(filter-out $(firstword $1),$1) +filter_out2 = $(call filter_out1,$(call filter_out1,$1)) +unixpath = $(subst \,/,$1) +unixcygpath = /$(subst :,,$(call unixpath,$1)) + CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" ifeq ($(platform),) @@ -134,6 +143,102 @@ else ifeq ($(platform), vita) AR = arm-vita-eabi-ar CFLAGS += -Wl,-q -Wall -O3 -fno-short-enums -fno-optimize-sibling-calls STATIC_LINKING = 1 + +# Windows MSVC 2017 all architectures +else ifneq (,$(findstring windows_msvc2017,$(platform))) + + NO_GCC := 1 + CFLAGS += -DNOMINMAX + CXXFLAGS += -DNOMINMAX + WINDOWS_VERSION = 1 + + PlatformSuffix = $(subst windows_msvc2017_,,$(platform)) + ifneq (,$(findstring desktop,$(PlatformSuffix))) + WinPartition = desktop + MSVC2017CompileFlags = -DWINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP -FS + LDFLAGS += -MANIFEST -LTCG:incremental -NXCOMPAT -DYNAMICBASE -DEBUG -OPT:REF -INCREMENTAL:NO -SUBSYSTEM:WINDOWS -MANIFESTUAC:"level='asInvoker' uiAccess='false'" -OPT:ICF -ERRORREPORT:PROMPT -NOLOGO -TLBID:1 + LIBS += kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib + else ifneq (,$(findstring uwp,$(PlatformSuffix))) + WinPartition = uwp + MSVC2017CompileFlags = -DWINAPI_FAMILY=WINAPI_FAMILY_APP -D_WINDLL -D_UNICODE -DUNICODE -D__WRL_NO_DEFAULT_LIB__ -EHsc -FS + LDFLAGS += -APPCONTAINER -NXCOMPAT -DYNAMICBASE -MANIFEST:NO -LTCG -OPT:REF -SUBSYSTEM:CONSOLE -MANIFESTUAC:NO -OPT:ICF -ERRORREPORT:PROMPT -NOLOGO -TLBID:1 -DEBUG:FULL -WINMD:NO + LIBS += WindowsApp.lib + endif + + CFLAGS += $(MSVC2017CompileFlags) + CXXFLAGS += $(MSVC2017CompileFlags) + + TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix)) + + CC = cl.exe + CXX = cl.exe + LD = link.exe + + reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>nul))) + fix_path = $(subst $(SPACE),\ ,$(subst \,/,$1)) + + ProgramFiles86w := $(shell cmd /c "echo %PROGRAMFILES(x86)%") + ProgramFiles86 := $(shell cygpath "$(ProgramFiles86w)") + + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_CURRENT_USER\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir := $(WindowsSdkDir) + + WindowsSDKVersion ?= $(firstword $(foreach folder,$(subst $(subst \,/,$(WindowsSdkDir)Include/),,$(wildcard $(call fix_path,$(WindowsSdkDir)Include\*))),$(if $(wildcard $(call fix_path,$(WindowsSdkDir)Include/$(folder)/um/Windows.h)),$(folder),)))$(BACKSLASH) + WindowsSDKVersion := $(WindowsSDKVersion) + + VsInstallBuildTools = $(ProgramFiles86)/Microsoft Visual Studio/2017/BuildTools + VsInstallEnterprise = $(ProgramFiles86)/Microsoft Visual Studio/2017/Enterprise + VsInstallProfessional = $(ProgramFiles86)/Microsoft Visual Studio/2017/Professional + VsInstallCommunity = $(ProgramFiles86)/Microsoft Visual Studio/2017/Community + + VsInstallRoot ?= $(shell if [ -d "$(VsInstallBuildTools)" ]; then echo "$(VsInstallBuildTools)"; fi) + ifeq ($(VsInstallRoot), ) + VsInstallRoot = $(shell if [ -d "$(VsInstallEnterprise)" ]; then echo "$(VsInstallEnterprise)"; fi) + endif + ifeq ($(VsInstallRoot), ) + VsInstallRoot = $(shell if [ -d "$(VsInstallProfessional)" ]; then echo "$(VsInstallProfessional)"; fi) + endif + ifeq ($(VsInstallRoot), ) + VsInstallRoot = $(shell if [ -d "$(VsInstallCommunity)" ]; then echo "$(VsInstallCommunity)"; fi) + endif + VsInstallRoot := $(VsInstallRoot) + + VcCompilerToolsVer := $(shell cat "$(VsInstallRoot)/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt" | grep -o '[0-9\.]*') + VcCompilerToolsDir := $(VsInstallRoot)/VC/Tools/MSVC/$(VcCompilerToolsVer) + + WindowsSDKSharedIncludeDir := $(shell cygpath -w "$(WindowsSdkDir)\Include\$(WindowsSDKVersion)\shared") + WindowsSDKUCRTIncludeDir := $(shell cygpath -w "$(WindowsSdkDir)\Include\$(WindowsSDKVersion)\ucrt") + WindowsSDKUMIncludeDir := $(shell cygpath -w "$(WindowsSdkDir)\Include\$(WindowsSDKVersion)\um") + WindowsSDKUCRTLibDir := $(shell cygpath -w "$(WindowsSdkDir)\Lib\$(WindowsSDKVersion)\ucrt\$(TargetArchMoniker)") + WindowsSDKUMLibDir := $(shell cygpath -w "$(WindowsSdkDir)\Lib\$(WindowsSDKVersion)\um\$(TargetArchMoniker)") + + # For some reason the HostX86 compiler doesn't like compiling for x64 + # ("no such file" opening a shared library), and vice-versa. + # Work around it for now by using the strictly x86 compiler for x86, and x64 for x64. + # NOTE: What about ARM? + ifneq (,$(findstring x64,$(TargetArchMoniker))) + VCCompilerToolsBinDir := $(VcCompilerToolsDir)\bin\HostX64 + else + VCCompilerToolsBinDir := $(VcCompilerToolsDir)\bin\HostX86 + endif + + PATH := $(shell IFS=$$'\n'; cygpath "$(VCCompilerToolsBinDir)/$(TargetArchMoniker)"):$(PATH) + PATH := $(PATH):$(shell IFS=$$'\n'; cygpath "$(VsInstallRoot)/Common7/IDE") + INCLUDE := $(shell IFS=$$'\n'; cygpath -w "$(VcCompilerToolsDir)/include") + LIB := $(shell IFS=$$'\n'; cygpath -w "$(VcCompilerToolsDir)/lib/$(TargetArchMoniker)") + ifneq (,$(findstring uwp,$(PlatformSuffix))) + LIB := $(LIB);$(shell IFS=$$'\n'; cygpath -w "$(LIB)/store") + endif + + export INCLUDE := $(INCLUDE);$(WindowsSDKSharedIncludeDir);$(WindowsSDKUCRTIncludeDir);$(WindowsSDKUMIncludeDir) + export LIB := $(LIB);$(WindowsSDKUCRTLibDir);$(WindowsSDKUMLibDir) + TARGET := $(TARGET_NAME)_libretro.dll + PSS_STYLE :=2 + LDFLAGS += -DLL + else CC = gcc TARGET := $(TARGET_NAME)_libretro.dll @@ -157,6 +262,22 @@ include Makefile.common OBJECTS := $(patsubst %.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) +OBJOUT = -o +LINKOUT = -o + +ifneq (,$(findstring msvc,$(platform))) + OBJOUT = -Fo + LINKOUT = -out: +ifeq ($(STATIC_LINKING),1) + LD ?= lib.exe + STATIC_LINKING=0 +else + LD = link.exe +endif +else + LD = $(CC) +endif + CFLAGS += -Wall -D__LIBRETRO__ $(fpic) $(INCFLAGS) -std=gnu11 -D_GNU_SOURCE -D_USE_MATH_DEFINES all: $(TARGET) @@ -182,15 +303,15 @@ $(TARGET): $(OBJECTS) ifeq ($(STATIC_LINKING), 1) $(AR) rcs $@ $(OBJECTS) else - $(CC) $(fpic) $(SHARED) $(INCFLAGS) -o $@ $(OBJECTS) $(LDFLAGS) + $(LD) $(fpic) $(SHARED) $(INCFLAGS) $(LINKOUT)$@ $(OBJECTS) $(LDFLAGS) endif $(CORE_DIR)/build/obj/%_libretro.c.o: %.c -@$(MKDIR) -p $(dir $@) - $(CC) -c -o $@ $< $(CFLAGS) $(fpic) -DGB_INTERNAL + $(CC) -c $(OBJOUT)$@ $< $(CFLAGS) $(fpic) -DGB_INTERNAL %.o: %.c - $(CC) $(CFLAGS) $(fpic) -c -o $@ $< + $(CC) $(CFLAGS) $(fpic) -c $(OBJOUT)$@ $< clean: rm -f $(OBJECTS) $(TARGET) From ca8426ea605b90738c1a9c3ce60f225d64450b09 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 5 Jan 2019 20:58:23 -0500 Subject: [PATCH 0807/1216] update libretro core --- libretro/Makefile.common | 3 +++ libretro/libretro.c | 32 +++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/libretro/Makefile.common b/libretro/Makefile.common index dfedccfb..fb3a02f9 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -1,6 +1,7 @@ INCFLAGS := -I$(CORE_DIR) SOURCES_C := $(CORE_DIR)/Core/gb.c \ + $(CORE_DIR)/Core/sgb.c \ $(CORE_DIR)/Core/apu.c \ $(CORE_DIR)/Core/memory.c \ $(CORE_DIR)/Core/mbc.c \ @@ -14,6 +15,8 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/libretro/agb_boot.c \ $(CORE_DIR)/libretro/cgb_boot.c \ $(CORE_DIR)/libretro/dmg_boot.c \ + $(CORE_DIR)/libretro/sgb_boot.c \ + $(CORE_DIR)/libretro/sgb2_boot.c \ $(CORE_DIR)/libretro/libretro.c CFLAGS += -DDISABLE_TIMEKEEPING -DDISABLE_REWIND -DDISABLE_DEBUGGER diff --git a/libretro/libretro.c b/libretro/libretro.c index 7d50c52a..b3eb14e6 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -49,6 +49,8 @@ enum model { MODEL_DMG, MODEL_CGB, MODEL_AGB, + MODEL_SGB, + MODEL_SGB2, MODEL_AUTO }; @@ -56,7 +58,9 @@ static const GB_model_t libretro_to_internal_model[] = { [MODEL_DMG] = GB_MODEL_DMG_B, [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB + [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_SGB] = GB_MODEL_SGB, + [MODEL_SGB2] = GB_MODEL_SGB2 }; enum screen_layout { @@ -109,8 +113,8 @@ char retro_game_path[4096]; GB_gameboy_t gameboy[2]; -extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[]; -extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length; +extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[], sgb_boot[], sgb2_boot[]; +extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length, sgb_boot_length, sgb2_boot_length; bool vblank1_occurred = false, vblank2_occurred = false; static void fallback_log(enum retro_log_level level, const char *fmt, ...) @@ -233,7 +237,7 @@ static const struct retro_variable vars_single[] = { { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated model; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model", "Emulated model; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy|Super Game Boy 2" }, { NULL } }; @@ -244,8 +248,8 @@ static const struct retro_variable vars_single_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy|Super Game Boy 2" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy|Super Game Boy 2" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -259,8 +263,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy 2" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy 2" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -327,9 +331,9 @@ static void init_for_current_model(unsigned id) else { GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); } - const char *model_name = (const char *[]){"dmg", "cgb", "agb"}[effective_model]; - const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot}[effective_model]; - unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length}[effective_model]; + const char *model_name = (const char *[]){"dmg", "cgb", "agb", "sgb", "sgb2"}[effective_model]; + const unsigned char *boot_code = (const unsigned char *[]){dmg_boot, cgb_boot, agb_boot, sgb_boot, sgb2_boot}[effective_model]; + unsigned boot_length = (unsigned []){dmg_boot_length, cgb_boot_length, agb_boot_length, sgb_boot_length, sgb2_boot_length}[effective_model]; char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); @@ -441,6 +445,8 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; + else if (strcmp(var.value, "Super Game Boy") == 0) + new_model = MODEL_SGB; else new_model = MODEL_AUTO; @@ -516,6 +522,8 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; + else if (strcmp(var.value, "Super Game Boy") == 0) + new_model = MODEL_SGB; else new_model = MODEL_AUTO; @@ -537,6 +545,8 @@ static void check_variables(bool link) new_model = MODEL_CGB; else if (strcmp(var.value, "Game Boy Advance") == 0) new_model = MODEL_AGB; + else if (strcmp(var.value, "Super Game Boy") == 0) + new_model = MODEL_SGB; else new_model = MODEL_AUTO; From c266e4045b7dd8160ff19549a4cbee7cefc20e70 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 5 Jan 2019 21:25:20 -0500 Subject: [PATCH 0808/1216] try to hookup sgb2 --- libretro/libretro.c | 110 ++++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index b3eb14e6..29e60b77 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -35,6 +35,10 @@ static const char slash = '/'; #define VIDEO_HEIGHT 144 #define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT) +#define SGB_VIDEO_WIDTH 256 +#define SGB_VIDEO_HEIGHT 224 +#define SGB_VIDEO_PIXELS (SGB_VIDEO_WIDTH * SGB_VIDEO_HEIGHT) + #define RETRO_MEMORY_GAMEBOY_1_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) #define RETRO_MEMORY_GAMEBOY_1_RTC ((2 << 8) | RETRO_MEMORY_RTC) #define RETRO_MEMORY_GAMEBOY_2_SRAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) @@ -248,8 +252,8 @@ static const struct retro_variable vars_single_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy|Super Game Boy 2" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy|Super Game Boy 2" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -263,8 +267,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy 2" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy 2" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -342,7 +346,8 @@ static void init_for_current_model(unsigned id) if (GB_load_boot_rom(&gameboy[i], buf)) GB_load_boot_rom_from_buffer(&gameboy[i], boot_code, boot_length); GB_set_user_data(&gameboy[i], (void*)NULL); - GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * VIDEO_PIXELS)); + + GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * ((model[i] == MODEL_SGB || model[i] == MODEL_SGB2) ? SGB_VIDEO_PIXELS : VIDEO_PIXELS))); GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); @@ -447,11 +452,14 @@ static void check_variables(bool link) new_model = MODEL_AGB; else if (strcmp(var.value, "Super Game Boy") == 0) new_model = MODEL_SGB; + else if (strcmp(var.value, "Super Game Boy 2") == 0) + new_model = MODEL_SGB2; else new_model = MODEL_AUTO; if (new_model != model[0]) { + geometry_updated = true; model[0] = new_model; init_for_current_model(0); } @@ -524,6 +532,8 @@ static void check_variables(bool link) new_model = MODEL_AGB; else if (strcmp(var.value, "Super Game Boy") == 0) new_model = MODEL_SGB; + else if (strcmp(var.value, "Super Game Boy 2") == 0) + new_model = MODEL_SGB2; else new_model = MODEL_AUTO; @@ -547,6 +557,8 @@ static void check_variables(bool link) new_model = MODEL_AGB; else if (strcmp(var.value, "Super Game Boy") == 0) new_model = MODEL_SGB; + else if (strcmp(var.value, "Super Game Boy 2") == 0) + new_model = MODEL_SGB; else new_model = MODEL_AUTO; @@ -689,23 +701,42 @@ void retro_get_system_av_info(struct retro_system_av_info *info) struct retro_game_geometry geom; struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; - if (screen_layout == LAYOUT_TOP_DOWN) { - geom.base_width = VIDEO_WIDTH; - geom.base_height = VIDEO_HEIGHT * emulated_devices; - geom.aspect_ratio = (double)VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); - }else if (screen_layout == LAYOUT_LEFT_RIGHT) { - geom.base_width = VIDEO_WIDTH * emulated_devices; - geom.base_height = VIDEO_HEIGHT; - geom.aspect_ratio = ((double)VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; + if (sameboy_dual) + { + if (screen_layout == LAYOUT_TOP_DOWN) { + geom.base_width = VIDEO_WIDTH; + geom.base_height = VIDEO_HEIGHT * emulated_devices; + geom.aspect_ratio = (double)VIDEO_WIDTH / (emulated_devices * VIDEO_HEIGHT); + }else if (screen_layout == LAYOUT_LEFT_RIGHT) { + geom.base_width = VIDEO_WIDTH * emulated_devices; + geom.base_height = VIDEO_HEIGHT; + geom.aspect_ratio = ((double)VIDEO_WIDTH * emulated_devices) / VIDEO_HEIGHT; + } + } + else + { + if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) + { + geom.base_width = SGB_VIDEO_WIDTH; + geom.base_height = SGB_VIDEO_HEIGHT; + geom.aspect_ratio = (double)SGB_VIDEO_WIDTH / SGB_VIDEO_HEIGHT; + } + else + { + geom.base_width = VIDEO_WIDTH; + geom.base_height = VIDEO_HEIGHT; + geom.aspect_ratio = (double)VIDEO_WIDTH / VIDEO_HEIGHT; + } } - geom.max_width = VIDEO_WIDTH * emulated_devices; - geom.max_height = VIDEO_HEIGHT * emulated_devices; + geom.max_width = SGB_VIDEO_WIDTH * emulated_devices; + geom.max_height = SGB_VIDEO_HEIGHT * emulated_devices; info->geometry = geom; info->timing = timing; } + void retro_set_environment(retro_environment_t cb) { environ_cb = cb; @@ -745,7 +776,7 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { for (int i = 0; i < emulated_devices; i++) - GB_reset(&gameboy[i]); + GB_reset(&gameboy[i]); } @@ -760,8 +791,8 @@ void retro_run(void) if (geometry_updated) { struct retro_system_av_info info; retro_get_system_av_info(&info); - geometry_updated = false; environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &info.geometry); + geometry_updated = false; } if (!frame_buf) @@ -772,7 +803,7 @@ void retro_run(void) GB_update_keys_status(&gameboy[0], 0); if (emulated_devices == 2) - GB_update_keys_status(&gameboy[1], 1); + GB_update_keys_status(&gameboy[1], 1); vblank1_occurred = vblank2_occurred = false; signed delta = 0; @@ -788,22 +819,36 @@ void retro_run(void) } } else - GB_run_frame(&gameboy[0]); + { + int x = GB_run_frame(&gameboy[0]); + log_cb(RETRO_LOG_DEBUG, "%d\n", x); + } - if (screen_layout == LAYOUT_TOP_DOWN) { - video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); - }else if (screen_layout == LAYOUT_LEFT_RIGHT) { - /* use slow memcpy method for now */ - for (int index = 0; index < emulated_devices; index++) { - for (int y = 0; y < VIDEO_HEIGHT; y++) { - for (int x = 0; x < VIDEO_WIDTH; x++) { - frame_buf_copy[VIDEO_WIDTH * emulated_devices * y + (x + VIDEO_WIDTH * index)] = frame_buf[VIDEO_WIDTH * (y + VIDEO_HEIGHT * index) + x]; + if (sameboy_dual) + { + if (screen_layout == LAYOUT_TOP_DOWN) { + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); + }else if (screen_layout == LAYOUT_LEFT_RIGHT) { + /* use slow memcpy method for now */ + for (int index = 0; index < emulated_devices; index++) { + for (int y = 0; y < VIDEO_HEIGHT; y++) { + for (int x = 0; x < VIDEO_WIDTH; x++) { + frame_buf_copy[VIDEO_WIDTH * emulated_devices * y + (x + VIDEO_WIDTH * index)] = frame_buf[VIDEO_WIDTH * (y + VIDEO_HEIGHT * index) + x]; + } } } - } - video_cb(frame_buf_copy, VIDEO_WIDTH * emulated_devices, VIDEO_HEIGHT, VIDEO_WIDTH * emulated_devices * sizeof(uint32_t)); + video_cb(frame_buf_copy, VIDEO_WIDTH * emulated_devices, VIDEO_HEIGHT, VIDEO_WIDTH * emulated_devices * sizeof(uint32_t)); + } } + else + { + if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) + video_cb(frame_buf, SGB_VIDEO_WIDTH, SGB_VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); + else + video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); + } + initialized = true; } @@ -823,11 +868,8 @@ bool retro_load_game(const struct retro_game_info *info) else mode = MODE_SINGLE_GAME; - frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - - memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf = (uint32_t*)malloc(SGB_VIDEO_PIXELS* emulated_devices * sizeof(uint32_t)); + memset(frame_buf, 0, SGB_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) From e2d9a353a02c0a9c0c20600868dc6ac89a8a9df2 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 6 Jan 2019 17:09:37 -0500 Subject: [PATCH 0809/1216] remove single_dual mode, code cleanup --- libretro/libretro.c | 428 ++++++++++++++++---------------------------- 1 file changed, 158 insertions(+), 270 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 29e60b77..59f4f9f8 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -77,12 +77,6 @@ enum audio_out { GB_2 }; -enum mode{ - MODE_SINGLE_GAME, - MODE_SINGLE_GAME_DUAL, - MODE_DUAL_GAME -}; - static enum model model[2]; static enum model auto_model = MODEL_CGB; @@ -101,11 +95,7 @@ static bool initialized = false; static unsigned screen_layout = 0; static unsigned audio_out = 0; -static enum mode mode = MODE_SINGLE_GAME; - static bool geometry_updated = false; -static bool sameboy_dual = false; - static bool link_cable_emulation = false; /*static bool infrared_emulation = false;*/ @@ -132,28 +122,8 @@ static void fallback_log(enum retro_log_level level, const char *fmt, ...) static struct retro_rumble_interface rumble; -static void extract_basename(char *buf, const char *path, size_t size) -{ - const char *base = strrchr(path, '/'); - if (!base) - base = strrchr(path, '\\'); - if (!base) - base = path; - - if (*base == '\\' || *base == '/') - base++; - - strncpy(buf, base, size - 1); - buf[size - 1] = '\0'; - - char *ext = strrchr(buf, '.'); - if (ext) - *ext = '\0'; -} - static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { - input_poll_cb(); GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); @@ -169,7 +139,6 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535); else rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0); - } @@ -238,26 +207,9 @@ static retro_environment_t environ_cb; /* variables for single cart mode */ static const struct retro_variable vars_single[] = { - { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, - { "sameboy_model", "Emulated model; Game Boy Color|Game Boy Advance|Auto|Game Boy|Super Game Boy|Super Game Boy 2" }, - { NULL } -}; - -/* variables for single cart dual gameboy mode */ -static const struct retro_variable vars_single_dual[] = { - { "sameboy_dual", "Single cart dual mode (reload); disabled|enabled" }, - { "sameboy_link", "Link cable emulation; enabled|disabled" }, - /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ - { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, - { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, - { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, - { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; off|accurate|remove dc offset" }, + { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { NULL } }; @@ -267,8 +219,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Game Boy Color|Game Boy Advance|Auto|Game Boy" }, + { "sameboy_model_1", "Emulated model for Game Boy #1; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_model_2", "Emulated model for Game Boy #2; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; off|accurate|remove dc offset" }, @@ -291,9 +243,9 @@ static const struct retro_subsystem_rom_info gb_roms[] = { { "GameBoy #2", "gb|gbc", true, false, true, gb2_memory, 1 }, }; - static const struct retro_subsystem_info subsystems[] = { - { "2 Player Game Boy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P }, - { NULL }, +static const struct retro_subsystem_info subsystems[] = { + { "2 Player Game Boy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P }, + { NULL }, }; static const struct retro_controller_description controllers[] = { @@ -397,7 +349,7 @@ static void init_for_current_model(unsigned id) descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); descs[6].start = 0xFE00; descs[6].len = 0x00A0; - + descs[7].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank) + 0x1000; descs[7].start = 0xD000; descs[7].len = 0x1000; @@ -408,10 +360,10 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); } -static void check_variables(bool link) +static void check_variables() { struct retro_variable var = {0}; - if (!link) + if (emulated_devices == 1) { var.key = "sameboy_color_correction_mode"; var.value = NULL; @@ -569,53 +521,42 @@ static void check_variables(bool link) } } - } + var.key = "sameboy_screen_layout"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "top-down") == 0) + screen_layout = LAYOUT_TOP_DOWN; + else + screen_layout = LAYOUT_LEFT_RIGHT; - var.key = "sameboy_screen_layout"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - if (strcmp(var.value, "top-down") == 0) - screen_layout = LAYOUT_TOP_DOWN; - else - screen_layout = LAYOUT_LEFT_RIGHT; + geometry_updated = true; + } - geometry_updated = true; - } + var.key = "sameboy_link"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + bool tmp = link_cable_emulation; + if (strcmp(var.value, "enabled") == 0) + link_cable_emulation = true; + else + link_cable_emulation = false; + if (link_cable_emulation && link_cable_emulation != tmp) + set_link_cable_state(true); + else if (!link_cable_emulation && link_cable_emulation != tmp) + set_link_cable_state(false); + } - var.key = "sameboy_dual"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - if (strcmp(var.value, "enabled") == 0) - sameboy_dual = true; - else - sameboy_dual = false; - } - - var.key = "sameboy_link"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - bool tmp = link_cable_emulation; - if (strcmp(var.value, "enabled") == 0) - link_cable_emulation = true; - else - link_cable_emulation = false; - if (link_cable_emulation && link_cable_emulation != tmp) - set_link_cable_state(true); - else if (!link_cable_emulation && link_cable_emulation != tmp) - set_link_cable_state(false); - } - - var.key = "sameboy_audio_output"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - if (strcmp(var.value, "Game Boy #1") == 0) - audio_out = GB_1; - else - audio_out = GB_2; + var.key = "sameboy_audio_output"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "Game Boy #1") == 0) + audio_out = GB_1; + else + audio_out = GB_2; + } } } @@ -701,7 +642,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info) struct retro_game_geometry geom; struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; - if (sameboy_dual) + if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { geom.base_width = VIDEO_WIDTH; @@ -799,7 +740,7 @@ void retro_run(void) return; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(emulated_devices == 2 ? true : false); + check_variables(); GB_update_keys_status(&gameboy[0], 0); if (emulated_devices == 2) @@ -824,7 +765,7 @@ void retro_run(void) log_cb(RETRO_LOG_DEBUG, "%d\n", x); } - if (sameboy_dual) + if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT * emulated_devices, VIDEO_WIDTH * sizeof(uint32_t)); @@ -856,17 +797,7 @@ void retro_run(void) bool retro_load_game(const struct retro_game_info *info) { environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); - check_variables(false); - - if (sameboy_dual) - { - emulated_devices = 2; - mode = MODE_SINGLE_GAME_DUAL; - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single_dual); - check_variables(true); - } - else - mode = MODE_SINGLE_GAME; + check_variables(); frame_buf = (uint32_t*)malloc(SGB_VIDEO_PIXELS* emulated_devices * sizeof(uint32_t)); memset(frame_buf, 0, SGB_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); @@ -899,37 +830,12 @@ bool retro_load_game(const struct retro_game_info *info) else log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); - check_variables(emulated_devices == 2 ? true : false); - - /* hack: use upstream's file based I/O for Game Boy 2 battery in single cart mode */ - if (mode == MODE_SINGLE_GAME_DUAL) - { - char path[PATH_MAX]; - char file[PATH_MAX]; - - extract_basename(file, retro_game_path, sizeof(file)); - snprintf (path, sizeof(path), "%s%c%s.srm.2", retro_save_directory, slash, file); - log_cb(RETRO_LOG_INFO, "Loading battery for Game Boy 2 from: %s\n", path); - GB_load_battery(&gameboy[1], path); - } - + check_variables(); return true; } void retro_unload_game(void) { - /* hack: use upstream's file based I/O for Game Boy 2 battery in single cart mode */ - if (mode == MODE_SINGLE_GAME_DUAL) - { - char path[PATH_MAX]; - char file[PATH_MAX]; - - extract_basename(file, retro_game_path, sizeof(file)); - snprintf (path, sizeof(path), "%s%c%s.srm.2", retro_save_directory, slash, file); - log_cb(RETRO_LOG_INFO, "Saving battery for Game Boy 2 to: %s\n", path); - GB_save_battery(&gameboy[1], path); - } - for (int i = 0; i < emulated_devices; i++) GB_free(&gameboy[i]); } @@ -943,15 +849,12 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, { if (type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) - { emulated_devices = 2; - mode = MODE_DUAL_GAME; - } else return false; /* all other types are unhandled for now */ environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_dual); - check_variables(true); + check_variables(); frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); @@ -987,7 +890,7 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, else log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); - check_variables(emulated_devices == 2 ? true : false); + check_variables(); return true; } @@ -1050,139 +953,124 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { void *data = NULL; - switch(mode) + if (emulated_devices == 1) { - case MODE_SINGLE_GAME: - case MODE_SINGLE_GAME_DUAL: /* todo: hook this properly */ - { - switch(type) - { - case RETRO_MEMORY_SYSTEM_RAM: - data = gameboy[0].ram; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) - data = gameboy[0].mbc_ram; - else - data = NULL; - break; - case RETRO_MEMORY_VIDEO_RAM: - data = gameboy[0].vram; - break; - case RETRO_MEMORY_RTC: - if(gameboy[0].cartridge_type->has_battery) - data = GB_GET_SECTION(&gameboy[0], rtc); - else - data = NULL; - break; - default: - break; - } - } - break; - case MODE_DUAL_GAME: - { - switch (type) - { - case RETRO_MEMORY_GAMEBOY_1_SRAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) - data = gameboy[0].mbc_ram; - else - data = NULL; - break; - case RETRO_MEMORY_GAMEBOY_2_SRAM: - if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) - data = gameboy[1].mbc_ram; - else - data = NULL; - break; - case RETRO_MEMORY_GAMEBOY_1_RTC: - if(gameboy[0].cartridge_type->has_battery) - data = GB_GET_SECTION(&gameboy[0], rtc); - else - data = NULL; - break; - case RETRO_MEMORY_GAMEBOY_2_RTC: - if(gameboy[1].cartridge_type->has_battery) - data = GB_GET_SECTION(&gameboy[1], rtc); - else - data = NULL; - break; - default: - break; - } - } - break; - default: - break; + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + data = gameboy[0].ram; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + data = gameboy[0].mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_VIDEO_RAM: + data = gameboy[0].vram; + break; + case RETRO_MEMORY_RTC: + if(gameboy[0].cartridge_type->has_battery) + data = GB_GET_SECTION(&gameboy[0], rtc); + else + data = NULL; + break; + default: + break; + } } + else + { + switch (type) + { + case RETRO_MEMORY_GAMEBOY_1_SRAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + data = gameboy[0].mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_GAMEBOY_2_SRAM: + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) + data = gameboy[1].mbc_ram; + else + data = NULL; + break; + case RETRO_MEMORY_GAMEBOY_1_RTC: + if(gameboy[0].cartridge_type->has_battery) + data = GB_GET_SECTION(&gameboy[0], rtc); + else + data = NULL; + break; + case RETRO_MEMORY_GAMEBOY_2_RTC: + if(gameboy[1].cartridge_type->has_battery) + data = GB_GET_SECTION(&gameboy[1], rtc); + else + data = NULL; + break; + default: + break; + } + } + return data; } size_t retro_get_memory_size(unsigned type) { size_t size = 0; - switch(mode) + if (emulated_devices == 1) { - case MODE_SINGLE_GAME: - case MODE_SINGLE_GAME_DUAL: /* todo: hook this properly */ - { - switch(type) - { - case RETRO_MEMORY_SYSTEM_RAM: - size = gameboy[0].ram_size; - break; - case RETRO_MEMORY_SAVE_RAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) - size = gameboy[0].mbc_ram_size; - else - size = 0; - break; - case RETRO_MEMORY_VIDEO_RAM: - size = gameboy[0].vram_size; - break; - case RETRO_MEMORY_RTC: - if(gameboy[0].cartridge_type->has_battery) - size = GB_SECTION_SIZE(rtc); - else - size = 0; - break; - default: - break; - } - } - break; - case MODE_DUAL_GAME: - { - switch (type) - { - case RETRO_MEMORY_GAMEBOY_1_SRAM: - if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) - size = gameboy[0].mbc_ram_size; - else - size = 0; - break; - case RETRO_MEMORY_GAMEBOY_2_SRAM: - if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) - size = gameboy[1].mbc_ram_size; - else - size = 0; - break; - case RETRO_MEMORY_GAMEBOY_1_RTC: - if(gameboy[0].cartridge_type->has_battery) - size = GB_SECTION_SIZE(rtc); - break; - case RETRO_MEMORY_GAMEBOY_2_RTC: - if(gameboy[1].cartridge_type->has_battery) - size = GB_SECTION_SIZE(rtc); - break; - default: - break; - } - } - break; - default: - break; + switch(type) + { + case RETRO_MEMORY_SYSTEM_RAM: + size = gameboy[0].ram_size; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + size = gameboy[0].mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_VIDEO_RAM: + size = gameboy[0].vram_size; + break; + case RETRO_MEMORY_RTC: + if(gameboy[0].cartridge_type->has_battery) + size = GB_SECTION_SIZE(rtc); + else + size = 0; + break; + default: + break; + } + } + else + { + switch (type) + { + case RETRO_MEMORY_GAMEBOY_1_SRAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) + size = gameboy[0].mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_GAMEBOY_2_SRAM: + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) + size = gameboy[1].mbc_ram_size; + else + size = 0; + break; + case RETRO_MEMORY_GAMEBOY_1_RTC: + if(gameboy[0].cartridge_type->has_battery) + size = GB_SECTION_SIZE(rtc); + break; + case RETRO_MEMORY_GAMEBOY_2_RTC: + if(gameboy[1].cartridge_type->has_battery) + size = GB_SECTION_SIZE(rtc); + break; + default: + break; + } } return size; From 4536581a6ee357e6f97db068fb2a0a02e39a58a4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 18 Jan 2019 02:36:14 +0200 Subject: [PATCH 0810/1216] Fixed a bug where modifying RTC data would corrupt cartridge RAM data. Fixes #136 --- Core/memory.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/memory.c b/Core/memory.c index a3010b06..39f358eb 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -520,6 +520,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; + return; } if (!gb->mbc_ram) { From 10547a6d74b943520d3dcb0062c3b436b6dbb3d6 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 17 Jan 2019 19:44:43 -0500 Subject: [PATCH 0811/1216] hookup up 2 player SGB --- libretro/libretro.c | 128 ++++++++++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 39 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 59f4f9f8..5c4aa21b 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -126,14 +126,22 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { input_poll_cb(); - GB_set_key_state(gb, GB_KEY_RIGHT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); - GB_set_key_state(gb, GB_KEY_LEFT, input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); - GB_set_key_state(gb, GB_KEY_UP,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) ); - GB_set_key_state(gb, GB_KEY_DOWN,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); - GB_set_key_state(gb, GB_KEY_A,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) ); - GB_set_key_state(gb, GB_KEY_B,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ); - GB_set_key_state(gb, GB_KEY_SELECT,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); - GB_set_key_state(gb, GB_KEY_START,input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) ); + GB_set_key_state_for_player(gb, GB_KEY_RIGHT, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); + GB_set_key_state_for_player(gb, GB_KEY_LEFT, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); + GB_set_key_state_for_player(gb, GB_KEY_UP, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)); + GB_set_key_state_for_player(gb, GB_KEY_DOWN, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); + GB_set_key_state_for_player(gb, GB_KEY_A, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)); + GB_set_key_state_for_player(gb, GB_KEY_B, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)); + GB_set_key_state_for_player(gb, GB_KEY_SELECT, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); + GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0, + input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)); if (gb->rumble_state) rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535); @@ -252,6 +260,42 @@ static const struct retro_controller_description controllers[] = { { "Nintendo Game Boy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, }; +static const struct retro_controller_description controllers_sgb[] = { + { "SNES/SFC Gamepad", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, +}; + +static struct retro_input_descriptor descriptors_1p[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, +}; + +static struct retro_input_descriptor descriptors_2p[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, +}; + static void set_link_cable_state(bool state) { if (state && emulated_devices == 2) @@ -358,6 +402,41 @@ static void init_for_current_model(unsigned id) mmaps.descriptors = descs; mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); + + /* Let's be extremely nitpicky about how devices and descriptors are set */ + if (emulated_devices == 1) + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_1p); + else + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_2p); + + + if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) + { + static const struct retro_controller_info ports[] = { + { controllers_sgb, 1 }, + { controllers_sgb, 1 }, + { NULL, 0 }, + }; + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + } + else if (emulated_devices == 1) + { + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { NULL, 0 }, + }; + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + } + else + { + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { controllers, 1 }, + { NULL, 0 }, + }; + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + } + } static void check_variables() @@ -573,37 +652,6 @@ void retro_init(void) snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); else snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); - - static const struct retro_controller_info ports[] = { - { controllers, 1 }, - { controllers, 1 }, - { NULL, 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); - - struct retro_input_descriptor desc[] = { - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, - { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, - { 0 }, - }; - - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); - } void retro_deinit(void) @@ -745,6 +793,8 @@ void retro_run(void) GB_update_keys_status(&gameboy[0], 0); if (emulated_devices == 2) GB_update_keys_status(&gameboy[1], 1); + else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) + GB_update_keys_status(&gameboy[0], 1); vblank1_occurred = vblank2_occurred = false; signed delta = 0; From 68a72037fa1b84d14d81ddc52561a0f6453641a8 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 17 Jan 2019 20:33:20 -0500 Subject: [PATCH 0812/1216] add 4p support too --- libretro/libretro.c | 56 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 5c4aa21b..3dc33a5f 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -296,6 +296,43 @@ static struct retro_input_descriptor descriptors_2p[] = { { 0 }, }; +static struct retro_input_descriptor descriptors_4p[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, +}; + + static void set_link_cable_state(bool state) { if (state && emulated_devices == 2) @@ -404,20 +441,17 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); /* Let's be extremely nitpicky about how devices and descriptors are set */ - if (emulated_devices == 1) - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_1p); - else - environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_2p); - - if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { static const struct retro_controller_info ports[] = { + { controllers_sgb, 1 }, + { controllers_sgb, 1 }, { controllers_sgb, 1 }, { controllers_sgb, 1 }, { NULL, 0 }, }; environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_4p); } else if (emulated_devices == 1) { @@ -426,6 +460,7 @@ static void init_for_current_model(unsigned id) { NULL, 0 }, }; environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_1p); } else { @@ -435,6 +470,7 @@ static void init_for_current_model(unsigned id) { NULL, 0 }, }; environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_2p); } } @@ -790,11 +826,15 @@ void retro_run(void) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(); - GB_update_keys_status(&gameboy[0], 0); if (emulated_devices == 2) GB_update_keys_status(&gameboy[1], 1); else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) - GB_update_keys_status(&gameboy[0], 1); + { + for (unsigned i = 0; i < 4; i++) + GB_update_keys_status(&gameboy[0], i); + } + else + GB_update_keys_status(&gameboy[0], 0); vblank1_occurred = vblank2_occurred = false; signed delta = 0; From 73a54049d2c81a3bf6c87b3b70462c79c0e15942 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 19 Jan 2019 19:32:26 +0200 Subject: [PATCH 0813/1216] Accurate PPU access timings --- Core/debugger.c | 2 +- Core/display.c | 59 ++++++++++++++++++++++++++++++++++++++++--------- Core/gb.h | 1 + Core/memory.c | 8 +++---- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index fd292b78..b8092626 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1524,7 +1524,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg else if (gb->display_state == 7 || gb->display_state == 8) { GB_log(gb, "Reading OAM data (%d/40)\n", gb->display_state == 8? gb->oam_search_index : 0); } - else if (gb->display_state <= 3 || gb->display_state == 24) { + else if (gb->display_state <= 3 || gb->display_state == 24 || gb->display_state == 31) { GB_log(gb, "Glitched line 0 (%d cycles to next event)\n", -gb->display_cycles / 2); } else if (gb->mode_for_interrupt == 3) { diff --git a/Core/display.c b/Core/display.c index 6141e534..05a45b70 100644 --- a/Core/display.c +++ b/Core/display.c @@ -304,6 +304,7 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; /* Reset window rendering state */ gb->wy_diff = 0; @@ -586,6 +587,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 28); GB_STATE(gb, display, 29); GB_STATE(gb, display, 30); + GB_STATE(gb, display, 31); + GB_STATE(gb, display, 32); + GB_STATE(gb, display, 33); + GB_STATE(gb, display, 34); + GB_STATE(gb, display, 35); + GB_STATE(gb, display, 36); + } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -610,23 +618,30 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = false; gb->oam_write_blocked = false; gb->vram_write_blocked = false; - gb->cycles_for_line = MODE2_LENGTH - 2; + gb->cgb_palettes_blocked = false; + gb->cycles_for_line = MODE2_LENGTH - 4; GB_STAT_update(gb); - GB_SLEEP(gb, display, 2, MODE2_LENGTH - 2); + GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4); + + gb->oam_write_blocked = true; + gb->cycles_for_line += 2; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 34, 2); gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; gb->mode_for_interrupt = 3; gb->oam_read_blocked = true; - gb->vram_read_blocked = !GB_is_cgb(gb); - gb->oam_write_blocked = true; - gb->vram_write_blocked = !GB_is_cgb(gb); + gb->vram_read_blocked = !GB_is_cgb(gb) || gb->cgb_double_speed; + gb->vram_write_blocked = !GB_is_cgb(gb) || gb->cgb_double_speed; + gb->cgb_palettes_blocked = !GB_is_cgb(gb); GB_STAT_update(gb); gb->cycles_for_line += 2; GB_SLEEP(gb, display, 24, 2); gb->vram_read_blocked = true; gb->vram_write_blocked = true; + gb->cgb_palettes_blocked = true; /* TODO: How does the window affect this line? */ gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2; @@ -650,6 +665,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->oam_write_blocked = false; gb->vram_write_blocked = false; + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 31, 2); + gb->cgb_palettes_blocked = false; + GB_STAT_update(gb); /* Mode 0 is shorter in the very first line */ GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); @@ -659,9 +678,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { - gb->oam_write_blocked = GB_is_cgb(gb); + gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed; gb->accessed_oam_row = 0; - GB_SLEEP(gb, display, 6, 3); + + GB_SLEEP(gb, display, 35, 2); + gb->oam_write_blocked = GB_is_cgb(gb); + + GB_SLEEP(gb, display, 6, 1); gb->io_registers[GB_IO_LY] = gb->current_line; gb->oam_read_blocked = true; gb->ly_for_comparison = gb->current_line? -1 : 0; @@ -702,6 +725,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->oam_search_index == 37) { gb->vram_read_blocked = !GB_is_cgb(gb); gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; gb->oam_write_blocked = GB_is_cgb(gb); GB_STAT_update(gb); } @@ -712,6 +736,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->mode_for_interrupt = 3; gb->vram_read_blocked = true; gb->vram_write_blocked = true; + gb->cgb_palettes_blocked = false; gb->oam_write_blocked = true; GB_STAT_update(gb); @@ -725,13 +750,18 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); - uint8_t idle_cycles = 5; + uint8_t idle_cycles = 3; if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { - idle_cycles = 4; + idle_cycles = 2; } gb->cycles_for_line += idle_cycles; GB_SLEEP(gb, display, 10, idle_cycles); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 32, 2); + /* The actual rendering cycle */ gb->fetcher_state = 0; gb->bg_fifo_paused = false; @@ -848,9 +878,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); /* Todo: Measure this value */ + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 33, 2); + gb->cgb_palettes_blocked = !gb->cgb_double_speed; - gb->cycles_for_line += 12; - GB_SLEEP(gb, display, 25, 12); + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 36, 2); + gb->cgb_palettes_blocked = false; + + gb->cycles_for_line += 8; + GB_SLEEP(gb, display, 25, 8); if (gb->hdma_on_hblank) { gb->hdma_starting = true; diff --git a/Core/gb.h b/Core/gb.h index e37a1592..9dea4ef3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -465,6 +465,7 @@ struct GB_gameboy_internal_s { uint8_t extra_penalty_for_sprite_at_0; uint8_t mode_for_interrupt; bool lyc_interrupt_line; + bool cgb_palettes_blocked; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index 39f358eb..262fc4d1 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -197,7 +197,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF00) { - if (gb->oam_write_blocked) { + if (gb->oam_write_blocked && !GB_is_cgb(gb)) { GB_trigger_oam_bug_read(gb, addr); return 0xff; } @@ -357,8 +357,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (!gb->cgb_mode && gb->boot_rom_finished) { return 0xFF; } - /* TODO: Verify actual access timing */ - if (gb->vram_read_blocked) { + if (gb->cgb_palettes_blocked) { return 0xFF; } uint8_t index_reg = (addr & 0xFF) - 1; @@ -793,8 +792,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) is required. */ return; } - /* TODO: Verify actual access timing */ - if (gb->vram_write_blocked) { + if (gb->cgb_palettes_blocked) { return; } uint8_t index_reg = (addr & 0xFF) - 1; From b996ed922052a06e47d844e914652d3469ef6bc7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 19 Jan 2019 23:37:44 +0200 Subject: [PATCH 0814/1216] =?UTF-8?q?Writing=20to=20BGPD/OBPD=20while=20it?= =?UTF-8?q?=E2=80=99s=20blocked=20still=20increases=20BGPI/OBPI=20if=20nee?= =?UTF-8?q?ded.=20Fixes=20#145?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/memory.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 262fc4d1..bf01d202 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -792,10 +792,15 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) is required. */ return; } + + uint8_t index_reg = (addr & 0xFF) - 1; if (gb->cgb_palettes_blocked) { + if (gb->io_registers[index_reg] & 0x80) { + gb->io_registers[index_reg]++; + gb->io_registers[index_reg] |= 0x80; + } return; } - uint8_t index_reg = (addr & 0xFF) - 1; ((addr & 0xFF) == GB_IO_BGPD? gb->background_palettes_data : gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value; From dde06e7cae094859d7e0501bb824fe1e04379b61 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 28 Jan 2019 20:56:37 +0200 Subject: [PATCH 0815/1216] Work around a crashing race condition, proper fix needed --- Core/apu.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 6ea59955..1b3dd2bd 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -472,33 +472,37 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) } gb->apu_output.copy_in_progress = true; + /* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */ + size_t buffer_position = gb->apu_output.buffer_position; + if (!gb->apu_output.stream_started) { // Intentionally fail the first copy to sync the stream with the Gameboy. gb->apu_output.stream_started = true; gb->apu_output.buffer_position = 0; + buffer_position = 0; } - if (count > gb->apu_output.buffer_position) { + if (count > buffer_position) { // GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position); GB_sample_t output; render(gb, true, &output); - for (unsigned i = 0; i < count - gb->apu_output.buffer_position; i++) { - dest[gb->apu_output.buffer_position + i] = output; + for (unsigned i = 0; i < count - buffer_position; i++) { + dest[buffer_position + i] = output; } - if (gb->apu_output.buffer_position) { - if (gb->apu_output.buffer_size + (count - gb->apu_output.buffer_position) < count * 3) { - gb->apu_output.buffer_size += count - gb->apu_output.buffer_position; + if (buffer_position) { + if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) { + gb->apu_output.buffer_size += count - buffer_position; gb->apu_output.buffer = realloc(gb->apu_output.buffer, gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer)); gb->apu_output.stream_started = false; } } - count = gb->apu_output.buffer_position; + count = buffer_position; } memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer)); - memmove(gb->apu_output.buffer, gb->apu_output.buffer + count, (gb->apu_output.buffer_position - count) * sizeof(*gb->apu_output.buffer)); + memmove(gb->apu_output.buffer, gb->apu_output.buffer + count, (buffer_position - count) * sizeof(*gb->apu_output.buffer)); gb->apu_output.buffer_position -= count; gb->apu_output.copy_in_progress = false; From 9d0aadb83fa0d63fb6810f05ff6089686cf82db4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Feb 2019 17:04:48 +0200 Subject: [PATCH 0816/1216] Emulate missing Vreset signal (SGB only for now) and ICD2 desyncing --- Core/display.c | 19 ++++++++++++++++--- Core/gb.h | 2 ++ Core/memory.c | 5 ++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 05a45b70..d18c70dd 100644 --- a/Core/display.c +++ b/Core/display.c @@ -393,7 +393,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } if (gb->sgb) { - gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; + if (gb->current_lcd_line < LINES) { + gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel; + } } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; @@ -407,7 +409,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); } if (gb->sgb) { - gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; + if (gb->current_lcd_line < LINES) { + gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel; + } } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; @@ -609,6 +613,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } /* Todo: Merge this with the normal line routine */ + /* Todo: Needs actual rendering, affects SGB emulation */ /* Handle the very first line 0 */ gb->current_line = 0; gb->ly_for_comparison = 0; @@ -642,7 +647,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = true; gb->vram_write_blocked = true; gb->cgb_palettes_blocked = true; - + gb->current_lcd_line++; // TODO: Verify timing + if (gb->current_lcd_line == LINES) { + display_vblank(gb); + } /* TODO: How does the window affect this line? */ gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2; GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2); @@ -747,6 +755,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; + gb->current_lcd_line++; // Todo: unverified timing + if (gb->current_lcd_line == LINES) { + display_vblank(gb); + } gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); @@ -961,6 +973,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->wy_diff = 0; gb->window_disabled_while_active = false; gb->current_line = 0; + gb->current_lcd_line = -1; // TODO: not the correct timing } } diff --git a/Core/gb.h b/Core/gb.h index 9dea4ef3..66df9c96 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -432,6 +432,7 @@ struct GB_gameboy_internal_s { /* The LCDC will skip the first frame it renders after turning it on. On the CGB, a frame is not skipped if the previous frame was skipped as well. See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ + /* TODO: Drop this and properly emulate the dropped vreset signal*/ enum { GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, // on a CGB, the previous frame is repeated (which might be @@ -466,6 +467,7 @@ struct GB_gameboy_internal_s { uint8_t mode_for_interrupt; bool lyc_interrupt_line; bool cgb_palettes_blocked; + uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index bf01d202..980e92cd 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -702,7 +702,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->display_cycles = 0; gb->display_state = 0; - if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { + if (GB_is_sgb(gb)) { + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; } } From 0b03b61564b7dbf2df8498d8d71c8e59fb83c92e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Feb 2019 04:19:16 +0200 Subject: [PATCH 0817/1216] Render the first line 0, as required for SGB emulation --- Core/debugger.c | 2 +- Core/display.c | 112 +++++++++++++++++++----------------------------- 2 files changed, 46 insertions(+), 68 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index b8092626..9265ce6d 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1525,7 +1525,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "Reading OAM data (%d/40)\n", gb->display_state == 8? gb->oam_search_index : 0); } else if (gb->display_state <= 3 || gb->display_state == 24 || gb->display_state == 31) { - GB_log(gb, "Glitched line 0 (%d cycles to next event)\n", -gb->display_cycles / 2); + GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2); } else if (gb->mode_for_interrupt == 3) { signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line; diff --git a/Core/display.c b/Core/display.c index d18c70dd..24dda946 100644 --- a/Core/display.c +++ b/Core/display.c @@ -95,12 +95,8 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe Mode 1 is VBlank */ -/* Todo: Clean up the glitched line 0 and get rid of these defines */ #define MODE2_LENGTH (80) -#define MODE3_LENGTH (172) -#define MODE0_LENGTH (204) - -#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 +#define LINE_LENGTH (456) #define LINES (144) #define WIDTH (160) #define FRAME_LENGTH (LCDC_PERIOD) @@ -564,9 +560,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE_MACHINE(gb, display, cycles, 2) { GB_STATE(gb, display, 1); GB_STATE(gb, display, 2); - GB_STATE(gb, display, 3); - GB_STATE(gb, display, 4); - GB_STATE(gb, display, 5); + // GB_STATE(gb, display, 3); + // GB_STATE(gb, display, 4); + // GB_STATE(gb, display, 5); GB_STATE(gb, display, 6); GB_STATE(gb, display, 7); GB_STATE(gb, display, 8); @@ -584,19 +580,21 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 21); GB_STATE(gb, display, 22); GB_STATE(gb, display, 23); - GB_STATE(gb, display, 24); + // GB_STATE(gb, display, 24); GB_STATE(gb, display, 25); GB_STATE(gb, display, 26); GB_STATE(gb, display, 27); GB_STATE(gb, display, 28); GB_STATE(gb, display, 29); GB_STATE(gb, display, 30); - GB_STATE(gb, display, 31); + // GB_STATE(gb, display, 31); GB_STATE(gb, display, 32); GB_STATE(gb, display, 33); GB_STATE(gb, display, 34); GB_STATE(gb, display, 35); GB_STATE(gb, display, 36); + GB_STATE(gb, display, 37); + GB_STATE(gb, display, 38); } @@ -612,9 +610,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 23, 1); } - /* Todo: Merge this with the normal line routine */ - /* Todo: Needs actual rendering, affects SGB emulation */ - /* Handle the very first line 0 */ + /* Handle mode 2 on the very first line 0 */ gb->current_line = 0; gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; @@ -633,56 +629,33 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); GB_SLEEP(gb, display, 34, 2); + gb->n_visible_objs = 0; + gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles. + gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; gb->mode_for_interrupt = 3; + + gb->oam_write_blocked = true; gb->oam_read_blocked = true; - gb->vram_read_blocked = !GB_is_cgb(gb) || gb->cgb_double_speed; - gb->vram_write_blocked = !GB_is_cgb(gb) || gb->cgb_double_speed; - gb->cgb_palettes_blocked = !GB_is_cgb(gb); - GB_STAT_update(gb); - + gb->vram_read_blocked = gb->cgb_double_speed; + gb->vram_write_blocked = gb->cgb_double_speed; + if (!GB_is_cgb(gb)) { + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + } gb->cycles_for_line += 2; - GB_SLEEP(gb, display, 24, 2); + GB_SLEEP(gb, display, 37, 2); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 38, 2); + gb->vram_read_blocked = true; gb->vram_write_blocked = true; - gb->cgb_palettes_blocked = true; - gb->current_lcd_line++; // TODO: Verify timing - if (gb->current_lcd_line == LINES) { - display_vblank(gb); - } - /* TODO: How does the window affect this line? */ - gb->cycles_for_line += MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2; - GB_SLEEP(gb, display, 3, MODE3_LENGTH + (gb->io_registers[GB_IO_SCX] & 7) - 2); + goto mode_3_start; + - if (!gb->cgb_double_speed) { - gb->io_registers[GB_IO_STAT] &= ~3; - gb->mode_for_interrupt = 0; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - } - gb->cycles_for_line += 1; - GB_SLEEP(gb, display, 4, 1); - - gb->io_registers[GB_IO_STAT] &= ~3; - gb->mode_for_interrupt = 0; - gb->oam_read_blocked = false; - gb->vram_read_blocked = false; - gb->oam_write_blocked = false; - gb->vram_write_blocked = false; - - gb->cycles_for_line += 2; - GB_SLEEP(gb, display, 31, 2); - gb->cgb_palettes_blocked = false; - - GB_STAT_update(gb); - /* Mode 0 is shorter in the very first line */ - GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8); - - gb->mode_for_interrupt = 2; - gb->current_line = 1; while (true) { /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { @@ -738,6 +711,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); } } + gb->cycles_for_line = MODE2_LENGTH + 4; + gb->accessed_oam_row = -1; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; @@ -746,9 +721,23 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_write_blocked = true; gb->cgb_palettes_blocked = false; gb->oam_write_blocked = true; + gb->oam_read_blocked = true; + GB_STAT_update(gb); - gb->cycles_for_line = MODE2_LENGTH + 4; + + uint8_t idle_cycles = 3; + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + idle_cycles = 2; + } + gb->cycles_for_line += idle_cycles; + GB_SLEEP(gb, display, 10, idle_cycles); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 32, 2); + mode_3_start: + fifo_clear(&gb->bg_fifo); fifo_clear(&gb->oam_fifo); /* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */ @@ -761,17 +750,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); - - uint8_t idle_cycles = 3; - if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { - idle_cycles = 2; - } - gb->cycles_for_line += idle_cycles; - GB_SLEEP(gb, display, 10, idle_cycles); - - gb->cgb_palettes_blocked = true; - gb->cycles_for_line += 2; - GB_SLEEP(gb, display, 32, 2); /* The actual rendering cycle */ From 57b0fe7fed8e8218d7c890cd40dea4fa93f121a5 Mon Sep 17 00:00:00 2001 From: radius Date: Tue, 12 Mar 2019 19:54:26 -0500 Subject: [PATCH 0818/1216] fix input --- libretro/libretro.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 3dc33a5f..40802932 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -827,7 +827,10 @@ void retro_run(void) check_variables(); if (emulated_devices == 2) + { + GB_update_keys_status(&gameboy[0], 0); GB_update_keys_status(&gameboy[1], 1); + } else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { for (unsigned i = 0; i < 4; i++) From c3426632003d7d0664ec0d861c7aa876db62fe8a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 15 Mar 2019 14:36:10 +0200 Subject: [PATCH 0819/1216] Emulate serial bit shifting, update the serial API to use bits instead of bytes, update printer emulation and libretro to use the new API --- Cocoa/Document.m | 8 +++--- Core/gb.c | 27 +++++++++++-------- Core/gb.h | 17 ++++++------ Core/memory.c | 7 ++--- Core/printer.c | 37 ++++++++++++++++++-------- Core/printer.h | 4 +++ Core/timing.c | 63 ++++++++++++++++++++++++++++++++------------- libretro/libretro.c | 38 +++++++++++++-------------- 8 files changed, 127 insertions(+), 74 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c94f77b3..3d3ad7be 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1376,12 +1376,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, currentPrinterImageData = [[NSMutableData alloc] init]; } [currentPrinterImageData appendBytes:paddedImage length:sizeof(paddedImage)]; - self.feedImageView.image = [Document imageFromData:currentPrinterImageData - width:160 - height:currentPrinterImageData.length / 160 / sizeof(imageBytes[0]) - scale:2.0]; /* UI related code must run on main thread. */ dispatch_async(dispatch_get_main_queue(), ^{ + self.feedImageView.image = [Document imageFromData:currentPrinterImageData + width:160 + height:currentPrinterImageData.length / 160 / sizeof(imageBytes[0]) + scale:2.0]; NSRect frame = self.printerFeedWindow.frame; frame.size = self.feedImageView.image.size; frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height; diff --git a/Core/gb.c b/Core/gb.c index 0d957eef..e5094458 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -477,40 +477,45 @@ void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) gb->rumble_callback = callback; } -void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback) +void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback) { - gb->serial_transfer_start_callback = callback; + gb->serial_transfer_bit_start_callback = callback; } -void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback) +void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback) { - gb->serial_transfer_end_callback = callback; + gb->serial_transfer_bit_end_callback = callback; } -uint8_t GB_serial_get_data(GB_gameboy_t *gb) +bool GB_serial_get_data_bit(GB_gameboy_t *gb) { if (gb->io_registers[GB_IO_SC] & 1) { /* Internal Clock */ GB_log(gb, "Serial read request while using internal clock. \n"); return 0xFF; } - return gb->io_registers[GB_IO_SB]; + return gb->io_registers[GB_IO_SB] & 0x80; } -void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data) +void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data) { if (gb->io_registers[GB_IO_SC] & 1) { /* Internal Clock */ GB_log(gb, "Serial write request while using internal clock. \n"); return; } - gb->io_registers[GB_IO_SB] = data; - gb->io_registers[GB_IO_IF] |= 8; + gb->io_registers[GB_IO_SB] <<= 1; + gb->io_registers[GB_IO_SB] |= data; + gb->serial_count++; + if (gb->serial_count == 8) { + gb->io_registers[GB_IO_IF] |= 8; + gb->serial_count = 0; + } } void GB_disconnect_serial(GB_gameboy_t *gb) { - gb->serial_transfer_start_callback = NULL; - gb->serial_transfer_end_callback = NULL; + gb->serial_transfer_bit_start_callback = NULL; + gb->serial_transfer_bit_end_callback = NULL; /* Reset any internally-emulated device. Currently, only the printer. */ memset(&gb->printer, 0, sizeof(gb->printer)); diff --git a/Core/gb.h b/Core/gb.h index 66df9c96..f2c4199a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -227,8 +227,8 @@ typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); -typedef void (*GB_serial_transfer_start_callback_t)(GB_gameboy_t *gb, uint8_t byte_to_send); -typedef uint8_t (*GB_serial_transfer_end_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); +typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); typedef struct { bool state; @@ -404,6 +404,7 @@ struct GB_gameboy_internal_s { uint16_t serial_cycles; uint16_t serial_length; uint8_t double_speed_alignment; + uint8_t serial_count; ); /* APU */ @@ -514,8 +515,8 @@ struct GB_gameboy_internal_s { GB_camera_get_pixel_callback_t camera_get_pixel_callback; GB_camera_update_request_callback_t camera_update_request_callback; GB_rumble_callback_t rumble_callback; - GB_serial_transfer_start_callback_t serial_transfer_start_callback; - GB_serial_transfer_end_callback_t serial_transfer_end_callback; + GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback; + GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -662,12 +663,12 @@ void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); /* These APIs are used when using internal clock */ -void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback); -void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback); +void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); +void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback); /* These APIs are used when using external clock */ -uint8_t GB_serial_get_data(GB_gameboy_t *gb); -void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); +bool GB_serial_get_data_bit(GB_gameboy_t *gb); +void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data); void GB_disconnect_serial(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index 980e92cd..c15ae42c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -872,11 +872,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } gb->io_registers[GB_IO_SC] = value | (~0x83); if ((value & 0x80) && (value & 0x1) ) { - gb->serial_length = gb->cgb_mode && (value & 2)? 128 : 4096; + gb->serial_length = gb->cgb_mode && (value & 2)? 16 : 512; + gb->serial_count = 0; /* Todo: This is probably incorrect for CGB's faster clock mode. */ gb->serial_cycles &= 0xFF; - if (gb->serial_transfer_start_callback) { - gb->serial_transfer_start_callback(gb, gb->io_registers[GB_IO_SB]); + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); } } else { diff --git a/Core/printer.c b/Core/printer.c index bc1d45ab..add1f86e 100644 --- a/Core/printer.c +++ b/Core/printer.c @@ -70,7 +70,8 @@ static void handle_command(GB_gameboy_t *gb) } } -static void serial_start(GB_gameboy_t *gb, uint8_t byte_received) + +static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received) { gb->printer.byte_to_send = 0; switch (gb->printer.command_state) { @@ -147,12 +148,10 @@ static void serial_start(GB_gameboy_t *gb, uint8_t byte_received) gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; return; } + gb->printer.byte_to_send = 0x81; + break; case GB_PRINTER_COMMAND_ACTIVE: - gb->printer.byte_to_send = 0x81; - break; - case GB_PRINTER_COMMAND_STATUS: - if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) { /* Games expect INIT commands to return 0? */ gb->printer.byte_to_send = 0; @@ -160,6 +159,8 @@ static void serial_start(GB_gameboy_t *gb, uint8_t byte_received) else { gb->printer.byte_to_send = gb->printer.status; } + break; + case GB_PRINTER_COMMAND_STATUS: /* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */ if (gb->printer.status == 6) { @@ -184,18 +185,32 @@ static void serial_start(GB_gameboy_t *gb, uint8_t byte_received) gb->printer.command_state++; } } - } -static uint8_t serial_end(GB_gameboy_t *gb) +static void serial_start(GB_gameboy_t *gb, bool bit_received) { - return gb->printer.byte_to_send; + gb->printer.byte_being_recieved <<= 1; + gb->printer.byte_being_recieved |= bit_received; + gb->printer.bits_recieved++; + if (gb->printer.bits_recieved == 8) { + byte_reieve_completed(gb, gb->printer.byte_being_recieved); + gb->printer.bits_recieved = 0; + gb->printer.byte_being_recieved = 0; + } +} + +static bool serial_end(GB_gameboy_t *gb) +{ + bool ret = gb->printer.bit_to_send; + gb->printer.bit_to_send = gb->printer.byte_to_send & 0x80; + gb->printer.byte_to_send <<= 1; + return ret; } void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback) { memset(&gb->printer, 0, sizeof(gb->printer)); - GB_set_serial_transfer_start_callback(gb, serial_start); - GB_set_serial_transfer_end_callback(gb, serial_end); + GB_set_serial_transfer_bit_start_callback(gb, serial_start); + GB_set_serial_transfer_bit_end_callback(gb, serial_end); gb->printer.callback = callback; -} \ No newline at end of file +} diff --git a/Core/printer.h b/Core/printer.h index e5d9036a..7cf179ed 100644 --- a/Core/printer.h +++ b/Core/printer.h @@ -52,6 +52,10 @@ typedef struct uint8_t compression_run_lenth; bool compression_run_is_compressed; + + uint8_t bits_recieved; + uint8_t byte_being_recieved; + bool bit_to_send; } GB_printer_t; diff --git a/Core/timing.c b/Core/timing.c index 06c603a4..8affd865 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -161,30 +161,57 @@ main: } } +static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->serial_length == 0) { + gb->serial_cycles += cycles; + return; + } + + while (cycles > gb->serial_length) { + advance_serial(gb, gb->serial_length); + cycles -= gb->serial_length; + } + + uint16_t previous_serial_cycles = gb->serial_cycles; + gb->serial_cycles += cycles; + if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) { + gb->serial_count++; + if (gb->serial_count == 8) { + gb->serial_length = 0; + gb->serial_count = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->io_registers[GB_IO_IF] |= 8; + } + + gb->io_registers[GB_IO_SB] <<= 1; + + if (gb->serial_transfer_bit_end_callback) { + gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] |= 1; + } + + if (gb->serial_length) { + /* Still more bits to send */ + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + + } + return; + +} + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost gb->dma_cycles += cycles; GB_timers_run(gb, cycles); - - uint16_t previous_serial_cycles = gb->serial_cycles; - gb->serial_cycles += cycles; - if (gb->serial_length) { - if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) { - gb->serial_length = 0; - gb->io_registers[GB_IO_SC] &= ~0x80; - /* TODO: Does SB "update" bit by bit? */ - if (gb->serial_transfer_end_callback) { - gb->io_registers[GB_IO_SB] = gb->serial_transfer_end_callback(gb); - } - else { - gb->io_registers[GB_IO_SB] = 0xFF; - } - - gb->io_registers[GB_IO_IF] |= 8; - } - } + advance_serial(gb, cycles); gb->debugger_ticks += cycles; diff --git a/libretro/libretro.c b/libretro/libretro.c index 3dc33a5f..9bf894a7 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -180,29 +180,29 @@ static void vblank2(GB_gameboy_t *gb) audio_callback(gb); } -static uint8_t byte_to_send1 = 0xFF, byte_to_send2 = 0xFF; +static bool bit_to_send1 = true, bit_to_send2 = true; -static void serial_start1(GB_gameboy_t *gb, uint8_t byte_received) +static void serial_start1(GB_gameboy_t *gb, bool bit_received) { - byte_to_send1 = byte_received; + bit_to_send1 = bit_received; } -static uint8_t serial_end1(GB_gameboy_t *gb) +static bool serial_end1(GB_gameboy_t *gb) { - uint8_t ret = GB_serial_get_data(&gameboy[1]); - GB_serial_set_data(&gameboy[1], byte_to_send1); + bool ret = GB_serial_get_data_bit(&gameboy[1]); + GB_serial_set_data_bit(&gameboy[1], bit_to_send1); return ret; } -static void serial_start2(GB_gameboy_t *gb, uint8_t byte_received) +static void serial_start2(GB_gameboy_t *gb, bool bit_received) { - byte_to_send2 = byte_received; + bit_to_send2 = bit_received; } -static uint8_t serial_end2(GB_gameboy_t *gb) +static bool serial_end2(GB_gameboy_t *gb) { - uint8_t ret = GB_serial_get_data(&gameboy[0]); - GB_serial_set_data(&gameboy[0], byte_to_send2); + bool ret = GB_serial_get_data_bit(&gameboy[0]); + GB_serial_set_data_bit(&gameboy[0], bit_to_send2); return ret; } @@ -337,17 +337,17 @@ static void set_link_cable_state(bool state) { if (state && emulated_devices == 2) { - GB_set_serial_transfer_start_callback(&gameboy[0], serial_start1); - GB_set_serial_transfer_end_callback(&gameboy[0], serial_end1); - GB_set_serial_transfer_start_callback(&gameboy[1], serial_start2); - GB_set_serial_transfer_end_callback(&gameboy[1], serial_end2); + GB_set_serial_transfer_bit_start_callback(&gameboy[0], serial_start1); + GB_set_serial_transfer_bit_end_callback(&gameboy[0], serial_end1); + GB_set_serial_transfer_bit_start_callback(&gameboy[1], serial_start2); + GB_set_serial_transfer_bit_end_callback(&gameboy[1], serial_end2); } else if (!state) { - GB_set_serial_transfer_start_callback(&gameboy[0], NULL); - GB_set_serial_transfer_end_callback(&gameboy[0], NULL); - GB_set_serial_transfer_start_callback(&gameboy[1], NULL); - GB_set_serial_transfer_end_callback(&gameboy[1], NULL); + GB_set_serial_transfer_bit_start_callback(&gameboy[0], NULL); + GB_set_serial_transfer_bit_end_callback(&gameboy[0], NULL); + GB_set_serial_transfer_bit_start_callback(&gameboy[1], NULL); + GB_set_serial_transfer_bit_end_callback(&gameboy[1], NULL); } } From 7242ddae64cf2cdd8e1a33970b0946ca5c77ecfe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Mar 2019 20:56:22 +0200 Subject: [PATCH 0820/1216] speling is difikult --- Core/display.c | 12 ++++++------ Core/gb.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/display.c b/Core/display.c index 24dda946..4989cca9 100644 --- a/Core/display.c +++ b/Core/display.c @@ -327,12 +327,12 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { unsigned j = 0; for (; j < gb->n_visible_objs; j++) { - if (gb->obj_comperators[j] <= objects[index].x) break; + if (gb->obj_comparators[j] <= objects[index].x) break; } memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); - memmove(gb->obj_comperators + j + 1, gb->obj_comperators + j, gb->n_visible_objs - j); + memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j); gb->visible_objs[j] = index; - gb->obj_comperators[j] = objects[index].x; + gb->obj_comparators[j] = objects[index].x; gb->n_visible_objs++; } } @@ -764,12 +764,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (gb->n_visible_objs != 0 && (gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) && - gb->obj_comperators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { + gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { gb->n_visible_objs--; } while (gb->n_visible_objs != 0 && (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && - gb->obj_comperators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { + gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { while (gb->fetcher_state < 5) { advance_fetcher_state_machine(gb); @@ -779,7 +779,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: Measure if penalty occurs before or after waiting for the fetcher. */ if (gb->extra_penalty_for_sprite_at_0 != 0) { - if (gb->obj_comperators[gb->n_visible_objs - 1] == 0) { + if (gb->obj_comparators[gb->n_visible_objs - 1] == 0) { gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0; GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0); gb->extra_penalty_for_sprite_at_0 = 0; diff --git a/Core/gb.h b/Core/gb.h index f2c4199a..f20fd77c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -460,7 +460,7 @@ struct GB_gameboy_internal_s { bool oam_fifo_paused; bool in_window; uint8_t visible_objs[10]; - uint8_t obj_comperators[10]; + uint8_t obj_comparators[10]; uint8_t n_visible_objs; uint8_t oam_search_index; uint8_t accessed_oam_row; From d4e8a886c56528a09f889ec9f28a87a9546d2729 Mon Sep 17 00:00:00 2001 From: trinemark <49107242+trinemark@users.noreply.github.com> Date: Fri, 5 Apr 2019 22:35:52 -0500 Subject: [PATCH 0821/1216] Fix libretro achievements https://github.com/LIJI32/SameBoy/issues/48 https://github.com/LIJI32/SameBoy/issues/157 --- libretro/libretro.c | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 4d5df93b..a596e941 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -393,7 +393,7 @@ static void init_for_current_model(unsigned id) set_link_cable_state(true); } - struct retro_memory_descriptor descs[8]; + struct retro_memory_descriptor descs[9]; size_t size; uint16_t bank; @@ -414,26 +414,31 @@ static void init_for_current_model(unsigned id) descs[2].start = 0xC000; descs[2].len = 0x1000; - descs[3].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); - descs[3].start = 0xA000; - descs[3].len = 0x2000; + descs[3].ptr = descs[2].ptr + (bank * 0x1000); + descs[3].start = 0xD000; + descs[3].len = 0x1000; - descs[4].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); - descs[4].start = 0x8000; + descs[4].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + descs[4].start = 0xA000; descs[4].len = 0x2000; - descs[5].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_ROM, &size, &bank); - descs[5].start = 0x0000; - descs[5].len = 0x4000; - descs[5].flags = RETRO_MEMDESC_CONST; + descs[5].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); + descs[5].start = 0x8000; + descs[5].len = 0x2000; - descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); - descs[6].start = 0xFE00; - descs[6].len = 0x00A0; + descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_ROM, &size, &bank); + descs[6].start = 0x0000; + descs[6].len = 0x4000; + descs[6].flags = RETRO_MEMDESC_CONST; - descs[7].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank) + 0x1000; - descs[7].start = 0xD000; - descs[7].len = 0x1000; + descs[7].ptr = descs[6].ptr + (bank * 0x4000); + descs[7].start = 0x4000; + descs[7].len = 0x4000; + descs[7].flags = RETRO_MEMDESC_CONST; + + descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); + descs[8].start = 0xFE00; + descs[8].len = 0x00A0; struct retro_memory_map mmaps; mmaps.descriptors = descs; From b3939e8fdcec9882fc57a4e4fbe3722860c3d1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Zumer?= Date: Sat, 6 Apr 2019 04:10:41 -0400 Subject: [PATCH 0822/1216] Add static GBC RAM banks to libretro memory map --- libretro/libretro.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index a596e941..937c6e82 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -393,7 +393,7 @@ static void init_for_current_model(unsigned id) set_link_cable_state(true); } - struct retro_memory_descriptor descs[9]; + struct retro_memory_descriptor descs[10]; size_t size; uint16_t bank; @@ -440,6 +440,10 @@ static void init_for_current_model(unsigned id) descs[8].start = 0xFE00; descs[8].len = 0x00A0; + descs[9].ptr = descs[2].ptr + 0x1000; + descs[9].start = 0x10000; + descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x7000 : 0; + struct retro_memory_map mmaps; mmaps.descriptors = descs; mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); From 05cd81b77c81ddbbdbe74606512ac9606b800cbb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Apr 2019 20:29:43 +0300 Subject: [PATCH 0823/1216] Implemented jump-to breakpoints --- Core/debugger.c | 311 ++++++++++++++++++++++++++++++++++++++++++++++-- Core/gb.c | 8 ++ Core/gb.h | 3 + 3 files changed, 311 insertions(+), 11 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 9265ce6d..c18c1dfb 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -34,6 +34,7 @@ struct GB_breakpoint_s { uint32_t key; /* For sorting and comparing */ }; char *condition; + bool is_jump_to; }; #define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) @@ -845,7 +846,15 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { - NO_MODIFIERS + bool is_jump_to = true; + if (!modifiers) { + is_jump_to = false; + } + else if (strcmp(modifiers, "j") != 0) { + print_usage(gb, command); + return true; + } + if (strlen(lstrip(arguments)) == 0) { print_usage(gb, command); return true; @@ -904,6 +913,12 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const gb->breakpoints[index].condition = NULL; } gb->n_breakpoints++; + + gb->breakpoints[index].is_jump_to = is_jump_to; + + if (is_jump_to) { + gb->has_jump_to_breakpoints = true; + } GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; @@ -955,6 +970,16 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb free(gb->breakpoints[index].condition); } + if (gb->breakpoints[index].is_jump_to) { + gb->has_jump_to_breakpoints = false; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (i == index) continue; + if (gb->breakpoints[i].is_jump_to) { + gb->has_jump_to_breakpoints = true; + break; + } + } + } memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); gb->n_breakpoints--; @@ -1152,12 +1177,15 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug for (uint16_t i = 0; i < gb->n_breakpoints; i++) { value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; if (gb->breakpoints[i].condition) { - GB_log(gb, " %d. %s (Condition: %s)\n", i + 1, + GB_log(gb, " %d. %s (%sCondition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? "Jump to, ": "", gb->breakpoints[i].condition); } else { - GB_log(gb, " %d. %s\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank)); + GB_log(gb, " %d. %s%s\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? " (Jump to)" : ""); } } } @@ -1186,12 +1214,12 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return true; } -static bool _should_break(GB_gameboy_t *gb, value_t addr) +static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to) { uint16_t index = find_breakpoint(gb, addr); uint32_t key = BP_KEY(addr); - if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key && gb->breakpoints[index].is_jump_to == jump_to) { if (!gb->breakpoints[index].condition) { return true; } @@ -1208,16 +1236,16 @@ static bool _should_break(GB_gameboy_t *gb, value_t addr) return false; } -static bool should_break(GB_gameboy_t *gb, uint16_t addr) +static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) { /* Try any-bank breakpoint */ value_t full_addr = (VALUE_16(addr)); - if (_should_break(gb, full_addr)) return true; + if (_should_break(gb, full_addr, jump_to)) return true; /* Try bank-specific breakpoint */ full_addr.has_bank = true; full_addr.bank = bank_for_addr(gb, addr); - return _should_break(gb, full_addr); + return _should_break(gb, full_addr, jump_to); } static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) @@ -1562,8 +1590,10 @@ static const debugger_command_t commands[] = { {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE - "Can also modify the condition of existing breakpoints.", - "[ if ]"}, + "Can also modify the condition of existing breakpoints." HELP_NEWLINE + "If the j modifier is used, the breakpoint will occur just" HELP_NEWLINE + "before jumping to the target.", + "[ if ]", "(j)"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE @@ -1834,6 +1864,14 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) } } +typedef enum { + JUMP_TO_NONE, + JUMP_TO_BREAK, + JUMP_TO_NONTRIVIAL, +} jump_to_return_t; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address); + void GB_debugger_run(GB_gameboy_t *gb) { if (gb->debug_disable) return; @@ -1852,11 +1890,55 @@ next_command: if (input) { free(input); } - if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc)) { + if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc, false)) { gb->debug_stopped = true; GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_cpu_disassemble(gb, gb->pc, 5); } + + if (gb->breakpoints && !gb->debug_stopped) { + uint16_t address = 0; + jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address); + + bool should_delete_state = true; + if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) { + if (gb->non_trivial_jump_breakpoint_occured) { + gb->non_trivial_jump_breakpoint_occured = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1); + gb->debug_stopped = true; + } + } + else if (jump_to_result == JUMP_TO_BREAK) { + gb->debug_stopped = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, address, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + gb->non_trivial_jump_breakpoint_occured = false; + } + else if (jump_to_result == JUMP_TO_NONTRIVIAL) { + if (!gb->nontrivial_jump_state) { + gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb)); + } + GB_save_state_to_buffer(gb, gb->nontrivial_jump_state); + gb->non_trivial_jump_breakpoint_occured = false; + should_delete_state = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = false; + } + + if (should_delete_state) { + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + } + } + if (gb->debug_stopped && !gb->debug_disable) { gb->debug_next_command = false; gb->debug_fin_command = false; @@ -1986,3 +2068,210 @@ void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled) { gb->debug_disable = disabled; } + +/* Jump-to breakpoints */ + +static bool is_in_trivial_memory(uint16_t addr) +{ + /* ROM */ + if (addr < 0x8000) { + return true; + } + + /* HRAM */ + if (addr >= 0xFF80 && addr < 0xFFFF) { + return true; + } + + /* RAM */ + if (addr >= 0xC000 && addr < 0xE000) { + return true; + } + + return false; +} + +typedef uint16_t GB_opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode); + +uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 1; +} + +uint16_t trivial_2(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2; +} + +uint16_t trivial_3(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 3; +} + +static uint16_t jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t 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 uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + if (!condition_code(gb, opcode)) { + return gb->pc + 2; + } + + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); +} + + +static uint16_t ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return ret(gb, opcode); + } + else { + return gb->pc + 1; + } +} + +static uint16_t jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->pc + 1) | + (GB_read_memory(gb, gb->pc + 2) << 8); +} + +static uint16_t jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return jp_a16(gb, opcode); + } + else { + return gb->pc + 3; + } +} + +static uint16_t rst(GB_gameboy_t *gb, uint8_t opcode) +{ + return opcode ^ 0xC7; +} + +static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->hl; +} + +static GB_opcode_address_getter_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */ + trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_2, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 1X */ + jr_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 2X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 3X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 4X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 5X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 6X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, NULL, trivial_1, /* 7X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 8X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 9X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* aX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* bX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + ret_cc, trivial_1, jp_cc_a16, jp_a16, jp_cc_a16, trivial_1, trivial_2, rst, /* cX */ + ret_cc, ret, jp_cc_a16, trivial_2, jp_cc_a16, jp_a16, trivial_2, rst, + ret_cc, trivial_1, jp_cc_a16, NULL, jp_cc_a16, trivial_1, trivial_2, rst, /* dX */ + ret_cc, ret, jp_cc_a16, NULL, jp_cc_a16, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, NULL, NULL, trivial_1, trivial_2, rst, /* eX */ + trivial_2, jp_hl, trivial_3, NULL, NULL, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, trivial_1, NULL, trivial_1, trivial_2, rst, /* fX */ + trivial_2, trivial_1, trivial_3, trivial_1, NULL, NULL, trivial_2, rst, +}; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address) +{ + if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; + + if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || + !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { + return JUMP_TO_NONTRIVIAL; + } + + /* Interrupts */ + if (gb->ime) { + for (unsigned i = 0; i < 5; i++) { + if ((gb->interrupt_enable & (1 << i)) && (gb->io_registers[GB_IO_IF] & (1 << i))) { + if (should_break(gb, 0x40 + i * 8, true)) { + if (address) { + *address = 0x40 + i * 8; + } + return JUMP_TO_BREAK; + } + } + } + } + + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + uint8_t opcode = GB_read_memory(gb, gb->pc); + + if (opcode == 0x76) { + gb->n_watchpoints = n_watchpoints; + if (gb->ime) { /* Already handled in above */ + return JUMP_TO_NONE; + } + + if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) { + return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */ + } + + return JUMP_TO_NONE; + } + + GB_opcode_address_getter_t *getter = opcodes[opcode]; + if (!getter) { + gb->n_watchpoints = n_watchpoints; + return JUMP_TO_NONE; + } + + uint16_t new_pc = getter(gb, opcode); + + gb->n_watchpoints = n_watchpoints; + + if (address) { + *address = new_pc; + } + + return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE; +} diff --git a/Core/gb.c b/Core/gb.c index e5094458..9456f106 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -142,6 +142,9 @@ void GB_free(GB_gameboy_t *gb) if (gb->sgb) { free(gb->sgb); } + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + } #ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); #endif @@ -689,6 +692,11 @@ void GB_reset(GB_gameboy_t *gb) GB_apu_update_cycles_per_sample(gb); + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + gb->magic = (uintptr_t)'SAME'; } diff --git a/Core/gb.h b/Core/gb.h index f20fd77c..f793f30d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -531,6 +531,9 @@ struct GB_gameboy_internal_s { /* Breakpoints */ uint16_t n_breakpoints; struct GB_breakpoint_s *breakpoints; + bool has_jump_to_breakpoints; + void *nontrivial_jump_state; + bool non_trivial_jump_breakpoint_occured; /* SLD (Todo: merge with backtrace) */ bool stack_leak_detection; From 82ce59757387956d94b7e88570bf4f11f0c1c897 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Apr 2019 20:30:02 +0300 Subject: [PATCH 0824/1216] Line breaks --- Core/debugger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index c18c1dfb..7ea3a3f8 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1591,8 +1591,8 @@ static const debugger_command_t commands[] = { {"palettes", 3, palettes, "Displays the current CGB palettes"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE "Can also modify the condition of existing breakpoints." HELP_NEWLINE - "If the j modifier is used, the breakpoint will occur just" HELP_NEWLINE - "before jumping to the target.", + "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE + "jumping to the target.", "[ if ]", "(j)"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE From f8244c8119b2d1adef28ce6932d3a1432771b8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Zumer?= Date: Mon, 15 Apr 2019 16:39:14 -0400 Subject: [PATCH 0825/1216] Update libretro GBC memory map --- libretro/libretro.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 937c6e82..15ea0938 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -414,7 +414,7 @@ static void init_for_current_model(unsigned id) descs[2].start = 0xC000; descs[2].len = 0x1000; - descs[3].ptr = descs[2].ptr + (bank * 0x1000); + descs[3].ptr = descs[2].ptr + 0x1000; /* GB RAM/GBC RAM bank 1 */ descs[3].start = 0xD000; descs[3].len = 0x1000; @@ -440,9 +440,9 @@ static void init_for_current_model(unsigned id) descs[8].start = 0xFE00; descs[8].len = 0x00A0; - descs[9].ptr = descs[2].ptr + 0x1000; + descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ descs[9].start = 0x10000; - descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x7000 : 0; + descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ struct retro_memory_map mmaps; mmaps.descriptors = descs; From 9e44306c04bcd475efe9700663bceeb559b18f51 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Apr 2019 14:49:09 +0300 Subject: [PATCH 0826/1216] Update .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 255c40c2..427cb285 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ HexFiend/* linguist-vendored +*.inc linguist-language=C Core/*.h linguist-language=C SDL/*.h linguist-language=C Windows/*.h linguist-language=C From 2a0e5f667b616d5a460d249712e24951b51bbc6d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 4 May 2019 15:35:17 +0300 Subject: [PATCH 0827/1216] Fix build on some Windows machines using non-Latin locales. Fixes #165 --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 5f46008e..5870d423 100755 --- a/Makefile +++ b/Makefile @@ -13,6 +13,10 @@ ifneq ($(findstring MSYS,$(PLATFORM)),) PLATFORM := windows32 endif +ifeq ($(PLATFORM),windows32) +_ := $(shell chcp 65001) +endif + ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa else From 10be34b5b252711d961ac13a6b8417cb1b94d2bb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 4 May 2019 21:15:23 +0300 Subject: [PATCH 0828/1216] Fix the fast CGB boot ROM booting with data on the second VRAM bank --- BootROMs/cgb_boot.asm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index fa826f5f..ee0198a3 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -99,6 +99,10 @@ Start: ld hl, $98C2 ld b, 3 ld a, 8 +IF DEF(FAST) + xor a + ldh [$4F], a +ENDC .tilemapLoop ld c, $10 @@ -107,6 +111,7 @@ Start: ld [hl], a push af +IF !DEF(FAST) ; Switch to second VRAM Bank ld a, 1 ldh [$4F], a @@ -115,6 +120,7 @@ Start: ; Switch to back first VRAM Bank xor a ldh [$4F], a +ENDC pop af ldi [hl], a inc a From b6e92dc8a74c850df3c5af49310e6c018d6c6707 Mon Sep 17 00:00:00 2001 From: funbars <50187994+funbars@users.noreply.github.com> Date: Tue, 7 May 2019 12:36:04 -0500 Subject: [PATCH 0829/1216] libretro windows compiler (random) --- libretro/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/libretro/Makefile b/libretro/Makefile index 75ddfc6c..fffe71c8 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -243,6 +243,7 @@ else CC = gcc TARGET := $(TARGET_NAME)_libretro.dll SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined + CFLAGS += -Drandom=rand endif TARGET := $(CORE_DIR)/build/bin/$(TARGET) From 5ce8cf5016ec16c97521d49dc1ad98232ef21288 Mon Sep 17 00:00:00 2001 From: orbea Date: Thu, 9 May 2019 09:01:15 -0700 Subject: [PATCH 0830/1216] Makefile: Allow setting CC. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 5870d423..542fee06 100755 --- a/Makefile +++ b/Makefile @@ -44,9 +44,11 @@ endif # Set tools # Use clang if it's available. +ifeq ($(origin CC),default) ifneq (, $(shell which clang)) CC := clang endif +endif ifeq ($(PLATFORM),windows32) # To force use of the Unix version instead of the Windows version From 2bded45397f39772b57b7a8943ebbd15ea4a927d Mon Sep 17 00:00:00 2001 From: orbea Date: Thu, 9 May 2019 09:08:25 -0700 Subject: [PATCH 0831/1216] Disable pragmas for gcc. --- Core/apu.c | 6 ++++++ Core/display.c | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index 1b3dd2bd..9a55f26e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -69,7 +69,9 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) { GB_sample_t output = {0,0}; +#ifdef __clang__ #pragma unroll +#endif for (unsigned i = GB_N_CHANNELS; i--;) { double multiplier = CH_STEP; if (!is_DAC_enabled(gb, i)) { @@ -126,7 +128,9 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) unsigned mask = gb->io_registers[GB_IO_NR51]; unsigned left_volume = 0; unsigned right_volume = 0; +#ifdef __clang__ #pragma unroll +#endif for (unsigned i = GB_N_CHANNELS; i--;) { if (gb->apu.is_active[i]) { if (mask & 1) { @@ -374,7 +378,9 @@ void GB_apu_run(GB_gameboy_t *gb) } } +#ifdef __clang__ #pragma unroll +#endif for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.is_active[i]) { uint8_t cycles_left = cycles; diff --git a/Core/display.c b/Core/display.c index 4989cca9..fdd4a2fd 100644 --- a/Core/display.c +++ b/Core/display.c @@ -27,7 +27,9 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) { if (!flip_x) { +#ifdef __clang__ #pragma unroll +#endif for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower >> 7) | ((upper >> 7) << 1), @@ -43,7 +45,9 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint } } else { +#ifdef __clang__ #pragma unroll +#endif for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower & 1) | ((upper & 1) << 1), @@ -70,7 +74,9 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe uint8_t flip_xor = flip_x? 0: 0x7; +#ifdef __clang__ #pragma unroll +#endif for (unsigned i = 8; i--;) { uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)]; @@ -1117,7 +1123,9 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } for (unsigned y = 0; y < *sprite_height; y++) { +#ifdef __clang__ #pragma unroll +#endif for (unsigned x = 0; x < 8; x++) { uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); From 06670fc970dd1cfb2695534fc6e4d92ed5a632f2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 May 2019 21:51:11 +0300 Subject: [PATCH 0832/1216] Fix #172. Allow unroll optimizations when compiling with GCC. --- Core/apu.c | 12 +++--------- Core/display.c | 16 ++++------------ Core/gb.h | 9 +++++++++ Makefile | 2 +- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 9a55f26e..e4377185 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -69,9 +69,7 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) { GB_sample_t output = {0,0}; -#ifdef __clang__ - #pragma unroll -#endif + for (unsigned i = GB_N_CHANNELS; i--;) { double multiplier = CH_STEP; if (!is_DAC_enabled(gb, i)) { @@ -128,9 +126,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) unsigned mask = gb->io_registers[GB_IO_NR51]; unsigned left_volume = 0; unsigned right_volume = 0; -#ifdef __clang__ - #pragma unroll -#endif + UNROLL for (unsigned i = GB_N_CHANNELS; i--;) { if (gb->apu.is_active[i]) { if (mask & 1) { @@ -378,9 +374,7 @@ void GB_apu_run(GB_gameboy_t *gb) } } -#ifdef __clang__ - #pragma unroll -#endif + UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.is_active[i]) { uint8_t cycles_left = cycles; diff --git a/Core/display.c b/Core/display.c index fdd4a2fd..22f6e8d6 100644 --- a/Core/display.c +++ b/Core/display.c @@ -27,9 +27,7 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) { if (!flip_x) { -#ifdef __clang__ - #pragma unroll -#endif + UNROLL for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower >> 7) | ((upper >> 7) << 1), @@ -45,9 +43,7 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint } } else { -#ifdef __clang__ - #pragma unroll -#endif + UNROLL for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower & 1) | ((upper & 1) << 1), @@ -74,9 +70,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe uint8_t flip_xor = flip_x? 0: 0x7; -#ifdef __clang__ - #pragma unroll -#endif + UNROLL for (unsigned i = 8; i--;) { uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)]; @@ -1123,9 +1117,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } for (unsigned y = 0; y < *sprite_height; y++) { -#ifdef __clang__ - #pragma unroll -#endif + UNROLL for (unsigned x = 0; x < 8; x++) { uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); diff --git a/Core/gb.h b/Core/gb.h index f793f30d..a4b7256e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -29,6 +29,15 @@ #define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_CGB_FAMILY 0x200 #define GB_MODEL_PAL_BIT 0x1000 + +#if __clang__ +#define UNROLL _Pragma("unroll") +#elif __GNUC__ +#define UNROLL _Pragma("GCC unroll 8") +#else +#define UNROLL +#endif + #endif #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ diff --git a/Makefile b/Makefile index 542fee06..39d77e9a 100755 --- a/Makefile +++ b/Makefile @@ -204,7 +204,7 @@ $(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 -framework IOKit ifeq ($(CONF), release) - strip $@ + #strip $@ endif $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib From 32361589c9ed1f09349d8943674379dcbf25b77b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 May 2019 22:05:03 +0300 Subject: [PATCH 0833/1216] Fix GCC build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 Makefile diff --git a/Makefile b/Makefile old mode 100755 new mode 100644 index 39d77e9a..dec3a91d --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ endif # Set compilation and linkage flags based on target, platform and configuration -CFLAGS += -Werror -Wall -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -Werror -Wall -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows From ddc4e7484b4c09718ac8dd24d25a862c82f8f4a4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 May 2019 22:29:30 +0300 Subject: [PATCH 0834/1216] Fix and restore optimization --- Core/apu.c | 5 +++-- Makefile | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index e4377185..a0b9aa11 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -70,7 +70,8 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) { GB_sample_t output = {0,0}; - for (unsigned i = GB_N_CHANNELS; i--;) { + UNROLL + for (unsigned i = 0; i < GB_N_CHANNELS; i++) { double multiplier = CH_STEP; if (!is_DAC_enabled(gb, i)) { gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; @@ -375,7 +376,7 @@ void GB_apu_run(GB_gameboy_t *gb) } UNROLL - for (unsigned i = GB_SQUARE_2 + 1; i--;) { + for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { if (gb->apu.is_active[i]) { uint8_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { diff --git a/Makefile b/Makefile index dec3a91d..5a9f9b59 100644 --- a/Makefile +++ b/Makefile @@ -204,7 +204,7 @@ $(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 -framework IOKit ifeq ($(CONF), release) - #strip $@ + strip $@ endif $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib From 4a5498544148369f1f0d47c8131f23c9e87b1b6f Mon Sep 17 00:00:00 2001 From: funbars <50187994+funbars@users.noreply.github.com> Date: Fri, 10 May 2019 15:50:16 -0500 Subject: [PATCH 0835/1216] fix libretro log interface --- libretro/libretro.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 15ea0938..f5e4e2e5 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -697,6 +697,11 @@ void retro_init(void) snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); else snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); + + if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) + log_cb = logging.log; + else + log_cb = fallback_log; } void retro_deinit(void) @@ -775,11 +780,6 @@ void retro_set_environment(retro_environment_t cb) { environ_cb = cb; - if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) - log_cb = logging.log; - else - log_cb = fallback_log; - cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); } From 07bdc60a495c65b07b41e331c106c9df9e522f98 Mon Sep 17 00:00:00 2001 From: "Anthony J. Bentley" Date: Sat, 11 May 2019 21:38:32 -0600 Subject: [PATCH 0836/1216] Use dd instead of non-POSIX head(1) options to trim bootroms. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5a9f9b59..a2df8bf8 100644 --- a/Makefile +++ b/Makefile @@ -315,7 +315,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm -@$(MKDIR) -p $(dir $@) cd BootROMs && rgbasm -o ../$@.tmp ../$< rgblink -o $@.tmp2 $@.tmp - head -c $(if $(findstring dmg,$@)$(findstring sgb,$@), 256, 2304) $@.tmp2 > $@ + dd if=$@.tmp2 of=$@ count=1 bs=$(if $(findstring dmg,$@)$(findstring sgb,$@),256,2304) @rm $@.tmp $@.tmp2 # Libretro Core (uses its own build system) From 40f83c8f251c43c3ea61225a39b37af8f7930ca8 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Wed, 15 May 2019 12:39:08 +0200 Subject: [PATCH 0837/1216] Add APU-related debugger commands This change includes making one of the APU functions public --- Core/apu.c | 164 +++++++++++++++---------------- Core/apu.h | 39 ++++---- Core/debugger.c | 256 ++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 315 insertions(+), 144 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index a0b9aa11..8d2bf347 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -21,34 +21,34 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; } -static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index) +bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index) { switch (index) { case GB_SQUARE_1: return gb->io_registers[GB_IO_NR12] & 0xF8; - + case GB_SQUARE_2: return gb->io_registers[GB_IO_NR22] & 0xF8; - + case GB_WAVE: return gb->apu.wave_channel.enable; - + case GB_NOISE: return gb->io_registers[GB_IO_NR42] & 0xF8; } - + return 0; } static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) { - if (!is_DAC_enabled(gb, index)) { + if (!GB_apu_is_DAC_enabled(gb, index)) { value = gb->apu.samples[index]; } else { gb->apu.samples[index] = value; } - + if (gb->apu_output.sample_rate) { unsigned right_volume = 0; if (gb->io_registers[GB_IO_NR51] & (1 << index)) { @@ -73,7 +73,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) UNROLL for (unsigned i = 0; i < GB_N_CHANNELS; i++) { double multiplier = CH_STEP; - if (!is_DAC_enabled(gb, i)) { + if (!GB_apu_is_DAC_enabled(gb, i)) { gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; if (gb->apu_output.dac_discharge[i] < 0) { multiplier = 0; @@ -113,7 +113,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left, output.right - gb->apu_output.highpass_diff.right} : output; - + switch (gb->apu_output.highpass_mode) { case GB_HIGHPASS_OFF: gb->apu_output.highpass_diff = (GB_double_sample_t) {0, 0}; @@ -146,10 +146,10 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) gb->apu_output.highpass_diff = (GB_double_sample_t) {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate, right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate}; - + case GB_HIGHPASS_MAX:; } - + } if (dest) { *dest = filtered_output; @@ -176,7 +176,7 @@ static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb) static void update_square_sample(GB_gameboy_t *gb, unsigned index) { if (gb->apu.square_channels[index].current_sample_index & 0x80) return; - + uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; update_sample(gb, index, duties[gb->apu.square_channels[index].current_sample_index + duty * 8]? @@ -195,38 +195,38 @@ static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) if (value & 8) { (*volume)++; } - + if (((value ^ old_value) & 8)) { *volume = 0x10 - *volume; } - + if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) { (*volume)--; } - + if ((old_value & 7) && (value & 8)) { (*volume)--; } - + (*volume) &= 0xF; } static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - + if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) { if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { gb->apu.square_channels[index].current_volume++; } - + else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { gb->apu.square_channels[index].current_volume--; } - + gb->apu.square_channels[index].volume_countdown = nrx2 & 7; - + if (gb->apu.is_active[index]) { update_square_sample(gb, index); } @@ -237,19 +237,19 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) static void tick_noise_envelope(GB_gameboy_t *gb) { uint8_t nr42 = gb->io_registers[GB_IO_NR42]; - + if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) { if (!--gb->apu.noise_channel.volume_countdown) { if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { gb->apu.noise_channel.current_volume++; } - + else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { gb->apu.noise_channel.current_volume--; } - + gb->apu.noise_channel.volume_countdown = nr42 & 7; - + if (gb->apu.is_active[GB_NOISE]) { update_sample(gb, GB_NOISE, (gb->apu.noise_channel.lfsr & 1) ? @@ -276,20 +276,20 @@ void GB_apu_div_event(GB_gameboy_t *gb) tick_square_envelope(gb, i); } } - + if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) { tick_noise_envelope(gb); } } - + if ((gb->apu.div_divider & 7) == 0) { for (unsigned i = GB_SQUARE_2 + 1; i--;) { tick_square_envelope(gb, i); } - + tick_noise_envelope(gb); } - + if ((gb->apu.div_divider & 1) == 1) { for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.square_channels[i].length_enabled) { @@ -301,7 +301,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } } } - + if (gb->apu.wave_channel.length_enabled) { if (gb->apu.wave_channel.pulse_length) { if (!--gb->apu.wave_channel.pulse_length) { @@ -311,7 +311,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } } } - + if (gb->apu.noise_channel.length_enabled) { if (gb->apu.noise_channel.pulse_length) { if (!--gb->apu.noise_channel.pulse_length) { @@ -321,7 +321,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } } } - + if ((gb->apu.div_divider & 3) == 3) { if (!gb->apu.sweep_enabled) { return; @@ -333,12 +333,12 @@ void GB_apu_div_event(GB_gameboy_t *gb) gb->apu.shadow_sweep_sample_legnth = gb->apu.new_sweep_sample_legnth; } - + if (gb->io_registers[GB_IO_NR10] & 0x70) { /* Recalculation and overflow check only occurs after a delay */ gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div; } - + gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; } @@ -353,11 +353,11 @@ void GB_apu_run(GB_gameboy_t *gb) uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; - + /* To align the square signal to 1MHz */ gb->apu.lf_div ^= cycles & 1; gb->apu.noise_channel.alignment += cycles; - + if (gb->apu.square_sweep_calculate_countdown) { if (gb->apu.square_sweep_calculate_countdown > cycles) { gb->apu.square_sweep_calculate_countdown -= cycles; @@ -374,7 +374,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.square_sweep_calculate_countdown = 0; } } - + UNROLL for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { if (gb->apu.is_active[i]) { @@ -384,7 +384,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; gb->apu.square_channels[i].current_sample_index++; gb->apu.square_channels[i].current_sample_index &= 0x7; - + update_square_sample(gb, i); } if (cycles_left) { @@ -392,7 +392,7 @@ void GB_apu_run(GB_gameboy_t *gb) } } } - + gb->apu.wave_channel.wave_form_just_read = false; if (gb->apu.is_active[GB_WAVE]) { uint8_t cycles_left = cycles; @@ -413,19 +413,19 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.wave_channel.wave_form_just_read = false; } } - + if (gb->apu.is_active[GB_NOISE]) { uint8_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { cycles_left -= gb->apu.noise_channel.sample_countdown + 1; gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; - + /* Step LFSR */ unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; /* Todo: is this formula is different on a GBA? */ bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; gb->apu.noise_channel.lfsr >>= 1; - + if (new_high_bit) { gb->apu.noise_channel.lfsr |= high_bit_mask; } @@ -433,19 +433,19 @@ void GB_apu_run(GB_gameboy_t *gb) /* This code is not redundent, it's relevant when switching LFSR widths */ gb->apu.noise_channel.lfsr &= ~high_bit_mask; } - + gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; if (gb->model == GB_MODEL_CGB_C) { /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, I'll assume these devices are innocent until proven guilty. - + Also happens on CGB-B, but not on CGB-D. */ gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; } gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - + update_sample(gb, GB_NOISE, gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, @@ -455,10 +455,10 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.noise_channel.sample_countdown -= cycles_left; } } - + if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - + if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) { gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; render(gb, false, NULL); @@ -475,7 +475,7 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) /* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */ size_t buffer_position = gb->apu_output.buffer_position; - + if (!gb->apu_output.stream_started) { // Intentionally fail the first copy to sync the stream with the Gameboy. gb->apu_output.stream_started = true; @@ -487,11 +487,11 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) // GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position); GB_sample_t output; render(gb, true, &output); - + for (unsigned i = 0; i < count - buffer_position; i++) { dest[buffer_position + i] = output; } - + if (buffer_position) { if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) { gb->apu_output.buffer_size += count - buffer_position; @@ -579,7 +579,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; } - + /* Todo: this can and should be rewritten with a function table. */ switch (reg) { /* Globals */ @@ -593,7 +593,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } break; case GB_IO_NR52: { - + uint8_t old_nrx1[] = { gb->io_registers[GB_IO_NR11], gb->io_registers[GB_IO_NR21], @@ -612,10 +612,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); old_nrx1[0] &= 0x3F; old_nrx1[1] &= 0x3F; - + gb->apu.global_enable = false; } - + if (!GB_is_cgb(gb) && (value & 0x80)) { GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]); GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]); @@ -624,7 +624,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } break; - + /* Square channels */ case GB_IO_NR10: if (gb->apu.sweep_decreasing && !(value & 8)) { @@ -639,7 +639,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_sweep_calculate_countdown = 0; } break; - + case GB_IO_NR11: case GB_IO_NR21: { unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1; @@ -649,7 +649,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } break; } - + case GB_IO_NR12: case GB_IO_NR22: { unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; @@ -667,10 +667,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); update_square_sample(gb, index); } - + break; } - + case GB_IO_NR13: case GB_IO_NR23: { unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1; @@ -678,7 +678,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].sample_length |= value & 0xFF; break; } - + case GB_IO_NR14: case GB_IO_NR24: { unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1; @@ -700,16 +700,16 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div; } gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; - + /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously started sound). The playback itself is not instant which is why we don't update the sample for other cases. */ if (gb->apu.is_active[index]) { update_square_sample(gb, index); } - + gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7; - + if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { gb->apu.is_active[index] = true; update_sample(gb, index, 0, 0); @@ -720,7 +720,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].pulse_length = 0x40; gb->apu.square_channels[index].length_enabled = false; } - + if (index == GB_SQUARE_1) { gb->apu.sweep_decreasing = false; if (gb->io_registers[GB_IO_NR10] & 7) { @@ -734,9 +734,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; } - + } - + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ if ((value & 0x40) && !gb->apu.square_channels[index].length_enabled && @@ -756,7 +756,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].length_enabled = value & 0x40; break; } - + /* Wave channel */ case GB_IO_NR30: gb->apu.wave_channel.enable = value & 0x80; @@ -788,12 +788,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.enable) { unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; - + /* This glitch varies between models and even specific instances: DMG-B: Most of them behave as emulated. A few behave differently. SGB: As far as I know, all tested instances behave as emulated. MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated. - + Additionally, I believe DMGs, including those we behave differently than emulated, are all deterministic. */ if (offset < 4) { @@ -827,7 +827,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) mean differences in the DACs. */ /* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */ } - + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ if ((value & 0x40) && !gb->apu.wave_channel.length_enabled && @@ -851,14 +851,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } break; - + /* Noise Channel */ - + case GB_IO_NR41: { gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f)); break; } - + case GB_IO_NR42: { if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { /* Envelope disabled */ @@ -879,24 +879,24 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } break; } - + case GB_IO_NR43: { gb->apu.noise_channel.narrow = value & 8; unsigned divisor = (value & 0x07) << 1; if (!divisor) divisor = 1; gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1; - + /* Todo: changing the frequency sometimes delays the next sample. This is probably due to how the frequency is actually calculated in the noise channel, which is probably not by calculating the effective sample length and counting simiarly to the other channels. This is not emulated correctly. */ break; } - + case GB_IO_NR44: { if (value & 0x80) { gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div; - + /* I'm COMPLETELY unsure about this logic, but it passes all relevant tests. See comment in NR43. */ if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) { @@ -910,9 +910,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (gb->apu.is_active[GB_NOISE]) { gb->apu.noise_channel.sample_countdown += 2; } - + gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; - + /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously started sound). The playback itself is not instant which is why we don't update the sample for other cases. */ @@ -925,18 +925,18 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.lfsr = 0; gb->apu.current_lfsr_sample = false; gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; - + if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { gb->apu.is_active[GB_NOISE] = true; update_sample(gb, GB_NOISE, 0, 0); } - + if (gb->apu.noise_channel.pulse_length == 0) { gb->apu.noise_channel.pulse_length = 0x40; gb->apu.noise_channel.length_enabled = false; } } - + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ if ((value & 0x40) && !gb->apu.noise_channel.length_enabled && @@ -956,7 +956,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.length_enabled = value & 0x40; break; } - + default: if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; diff --git a/Core/apu.h b/Core/apu.h index bfa35984..56a7369e 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -54,23 +54,23 @@ typedef struct { bool global_enable; uint8_t apu_cycles; - + uint8_t samples[GB_N_CHANNELS]; bool is_active[GB_N_CHANNELS]; - + uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided // once more to generate 128Hz and 64Hz clocks - + uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide // need to divide the signal. - + uint8_t square_sweep_countdown; // In 128Hz uint8_t square_sweep_calculate_countdown; // In 2 MHz uint16_t new_sweep_sample_legnth; uint16_t shadow_sweep_sample_legnth; bool sweep_enabled; bool sweep_decreasing; - + struct { uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks uint8_t current_volume; // Reloaded from NRX2 @@ -78,44 +78,44 @@ typedef struct uint8_t current_sample_index; /* For save state compatibility, highest bit is reused (See NR14/NR24's write code)*/ - + uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_length; // From NRX3, NRX4, in APU ticks bool length_enabled; // NRX4 } square_channels[2]; - + struct { bool enable; // NR30 uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks uint8_t shift; // NR32 uint16_t sample_length; // NR33, NR34, in APU ticks bool length_enabled; // NR34 - + uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint8_t current_sample_index; uint8_t current_sample; // Current sample before shifting. - + int8_t wave_form[32]; bool wave_form_just_read; } wave_channel; - + struct { uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks uint8_t current_volume; // Reloaded from NR42 uint8_t volume_countdown; // Reloaded from NR42 uint16_t lfsr; bool narrow; - + uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length) uint16_t sample_length; // From NR43, in APU ticks bool length_enabled; // NR44 - + uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of // 1MHz. This variable keeps track of the alignment. - + } noise_channel; - + bool skip_div_event; bool current_lfsr_sample; bool previous_lfsr_sample; @@ -130,25 +130,25 @@ typedef enum { typedef struct { unsigned sample_rate; - + GB_sample_t *buffer; size_t buffer_size; size_t buffer_position; - + bool stream_started; /* detects first copy request to minimize lag */ volatile bool copy_in_progress; volatile bool lock; - + double sample_cycles; // In 8 MHz units double cycles_per_sample; - + // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! unsigned cycles_since_render; unsigned last_update[GB_N_CHANNELS]; GB_sample_t current_sample[GB_N_CHANNELS]; GB_sample_t summed_samples[GB_N_CHANNELS]; double dac_discharge[GB_N_CHANNELS]; - + GB_highpass_mode_t highpass_mode; double highpass_rate; GB_double_sample_t highpass_diff; @@ -160,6 +160,7 @@ size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb); void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); #ifdef GB_INTERNAL +bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); void GB_apu_div_event(GB_gameboy_t *gb); diff --git a/Core/debugger.c b/Core/debugger.c index 7ea3a3f8..b1ceff8a 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -214,7 +214,7 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) return r; } return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); - + case LVALUE_MEMORY16: if (lvalue.memory_address.has_bank) { banking_state_t state; @@ -254,7 +254,7 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) } GB_write_memory(gb, lvalue.memory_address.value, value); return; - + case LVALUE_MEMORY16: if (lvalue.memory_address.has_bank) { banking_state_t state; @@ -288,7 +288,7 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) 25 bit address 16 bit value = 25 bit address 16 bit value 25 bit address = 25 bit address 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) - + Boolean operators always return a 16-bit value */ #define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) @@ -473,9 +473,9 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, /* Disable watchpoints while evaulating expressions */ uint16_t n_watchpoints = gb->n_watchpoints; gb->n_watchpoints = 0; - + value_t ret = ERROR; - + *error = false; // Strip whitespace while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { @@ -547,7 +547,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (string[i] == '}') depth--; } - + if (depth == 0) { value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); banking_state_t state; @@ -751,7 +751,7 @@ static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug print_usage(gb, command); return true; } - + gb->debug_stopped = false; gb->debug_next_command = true; gb->debug_call_depth = 0; @@ -791,7 +791,7 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifi { NO_MODIFIERS STOPPED_ONLY - + if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -854,7 +854,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const print_usage(gb, command); return true; } - + if (strlen(lstrip(arguments)) == 0) { print_usage(gb, command); return true; @@ -913,9 +913,9 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const gb->breakpoints[index].condition = NULL; } gb->n_breakpoints++; - + gb->breakpoints[index].is_jump_to = is_jump_to; - + if (is_jump_to) { gb->has_jump_to_breakpoints = true; } @@ -957,7 +957,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb index = i; } } - + if (index >= gb->n_breakpoints) { GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; @@ -965,7 +965,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb result.bank = gb->breakpoints[index].bank; result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1; - + if (gb->breakpoints[index].condition) { free(gb->breakpoints[index].condition); } @@ -980,7 +980,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb } } } - + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); gb->n_breakpoints--; gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); @@ -1140,12 +1140,12 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de index = i; } } - + if (index >= gb->n_watchpoints) { GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; } - + result.bank = gb->watchpoints[index].bank; result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1; @@ -1457,7 +1457,7 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const print_usage(gb, command); return true; } - + GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); for (unsigned int i = gb->backtrace_size; i--;) { GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); @@ -1532,7 +1532,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); - + GB_log(gb, "\nSTAT:\n"); static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]); @@ -1541,9 +1541,9 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled"); GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled"); GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); - - - + + + GB_log(gb, "\nCurrent line: %d\n", gb->current_line); GB_log(gb, "Current state: "); if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -1566,7 +1566,173 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); - + + return true; +} + +static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + + GB_log(gb, "Current state: "); + if(!gb->apu.global_enable) { + GB_log(gb, "Disabled\n"); + } + else { + GB_log(gb, "Enabled\n"); + for(uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { + GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1, + gb->apu.is_active[channel] ? "active " : "inactive", + GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive", + gb->apu.samples[channel]); + } + } + + GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07); + if(gb->io_registers[GB_IO_NR51] & 0x0f) { + for(uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if(gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } + } + } else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + + GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4); + if(gb->io_registers[GB_IO_NR51] & 0xf0) { + for(uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if(gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } + } + } else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + + + for(uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { + GB_log(gb, "\nCH%u:\n", channel + 1); + GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.square_channels[channel].current_volume, + gb->apu.square_channels[channel].sample_length, + gb->apu.square_channels[channel].sample_countdown); + + uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.square_channels[channel].volume_countdown, + nrx2 & 8 ? "in" : "de", + nrx2 & 7); + + uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; + GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n", + (char*[]){"12.5", " 25", " 50", " 75"}[duty], + (char*[]){"_______-", "-______-", "-____---", "_------_"}[duty], + gb->apu.square_channels[channel].current_sample_index & 0x7f, + gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); + + if(channel == GB_SQUARE_1) { + GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", + gb->apu.sweep_enabled? "active" : "inactive", + gb->apu.sweep_decreasing? "decreasing" : "increasing", + gb->apu.square_sweep_calculate_countdown); + } + + if(gb->apu.square_channels[channel].length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.square_channels[channel].pulse_length); + } + } + + + GB_log(gb, "\nCH3:\n"); + GB_log(gb, " Wave:"); + for(uint8_t i = 0; i < 32; i++) { + GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]); + } + GB_log(gb, ", has %sjust been read\n", gb->apu.wave_channel.wave_form_just_read? "": "not "); + GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); + + GB_log(gb, " Volume %s (right-shifted %ux)\n", + (char*[]){"100%", "50%", "25%", NULL, "muted"}[gb->apu.wave_channel.shift], + gb->apu.wave_channel.shift); + + GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.wave_channel.sample_length, + gb->apu.wave_channel.sample_countdown); + + if(gb->apu.wave_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.wave_channel.pulse_length); + } + + + GB_log(gb, "\nCH4:\n"); + GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.noise_channel.current_volume, + gb->apu.noise_channel.sample_length, + gb->apu.noise_channel.sample_countdown); + + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.noise_channel.volume_countdown, + gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de", + gb->io_registers[GB_IO_NR42] & 7); + + GB_log(gb, " LFSR in %u-step mode, current value %%", + gb->apu.noise_channel.narrow? 7 : 15); + for(uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { + GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " "); + } + + if(gb->apu.noise_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.noise_channel.pulse_length); + } + + + GB_log(gb, "\n\nReminder: APU ticks are @ 2 MiHz\n"); + + return true; +} + +static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) { + print_usage(gb, command); + return true; + } + + uint8_t shift_amount = 1, mask; + if(modifiers) { + switch(modifiers[0]) { + case 'c': + shift_amount = 2; + break; + case 'l': + shift_amount = 8; + break; + } + } + mask = (0xf << (shift_amount - 1)) & 0xf; + + for(int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { + for(uint8_t i = 0; i < 32; i++) { + if((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { + GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]); + } else { + GB_log(gb, "%c", i%4 == 2 ? '-' : ' '); + } + } + GB_log(gb, "\n"); + } + return true; } @@ -1587,6 +1753,10 @@ static const debugger_command_t commands[] = { {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ + {"apu", 3, apu, "Displays information about the current state of the audio chip"}, + {"wave", 3, wave, "Prints a visual representation of the wave RAM" HELP_NEWLINE + "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE + "a more (c)ompact one, or a one-(l)iner"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE @@ -1875,7 +2045,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add void GB_debugger_run(GB_gameboy_t *gb) { if (gb->debug_disable) return; - + char *input = NULL; if (gb->debug_next_command && gb->debug_call_depth <= 0) { gb->debug_stopped = true; @@ -1895,11 +2065,11 @@ next_command: GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_cpu_disassemble(gb, gb->pc, 5); } - + if (gb->breakpoints && !gb->debug_stopped) { uint16_t address = 0; jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address); - + bool should_delete_state = true; if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) { if (gb->non_trivial_jump_breakpoint_occured) { @@ -1930,7 +2100,7 @@ next_command: else { gb->non_trivial_jump_breakpoint_occured = false; } - + if (should_delete_state) { if (gb->nontrivial_jump_state) { free(gb->nontrivial_jump_state); @@ -2077,17 +2247,17 @@ static bool is_in_trivial_memory(uint16_t addr) if (addr < 0x8000) { return true; } - + /* HRAM */ if (addr >= 0xFF80 && addr < 0xFFFF) { return true; } - + /* RAM */ if (addr >= 0xC000 && addr < 0xE000) { return true; } - + return false; } @@ -2125,7 +2295,7 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) case 3: return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); } - + return false; } @@ -2134,7 +2304,7 @@ static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) if (!condition_code(gb, opcode)) { return gb->pc + 2; } - + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); } @@ -2221,12 +2391,12 @@ static GB_opcode_address_getter_t *opcodes[256] = { static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address) { if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; - + if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { return JUMP_TO_NONTRIVIAL; } - + /* Interrupts */ if (gb->ime) { for (unsigned i = 0; i < 5; i++) { @@ -2240,38 +2410,38 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add } } } - + uint16_t n_watchpoints = gb->n_watchpoints; gb->n_watchpoints = 0; - + uint8_t opcode = GB_read_memory(gb, gb->pc); - + if (opcode == 0x76) { gb->n_watchpoints = n_watchpoints; if (gb->ime) { /* Already handled in above */ return JUMP_TO_NONE; } - + if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) { return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */ } - + return JUMP_TO_NONE; } - + GB_opcode_address_getter_t *getter = opcodes[opcode]; if (!getter) { gb->n_watchpoints = n_watchpoints; return JUMP_TO_NONE; } - + uint16_t new_pc = getter(gb, opcode); - + gb->n_watchpoints = n_watchpoints; - + if (address) { *address = new_pc; } - + return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE; } From 91eeb4d9d542a3bfd0de59409026a0f6ef4b1637 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 May 2019 00:08:34 +0300 Subject: [PATCH 0838/1216] Emulate AGB audio mixing --- Core/apu.c | 76 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index a0b9aa11..231b23f0 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -23,6 +23,13 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index) { + if (gb->model >= GB_MODEL_AGB) { + /* On the AGB, mixing is done digitally, so there are no per-channel + DACs. Instead, all channels are summed digital regardless of + whatever the DAC state would be on a CGB or earlier model. */ + return true; + } + switch (index) { case GB_SQUARE_1: return gb->io_registers[GB_IO_NR12] & 0xF8; @@ -37,11 +44,45 @@ static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index) return gb->io_registers[GB_IO_NR42] & 0xF8; } - return 0; + return false; } static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) { + if (gb->model >= GB_MODEL_AGB) { + /* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different. + A channel that is not connected to a terminal is idenitcal to a connected channel + playing PCM sample 0. */ + gb->apu.samples[index] = value; + + if (gb->apu_output.sample_rate) { + unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; + unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + + GB_sample_t output; + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + output.right = (0xf - value * 2) * right_volume; + } + else { + output.right = 0xf * right_volume; + } + + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + output.left = (0xf - value * 2) * left_volume; + } + else { + output.left = 0xf * left_volume; + } + + if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { + refresh_channel(gb, index, cycles_offset); + gb->apu_output.current_sample[index] = output; + } + } + + return; + } + if (!is_DAC_enabled(gb, index)) { value = gb->apu.samples[index]; } @@ -73,23 +114,26 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) UNROLL for (unsigned i = 0; i < GB_N_CHANNELS; i++) { double multiplier = CH_STEP; - if (!is_DAC_enabled(gb, i)) { - gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; - if (gb->apu_output.dac_discharge[i] < 0) { - multiplier = 0; - gb->apu_output.dac_discharge[i] = 0; + + if (gb->model < GB_MODEL_AGB) { + if (!is_DAC_enabled(gb, i)) { + gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] < 0) { + multiplier = 0; + gb->apu_output.dac_discharge[i] = 0; + } + else { + multiplier *= gb->apu_output.dac_discharge[i]; + } } else { - multiplier *= gb->apu_output.dac_discharge[i]; - } - } - else { - gb->apu_output.dac_discharge[i] += ((double) DAC_ATTACK_SPEED) / gb->apu_output.sample_rate; - if (gb->apu_output.dac_discharge[i] > 1) { - gb->apu_output.dac_discharge[i] = 1; - } - else { - multiplier *= gb->apu_output.dac_discharge[i]; + gb->apu_output.dac_discharge[i] += ((double) DAC_ATTACK_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] > 1) { + gb->apu_output.dac_discharge[i] = 1; + } + else { + multiplier *= gb->apu_output.dac_discharge[i]; + } } } From 6648a0a84d659b1b6d85075856c80584da00e8ab Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 May 2019 20:48:49 +0300 Subject: [PATCH 0839/1216] Minor adjustments and style fixes to the new APU debug functions --- Core/debugger.c | 53 ++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index b1ceff8a..795e9fe1 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1580,12 +1580,12 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "Current state: "); - if(!gb->apu.global_enable) { + if (!gb->apu.global_enable) { GB_log(gb, "Disabled\n"); } else { GB_log(gb, "Enabled\n"); - for(uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { + for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1, gb->apu.is_active[channel] ? "active " : "inactive", GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive", @@ -1594,31 +1594,33 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07); - if(gb->io_registers[GB_IO_NR51] & 0x0f) { - for(uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { - if(gb->io_registers[GB_IO_NR51] & mask) { + if (gb->io_registers[GB_IO_NR51] & 0x0f) { + for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { GB_log(gb, " CH%u", channel + 1); } } - } else { + } + else { GB_log(gb, " no channels"); } GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4); - if(gb->io_registers[GB_IO_NR51] & 0xf0) { - for(uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { - if(gb->io_registers[GB_IO_NR51] & mask) { + if (gb->io_registers[GB_IO_NR51] & 0xf0) { + for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { GB_log(gb, " CH%u", channel + 1); } } - } else { + } + else { GB_log(gb, " no channels"); } GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); - for(uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { + for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { GB_log(gb, "\nCH%u:\n", channel + 1); GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", gb->apu.square_channels[channel].current_volume, @@ -1638,14 +1640,14 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg gb->apu.square_channels[channel].current_sample_index & 0x7f, gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); - if(channel == GB_SQUARE_1) { + if (channel == GB_SQUARE_1) { GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", gb->apu.sweep_enabled? "active" : "inactive", gb->apu.sweep_decreasing? "decreasing" : "increasing", gb->apu.square_sweep_calculate_countdown); } - if(gb->apu.square_channels[channel].length_enabled) { + if (gb->apu.square_channels[channel].length_enabled) { GB_log(gb, " Channel will end in %u 256 Hz ticks\n", gb->apu.square_channels[channel].pulse_length); } @@ -1654,13 +1656,13 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "\nCH3:\n"); GB_log(gb, " Wave:"); - for(uint8_t i = 0; i < 32; i++) { + for (uint8_t i = 0; i < 32; i++) { GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]); } - GB_log(gb, ", has %sjust been read\n", gb->apu.wave_channel.wave_form_just_read? "": "not "); + GB_log(gb, "\n"); GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); - GB_log(gb, " Volume %s (right-shifted %ux)\n", + GB_log(gb, " Volume %s (right-shifted %u times)\n", (char*[]){"100%", "50%", "25%", NULL, "muted"}[gb->apu.wave_channel.shift], gb->apu.wave_channel.shift); @@ -1668,7 +1670,7 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg gb->apu.wave_channel.sample_length, gb->apu.wave_channel.sample_countdown); - if(gb->apu.wave_channel.length_enabled) { + if (gb->apu.wave_channel.length_enabled) { GB_log(gb, " Channel will end in %u 256 Hz ticks\n", gb->apu.wave_channel.pulse_length); } @@ -1685,13 +1687,13 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de", gb->io_registers[GB_IO_NR42] & 7); - GB_log(gb, " LFSR in %u-step mode, current value %%", + GB_log(gb, " LFSR in %u-step mode, current value ", gb->apu.noise_channel.narrow? 7 : 15); - for(uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { + for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " "); } - if(gb->apu.noise_channel.length_enabled) { + if (gb->apu.noise_channel.length_enabled) { GB_log(gb, " Channel will end in %u 256 Hz ticks\n", gb->apu.noise_channel.pulse_length); } @@ -1710,7 +1712,7 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug } uint8_t shift_amount = 1, mask; - if(modifiers) { + if (modifiers) { switch(modifiers[0]) { case 'c': shift_amount = 2; @@ -1722,11 +1724,12 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug } mask = (0xf << (shift_amount - 1)) & 0xf; - for(int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { - for(uint8_t i = 0; i < 32; i++) { - if((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { + for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { + for (uint8_t i = 0; i < 32; i++) { + if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]); - } else { + } + else { GB_log(gb, "%c", i%4 == 2 ? '-' : ' '); } } From 3ee2c648996c6eb3a2fb32113b984215f54b7b02 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 May 2019 22:03:10 +0300 Subject: [PATCH 0840/1216] Make the apu command a bit safer --- Core/debugger.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 795e9fe1..c433f2f2 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1635,16 +1635,16 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n", - (char*[]){"12.5", " 25", " 50", " 75"}[duty], - (char*[]){"_______-", "-______-", "-____---", "_------_"}[duty], - gb->apu.square_channels[channel].current_sample_index & 0x7f, - gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); + duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty], + duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty], + gb->apu.square_channels[channel].current_sample_index & 0x7f, + gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); if (channel == GB_SQUARE_1) { GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", - gb->apu.sweep_enabled? "active" : "inactive", - gb->apu.sweep_decreasing? "decreasing" : "increasing", - gb->apu.square_sweep_calculate_countdown); + gb->apu.sweep_enabled? "active" : "inactive", + gb->apu.sweep_decreasing? "decreasing" : "increasing", + gb->apu.square_sweep_calculate_countdown); } if (gb->apu.square_channels[channel].length_enabled) { @@ -1663,8 +1663,8 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); GB_log(gb, " Volume %s (right-shifted %u times)\n", - (char*[]){"100%", "50%", "25%", NULL, "muted"}[gb->apu.wave_channel.shift], - gb->apu.wave_channel.shift); + gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift], + gb->apu.wave_channel.shift); GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", gb->apu.wave_channel.sample_length, From ec5d1b7b88452e2c0778cbe28802132fc65a5fc0 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Sat, 18 May 2019 02:46:24 +0200 Subject: [PATCH 0841/1216] Fix sample lengths for CH1, 2 and 4 --- Core/debugger.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index c433f2f2..79321a5c 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1623,9 +1623,9 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { GB_log(gb, "\nCH%u:\n", channel + 1); GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", - gb->apu.square_channels[channel].current_volume, - gb->apu.square_channels[channel].sample_length, - gb->apu.square_channels[channel].sample_countdown); + gb->apu.square_channels[channel].current_volume, + (gb->apu.square_channels[channel].sample_length ^ 0x7FF) * 2 + 1, + gb->apu.square_channels[channel].sample_countdown); uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", @@ -1667,7 +1667,7 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg gb->apu.wave_channel.shift); GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", - gb->apu.wave_channel.sample_length, + gb->apu.wave_channel.sample_length ^ 0x7ff, gb->apu.wave_channel.sample_countdown); if (gb->apu.wave_channel.length_enabled) { @@ -1679,7 +1679,7 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "\nCH4:\n"); GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", gb->apu.noise_channel.current_volume, - gb->apu.noise_channel.sample_length, + gb->apu.noise_channel.sample_length * 4 + 3, gb->apu.noise_channel.sample_countdown); GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", From 3e724afb0a5e42c8520dce054483be0982967733 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 May 2019 18:45:31 +0300 Subject: [PATCH 0842/1216] Basic SGB support in the SDL port --- Core/gb.c | 4 +- Core/gb.h | 4 +- SDL/gui.c | 94 ++++++++++++++++++++++++---------------- SDL/gui.h | 1 + SDL/main.c | 18 ++++++-- SDL/shader.c | 8 ++-- SDL/shader.h | 4 +- Shaders/MasterShader.fsh | 1 - 8 files changed, 84 insertions(+), 50 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 9456f106..cb99d4c8 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -798,12 +798,12 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb) return CPU_FREQUENCY * gb->clock_multiplier; } -size_t GB_get_screen_width(GB_gameboy_t *gb) +unsigned GB_get_screen_width(GB_gameboy_t *gb) { return GB_is_sgb(gb)? 256 : 160; } -size_t GB_get_screen_height(GB_gameboy_t *gb) +unsigned GB_get_screen_height(GB_gameboy_t *gb) { return GB_is_sgb(gb)? 224 : 144; } diff --git a/Core/gb.h b/Core/gb.h index a4b7256e..30654d52 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -689,8 +689,8 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb); #endif void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); -size_t GB_get_screen_width(GB_gameboy_t *gb); -size_t GB_get_screen_height(GB_gameboy_t *gb); +unsigned GB_get_screen_width(GB_gameboy_t *gb); +unsigned GB_get_screen_height(GB_gameboy_t *gb); unsigned GB_get_player_count(GB_gameboy_t *gb); diff --git a/SDL/gui.c b/SDL/gui.c index 4de76f51..80f15073 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -44,7 +44,9 @@ void render_texture(void *pixels, void *previous) } glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); - render_bitmap_with_shader(&shader, _pixels, previous, rect.x, rect.y, rect.w, rect.h); + render_bitmap_with_shader(&shader, _pixels, previous, + GB_get_screen_width(&gb), GB_get_screen_height(&gb), + rect.x, rect.y, rect.w, rect.h); SDL_GL_SwapWindow(window); } } @@ -116,8 +118,8 @@ void update_viewport(void) { int win_width, win_height; SDL_GL_GetDrawableSize(window, &win_width, &win_height); - double x_factor = win_width / 160.0; - double y_factor = win_height / 144.0; + double x_factor = win_width / (double) GB_get_screen_width(&gb); + double y_factor = win_height / (double) GB_get_screen_height(&gb); if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { x_factor = (int)(x_factor); @@ -133,8 +135,8 @@ void update_viewport(void) } } - unsigned new_width = x_factor * 160; - unsigned new_height = y_factor * 144; + unsigned new_width = x_factor * GB_get_screen_width(&gb); + unsigned new_height = y_factor * GB_get_screen_height(&gb); rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, new_width, new_height}; @@ -148,7 +150,7 @@ void update_viewport(void) } /* Does NOT check for bounds! */ -static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color) +static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color) { if (ch < ' ' || ch > font_max) { ch = '?'; @@ -163,11 +165,11 @@ static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color) } buffer++; } - buffer += 160 - GLYPH_WIDTH; + buffer += width - GLYPH_WIDTH; } } -static void draw_unbordered_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color) +static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color) { unsigned orig_x = x; while (*string) { @@ -178,23 +180,23 @@ static void draw_unbordered_text(uint32_t *buffer, unsigned x, unsigned y, const continue; } - if (x > 160 - GLYPH_WIDTH || y == 0 || y > 144 - GLYPH_HEIGHT) { + if (x > width - GLYPH_WIDTH || y == 0 || y > height - GLYPH_HEIGHT) { break; } - draw_char(&buffer[x + 160 * y], *string, color); + draw_char(&buffer[x + width * y], width, height, *string, color); x += GLYPH_WIDTH; string++; } } -static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border) +static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border) { - draw_unbordered_text(buffer, x - 1, y, string, border); - draw_unbordered_text(buffer, x + 1, y, string, border); - draw_unbordered_text(buffer, x, y - 1, string, border); - draw_unbordered_text(buffer, x, y + 1, string, border); - draw_unbordered_text(buffer, x, y, string, color); + draw_unbordered_text(buffer, width, height, x - 1, y, string, border); + draw_unbordered_text(buffer, width, height, x + 1, y, string, border); + draw_unbordered_text(buffer, width, height, x, y - 1, string, border); + draw_unbordered_text(buffer, width, height, x, y + 1, string, border); + draw_unbordered_text(buffer, width, height, x, y, string, color); } enum decoration { @@ -203,17 +205,17 @@ enum decoration { DECORATION_ARROWS, }; -static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration) +static void draw_text_centered(uint32_t *buffer, unsigned width, unsigned height, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration) { - unsigned x = 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2; - draw_text(buffer, x, y, string, color, border); + unsigned x = width / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2; + draw_text(buffer, width, height, x, y, string, color, border); switch (decoration) { case DECORATION_SELECTION: - draw_text(buffer, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border); + draw_text(buffer, width, height, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border); break; case DECORATION_ARROWS: - draw_text(buffer, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border); - draw_text(buffer, 160 - x, y, RIGHT_ARROW_STRING, color, border); + draw_text(buffer, width, height, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border); + draw_text(buffer, width, height, width - x, y, RIGHT_ARROW_STRING, color, border); break; case DECORATION_NONE: @@ -301,7 +303,7 @@ static void cycle_model_backwards(unsigned index) const char *current_model_string(unsigned index) { - return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance"} + return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance" , "Super Game Boy"} [configuration.model]; } @@ -764,7 +766,18 @@ void run_gui(bool is_running) } } - uint32_t pixels[160 * 144]; + unsigned width = GB_get_screen_width(&gb); + unsigned height = GB_get_screen_height(&gb); + unsigned x_offset = (width - 160) / 2; + unsigned y_offset = (height - 144) / 2; + uint32_t pixels[width * height]; + + if (width != 160 || height != 144) { + for (unsigned i = 0; i < width * height; i++) { + pixels[i] = gui_palette_native[0]; + } + } + SDL_Event event = {0,}; gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE; bool should_render = true; @@ -994,32 +1007,39 @@ void run_gui(bool is_running) if (should_render) { should_render = false; - memcpy(pixels, converted_background->pixels, sizeof(pixels)); + if (width == 160 && height == 144) { + memcpy(pixels, converted_background->pixels, sizeof(pixels)); + } + else { + for (unsigned y = 0; y < 144; y++) { + memcpy(pixels + x_offset + width * (y + y_offset), ((uint32_t *)converted_background->pixels) + 160 * y, 160 * 4); + } + } switch (gui_state) { case SHOWING_DROP_MESSAGE: - draw_text_centered(pixels, 8, "Press ESC for menu", gui_palette_native[3], gui_palette_native[0], false); - draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false); - draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, width, height, 8 + y_offset, "Press ESC for menu", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, width, height, 116 + y_offset, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, width, height, 128 + y_offset, "file to play", gui_palette_native[3], gui_palette_native[0], false); break; case SHOWING_MENU: - draw_text_centered(pixels, 8, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, width, height, 8 + y_offset, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); unsigned i = 0, y = 24; for (const struct menu_item *item = current_menu; item->string; item++, i++) { if (item->value_getter && !item->backwards_handler) { char line[25]; snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (int)strlen(item->string), item->value_getter(i)); - draw_text_centered(pixels, y, line, gui_palette_native[3], gui_palette_native[0], + draw_text_centered(pixels, width, height, y + y_offset, line, gui_palette_native[3], gui_palette_native[0], i == current_selection ? DECORATION_SELECTION : DECORATION_NONE); y += 12; } else { - draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0], + draw_text_centered(pixels, width, height, y + y_offset, item->string, gui_palette_native[3], gui_palette_native[0], i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE); y += 12; if (item->value_getter) { - draw_text_centered(pixels, y, item->value_getter(i), gui_palette_native[3], gui_palette_native[0], + draw_text_centered(pixels, width, height, y + y_offset, item->value_getter(i), gui_palette_native[3], gui_palette_native[0], i == current_selection ? DECORATION_ARROWS : DECORATION_NONE); y += 12; } @@ -1027,16 +1047,16 @@ void run_gui(bool is_running) } break; case SHOWING_HELP: - draw_text(pixels, 2, 2, help[current_help_page], gui_palette_native[3], gui_palette_native[0]); + draw_text(pixels, width, height, 2 + x_offset, 2 + y_offset, help[current_help_page], gui_palette_native[3], gui_palette_native[0]); break; case WAITING_FOR_KEY: - draw_text_centered(pixels, 68, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + draw_text_centered(pixels, width, height, 68 + y_offset, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); break; case WAITING_FOR_JBUTTON: - draw_text_centered(pixels, 68, + draw_text_centered(pixels, width, height, 68 + y_offset, joypad_configuration_progress != JOYPAD_BUTTONS_MAX ? "Press button for" : "Move the Analog Stick", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); - draw_text_centered(pixels, 80, + draw_text_centered(pixels, width, height, 80 + y_offset, (const char *[]) { "Right", @@ -1054,7 +1074,7 @@ void run_gui(bool is_running) "", } [joypad_configuration_progress], gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); - draw_text_centered(pixels, 104, "Press Enter to skip", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + draw_text_centered(pixels, width, height, 104 + y_offset, "Press Enter to skip", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); break; } diff --git a/SDL/gui.h b/SDL/gui.h index 4d106143..9711e832 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -77,6 +77,7 @@ typedef struct { MODEL_DMG, MODEL_CGB, MODEL_AGB, + MODEL_SGB, MODEL_MAX, } model; diff --git a/SDL/main.c b/SDL/main.c index 99facf8d..8a65a742 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -24,7 +24,7 @@ GB_gameboy_t gb; static bool paused = false; -static uint32_t pixel_buffer_1[160*144], pixel_buffer_2[160*144]; +static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; static double clock_mutliplier = 1.0; @@ -39,7 +39,8 @@ static const GB_model_t sdl_to_internal_model[] = { [MODEL_DMG] = GB_MODEL_DMG_B, [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB + [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_SGB] = GB_MODEL_SGB, }; void set_filename(const char *new_filename, bool new_should_free) @@ -423,9 +424,16 @@ restart: GB_set_rewind_length(&gb, configuration.rewind_length); } + SDL_DestroyTexture(texture); + texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, + GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + + SDL_SetWindowMinimumSize(window, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + + bool error = false; start_capturing_logs(); - const char * const boot_roms[] = {"dmg_boot.bin", "cgb_boot.bin", "agb_boot.bin"}; + const char * const boot_roms[] = {"dmg_boot.bin", "cgb_boot.bin", "agb_boot.bin", "sgb_boot.bin"}; error = GB_load_boot_rom(&gb, resource_path(boot_roms[configuration.model])); end_capturing_logs(true, error); @@ -447,7 +455,9 @@ restart: char symbols_path[path_length + 5]; replace_extension(filename, path_length, symbols_path, ".sym"); GB_debugger_load_symbol_file(&gb, symbols_path); - + + update_viewport(); + /* Run emulation */ while (true) { if (paused || rewind_paused) { diff --git a/SDL/shader.c b/SDL/shader.c index ed45c42f..37e5be7e 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -162,20 +162,22 @@ bool init_shader_with_name(shader_t *shader, const char *name) return true; } -void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,unsigned x, unsigned y, unsigned w, unsigned h) +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h) { glUseProgram(shader->program); glUniform2f(shader->origin_uniform, x, y); glUniform2f(shader->resolution_uniform, w, h); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, shader->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); glUniform1i(shader->texture_uniform, 0); glUniform1i(shader->mix_previous_uniform, previous != NULL); if (previous) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, shader->previous_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); glUniform1i(shader->previous_texture_uniform, 1); } glBindFragDataLocation(shader->program, 0, "frag_color"); diff --git a/SDL/shader.h b/SDL/shader.h index 20baf765..3a1c3040 100644 --- a/SDL/shader.h +++ b/SDL/shader.h @@ -17,7 +17,9 @@ typedef struct shader_s { } shader_t; bool init_shader_with_name(shader_t *shader, const char *name); -void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,unsigned x, unsigned y, unsigned w, unsigned h); +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h); void free_shader(struct shader_s *shader); #endif /* shader_h */ diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index a489cf76..cd569c2d 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -5,7 +5,6 @@ uniform bool mix_previous; uniform vec2 output_resolution; uniform vec2 origin; -const vec2 input_resolution = vec2(160, 144); #define equal(x, y) ((x) == (y)) #define inequal(x, y) ((x) != (y)) From e12e03d9c2c475288336cd1b14094603d61e84e0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 May 2019 20:37:41 +0300 Subject: [PATCH 0843/1216] SGB revision selection in the SDL port --- SDL/gui.c | 26 ++++++++++++++++++++++++++ SDL/gui.h | 8 ++++++++ SDL/main.c | 32 +++++++++++++++++++++----------- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 80f15073..c515273e 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -307,6 +307,31 @@ const char *current_model_string(unsigned index) [configuration.model]; } +static void cycle_sgb_revision(unsigned index) +{ + + configuration.sgb_revision++; + if (configuration.sgb_revision == SGB_MAX) { + configuration.sgb_revision = 0; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static void cycle_sgb_revision_backwards(unsigned index) +{ + if (configuration.sgb_revision == 0) { + configuration.sgb_revision = SGB_MAX; + } + configuration.sgb_revision--; + pending_command = GB_SDL_RESET_COMMAND; +} + +const char *current_sgb_revision_string(unsigned index) +{ + return (const char *[]){"Super Game Boy NTSC", "Super Game Boy PAL", "Super Game Boy 2"} + [configuration.sgb_revision]; +} + static const uint32_t rewind_lengths[] = {0, 10, 30, 60, 60 * 2, 60 * 5, 60 * 10}; static const char *rewind_strings[] = {"Disabled", "10 Seconds", @@ -355,6 +380,7 @@ const char *current_rewind_string(unsigned index) static const struct menu_item emulation_menu[] = { {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, + {"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards}, {"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards}, {"Back", return_to_root_menu}, {NULL,} diff --git a/SDL/gui.h b/SDL/gui.h index 9711e832..de7ddaa6 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -86,6 +86,14 @@ typedef struct { SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */ uint8_t joypad_configuration[32]; /* 12 Keys + padding for the future*/; uint8_t joypad_axises[JOYPAD_AXISES_MAX]; + + /* v0.12 */ + enum { + SGB_NTSC, + SGB_PAL, + SGB_2, + SGB_MAX + } sgb_revision; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 8a65a742..6506a3e6 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -35,14 +35,6 @@ static char *battery_save_path_ptr; SDL_AudioDeviceID device_id; -static const GB_model_t sdl_to_internal_model[] = -{ - [MODEL_DMG] = GB_MODEL_DMG_B, - [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB, - [MODEL_SGB] = GB_MODEL_SGB, -}; - void set_filename(const char *new_filename, bool new_should_free) { if (filename && should_free_filename) { @@ -407,13 +399,27 @@ static bool handle_pending_command(void) static void run(void) { + GB_model_t model; pending_command = GB_SDL_NO_COMMAND; restart: + model = (GB_model_t []) + { + [MODEL_DMG] = GB_MODEL_DMG_B, + [MODEL_CGB] = GB_MODEL_CGB_E, + [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_SGB] = (GB_model_t []) + { + [SGB_NTSC] = GB_MODEL_SGB_NTSC, + [SGB_PAL] = GB_MODEL_SGB_PAL, + [SGB_2] = GB_MODEL_SGB2, + }[configuration.sgb_revision], + }[configuration.model]; + if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, sdl_to_internal_model[configuration.model]); + GB_switch_model_and_reset(&gb, model); } else { - GB_init(&gb, sdl_to_internal_model[configuration.model]); + GB_init(&gb, model); GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_pixels_output(&gb, active_pixel_buffer); @@ -434,7 +440,11 @@ restart: bool error = false; start_capturing_logs(); const char * const boot_roms[] = {"dmg_boot.bin", "cgb_boot.bin", "agb_boot.bin", "sgb_boot.bin"}; - error = GB_load_boot_rom(&gb, resource_path(boot_roms[configuration.model])); + const char *boot_rom = boot_roms[configuration.model]; + if (configuration.model == GB_MODEL_SGB && configuration.sgb_revision == SGB_2) { + boot_rom = "sgb2_boot.bin"; + } + error = GB_load_boot_rom(&gb, resource_path(boot_rom)); end_capturing_logs(true, error); start_capturing_logs(); From c29b5b58007288366b1f2d80d34ab2215e7c75e4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 May 2019 20:38:10 +0300 Subject: [PATCH 0844/1216] Fixed the CRT shader for OpenGL frontends (SDL and older Macs) --- Shaders/CRT.fsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shaders/CRT.fsh b/Shaders/CRT.fsh index cbb15283..86844515 100644 --- a/Shaders/CRT.fsh +++ b/Shaders/CRT.fsh @@ -55,7 +55,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou right *= scanline_multiplier; /* Vertical seperator for shadow masks */ - bool odd = (int)(position * input_resolution).x & 1; + bool odd = bool(int((position * input_resolution).x) & 1); if (odd) { pos.y += 0.5; pos.y = fract(pos.y); From 85c43fa81f3c8d3258475da30632918d6ad29d9f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 May 2019 19:12:09 +0300 Subject: [PATCH 0845/1216] =?UTF-8?q?Fixed=20Channel=203=E2=80=99s=20first?= =?UTF-8?q?=20sample=20behavior,=20update=20analog=20characteristic=20to?= =?UTF-8?q?=20more=20realistic=20values.=20Fixes=20#177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 24 +++++++++++++----------- Core/apu.h | 8 ++------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index af90d81f..b4f4d752 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -107,6 +107,11 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign } } +static double smooth(double x) +{ + return 3*x*x - 2*x*x*x; +} + static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) { GB_sample_t output = {0,0}; @@ -123,7 +128,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) gb->apu_output.dac_discharge[i] = 0; } else { - multiplier *= gb->apu_output.dac_discharge[i]; + multiplier *= smooth(gb->apu_output.dac_discharge[i]); } } else { @@ -132,7 +137,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) gb->apu_output.dac_discharge[i] = 1; } else { - multiplier *= gb->apu_output.dac_discharge[i]; + multiplier *= smooth(gb->apu_output.dac_discharge[i]); } } } @@ -350,7 +355,6 @@ void GB_apu_div_event(GB_gameboy_t *gb) if (gb->apu.wave_channel.pulse_length) { if (!--gb->apu.wave_channel.pulse_length) { gb->apu.is_active[GB_WAVE] = false; - gb->apu.wave_channel.current_sample = 0; update_sample(gb, GB_WAVE, 0, 0); } } @@ -806,7 +810,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.enable = value & 0x80; if (!gb->apu.wave_channel.enable) { gb->apu.is_active[GB_WAVE] = false; - gb->apu.wave_channel.current_sample = 0; update_sample(gb, GB_WAVE, 0, 0); } break; @@ -815,7 +818,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) break; case GB_IO_NR32: gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; - update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); + if (gb->apu.is_active[GB_WAVE]) { + update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); + } break; case GB_IO_NR33: gb->apu.wave_channel.sample_length &= ~0xFF; @@ -856,7 +861,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (!gb->apu.is_active[GB_WAVE]) { gb->apu.is_active[GB_WAVE] = true; - update_sample(gb, GB_WAVE, 0, 0); + update_sample(gb, GB_WAVE, + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + 0); } gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; gb->apu.wave_channel.current_sample_index = 0; @@ -865,11 +872,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.length_enabled = false; } /* Note that we don't change the sample just yet! This was verified on hardware. */ - /* Todo: The first sample might *not* beskipped on the DMG, this could be a bug - introduced on the CGB. It appears that the bug was fixed on the AGB, but it's - not reflected by PCM34. This should be probably verified as this could just - mean differences in the DACs. */ - /* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */ } /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ diff --git a/Core/apu.h b/Core/apu.h index 56a7369e..2a49c048 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -8,12 +8,8 @@ #ifdef GB_INTERNAL /* Speed = 1 / Length (in seconds) */ -/* Todo: Measure these and find the actual curve shapes. - They are known to be incorrect (Some analog test ROM sound different), - but are good enough approximations to fix Cannon Fodder's terrible audio. - It also varies by model. */ -#define DAC_DECAY_SPEED 50000 -#define DAC_ATTACK_SPEED 1000 +#define DAC_DECAY_SPEED 20000 +#define DAC_ATTACK_SPEED 20000 /* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ From 54c353830fa3528a8a3b12667a744ebdaf5f0763 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 31 May 2019 18:33:51 +0300 Subject: [PATCH 0846/1216] SDL GUI mouse support --- SDL/gui.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index c515273e..bad36f28 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -25,7 +25,8 @@ unsigned command_parameter; #endif shader_t shader; -SDL_Rect rect; +static SDL_Rect rect; +static unsigned factor; void render_texture(void *pixels, void *previous) { @@ -118,6 +119,10 @@ void update_viewport(void) { int win_width, win_height; SDL_GL_GetDrawableSize(window, &win_width, &win_height); + int logical_width, logical_height; + SDL_GetWindowSize(window, &logical_width, &logical_height); + factor = win_width / logical_width; + double x_factor = win_width / (double) GB_get_screen_width(&gb); double y_factor = win_height / (double) GB_get_screen_height(&gb); @@ -810,9 +815,60 @@ void run_gui(bool is_running) current_menu = root_menu = is_running? paused_menu : nonpaused_menu; current_selection = 0; do { - /* Convert Joypad events (We only generate down events) */ + /* Convert Joypad and mouse events (We only generate down events) */ if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) { switch (event.type) { + case SDL_MOUSEBUTTONDOWN: + if (gui_state == SHOWING_HELP) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_RETURN; + } + else if (gui_state == SHOWING_DROP_MESSAGE) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + } + else if (gui_state == SHOWING_MENU) { + signed x = (event.button.x - rect.x / factor) * 160 / (rect.w / factor) - x_offset; + signed y = (event.button.y - rect.y / factor) * 144 / (rect.h / factor) - y_offset; + + if (strcmp("CRT", configuration.filter) == 0) { + y = y * 8 / 7; + y -= 144 / 16; + } + + if (x < 0 || x >= 160 || y < 24) { + continue; + } + + unsigned item_y = 24; + unsigned index = 0; + for (const struct menu_item *item = current_menu; item->string; item++, index++) { + if (!item->backwards_handler) { + if (y >= item_y && y < item_y + 12) { + break; + } + item_y += 12; + } + else { + if (y >= item_y && y < item_y + 24) { + break; + } + item_y += 24; + } + } + + if (!current_menu[index].string) continue; + + current_selection = index; + event.type = SDL_KEYDOWN; + if (current_menu[index].backwards_handler) { + event.key.keysym.scancode = x < 80? SDL_SCANCODE_LEFT : SDL_SCANCODE_RIGHT; + } + else { + event.key.keysym.scancode = SDL_SCANCODE_RETURN; + } + + } case SDL_JOYBUTTONDOWN: event.type = SDL_KEYDOWN; joypad_button_t button = get_joypad_button(event.jbutton.button); From f9cc7a3b4662a5a8b4eff848aaec33d4c1786af0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 31 May 2019 18:50:02 +0300 Subject: [PATCH 0847/1216] Fix SDL mouse support in SGB mode --- SDL/gui.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index bad36f28..39d73571 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -828,8 +828,8 @@ void run_gui(bool is_running) event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; } else if (gui_state == SHOWING_MENU) { - signed x = (event.button.x - rect.x / factor) * 160 / (rect.w / factor) - x_offset; - signed y = (event.button.y - rect.y / factor) * 144 / (rect.h / factor) - y_offset; + signed x = (event.button.x - rect.x / factor) * width / (rect.w / factor) - x_offset; + signed y = (event.button.y - rect.y / factor) * height / (rect.h / factor) - y_offset; if (strcmp("CRT", configuration.filter) == 0) { y = y * 8 / 7; From cdc36f329e5867fa59a99eb7658c9d0bb3c7ff7c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Jun 2019 14:29:46 +0300 Subject: [PATCH 0848/1216] Added open dialog to the SDL GUI, misc fixes --- BootROMs/sgb_boot.asm | 16 +++---- Core/sm83_cpu.c | 2 +- Makefile | 19 ++++++-- OpenDialog/cocoa.m | 20 +++++++++ OpenDialog/gtk.c | 96 ++++++++++++++++++++++++++++++++++++++++ OpenDialog/open_dialog.h | 6 +++ OpenDialog/windows.c | 28 ++++++++++++ SDL/gui.c | 34 +++++++++++--- SDL/gui.h | 6 +++ SDL/main.c | 29 +++++++----- 10 files changed, 227 insertions(+), 29 deletions(-) create mode 100644 OpenDialog/cocoa.m create mode 100644 OpenDialog/gtk.c create mode 100644 OpenDialog/open_dialog.h create mode 100644 OpenDialog/windows.c diff --git a/BootROMs/sgb_boot.asm b/BootROMs/sgb_boot.asm index 0574d29c..108af18b 100644 --- a/BootROMs/sgb_boot.asm +++ b/BootROMs/sgb_boot.asm @@ -82,9 +82,9 @@ Start: .sendCommand xor a - ldh [c], a + ld [c], a ld a, $30 - ldh [c], a + ld [c], a ldh a, [$80] call SendByte @@ -112,9 +112,9 @@ Start: ; Done bit ld a, $20 - ldh [c], a + ld [c], a ld a, $30 - ldh [c], a + ld [c], a ; Update command ldh a, [$80] @@ -128,10 +128,10 @@ Start: ; Write to sound registers for DMG compatibility ld c, $13 ld a, $c1 - ldh [c], a + ld [c], a inc c ld a, 7 - ldh [c], a + ld [c], a ; Init BG palette ld a, $fc @@ -168,9 +168,9 @@ SendByte: jr c, .zeroBit add a ; 10 -> 20 .zeroBit - ldh [c], a + ld [c], a ld a, $30 - ldh [c], a + ld [c], a dec d ret z jr .loop diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index fdf357ce..1416000c 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -606,7 +606,7 @@ static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) cycle_write(gb, gb->registers[GB_REGISTER_HL], data); } -uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) +static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) { uint8_t src_register_id; uint8_t src_low; diff --git a/Makefile b/Makefile index a2df8bf8..38b3e832 100644 --- a/Makefile +++ b/Makefile @@ -65,14 +65,25 @@ endif # Set compilation and linkage flags based on target, platform and configuration +OPEN_DIALOG = OpenDialog/gtk.c + +ifeq ($(PLATFORM),windows32) +OPEN_DIALOG = OpenDialog/windows.c +endif + +ifeq ($(PLATFORM),Darwin) +OPEN_DIALOG = OpenDialog/cocoa.m +endif + + CFLAGS += -Werror -Wall -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) -CFLAGS += -IWindows -LDFLAGS += -lmsvcrt -lSDL2main -Wl,/MANIFESTFILE:NUL +CFLAGS += -IWindows -Drandom=rand +LDFLAGS += -lmsvcrt -lcomdlg32 -lSDL2main -Wl,/MANIFESTFILE:NUL SDL_LDFLAGS := -lSDL2 -lopengl32 else -LDFLAGS += -lc -lm +LDFLAGS += -lc -lm -ldl endif ifeq ($(PLATFORM),Darwin) @@ -120,7 +131,7 @@ all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets CORE_SOURCES := $(shell ls Core/*.c) -SDL_SOURCES := $(shell ls SDL/*.c) +SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) TESTER_SOURCES := $(shell ls Tester/*.c) ifeq ($(PLATFORM),Darwin) diff --git a/OpenDialog/cocoa.m b/OpenDialog/cocoa.m new file mode 100644 index 00000000..29a722cb --- /dev/null +++ b/OpenDialog/cocoa.m @@ -0,0 +1,20 @@ +#import +#include "open_dialog.h" + + +char *do_open_rom_dialog(void) +{ + @autoreleasepool { + NSWindow *key = [NSApp keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + dialog.title = @"Open ROM"; + dialog.allowedFileTypes = @[@"gb", @"gbc", @"sgb"]; + [dialog runModal]; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[[dialog URLs] firstObject] path]; + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} diff --git a/OpenDialog/gtk.c b/OpenDialog/gtk.c new file mode 100644 index 00000000..2c3bfe76 --- /dev/null +++ b/OpenDialog/gtk.c @@ -0,0 +1,96 @@ +#include "open_dialog.h" +#include +#include +#include +#include +#include + +#define GTK_FILE_CHOOSER_ACTION_OPEN 0 + + +void *_gtk_file_chooser_dialog_new (const char *title, + void *parent, + int action, + const char *first_button_text, + ...); +bool _gtk_init_check (int *argc, char ***argv); +int _gtk_dialog_run(void *); +void _g_free(void *); +void _g_object_unref(void *); +char *_gtk_file_chooser_get_filename(void *); +void _g_log_set_default_handler (void *function, void *data); +void *_gtk_file_filter_new(void); +void _gtk_file_filter_add_pattern(void *filter, const char *pattern); +void _gtk_file_filter_set_name(void *filter, const char *name); +void _gtk_file_chooser_add_filter(void *dialog, void *filter); + +#define LAZY(symbol) static typeof(_##symbol) *symbol = NULL;\ +if (symbol == NULL) symbol = dlsym(handle, #symbol);\ +if (symbol == NULL) goto lazy_error +#define TRY_DLOPEN(name) handle = handle? handle : dlopen(name, RTLD_NOW) + +void nop(){} + +char *do_open_rom_dialog(void) +{ + static void *handle = NULL; + + TRY_DLOPEN("libgtk-3.so"); + TRY_DLOPEN("libgtk-3.so.0"); + TRY_DLOPEN("libgtk-2.so"); + TRY_DLOPEN("libgtk-2.so.0"); + + if (!handle) { + goto lazy_error; + } + + + LAZY(gtk_init_check); + LAZY(gtk_file_chooser_dialog_new); + LAZY(gtk_dialog_run); + LAZY(g_free); + LAZY(g_object_unref); + LAZY(gtk_file_chooser_get_filename); + LAZY(g_log_set_default_handler); + LAZY(gtk_file_filter_new); + LAZY(gtk_file_filter_add_pattern); + LAZY(gtk_file_filter_set_name); + LAZY(gtk_file_chooser_add_filter); + + /* Shut up GTK */ + g_log_set_default_handler(nop, NULL); + + gtk_init_check(0, 0); + + + void *dialog = gtk_file_chooser_dialog_new("Open ROM", + 0, + GTK_FILE_CHOOSER_ACTION_OPEN, + "Open", 0, NULL); + + + void *filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.gb"); + gtk_file_filter_add_pattern(filter, "*.gbc"); + gtk_file_filter_add_pattern(filter, "*.sgb"); + gtk_file_filter_set_name(filter, "Game Boy ROMs"); + gtk_file_chooser_add_filter(dialog, filter); + + int res = gtk_dialog_run (dialog); + char *ret = NULL; + + if (res == 0) + { + char *filename; + filename = gtk_file_chooser_get_filename(dialog); + ret = strdup(filename); + g_free(filename); + } + + g_object_unref(dialog); + return ret; + +lazy_error: + fprintf(stderr, "Failed to display GTK dialog\n"); + return NULL; +} diff --git a/OpenDialog/open_dialog.h b/OpenDialog/open_dialog.h new file mode 100644 index 00000000..85e5721f --- /dev/null +++ b/OpenDialog/open_dialog.h @@ -0,0 +1,6 @@ +#ifndef open_rom_h +#define open_rom_h + +char *do_open_rom_dialog(void); + +#endif /* open_rom_h */ diff --git a/OpenDialog/windows.c b/OpenDialog/windows.c new file mode 100644 index 00000000..75fe7674 --- /dev/null +++ b/OpenDialog/windows.c @@ -0,0 +1,28 @@ +#include +#include "open_dialog.h" + +char *do_open_rom_dialog(void) +{ + OPENFILENAMEW dialog; + wchar_t filename[MAX_PATH] = {0}; + + memset(&dialog, 0, sizeof(dialog)); + dialog.lStructSize = sizeof(dialog); + dialog.lpstrFile = filename; + dialog.nMaxFile = sizeof(filename); + dialog.lpstrFilter = L"Game Boy ROMs\0*.gb;*.gbc;*.sgb\0All files\0*.*\0\0"; + dialog.nFilterIndex = 1; + dialog.lpstrFileTitle = NULL; + dialog.nMaxFileTitle = 0; + dialog.lpstrInitialDir = NULL; + dialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + if (GetOpenFileNameW(&dialog) == TRUE) + { + char *ret = malloc(MAX_PATH * 4); + WideCharToMultiByte(CP_UTF8, 0, filename, sizeof(filename), ret, MAX_PATH * 4, NULL, NULL); + return ret; + } + + return NULL; +} diff --git a/SDL/gui.c b/SDL/gui.c index 39d73571..77167602 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -97,11 +98,11 @@ configuration_t configuration = static const char *help[] ={ -"Drop a GB or GBC ROM\n" -"file to play.\n" +"Drop a ROM to play.\n" "\n" "Keyboard Shortcuts:\n" " Open Menu: Escape\n" +" Open ROM: " MODIFIER_NAME "+O\n" " Reset: " MODIFIER_NAME "+R\n" " Pause: " MODIFIER_NAME "+P\n" " Save state: " MODIFIER_NAME "+(0-9)\n" @@ -267,8 +268,19 @@ static void enter_controls_menu(unsigned index); static void enter_joypad_menu(unsigned index); static void enter_audio_menu(unsigned index); +extern void set_filename(const char *new_filename, typeof(free) *new_free_function); +static void open_rom(unsigned index) +{ + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } +} + static const struct menu_item paused_menu[] = { {"Resume", NULL}, + {"Open ROM", open_rom}, {"Emulation Options", enter_emulation_menu}, {"Graphic Options", enter_graphics_menu}, {"Audio Options", enter_audio_menu}, @@ -778,7 +790,6 @@ void connect_joypad(void) } } -extern void set_filename(const char *new_filename, bool new_should_free); void run_gui(bool is_running) { connect_joypad(); @@ -818,6 +829,9 @@ void run_gui(bool is_running) /* Convert Joypad and mouse events (We only generate down events) */ if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) { switch (event.type) { + case SDL_WINDOWEVENT: + should_render = true; + break; case SDL_MOUSEBUTTONDOWN: if (gui_state == SHOWING_HELP) { event.type = SDL_KEYDOWN; @@ -957,7 +971,7 @@ void run_gui(bool is_running) break; } case SDL_DROPFILE: { - set_filename(event.drop.file, true); + set_filename(event.drop.file, SDL_free); pending_command = GB_SDL_NEW_FILE_COMMAND; return; } @@ -995,7 +1009,17 @@ void run_gui(bool is_running) } case SDL_KEYDOWN: - if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && gui_state == WAITING_FOR_JBUTTON) { + if (event.key.keysym.scancode == SDL_SCANCODE_O) { + if (event.key.keysym.mod & MODIFIER) { + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + return; + } + } + } + else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && gui_state == WAITING_FOR_JBUTTON) { should_render = true; if (joypad_configuration_progress != JOYPAD_BUTTONS_MAX) { configuration.joypad_configuration[joypad_configuration_progress] = -1; diff --git a/SDL/gui.h b/SDL/gui.h index de7ddaa6..6974849a 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -9,6 +9,12 @@ #define JOYSTICK_HIGH 0x4000 #define JOYSTICK_LOW 0x3800 +#ifdef __APPLE__ +#define MODIFIER KMOD_GUI +#else +#define MODIFIER KMOD_CTRL +#endif + extern GB_gameboy_t gb; extern SDL_Window *window; diff --git a/SDL/main.c b/SDL/main.c index 6506a3e6..451ac9c2 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include "utils.h" @@ -30,18 +31,18 @@ static bool underclock_down = false, rewind_down = false, do_rewind = false, rew static double clock_mutliplier = 1.0; static char *filename = NULL; -static bool should_free_filename = false; +static typeof(free) *free_function = NULL; static char *battery_save_path_ptr; SDL_AudioDeviceID device_id; -void set_filename(const char *new_filename, bool new_should_free) +void set_filename(const char *new_filename, typeof(free) *new_free_function) { - if (filename && should_free_filename) { - SDL_free(filename); + if (filename && free_function) { + free_function(filename); } filename = (char *) new_filename; - should_free_filename = new_should_free; + free_function = new_free_function; } static SDL_AudioSpec want_aspec, have_aspec; @@ -101,11 +102,6 @@ static void open_menu(void) static void handle_events(GB_gameboy_t *gb) { -#ifdef __APPLE__ -#define MODIFIER KMOD_GUI -#else -#define MODIFIER KMOD_CTRL -#endif SDL_Event event; while (SDL_PollEvent(&event)) { @@ -115,7 +111,7 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_DROPFILE: { - set_filename(event.drop.file, true); + set_filename(event.drop.file, SDL_free); pending_command = GB_SDL_NEW_FILE_COMMAND; break; } @@ -226,6 +222,17 @@ static void handle_events(GB_gameboy_t *gb) pending_command = GB_SDL_RESET_COMMAND; } break; + + case SDL_SCANCODE_O: { + if (event.key.keysym.mod & MODIFIER) { + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } + } + break; + } case SDL_SCANCODE_P: if (event.key.keysym.mod & MODIFIER) { From 9acb4636db0f6fc6ba9a778073b601a83ed000d7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Jun 2019 16:19:44 +0300 Subject: [PATCH 0849/1216] Fix various GTK bugs --- OpenDialog/gtk.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/OpenDialog/gtk.c b/OpenDialog/gtk.c index 2c3bfe76..7947ea2e 100644 --- a/OpenDialog/gtk.c +++ b/OpenDialog/gtk.c @@ -6,6 +6,8 @@ #include #define GTK_FILE_CHOOSER_ACTION_OPEN 0 +#define GTK_RESPONSE_ACCEPT -3 +#define GTK_RESPONSE_CANCEL -6 void *_gtk_file_chooser_dialog_new (const char *title, @@ -16,13 +18,16 @@ void *_gtk_file_chooser_dialog_new (const char *title, bool _gtk_init_check (int *argc, char ***argv); int _gtk_dialog_run(void *); void _g_free(void *); -void _g_object_unref(void *); +void _gtk_widget_destroy(void *); char *_gtk_file_chooser_get_filename(void *); void _g_log_set_default_handler (void *function, void *data); void *_gtk_file_filter_new(void); void _gtk_file_filter_add_pattern(void *filter, const char *pattern); void _gtk_file_filter_set_name(void *filter, const char *name); void _gtk_file_chooser_add_filter(void *dialog, void *filter); +void _gtk_main_iteration(void); +bool _gtk_events_pending(void); + #define LAZY(symbol) static typeof(_##symbol) *symbol = NULL;\ if (symbol == NULL) symbol = dlsym(handle, #symbol);\ @@ -49,13 +54,15 @@ char *do_open_rom_dialog(void) LAZY(gtk_file_chooser_dialog_new); LAZY(gtk_dialog_run); LAZY(g_free); - LAZY(g_object_unref); + LAZY(gtk_widget_destroy); LAZY(gtk_file_chooser_get_filename); LAZY(g_log_set_default_handler); LAZY(gtk_file_filter_new); LAZY(gtk_file_filter_add_pattern); LAZY(gtk_file_filter_set_name); LAZY(gtk_file_chooser_add_filter); + LAZY(gtk_events_pending); + LAZY(gtk_main_iteration); /* Shut up GTK */ g_log_set_default_handler(nop, NULL); @@ -66,7 +73,9 @@ char *do_open_rom_dialog(void) void *dialog = gtk_file_chooser_dialog_new("Open ROM", 0, GTK_FILE_CHOOSER_ACTION_OPEN, - "Open", 0, NULL); + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); void *filter = gtk_file_filter_new(); @@ -79,15 +88,23 @@ char *do_open_rom_dialog(void) int res = gtk_dialog_run (dialog); char *ret = NULL; - if (res == 0) + if (res == GTK_RESPONSE_ACCEPT) { char *filename; filename = gtk_file_chooser_get_filename(dialog); ret = strdup(filename); g_free(filename); } - - g_object_unref(dialog); + + while (gtk_events_pending()) { + gtk_main_iteration(); + } + + gtk_widget_destroy(dialog); + + while (gtk_events_pending()) { + gtk_main_iteration(); + } return ret; lazy_error: From 6888047102d86fc1fcf10503fc9bf921ad455cfc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Jun 2019 16:42:17 +0300 Subject: [PATCH 0850/1216] Show flags in the registers command --- Core/debugger.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 79321a5c..ba413ca4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -811,7 +811,12 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } - GB_log(gb, "AF = $%04x\n", gb->registers[GB_REGISTER_AF]); /* AF can't really be an address */ + + GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ + (gb->f & GB_CARRY_FLAG)? 'C' : '-', + (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', + (gb->f & GB_SUBSTRACT_FLAG)? 'S' : '-', + (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); From 4c34e0a6e063ead12d89bb7e085e79d04702ba57 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 1 Jun 2019 18:22:58 +0300 Subject: [PATCH 0851/1216] =?UTF-8?q?Turns=20out=20the=20AGB=20inverts=20C?= =?UTF-8?q?hannel=203=E2=80=99s=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index b4f4d752..d5537a6e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -59,6 +59,11 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + if (index == GB_WAVE) { + /* For some reason, channel 3 is inverted on the AGB */ + value ^= 0xF; + } + GB_sample_t output; if (gb->io_registers[GB_IO_NR51] & (1 << index)) { output.right = (0xf - value * 2) * right_volume; From 64879f5b02b3b583e9da7c8c5f84ca1794644b86 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 13:53:50 +0300 Subject: [PATCH 0852/1216] Accurate emulation of (most aspects of) stop mode --- Core/apu.c | 192 ++++++++++++++++++++++++------------------------ Core/display.c | 15 +++- Core/gb.h | 1 + Core/joypad.c | 1 - Core/sm83_cpu.c | 20 ++++- Core/timing.c | 12 ++- 6 files changed, 136 insertions(+), 105 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index d5537a6e..7a54c439 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -406,106 +406,108 @@ void GB_apu_run(GB_gameboy_t *gb) uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; + + if (likely(!gb->stopped || GB_is_cgb(gb))) { + /* To align the square signal to 1MHz */ + gb->apu.lf_div ^= cycles & 1; + gb->apu.noise_channel.alignment += cycles; - /* To align the square signal to 1MHz */ - gb->apu.lf_div ^= cycles & 1; - gb->apu.noise_channel.alignment += cycles; - - if (gb->apu.square_sweep_calculate_countdown) { - if (gb->apu.square_sweep_calculate_countdown > cycles) { - gb->apu.square_sweep_calculate_countdown -= cycles; - } - else { - /* APU bug: sweep frequency is checked after adding the sweep delta twice */ - gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb); - if (gb->apu.new_sweep_sample_legnth > 0x7ff) { - gb->apu.is_active[GB_SQUARE_1] = false; - update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); - gb->apu.sweep_enabled = false; - } - gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8; - gb->apu.square_sweep_calculate_countdown = 0; - } - } - - UNROLL - for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { - if (gb->apu.is_active[i]) { - uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { - cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; - gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; - gb->apu.square_channels[i].current_sample_index++; - gb->apu.square_channels[i].current_sample_index &= 0x7; - - update_square_sample(gb, i); - } - if (cycles_left) { - gb->apu.square_channels[i].sample_countdown -= cycles_left; - } - } - } - - gb->apu.wave_channel.wave_form_just_read = false; - if (gb->apu.is_active[GB_WAVE]) { - uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { - cycles_left -= gb->apu.wave_channel.sample_countdown + 1; - gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; - gb->apu.wave_channel.current_sample_index++; - gb->apu.wave_channel.current_sample_index &= 0x1F; - gb->apu.wave_channel.current_sample = - gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - cycles - cycles_left); - gb->apu.wave_channel.wave_form_just_read = true; - } - if (cycles_left) { - gb->apu.wave_channel.sample_countdown -= cycles_left; - gb->apu.wave_channel.wave_form_just_read = false; - } - } - - if (gb->apu.is_active[GB_NOISE]) { - uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { - cycles_left -= gb->apu.noise_channel.sample_countdown + 1; - gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; - - /* Step LFSR */ - unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; - /* Todo: is this formula is different on a GBA? */ - bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; - gb->apu.noise_channel.lfsr >>= 1; - - if (new_high_bit) { - gb->apu.noise_channel.lfsr |= high_bit_mask; + if (gb->apu.square_sweep_calculate_countdown) { + if (gb->apu.square_sweep_calculate_countdown > cycles) { + gb->apu.square_sweep_calculate_countdown -= cycles; } else { - /* This code is not redundent, it's relevant when switching LFSR widths */ - gb->apu.noise_channel.lfsr &= ~high_bit_mask; + /* APU bug: sweep frequency is checked after adding the sweep delta twice */ + gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb); + if (gb->apu.new_sweep_sample_legnth > 0x7ff) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); + gb->apu.sweep_enabled = false; + } + gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8; + gb->apu.square_sweep_calculate_countdown = 0; } - - gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - if (gb->model == GB_MODEL_CGB_C) { - /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. - Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, - I'll assume these devices are innocent until proven guilty. - - Also happens on CGB-B, but not on CGB-D. - */ - gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; - } - gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - - update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? - gb->apu.noise_channel.current_volume : 0, - 0); } - if (cycles_left) { - gb->apu.noise_channel.sample_countdown -= cycles_left; + + UNROLL + for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { + if (gb->apu.is_active[i]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { + cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; + gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; + gb->apu.square_channels[i].current_sample_index++; + gb->apu.square_channels[i].current_sample_index &= 0x7; + + update_square_sample(gb, i); + } + if (cycles_left) { + gb->apu.square_channels[i].sample_countdown -= cycles_left; + } + } + } + + gb->apu.wave_channel.wave_form_just_read = false; + if (gb->apu.is_active[GB_WAVE]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + cycles_left -= gb->apu.wave_channel.sample_countdown + 1; + gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; + gb->apu.wave_channel.current_sample_index++; + gb->apu.wave_channel.current_sample_index &= 0x1F; + gb->apu.wave_channel.current_sample = + gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; + update_sample(gb, GB_WAVE, + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + cycles - cycles_left); + gb->apu.wave_channel.wave_form_just_read = true; + } + if (cycles_left) { + gb->apu.wave_channel.sample_countdown -= cycles_left; + gb->apu.wave_channel.wave_form_just_read = false; + } + } + + if (gb->apu.is_active[GB_NOISE]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { + cycles_left -= gb->apu.noise_channel.sample_countdown + 1; + gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; + + /* Step LFSR */ + unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; + /* Todo: is this formula is different on a GBA? */ + bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; + gb->apu.noise_channel.lfsr >>= 1; + + if (new_high_bit) { + gb->apu.noise_channel.lfsr |= high_bit_mask; + } + else { + /* This code is not redundent, it's relevant when switching LFSR widths */ + gb->apu.noise_channel.lfsr &= ~high_bit_mask; + } + + gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + if (gb->model == GB_MODEL_CGB_C) { + /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. + Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, + I'll assume these devices are innocent until proven guilty. + + Also happens on CGB-B, but not on CGB-D. + */ + gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; + } + gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + if (cycles_left) { + gb->apu.noise_channel.sample_countdown -= cycles_left; + } } } diff --git a/Core/display.c b/Core/display.c index 22f6e8d6..c84b3a92 100644 --- a/Core/display.c +++ b/Core/display.c @@ -137,13 +137,13 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (gb->sgb) { - uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0x3 : 0x0; + uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? 0x3 : 0x0; for (unsigned i = 0; i < WIDTH * LINES; i++) { gb->sgb->screen_buffer[i] = color; } } else { - uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? gb->rgb_encode_callback(gb, 0, 0, 0) : gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); for (unsigned i = 0; i < WIDTH * LINES; i++) { @@ -555,6 +555,15 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) */ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { + /* The PPU does not advance while in STOP mode on the DMG */ + if (gb->stopped && !GB_is_cgb(gb)) { + gb->cycles_in_stop_mode += cycles; + if (gb->cycles_in_stop_mode >= LCDC_PERIOD) { + gb->cycles_in_stop_mode -= LCDC_PERIOD; + display_vblank(gb); + } + return; + } GB_object_t *objects = (GB_object_t *) &gb->oam; GB_STATE_MACHINE(gb, display, cycles, 2) { @@ -696,7 +705,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { if (GB_is_cgb(gb)) { add_object_from_index(gb, gb->oam_search_index); - /* The CGB does not care about the accessed OAM row as there's no OAM bug*/ + /* The CGB does not care about the accessed OAM row as there's no OAM bug */ } GB_SLEEP(gb, display, 8, 2); if (!GB_is_cgb(gb)) { diff --git a/Core/gb.h b/Core/gb.h index 30654d52..f429b450 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -478,6 +478,7 @@ struct GB_gameboy_internal_s { bool lyc_interrupt_line; bool cgb_palettes_blocked; uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. + uint32_t cycles_in_stop_mode; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/joypad.c b/Core/joypad.c index 7713cbdf..ebdf2f50 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -56,7 +56,6 @@ void GB_update_joyp(GB_gameboy_t *gb) if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ gb->io_registers[GB_IO_IF] |= 0x10; - gb->stopped = false; } gb->io_registers[GB_IO_JOYP] |= 0xC0; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 1416000c..cf8bf090 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -234,7 +234,15 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) } else { - gb->stopped = true; + if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { + /* HW Bug? When STOP is executed while a button is down, the CPU halts forever + yet the other hardware keeps running. */ + gb->interrupt_enable = 0; + gb->halted = true; + } + else { + gb->stopped = true; + } } /* Todo: is PC being actually read? */ @@ -1389,7 +1397,15 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } if (gb->stopped) { - GB_advance_cycles(gb, 64); + GB_advance_cycles(gb, 4); + if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { + gb->stopped = false; + /* The CPU takes more time to wake up then the other components */ + for (unsigned i = 0x800; i--;) { + GB_advance_cycles(gb, 0x40); + } + GB_advance_cycles(gb, 8); + } return; } diff --git a/Core/timing.c b/Core/timing.c index 8affd865..039f7503 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -210,8 +210,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Affected by speed boost gb->dma_cycles += cycles; - GB_timers_run(gb, cycles); - advance_serial(gb, cycles); + if (!gb->stopped) { + GB_timers_run(gb, cycles); + advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode + } gb->debugger_ticks += cycles; @@ -227,8 +229,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_input_ir_change += cycles; gb->cycles_since_last_sync += cycles; gb->cycles_since_run += cycles; - GB_dma_run(gb); - GB_hdma_run(gb); + if (!gb->stopped) { // TODO: Verify what happens in STOP mode + GB_dma_run(gb); + GB_hdma_run(gb); + } GB_apu_run(gb); GB_display_run(gb, cycles); GB_ir_run(gb); From 2f9de4942c7cf9adfe32309b2927c99c97d12d12 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 18:27:25 +0300 Subject: [PATCH 0853/1216] Increase input polling frequency in the Cocoa and SDL frontends, should make inputs look less synthetic and potentially reduce input lag --- Core/gb.c | 6 +++++- Core/gb.h | 3 +++ Core/joypad.c | 2 ++ Core/memory.c | 1 + Core/sm83_cpu.c | 6 ++++++ Core/timing.c | 3 +++ SDL/main.c | 1 + 7 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index cb99d4c8..bad19108 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -372,7 +372,6 @@ uint8_t GB_run(GB_gameboy_t *gb) gb->cycles_since_run = 0; GB_cpu_run(gb); if (gb->vblank_just_occured) { - GB_update_joyp(gb); GB_rtc_run(gb); GB_debugger_handle_async_commands(gb); GB_rewind_push(gb); @@ -812,3 +811,8 @@ unsigned GB_get_player_count(GB_gameboy_t *gb) { return GB_is_sgb(gb)? gb->sgb->player_count : 1; } + +void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback) +{ + gb->update_input_hint_callback = callback; +} diff --git a/Core/gb.h b/Core/gb.h index f429b450..b798a04a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -238,6 +238,7 @@ typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_si typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); typedef struct { bool state; @@ -527,6 +528,7 @@ struct GB_gameboy_internal_s { GB_rumble_callback_t rumble_callback; GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback; GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; + GB_update_input_hint_callback_t update_input_hint_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -674,6 +676,7 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); +void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback); /* These APIs are used when using internal clock */ void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); diff --git a/Core/joypad.c b/Core/joypad.c index ebdf2f50..4ae6e676 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -65,6 +65,7 @@ void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) { assert(index >= 0 && index < GB_KEY_MAX); gb->keys[0][index] = pressed; + GB_update_joyp(gb); } void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed) @@ -72,4 +73,5 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play assert(index >= 0 && index < GB_KEY_MAX); assert(player < 4); gb->keys[player][index] = pressed; + GB_update_joyp(gb); } diff --git a/Core/memory.c b/Core/memory.c index c15ae42c..9104cbdb 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -307,6 +307,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return (gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0); case GB_IO_JOYP: + GB_timing_sync(gb); case GB_IO_TMA: case GB_IO_LCDC: case GB_IO_SCY: diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index cf8bf090..12ca6704 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -234,6 +234,7 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) } else { + GB_timing_sync(gb); if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { /* HW Bug? When STOP is executed while a button is down, the CPU halts forever yet the other hardware keeps running. */ @@ -1397,6 +1398,7 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } if (gb->stopped) { + GB_timing_sync(gb); GB_advance_cycles(gb, 4); if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { gb->stopped = false; @@ -1409,6 +1411,10 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } + if ((gb->interrupt_enable & 0x10) && (gb->ime || gb->halted)) { + GB_timing_sync(gb); + } + if (gb->halted && !GB_is_cgb(gb) && !gb->just_halted) { GB_advance_cycles(gb, 2); } diff --git a/Core/timing.c b/Core/timing.c index 039f7503..b14a00b1 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -73,6 +73,9 @@ void GB_timing_sync(GB_gameboy_t *gb) } gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } } #else diff --git a/SDL/main.c b/SDL/main.c index 451ac9c2..be52afda 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -435,6 +435,7 @@ restart: GB_set_color_correction_mode(&gb, configuration.color_correction_mode); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); + GB_set_update_input_hint_callback(&gb, handle_events); } SDL_DestroyTexture(texture); From 9d8adbb5818d39143a143e18549c84a8f092b618 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 18:37:19 +0300 Subject: [PATCH 0854/1216] This is not correct, this bug only affects the PCM registers and not actual output. Currently not emulated at all. --- Core/apu.c | 10 ---------- Core/apu.h | 1 - 2 files changed, 11 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 7a54c439..664530d3 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -489,16 +489,6 @@ void GB_apu_run(GB_gameboy_t *gb) } gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - if (gb->model == GB_MODEL_CGB_C) { - /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. - Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, - I'll assume these devices are innocent until proven guilty. - - Also happens on CGB-B, but not on CGB-D. - */ - gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; - } - gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; update_sample(gb, GB_NOISE, gb->apu.current_lfsr_sample ? diff --git a/Core/apu.h b/Core/apu.h index 2a49c048..f8f56e99 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -114,7 +114,6 @@ typedef struct bool skip_div_event; bool current_lfsr_sample; - bool previous_lfsr_sample; } GB_apu_t; typedef enum { From 7fc3de69da3d2d3e0d561179f88858cbae5e049f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 18:40:54 +0300 Subject: [PATCH 0855/1216] Mark CGB-C as experimental --- Cocoa/Preferences.xib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index e754199b..8278ee16 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -288,7 +288,7 @@ - + From 274760746e67d7939924cec2bd6ef55057f2c036 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 19:18:07 +0300 Subject: [PATCH 0856/1216] Fix #165 --- Core/gb.c | 6 +++--- Core/gb.h | 2 +- Core/save_state.c | 39 ++++++++++++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index bad19108..2712da9d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -95,7 +95,7 @@ void GB_init(GB_gameboy_t *gb, GB_model_t model) memset(gb, 0, sizeof(*gb)); gb->model = model; if (GB_is_cgb(gb)) { - gb->ram = malloc(gb->ram_size = 0x2000 * 8); + gb->ram = malloc(gb->ram_size = 0x1000 * 8); gb->vram = malloc(gb->vram_size = 0x2000 * 2); } else { @@ -632,7 +632,7 @@ void GB_reset(GB_gameboy_t *gb) gb->io_registers[GB_IO_JOYP] = 0xF; gb->mbc_ram_size = mbc_ram_size; if (GB_is_cgb(gb)) { - gb->ram_size = 0x2000 * 8; + gb->ram_size = 0x1000 * 8; gb->vram_size = 0x2000 * 2; memset(gb->vram, 0, gb->vram_size); gb->cgb_mode = true; @@ -703,7 +703,7 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) { gb->model = model; if (GB_is_cgb(gb)) { - gb->ram = realloc(gb->ram, gb->ram_size = 0x2000 * 8); + gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8); gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2); } else { diff --git a/Core/gb.h b/Core/gb.h index b798a04a..5385710a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -333,6 +333,7 @@ struct GB_gameboy_internal_s { bool infrared_input; GB_printer_t printer; uint8_t extra_oam[0xff00 - 0xfea0]; + uint32_t ram_size; // Different between CGB and DMG ); /* DMA and HDMA */ @@ -593,7 +594,6 @@ struct GB_gameboy_internal_s { bool turbo; bool turbo_dont_skip; bool disable_rendering; - uint32_t ram_size; // Different between CGB and DMG uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units diff --git a/Core/save_state.c b/Core/save_state.c index 26c9b522..1cd34587 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -156,11 +156,6 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) return false; } - if (gb->ram_size != save->ram_size) { - GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); - return false; - } - if (gb->vram_size != save->vram_size) { GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n"); return false; @@ -171,6 +166,17 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) return false; } + if (gb->ram_size != save->ram_size) { + if (gb->ram_size == 0x1000 * 8 && save->ram_size == 0x2000 * 8) { + /* A bug in versions prior to 0.12 made CGB instances allocate twice the ammount of RAM. + Ignore this issue to retain compatibility with older, 0.11, save states. */ + } + else { + GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); + return false; + } + } + return true; } @@ -182,6 +188,8 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) /* Every unread value should be kept the same. */ memcpy(&save, gb, sizeof(save)); + /* ...Except ram size, we use it to detect old saves with incorrect ram sizes */ + save.ram_size = 0; FILE *f = fopen(path, "rb"); if (!f) { @@ -199,6 +207,17 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) if (!READ_SECTION(&save, f, rtc )) goto error; if (!READ_SECTION(&save, f, video )) goto error; + if (save.ram_size == 0) { + /* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially + incorrect RAM amount if it's a CGB instance */ + if (GB_is_cgb(&save)) { + save.ram_size = 0x2000 * 8; // Incorrect RAM size + } + else { + save.ram_size = gb->ram_size; + } + } + if (!verify_state_compatibility(gb, &save)) { errno = -1; goto error; @@ -218,13 +237,19 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) fclose(f); return EIO; } + + /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ + fseek(f, save.ram_size - gb->ram_size, SEEK_CUR); if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { fclose(f); return EIO; } + size_t orig_ram_size = gb->ram_size; memcpy(gb, &save, sizeof(save)); + gb->ram_size = orig_ram_size; + errno = 0; if (gb->cartridge_type->has_rumble && gb->rumble_callback) { @@ -324,6 +349,10 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return -1; } + /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ + buffer += save.ram_size - gb->ram_size; + length -= save.ram_size - gb->ram_size; + memcpy(gb, &save, sizeof(save)); if (gb->cartridge_type->has_rumble && gb->rumble_callback) { From d0bd741049b57bb90d6838ee7dfc8ae017575ad6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 23:38:34 +0300 Subject: [PATCH 0857/1216] Added SCX/SCY display in the VRAM viewer. Closes #168 --- Cocoa/Document.m | 6 ++++++ Cocoa/Document.xib | 39 +++++++++++++++++++++++++-------------- Cocoa/GBImageView.h | 4 +++- Cocoa/GBImageView.m | 39 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 3d3ad7be..4d79148b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -936,6 +936,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem, (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem); + self.tilemapImageView.scrollRect = NSMakeRect(gb.io_registers[GB_IO_SCX], gb.io_registers[GB_IO_SCY], 160, 144); self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0]; self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest; } @@ -1192,6 +1193,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } } +- (IBAction)toggleScrollingDisplay:(NSButton *)sender +{ + self.tilemapImageView.displayScrollRect = sender.state; +} + - (IBAction)vramTabChanged:(NSSegmentedControl *)sender { [self.vramTabView selectTabViewItemAtIndex:[sender selectedSegment]]; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 88c52757..d5582165 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -1,8 +1,8 @@ - + - + @@ -83,9 +83,9 @@ - + - + @@ -109,7 +109,7 @@ - + @@ -133,9 +133,9 @@ - + - + @@ -163,9 +163,9 @@ - + - + @@ -243,7 +243,7 @@ - + @@ -260,7 +260,7 @@ - + @@ -285,7 +285,7 @@ - + @@ -298,7 +298,7 @@ - + @@ -377,6 +377,17 @@ + @@ -612,7 +623,7 @@ - + diff --git a/Cocoa/GBImageView.h b/Cocoa/GBImageView.h index 22a8829e..d5ee534b 100644 --- a/Cocoa/GBImageView.h +++ b/Cocoa/GBImageView.h @@ -11,7 +11,9 @@ @interface GBImageView : NSImageView @property (nonatomic) NSArray *horizontalGrids; @property (nonatomic) NSArray *verticalGrids; -@property (weak) IBOutlet id delegate; +@property (nonatomic) bool displayScrollRect; +@property NSRect scrollRect; +@property (weak) IBOutlet id delegate; @end @protocol GBImageViewDelegate diff --git a/Cocoa/GBImageView.m b/Cocoa/GBImageView.m index 674d4b2c..973625ee 100644 --- a/Cocoa/GBImageView.m +++ b/Cocoa/GBImageView.m @@ -25,8 +25,8 @@ [conf.color set]; for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) { NSBezierPath *line = [NSBezierPath bezierPath]; - [line moveToPoint:NSMakePoint(0, y + 0.5)]; - [line lineToPoint:NSMakePoint(self.frame.size.width, y + 0.5)]; + [line moveToPoint:NSMakePoint(0, y - 0.5)]; + [line lineToPoint:NSMakePoint(self.frame.size.width, y - 0.5)]; [line setLineWidth:1.0]; [line stroke]; } @@ -42,6 +42,35 @@ [line stroke]; } } + + if (self.displayScrollRect) { + NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite]; + for (unsigned x = 0; x < 2; x++) { + for (unsigned y = 0; y < 2; y++) { + NSRect rect = self.scrollRect; + rect.origin.x *= x_ratio; + rect.origin.y *= y_ratio; + rect.size.width *= x_ratio; + rect.size.height *= y_ratio; + rect.origin.y = self.frame.size.height - rect.origin.y - rect.size.height; + + rect.origin.x -= self.frame.size.width * x; + rect.origin.y += self.frame.size.height * y; + + + NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect]; + [path appendBezierPath:subpath]; + } + } + [path setWindingRule:NSEvenOddWindingRule]; + [path setLineWidth:4.0]; + [path setLineJoinStyle:NSRoundLineJoinStyle]; + [[NSColor colorWithWhite:0.2 alpha:0.5] set]; + [path fill]; + [path addClip]; + [[NSColor colorWithWhite:0.0 alpha:0.6] set]; + [path stroke]; + } } - (void)setHorizontalGrids:(NSArray *)horizontalGrids @@ -56,6 +85,12 @@ [self setNeedsDisplay]; } +- (void)setDisplayScrollRect:(bool)displayScrollRect +{ + self->_displayScrollRect = displayScrollRect; + [self setNeedsDisplay]; +} + - (void)updateTrackingAreas { if(trackingArea != nil) { From 5cda1f2f5f5e854bf64d63c72f1d665086611f05 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 8 Jun 2019 00:04:58 +0300 Subject: [PATCH 0858/1216] Fix the last commit --- Cocoa/Document.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 4d79148b..731f7c81 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -936,7 +936,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem, (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem); - self.tilemapImageView.scrollRect = NSMakeRect(gb.io_registers[GB_IO_SCX], gb.io_registers[GB_IO_SCY], 160, 144); + self.tilemapImageView.scrollRect = NSMakeRect(GB_read_memory(&gb, 0xFF00 | GB_IO_SCX), + GB_read_memory(&gb, 0xFF00 | GB_IO_SCY), + 160, 144); self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0]; self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest; } From bb7fa95426d25bb03d6e37e6e34412c218acdbf8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 8 Jun 2019 13:37:49 +0300 Subject: [PATCH 0859/1216] Fix incorrect register values when changing the color palette via the boot ROM --- BootROMs/cgb_boot.asm | 4 ++++ Makefile | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index ee0198a3..3e07399e 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -758,13 +758,17 @@ ENDC ld a, [$143] bit 7, a call z, EmulateDMG + bit 7, a + ldh [$4C], a ldh a, [TitleChecksum] ld b, a + jr z, .skipDMGForCGBCheck ldh a, [InputPalette] and a jr nz, .emulateDMGForCGBGame +.skipDMGForCGBCheck IF DEF(AGB) ; Set registers to match the original AGB-CGB boot ; AF = $1100, C = 0 diff --git a/Makefile b/Makefile index 38b3e832..e052287d 100644 --- a/Makefile +++ b/Makefile @@ -322,6 +322,10 @@ $(BIN)/SDL/Shaders: Shaders # Boot ROMs +$(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm +$(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm +$(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm + $(BIN)/BootROMs/%.bin: BootROMs/%.asm -@$(MKDIR) -p $(dir $@) cd BootROMs && rgbasm -o ../$@.tmp ../$< From 0da293010911d0cfc29de28b2a554a47f9546616 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 8 Jun 2019 14:35:52 +0300 Subject: [PATCH 0860/1216] Fix #175 --- Core/debugger.c | 2 +- Core/display.c | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index ba413ca4..d8872a4d 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1529,7 +1529,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } GB_log(gb, "LCDC:\n"); GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); - GB_log(gb, " %s: %s\n", GB_is_cgb(gb)? (gb->cgb_mode? "Sprite priority flags" : "Background and Window") : "Background", + GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Sprite priority flags" : "Background and Window"), (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); diff --git a/Core/display.c b/Core/display.c index c84b3a92..240e3c76 100644 --- a/Core/display.c +++ b/Core/display.c @@ -112,7 +112,7 @@ typedef struct __attribute__((packed)) { static bool window_enabled(GB_gameboy_t *gb) { if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { - if (!gb->cgb_mode && GB_is_cgb(gb)) { + if (!gb->cgb_mode) { return false; } } @@ -376,9 +376,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bg_enabled = false; } } - if (!GB_is_cgb(gb) && gb->in_window) { - bg_enabled = true; - } { uint8_t pixel = bg_enabled? fifo_item->pixel : 0; From 49d8a5cb440026d4455c5b981567e92acb057694 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 8 Jun 2019 16:08:07 +0300 Subject: [PATCH 0861/1216] Fixed the parsing of comparison operators as well as their priorities. Fixes #155 --- Core/debugger.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index d8872a4d..79b59911 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -350,15 +350,15 @@ static struct { {"&", 1, and}, {"^", 1, xor}, {"<<", 2, shleft}, - {"<=", 3, lower_equals}, - {"<", 3, lower}, + {"<=", -1, lower_equals}, + {"<", -1, lower}, {">>", 2, shright}, - {">=", 3, greater_equals}, - {">", 3, greater}, - {"==", 3, equals}, - {"=", -1, NULL, assign}, - {"!=", 3, different}, - {":", 4, bank}, + {">=", -1, greater_equals}, + {">", -1, greater}, + {"==", -1, equals}, + {"=", -2, NULL, assign}, + {"!=", -1, different}, + {":", 3, bank}, }; value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, @@ -575,9 +575,13 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, 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; unsigned long operator_length = strlen(operators[j].string); if (memcmp(string + i, operators[j].string, operator_length) == 0) { + if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) { + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + continue; + } // Found an operator! operator_pos = i; operator_index = j; From a0c5baecd87426bea56853b85bd9a634e6d7473b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 9 Jun 2019 00:53:44 +0300 Subject: [PATCH 0862/1216] More realistic initial V/RAM values in the boot ROM. Fixes #150 and #91 --- BootROMs/cgb_boot.asm | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 3e07399e..4c073ed3 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -5,21 +5,26 @@ Start: ; Init stack pointer ld sp, $fffe +; Clear memory VRAM + ld hl, $8000 + call ClearMemoryPage + ld a, 2 + ld c, $70 + ld [c], a +; Clear RAM Bank 2 (Like the original boot ROM + ld h, $D0 xor a + call ClearMemoryPage + ld [c], a + ; Clear chosen input palette ldh [InputPalette], a ; Clear title checksum ldh [TitleChecksum], a -; Clear memory VRAM - ld hl, $8000 - call ClearMemoryPage - ld h, $d0 - call ClearMemoryPage - + ; Clear OAM - ld hl, $fe00 + ld h, $fe ld c, $a0 - xor a .clearOAMLoop ldi [hl], a dec c @@ -67,7 +72,7 @@ Start: ld hl, $8000 call ClearMemoryPage -; Copy Sameboy Logo +; Copy SameBoy Logo ld de, SameboyLogo ld hl, $8080 ld c, (SameboyLogoEnd - SameboyLogo) / 2 @@ -183,7 +188,7 @@ ENDC ld hl, BgPalettes ld d, 64 ; Length of write - ld e, 0 ; Index of write + ld e, c ; Index of write (C=0) call LoadBGPalettes ; Turn on LCD @@ -1008,7 +1013,7 @@ ClearVRAMViaHDMA: ld hl, $FF51 ; Src - ld a, $D0 + ld a, $88 ld [hli], a xor a ld [hli], a @@ -1021,7 +1026,7 @@ ClearVRAMViaHDMA: ; Do it ld a, $12 - ld [hli], a + ld [hl], a ret GetInputPaletteIndex: From 8b1c1652536c81d8622ec77f6de21f3cbeaa88bc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 9 Jun 2019 13:48:05 +0300 Subject: [PATCH 0863/1216] Automation fixes --- Tester/main.c | 94 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 643c39f9..d9ce165b 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -30,7 +30,8 @@ static char *log_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, - do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right; + do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right, + semi_random, limit_start, pointer_control; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -63,35 +64,54 @@ static void vblank(GB_gameboy_t *gb) unsigned combo_length = 40; if (start_is_not_first || push_a_twice) combo_length = 60; /* The start item in the menu is not the first, so also push down */ else if (a_is_bad || start_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ - - switch ((push_faster ? frames * 2 : - push_slower ? frames / 2 : - push_a_twice? frames / 4: - frames) % combo_length + (start_is_bad? 20 : 0) ) { - case 0: - gb->keys[0][push_right? 0 : 7] = true; // Start (Or right) down - break; - case 10: - gb->keys[0][push_right? 0 : 7] = false; // Start (Or right) up - break; - case 20: - gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) - break; - case 30: - gb->keys[0][b_is_confirm? 5: 4] = false; // A up (or B) - break; - case 40: - if (push_a_twice) { + + if (semi_random) { + if (frames % 10 == 0) { + unsigned key = (((frames / 20) * 0x1337cafe) >> 29) & 7; + gb->keys[0][key] = (frames % 20) == 0; + } + } + else { + switch ((push_faster ? frames * 2 : + push_slower ? frames / 2 : + push_a_twice? frames / 4: + frames) % combo_length + (start_is_bad? 20 : 0) ) { + case 0: + if (!limit_start || frames < 20 * 60) { + gb->keys[0][push_right? 0 : 7] = true; // Start (Or right) down + } + if (pointer_control) { + gb->keys[0][1] = true; // left + gb->keys[0][2] = true; // up + } + + break; + case 10: + gb->keys[0][push_right? 0 : 7] = false; // Start (Or right) up + if (pointer_control) { + gb->keys[0][1] = false; // left + gb->keys[0][2] = false; // up + } + break; + case 20: gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) - } - else if (gb->boot_rom_finished) { - gb->keys[0][3] = true; // D-Pad Down down - } - break; - case 50: - gb->keys[0][b_is_confirm? 5: 4] = false; // A down (or B) - gb->keys[0][3] = false; // D-Pad Down up - break; + break; + case 30: + gb->keys[0][b_is_confirm? 5: 4] = false; // A up (or B) + break; + case 40: + if (push_a_twice) { + gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) + } + else if (gb->boot_rom_finished) { + gb->keys[0][3] = true; // D-Pad Down down + } + break; + case 50: + gb->keys[0][b_is_confirm? 5: 4] = false; // A down (or B) + gb->keys[0][3] = false; // D-Pad Down up + break; + } } } @@ -337,7 +357,9 @@ int main(int argc, char **argv) strcmp((const char *)(gb.rom + 0x134), "KWIRK") == 0 || strcmp((const char *)(gb.rom + 0x134), "PUZZLE BOY") == 0; start_is_bad = strcmp((const char *)(gb.rom + 0x134), "BLUESALPHA") == 0; - b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0; + b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0 || + strcmp((const char *)(gb.rom + 0x134), "SOCCER") == 0 || + strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0; push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0; push_slower = strcmp((const char *)(gb.rom + 0x134), "BAKENOU") == 0; do_not_stop = strcmp((const char *)(gb.rom + 0x134), "SPACE INVADERS") == 0; @@ -346,6 +368,9 @@ int main(int argc, char **argv) /* M&M's Minis Madness Demo (which has no menu but the same title as the full game) */ (memcmp((const char *)(gb.rom + 0x134), "MINIMADNESSBMIE", strlen("MINIMADNESSBMIE")) == 0 && gb.rom[0x14e] == 0x6c); + /* This game has some terrible menus. */ + semi_random = strcmp((const char *)(gb.rom + 0x134), "KUKU GAME") == 0; + /* This game temporarily sets SP to OAM RAM */ @@ -356,6 +381,10 @@ int main(int argc, char **argv) /* This game uses some recursive algorithms and therefore requires quite a large call stack */ large_stack = memcmp((const char *)(gb.rom + 0x134), "MICRO EPAK1BM", strlen("MICRO EPAK1BM")) == 0 || strcmp((const char *)(gb.rom + 0x134), "TECMO BOWL") == 0; + /* High quality game that leaks stack whenever you open the menu (with start), + but requires pressing start to play it. */ + limit_start = strcmp((const char *)(gb.rom + 0x134), "DIVA STARS") == 0; + large_stack |= limit_start; /* Pressing start while in the map in Tsuri Sensei will leak an internal screen-stack which will eventually overflow, override an array of jump-table indexes, jump to a random @@ -363,6 +392,10 @@ int main(int argc, char **argv) will prevent this scenario. */ push_a_twice = strcmp((const char *)(gb.rom + 0x134), "TURI SENSEI V1") == 0; + /* Yes, you should totally use a cursor point & click interface for the language select menu. */ + pointer_control = memcmp((const char *)(gb.rom + 0x134), "LEGO ATEAM BLPP", strlen("LEGO ATEAM BLPP")) == 0; + push_faster |= pointer_control; + /* Run emulation */ running = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; @@ -376,6 +409,7 @@ int main(int argc, char **argv) } } + if (log_file) { fclose(log_file); log_file = NULL; From 8386aaf12f47fcffd4170777689f09c3f113787a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sun, 9 Jun 2019 15:15:08 +0200 Subject: [PATCH 0864/1216] Save 20 bytes in the CGB boot ROM --- BootROMs/cgb_boot.asm | 73 ++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 4c073ed3..ac9b0546 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -103,10 +103,11 @@ Start: ; Load Tilemap ld hl, $98C2 ld b, 3 - ld a, 8 IF DEF(FAST) xor a ldh [$4F], a +ELSE + ld a, 8 ENDC .tilemapLoop @@ -120,8 +121,7 @@ IF !DEF(FAST) ; Switch to second VRAM Bank ld a, 1 ldh [$4F], a - ld a, 8 - ld [hl], a + ld [hl], 8 ; Switch to back first VRAM Bank xor a ldh [$4F], a @@ -628,6 +628,8 @@ ClearMemoryPage: jr z, ClearMemoryPage ret +ReadTwoTileLines: + call ReadTileLine ; c = $f0 for even lines, $f for odd lines. ReadTileLine: ld a, [de] @@ -647,34 +649,28 @@ ReadTileLine: .dontSwap inc hl ldi [hl], a + swap c ret ReadCGBLogoHalfTile: - ld c, $f0 - call ReadTileLine - ld c, $f - call ReadTileLine - inc e - ld c, $f0 - call ReadTileLine - ld c, $f - call ReadTileLine + call .do_twice +.do_twice + call ReadTwoTileLines inc e + ld a, e ret ReadCGBLogoTile: + ld c, $f0 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 @@ -706,19 +702,23 @@ LoadPalettes: jr nz, .loop ret - -AdvanceIntroAnimation: +DoIntroAnimation: + ; Animate the intro + ld a, 1 + ldh [$4F], a + ld d, 26 +.animationLoop + ld b, 2 + call WaitBFrames ld hl, $98C0 ld c, 3 ; Row count .loop ld a, [hl] cp $F ; Already blue jr z, .nextTile - inc a - ld [hl], a + inc [hl] and $7 - cp $1 ; Changed a white tile, go to next line - jr z, .nextLine + jr z, .nextLine ; Changed a white tile, go to next line .nextTile inc hl jr .loop @@ -728,18 +728,7 @@ AdvanceIntroAnimation: 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 + jr nz, .loop dec d jr nz, .animationLoop ret @@ -796,7 +785,7 @@ ENDC .emulateDMGForCGBGame call EmulateDMG ldh [$4C], a - ld a, $1; + ld a, $1 ret EmulateDMG: @@ -833,7 +822,7 @@ GetPaletteIndex: ld a, [hl] ; Old Licensee cp $33 jr z, .newLicensee - cp 1 ; Nintendo + dec a ; 1 = Nintendo jr nz, .notNintendo jr .doChecksum .newLicensee @@ -848,22 +837,22 @@ GetPaletteIndex: .doChecksum ld l, $34 ld c, $10 - ld b, 0 + xor a .checksumLoop - ld a, [hli] - add b - ld b, a + add [hl] + inc l dec c jr nz, .checksumLoop + ld b, a ; c = 0 ld hl, TitleChecksums .searchLoop ld a, l - cp ChecksumsEnd & $FF - jr z, .notNintendo + sub LOW(ChecksumsEnd) ; use sub to zero out a + ret z ld a, [hli] cp b jr nz, .searchLoop @@ -1189,4 +1178,4 @@ BgPalettes: InputPalette: ds 1 WaitLoopCounter: - ds 1 \ No newline at end of file + ds 1 From 843683a4928bec1e00c0dc8626295a866e1a7bf4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 9 Jun 2019 18:14:32 +0300 Subject: [PATCH 0865/1216] Randomize everything! --- Core/gb.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 2712da9d..6c0bd6c2 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -602,6 +602,87 @@ static void reset_ram(GB_gameboy_t *gb) break; } + /* HRAM */ + switch (gb->model) { + case GB_MODEL_CGB_C: + // case GB_MODEL_CGB_D: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + for (unsigned i = 0; i < sizeof(gb->hram); i++) { + gb->hram[i] = (random() & 0xFF); + } + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB2: + for (unsigned i = 0; i < sizeof(gb->hram); i++) { + if (i & 1) { + gb->hram[i] = random() | random() | random(); + } + else { + gb->hram[i] = random() & random() & random(); + } + } + break; + } + + /* OAM */ + switch (gb->model) { + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + /* Zero'd out by boot ROM anyway*/ + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB2: + for (unsigned i = 0; i < 8; i++) { + if (i & 2) { + gb->oam[i] = random() & random() & random(); + } + else { + gb->oam[i] = random() | random() | random(); + } + } + for (unsigned i = 8; i < sizeof(gb->oam); i++) { + gb->oam[i] = gb->oam[i - 8]; + } + break; + } + + /* Wave RAM */ + switch (gb->model) { + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + /* Initialized by CGB-A and newer, 0s in CGB-0*/ + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB2: { + uint8_t temp; + for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { + if (i & 1) { + temp = random() & random() & random(); + } + else { + temp = random() | random() | random(); + } + gb->apu.wave_channel.wave_form[i * 2] = temp >> 4; + gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF; + gb->io_registers[GB_IO_WAV_START + i] = temp; + + } + break; + } + } + for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) { gb->extra_oam[i] = (random() & 0xFF); } From e2ef8dbbe03fa94c084ee87769934805de820e92 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 9 Jun 2019 18:43:23 +0300 Subject: [PATCH 0866/1216] Fix the GUI on some Windows 10 machines (Intel HD?). Fixes #112 --- SDL/gui.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SDL/gui.c b/SDL/gui.c index 77167602..70a449db 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1185,6 +1185,10 @@ void run_gui(bool is_running) } render_texture(pixels, NULL); +#ifdef _WIN32 + /* Required for some Windows 10 machines, god knows why */ + render_texture(pixels, NULL); +#endif } } while (SDL_WaitEvent(&event)); } From c678407d1eae9fec625a644506b3cd66654d9b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Mon, 10 Jun 2019 17:45:14 +0200 Subject: [PATCH 0867/1216] Compress the Sameboy logo. 117 bytes are now free --- BootROMs/SameboyLogo.1bpp | Bin 384 -> 0 bytes BootROMs/cgb_boot.asm | 175 ++++++++++++++++++++------------------ BootROMs/logo-compress.c | 48 +++++++++++ Makefile | 14 ++- 4 files changed, 151 insertions(+), 86 deletions(-) delete mode 100644 BootROMs/SameboyLogo.1bpp create mode 100644 BootROMs/logo-compress.c diff --git a/BootROMs/SameboyLogo.1bpp b/BootROMs/SameboyLogo.1bpp deleted file mode 100644 index b219f7d2a198da14e871fc1559abfda928c026ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmXYtJ#GRq5QS$&;nEjC_7&1+WM7Fci{!{e6qI{|y5T9yQwX0cF;Mj>ZX4>YBeBtshI-bXqK=bkE%wyuL>49$o&`iZ`_ zyRIXPI1L-xU2fCgr+fPROypf?^O&5sS~X%YjCl?$CXPeQZD3|sv|z1St=e|Ie)k{u z`}O*LI`#b?^ar8>h>XEjo>}RFOSIaH4Kws7&Hxzy#^}8>1}kk|(p#+2GKXopU3Rh7 zF{NQBrCJ${E@Sv`fVua^Eagl|!kc}JQ9NV=gaKhxcxNs1cs%ZL*aB*^#Ruk)aV*~O K*b2q3+4K*8j+-0+ diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index ac9b0546..5d492f92 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -57,11 +57,10 @@ Start: .loadLogoLoop ld a, [de] ; Read 2 rows ld b, a - call DoubleBitsAndWriteRow - call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRowTwice inc de ld a, e - xor $34 ; End of logo + cp $34 ; End of logo jr nz, .loadLogoLoop call ReadTrademarkSymbol @@ -71,53 +70,24 @@ Start: xor a ld hl, $8000 call ClearMemoryPage + call LoadTileset -; 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 IF DEF(FAST) xor a ldh [$4F], a ELSE +; Load Tilemap + ld hl, $98C2 + ld d, 3 ld a, 8 -ENDC .tilemapLoop ld c, $10 .tilemapRowLoop - ld [hl], a push af -IF !DEF(FAST) ; Switch to second VRAM Bank ld a, 1 ldh [$4F], a @@ -125,25 +95,29 @@ IF !DEF(FAST) ; Switch to back first VRAM Bank xor a ldh [$4F], a -ENDC pop af ldi [hl], a - inc a + add d dec c jr nz, .tilemapRowLoop + sub 47 + push de ld de, $10 add hl, de + pop de dec b jr nz, .tilemapLoop - cp $38 - jr nz, .doneTilemap + dec d + jr z, .endTilemap + dec d - ld hl, $99a7 - ld b, 1 - ld c, 7 + ld a, $38 + ld l, $a7 + ld bc, $0107 jr .tilemapRowLoop -.doneTilemap +.endTilemap +ENDC ; Expand Palettes ld de, AnimationColors @@ -187,9 +161,7 @@ ENDC jr nz, .expandPalettesLoop ld hl, BgPalettes - ld d, 64 ; Length of write - ld e, c ; Index of write (C=0) - call LoadBGPalettes + call LoadBGPalettes64 ; Turn on LCD ld a, $91 @@ -560,8 +532,7 @@ TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SameboyLogo: - incbin "SameboyLogo.1bpp" -SameboyLogoEnd: + incbin "SameboyLogo.rle" AnimationColors: dw $7FFF ; White @@ -578,7 +549,9 @@ DMGPalettes: dw $7FFF, $32BF, $00D0, $0000 ; Helper Functions -DoubleBitsAndWriteRow: +DoubleBitsAndWriteRowTwice: + call .twice +.twice ; Double the most significant 4 bits, b is shifted by 4 ld a, 4 ld c, 0 @@ -661,7 +634,46 @@ ReadCGBLogoHalfTile: ld a, e ret -ReadCGBLogoTile: +LoadTileset: +; Copy SameBoy Logo + ld de, SameboyLogo + ld hl, $8080 +.sameboyLogoLoop + ld a, [de] + inc de + + ld b, a + and $0f + jr z, .skipLiteral + ld c, a + +.literalLoop + ld a, [de] + ldi [hl], a + inc hl + inc de + dec c + jr nz, .literalLoop +.skipLiteral + swap b + ld a, b + and $0f + jr z, .sameboyLogoEnd + ld c, a + ld a, [de] + inc de + +.repeatLoop + ldi [hl], a + inc hl + dec c + jr nz, .repeatLoop + jr .sameboyLogoLoop + +.sameboyLogoEnd +; Copy (unresized) ROM logo + ld de, $104 +.CGBROMLogoLoop ld c, $f0 call ReadCGBLogoHalfTile add a, 22 @@ -669,8 +681,10 @@ ReadCGBLogoTile: call ReadCGBLogoHalfTile sub a, 22 ld e, a - ret - + cp $1c + jr nz, .CGBROMLogoLoop + inc hl + ; fallthrough ReadTrademarkSymbol: ld de, TrademarkSymbol ld c,$08 @@ -687,7 +701,11 @@ LoadObjPalettes: ld c, $6A jr LoadPalettes +LoadBGPalettes64: + ld d, 64 + LoadBGPalettes: + ld e, 0 ld c, $68 LoadPalettes: @@ -735,7 +753,23 @@ DoIntroAnimation: Preboot: IF !DEF(FAST) - call FadeOut + ld b, 32 ; 32 times to fade +.fadeLoop + ld c, 32 ; 32 colors to fade + ld hl, BgPalettes +.frameLoop + push bc + call BrightenColor + pop bc + dec c + jr nz, .frameLoop + + call WaitFrame + call WaitFrame + ld hl, BgPalettes + call LoadBGPalettes64 + dec b + jr nz, .fadeLoop ENDC call ClearVRAMViaHDMA ; Select the first bank @@ -921,9 +955,7 @@ LoadPalettesFromIndex: ; a = index of combination ld c, a add hl, bc ld d, 8 - ld e, 0 - call LoadBGPalettes - ret + jp LoadBGPalettes BrightenColor: ld a, [hli] @@ -976,28 +1008,6 @@ BrightenColor: 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 BrightenColor - 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 @@ -1014,8 +1024,7 @@ ClearVRAMViaHDMA: ld [hli], a ; Do it - ld a, $12 - ld [hl], a + ld [hl], $12 ret GetInputPaletteIndex: @@ -1113,9 +1122,7 @@ ChangeAnimationPalette: call WaitFrame ld hl, BgPalettes - ld d, 64 ; Length of write - ld e, 0 ; Index of write - call LoadBGPalettes + call LoadBGPalettes64 ; Delay the wait loop while the user is selecting a palette ld a, 30 ldh [WaitLoopCounter], a diff --git a/BootROMs/logo-compress.c b/BootROMs/logo-compress.c new file mode 100644 index 00000000..e4d26773 --- /dev/null +++ b/BootROMs/logo-compress.c @@ -0,0 +1,48 @@ +#include +#include +#include + +void pair(size_t count, uint8_t byte) { + static size_t unique_count = 0; + static uint8_t unique_data[15]; + if (count == 1) { + unique_data[unique_count++] = byte; + assert(unique_count <= 15); + } else { + assert(count <= 15); + uint8_t control = (count << 4) | unique_count; + putchar(control); + + for (size_t i = 0; i < unique_count; i++) { + putchar(unique_data[i]); + } + + if (count != 0) { + putchar(byte); + } else { + assert(control == 0); + } + + unique_count = 0; + } +} + +int main(int argc, char *argv[]) { + size_t count = 1; + uint8_t byte = getchar(); + int new; + size_t position = 0; + + while ((new = getchar()) != EOF) { + if (byte == new) { + count++; + } else { + pair(count, byte); + byte = new; + count = 1; + } + } + + pair(count, byte); + pair(0, 0); +} diff --git a/Makefile b/Makefile index e052287d..b4be3553 100644 --- a/Makefile +++ b/Makefile @@ -322,13 +322,23 @@ $(BIN)/SDL/Shaders: Shaders # Boot ROMs +$(OBJ)/%.1bpp: %.png + -@$(MKDIR) -p $(dir $@) + rgbgfx -d 1 -h -o $@ $< + +$(OBJ)/BootROMs/SameboyLogo.rle: $(OBJ)/BootROMs/SameboyLogo.1bpp build/logo-compress + build/logo-compress < $< > $@ + +build/logo-compress: BootROMs/logo-compress.c + $(CC) $< -o $@ + $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm -$(BIN)/BootROMs/%.bin: BootROMs/%.asm +$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameboyLogo.rle -@$(MKDIR) -p $(dir $@) - cd BootROMs && rgbasm -o ../$@.tmp ../$< + rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $< rgblink -o $@.tmp2 $@.tmp dd if=$@.tmp2 of=$@ count=1 bs=$(if $(findstring dmg,$@)$(findstring sgb,$@),256,2304) @rm $@.tmp $@.tmp2 From 8389c6a45060a7b6bc9dc909d8f5e36c7d5c7549 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 14 Jun 2019 14:31:17 +0300 Subject: [PATCH 0868/1216] Long overdue capitalization fixes --- BootROMs/{SameboyLogo.png => SameBoyLogo.png} | Bin BootROMs/cgb_boot.asm | 14 +++++++------- BootROMs/dmg_boot.asm | 2 +- BootROMs/sgb_boot.asm | 2 +- Makefile | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename BootROMs/{SameboyLogo.png => SameBoyLogo.png} (100%) diff --git a/BootROMs/SameboyLogo.png b/BootROMs/SameBoyLogo.png similarity index 100% rename from BootROMs/SameboyLogo.png rename to BootROMs/SameBoyLogo.png diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 5d492f92..0472cbed 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -1,4 +1,4 @@ -; Sameboy CGB bootstrap ROM +; SameBoy CGB bootstrap ROM ; Todo: use friendly names for HW registers instead of magic numbers SECTION "BootCode", ROM0[$0] Start: @@ -468,7 +468,7 @@ ENDM palette_comb 4, 3, 28 palette_comb 28, 3, 6 palette_comb 4, 28, 29 - ; Sameboy "Exclusives" + ; SameBoy "Exclusives" palette_comb 30, 30, 30 ; CGA palette_comb 31, 31, 31 ; DMG LCD palette_comb 28, 4, 1 @@ -505,7 +505,7 @@ Palettes: dw $0000, $4200, $037F, $7FFF dw $7FFF, $7E8C, $7C00, $0000 dw $7FFF, $1BEF, $6180, $0000 - ; Sameboy "Exclusives" + ; SameBoy "Exclusives" dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1 dw $4778, $3290, $1D87, $0861 ; DMG LCD @@ -522,7 +522,7 @@ KeyCombinationPalettes db 7 ; Left + B db 28 ; Up + B db 49 ; Down + B - ; Sameboy "Exclusives" + ; SameBoy "Exclusives" db 51 ; Right + A + B db 52 ; Left + A + B db 53 ; Up + A + B @@ -531,8 +531,8 @@ KeyCombinationPalettes TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c -SameboyLogo: - incbin "SameboyLogo.rle" +SameBoyLogo: + incbin "SameBoyLogo.rle" AnimationColors: dw $7FFF ; White @@ -636,7 +636,7 @@ ReadCGBLogoHalfTile: LoadTileset: ; Copy SameBoy Logo - ld de, SameboyLogo + ld de, SameBoyLogo ld hl, $8080 .sameboyLogoLoop ld a, [de] diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm index 46b389a9..6fb74fb5 100644 --- a/BootROMs/dmg_boot.asm +++ b/BootROMs/dmg_boot.asm @@ -1,4 +1,4 @@ -; Sameboy CGB bootstrap ROM +; SameBoy DMG bootstrap ROM ; Todo: use friendly names for HW registers instead of magic numbers SECTION "BootCode", ROM0[$0] Start: diff --git a/BootROMs/sgb_boot.asm b/BootROMs/sgb_boot.asm index 108af18b..cdb9d774 100644 --- a/BootROMs/sgb_boot.asm +++ b/BootROMs/sgb_boot.asm @@ -1,4 +1,4 @@ -; Sameboy CGB bootstrap ROM +; SameBoy SGB bootstrap ROM ; Todo: use friendly names for HW registers instead of magic numbers SECTION "BootCode", ROM0[$0] Start: diff --git a/Makefile b/Makefile index b4be3553..ee1e56e4 100644 --- a/Makefile +++ b/Makefile @@ -326,7 +326,7 @@ $(OBJ)/%.1bpp: %.png -@$(MKDIR) -p $(dir $@) rgbgfx -d 1 -h -o $@ $< -$(OBJ)/BootROMs/SameboyLogo.rle: $(OBJ)/BootROMs/SameboyLogo.1bpp build/logo-compress +$(OBJ)/BootROMs/SameBoyLogo.rle: $(OBJ)/BootROMs/SameBoyLogo.1bpp build/logo-compress build/logo-compress < $< > $@ build/logo-compress: BootROMs/logo-compress.c @@ -336,7 +336,7 @@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm -$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameboyLogo.rle +$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.rle -@$(MKDIR) -p $(dir $@) rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $< rgblink -o $@.tmp2 $@.tmp From 5a04054145e7215c5afe74cce1fb35b3a50bab26 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 14 Jun 2019 14:34:02 +0300 Subject: [PATCH 0869/1216] Style changes --- BootROMs/logo-compress.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/BootROMs/logo-compress.c b/BootROMs/logo-compress.c index e4d26773..ff291436 100644 --- a/BootROMs/logo-compress.c +++ b/BootROMs/logo-compress.c @@ -2,13 +2,15 @@ #include #include -void pair(size_t count, uint8_t byte) { +void pair(size_t count, uint8_t byte) +{ static size_t unique_count = 0; static uint8_t unique_data[15]; if (count == 1) { unique_data[unique_count++] = byte; assert(unique_count <= 15); - } else { + } + else { assert(count <= 15); uint8_t control = (count << 4) | unique_count; putchar(control); @@ -19,7 +21,8 @@ void pair(size_t count, uint8_t byte) { if (count != 0) { putchar(byte); - } else { + } + else { assert(control == 0); } @@ -27,7 +30,8 @@ void pair(size_t count, uint8_t byte) { } } -int main(int argc, char *argv[]) { +int main(int argc, char *argv[]) +{ size_t count = 1; uint8_t byte = getchar(); int new; @@ -36,7 +40,8 @@ int main(int argc, char *argv[]) { while ((new = getchar()) != EOF) { if (byte == new) { count++; - } else { + } + else { pair(count, byte); byte = new; count = 1; From 66b814a226c596faf14ac6c1522bb52413e8d454 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 14 Jun 2019 16:49:41 +0300 Subject: [PATCH 0870/1216] =?UTF-8?q?Don=E2=80=99t=20use=20libc=E2=80=99s?= =?UTF-8?q?=20random/rand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/gb.c | 34 ++++++++++++++++++---------------- Core/random.c | 38 ++++++++++++++++++++++++++++++++++++++ Core/random.h | 12 ++++++++++++ Core/sgb.c | 3 ++- Tester/main.c | 9 +++------ libretro/Makefile | 1 - 6 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 Core/random.c create mode 100644 Core/random.h diff --git a/Core/gb.c b/Core/gb.c index 6c0bd6c2..2f395a94 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -9,8 +9,10 @@ #include #include #endif +#include "random.h" #include "gb.h" + #ifdef DISABLE_REWIND #define GB_rewind_free(...) #define GB_rewind_push(...) @@ -565,7 +567,7 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_CGB_E: case GB_MODEL_AGB: /* Unverified */ for (unsigned i = 0; i < gb->ram_size; i++) { - gb->ram[i] = (random() & 0xFF); + gb->ram[i] = GB_random(); } break; @@ -573,12 +575,12 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ for (unsigned i = 0; i < gb->ram_size; i++) { - gb->ram[i] = (random() & 0xFF); + gb->ram[i] = GB_random(); if (i & 0x100) { - gb->ram[i] &= random(); + gb->ram[i] &= GB_random(); } else { - gb->ram[i] |= random(); + gb->ram[i] |= GB_random(); } } break; @@ -586,7 +588,7 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_SGB2: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = 0x55; - gb->ram[i] ^= random() & random() & random(); + gb->ram[i] ^= GB_random() & GB_random() & GB_random(); } break; @@ -596,7 +598,7 @@ static void reset_ram(GB_gameboy_t *gb) gb->ram[i] = 0; } else { - gb->ram[i] = (random() | random() | random() | random()) & 0xFF; + gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random(); } } break; @@ -609,7 +611,7 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_CGB_E: case GB_MODEL_AGB: for (unsigned i = 0; i < sizeof(gb->hram); i++) { - gb->hram[i] = (random() & 0xFF); + gb->hram[i] = GB_random(); } break; @@ -619,10 +621,10 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_SGB2: for (unsigned i = 0; i < sizeof(gb->hram); i++) { if (i & 1) { - gb->hram[i] = random() | random() | random(); + gb->hram[i] = GB_random() | GB_random() | GB_random(); } else { - gb->hram[i] = random() & random() & random(); + gb->hram[i] = GB_random() & GB_random() & GB_random(); } } break; @@ -642,10 +644,10 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_SGB2: for (unsigned i = 0; i < 8; i++) { if (i & 2) { - gb->oam[i] = random() & random() & random(); + gb->oam[i] = GB_random() & GB_random() & GB_random(); } else { - gb->oam[i] = random() | random() | random(); + gb->oam[i] = GB_random() | GB_random() | GB_random(); } } for (unsigned i = 8; i < sizeof(gb->oam); i++) { @@ -669,10 +671,10 @@ static void reset_ram(GB_gameboy_t *gb) uint8_t temp; for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { if (i & 1) { - temp = random() & random() & random(); + temp = GB_random() & GB_random() & GB_random(); } else { - temp = random() | random() | random(); + temp = GB_random() | GB_random() | GB_random(); } gb->apu.wave_channel.wave_form[i * 2] = temp >> 4; gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF; @@ -684,13 +686,13 @@ static void reset_ram(GB_gameboy_t *gb) } for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) { - gb->extra_oam[i] = (random() & 0xFF); + gb->extra_oam[i] = GB_random(); } if (GB_is_cgb(gb)) { for (unsigned i = 0; i < 64; i++) { - gb->background_palettes_data[i] = random() & 0xFF; /* Doesn't really matter as the boot ROM overrides it anyway*/ - gb->sprite_palettes_data[i] = random() & 0xFF; + gb->background_palettes_data[i] = GB_random(); /* Doesn't really matter as the boot ROM overrides it anyway*/ + gb->sprite_palettes_data[i] = GB_random(); } for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, true, i * 2); diff --git a/Core/random.c b/Core/random.c new file mode 100644 index 00000000..cc4d4d3a --- /dev/null +++ b/Core/random.c @@ -0,0 +1,38 @@ +#include "random.h" +#include + +static uint64_t seed; +static bool enabled = true; + +uint8_t GB_random(void) +{ + if (!enabled) return 0; + + seed *= 0x27BB2EE687B0B0FDL; + seed += 0xB504F32D; + return seed >> 56; +} + +uint32_t GB_random32(void) +{ + GB_random(); + return seed >> 32; +} + +void GB_random_seed(uint64_t new_seed) +{ + seed = new_seed; +} + +void GB_random_set_enabled(bool enable) +{ + enabled = enable; +} + +static void __attribute__((constructor)) init_seed(void) +{ + seed = time(NULL); + for (unsigned i = 64; i--;) { + GB_random(); + } +} diff --git a/Core/random.h b/Core/random.h new file mode 100644 index 00000000..8ab0e502 --- /dev/null +++ b/Core/random.h @@ -0,0 +1,12 @@ +#ifndef random_h +#define random_h + +#include +#include + +uint8_t GB_random(void); +uint32_t GB_random32(void); +void GB_random_seed(uint64_t seed); +void GB_random_set_enabled(bool enable); + +#endif /* random_h */ diff --git a/Core/sgb.c b/Core/sgb.c index 9e7e3822..6b1dbd09 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,4 +1,5 @@ #include "gb.h" +#include "random.h" #include #define INTRO_ANIMATION_LENGTH 200 @@ -682,7 +683,7 @@ static double fm_sweep(double phase) } static double random_double(void) { - return ((random() % 0x10001) - 0x8000) / (double) 0x8000; + return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000; } bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) diff --git a/Tester/main.c b/Tester/main.c index d9ce165b..dd126cb6 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -16,12 +16,7 @@ #endif #include - -/* Disable all randomness during automatic tests */ -long random(void) -{ - return 0; -} +#include static bool running = false; static char *filename; @@ -262,6 +257,8 @@ int main(int argc, char **argv) bool dmg = false; const char *boot_rom_path = NULL; + + GB_random_set_enabled(false); for (unsigned i = 1; i < argc; i++) { if (strcmp(argv[i], "--dmg") == 0) { diff --git a/libretro/Makefile b/libretro/Makefile index fffe71c8..75ddfc6c 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -243,7 +243,6 @@ else CC = gcc TARGET := $(TARGET_NAME)_libretro.dll SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined - CFLAGS += -Drandom=rand endif TARGET := $(CORE_DIR)/build/bin/$(TARGET) From b2397a2e7a25e1f451a389671889c771d4c3b570 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 14 Jun 2019 18:06:15 +0300 Subject: [PATCH 0871/1216] Joystick hat support in Cocoa --- Cocoa/GBJoystickListener.h | 1 + Cocoa/GBPreferencesWindow.m | 36 ++++++++++++++++++++ Cocoa/GBView.m | 21 ++++++++++++ Cocoa/joypad.m | 68 +++++++++++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 2 deletions(-) diff --git a/Cocoa/GBJoystickListener.h b/Cocoa/GBJoystickListener.h index 690fde97..069db103 100644 --- a/Cocoa/GBJoystickListener.h +++ b/Cocoa/GBJoystickListener.h @@ -4,5 +4,6 @@ - (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state; - (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value; +- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) value; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 982aa450..ecf03111 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -309,6 +309,42 @@ [self advanceConfigurationStateMachine]; } +- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state +{ + /* Hats are always mapped to the D-pad, ignore them on non-Dpad keys and skip the D-pad configuration if used*/ + if (!state) return; + if (joystick_configuration_state == -1) return; + if (joystick_configuration_state > GBDown) return; + if (!joystick_being_configured) { + joystick_being_configured = joystick_name; + } + else if (![joystick_being_configured isEqualToString:joystick_name]) { + return; + } + + NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy]; + + if (!all_mappings) { + all_mappings = [[NSMutableDictionary alloc] init]; + } + + NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy]; + + if (!mapping) { + mapping = [[NSMutableDictionary alloc] init]; + } + + for (joystick_configuration_state = 0;; joystick_configuration_state++) { + [mapping removeObjectForKey:GBButtonNames[joystick_configuration_state]]; + if (joystick_configuration_state == GBDown) break; + } + + all_mappings[joystick_name] = mapping; + [[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; + [self refreshJoypadMenu:nil]; + [self advanceConfigurationStateMachine]; +} + - (NSButton *)aspectRatioCheckbox { return _aspectRatioCheckbox; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 7e425c54..7f879010 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -341,6 +341,27 @@ } } +- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state +{ + unsigned player_count = GB_get_player_count(_gb); + + UpdateSystemActivity(UsrActivity); + for (unsigned player = 0; player < player_count; player++) { + NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] + objectForKey:[NSString stringWithFormat:@"%u", player]]; + if (player_count != 1 && // Single player, accpet inputs from all joypads + !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads + ![preferred_joypad isEqualToString:joystick_name]) { + continue; + } + assert(state + 1 < 9); + /* - N NE E SE S SW W NW */ + GB_set_key_state_for_player(_gb, GB_KEY_UP, player, (bool []){0, 1, 1, 0, 0, 0, 0, 0, 1}[state + 1]); + GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, (bool []){0, 0, 1, 1, 1, 0, 0, 0, 0}[state + 1]); + GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, (bool []){0, 0, 0, 0, 1, 1, 1, 0, 0}[state + 1]); + GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, (bool []){0, 0, 0, 0, 0, 0, 1, 1, 1}[state + 1]); + } +} - (BOOL)acceptsFirstResponder { diff --git a/Cocoa/joypad.m b/Cocoa/joypad.m index bca40973..2ffe56b8 100755 --- a/Cocoa/joypad.m +++ b/Cocoa/joypad.m @@ -53,6 +53,9 @@ struct _SDL_Joystick int nbuttons; /* Number of buttons on the joystick */ uint8_t *buttons; /* Current button states */ + int nhats; + uint8_t *hats; + struct joystick_hwdata *hwdata; /* Driver dependent information */ int ref_count; /* Reference count for multiple opens */ @@ -93,11 +96,13 @@ struct joystick_hwdata int axes; /* number of axis (calculated, not reported by device) */ int buttons; /* number of buttons (calculated, not reported by device) */ + int hats; int elements; /* number of total elements (should be total of above) (calculated, not reported by device) */ recElement *firstAxis; recElement *firstButton; - + recElement *firstHat; + bool removed; int instance_id; @@ -178,6 +183,30 @@ void SDL_PrivateJoystickButton(SDL_Joystick *joystick, uint8_t button, uint8_t s } } +void SDL_PrivateJoystickHat(SDL_Joystick *joystick, uint8_t hat, uint8_t state) +{ + + /* Make sure we're not getting garbage or duplicate events */ + if (hat >= joystick->nhats) { + return; + } + if (state == joystick->hats[hat]) { + return; + } + + /* Update internal joystick state */ + joystick->hats[hat] = state; + + NSResponder *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder]; + while (responder) { + if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) { + [responder joystick:@(joystick->name) hat:hat changedState:state]; + break; + } + responder = (typeof(responder)) [responder nextResponder]; + } +} + static void FreeElementList(recElement *pElement) { @@ -202,6 +231,7 @@ FreeDevice(recDevice *removeDevice) /* free element lists */ FreeElementList(removeDevice->firstAxis); FreeElementList(removeDevice->firstButton); + FreeElementList(removeDevice->firstHat); free(removeDevice); } @@ -315,6 +345,15 @@ AddHIDElement(const void *value, void *parameter) } } break; + case kHIDUsage_GD_Hatswitch: + if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) { + element = (recElement *) calloc(1, sizeof (recElement)); + if (element) { + pDevice->hats++; + headElement = &(pDevice->firstHat); + } + } + break; case kHIDUsage_GD_DPadUp: case kHIDUsage_GD_DPadDown: @@ -535,6 +574,27 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick) element = element->pNext; ++i; } + + element = device->firstHat; + i = 0; + while (element) { + signed range = (element->max - element->min + 1); + value = GetHIDElementState(device, element) - element->min; + if (range == 4) { /* 4 position hatswitch - scale up value */ + value *= 2; + } else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */ + value = -1; + } + if ((unsigned)value >= 8) { + value = -1; + } + + SDL_PrivateJoystickHat(joystick, i, value); + + element = element->pNext; + ++i; + } + } static void JoystickInputCallback( @@ -579,13 +639,17 @@ JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDevic joystick->naxes = device->axes; joystick->nbuttons = device->buttons; - + joystick->nhats = device->hats; + if (joystick->naxes > 0) { joystick->axes = (SDL_JoystickAxisInfo *) calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo)); } if (joystick->nbuttons > 0) { joystick->buttons = (uint8_t *) calloc(joystick->nbuttons, 1); } + if (joystick->nhats > 0) { + joystick->hats = (uint8_t *) calloc(joystick->nhats, 1); + } /* Get notified when this device is disconnected. */ IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device); From 8b7922b6798ef51288e5400e7a2e78ab810656d4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Jun 2019 03:42:53 +0300 Subject: [PATCH 0872/1216] Fix #144 by ignored malformed commands with 0 length --- Core/sgb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/sgb.c b/Core/sgb.c index 6b1dbd09..09449857 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -98,6 +98,9 @@ static void command_ready(GB_gameboy_t *gb) return; } + /* Ignore malformed commands (0 length)*/ + if ((gb->sgb->command[0] & 7) == 0) return; + switch (gb->sgb->command[0] >> 3) { case PAL01: pal_command(gb, 0, 1); From 083b4a2970fe514e23e2e23927b682322299c578 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Jun 2019 12:53:00 +0300 Subject: [PATCH 0873/1216] Fix joypad hat input in the menu in the SDL port --- SDL/gui.c | 1 + 1 file changed, 1 insertion(+) diff --git a/SDL/gui.c b/SDL/gui.c index 70a449db..9fb5ab22 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -913,6 +913,7 @@ void run_gui(bool is_running) event.key.keysym.scancode = scancode; } } + break; } case SDL_JOYAXISMOTION: { From e268efefeff5720a6a3ae29a7b88283849c05076 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Jun 2019 23:22:27 +0300 Subject: [PATCH 0874/1216] Redesign and reimplement the audio API, let the frontends handle more stuff. Probably affects #161 --- Cocoa/Document.m | 69 ++++++++++++++++++++++++++++++++- Core/apu.c | 84 +++++++--------------------------------- Core/apu.h | 18 +++------ Core/debugger.c | 42 ++++++++++---------- Core/gb.c | 3 -- Core/gb.h | 2 +- Core/memory.c | 4 +- Core/sgb.c | 25 ++++++++---- Core/sgb.h | 1 - Core/timing.c | 6 +-- SDL/main.c | 19 ++++----- libretro/Makefile.common | 1 + libretro/libretro.c | 21 +++------- 13 files changed, 146 insertions(+), 149 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 731f7c81..b99e4c8d 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -55,6 +55,12 @@ enum model { bool rewind; bool modelsChanging; + + NSCondition *audioLock; + GB_sample_t *audioBuffer; + size_t audioBufferSize; + size_t audioBufferPosition; + size_t audioBufferNeeded; } @property GBAudioClient *audioClient; @@ -67,6 +73,7 @@ enum model { - (void) printImage:(uint32_t *)image height:(unsigned) height topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin exposure:(unsigned) exposure; +- (void) gotNewSample:(GB_sample_t *)sample; @end static void vblank(GB_gameboy_t *gb) @@ -118,6 +125,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure]; } +static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self gotNewSample:sample]; +} + @implementation Document { GB_gameboy_t gb; @@ -133,6 +146,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; debugger_input_queue = [[NSMutableArray alloc] init]; console_output_lock = [[NSRecursiveLock alloc] init]; + audioLock = [[NSCondition alloc] init]; } return self; } @@ -184,6 +198,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); + GB_apu_set_sample_callback(&gb, audioCallback); } - (void) vblank @@ -201,13 +216,57 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } } +- (void)gotNewSample:(GB_sample_t *)sample +{ + [audioLock lock]; + if (self.audioClient.isPlaying) { + if (audioBufferPosition == audioBufferSize) { + if (audioBufferSize >= 0x4000) { + audioBufferPosition = 0; + [audioLock unlock]; + return; + } + + if (audioBufferSize == 0) { + audioBufferSize = 512; + } + else { + audioBufferSize += audioBufferSize >> 2; + } + audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize); + } + audioBuffer[audioBufferPosition++] = *sample; + } + if (audioBufferPosition == audioBufferNeeded) { + [audioLock signal]; + audioBufferNeeded = 0; + } + [audioLock unlock]; +} + - (void) run { running = true; GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { - GB_apu_copy_buffer(&gb, buffer, nFrames); + [audioLock lock]; + + if (audioBufferPosition < nFrames) { + audioBufferNeeded = nFrames; + [audioLock wait]; + } + + if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) { + memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer)); + memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer)); + audioBufferPosition = audioBufferPosition - nFrames; + } + else { + memcpy(buffer, audioBuffer + (audioBufferPosition - nFrames), nFrames * sizeof(*buffer)); + audioBufferPosition = 0; + } + [audioLock unlock]; } andSampleRate:96000]; if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { [self.audioClient start]; @@ -227,6 +286,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, } } [hex_timer invalidate]; + [audioLock lock]; + memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer)); + audioBufferPosition = audioBufferNeeded; + [audioLock signal]; + [audioLock unlock]; [self.audioClient stop]; self.audioClient = nil; self.view.mouseHidingEnabled = NO; @@ -329,6 +393,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (cameraImage) { CVBufferRelease(cameraImage); } + if (audioBuffer) { + free(audioBuffer); + } } - (void)windowControllerDidLoadNib:(NSWindowController *)aController { diff --git a/Core/apu.c b/Core/apu.c index 664530d3..5e975cb1 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "gb.h" #define likely(x) __builtin_expect((x), 1) @@ -117,7 +118,7 @@ static double smooth(double x) return 3*x*x - 2*x*x*x; } -static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) +static void render(GB_gameboy_t *gb) { GB_sample_t output = {0,0}; @@ -147,7 +148,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) } } - if (likely(gb->apu_output.last_update[i] == 0 || no_downsampling)) { + if (likely(gb->apu_output.last_update[i] == 0)) { output.left += gb->apu_output.current_sample[i].left * multiplier; output.right += gb->apu_output.current_sample[i].right * multiplier; } @@ -205,17 +206,9 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) } } - if (dest) { - *dest = filtered_output; - return; - } - - while (gb->apu_output.copy_in_progress); - while (!__sync_bool_compare_and_swap(&gb->apu_output.lock, false, true)); - if (gb->apu_output.buffer_position < gb->apu_output.buffer_size) { - gb->apu_output.buffer[gb->apu_output.buffer_position++] = filtered_output; - } - gb->apu_output.lock = false; + + assert(gb->apu_output.sample_callback); + gb->apu_output.sample_callback(gb, &filtered_output); } static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb) @@ -504,56 +497,12 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) { + if (gb->apu_output.sample_cycles >= gb->apu_output.cycles_per_sample) { gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; - render(gb, false, NULL); + render(gb); } } } - -void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) -{ - if (gb->sgb) { - if (GB_sgb_render_jingle(gb, dest, count)) return; - } - gb->apu_output.copy_in_progress = true; - - /* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */ - size_t buffer_position = gb->apu_output.buffer_position; - - if (!gb->apu_output.stream_started) { - // Intentionally fail the first copy to sync the stream with the Gameboy. - gb->apu_output.stream_started = true; - gb->apu_output.buffer_position = 0; - buffer_position = 0; - } - - if (count > buffer_position) { - // GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position); - GB_sample_t output; - render(gb, true, &output); - - for (unsigned i = 0; i < count - buffer_position; i++) { - dest[buffer_position + i] = output; - } - - if (buffer_position) { - if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) { - gb->apu_output.buffer_size += count - buffer_position; - gb->apu_output.buffer = realloc(gb->apu_output.buffer, - gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer)); - gb->apu_output.stream_started = false; - } - } - count = buffer_position; - } - memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer)); - memmove(gb->apu_output.buffer, gb->apu_output.buffer + count, (buffer_position - count) * sizeof(*gb->apu_output.buffer)); - gb->apu_output.buffer_position -= count; - - gb->apu_output.copy_in_progress = false; -} - void GB_apu_init(GB_gameboy_t *gb) { memset(&gb->apu, 0, sizeof(gb->apu)); @@ -1009,26 +958,21 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->io_registers[reg] = value; } -size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb) +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate) { - return gb->apu_output.buffer_position; -} -void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) -{ - if (gb->apu_output.buffer) { - free(gb->apu_output.buffer); - } - gb->apu_output.buffer_size = sample_rate / 25; // 40ms delay - gb->apu_output.buffer = malloc(gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer)); gb->apu_output.sample_rate = sample_rate; - gb->apu_output.buffer_position = 0; if (sample_rate) { gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); } GB_apu_update_cycles_per_sample(gb); } +void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) +{ + gb->apu_output.sample_callback = callback; +} + void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) { gb->apu_output.highpass_mode = mode; diff --git a/Core/apu.h b/Core/apu.h index f8f56e99..7f8acfcc 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -46,6 +46,8 @@ enum GB_CHANNELS { GB_N_CHANNELS }; +typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample); + typedef struct { bool global_enable; @@ -126,14 +128,6 @@ typedef enum { typedef struct { unsigned sample_rate; - GB_sample_t *buffer; - size_t buffer_size; - size_t buffer_position; - - bool stream_started; /* detects first copy request to minimize lag */ - volatile bool copy_in_progress; - volatile bool lock; - double sample_cycles; // In 8 MHz units double cycles_per_sample; @@ -147,13 +141,13 @@ typedef struct { GB_highpass_mode_t highpass_mode; double highpass_rate; GB_double_sample_t highpass_diff; + + GB_sample_callback_t sample_callback; } GB_apu_output_t; -void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate); -void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count); -size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb); +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); - +void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); #ifdef GB_INTERNAL bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); diff --git a/Core/debugger.c b/Core/debugger.c index 79b59911..68744334 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -455,12 +455,12 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; } } - GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string); + GB_log(gb, "Unknown register: %.*s\n", (unsigned) length, string); *error = true; return (lvalue_t){0,}; } - GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string); + GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned) length, string); *error = true; return (lvalue_t){0,}; } @@ -564,8 +564,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } // Search for lowest priority operator signed int depth = 0; - unsigned int operator_index = -1; - unsigned int operator_pos = 0; + unsigned operator_index = -1; + unsigned operator_pos = 0; for (int i = 0; i < length; i++) { if (string[i] == '(') depth++; else if (string[i] == ')') depth--; @@ -593,7 +593,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } } if (operator_index != -1) { - unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); + unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string)); value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); if (*error) goto exit; if (operators[operator_index].lvalue_operator) { @@ -661,7 +661,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, goto exit; } - GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); + GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned) length, string); *error = true; goto exit; } @@ -675,7 +675,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } uint16_t literal = (uint16_t) (strtol(string, &end, base)); if (end != string + length) { - GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); + GB_log(gb, "Failed to parse: %.*s\n", (unsigned) length, string); *error = true; goto exit; } @@ -880,13 +880,13 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const condition += strlen(" if "); /* Verify condition is sane (Todo: This might have side effects!) */ bool error; - debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, NULL, NULL); + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL); if (error) return true; } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); uint32_t key = BP_KEY(result); if (error) return true; @@ -949,7 +949,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); uint32_t key = BP_KEY(result); if (error) return true; @@ -1065,13 +1065,13 @@ print_usage: /* To make $new and $old legal */ uint16_t dummy = 0; uint8_t dummy2 = 0; - debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, &dummy, &dummy2); + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2); if (error) return true; } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); uint32_t key = WP_KEY(result); if (error) return true; @@ -1132,7 +1132,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); uint32_t key = WP_KEY(result); if (error) return true; @@ -1234,7 +1234,7 @@ static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to) } bool error; bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, - (unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; + (unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -1273,7 +1273,7 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); if (!error) { switch (modifiers[0]) { case 'a': @@ -1319,7 +1319,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de } bool error; - value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); uint16_t count = 32; if (modifiers) { @@ -1371,7 +1371,7 @@ static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, cons } bool error; - value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); uint16_t count = 5; if (modifiers) { @@ -1468,7 +1468,7 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const } GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); - for (unsigned int i = gb->backtrace_size; i--;) { + for (unsigned i = gb->backtrace_size; i--;) { GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); } @@ -1937,7 +1937,7 @@ static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, u } bool error; bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; + (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -1982,7 +1982,7 @@ static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) } bool error; bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; + (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; if (error) { /* Should never happen */ GB_log(gb, "An internal error has occured\n"); @@ -2169,7 +2169,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) } if (length == 0) continue; - unsigned int bank, address; + unsigned bank, address; char symbol[length]; if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { diff --git a/Core/gb.c b/Core/gb.c index 2f395a94..cbd295c3 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -135,9 +135,6 @@ void GB_free(GB_gameboy_t *gb) if (gb->rom) { free(gb->rom); } - if (gb->apu_output.buffer) { - free(gb->apu_output.buffer); - } if (gb->breakpoints) { free(gb->breakpoints); } diff --git a/Core/gb.h b/Core/gb.h index 5385710a..2d7d5991 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -555,7 +555,7 @@ struct GB_gameboy_internal_s { uint16_t addr_for_call_depth[0x200]; /* Backtrace */ - unsigned int backtrace_size; + unsigned backtrace_size; uint16_t backtrace_sps[0x200]; struct { uint16_t bank; diff --git a/Core/memory.c b/Core/memory.c index 9104cbdb..99ae1909 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -126,13 +126,13 @@ static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) if (!gb->rom_size) { return 0xFF; } - unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; + unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; return gb->rom[effective_address & (gb->rom_size - 1)]; } static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) { - unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; + unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; return gb->rom[effective_address & (gb->rom_size - 1)]; } diff --git a/Core/sgb.c b/Core/sgb.c index 09449857..323e10fb 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,6 +1,7 @@ #include "gb.h" #include "random.h" #include +#include #define INTRO_ANIMATION_LENGTH 200 @@ -461,8 +462,13 @@ static void render_boot_animation (GB_gameboy_t *gb) } } +static void render_jingle(GB_gameboy_t *gb, size_t count); void GB_sgb_render(GB_gameboy_t *gb) { + if (gb->apu_output.sample_rate) { + render_jingle(gb, gb->apu_output.sample_rate / (GB_get_clock_rate(gb) / LCDC_PERIOD)); + } + if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; if (!gb->screen || !gb->rgb_encode_callback) return; @@ -689,7 +695,7 @@ static double random_double(void) return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000; } -bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) +static void render_jingle(GB_gameboy_t *gb, size_t count) { const double frequencies[7] = { 466.16, // Bb4 @@ -701,15 +707,17 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) 1567.98, // G6 }; + assert(gb->apu_output.sample_callback); + if (gb->sgb->intro_animation < 0) { + GB_sample_t sample = {0, 0}; for (unsigned i = 0; i < count; i++) { - dest->left = dest->right = 0; - dest++; + gb->apu_output.sample_callback(gb, &sample); } - return true; + return; } - if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return false; + if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return; signed jingle_stage = (gb->sgb->intro_animation - 64) / 3; double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate; @@ -718,6 +726,7 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) sweep_cutoff_ratio = 1; } + GB_sample_t stereo; for (unsigned i = 0; i < count; i++) { double sample = 0; for (signed f = 0; f < 7 && f < jingle_stage; f++) { @@ -738,10 +747,10 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8; } - dest->left = dest->right = sample * 0x7000; - dest++; + stereo.left = stereo.right = sample * 0x7000; + gb->apu_output.sample_callback(gb, &stereo); } - return true; + return; } diff --git a/Core/sgb.h b/Core/sgb.h index 7e361988..2c6e8ee9 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -54,7 +54,6 @@ struct GB_sgb_s { void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); void GB_sgb_load_default_data(GB_gameboy_t *gb); -bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count); #endif diff --git a/Core/timing.c b/Core/timing.c index b14a00b1..5bc369b5 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -8,7 +8,7 @@ #include #endif -static const unsigned int GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; +static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; #ifndef DISABLE_TIMEKEEPING static int64_t get_nanoseconds(void) @@ -251,8 +251,8 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) /* Glitch only happens when old_tac is enabled. */ if (!(old_tac & 4)) return; - unsigned int old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; - unsigned int new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; + unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; + unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; /* The bit used for overflow testing must have been 1 */ if (gb->div_counter & old_clocks) { diff --git a/SDL/main.c b/SDL/main.c index be52afda..c0a05851 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -33,6 +33,7 @@ static double clock_mutliplier = 1.0; static char *filename = NULL; static typeof(free) *free_function = NULL; static char *battery_save_path_ptr; +static bool skip_audio; SDL_AudioDeviceID device_id; @@ -336,6 +337,8 @@ static void vblank(GB_gameboy_t *gb) } do_rewind = rewind_down; handle_events(gb); + + skip_audio = (SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t)) > have_aspec.freq / 20; } @@ -355,16 +358,12 @@ static void debugger_interrupt(int ignore) GB_debugger_break(&gb); } - -static void audio_callback(void *gb, Uint8 *stream, int len) +static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { - if (GB_is_inited(gb)) { - GB_apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); - } - else { - memset(stream, 0, len); - } + if (skip_audio) return; + SDL_QueueAudio(device_id, sample, sizeof(*sample)); } + static bool handle_pending_command(void) { @@ -436,6 +435,7 @@ restart: GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_update_input_hint_callback(&gb, handle_events); + GB_apu_set_sample_callback(&gb, gb_audio_callback); } SDL_DestroyTexture(texture); @@ -612,9 +612,6 @@ int main(int argc, char **argv) } #endif - - want_aspec.callback = audio_callback; - want_aspec.userdata = &gb; device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); /* Start Audio */ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index fb3a02f9..3bf1783f 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -12,6 +12,7 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/Core/sm83_cpu.c \ $(CORE_DIR)/Core/joypad.c \ $(CORE_DIR)/Core/save_state.c \ + $(CORE_DIR)/Core/random.c \ $(CORE_DIR)/libretro/agb_boot.c \ $(CORE_DIR)/libretro/cgb_boot.c \ $(CORE_DIR)/libretro/dmg_boot.c \ diff --git a/libretro/libretro.c b/libretro/libretro.c index f5e4e2e5..cec93f86 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -150,34 +150,22 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) } -static void audio_callback(void *gb) +static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { - size_t length = GB_apu_get_current_buffer_length(gb); - - while (length > sizeof(soundbuf) / 4) - { - GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, 1024); - audio_batch_cb(soundbuf, 1024); - length -= 1024; - } - if (length) { - GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, length); - audio_batch_cb(soundbuf, length); + if ((audio_out == GB_1 && gb == &gameboy[0]) || + (audio_out == GB_2 && gb == &gameboy[1])) { + audio_batch_cb((void*)sample, 1); } } static void vblank1(GB_gameboy_t *gb) { vblank1_occurred = true; - if (audio_out == GB_1) - audio_callback(gb); } static void vblank2(GB_gameboy_t *gb) { vblank2_occurred = true; - if (audio_out == GB_2) - audio_callback(gb); } static bool bit_to_send1 = true, bit_to_send2 = true; @@ -383,6 +371,7 @@ static void init_for_current_model(unsigned id) GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * ((model[i] == MODEL_SGB || model[i] == MODEL_SGB2) ? SGB_VIDEO_PIXELS : VIDEO_PIXELS))); GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); + GB_apu_set_sample_callback(&gameboy[i], audio_callback); /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); From 431f1f8199166578b051651e6ff1c0cbd48766ab Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 18 Jun 2019 23:16:28 +0300 Subject: [PATCH 0875/1216] Remove redundant calls to display_vblank on non-SGB models and in irregular FPS scenarios. Affects #161 --- Core/display.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 240e3c76..49723461 100644 --- a/Core/display.c +++ b/Core/display.c @@ -751,7 +751,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->current_lcd_line++; // Todo: unverified timing - if (gb->current_lcd_line == LINES) { + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { display_vblank(gb); } gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; @@ -915,14 +915,18 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { display_vblank(gb); + } gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; } else { gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { display_vblank(gb); } } + } GB_STAT_update(gb); GB_SLEEP(gb, display, 13, LINE_LENGTH - 4); From 280f609785b78d6d5f57ef4c86749cb22dd16250 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 19 Jun 2019 22:25:59 +0300 Subject: [PATCH 0876/1216] Fix under clock speed (Should have been 0.5, but ended up as ~0.4 due to rounding errors) --- Cocoa/GBView.m | 4 ++-- SDL/main.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 7f879010..5a851f34 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -148,11 +148,11 @@ - (void) flip { if (underclockKeyDown && clockMultiplier > 0.5) { - clockMultiplier -= 0.1; + clockMultiplier -= 1.0/16; GB_set_clock_multiplier(_gb, clockMultiplier); } if (!underclockKeyDown && clockMultiplier < 1.0) { - clockMultiplier += 0.1; + clockMultiplier += 1.0/16; GB_set_clock_multiplier(_gb, clockMultiplier); } current_buffer = (current_buffer + 1) % self.numberOfBuffers; diff --git a/SDL/main.c b/SDL/main.c index c0a05851..9dbea9b6 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -318,11 +318,11 @@ static void handle_events(GB_gameboy_t *gb) static void vblank(GB_gameboy_t *gb) { if (underclock_down && clock_mutliplier > 0.5) { - clock_mutliplier -= 0.1; + clock_mutliplier -= 1.0/16; GB_set_clock_multiplier(gb, clock_mutliplier); } else if (!underclock_down && clock_mutliplier < 1.0) { - clock_mutliplier += 0.1; + clock_mutliplier += 1.0/16; GB_set_clock_multiplier(gb, clock_mutliplier); } if (configuration.blend_frames) { From 91b0e491c5d7867b0faf2b8f4a73a3673f9c6816 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 19 Jun 2019 22:44:54 +0300 Subject: [PATCH 0877/1216] Increase the minimum required cycles for a sync, fix SGB jingle audio --- Core/sgb.c | 2 +- Core/timing.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index 323e10fb..89218aba 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -466,7 +466,7 @@ static void render_jingle(GB_gameboy_t *gb, size_t count); void GB_sgb_render(GB_gameboy_t *gb) { if (gb->apu_output.sample_rate) { - render_jingle(gb, gb->apu_output.sample_rate / (GB_get_clock_rate(gb) / LCDC_PERIOD)); + render_jingle(gb, gb->apu_output.sample_rate / (GB_get_clock_rate(gb) / (double)LCDC_PERIOD)); } if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; diff --git a/Core/timing.c b/Core/timing.c index 5bc369b5..138819fe 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -59,7 +59,7 @@ void GB_timing_sync(GB_gameboy_t *gb) return; } /* Prevent syncing if not enough time has passed.*/ - if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return; + if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); From 50a6a3e35c0599fc7cea4cf73ec99383a8492ef2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 19 Jun 2019 23:49:43 +0300 Subject: [PATCH 0878/1216] Fix libretro SGB1 FPS, fix un/serialization memory corruptions in libretro --- Core/gb.c | 5 +++ Core/gb.h | 2 +- Core/sgb.c | 2 +- libretro/libretro.c | 83 +++++++++++++++++++++++++-------------------- 4 files changed, 53 insertions(+), 39 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index cbd295c3..dec32a97 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -896,3 +896,8 @@ void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_ca { gb->update_input_hint_callback = callback; } + +double GB_get_usual_frame_rate(GB_gameboy_t *gb) +{ + return GB_get_clock_rate(gb) / (double)LCDC_PERIOD; +} diff --git a/Core/gb.h b/Core/gb.h index 2d7d5991..b943d85a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -695,7 +695,7 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); unsigned GB_get_screen_width(GB_gameboy_t *gb); unsigned GB_get_screen_height(GB_gameboy_t *gb); - +double GB_get_usual_frame_rate(GB_gameboy_t *gb); unsigned GB_get_player_count(GB_gameboy_t *gb); #endif /* GB_h */ diff --git a/Core/sgb.c b/Core/sgb.c index 89218aba..5bcb72d7 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -466,7 +466,7 @@ static void render_jingle(GB_gameboy_t *gb, size_t count); void GB_sgb_render(GB_gameboy_t *gb) { if (gb->apu_output.sample_rate) { - render_jingle(gb, gb->apu_output.sample_rate / (GB_get_clock_rate(gb) / (double)LCDC_PERIOD)); + render_jingle(gb, gb->apu_output.sample_rate / GB_get_usual_frame_rate(gb)); } if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; diff --git a/libretro/libretro.c b/libretro/libretro.c index cec93f86..1dd411c1 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -14,8 +14,6 @@ #define AUDIO_FREQUENCY 48000 #endif -#define FRAME_RATE (0x400000 / 70224.0) - #ifdef _WIN32 #include #include @@ -727,7 +725,7 @@ void retro_get_system_info(struct retro_system_info *info) void retro_get_system_av_info(struct retro_system_av_info *info) { struct retro_game_geometry geom; - struct retro_system_timing timing = { FRAME_RATE, AUDIO_FREQUENCY }; + struct retro_system_timing timing = { GB_get_usual_frame_rate(&gameboy[0]), AUDIO_FREQUENCY }; if (emulated_devices == 2) { @@ -947,11 +945,11 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_dual); check_variables(); - frame_buf = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - frame_buf_copy = (uint32_t*)malloc(emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf = (uint32_t*)malloc(emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf_copy = (uint32_t*)malloc(emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); - memset(frame_buf, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); - memset(frame_buf_copy, 0, emulated_devices * VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf, 0, emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); + memset(frame_buf_copy, 0, emulated_devices * SGB_VIDEO_PIXELS * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) @@ -987,54 +985,65 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - if (emulated_devices == 2) - return GB_get_save_state_size(&gameboy[0]) + GB_get_save_state_size(&gameboy[1]); - else - return GB_get_save_state_size(&gameboy[0]); + static size_t maximum_save_size = 0; + if (maximum_save_size) { + return maximum_save_size * 2; + } + + GB_gameboy_t temp; + + GB_init(&temp, GB_MODEL_DMG_B); + maximum_save_size = GB_get_save_state_size(&temp); + GB_free(&temp); + + GB_init(&temp, GB_MODEL_CGB_E); + maximum_save_size = MAX(maximum_save_size, GB_get_save_state_size(&temp)); + GB_free(&temp); + + GB_init(&temp, GB_MODEL_SGB2); + maximum_save_size = MAX(maximum_save_size, GB_get_save_state_size(&temp)); + GB_free(&temp); + + return maximum_save_size * 2; } bool retro_serialize(void *data, size_t size) { - if (!initialized) + if (!initialized || !data) return false; - void* save_data[2]; - size_t state_size[2]; size_t offset = 0; - for (int i = 0; i < emulated_devices; i++) - { - state_size[i] = GB_get_save_state_size(&gameboy[i]); - save_data[i] = (uint8_t*)malloc(state_size[i]); - GB_save_state_to_buffer(&gameboy[i], (uint8_t*) save_data[i]); - memcpy(data + offset, save_data[i], state_size[i]); - offset += state_size[i]; - free(save_data[i]); + for (int i = 0; i < emulated_devices; i++) { + size_t state_size = GB_get_save_state_size(&gameboy[i]); + if (state_size > size) { + return false; + } + + GB_save_state_to_buffer(&gameboy[i], ((uint8_t *) data) + offset); + offset += state_size; + size -= state_size; } - if (data) - return true; - else - return false; + return true; } bool retro_unserialize(const void *data, size_t size) { - void* save_data[2]; - size_t state_size[2]; - int ret; - for (int i = 0; i < emulated_devices; i++) { - state_size[i] = GB_get_save_state_size(&gameboy[i]); - save_data[i] = (uint8_t*)malloc(state_size[i]); - memcpy (save_data[i], data + (state_size[i] * i), state_size[i]); - ret = GB_load_state_from_buffer(&gameboy[i], save_data[i], state_size[i]); - free(save_data[i]); - - if (ret != 0) + size_t state_size = GB_get_save_state_size(&gameboy[i]); + if (state_size > size) { return false; + } + + if (GB_load_state_from_buffer(&gameboy[i], data, state_size)) { + return false; + } + + size -= state_size; + data = ((uint8_t *)data) + state_size; } return true; From 72b1fe05001bdd0c5c0f3f49729ef708195f0e65 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 01:03:52 +0300 Subject: [PATCH 0879/1216] =?UTF-8?q?Minor=20Fixes=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cocoa/Document.xib | 12 ++++++------ Core/debugger.c | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index d5582165..ae9cf909 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -397,8 +397,8 @@ - - + + @@ -430,8 +430,8 @@ - - + + @@ -448,8 +448,8 @@ - - + + diff --git a/Core/debugger.c b/Core/debugger.c index 68744334..d7fbdf8c 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -819,7 +819,7 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ (gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', - (gb->f & GB_SUBSTRACT_FLAG)? 'S' : '-', + (gb->f & GB_SUBSTRACT_FLAG)? 'N' : '-', (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); @@ -1766,9 +1766,9 @@ static const debugger_command_t commands[] = { {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ {"apu", 3, apu, "Displays information about the current state of the audio chip"}, - {"wave", 3, wave, "Prints a visual representation of the wave RAM" HELP_NEWLINE + {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE - "a more (c)ompact one, or a one-(l)iner"}, + "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE From 7c61445fe3203c591429d8d123cad6191aa9874f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 03:04:38 +0300 Subject: [PATCH 0880/1216] Fixed out of bound read in GB_load_state_from_buffer. Closes #104 --- Core/save_state.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/save_state.c b/Core/save_state.c index 1cd34587..5a7d9207 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -294,6 +294,8 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v return false; } + if (saved_size > *buffer_length) return false; + if (saved_size <= size) { if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) { return false; From 24b58da8c6dd05c8d138fd55f482f64d7265c593 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 14:18:48 +0300 Subject: [PATCH 0881/1216] Minor text change --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index d7fbdf8c..357a896d 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1775,7 +1775,7 @@ static const debugger_command_t commands[] = { "Can also modify the condition of existing breakpoints." HELP_NEWLINE "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE "jumping to the target.", - "[ if ]", "(j)"}, + "[ if ]", "j"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE From 36a87f96bd800fb07f33cb5e4b41a3c27354a88b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 16:58:56 +0300 Subject: [PATCH 0882/1216] Formatting --- Core/display.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 49723461..cf532167 100644 --- a/Core/display.c +++ b/Core/display.c @@ -916,17 +916,17 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { - display_vblank(gb); + display_vblank(gb); } gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; } else { gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { - display_vblank(gb); + display_vblank(gb); + } } } - } GB_STAT_update(gb); GB_SLEEP(gb, display, 13, LINE_LENGTH - 4); From 72d1d9b1540aaf9b3973bc2dcb2f3dae6983f3bd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 17:08:10 +0300 Subject: [PATCH 0883/1216] Fix Windows build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ee1e56e4..f666d7f5 100644 --- a/Makefile +++ b/Makefile @@ -327,7 +327,7 @@ $(OBJ)/%.1bpp: %.png rgbgfx -d 1 -h -o $@ $< $(OBJ)/BootROMs/SameBoyLogo.rle: $(OBJ)/BootROMs/SameBoyLogo.1bpp build/logo-compress - build/logo-compress < $< > $@ + ./build/logo-compress < $< > $@ build/logo-compress: BootROMs/logo-compress.c $(CC) $< -o $@ From b478b5b568a1b3bbbd3c9547a74917c6aae36c48 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 17:20:18 +0300 Subject: [PATCH 0884/1216] Fix bugged mouse support on some platforms --- SDL/gui.c | 1 + 1 file changed, 1 insertion(+) diff --git a/SDL/gui.c b/SDL/gui.c index 9fb5ab22..07233842 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -883,6 +883,7 @@ void run_gui(bool is_running) } } + break; case SDL_JOYBUTTONDOWN: event.type = SDL_KEYDOWN; joypad_button_t button = get_joypad_button(event.jbutton.button); From f1b578fd2ee9d5c140b0dcaedfa1a2af9ed6278c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jun 2019 19:20:51 +0300 Subject: [PATCH 0885/1216] Update version to 0.12, update copyright year --- Cocoa/Info.plist | 2 +- Cocoa/License.html | 2 +- LICENSE | 2 +- Makefile | 2 +- QuickLook/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 65f0df85..dd801cb2 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -70,7 +70,7 @@ LSMinimumSystemVersion 10.9 NSHumanReadableCopyright - Copyright © 2015-2018 Lior Halphon + Copyright © 2015-2019 Lior Halphon NSMainNibFile MainMenu NSPrincipalClass diff --git a/Cocoa/License.html b/Cocoa/License.html index 26731434..49851fd5 100644 --- a/Cocoa/License.html +++ b/Cocoa/License.html @@ -30,7 +30,7 @@

    SameBoy

    MIT License

    -

    Copyright © 2015-2018 Lior Halphon

    +

    Copyright © 2015-2019 Lior Halphon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index 63c6787e..94966be8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2018 Lior Halphon +Copyright (c) 2015-2019 Lior Halphon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index f666d7f5..4bc4f3da 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.11.1 +VERSION := 0.12 export VERSION CONF ?= debug diff --git a/QuickLook/Info.plist b/QuickLook/Info.plist index 051e26c5..95402149 100644 --- a/QuickLook/Info.plist +++ b/QuickLook/Info.plist @@ -47,7 +47,7 @@ CFPlugInUnloadFunction NSHumanReadableCopyright - Copyright © 2015-2018 Lior Halphon + Copyright © 2015-2019 Lior Halphon QLNeedsToBeRunInMainThread QLPreviewHeight From 970a5f562b6092f697233dac7e03497908bd710a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 22 Jun 2019 18:16:55 +0300 Subject: [PATCH 0886/1216] Fix #183 --- BootROMs/logo-compress.c | 13 +++++++++++-- Makefile | 9 ++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/BootROMs/logo-compress.c b/BootROMs/logo-compress.c index ff291436..2274eb28 100644 --- a/BootROMs/logo-compress.c +++ b/BootROMs/logo-compress.c @@ -1,6 +1,10 @@ #include #include -#include +#include +#ifdef _WIN32 +#include +#include +#endif void pair(size_t count, uint8_t byte) { @@ -35,7 +39,12 @@ int main(int argc, char *argv[]) size_t count = 1; uint8_t byte = getchar(); int new; - size_t position = 0; + size_t position = 0; + +#ifdef _WIN32 + _setmode(0,_O_BINARY); + _setmode(1,_O_BINARY); +#endif while ((new = getchar()) != EOF) { if (byte == new) { diff --git a/Makefile b/Makefile index 4bc4f3da..02466160 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,11 @@ ifneq ($(findstring MSYS,$(PLATFORM)),) PLATFORM := windows32 endif +LOGO_COMPRESS := build/logo-compress + ifeq ($(PLATFORM),windows32) _ := $(shell chcp 65001) +LOGO_COMPRESS := build/logo-compress.exe endif ifeq ($(PLATFORM),Darwin) @@ -326,10 +329,10 @@ $(OBJ)/%.1bpp: %.png -@$(MKDIR) -p $(dir $@) rgbgfx -d 1 -h -o $@ $< -$(OBJ)/BootROMs/SameBoyLogo.rle: $(OBJ)/BootROMs/SameBoyLogo.1bpp build/logo-compress - ./build/logo-compress < $< > $@ +$(OBJ)/BootROMs/SameBoyLogo.rle: $(OBJ)/BootROMs/SameBoyLogo.1bpp $(LOGO_COMPRESS) + $(realpath $(LOGO_COMPRESS)) < $< > $@ -build/logo-compress: BootROMs/logo-compress.c +$(LOGO_COMPRESS): BootROMs/logo-compress.c $(CC) $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm From 080fde08b6fd0f4bc2af05345c8dd2f1ef239cce Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 22 Jun 2019 19:01:35 +0300 Subject: [PATCH 0887/1216] Improve audio quality on the SDL port by being more forgiving to system with bigger buffer sizes --- SDL/main.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 9dbea9b6..e0b40e8a 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -33,7 +33,6 @@ static double clock_mutliplier = 1.0; static char *filename = NULL; static typeof(free) *free_function = NULL; static char *battery_save_path_ptr; -static bool skip_audio; SDL_AudioDeviceID device_id; @@ -337,8 +336,6 @@ static void vblank(GB_gameboy_t *gb) } do_rewind = rewind_down; handle_events(gb); - - skip_audio = (SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t)) > have_aspec.freq / 20; } @@ -360,7 +357,9 @@ static void debugger_interrupt(int ignore) static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { - if (skip_audio) return; + if ((SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t)) > have_aspec.freq / 12) { + return; + } SDL_QueueAudio(device_id, sample, sizeof(*sample)); } From 23229f1118f42b76cdbb650961aa62b48ebb846a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 22 Jun 2019 19:12:12 +0300 Subject: [PATCH 0888/1216] Update version to 0.12.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 02466160..401fa3a4 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.12 +VERSION := 0.12.1 export VERSION CONF ?= debug From 72be66414d82495b28637f1f248aed50879c41ba Mon Sep 17 00:00:00 2001 From: Aaron Kling Date: Thu, 23 May 2019 15:54:39 -0500 Subject: [PATCH 0889/1216] libretro: jni: Switch stl to c++ in preparation for ndk r20 --- libretro/jni/Application.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/jni/Application.mk b/libretro/jni/Application.mk index 219fdf26..a169e740 100644 --- a/libretro/jni/Application.mk +++ b/libretro/jni/Application.mk @@ -1,2 +1,2 @@ -APP_STL := gnustl_static +APP_STL := c++_static APP_ABI := all From 4541efe86ab169cd6dc9895e44f7d830ff8e3c02 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 29 Jun 2019 14:03:42 +0300 Subject: [PATCH 0890/1216] =?UTF-8?q?Fixed=20a=20bug=20that=20prevented=20?= =?UTF-8?q?writing=20to=20the=20wave=20RAM,=20as=20well=20as=20a=20bug=20w?= =?UTF-8?q?here=20the=20wave=20RAM=20was=20treated=20as=20zeros=20despite?= =?UTF-8?q?=20not=20being=20zero=E2=80=99d=20out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 5e975cb1..ee581385 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -506,6 +506,11 @@ void GB_apu_run(GB_gameboy_t *gb) void GB_apu_init(GB_gameboy_t *gb) { memset(&gb->apu, 0, sizeof(gb->apu)); + /* Restore the wave form */ + for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) { + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; + } gb->apu.lf_div = 1; /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, the first DIV/APU event is skipped. */ @@ -556,14 +561,14 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { - if (!gb->apu.global_enable && reg != GB_IO_NR52 && (GB_is_cgb(gb) || - ( - reg != GB_IO_NR11 && - reg != GB_IO_NR21 && - reg != GB_IO_NR31 && - reg != GB_IO_NR41 - ) - )) { + if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) || + ( + reg != GB_IO_NR11 && + reg != GB_IO_NR21 && + reg != GB_IO_NR31 && + reg != GB_IO_NR41 + ) + )) { return; } From 6b06d07bcc204d4ef05a358a60243f548e1e068b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Jul 2019 01:53:06 +0300 Subject: [PATCH 0891/1216] More attempts to improve audio in the SDL frontend --- SDL/main.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index e0b40e8a..df6837fa 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -94,6 +94,7 @@ static void open_menu(void) } run_gui(true); if (audio_playing) { + SDL_ClearQueuedAudio(device_id); SDL_PauseAudioDevice(device_id, 0); } GB_set_color_correction_mode(&gb, configuration.color_correction_mode); @@ -130,6 +131,7 @@ static void handle_events(GB_gameboy_t *gb) GB_set_key_state(gb, (GB_key_t) button, event.type == SDL_JOYBUTTONDOWN); } else if (button == JOYPAD_BUTTON_TURBO) { + SDL_ClearQueuedAudio(device_id); turbo_down = event.type == SDL_JOYBUTTONDOWN; GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); } @@ -239,7 +241,6 @@ static void handle_events(GB_gameboy_t *gb) paused = !paused; } break; - case SDL_SCANCODE_M: if (event.key.keysym.mod & MODIFIER) { #ifdef __APPLE__ @@ -253,6 +254,7 @@ static void handle_events(GB_gameboy_t *gb) SDL_PauseAudioDevice(device_id, 1); } else if (!audio_playing) { + SDL_ClearQueuedAudio(device_id); SDL_PauseAudioDevice(device_id, 0); } } @@ -288,6 +290,7 @@ static void handle_events(GB_gameboy_t *gb) case SDL_KEYUP: // Fallthrough if (event.key.keysym.scancode == configuration.keys[8]) { turbo_down = event.type == SDL_KEYDOWN; + SDL_ClearQueuedAudio(device_id); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); } else if (event.key.keysym.scancode == configuration.keys_2[0]) { @@ -356,11 +359,24 @@ static void debugger_interrupt(int ignore) } static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) -{ - if ((SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t)) > have_aspec.freq / 12) { +{ + if (turbo_down) { + static unsigned skip = 0; + skip++; + if (skip == have_aspec.freq / 8) { + skip = 0; + } + if (skip > have_aspec.freq / 16) { + return; + } + } + + if (SDL_GetQueuedAudioSize(device_id) / sizeof(*sample) > have_aspec.freq / 4) { return; } + SDL_QueueAudio(device_id, sample, sizeof(*sample)); + } @@ -599,20 +615,14 @@ int main(int argc, char **argv) want_aspec.samples = 2048; } #else - if (sdl_version >= 2006) { - /* SDL 2.0.6 offers WASAPI support which allows for much lower audio buffer lengths which at least - theoretically reduces lagging. */ - want_aspec.samples = 32; - } - else { + if (sdl_version < 2006) { /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency to 44100 because otherwise we would get garbled audio output.*/ want_aspec.freq = 44100; } #endif - device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - + device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); /* Start Audio */ SDL_EventState(SDL_DROPFILE, SDL_ENABLE); From 30a58ecd5cc3e40fe6f0996cd97d6beff7f9141a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Jul 2019 01:53:20 +0300 Subject: [PATCH 0892/1216] Use color correction in the QL previewer --- QuickLook/get_image_for_rom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/QuickLook/get_image_for_rom.c b/QuickLook/get_image_for_rom.c index e0482ec7..3950dac8 100755 --- a/QuickLook/get_image_for_rom.c +++ b/QuickLook/get_image_for_rom.c @@ -58,6 +58,7 @@ int get_image_for_rom(const char *filename, const char *boot_path, uint32_t *out GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_async_input_callback(&gb, async_input_callback); GB_set_log_callback(&gb, log_callback); + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); if (GB_load_rom(&gb, filename)) { GB_free(&gb); From f55c2549595e9380f005f1133f043d3d98db0fe1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Jul 2019 02:18:25 +0300 Subject: [PATCH 0893/1216] Fixed a regression that made ly_lyc_0_write and ly_lyc_write fail --- Core/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 99ae1909..868a245c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -663,7 +663,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* These are the states when LY changes, let the display routine call GB_STAT_update for use so it correctly handles T-cycle accurate LYC writes */ if (!GB_is_cgb(gb) || ( - gb->display_state != 6 && + gb->display_state != 35 && gb->display_state != 26 && gb->display_state != 15 && gb->display_state != 16)) { From 8c8d5afe6230433adbb5d27902980cfde848b9be Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 13 Jul 2019 17:17:55 +0300 Subject: [PATCH 0894/1216] Make the debugger compatible with more sym formats --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 357a896d..df480f34 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -2172,7 +2172,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) unsigned bank, address; char symbol[length]; - if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { + if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) { bank &= 0x1FF; if (!gb->bank_symbols[bank]) { gb->bank_symbols[bank] = GB_map_alloc(); From 9f7255cd239c55af4356d76e22e1c57d20c164ba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 13 Jul 2019 20:29:11 +0300 Subject: [PATCH 0895/1216] Make the automation results more consistent across revisions, and making use of this change as a chance to add color correction to the automation --- Core/gb.h | 2 +- Tester/main.c | 36 ++++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index b943d85a..7cebbce1 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -623,7 +623,7 @@ void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model); -/* Returns the time passed, in 4MHz ticks. */ +/* Returns the time passed, in 8MHz ticks. */ uint8_t GB_run(GB_gameboy_t *gb); /* Returns the time passed since the last frame, in nanoseconds */ uint64_t GB_run_frame(GB_gameboy_t *gb); diff --git a/Tester/main.c b/Tester/main.c index dd126cb6..03b99850 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -50,12 +50,12 @@ static char *async_input_callback(GB_gameboy_t *gb) return NULL; } -static void vblank(GB_gameboy_t *gb) +static void handle_buttons(GB_gameboy_t *gb) { /* Do not press any buttons during the last two seconds, this might cause a - screenshot to be taken while the LCD is off if the press makes the game - load graphics. */ - if (push_start_a && (frames < test_length - 120 || do_not_stop)) { + screenshot to be taken while the LCD is off if the press makes the game + load graphics. */ + if (push_start_a && (frames < test_length - 120 || do_not_stop)) { unsigned combo_length = 40; if (start_is_not_first || push_a_twice) combo_length = 60; /* The start item in the menu is not the first, so also push down */ else if (a_is_bad || start_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ @@ -109,7 +109,11 @@ static void vblank(GB_gameboy_t *gb) } } } - + +} + +static void vblank(GB_gameboy_t *gb) +{ /* Detect common crashes and stop the test early */ if (frames < test_length - 1) { if (gb->backtrace_size >= 0x200 + (large_stack? 0x80: 0) || (!allow_weird_sp_values && (gb->registers[GB_REGISTER_SP] >= 0xfe00 && gb->registers[GB_REGISTER_SP] < 0xff80))) { @@ -123,7 +127,7 @@ static void vblank(GB_gameboy_t *gb) } } - if (frames >= test_length ) { + if (frames >= test_length && !gb->disable_rendering) { bool is_screen_blank = true; for (unsigned i = 160*144; i--;) { if (bitmap[i] != bitmap[0]) { @@ -147,11 +151,9 @@ static void vblank(GB_gameboy_t *gb) running = false; } } - else if (frames == test_length - 1) { + else if (frames >= test_length - 1) { gb->disable_rendering = false; } - - frames++; } static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -339,6 +341,7 @@ int main(int argc, char **argv) GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_log_callback(&gb, log_callback); GB_set_async_input_callback(&gb, async_input_callback); + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); if (GB_load_rom(&gb, filename)) { perror("Failed to load ROM"); @@ -353,11 +356,14 @@ int main(int argc, char **argv) /* Restarting in Puzzle Boy/Kwirk (Start followed by A) leaks stack. */ strcmp((const char *)(gb.rom + 0x134), "KWIRK") == 0 || strcmp((const char *)(gb.rom + 0x134), "PUZZLE BOY") == 0; - start_is_bad = strcmp((const char *)(gb.rom + 0x134), "BLUESALPHA") == 0; + start_is_bad = strcmp((const char *)(gb.rom + 0x134), "BLUESALPHA") == 0 || + strcmp((const char *)(gb.rom + 0x134), "ONI 5") == 0; b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0 || strcmp((const char *)(gb.rom + 0x134), "SOCCER") == 0 || strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0; - push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0; + push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0 || + strcmp((const char *)(gb.rom + 0x134), "HUGO2 1/2") == 0 || + strcmp((const char *)(gb.rom + 0x134), "HUGO") == 0; push_slower = strcmp((const char *)(gb.rom + 0x134), "BAKENOU") == 0; do_not_stop = strcmp((const char *)(gb.rom + 0x134), "SPACE INVADERS") == 0; push_right = memcmp((const char *)(gb.rom + 0x134), "BOB ET BOB", strlen("BOB ET BOB")) == 0 || @@ -397,8 +403,14 @@ int main(int argc, char **argv) running = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; frames = 0; + unsigned cycles = 0; while (running) { - GB_run(&gb); + cycles += GB_run(&gb); + if (cycles >= 139810) { /* Approximately 1/60 a second. Intentionally not the actual length of a frame. */ + handle_buttons(&gb); + cycles -= 139810; + frames++; + } /* This early crash test must not run in vblank because PC might not point to the next instruction. */ if (gb.pc == 0x38 && frames < test_length - 1 && GB_read_memory(&gb, 0x38) == 0xFF) { GB_log(&gb, "The game is probably stuck in an FF loop.\n"); From 2bfe9226501191a6ab355792b98b343816843830 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 15 Jul 2019 20:47:16 +0300 Subject: [PATCH 0896/1216] Allow emulating an SGB without SFC HLE --- Core/display.c | 4 ++-- Core/gb.c | 13 +++++++++---- Core/gb.h | 8 ++++++-- Core/save_state.c | 14 +++++++------- Core/sgb.c | 4 ++++ 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Core/display.c b/Core/display.c index cf532167..cdcdeb2f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -123,8 +123,8 @@ static void display_vblank(GB_gameboy_t *gb) { gb->vblank_just_occured = true; - /* TODO: Slow in trubo mode! */ - if (GB_is_sgb(gb)) { + /* TODO: Slow in turbo mode! */ + if (GB_is_hle_sgb(gb)) { GB_sgb_render(gb); } diff --git a/Core/gb.c b/Core/gb.c index dec32a97..6f846caa 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -533,6 +533,11 @@ bool GB_is_cgb(GB_gameboy_t *gb) } bool GB_is_sgb(GB_gameboy_t *gb) +{ + return (gb->model & ~GB_MODEL_PAL_BIT & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB || (gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2; +} + +bool GB_is_hle_sgb(GB_gameboy_t *gb) { return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2; } @@ -745,7 +750,7 @@ void GB_reset(GB_gameboy_t *gb) gb->accessed_oam_row = -1; - if (GB_is_sgb(gb)) { + if (GB_is_hle_sgb(gb)) { if (!gb->sgb) { gb->sgb = malloc(sizeof(*gb->sgb)); } @@ -879,17 +884,17 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb) unsigned GB_get_screen_width(GB_gameboy_t *gb) { - return GB_is_sgb(gb)? 256 : 160; + return GB_is_hle_sgb(gb)? 256 : 160; } unsigned GB_get_screen_height(GB_gameboy_t *gb) { - return GB_is_sgb(gb)? 224 : 144; + return GB_is_hle_sgb(gb)? 224 : 144; } unsigned GB_get_player_count(GB_gameboy_t *gb) { - return GB_is_sgb(gb)? gb->sgb->player_count : 1; + return GB_is_hle_sgb(gb)? gb->sgb->player_count : 1; } void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback) diff --git a/Core/gb.h b/Core/gb.h index 7cebbce1..6d102839 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -29,6 +29,7 @@ #define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_CGB_FAMILY 0x200 #define GB_MODEL_PAL_BIT 0x1000 +#define GB_MODEL_NO_SFC_BIT 0x2000 #if __clang__ #define UNROLL _Pragma("unroll") @@ -67,9 +68,11 @@ typedef enum { // GB_MODEL_DMG_C = 0x003, GB_MODEL_SGB = 0x004, GB_MODEL_SGB_NTSC = GB_MODEL_SGB, - GB_MODEL_SGB_PAL = 0x1004, + GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT, + GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, // GB_MODEL_MGB = 0x100, GB_MODEL_SGB2 = 0x101, + GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, // GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, // GB_MODEL_CGB_B = 0x202, @@ -617,7 +620,8 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) void GB_init(GB_gameboy_t *gb, GB_model_t model); bool GB_is_inited(GB_gameboy_t *gb); bool GB_is_cgb(GB_gameboy_t *gb); -bool GB_is_sgb(GB_gameboy_t *gb); +bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 +bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd GB_model_t GB_get_model(GB_gameboy_t *gb); void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); diff --git a/Core/save_state.c b/Core/save_state.c index 5a7d9207..8ef99aed 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -36,7 +36,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) if (!DUMP_SECTION(gb, f, rtc )) goto error; if (!DUMP_SECTION(gb, f, video )) goto error; - if (GB_is_sgb(gb)) { + if (GB_is_hle_sgb(gb)) { if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; } @@ -73,7 +73,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + GB_SECTION_SIZE(apu ) + sizeof(uint32_t) + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t) + GB_SECTION_SIZE(video ) + sizeof(uint32_t) - + (GB_is_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + gb->mbc_ram_size + gb->ram_size + gb->vram_size; @@ -105,7 +105,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) DUMP_SECTION(gb, buffer, rtc ); DUMP_SECTION(gb, buffer, video ); - if (GB_is_sgb(gb)) { + if (GB_is_hle_sgb(gb)) { buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb)); } @@ -161,8 +161,8 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) return false; } - if (GB_is_sgb(gb) != GB_is_sgb(save)) { - GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_sgb(save)? "" : "not "); + if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) { + GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not "); return false; } @@ -223,7 +223,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) goto error; } - if (GB_is_sgb(gb)) { + if (GB_is_hle_sgb(gb)) { if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; } @@ -334,7 +334,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return -1; } - if (GB_is_sgb(gb)) { + if (GB_is_hle_sgb(gb)) { if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1; } diff --git a/Core/sgb.c b/Core/sgb.c index 5bcb72d7..a422af05 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -296,6 +296,10 @@ static void command_ready(GB_gameboy_t *gb) void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { if (!GB_is_sgb(gb)) return; + if (!GB_is_hle_sgb(gb)) { + /* Notify via callback */ + return; + } if (gb->sgb->disable_commands) return; if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return; From e1873ad2ec5833aaf963fe57e4972c24c5c2a30a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 15 Jul 2019 22:01:41 +0300 Subject: [PATCH 0897/1216] Add JOYP write callback API --- Core/gb.c | 5 +++++ Core/gb.h | 5 +++++ Core/sgb.c | 3 +++ 3 files changed, 13 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 6f846caa..a1d0f19f 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -906,3 +906,8 @@ double GB_get_usual_frame_rate(GB_gameboy_t *gb) { return GB_get_clock_rate(gb) / (double)LCDC_PERIOD; } + +void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback) +{ + gb->joyp_write_callback = callback; +} diff --git a/Core/gb.h b/Core/gb.h index 6d102839..b82d743b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -242,6 +242,7 @@ typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value); typedef struct { bool state; @@ -533,6 +534,7 @@ struct GB_gameboy_internal_s { GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback; GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; GB_update_input_hint_callback_t update_input_hint_callback; + GB_joyp_write_callback_t joyp_write_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -691,6 +693,9 @@ bool GB_serial_get_data_bit(GB_gameboy_t *gb); void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data); void GB_disconnect_serial(GB_gameboy_t *gb); + +/* For integration with SFC/SNES emulators */ +void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); #ifdef GB_INTERNAL uint32_t GB_get_clock_rate(GB_gameboy_t *gb); diff --git a/Core/sgb.c b/Core/sgb.c index a422af05..ccc439ab 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -295,6 +295,9 @@ static void command_ready(GB_gameboy_t *gb) void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { + if (gb->joyp_write_callback) { + gb->joyp_write_callback(gb, value); + } if (!GB_is_sgb(gb)) return; if (!GB_is_hle_sgb(gb)) { /* Notify via callback */ From 346e499602babe09288c76386a33e9528eb79d3a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 15 Jul 2019 23:02:58 +0300 Subject: [PATCH 0898/1216] ICD APIs --- Core/display.c | 24 +++++++++++++++++++----- Core/gb.c | 27 +++++++++++++++++++++++++-- Core/gb.h | 11 +++++++++-- Core/memory.c | 4 ++++ 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/Core/display.c b/Core/display.c index cdcdeb2f..54c53a5b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -137,9 +137,8 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (gb->sgb) { - uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? 0x3 : 0x0; for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb->sgb->screen_buffer[i] = color; + gb->sgb->screen_buffer[i] = 0x0; } } else { @@ -387,9 +386,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } if (gb->sgb) { if (gb->current_lcd_line < LINES) { - gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel; + gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; } } + else if (gb->model & GB_MODEL_NO_SFC_BIT) { + gb->icd_row[gb->position_in_line] = pixel; + } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; } @@ -403,9 +405,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } if (gb->sgb) { if (gb->current_lcd_line < LINES) { - gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel; + gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; } } + else if (gb->model & GB_MODEL_NO_SFC_BIT) { + gb->icd_row[gb->position_in_line] = pixel; + } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; } @@ -890,6 +895,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); gb->mode_for_interrupt = 2; + + /* TODO: Can this timing even be verified? */ + if (gb->icd_row_callback) { + gb->icd_row_callback(gb, gb->icd_row); + } } /* Lines 144 - 152 */ @@ -961,7 +971,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->wy_diff = 0; gb->window_disabled_while_active = false; gb->current_line = 0; - gb->current_lcd_line = -1; // TODO: not the correct timing + // TODO: not the correct timing + gb->current_lcd_line = -1; + if (gb->icd_vreset_callback) { + gb->icd_vreset_callback(gb); + } } } diff --git a/Core/gb.c b/Core/gb.c index a1d0f19f..ff2fd3c2 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -112,6 +112,11 @@ void GB_init(GB_gameboy_t *gb, GB_model_t model) gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->clock_multiplier = 1.0; + if (model & GB_MODEL_NO_SFC_BIT) { + /* Disable time syncing. Timing should be done by the SFC emulator. */ + gb->turbo = true; + } + GB_reset(gb); } @@ -576,6 +581,7 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NO_SFC: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = GB_random(); if (i & 0x100) { @@ -588,6 +594,7 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = 0x55; gb->ram[i] ^= GB_random() & GB_random() & GB_random(); @@ -620,7 +627,9 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NO_SFC: case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: for (unsigned i = 0; i < sizeof(gb->hram); i++) { if (i & 1) { gb->hram[i] = GB_random() | GB_random() | GB_random(); @@ -641,9 +650,11 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_DMG_B: - case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_NTSC: /* Unverified */ case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NO_SFC: /* Unverified */ case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: for (unsigned i = 0; i < 8; i++) { if (i & 2) { gb->oam[i] = GB_random() & GB_random() & GB_random(); @@ -669,7 +680,9 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ - case GB_MODEL_SGB2: { + case GB_MODEL_SGB_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: { uint8_t temp; for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { if (i & 1) { @@ -911,3 +924,13 @@ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callb { gb->joyp_write_callback = callback; } + +void GB_set_icd_row_callback(GB_gameboy_t *gb, GB_icd_row_callback_t callback) +{ + gb->icd_row_callback = callback; +} + +void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback) +{ + gb->icd_vreset_callback = callback; +} diff --git a/Core/gb.h b/Core/gb.h index b82d743b..eed23e42 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -23,7 +23,6 @@ #define GB_STRUCT_VERSION 13 -#ifdef GB_INTERNAL #define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_MGB_FAMILY 0x100 @@ -31,6 +30,7 @@ #define GB_MODEL_PAL_BIT 0x1000 #define GB_MODEL_NO_SFC_BIT 0x2000 +#ifdef GB_INTERNAL #if __clang__ #define UNROLL _Pragma("unroll") #elif __GNUC__ @@ -243,6 +243,8 @@ typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool b typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value); +typedef void (*GB_icd_row_callback_t)(GB_gameboy_t *gb, uint8_t *row); +typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); typedef struct { bool state; @@ -420,6 +422,7 @@ struct GB_gameboy_internal_s { uint16_t serial_length; uint8_t double_speed_alignment; uint8_t serial_count; + uint8_t icd_row[160]; ); /* APU */ @@ -535,6 +538,8 @@ struct GB_gameboy_internal_s { GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; GB_update_input_hint_callback_t update_input_hint_callback; GB_joyp_write_callback_t joyp_write_callback; + GB_icd_row_callback_t icd_row_callback; + GB_icd_vreset_callback_t icd_vreset_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -696,7 +701,9 @@ void GB_disconnect_serial(GB_gameboy_t *gb); /* For integration with SFC/SNES emulators */ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); - +void GB_set_icd_row_callback(GB_gameboy_t *gb, GB_icd_row_callback_t callback); +void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); + #ifdef GB_INTERNAL uint32_t GB_get_clock_rate(GB_gameboy_t *gb); #endif diff --git a/Core/memory.c b/Core/memory.c index 868a245c..e5983cbd 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -273,7 +273,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NO_SFC: case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: ; } } @@ -584,7 +586,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NO_SFC: case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: case GB_MODEL_CGB_E: case GB_MODEL_AGB: break; From ce9ce078172f65deb65fdd0c448696b67f65b690 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 20:44:27 +0300 Subject: [PATCH 0899/1216] Make the ICD APIs pixel based --- Core/display.c | 12 ++++++++---- Core/gb.c | 10 ++++++++-- Core/gb.h | 13 ++++++++----- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Core/display.c b/Core/display.c index 54c53a5b..04790f57 100644 --- a/Core/display.c +++ b/Core/display.c @@ -390,7 +390,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } } else if (gb->model & GB_MODEL_NO_SFC_BIT) { - gb->icd_row[gb->position_in_line] = pixel; + if (gb->icd_pixel_callback) { + gb->icd_pixel_callback(gb, pixel); + } } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; @@ -409,7 +411,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } } else if (gb->model & GB_MODEL_NO_SFC_BIT) { - gb->icd_row[gb->position_in_line] = pixel; + if (gb->icd_pixel_callback) { + gb->icd_pixel_callback(gb, pixel); + } } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; @@ -897,8 +901,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->mode_for_interrupt = 2; /* TODO: Can this timing even be verified? */ - if (gb->icd_row_callback) { - gb->icd_row_callback(gb, gb->icd_row); + if (gb->icd_hreset_callback) { + gb->icd_hreset_callback(gb); } } diff --git a/Core/gb.c b/Core/gb.c index ff2fd3c2..70f6c546 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -925,11 +925,17 @@ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callb gb->joyp_write_callback = callback; } -void GB_set_icd_row_callback(GB_gameboy_t *gb, GB_icd_row_callback_t callback) +void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback) { - gb->icd_row_callback = callback; + gb->icd_pixel_callback = callback; } +void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback) +{ + gb->icd_hreset_callback = callback; +} + + void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback) { gb->icd_vreset_callback = callback; diff --git a/Core/gb.h b/Core/gb.h index eed23e42..976a0aa7 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -243,7 +243,8 @@ typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool b typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value); -typedef void (*GB_icd_row_callback_t)(GB_gameboy_t *gb, uint8_t *row); +typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row); +typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); typedef struct { @@ -422,7 +423,6 @@ struct GB_gameboy_internal_s { uint16_t serial_length; uint8_t double_speed_alignment; uint8_t serial_count; - uint8_t icd_row[160]; ); /* APU */ @@ -451,7 +451,8 @@ struct GB_gameboy_internal_s { /* The LCDC will skip the first frame it renders after turning it on. On the CGB, a frame is not skipped if the previous frame was skipped as well. See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ - /* TODO: Drop this and properly emulate the dropped vreset signal*/ + + /* TODO: Drop this and properly emulate the dropped vreset signal*/ enum { GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, // on a CGB, the previous frame is repeated (which might be @@ -538,7 +539,8 @@ struct GB_gameboy_internal_s { GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; GB_update_input_hint_callback_t update_input_hint_callback; GB_joyp_write_callback_t joyp_write_callback; - GB_icd_row_callback_t icd_row_callback; + GB_icd_pixel_callback_t icd_pixel_callback; + GB_icd_vreset_callback_t icd_hreset_callback; GB_icd_vreset_callback_t icd_vreset_callback; /* IR */ @@ -701,7 +703,8 @@ void GB_disconnect_serial(GB_gameboy_t *gb); /* For integration with SFC/SNES emulators */ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); -void GB_set_icd_row_callback(GB_gameboy_t *gb, GB_icd_row_callback_t callback); +void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); +void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback); void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); #ifdef GB_INTERNAL From 2d7f54a775a185b239d64fead45b980a529812c6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 21:04:29 +0300 Subject: [PATCH 0900/1216] Load ROM from buffer API --- Core/gb.c | 18 +++++++++++++++++- Core/gb.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index 70f6c546..1e940cfe 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -198,13 +198,29 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) } gb->rom = malloc(gb->rom_size); memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ - fread(gb->rom, gb->rom_size, 1, f); + fread(gb->rom, 1, gb->rom_size, f); fclose(f); GB_configure_cart(gb); return 0; } +void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) +{ + gb->rom_size = (size + 0x3fff) & ~0x3fff; + while (gb->rom_size & (gb->rom_size - 1)) { + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + if (gb->rom) { + free(gb->rom); + } + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xff, gb->rom_size); + memcpy(gb->rom, buffer, size); + GB_configure_cart(gb); +} + typedef struct { uint8_t seconds; uint8_t padding1[3]; diff --git a/Core/gb.h b/Core/gb.h index 976a0aa7..e12fcfac 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -667,6 +667,7 @@ void GB_set_user_data(GB_gameboy_t *gb, void *data); int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); int GB_load_rom(GB_gameboy_t *gb, const char *path); +void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); int GB_save_battery(GB_gameboy_t *gb, const char *path); void GB_load_battery(GB_gameboy_t *gb, const char *path); From 9ba6915c85770ffb85b9e52d5dcfc4d4d79fe737 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 21:42:57 +0300 Subject: [PATCH 0901/1216] ICD JOYP write API --- Core/joypad.c | 17 ++++++++++++++++- Core/joypad.h | 1 + Core/memory.c | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Core/joypad.c b/Core/joypad.c index 4ae6e676..6c0eaff7 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -3,6 +3,8 @@ void GB_update_joyp(GB_gameboy_t *gb) { + if (gb->model & GB_MODEL_SGB_NO_SFC) return; + uint8_t key_selection = 0; uint8_t previous_state = 0; @@ -53,14 +55,27 @@ void GB_update_joyp(GB_gameboy_t *gb) break; } + /* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */ if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { - /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ + /* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */ gb->io_registers[GB_IO_IF] |= 0x10; } gb->io_registers[GB_IO_JOYP] |= 0xC0; } +void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value) +{ + uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + gb->io_registers[GB_IO_JOYP] |= value & 0xF; + + if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { + gb->io_registers[GB_IO_IF] |= 0x10; + } + +} + void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) { assert(index >= 0 && index < GB_KEY_MAX); diff --git a/Core/joypad.h b/Core/joypad.h index 768d685a..21fad534 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -17,6 +17,7 @@ typedef enum { void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed); +void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); #ifdef GB_INTERNAL void GB_update_joyp(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index e5983cbd..76d8821d 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -741,8 +741,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_JOYP: - GB_sgb_write(gb, value); gb->io_registers[GB_IO_JOYP] = value & 0xF0; + GB_sgb_write(gb, value); GB_update_joyp(gb); return; From eb95f1de5598807b07d5ee59771515d6aacc1d6e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 23:14:26 +0300 Subject: [PATCH 0902/1216] Fixed a bug where the SDL port loaded the incorrect boot ROM for SGB2. Made SameBoy compatible with older SDL versions. --- SDL/main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index df6837fa..ba2a5fb1 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -23,6 +23,11 @@ #define AUDIO_FREQUENCY 48000 #endif +/* Compatibility with older SDL versions */ +#ifndef SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0 +#endif + GB_gameboy_t gb; static bool paused = false; static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; @@ -464,7 +469,7 @@ restart: start_capturing_logs(); const char * const boot_roms[] = {"dmg_boot.bin", "cgb_boot.bin", "agb_boot.bin", "sgb_boot.bin"}; const char *boot_rom = boot_roms[configuration.model]; - if (configuration.model == GB_MODEL_SGB && configuration.sgb_revision == SGB_2) { + if (configuration.model == MODEL_SGB && configuration.sgb_revision == SGB_2) { boot_rom = "sgb2_boot.bin"; } error = GB_load_boot_rom(&gb, resource_path(boot_rom)); From 11a9f1df21288e2d46cbcb224e60fc4ff8816fd2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 23:27:35 +0300 Subject: [PATCH 0903/1216] Silence some GCC warnings --- Core/gb.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index dec32a97..94e4d57c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -21,7 +21,7 @@ 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); + (void)vasprintf(&string, fmt, args); if (string) { if (gb->log_callback) { gb->log_callback(gb, string, attributes); @@ -158,9 +158,12 @@ int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno)); return errno; } - fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); + int ret = 0; + if (fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f) != 1) { + ret = -1; + } fclose(f); - return 0; + return ret; } void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size) @@ -193,7 +196,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) } gb->rom = malloc(gb->rom_size); memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ - fread(gb->rom, gb->rom_size, 1, f); + (void) fread(gb->rom, gb->rom_size, 1, f); fclose(f); GB_configure_cart(gb); From 9efd20d7cd17eb9b9d1756998f360b6f01b9cc90 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 23:33:07 +0300 Subject: [PATCH 0904/1216] Revert "Silence some GCC warnings" This reverts commit 11a9f1df21288e2d46cbcb224e60fc4ff8816fd2. --- Core/gb.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 94e4d57c..dec32a97 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -21,7 +21,7 @@ void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { char *string = NULL; - (void)vasprintf(&string, fmt, args); + vasprintf(&string, fmt, args); if (string) { if (gb->log_callback) { gb->log_callback(gb, string, attributes); @@ -158,12 +158,9 @@ int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno)); return errno; } - int ret = 0; - if (fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f) != 1) { - ret = -1; - } + fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); fclose(f); - return ret; + return 0; } void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size) @@ -196,7 +193,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) } gb->rom = malloc(gb->rom_size); memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ - (void) fread(gb->rom, gb->rom_size, 1, f); + fread(gb->rom, gb->rom_size, 1, f); fclose(f); GB_configure_cart(gb); From 1bf5fb208f20b4338a75d58d278515c038f23138 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 16 Jul 2019 23:41:05 +0300 Subject: [PATCH 0905/1216] Silence an unwanted GCC warning --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 401fa3a4..a8354701 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ OPEN_DIALOG = OpenDialog/cocoa.m endif -CFLAGS += -Werror -Wall -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -Werror -Wall -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -Drandom=rand From 4504de828a188b520a324687a1181af6c45a7e3a Mon Sep 17 00:00:00 2001 From: Damian Yerrick Date: Tue, 16 Jul 2019 16:58:16 -0400 Subject: [PATCH 0906/1216] cgb_boot: Compress logo with PB8 The logo is compressed using PB8, a form of RLE with unary-coded run lengths. Each block representing 8 bytes consists of a control byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat previous, followed by the literals in that block. PB8 compression is also used in a few NES games. A variant called PB16, where 1 means repeat 2 bytes back, is used in the Game Boy port of 240p Test Suite and in Libbet and the Magic Floor. Switching from logo-compress RLE to PB8 decreases the compressed logo data size from 287 bytes to 253 bytes, saving 34 bytes. The decompression code is also about 10 bytes smaller. --- BootROMs/cgb_boot.asm | 78 ++++++---- BootROMs/pb8.c | 330 ++++++++++++++++++++++++++++++++++++++++++ Makefile | 24 +-- 3 files changed, 389 insertions(+), 43 deletions(-) create mode 100644 BootROMs/pb8.c diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 0472cbed..6ae869b2 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -532,7 +532,7 @@ TrademarkSymbol: db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SameBoyLogo: - incbin "SameBoyLogo.rle" + incbin "SameBoyLogo.pb8" AnimationColors: dw $7FFF ; White @@ -634,41 +634,55 @@ ReadCGBLogoHalfTile: ld a, e ret +; LoadTileset using PB8 codec, 2019 Damian Yerrick +; +; The logo is compressed using PB8, a form of RLE with unary-coded +; run lengths. Each block representing 8 bytes consists of a control +; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat +; previous, followed by the literals in that block. + +SameBoyLogo_dst = $8080 +SameBoyLogo_length = (128 * 24) / 64 + LoadTileset: -; Copy SameBoy Logo - ld de, SameBoyLogo - ld hl, $8080 -.sameboyLogoLoop - ld a, [de] - inc de - - ld b, a - and $0f - jr z, .skipLiteral - ld c, a - -.literalLoop - ld a, [de] - ldi [hl], a + ld hl, SameBoyLogo + ld de, SameBoyLogo_dst + ld c, SameBoyLogo_length +.pb8BlockLoop: + ; Register map for PB8 decompression + ; HL: source address in boot ROM + ; DE: destination address in VRAM + ; A: Current literal value + ; B: Repeat bits, terminated by 1000... + ; C: Number of 8-byte blocks left in this block + ; Source address in HL lets the repeat bits go straight to B, + ; bypassing A and avoiding spilling registers to the stack. + ld b, [hl] inc hl - inc de - dec c - jr nz, .literalLoop -.skipLiteral - swap b - ld a, b - and $0f - jr z, .sameboyLogoEnd - ld c, a - ld a, [de] - inc de -.repeatLoop - ldi [hl], a - inc hl + ; Shift a 1 into lower bit of shift value. Once this bit + ; reaches the carry, B becomes 0 and the byte is over + scf + rl b + +.pb8BitLoop: + ; If not a repeat, load a literal byte + jr c,.pb8Repeat + ld a, [hli] +.pb8Repeat: + ; Decompressed data uses colors 0 and 1, so write once, inc twice + ld [de], a + inc de + inc de + sla b + jr nz, .pb8BitLoop + dec c - jr nz, .repeatLoop - jr .sameboyLogoLoop + jr nz, .pb8BlockLoop + +; End PB8 decoding. The rest uses HL as the destination + ld h, d + ld l, e .sameboyLogoEnd ; Copy (unresized) ROM logo diff --git a/BootROMs/pb8.c b/BootROMs/pb8.c new file mode 100644 index 00000000..03a196e9 --- /dev/null +++ b/BootROMs/pb8.c @@ -0,0 +1,330 @@ +/* + +PB8 compressor and decompressor + +Copyright 2019 Damian Yerrick + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +*/ + +#include +#include +#include +#include +#include +#include + +// For setting stdin/stdout to binary mode +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +#include +#define fd_isatty isatty +#elif defined (_WIN32) +#include +#include +#define fd_isatty _isatty +#endif + +/* + +; The logo is compressed using PB8, a form of RLE with unary-coded +; run lengths. Each block representing 8 bytes consists of a control +; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat +; previous, followed by the literals in that block. + +SameBoyLogo_dst = $8080 +SameBoyLogo_length = (128 * 24) / 64 + +LoadTileset: + ld hl, SameBoyLogo + ld de, SameBoyLogo_dst + ld c, SameBoyLogo_length +.pb8BlockLoop: + ; Register map for PB8 decompression + ; HL: source address in boot ROM + ; DE: destination address in VRAM + ; A: Current literal value + ; B: Repeat bits, terminated by 1000... + ; C: Number of 8-byte blocks left in this block + ; Source address in HL lets the repeat bits go straight to B, + ; bypassing A and avoiding spilling registers to the stack. + ld b, [hl] + inc hl + + ; Shift a 1 into lower bit of shift value. Once this bit + ; reaches the carry, B becomes 0 and the byte is over + scf + rl b + +.pb8BitLoop: + ; If not a repeat, load a literal byte + jr c,.pb8Repeat + ld a, [hli] +.pb8Repeat: + ; Decompressed data uses colors 0 and 1, so write once, inc twice + ld [de], a + inc de + inc de + sla b + jr nz, .pb8BitLoop + + dec c + jr nz, .pb8BlockLoop + ret + +*/ + +/* Compressor and decompressor *************************************/ + +/** + * Compresses an input stream to PB8 data on an output stream. + * @param infp input stream + * @param outfp output stream + * @param blocklength size of an independent input block in bytes + * @return 0 for reaching infp end of file, or EOF for error + */ +int pb8(FILE *infp, FILE *outfp, size_t blocklength) { + blocklength >>= 3; // convert bytes to blocks + assert(blocklength > 0); + while (1) { + int last_byte = EOF; // value that never occurs in a file + for (size_t blkleft = blocklength; blkleft > 0; --blkleft) { + unsigned int control_byte = 0x0001; + unsigned char literals[8]; + size_t nliterals = 0; + while (control_byte < 0x100) { + int c = fgetc(infp); + if (c == EOF) break; + + control_byte <<= 1; + if (c == last_byte) { + control_byte |= 0x01; + } else { + literals[nliterals++] = last_byte = c; + } + } + if (control_byte > 1) { + // Fill partial block with repeats + while (control_byte < 0x100) { + control_byte = (control_byte << 1) | 1; + } + + // Write control byte and check for write failure + int ok = fputc(control_byte & 0xFF, outfp); + if (ok == EOF) return EOF; + size_t ok2 = fwrite(literals, 1, nliterals, outfp); + if (ok2 < nliterals) return EOF; + } + + // If finished, return success or failure + if (ferror(infp) || ferror(outfp)) return EOF; + if (feof(infp)) return 0; + } // End 8-byte block + } // End packet, resetting last_byte +} + +/** + * Decompresses PB8 data on an input stream to an output stream. + * @param infp input stream + * @param outfp output stream + * @return 0 for reaching infp end of file, or EOF for error + */ +int unpb8(FILE *infp, FILE *outfp) { + int last_byte = 0; + while (1) { + int control_byte = fgetc(infp); + if (control_byte == EOF) { + return feof(infp) ? 0 : EOF; + } + control_byte &= 0xFF; + for (size_t bytesleft = 8; bytesleft > 0; --bytesleft) { + if (!(control_byte & 0x80)) { + last_byte = fgetc(infp); + if (last_byte == EOF) return EOF; // read error + } + control_byte <<= 1; + int ok = fputc(last_byte, outfp); + if (ok == EOF) return EOF; + } + } +} + +/* CLI frontend ****************************************************/ + +static inline void set_fd_binary(unsigned int fd) { +#ifdef _WIN32 + _setmode(fd, _O_BINARY); +#else + (void) fd; +#endif +} + +static const char *usage_msg = +"usage: pb8 [-d] [-l blocklength] [infile [outfile]]\n" +"Compresses a file using RLE with unary run and literal lengths.\n" +"\n" +"options:\n" +" -d decompress\n" +" -l blocklength allow RLE packets to span up to blocklength\n" +" input bytes (multiple of 8; default 8)\n" +" -h, -?, --help show this usage page\n" +" --version show copyright info\n" +"\n" +"If infile is - or missing, it is standard input.\n" +"If outfile is - or missing, it is standard output.\n" +"You cannot compress to or decompress from a terminal.\n" +; +static const char *version_msg = +"PB8 compressor (C version) v0.01\n" +"Copyright 2019 Damian Yerrick \n" +"This software is provided 'as-is', without any express or implied\n" +"warranty.\n" +; +static const char *toomanyfilenames_msg = +"pb8: too many filenames; try pb8 --help\n"; + +int main(int argc, char **argv) { + const char *infilename = NULL; + const char *outfilename = NULL; + bool decompress = false; + size_t blocklength = 8; + + for (int i = 1; i < argc; ++i) { + if (argv[i][0] == '-' && argv[i][1] != 0) { + if (!strcmp(argv[i], "--help")) { + fputs(usage_msg, stdout); + return 0; + } + if (!strcmp(argv[i], "--version")) { + fputs(version_msg, stdout); + return 0; + } + + // -t1 or -t 1 + int argtype = argv[i][1]; + switch (argtype) { + case 'h': + case '?': + fputs(usage_msg, stdout); + return 0; + + case 'd': + decompress = true; + break; + + case 'l': { + const char *argvalue = argv[i][2] ? argv[i] + 2 : argv[++i]; + const char *endptr = NULL; + + unsigned long tvalue = strtoul(argvalue, (char **)&endptr, 10); + if (endptr == argvalue || tvalue == 0 || tvalue > SIZE_MAX) { + fprintf(stderr, "pb8: block length %s not a positive integer\n", + argvalue); + return EXIT_FAILURE; + } + if (tvalue % 8 != 0) { + fprintf(stderr, "pb8: block length %s not a multiple of 8\n", + argvalue); + return EXIT_FAILURE; + } + blocklength = tvalue; + } break; + + default: + fprintf(stderr, "pb8: unknown option -%c\n", argtype); + return EXIT_FAILURE; + } + } else if (!infilename) { + infilename = argv[i]; + } else if (!outfilename) { + outfilename = argv[i]; + } else { + fputs(toomanyfilenames_msg, stderr); + return EXIT_FAILURE; + } + } + if (infilename && !strcmp(infilename, "-")) { + infilename = NULL; + } + if (!infilename && decompress && fd_isatty(0)) { + fputs("pb8: cannot decompress from terminal; try redirecting stdin\n", + stderr); + return EXIT_FAILURE; + } + if (outfilename && !strcmp(outfilename, "-")) { + outfilename = NULL; + } + if (!outfilename && !decompress && fd_isatty(1)) { + fputs("pb8: cannot compress to terminal; try redirecting stdout or pb8 --help\n", + stderr); + return EXIT_FAILURE; + } + + FILE *infp = NULL; + if (infilename) { + infp = fopen(infilename, "rb"); + if (!infp) { + fprintf(stderr, "pb8: error opening %s ", infilename); + perror("for reading"); + return EXIT_FAILURE; + } + } else { + infp = stdin; + set_fd_binary(0); + } + + FILE *outfp = NULL; + if (outfilename) { + outfp = fopen(outfilename, "wb"); + if (!outfp) { + fprintf(stderr, "pb8: error opening %s ", outfilename); + perror("for writing"); + fclose(infp); + return EXIT_FAILURE; + } + } else { + outfp = stdout; + set_fd_binary(1); + } + + int compfailed = 0; + int has_ferror = 0; + if (decompress) { + compfailed = unpb8(infp, outfp); + } else { + compfailed = pb8(infp, outfp, blocklength); + } + fflush(outfp); + if (ferror(infp)) { + fprintf(stderr, "pb8: error reading %s\n", + infilename ? infilename : ""); + has_ferror = EOF; + } + fclose(infp); + if (ferror(outfp)) { + fprintf(stderr, "pb8: error writing %s\n", + outfilename ? outfilename : ""); + has_ferror = EOF; + } + fclose(outfp); + + if (compfailed && !has_ferror) { + fputs("pb8: unknown compression failure\n", stderr); + } + + return (compfailed || has_ferror) ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/Makefile b/Makefile index 401fa3a4..5db8c424 100644 --- a/Makefile +++ b/Makefile @@ -13,13 +13,15 @@ ifneq ($(findstring MSYS,$(PLATFORM)),) PLATFORM := windows32 endif -LOGO_COMPRESS := build/logo-compress - ifeq ($(PLATFORM),windows32) _ := $(shell chcp 65001) -LOGO_COMPRESS := build/logo-compress.exe +EXESUFFIX:=.exe +else +EXESUFFIX:= endif +PB8_COMPRESS := build/pb8$(EXESUFFIX) + ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa else @@ -302,11 +304,11 @@ $(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(BIN)/SDL/%.bin $(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ - + $(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ - + $(BIN)/SDL/LICENSE: LICENSE -@$(MKDIR) -p $(dir $@) cp -f $^ $@ @@ -314,7 +316,7 @@ $(BIN)/SDL/LICENSE: LICENSE $(BIN)/SDL/registers.sym: Misc/registers.sym -@$(MKDIR) -p $(dir $@) cp -f $^ $@ - + $(BIN)/SDL/background.bmp: SDL/background.bmp -@$(MKDIR) -p $(dir $@) cp -f $^ $@ @@ -329,17 +331,17 @@ $(OBJ)/%.1bpp: %.png -@$(MKDIR) -p $(dir $@) rgbgfx -d 1 -h -o $@ $< -$(OBJ)/BootROMs/SameBoyLogo.rle: $(OBJ)/BootROMs/SameBoyLogo.1bpp $(LOGO_COMPRESS) - $(realpath $(LOGO_COMPRESS)) < $< > $@ +$(OBJ)/BootROMs/SameBoyLogo.pb8: $(OBJ)/BootROMs/SameBoyLogo.1bpp $(PB8_COMPRESS) + $(realpath $(PB8_COMPRESS)) -l 384 $< $@ -$(LOGO_COMPRESS): BootROMs/logo-compress.c +$(PB8_COMPRESS): BootROMs/pb8.c $(CC) $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm -$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.rle +$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb8 -@$(MKDIR) -p $(dir $@) rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $< rgblink -o $@.tmp2 $@.tmp @@ -349,7 +351,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.rle # Libretro Core (uses its own build system) libretro: $(MAKE) -C libretro - + # Clean clean: rm -rf build From 26cf9707137971dee988c69d0b5cd09a3f8f8673 Mon Sep 17 00:00:00 2001 From: Damian Yerrick Date: Tue, 16 Jul 2019 17:04:23 -0400 Subject: [PATCH 0907/1216] don't need logo-compress.c anymore --- BootROMs/logo-compress.c | 62 ---------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 BootROMs/logo-compress.c diff --git a/BootROMs/logo-compress.c b/BootROMs/logo-compress.c deleted file mode 100644 index 2274eb28..00000000 --- a/BootROMs/logo-compress.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#ifdef _WIN32 -#include -#include -#endif - -void pair(size_t count, uint8_t byte) -{ - static size_t unique_count = 0; - static uint8_t unique_data[15]; - if (count == 1) { - unique_data[unique_count++] = byte; - assert(unique_count <= 15); - } - else { - assert(count <= 15); - uint8_t control = (count << 4) | unique_count; - putchar(control); - - for (size_t i = 0; i < unique_count; i++) { - putchar(unique_data[i]); - } - - if (count != 0) { - putchar(byte); - } - else { - assert(control == 0); - } - - unique_count = 0; - } -} - -int main(int argc, char *argv[]) -{ - size_t count = 1; - uint8_t byte = getchar(); - int new; - size_t position = 0; - -#ifdef _WIN32 - _setmode(0,_O_BINARY); - _setmode(1,_O_BINARY); -#endif - - while ((new = getchar()) != EOF) { - if (byte == new) { - count++; - } - else { - pair(count, byte); - byte = new; - count = 1; - } - } - - pair(count, byte); - pair(0, 0); -} From 23ca39720626cf50ae90183ae3a50921d371ec55 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 17 Jul 2019 00:52:01 +0300 Subject: [PATCH 0908/1216] Remove unused flag --- libretro/jni/Application.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/libretro/jni/Application.mk b/libretro/jni/Application.mk index a169e740..a252a72d 100644 --- a/libretro/jni/Application.mk +++ b/libretro/jni/Application.mk @@ -1,2 +1 @@ -APP_STL := c++_static APP_ABI := all From 597dc72e460f3e95caad40b8c557fb01f53f18ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 18 Jul 2019 00:13:41 +0300 Subject: [PATCH 0909/1216] Fix audio issues with some RetroArch audio drivers. Fixes #189 --- libretro/libretro.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 1dd411c1..bd93e97a 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -84,7 +84,7 @@ static struct retro_log_callback logging; static retro_log_printf_t log_cb; static retro_video_refresh_t video_cb; -static retro_audio_sample_batch_t audio_batch_cb; +static retro_audio_sample_t audio_sample_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; @@ -152,7 +152,7 @@ static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { if ((audio_out == GB_1 && gb == &gameboy[0]) || (audio_out == GB_2 && gb == &gameboy[1])) { - audio_batch_cb((void*)sample, 1); + audio_sample_cb(sample->left, sample->right); } } @@ -772,11 +772,11 @@ void retro_set_environment(retro_environment_t cb) void retro_set_audio_sample(retro_audio_sample_t cb) { + audio_sample_cb = cb; } void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { - audio_batch_cb = cb; } void retro_set_input_poll(retro_input_poll_t cb) @@ -850,8 +850,7 @@ void retro_run(void) } else { - int x = GB_run_frame(&gameboy[0]); - log_cb(RETRO_LOG_DEBUG, "%d\n", x); + GB_run_frame(&gameboy[0]); } if (emulated_devices == 2) From 772289c54513ebc63c37b16880923d37c5f3906f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 18 Jul 2019 00:53:11 +0300 Subject: [PATCH 0910/1216] Fix a silly bug --- Core/display.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 04790f57..18920849 100644 --- a/Core/display.c +++ b/Core/display.c @@ -375,6 +375,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bg_enabled = false; } } + + uint8_t icd_pixel = 0; { uint8_t pixel = bg_enabled? fifo_item->pixel : 0; @@ -391,7 +393,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } else if (gb->model & GB_MODEL_NO_SFC_BIT) { if (gb->icd_pixel_callback) { - gb->icd_pixel_callback(gb, pixel); + icd_pixel = pixel; + //gb->icd_pixel_callback(gb, pixel); } } else { @@ -412,13 +415,20 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } else if (gb->model & GB_MODEL_NO_SFC_BIT) { if (gb->icd_pixel_callback) { - gb->icd_pixel_callback(gb, pixel); + icd_pixel = pixel; + //gb->icd_pixel_callback(gb, pixel); } } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; } } + + if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + gb->icd_pixel_callback(gb, icd_pixel); + } + } gb->position_in_line++; } From df7f7d81713636df0ceb7d51a280f480bc1f56d4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 18 Jul 2019 22:55:11 +0300 Subject: [PATCH 0911/1216] Fix silly desync inaccuracy --- Core/display.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index 18920849..2bf00ba9 100644 --- a/Core/display.c +++ b/Core/display.c @@ -769,7 +769,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; - gb->current_lcd_line++; // Todo: unverified timing + + // Todo: unverified timing + gb->current_lcd_line++; + if (gb->icd_hreset_callback) { + gb->icd_hreset_callback(gb); + } + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { display_vblank(gb); } @@ -909,11 +915,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); gb->mode_for_interrupt = 2; - - /* TODO: Can this timing even be verified? */ - if (gb->icd_hreset_callback) { - gb->icd_hreset_callback(gb); - } } /* Lines 144 - 152 */ From f0809a667fe1369809233589e17455d9599f4804 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 15:50:36 +0300 Subject: [PATCH 0912/1216] Fixed a potential Cocoa crash when closing a window --- Cocoa/Document.m | 1 + Cocoa/GBViewMetal.m | 1 + 2 files changed, 2 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index b99e4c8d..a520d62e 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -389,6 +389,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) - (void)dealloc { [cameraSession stopRunning]; + self.view.gb = NULL; GB_free(&gb); if (cameraImage) { CVBufferRelease(cameraImage); diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 9acb11e9..94b4975d 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -131,6 +131,7 @@ static const vector_float2 rect[] = - (void)drawInMTKView:(nonnull MTKView *)view { if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; + if (!self.gb) return; if (texture.width != GB_get_screen_width(self.gb) || texture.height != GB_get_screen_height(self.gb)) { [self allocateTextures]; From 33198fc7b7b800087f2a5e43bef49c4fa38f224b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 15:50:49 +0300 Subject: [PATCH 0913/1216] Give SGB its own conflict map --- Core/sm83_cpu.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 12ca6704..77248b51 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -33,6 +33,7 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { /* Todo: most values not verified, and probably differ between revisions */ }; +/* Todo: verify on an MGB */ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, @@ -40,7 +41,6 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_SCY] = GB_CONFLICT_READ_NEW, [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, - /* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */ [GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG, [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG, [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG, @@ -51,6 +51,24 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; +/* Todo: Verify on an SGB1 */ +static const GB_conflict_t sgb_conflict_map[0x80] = { + [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + [GB_IO_LYC] = GB_CONFLICT_READ_OLD, + [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, + + [GB_IO_BGP] = GB_CONFLICT_READ_NEW, + [GB_IO_OBP0] = GB_CONFLICT_READ_NEW, + [GB_IO_OBP1] = GB_CONFLICT_READ_NEW, + + /* Todo: these were not verified at all */ + [GB_IO_WY] = GB_CONFLICT_READ_NEW, + [GB_IO_WX] = GB_CONFLICT_READ_NEW, + [GB_IO_SCX] = GB_CONFLICT_READ_NEW, +}; + static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) { if (gb->pending_cycles) { @@ -92,7 +110,17 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) assert(gb->pending_cycles); GB_conflict_t conflict = GB_CONFLICT_READ_OLD; if ((addr & 0xFF80) == 0xFF00) { - conflict = (GB_is_cgb(gb)? cgb_conflict_map : dmg_conflict_map)[addr & 0x7F]; + const GB_conflict_t *map = NULL; + if (GB_is_cgb(gb)) { + map = cgb_conflict_map; + } + else if (GB_is_sgb(gb)) { + map = sgb_conflict_map; + } + else { + map = dmg_conflict_map; + } + conflict = map[addr & 0x7F]; } switch (conflict) { case GB_CONFLICT_READ_OLD: From 4f9c8e93748144a4b63036820892e34744c4d4eb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 20:19:09 +0300 Subject: [PATCH 0914/1216] Match the HLE timings to the LLE timings --- Core/display.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/display.c b/Core/display.c index 2bf00ba9..fc230444 100644 --- a/Core/display.c +++ b/Core/display.c @@ -769,13 +769,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; - - // Todo: unverified timing - gb->current_lcd_line++; - if (gb->icd_hreset_callback) { - gb->icd_hreset_callback(gb); - } - + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { display_vblank(gb); } @@ -915,6 +909,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); gb->mode_for_interrupt = 2; + + // Todo: unverified timing + gb->current_lcd_line++; + if (gb->icd_hreset_callback) { + gb->icd_hreset_callback(gb); + } } /* Lines 144 - 152 */ @@ -987,7 +987,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->window_disabled_while_active = false; gb->current_line = 0; // TODO: not the correct timing - gb->current_lcd_line = -1; + gb->current_lcd_line = 0; if (gb->icd_vreset_callback) { gb->icd_vreset_callback(gb); } From e634019ac9f418a104d81dc0fc8abeaa6c599aa8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 20:27:53 +0300 Subject: [PATCH 0915/1216] Fix CGB emulation --- Core/joypad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/joypad.c b/Core/joypad.c index 6c0eaff7..124d9080 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -3,7 +3,7 @@ void GB_update_joyp(GB_gameboy_t *gb) { - if (gb->model & GB_MODEL_SGB_NO_SFC) return; + if (gb->model & GB_MODEL_NO_SFC_BIT) return; uint8_t key_selection = 0; uint8_t previous_state = 0; From ffb9f1b134e7de017c1a22c8a2ca471c176171b7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 20:34:26 +0300 Subject: [PATCH 0916/1216] Fix HLE SGB --- Core/display.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index fc230444..02dd9a8d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -377,7 +377,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } uint8_t icd_pixel = 0; - { uint8_t pixel = bg_enabled? fifo_item->pixel : 0; if (pixel && bg_priority) { @@ -394,7 +393,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) else if (gb->model & GB_MODEL_NO_SFC_BIT) { if (gb->icd_pixel_callback) { icd_pixel = pixel; - //gb->icd_pixel_callback(gb, pixel); } } else { @@ -770,9 +768,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; - if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { - display_vblank(gb); - } gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); @@ -912,6 +907,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) // Todo: unverified timing gb->current_lcd_line++; + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { + display_vblank(gb); + } if (gb->icd_hreset_callback) { gb->icd_hreset_callback(gb); } From 8c1f76a5948f8e9a8e2fd14fab8d6b0f2c8ab891 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 20:37:58 +0300 Subject: [PATCH 0917/1216] Fix HLE SGB --- Core/display.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index 02dd9a8d..25e95e7e 100644 --- a/Core/display.c +++ b/Core/display.c @@ -905,11 +905,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); gb->mode_for_interrupt = 2; - // Todo: unverified timing - gb->current_lcd_line++; - if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { - display_vblank(gb); - } + // Todo: unverified timing + gb->current_lcd_line++; + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { + display_vblank(gb); + } + if (gb->icd_hreset_callback) { gb->icd_hreset_callback(gb); } From 1a263a3accfb8d40cc13d6f0d2b5eda2cba8c8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Zumer?= Date: Fri, 19 Jul 2019 16:55:59 -0400 Subject: [PATCH 0918/1216] Fix GBC memory map and add IO port range for cheevos --- libretro/libretro.c | 74 +++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index bd93e97a..e075b2cf 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -380,7 +380,7 @@ static void init_for_current_model(unsigned id) set_link_cable_state(true); } - struct retro_memory_descriptor descs[10]; + struct retro_memory_descriptor descs[11]; size_t size; uint16_t bank; @@ -389,47 +389,55 @@ static void init_for_current_model(unsigned id) i = 0; memset(descs, 0, sizeof(descs)); - descs[0].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IE, &size, &bank); - descs[0].start = 0xFFFF; - descs[0].len = 1; + descs[0].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IE, &size, &bank); + descs[0].start = 0xFFFF; + descs[0].len = 1; - descs[1].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_HRAM, &size, &bank); - descs[1].start = 0xFF80; - descs[1].len = 0x0080; + descs[1].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_HRAM, &size, &bank); + descs[1].start = 0xFF80; + descs[1].len = 0x0080; - descs[2].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank); - descs[2].start = 0xC000; - descs[2].len = 0x1000; + descs[2].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank); + descs[2].start = 0xC000; + descs[2].len = 0x1000; - descs[3].ptr = descs[2].ptr + 0x1000; /* GB RAM/GBC RAM bank 1 */ - descs[3].start = 0xD000; - descs[3].len = 0x1000; + descs[3].ptr = descs[2].ptr + 0x1000; /* GB RAM/GBC RAM bank 1 */ + descs[3].start = 0xD000; + descs[3].len = 0x1000; - descs[4].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); - descs[4].start = 0xA000; - descs[4].len = 0x2000; + descs[4].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + descs[4].start = 0xA000; + descs[4].len = 0x2000; - descs[5].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); - descs[5].start = 0x8000; - descs[5].len = 0x2000; + descs[5].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); + descs[5].start = 0x8000; + descs[5].len = 0x2000; - descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_ROM, &size, &bank); - descs[6].start = 0x0000; - descs[6].len = 0x4000; - descs[6].flags = RETRO_MEMDESC_CONST; + descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_ROM, &size, &bank); + descs[6].start = 0x0000; + descs[6].len = 0x4000; + descs[6].flags = RETRO_MEMDESC_CONST; - descs[7].ptr = descs[6].ptr + (bank * 0x4000); - descs[7].start = 0x4000; - descs[7].len = 0x4000; - descs[7].flags = RETRO_MEMDESC_CONST; + descs[7].ptr = descs[6].ptr + (bank * 0x4000); + descs[7].start = 0x4000; + descs[7].len = 0x4000; + descs[7].flags = RETRO_MEMDESC_CONST; - descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); - descs[8].start = 0xFE00; - descs[8].len = 0x00A0; + descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); + descs[8].start = 0xFE00; + descs[8].select = 0xFFFFFF00; + descs[8].len = 0x00A0; - descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ - descs[9].start = 0x10000; - descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ + descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ + descs[9].start = 0x10000; + descs[9].select = 0xFFFF0000; + descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ + + descs[10].ptr = descs[8].ptr; + descs[10].offset = 0x100; + descs[10].start = 0xFF00; + descs[10].select = 0xFFFFFF00; + descs[10].len = 0x0080; struct retro_memory_map mmaps; mmaps.descriptors = descs; From d2e9025be65ce95b5ff15683af7dc6204f001588 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 19 Jul 2019 23:59:25 +0300 Subject: [PATCH 0919/1216] Fixed major performence issues in the Cocoa port that affected some Macs, especially when emulating SGB1 --- Cocoa/GBViewMetal.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 94b4975d..fde4b7e0 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -50,6 +50,7 @@ static const vector_float2 rect[] = view.delegate = self; self.internalView = view; view.paused = YES; + view.enableSetNeedsDisplay = YES; vertices = [device newBufferWithBytes:rect length:sizeof(rect) @@ -206,7 +207,7 @@ static const vector_float2 rect[] = { [super flip]; dispatch_async(dispatch_get_main_queue(), ^{ - [(MTKView *)self.internalView draw]; + [(MTKView *)self.internalView setNeedsDisplay:YES]; }); } From e3672e829339c604952eb4ee13b75828a90bff21 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 20 Jul 2019 16:10:24 +0300 Subject: [PATCH 0920/1216] Emulate built in SGB palettes --- Core/sgb.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++------ Core/sgb.h | 3 ++ 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index ccc439ab..7d648f77 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -68,6 +68,75 @@ static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index) } } +static const uint16_t built_in_palettes[] = +{ + 0x67BF, 0x265B, 0x10B5, 0x2866, + 0x637B, 0x3AD9, 0x0956, 0x0000, + 0x7F1F, 0x2A7D, 0x30F3, 0x4CE7, + 0x57FF, 0x2618, 0x001F, 0x006A, + 0x5B7F, 0x3F0F, 0x222D, 0x10EB, + 0x7FBB, 0x2A3C, 0x0015, 0x0900, + 0x2800, 0x7680, 0x01EF, 0x2FFF, + 0x73BF, 0x46FF, 0x0110, 0x0066, + 0x533E, 0x2638, 0x01E5, 0x0000, + 0x7FFF, 0x2BBF, 0x00DF, 0x2C0A, + 0x7F1F, 0x463D, 0x74CF, 0x4CA5, + 0x53FF, 0x03E0, 0x00DF, 0x2800, + 0x433F, 0x72D2, 0x3045, 0x0822, + 0x7FFA, 0x2A5F, 0x0014, 0x0003, + 0x1EED, 0x215C, 0x42FC, 0x0060, + 0x7FFF, 0x5EF7, 0x39CE, 0x0000, + 0x4F5F, 0x630E, 0x159F, 0x3126, + 0x637B, 0x121C, 0x0140, 0x0840, + 0x66BC, 0x3FFF, 0x7EE0, 0x2C84, + 0x5FFE, 0x3EBC, 0x0321, 0x0000, + 0x63FF, 0x36DC, 0x11F6, 0x392A, + 0x65EF, 0x7DBF, 0x035F, 0x2108, + 0x2B6C, 0x7FFF, 0x1CD9, 0x0007, + 0x53FC, 0x1F2F, 0x0E29, 0x0061, + 0x36BE, 0x7EAF, 0x681A, 0x3C00, + 0x7BBE, 0x329D, 0x1DE8, 0x0423, + 0x739F, 0x6A9B, 0x7293, 0x0001, + 0x5FFF, 0x6732, 0x3DA9, 0x2481, + 0x577F, 0x3EBC, 0x456F, 0x1880, + 0x6B57, 0x6E1B, 0x5010, 0x0007, + 0x0F96, 0x2C97, 0x0045, 0x3200, + 0x67FF, 0x2F17, 0x2230, 0x1548, +}; + +static const struct { + char name[16]; + unsigned palette_index; +} palette_assignments[] = +{ + {"ZELDA", 5}, + {"SUPER MARIOLAND", 6}, + {"MARIOLAND2", 0x14}, + {"SUPERMARIOLAND3", 2}, + {"KIRBY DREAM LAND", 0xB}, + {"HOSHINOKA-BI", 0xB}, + {"KIRBY'S PINBALL", 3}, + {"YOSSY NO TAMAGO", 0xC}, + {"MARIO & YOSHI", 0xC}, + {"YOSSY NO COOKIE", 4}, + {"YOSHI'S COOKIE", 4}, + {"DR.MARIO", 0x12}, + {"TETRIS", 0x11}, + {"YAKUMAN", 0x13}, + {"METROID2", 0x1F}, + {"KAERUNOTAMENI", 9}, + {"GOLF", 0x18}, + {"ALLEY WAY", 0x16}, + {"BASEBALL", 0xF}, + {"TENNIS", 0x17}, + {"F1RACE", 0x1E}, + {"KID ICARUS", 0xE}, + {"QIX", 0x19}, + {"SOLARSTRIKER", 7}, + {"X", 0x1C}, + {"GBWARS", 0x15}, +}; + static void command_ready(GB_gameboy_t *gb) { /* SGB header commands are used to send the contents of the header to the SNES CPU. @@ -77,6 +146,8 @@ static void command_ready(GB_gameboy_t *gb) 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ if ((gb->sgb->command[0] & 0xF1) == 0xF1) { + if(gb->boot_rom_finished) return; + uint8_t checksum = 0; for (unsigned i = 2; i < 0x10; i++) { checksum += gb->sgb->command[i]; @@ -86,14 +157,23 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb->disable_commands = true; return; } - if (gb->sgb->command[0] == 0xf9) { - if (gb->sgb->command[0xc] != 3) { // SGB Flag - gb->sgb->disable_commands = true; - } + unsigned index = (gb->sgb->command[0] >> 1) & 7; + if (index > 5) { + return; } - else if (gb->sgb->command[0] == 0xfb) { - if (gb->sgb->command[0x3] != 0x33) { // Old licensee code + memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14); + if (gb->sgb->command[0] == 0xfb) { + if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) { gb->sgb->disable_commands = true; + for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) { + if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) { + gb->sgb->effective_palettes[0] = built_in_palettes[palette_assignments[i].palette_index * 4 - 4]; + gb->sgb->effective_palettes[1] = built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4]; + gb->sgb->effective_palettes[2] = built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4]; + gb->sgb->effective_palettes[3] = built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4]; + break; + } + } } } return; @@ -675,10 +755,10 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb) /* Re-center */ memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0])); } - gb->sgb->effective_palettes[0] = 0x639E; - gb->sgb->effective_palettes[1] = 0x263A; - gb->sgb->effective_palettes[2] = 0x10D4; - gb->sgb->effective_palettes[3] = 0x2866; + gb->sgb->effective_palettes[0] = built_in_palettes[0]; + gb->sgb->effective_palettes[1] = built_in_palettes[1]; + gb->sgb->effective_palettes[2] = built_in_palettes[2]; + gb->sgb->effective_palettes[3] = built_in_palettes[3]; } static double fm_synth(double phase) diff --git a/Core/sgb.h b/Core/sgb.h index 2c6e8ee9..49bf6d9c 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -49,6 +49,9 @@ struct GB_sgb_s { /* Intro */ int16_t intro_animation; + + /* GB Header */ + uint8_t received_header[0x54]; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From eaa1c1cd4a28fbe354286293b3b12aab9ca3c0c2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Aug 2019 17:38:43 +0300 Subject: [PATCH 0921/1216] =?UTF-8?q?Merge=20bsnes=E2=80=99s=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/gb.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ Core/gb.h | 8 +++- Core/memory.c | 5 +++ Core/memory.h | 3 ++ Core/sgb.c | 4 ++ Core/timing.c | 2 +- 6 files changed, 135 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 1e940cfe..e91420ce 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -251,6 +251,48 @@ typedef union { } vba64; } GB_rtc_save_t; +int GB_save_battery_size(GB_gameboy_t *gb) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + + GB_rtc_save_t rtc_save_size; + return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); +} + +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + + if (size < GB_save_battery_size(gb)) return EIO; + + memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); + + if (gb->cartridge_type->has_rtc) { + GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; + rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; + rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; + rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; + rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; +#ifdef GB_BIG_ENDIAN + rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); +#else + rtc_save.vba64.last_rtc_second = gb->last_rtc_second; +#endif + memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); + } + + errno = 0; + return errno; +} + int GB_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. @@ -294,6 +336,79 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return errno; } +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) +{ + memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size)); + if (size <= gb->mbc_ram_size) { + goto reset_rtc; + } + + GB_rtc_save_t rtc_save; + memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size)); + switch (size - gb->mbc_ram_size) { + case sizeof(rtc_save.sameboy_legacy): + memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba32.last_rtc_second; +#endif + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba64.last_rtc_second; +#endif + break; + + default: + 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_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ +exit: + return; +} + /* Loading will silently stop if the format is incomplete */ void GB_load_battery(GB_gameboy_t *gb, const char *path) { diff --git a/Core/gb.h b/Core/gb.h index e12fcfac..59a11c29 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -1,5 +1,6 @@ #ifndef GB_h #define GB_h +#define typeof __typeof__ #include #include #include @@ -542,6 +543,7 @@ struct GB_gameboy_internal_s { GB_icd_pixel_callback_t icd_pixel_callback; GB_icd_vreset_callback_t icd_hreset_callback; GB_icd_vreset_callback_t icd_vreset_callback; + GB_read_memory_callback_t read_memory_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -668,8 +670,12 @@ int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); int GB_load_rom(GB_gameboy_t *gb, const char *path); void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); - + +int GB_save_battery_size(GB_gameboy_t *gb); +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); int GB_save_battery(GB_gameboy_t *gb, const char *path); + +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); void GB_load_battery(GB_gameboy_t *gb, const char *path); void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip); diff --git a/Core/memory.c b/Core/memory.c index 76d8821d..40ea00fa 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -429,6 +429,11 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) if (is_addr_in_dma_use(gb, addr)) { addr = gb->dma_current_src; } + if (gb->read_memory_callback) { + uint8_t data = read_map[addr >> 12](gb, addr); + data = gb->read_memory_callback(gb, addr, data); + return data; + } return read_map[addr >> 12](gb, addr); } diff --git a/Core/memory.h b/Core/memory.h index 03d636d0..f0d03907 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -3,6 +3,9 @@ #include "gb_struct_def.h" #include +typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback); + uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); #ifdef GB_INTERNAL diff --git a/Core/sgb.c b/Core/sgb.c index 7d648f77..c36bc3c5 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -3,6 +3,10 @@ #include #include +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + #define INTRO_ANIMATION_LENGTH 200 enum { diff --git a/Core/timing.c b/Core/timing.c index 138819fe..283558c0 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -3,7 +3,7 @@ #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif -#include +#include #else #include #endif From 4fcc921b46e515b2909fe76eb22ef46f1118353e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Sep 2019 13:13:28 +0300 Subject: [PATCH 0922/1216] Fix SGB multiplayer, improve multiplayer accuracy --- Core/gb.c | 2 +- Core/joypad.c | 4 ++-- Core/memory.c | 8 +++++--- Core/sgb.c | 13 ++++++++----- Core/sgb.h | 3 +++ 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index e91420ce..6604e4f9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -858,7 +858,7 @@ void GB_reset(GB_gameboy_t *gb) gb->mbc_rom_bank = 1; gb->last_rtc_second = time(NULL); gb->cgb_ram_bank = 1; - gb->io_registers[GB_IO_JOYP] = 0xF; + gb->io_registers[GB_IO_JOYP] = 0xCF; gb->mbc_ram_size = mbc_ram_size; if (GB_is_cgb(gb)) { gb->ram_size = 0x1000 * 8; diff --git a/Core/joypad.c b/Core/joypad.c index 124d9080..91f6ae37 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -12,7 +12,7 @@ void GB_update_joyp(GB_gameboy_t *gb) 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; - uint8_t current_player = gb->sgb? gb->sgb->current_player : 0; + uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1)) : 0; switch (key_selection) { case 3: if (gb->sgb && gb->sgb->player_count > 1) { @@ -73,7 +73,7 @@ void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value) if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { gb->io_registers[GB_IO_IF] |= 0x10; } - + gb->io_registers[GB_IO_JOYP] |= 0xC0; } void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) diff --git a/Core/memory.c b/Core/memory.c index 40ea00fa..a218f8e7 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -746,9 +746,11 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_JOYP: - gb->io_registers[GB_IO_JOYP] = value & 0xF0; - GB_sgb_write(gb, value); - GB_update_joyp(gb); + if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { + gb->io_registers[GB_IO_JOYP] = value & 0xF0; + GB_sgb_write(gb, value); + GB_update_joyp(gb); + } return; case GB_IO_BIOS: diff --git a/Core/sgb.c b/Core/sgb.c index c36bc3c5..d0ac4727 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -338,7 +338,6 @@ static void command_ready(GB_gameboy_t *gb) break; case MLT_REQ: gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; - gb->sgb->current_player = gb->sgb->player_count - 1; break; case CHR_TRN: gb->sgb->vram_transfer_countdown = 2; @@ -382,13 +381,16 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) if (gb->joyp_write_callback) { gb->joyp_write_callback(gb, value); } + if (!GB_is_sgb(gb)) return; if (!GB_is_hle_sgb(gb)) { /* Notify via callback */ return; } if (gb->sgb->disable_commands) return; - if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return; + if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) { + return; + } uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; if ((gb->sgb->command[0] & 0xF1) == 0xF1) { @@ -398,10 +400,10 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) switch ((value >> 4) & 3) { case 3: gb->sgb->ready_for_pulse = true; - /* TODO: This is the logic used by BGB which *should* work for most/all games, but a proper test ROM is needed */ - if (gb->sgb->player_count > 1 && (gb->io_registers[GB_IO_JOYP] & 0x30) == 0x10) { + if (gb->sgb->player_count > 1 && !gb->sgb->mlt_lock) { gb->sgb->current_player++; - gb->sgb->current_player &= gb->sgb->player_count - 1; + gb->sgb->current_player &= 3; + gb->sgb->mlt_lock = true; } break; @@ -426,6 +428,7 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) } break; case 1: // One + gb->sgb->mlt_lock ^= true; if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; if (gb->sgb->ready_for_stop) { GB_log(gb, "Corrupt SGB command.\n"); diff --git a/Core/sgb.h b/Core/sgb.h index 49bf6d9c..df90253c 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -52,6 +52,9 @@ struct GB_sgb_s { /* GB Header */ uint8_t received_header[0x54]; + + /* Multiplayer (cont) */ + bool mlt_lock; }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From 851dbd3ccd5533f2140f11a182ea6ff61557cbc0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Sep 2019 17:13:21 +0300 Subject: [PATCH 0923/1216] SGB and AGB color correction --- Core/display.c | 41 ++++++++++++++++++++++++++++++++++------- Core/sgb.c | 21 +++------------------ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/Core/display.c b/Core/display.c index 25e95e7e..5c1935c6 100644 --- a/Core/display.c +++ b/Core/display.c @@ -162,9 +162,20 @@ static inline uint8_t scale_channel(uint8_t x) static inline uint8_t scale_channel_with_curve(uint8_t x) { - return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255,}[x]; + return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255}[x]; } +static inline uint8_t scale_channel_with_curve_agb(uint8_t x) +{ + return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x]; +} + +static inline uint8_t scale_channel_with_curve_sgb(uint8_t x) +{ + return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x]; +} + + uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) { uint8_t r = (color) & 0x1F; @@ -177,13 +188,29 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) b = scale_channel(b); } else { - r = scale_channel_with_curve(r); - g = scale_channel_with_curve(g); - b = scale_channel_with_curve(b); + if (GB_is_sgb(gb)) { + return gb->rgb_encode_callback(gb, + scale_channel_with_curve_sgb(r), + scale_channel_with_curve_sgb(g), + scale_channel_with_curve_sgb(b)); + } + bool agb = gb->model == GB_MODEL_AGB; + r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r); + g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g); + b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b); if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) { - uint8_t new_g = (g * 3 + b) / 4; - uint8_t new_r = r, new_b = b; + uint8_t new_r, new_g, new_b; + if (agb) { + new_r = (r * 7 + g * 1) / 8; + new_g = (g * 3 + b * 1) / 4; + new_b = (b * 7 + r * 1) / 8; + } + else { + new_g = (g * 3 + b) / 4; + new_r = r; + new_b = b; + } if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { uint8_t old_max = MAX(r, MAX(g, b)); uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); @@ -200,7 +227,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) if (new_min != 0xff) { new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min); new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min); - new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);; + new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min); } } r = new_r; diff --git a/Core/sgb.c b/Core/sgb.c index d0ac4727..18daa470 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -465,22 +465,9 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) } } -static inline uint8_t scale_channel(uint8_t x) -{ - return (x << 3) | (x >> 2); -} - static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color) { - uint8_t r = (color) & 0x1F; - uint8_t g = (color >> 5) & 0x1F; - uint8_t b = (color >> 10) & 0x1F; - - r = scale_channel(r); - g = scale_channel(g); - b = scale_channel(b); - - return gb->rgb_encode_callback(gb, r, g, b); + return GB_convert_rgb15(gb, color); } static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade) @@ -493,11 +480,9 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_ if (g >= 0x20) g = 0; if (b >= 0x20) b = 0; - r = scale_channel(r); - g = scale_channel(g); - b = scale_channel(b); + color = r | (g << 5) | (b << 10); - return gb->rgb_encode_callback(gb, r, g, b); + return GB_convert_rgb15(gb, color); } #include From 652e52df3dbe5b36e5352a18f50aadf1479dd3c7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Sep 2019 12:31:52 +0300 Subject: [PATCH 0924/1216] Pass the SGB multiplayer tests --- Core/joypad.c | 2 +- Core/memory.c | 2 +- Core/sgb.c | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Core/joypad.c b/Core/joypad.c index 91f6ae37..b8d4fdb4 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -12,7 +12,7 @@ void GB_update_joyp(GB_gameboy_t *gb) 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; - uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1)) : 0; + uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0; switch (key_selection) { case 3: if (gb->sgb && gb->sgb->player_count > 1) { diff --git a/Core/memory.c b/Core/memory.c index a218f8e7..4c4a702c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -747,8 +747,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_JOYP: if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { - gb->io_registers[GB_IO_JOYP] = value & 0xF0; GB_sgb_write(gb, value); + gb->io_registers[GB_IO_JOYP] = value & 0xF0; GB_update_joyp(gb); } return; diff --git a/Core/sgb.c b/Core/sgb.c index 18daa470..8539238c 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -337,7 +337,15 @@ static void command_ready(GB_gameboy_t *gb) // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this break; case MLT_REQ: - gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; + if (gb->sgb->player_count == 1) { + gb->sgb->current_player = 0; + } + gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility, + fix this to be 0 based. */ + if (gb->sgb->player_count == 3) { + gb->sgb->current_player++; + } + gb->sgb->mlt_lock = true; break; case CHR_TRN: gb->sgb->vram_transfer_countdown = 2; @@ -397,10 +405,14 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) command_size = SGB_PACKET_SIZE * 8; } + if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) { + gb->sgb->mlt_lock ^= true; + } + switch ((value >> 4) & 3) { case 3: gb->sgb->ready_for_pulse = true; - if (gb->sgb->player_count > 1 && !gb->sgb->mlt_lock) { + if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) { gb->sgb->current_player++; gb->sgb->current_player &= 3; gb->sgb->mlt_lock = true; @@ -428,7 +440,6 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) } break; case 1: // One - gb->sgb->mlt_lock ^= true; if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; if (gb->sgb->ready_for_stop) { GB_log(gb, "Corrupt SGB command.\n"); From 0c48ecb3f8292b063df78ded5eace3c675368236 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Sep 2019 20:06:01 +0300 Subject: [PATCH 0925/1216] Updated version to 0.12.2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2419061a..79b5e146 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.12.1 +VERSION := 0.12.2 export VERSION CONF ?= debug From ac418b9de18765cdd8c0e94d7df46d5ca0820f0f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 30 Sep 2019 00:09:25 +0300 Subject: [PATCH 0926/1216] Pass channel_1_freq_change_timing --- Core/apu.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index ee581385..a6247ec2 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -681,6 +681,21 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR14: case GB_IO_NR24: { unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1; + + /* TODO: When the sample length changes right before being updated, the countdown should change to the + old length, but the current sample should not change. Because our write timing isn't accurate to + the T-cycle, we hack around it by stepping the sample index backwards. */ + if ((value & 0x80) == 0 && gb->apu.is_active[index]) { + /* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on + double speed. */ + if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */ || gb->apu.square_channels[index].sample_countdown & 1) { + if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) { + gb->apu.square_channels[index].current_sample_index--; + gb->apu.square_channels[index].current_sample_index &= 7; + } + } + } + gb->apu.square_channels[index].sample_length &= 0xFF; gb->apu.square_channels[index].sample_length |= (value & 7) << 8; if (index == GB_SQUARE_1) { From ca370eee7e05224edfcc385b3c755b671645e732 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 1 Oct 2019 18:50:59 +0300 Subject: [PATCH 0927/1216] A bit more accurate AGB audio rendering --- Core/apu.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index a6247ec2..55898b29 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -48,6 +48,23 @@ bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index) return false; } +static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index) +{ + if (!gb->apu.is_active[index]) return 0; + + switch (index) { + case GB_SQUARE_1: + return gb->apu.square_channels[GB_SQUARE_1].current_volume; + case GB_SQUARE_2: + return gb->apu.square_channels[GB_SQUARE_2].current_volume; + case GB_WAVE: + return 0; + case GB_NOISE: + return gb->apu.noise_channel.current_volume; + } + return 0; +} + static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) { if (gb->model >= GB_MODEL_AGB) { @@ -66,15 +83,17 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign } GB_sample_t output; + uint8_t bias = agb_bias_for_channel(gb, index); + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { - output.right = (0xf - value * 2) * right_volume; + output.right = (0xf - value * 2 + bias) * right_volume; } else { output.right = 0xf * right_volume; } if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { - output.left = (0xf - value * 2) * left_volume; + output.left = (0xf - value * 2 + bias) * left_volume; } else { output.left = 0xf * left_volume; From c50ea6a63fb76f4111e305048788071dd0541730 Mon Sep 17 00:00:00 2001 From: f21red <54341442+f21red@users.noreply.github.com> Date: Sat, 5 Oct 2019 20:24:32 -0500 Subject: [PATCH 0928/1216] libretro: sgb color correction --- libretro/libretro.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index e075b2cf..8c00ac57 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -486,7 +486,7 @@ static void check_variables() { var.key = "sameboy_color_correction_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gameboy[0])) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); @@ -540,7 +540,7 @@ static void check_variables() { var.key = "sameboy_color_correction_mode_1"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gameboy[0])) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); @@ -554,7 +554,7 @@ static void check_variables() var.key = "sameboy_color_correction_mode_2"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GB_is_cgb(&gameboy[1])) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_DISABLED); From 0a7a0ca5fe28e34e479411d00c0ce3bc79c69334 Mon Sep 17 00:00:00 2001 From: f21red <54341442+f21red@users.noreply.github.com> Date: Sat, 5 Oct 2019 20:51:59 -0500 Subject: [PATCH 0929/1216] libretro: sgb border option --- libretro/libretro.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 8c00ac57..54c79060 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -92,6 +92,7 @@ static unsigned emulated_devices = 1; static bool initialized = false; static unsigned screen_layout = 0; static unsigned audio_out = 0; +static unsigned sgb_border = 1; static bool geometry_updated = false; static bool link_cable_emulation = false; @@ -204,6 +205,7 @@ static const struct retro_variable vars_single[] = { { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, + { "sameboy_border", "Super Game Boy border; enabled|disabled" }, { NULL } }; @@ -535,6 +537,16 @@ static void check_variables() init_for_current_model(0); } } + + var.key = "sameboy_border"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "enabled") == 0) + sgb_border = 1; + else if (strcmp(var.value, "disabled") == 0) + sgb_border = 0; + } } else { @@ -880,8 +892,15 @@ void retro_run(void) } else { - if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) - video_cb(frame_buf, SGB_VIDEO_WIDTH, SGB_VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); + if (model[0] == MODEL_SGB || model[0] == MODEL_SGB2) { + if (sgb_border == 1) + video_cb(frame_buf, SGB_VIDEO_WIDTH, SGB_VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); + else { + int crop = SGB_VIDEO_WIDTH * ((SGB_VIDEO_HEIGHT - VIDEO_HEIGHT) / 2) + ((SGB_VIDEO_WIDTH - VIDEO_WIDTH) / 2); + + video_cb(frame_buf + crop, VIDEO_WIDTH, VIDEO_HEIGHT, SGB_VIDEO_WIDTH * sizeof(uint32_t)); + } + } else video_cb(frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(uint32_t)); } From dee29c118cbded7703c2f7edcbc7230ffb70b844 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 8 Oct 2019 15:10:24 +0300 Subject: [PATCH 0930/1216] Added GB_set_sample_rate_by_clocks API, split SGB_NO_SFC into PAL and NTSC; now they report the correct clock rate. --- Core/apu.c | 15 +++++++++++++++ Core/apu.h | 3 +++ Core/gb.c | 20 ++++++++++++-------- Core/gb.h | 4 +++- Core/memory.c | 6 ++++-- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 55898b29..3be92d67 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -1004,9 +1004,23 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate) if (sample_rate) { gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); } + gb->apu_output.rate_set_in_clocks = false; GB_apu_update_cycles_per_sample(gb); } +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, unsigned cycles_per_sample) +{ + + if (cycles_per_sample == 0) { + GB_set_sample_rate(gb, 0); + return; + } + gb->apu_output.cycles_per_sample = cycles_per_sample; + gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2; + gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample); + gb->apu_output.rate_set_in_clocks = true; +} + void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) { gb->apu_output.sample_callback = callback; @@ -1019,6 +1033,7 @@ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb) { + if (gb->apu_output.rate_set_in_clocks) return; if (gb->apu_output.sample_rate) { gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ } diff --git a/Core/apu.h b/Core/apu.h index 7f8acfcc..ee6055ba 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -143,9 +143,12 @@ typedef struct { GB_double_sample_t highpass_diff; GB_sample_callback_t sample_callback; + + bool rate_set_in_clocks; } GB_apu_output_t; void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, unsigned cycles_per_sample); /* Cycles are in 8MHz units */ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); #ifdef GB_INTERNAL diff --git a/Core/gb.c b/Core/gb.c index 6604e4f9..93ac9d72 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -712,7 +712,8 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ - case GB_MODEL_SGB_NO_SFC: + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = GB_random(); if (i & 0x100) { @@ -758,7 +759,8 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ - case GB_MODEL_SGB_NO_SFC: + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: for (unsigned i = 0; i < sizeof(gb->hram); i++) { @@ -783,7 +785,8 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified */ case GB_MODEL_SGB_PAL: /* Unverified */ - case GB_MODEL_SGB_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: for (unsigned i = 0; i < 8; i++) { @@ -811,7 +814,8 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ - case GB_MODEL_SGB_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: { uint8_t temp; @@ -1017,12 +1021,12 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) uint32_t GB_get_clock_rate(GB_gameboy_t *gb) { - if (gb->model == GB_MODEL_SGB_NTSC) { - return SGB_NTSC_FREQUENCY * gb->clock_multiplier; - } - if (gb->model == GB_MODEL_SGB_PAL) { + if (gb->model & GB_MODEL_PAL_BIT) { return SGB_PAL_FREQUENCY * gb->clock_multiplier; } + if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { + return SGB_NTSC_FREQUENCY * gb->clock_multiplier; + } return CPU_FREQUENCY * gb->clock_multiplier; } diff --git a/Core/gb.h b/Core/gb.h index 59a11c29..dbd9e165 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -70,7 +70,9 @@ typedef enum { GB_MODEL_SGB = 0x004, GB_MODEL_SGB_NTSC = GB_MODEL_SGB, GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT, - GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, + GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, + GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, + GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, // GB_MODEL_MGB = 0x100, GB_MODEL_SGB2 = 0x101, GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, diff --git a/Core/memory.c b/Core/memory.c index 4c4a702c..c481ad2b 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -273,7 +273,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_PAL: - case GB_MODEL_SGB_NO_SFC: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: ; @@ -591,7 +592,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_PAL: - case GB_MODEL_SGB_NO_SFC: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: case GB_MODEL_CGB_E: From 7d6cdf381974819d2c9b94a0d9344058bed90910 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 17 Oct 2019 21:21:10 +0300 Subject: [PATCH 0931/1216] =?UTF-8?q?Fix=20SGB=20support=20in=20SDL?= =?UTF-8?q?=E2=80=99s=20software=20rendering.=20Fixes=20#208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SDL/gui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index 07233842..6cffeaf0 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -33,7 +33,7 @@ void render_texture(void *pixels, void *previous) { if (renderer) { if (pixels) { - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); + SDL_UpdateTexture(texture, NULL, pixels, GB_get_screen_width(&gb) * sizeof (uint32_t)); } SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); From 0ece21bca7b6d4cbcdab87d4ddbdcd1ea8399c1b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 19 Oct 2019 19:26:04 +0300 Subject: [PATCH 0932/1216] Replace the SDL-derived controller support with my own JoyKit framework. Adds rumble support, LED support, better manual and automatic configurations, analog speed controls. --- Cocoa/AppDelegate.m | 6 + Cocoa/Document.m | 15 +- Cocoa/GBButtons.h | 5 + Cocoa/GBJoystickListener.h | 9 - Cocoa/GBPreferencesWindow.h | 5 +- Cocoa/GBPreferencesWindow.m | 226 +++++---- Cocoa/GBView.h | 5 +- Cocoa/GBView.m | 216 ++++---- Cocoa/Preferences.xib | 72 +-- Cocoa/joypad.m | 748 ---------------------------- Cocoa/main.m | 3 +- Core/debugger.c | 2 +- Core/display.c | 7 + Core/gb.h | 4 +- Core/memory.c | 3 - Core/save_state.c | 8 - Core/timing.c | 8 + JoyKit/ControllerConfiguration.inc | 369 ++++++++++++++ JoyKit/JOYAxes2D.h | 24 + JoyKit/JOYAxes2D.m | 168 +++++++ JoyKit/JOYAxis.h | 29 ++ JoyKit/JOYAxis.m | 90 ++++ JoyKit/JOYButton.h | 42 ++ JoyKit/JOYButton.m | 102 ++++ JoyKit/JOYController.h | 41 ++ JoyKit/JOYController.m | 760 +++++++++++++++++++++++++++++ JoyKit/JOYElement.h | 20 + JoyKit/JOYElement.m | 96 ++++ JoyKit/JOYEmulatedButton.h | 11 + JoyKit/JOYEmulatedButton.m | 91 ++++ JoyKit/JOYHat.h | 11 + JoyKit/JOYHat.m | 60 +++ JoyKit/JOYMultiplayerController.h | 8 + JoyKit/JOYMultiplayerController.m | 44 ++ JoyKit/JOYSubElement.h | 14 + JoyKit/JOYSubElement.m | 99 ++++ JoyKit/JoyKit.h | 6 + Makefile | 2 +- libretro/libretro.c | 15 +- 39 files changed, 2418 insertions(+), 1026 deletions(-) delete mode 100644 Cocoa/GBJoystickListener.h delete mode 100755 Cocoa/joypad.m create mode 100644 JoyKit/ControllerConfiguration.inc create mode 100644 JoyKit/JOYAxes2D.h create mode 100644 JoyKit/JOYAxes2D.m create mode 100644 JoyKit/JOYAxis.h create mode 100644 JoyKit/JOYAxis.m create mode 100644 JoyKit/JOYButton.h create mode 100644 JoyKit/JOYButton.m create mode 100644 JoyKit/JOYController.h create mode 100644 JoyKit/JOYController.m create mode 100644 JoyKit/JOYElement.h create mode 100644 JoyKit/JOYElement.m create mode 100644 JoyKit/JOYEmulatedButton.h create mode 100644 JoyKit/JOYEmulatedButton.m create mode 100644 JoyKit/JOYHat.h create mode 100644 JoyKit/JOYHat.m create mode 100644 JoyKit/JOYMultiplayerController.h create mode 100644 JoyKit/JOYMultiplayerController.m create mode 100644 JoyKit/JOYSubElement.h create mode 100644 JoyKit/JOYSubElement.m create mode 100644 JoyKit/JoyKit.h diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index bbaa3aee..e9666151 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -2,6 +2,7 @@ #include "GBButtons.h" #include #import +#import @implementation AppDelegate { @@ -41,6 +42,11 @@ @"GBCGBModel": @(GB_MODEL_CGB_E), @"GBSGBModel": @(GB_MODEL_SGB2), }]; + + [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ + JOYAxes2DEmulateButtonsKey: @YES, + JOYHatsEmulateButtonsKey: @YES, + }]; } - (IBAction)toggleDeveloperMode:(id)sender diff --git a/Cocoa/Document.m b/Cocoa/Document.m index a520d62e..eef4c7ad 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -74,6 +74,7 @@ enum model { topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin exposure:(unsigned) exposure; - (void) gotNewSample:(GB_sample_t *)sample; +- (void) rumbleChanged:(double)amp; @end static void vblank(GB_gameboy_t *gb) @@ -131,6 +132,12 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [self gotNewSample:sample]; } +static void rumbleCallback(GB_gameboy_t *gb, double amp) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self rumbleChanged:amp]; +} + @implementation Document { GB_gameboy_t gb; @@ -199,6 +206,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); GB_apu_set_sample_callback(&gb, audioCallback); + GB_set_rumble_callback(&gb, rumbleCallback); } - (void) vblank @@ -244,6 +252,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [audioLock unlock]; } +- (void)rumbleChanged:(double)amp +{ + [_view setRumble:amp]; +} + - (void) run { running = true; @@ -295,6 +308,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.audioClient = nil; self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); + [_view setRumble:false]; stopping = false; } @@ -1563,5 +1577,4 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[fileURL path] lastPathComponent]]; } - @end diff --git a/Cocoa/GBButtons.h b/Cocoa/GBButtons.h index 7b2ea5d9..1f8b5afb 100644 --- a/Cocoa/GBButtons.h +++ b/Cocoa/GBButtons.h @@ -19,6 +19,11 @@ typedef enum : NSUInteger { extern NSString const *GBButtonNames[GBButtonCount]; +static inline NSString *n2s(uint64_t number) +{ + return [NSString stringWithFormat:@"%llx", number]; +} + static inline NSString *button_to_preference_name(GBButton button, unsigned player) { if (player) { diff --git a/Cocoa/GBJoystickListener.h b/Cocoa/GBJoystickListener.h deleted file mode 100644 index 069db103..00000000 --- a/Cocoa/GBJoystickListener.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@protocol GBJoystickListener - -- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state; -- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value; -- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) value; - -@end diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 90eee545..cc308a02 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -1,9 +1,10 @@ #import -#import "GBJoystickListener.h" +#import -@interface GBPreferencesWindow : NSWindow +@interface GBPreferencesWindow : NSWindow @property IBOutlet NSTableView *controlsTableView; @property IBOutlet NSPopUpButton *graphicsFilterPopupButton; +@property (strong) IBOutlet NSButton *analogControlsCheckbox; @property (strong) IBOutlet NSButton *aspectRatioCheckbox; @property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; @property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index ecf03111..97628f10 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -9,13 +9,14 @@ NSInteger button_being_modified; signed joystick_configuration_state; NSString *joystick_being_configured; - signed last_axis; + bool joypad_wait; NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton; NSPopUpButton *_colorCorrectionPopupButton; NSPopUpButton *_rewindPopupButton; NSButton *_aspectRatioCheckbox; + NSButton *_analogControlsCheckbox; NSEventModifierFlags previousModifiers; NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; @@ -51,7 +52,7 @@ joystick_configuration_state = -1; [self.configureJoypadButton setEnabled:YES]; [self.skipButton setEnabled:NO]; - [self.configureJoypadButton setTitle:@"Configure Joypad"]; + [self.configureJoypadButton setTitle:@"Configure Controller"]; [super close]; } @@ -184,6 +185,12 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil]; } +- (IBAction)changeAnalogControls:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAnalogControls"]; +} + - (IBAction)changeAspectRatio:(id)sender { [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState @@ -212,7 +219,6 @@ [self.skipButton setEnabled:YES]; joystick_being_configured = nil; [self advanceConfigurationStateMachine]; - last_axis = -1; } - (IBAction) skipButton:(id)sender @@ -223,11 +229,11 @@ - (void) advanceConfigurationStateMachine { joystick_configuration_state++; - if (joystick_configuration_state < GBButtonCount) { - [self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]]; + if (joystick_configuration_state == GBUnderclock) { + [self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :< } - else if (joystick_configuration_state == GBButtonCount) { - [self.configureJoypadButton setTitle:@"Move the Analog Stick"]; + else if (joystick_configuration_state < GBButtonCount) { + [self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]]; } else { joystick_configuration_state = -1; @@ -237,112 +243,97 @@ } } -- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { - if (!state) return; + /* Debounce */ + if (joypad_wait) return; + joypad_wait = true; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + joypad_wait = false; + }); + + NSLog(@"%@", button); + + if (!button.isPressed) return; if (joystick_configuration_state == -1) return; if (joystick_configuration_state == GBButtonCount) return; if (!joystick_being_configured) { - joystick_being_configured = joystick_name; + joystick_being_configured = controller.uniqueID; } - else if (![joystick_being_configured isEqualToString:joystick_name]) { + else if (![joystick_being_configured isEqualToString:controller.uniqueID]) { return; } - NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy]; + NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy]; - if (!all_mappings) { - all_mappings = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy]; + + + if (!instance_mappings) { + instance_mappings = [[NSMutableDictionary alloc] init]; } - NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy]; + if (!name_mappings) { + name_mappings = [[NSMutableDictionary alloc] init]; + } - if (!mapping) { + NSMutableDictionary *mapping = nil; + if (joystick_configuration_state != 0) { + mapping = [instance_mappings[controller.uniqueID] mutableCopy]; + } + else { mapping = [[NSMutableDictionary alloc] init]; } + - mapping[GBButtonNames[joystick_configuration_state]] = @(button); + static const unsigned gb_to_joykit[] = { + [GBRight]=JOYButtonUsageDPadRight, + [GBLeft]=JOYButtonUsageDPadLeft, + [GBUp]=JOYButtonUsageDPadUp, + [GBDown]=JOYButtonUsageDPadDown, + [GBA]=JOYButtonUsageA, + [GBB]=JOYButtonUsageB, + [GBSelect]=JOYButtonUsageSelect, + [GBStart]=JOYButtonUsageStart, + [GBTurbo]=JOYButtonUsageL1, + [GBRewind]=JOYButtonUsageL2, + [GBUnderclock]=JOYButtonUsageR1, + }; - all_mappings[joystick_name] = mapping; - [[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; - [self refreshJoypadMenu:nil]; + if (joystick_configuration_state == GBUnderclock) { + for (JOYAxis *axis in controller.axes) { + if (axis.value > 0.5) { + mapping[@"AnalogUnderclock"] = @(axis.uniqueID); + } + } + } + + if (joystick_configuration_state == GBTurbo) { + for (JOYAxis *axis in controller.axes) { + if (axis.value > 0.5) { + mapping[@"AnalogTurbo"] = @(axis.uniqueID); + } + } + } + + mapping[n2s(button.uniqueID)] = @(gb_to_joykit[joystick_configuration_state]); + + instance_mappings[controller.uniqueID] = mapping; + name_mappings[controller.deviceName] = mapping; + [[NSUserDefaults standardUserDefaults] setObject:instance_mappings forKey:@"JoyKitInstanceMapping"]; + [[NSUserDefaults standardUserDefaults] setObject:name_mappings forKey:@"JoyKitNameMapping"]; [self advanceConfigurationStateMachine]; } -- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value +- (NSButton *)analogControlsCheckbox { - if (abs(value) < 0x4000) return; - if (joystick_configuration_state != GBButtonCount) return; - if (!joystick_being_configured) { - joystick_being_configured = joystick_name; - } - else if (![joystick_being_configured isEqualToString:joystick_name]) { - return; - } - - if (last_axis == -1) { - last_axis = axis; - return; - } - - if (axis == last_axis) { - return; - } - - NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy]; - - if (!all_mappings) { - all_mappings = [[NSMutableDictionary alloc] init]; - } - - NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy]; - - if (!mapping) { - mapping = [[NSMutableDictionary alloc] init]; - } - - mapping[@"XAxis"] = @(MIN(axis, last_axis)); - mapping[@"YAxis"] = @(MAX(axis, last_axis)); - - all_mappings[joystick_name] = mapping; - [[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; - [self advanceConfigurationStateMachine]; + return _analogControlsCheckbox; } -- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state +- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox { - /* Hats are always mapped to the D-pad, ignore them on non-Dpad keys and skip the D-pad configuration if used*/ - if (!state) return; - if (joystick_configuration_state == -1) return; - if (joystick_configuration_state > GBDown) return; - if (!joystick_being_configured) { - joystick_being_configured = joystick_name; - } - else if (![joystick_being_configured isEqualToString:joystick_name]) { - return; - } - - NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy]; - - if (!all_mappings) { - all_mappings = [[NSMutableDictionary alloc] init]; - } - - NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy]; - - if (!mapping) { - mapping = [[NSMutableDictionary alloc] init]; - } - - for (joystick_configuration_state = 0;; joystick_configuration_state++) { - [mapping removeObjectForKey:GBButtonNames[joystick_configuration_state]]; - if (joystick_configuration_state == GBDown) break; - } - - all_mappings[joystick_name] = mapping; - [[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; - [self refreshJoypadMenu:nil]; - [self advanceConfigurationStateMachine]; + _analogControlsCheckbox = analogControlsCheckbox; + [_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]]; } - (NSButton *)aspectRatioCheckbox @@ -361,10 +352,13 @@ [super awakeFromNib]; [self updateBootROMFolderButton]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil]; + [JOYController registerListener:self]; + joystick_configuration_state = -1; } - (void)dealloc { + [JOYController unregisterListener:self]; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView]; } @@ -483,21 +477,47 @@ return _preferredJoypadButton; } +- (void)controllerConnected:(JOYController *)controller +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self refreshJoypadMenu:nil]; + }); +} + +- (void)controllerDisconnected:(JOYController *)controller +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self refreshJoypadMenu:nil]; + }); +} + - (IBAction)refreshJoypadMenu:(id)sender { - NSArray *joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] allKeys]; - for (NSString *joypad in joypads) { - if ([self.preferredJoypadButton indexOfItemWithTitle:joypad] == -1) { - [self.preferredJoypadButton addItemWithTitle:joypad]; + bool preferred_is_connected = false; + NSString *player_string = n2s(self.playerListButton.selectedTag); + NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string]; + + [self.preferredJoypadButton removeAllItems]; + [self.preferredJoypadButton addItemWithTitle:@"None"]; + for (JOYController *controller in [JOYController allControllers]) { + [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]]; + + self.preferredJoypadButton.lastItem.identifier = controller.uniqueID; + + if ([controller.uniqueID isEqualToString:selected_controller]) { + preferred_is_connected = true; + [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem]; } } - NSString *player_string = [NSString stringWithFormat: @"%ld", (long)self.playerListButton.selectedTag]; - NSString *selected_joypad = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"][player_string]; - if (selected_joypad && [self.preferredJoypadButton indexOfItemWithTitle:selected_joypad] != -1) { - [self.preferredJoypadButton selectItemWithTitle:selected_joypad]; + if (!preferred_is_connected && selected_controller) { + [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]]; + self.preferredJoypadButton.lastItem.identifier = selected_controller; + [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem]; } - else { + + + if (!selected_controller) { [self.preferredJoypadButton selectItemWithTitle:@"None"]; } [self.controlsTableView reloadData]; @@ -505,18 +525,18 @@ - (IBAction)changeDefaultJoypad:(id)sender { - NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] mutableCopy]; + NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy]; if (!default_joypads) { default_joypads = [[NSMutableDictionary alloc] init]; } - NSString *player_string = [NSString stringWithFormat: @"%ld", self.playerListButton.selectedTag]; + NSString *player_string = n2s(self.playerListButton.selectedTag); if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) { [default_joypads removeObjectForKey:player_string]; } else { - default_joypads[player_string] = [sender titleOfSelectedItem]; + default_joypads[player_string] = [[sender selectedItem] identifier]; } - [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"GBDefaultJoypads"]; + [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"]; } @end diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index f4c5e449..20bc7bf8 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,8 +1,8 @@ #import #include -#import "GBJoystickListener.h" +#import -@interface GBView : NSView +@interface GBView : NSView - (void) flip; - (uint32_t *) pixels; @property GB_gameboy_t *gb; @@ -14,4 +14,5 @@ - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; - (void)screenSizeChanged; +- (void)setRumble: (bool)on; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 5a851f34..adc0781b 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -1,4 +1,4 @@ -#import +#import #import "GBView.h" #import "GBViewGL.h" #import "GBViewMetal.h" @@ -18,7 +18,10 @@ bool axisActive[2]; bool underclockKeyDown; double clockMultiplier; + double analogClockMultiplier; + bool analogClockMultiplierValid; NSEventModifierFlags previousModifiers; + JOYController *lastController; } + (instancetype)alloc @@ -55,6 +58,7 @@ [self createInternalView]; [self addSubview:self.internalView]; self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [JOYController registerListener:self]; } - (void)screenSizeChanged @@ -100,6 +104,8 @@ [NSCursor unhide]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; + [lastController setRumbleAmplitude:0]; + [JOYController unregisterListener:self]; } - (instancetype)initWithCoder:(NSCoder *)coder { @@ -147,13 +153,21 @@ - (void) flip { - if (underclockKeyDown && clockMultiplier > 0.5) { - clockMultiplier -= 1.0/16; - GB_set_clock_multiplier(_gb, clockMultiplier); + if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) { + GB_set_clock_multiplier(_gb, analogClockMultiplier); + if (analogClockMultiplier == 1.0) { + analogClockMultiplierValid = false; + } } - if (!underclockKeyDown && clockMultiplier < 1.0) { - clockMultiplier += 1.0/16; - GB_set_clock_multiplier(_gb, clockMultiplier); + else { + if (underclockKeyDown && clockMultiplier > 0.5) { + clockMultiplier -= 1.0/16; + GB_set_clock_multiplier(_gb, clockMultiplier); + } + if (!underclockKeyDown && clockMultiplier < 1.0) { + clockMultiplier += 1.0/16; + GB_set_clock_multiplier(_gb, clockMultiplier); + } } current_buffer = (current_buffer + 1) % self.numberOfBuffers; } @@ -180,6 +194,7 @@ switch (button) { case GBTurbo: GB_set_turbo_mode(_gb, true, self.isRewinding); + analogClockMultiplierValid = false; break; case GBRewind: @@ -189,6 +204,7 @@ case GBUnderclock: underclockKeyDown = true; + analogClockMultiplierValid = false; break; default: @@ -221,6 +237,7 @@ switch (button) { case GBTurbo: GB_set_turbo_mode(_gb, false, false); + analogClockMultiplierValid = false; break; case GBRewind: @@ -229,6 +246,7 @@ case GBUnderclock: underclockKeyDown = false; + analogClockMultiplierValid = false; break; default: @@ -243,123 +261,97 @@ } } -- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state +- (void)setRumble:(bool)on { - unsigned player_count = GB_get_player_count(_gb); - - UpdateSystemActivity(UsrActivity); - for (unsigned player = 0; player < player_count; player++) { - NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] - objectForKey:[NSString stringWithFormat:@"%u", player]]; - if (player_count != 1 && // Single player, accpet inputs from all joypads - !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads - ![preferred_joypad isEqualToString:joystick_name]) { - continue; - } - NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; - - for (GBButton i = 0; i < GBButtonCount; i++) { - NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]]; - if (mapped_button && [mapped_button integerValue] == button) { - switch (i) { - case GBTurbo: - GB_set_turbo_mode(_gb, state, state && self.isRewinding); - break; - - case GBRewind: - self.isRewinding = state; - if (state) { - GB_set_turbo_mode(_gb, false, false); - } - break; - - case GBUnderclock: - underclockKeyDown = state; - break; - - default: - GB_set_key_state_for_player(_gb, (GB_key_t)i, player, state); - break; - } - } - } - } + [lastController setRumbleAmplitude:(double)on]; } -- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value +- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis { - unsigned player_count = GB_get_player_count(_gb); + if (![self.window isMainWindow]) return; - UpdateSystemActivity(UsrActivity); - for (unsigned player = 0; player < player_count; player++) { - NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] - objectForKey:[NSString stringWithFormat:@"%u", player]]; - if (player_count != 1 && // Single player, accpet inputs from all joypads - !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads - ![preferred_joypad isEqualToString:joystick_name]) { - continue; - } - - NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; - NSNumber *x_axis = [mapping objectForKey:@"XAxis"]; - NSNumber *y_axis = [mapping objectForKey:@"YAxis"]; - - if (axis == [x_axis integerValue]) { - if (value > JOYSTICK_HIGH) { - axisActive[0] = true; - GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, true); - GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false); - } - else if (value < -JOYSTICK_HIGH) { - axisActive[0] = true; - GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false); - GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, true); - } - else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { - axisActive[0] = false; - GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false); - GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false); - } - } - else if (axis == [y_axis integerValue]) { - if (value > JOYSTICK_HIGH) { - axisActive[1] = true; - GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, true); - GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false); - } - else if (value < -JOYSTICK_HIGH) { - axisActive[1] = true; - GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false); - GB_set_key_state_for_player(_gb, GB_KEY_UP, player, true); - } - else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) { - axisActive[1] = false; - GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false); - GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false); - } - } + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; + if (!mapping) { + mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; } -} - -- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state -{ - unsigned player_count = GB_get_player_count(_gb); - UpdateSystemActivity(UsrActivity); + if ((axis.usage == JOYAxisUsageR1 && !mapping) || + axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){ + analogClockMultiplier = MIN(MAX(1 - axis.value + 0.2, 1.0 / 3), 1.0); + analogClockMultiplierValid = true; + } + + else if ((axis.usage == JOYAxisUsageL1 && !mapping) || + axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){ + analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.8, 1.0), 3.0); + analogClockMultiplierValid = true; + } +} + +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button +{ + if (![self.window isMainWindow]) return; + if (controller != lastController) { + [lastController setRumbleAmplitude:0]; + lastController = controller; + } + + + unsigned player_count = GB_get_player_count(_gb); + + IOPMAssertionID assertionID; + IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID); + for (unsigned player = 0; player < player_count; player++) { - NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] - objectForKey:[NSString stringWithFormat:@"%u", player]]; + NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] + objectForKey:n2s(player)]; if (player_count != 1 && // Single player, accpet inputs from all joypads !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads - ![preferred_joypad isEqualToString:joystick_name]) { + ![preferred_joypad isEqualToString:controller.uniqueID]) { continue; } - assert(state + 1 < 9); - /* - N NE E SE S SW W NW */ - GB_set_key_state_for_player(_gb, GB_KEY_UP, player, (bool []){0, 1, 1, 0, 0, 0, 0, 0, 1}[state + 1]); - GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, (bool []){0, 0, 1, 1, 1, 0, 0, 0, 0}[state + 1]); - GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, (bool []){0, 0, 0, 0, 1, 1, 1, 0, 0}[state + 1]); - GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, (bool []){0, 0, 0, 0, 0, 0, 1, 1, 1}[state + 1]); + [controller setPlayerLEDs:1 << player]; + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; + if (!mapping) { + mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; + } + + JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage; + if (!mapping && usage >= JOYButtonUsageGeneric0) { + usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3]; + } + + switch (usage) { + + case JOYButtonUsageNone: break; + case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break; + case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break; + case JOYButtonUsageC: break; + case JOYButtonUsageStart: + case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break; + case JOYButtonUsageSelect: + case JOYButtonUsageY: GB_set_key_state_for_player(_gb, GB_KEY_SELECT, player, button.isPressed); break; + case JOYButtonUsageR2: + case JOYButtonUsageL2: + case JOYButtonUsageZ: { + self.isRewinding = button.isPressed; + if (button.isPressed) { + GB_set_turbo_mode(_gb, false, false); + } + break; + } + + case JOYButtonUsageL1: GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break; + + case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break; + case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break; + case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break; + case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break; + case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break; + + default: + break; + } } } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 8278ee16..f9dd9c08 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,8 +1,8 @@ - + - + @@ -17,7 +17,7 @@ - + @@ -58,6 +58,7 @@ + @@ -369,22 +370,11 @@ - + - - + @@ -393,7 +383,7 @@ - + @@ -441,28 +431,28 @@ -

    @@ -476,11 +466,11 @@ - + - + @@ -489,7 +479,7 @@ - + @@ -507,10 +497,21 @@ - + + - + diff --git a/Cocoa/joypad.m b/Cocoa/joypad.m deleted file mode 100755 index 2ffe56b8..00000000 --- a/Cocoa/joypad.m +++ /dev/null @@ -1,748 +0,0 @@ -/* - Joypad support is based on a stripped-down version of SDL's Darwin implementation - of the Joystick API, under the following license: -*/ - -/* - Simple DirectMedia Layer - Copyright (C) 1997-2017 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#include -#include -#include -#include -#include "GBJoystickListener.h" - -typedef signed SDL_JoystickID; -typedef struct _SDL_Joystick SDL_Joystick; - -typedef struct _SDL_JoystickAxisInfo -{ - int16_t initial_value; /* Initial axis state */ - int16_t value; /* Current axis state */ - int16_t zero; /* Zero point on the axis (-32768 for triggers) */ - bool has_initial_value; /* Whether we've seen a value on the axis yet */ - bool sent_initial_value; /* Whether we've sent the initial axis value */ -} SDL_JoystickAxisInfo; - -struct _SDL_Joystick -{ - SDL_JoystickID instance_id; /* Device instance, monotonically increasing from 0 */ - char *name; /* Joystick name - system dependent */ - - int naxes; /* Number of axis controls on the joystick */ - SDL_JoystickAxisInfo *axes; - - int nbuttons; /* Number of buttons on the joystick */ - uint8_t *buttons; /* Current button states */ - - int nhats; - uint8_t *hats; - - struct joystick_hwdata *hwdata; /* Driver dependent information */ - - int ref_count; /* Reference count for multiple opens */ - - bool is_game_controller; - bool force_recentering; /* SDL_TRUE if this device needs to have its state reset to 0 */ - struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */ -}; - -typedef struct { - uint8_t data[16]; -} SDL_JoystickGUID; - -struct recElement -{ - IOHIDElementRef elementRef; - IOHIDElementCookie cookie; - uint32_t usagePage, usage; /* HID usage */ - SInt32 min; /* reported min value possible */ - SInt32 max; /* reported max value possible */ - - /* runtime variables used for auto-calibration */ - SInt32 minReport; /* min returned value */ - SInt32 maxReport; /* max returned value */ - - struct recElement *pNext; /* next element in list */ -}; -typedef struct recElement recElement; - -struct joystick_hwdata -{ - IOHIDDeviceRef deviceRef; /* HIDManager device handle */ - io_service_t ffservice; /* Interface for force feedback, 0 = no ff */ - - char product[256]; /* name of product */ - uint32_t usage; /* usage page from IOUSBHID Parser.h which defines general usage */ - uint32_t usagePage; /* usage within above page from IOUSBHID Parser.h which defines specific usage */ - - int axes; /* number of axis (calculated, not reported by device) */ - int buttons; /* number of buttons (calculated, not reported by device) */ - int hats; - int elements; /* number of total elements (should be total of above) (calculated, not reported by device) */ - - recElement *firstAxis; - recElement *firstButton; - recElement *firstHat; - - bool removed; - - int instance_id; - SDL_JoystickGUID guid; - - SDL_Joystick joystick; -}; -typedef struct joystick_hwdata recDevice; - -/* The base object of the HID Manager API */ -static IOHIDManagerRef hidman = NULL; - -/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */ -static int s_joystick_instance_id = -1; - -#define SDL_JOYSTICK_AXIS_MAX 32767 - -void SDL_PrivateJoystickAxis(SDL_Joystick * joystick, uint8_t axis, int16_t value) -{ - /* Make sure we're not getting garbage or duplicate events */ - if (axis >= joystick->naxes) { - return; - } - if (!joystick->axes[axis].has_initial_value) { - joystick->axes[axis].initial_value = value; - joystick->axes[axis].value = value; - joystick->axes[axis].zero = value; - joystick->axes[axis].has_initial_value = true; - } - if (value == joystick->axes[axis].value) { - return; - } - if (!joystick->axes[axis].sent_initial_value) { - /* Make sure we don't send motion until there's real activity on this axis */ - const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 controller needed 96 */ - if (abs(value - joystick->axes[axis].value) <= MAX_ALLOWED_JITTER) { - return; - } - joystick->axes[axis].sent_initial_value = true; - joystick->axes[axis].value = value; /* Just so we pass the check above */ - SDL_PrivateJoystickAxis(joystick, axis, joystick->axes[axis].initial_value); - } - - /* Update internal joystick state */ - joystick->axes[axis].value = value; - - NSResponder *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder]; - while (responder) { - if ([responder respondsToSelector:@selector(joystick:axis:movedTo:)]) { - [responder joystick:@(joystick->name) axis:axis movedTo:value]; - break; - } - responder = (typeof(responder)) [responder nextResponder]; - } -} - -void SDL_PrivateJoystickButton(SDL_Joystick *joystick, uint8_t button, uint8_t state) -{ - - /* Make sure we're not getting garbage or duplicate events */ - if (button >= joystick->nbuttons) { - return; - } - if (state == joystick->buttons[button]) { - return; - } - - /* Update internal joystick state */ - joystick->buttons[button] = state; - - NSResponder *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder]; - while (responder) { - if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) { - [responder joystick:@(joystick->name) button:button changedState:state]; - break; - } - responder = (typeof(responder)) [responder nextResponder]; - } -} - -void SDL_PrivateJoystickHat(SDL_Joystick *joystick, uint8_t hat, uint8_t state) -{ - - /* Make sure we're not getting garbage or duplicate events */ - if (hat >= joystick->nhats) { - return; - } - if (state == joystick->hats[hat]) { - return; - } - - /* Update internal joystick state */ - joystick->hats[hat] = state; - - NSResponder *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder]; - while (responder) { - if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) { - [responder joystick:@(joystick->name) hat:hat changedState:state]; - break; - } - responder = (typeof(responder)) [responder nextResponder]; - } -} - -static void -FreeElementList(recElement *pElement) -{ - while (pElement) { - recElement *pElementNext = pElement->pNext; - free(pElement); - pElement = pElementNext; - } -} - - -static recDevice * -FreeDevice(recDevice *removeDevice) -{ - recDevice *pDeviceNext = NULL; - if (removeDevice) { - if (removeDevice->deviceRef) { - IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - removeDevice->deviceRef = NULL; - } - - /* free element lists */ - FreeElementList(removeDevice->firstAxis); - FreeElementList(removeDevice->firstButton); - FreeElementList(removeDevice->firstHat); - - free(removeDevice); - } - return pDeviceNext; -} - -static SInt32 -GetHIDElementState(recDevice *pDevice, recElement *pElement) -{ - SInt32 value = 0; - - if (pDevice && pElement) { - IOHIDValueRef valueRef; - if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) { - value = (SInt32) IOHIDValueGetIntegerValue(valueRef); - - /* record min and max for auto calibration */ - if (value < pElement->minReport) { - pElement->minReport = value; - } - if (value > pElement->maxReport) { - pElement->maxReport = value; - } - } - } - - return value; -} - -static SInt32 -GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max) -{ - const float deviceScale = max - min; - const float readScale = pElement->maxReport - pElement->minReport; - const SInt32 value = GetHIDElementState(pDevice, pElement); - if (readScale == 0) { - return value; /* no scaling at all */ - } - return ((value - pElement->minReport) * deviceScale / readScale) + min; -} - - -static void -JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) -{ - recDevice *device = (recDevice *) ctx; - device->removed = true; - device->deviceRef = NULL; // deviceRef was invalidated due to the remove - FreeDevice(device); -} - - -static void AddHIDElement(const void *value, void *parameter); - -/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */ -static void -AddHIDElements(CFArrayRef array, recDevice *pDevice) -{ - const CFRange range = { 0, CFArrayGetCount(array) }; - CFArrayApplyFunction(array, range, AddHIDElement, pDevice); -} - -static bool -ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) { - while (listitem) { - if (listitem->cookie == cookie) { - return true; - } - listitem = listitem->pNext; - } - return false; -} - -/* See if we care about this HID element, and if so, note it in our recDevice. */ -static void -AddHIDElement(const void *value, void *parameter) -{ - recDevice *pDevice = (recDevice *) parameter; - IOHIDElementRef refElement = (IOHIDElementRef) value; - const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0; - - if (refElement && (elementTypeID == IOHIDElementGetTypeID())) { - const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement); - const uint32_t usagePage = IOHIDElementGetUsagePage(refElement); - const uint32_t usage = IOHIDElementGetUsage(refElement); - recElement *element = NULL; - recElement **headElement = NULL; - - /* look at types of interest */ - switch (IOHIDElementGetType(refElement)) { - case kIOHIDElementTypeInput_Misc: - case kIOHIDElementTypeInput_Button: - case kIOHIDElementTypeInput_Axis: { - switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */ - case kHIDPage_GenericDesktop: - switch (usage) { - case kHIDUsage_GD_X: - case kHIDUsage_GD_Y: - case kHIDUsage_GD_Z: - case kHIDUsage_GD_Rx: - case kHIDUsage_GD_Ry: - case kHIDUsage_GD_Rz: - case kHIDUsage_GD_Slider: - case kHIDUsage_GD_Dial: - case kHIDUsage_GD_Wheel: - if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { - element = (recElement *) calloc(1, sizeof (recElement)); - if (element) { - pDevice->axes++; - headElement = &(pDevice->firstAxis); - } - } - break; - case kHIDUsage_GD_Hatswitch: - if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) { - element = (recElement *) calloc(1, sizeof (recElement)); - if (element) { - pDevice->hats++; - headElement = &(pDevice->firstHat); - } - } - break; - - case kHIDUsage_GD_DPadUp: - case kHIDUsage_GD_DPadDown: - case kHIDUsage_GD_DPadRight: - case kHIDUsage_GD_DPadLeft: - case kHIDUsage_GD_Start: - case kHIDUsage_GD_Select: - case kHIDUsage_GD_SystemMainMenu: - if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { - element = (recElement *) calloc(1, sizeof (recElement)); - if (element) { - pDevice->buttons++; - headElement = &(pDevice->firstButton); - } - } - break; - } - break; - - case kHIDPage_Simulation: - switch (usage) { - case kHIDUsage_Sim_Rudder: - case kHIDUsage_Sim_Throttle: - case kHIDUsage_Sim_Accelerator: - case kHIDUsage_Sim_Brake: - if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { - element = (recElement *) calloc(1, sizeof (recElement)); - if (element) { - pDevice->axes++; - headElement = &(pDevice->firstAxis); - } - } - break; - - default: - break; - } - break; - - case kHIDPage_Button: - case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */ - if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { - element = (recElement *) calloc(1, sizeof (recElement)); - if (element) { - pDevice->buttons++; - headElement = &(pDevice->firstButton); - } - } - break; - - default: - break; - } - } - break; - - case kIOHIDElementTypeCollection: { - CFArrayRef array = IOHIDElementGetChildren(refElement); - if (array) { - AddHIDElements(array, pDevice); - } - } - break; - - default: - break; - } - - if (element && headElement) { /* add to list */ - recElement *elementPrevious = NULL; - recElement *elementCurrent = *headElement; - while (elementCurrent && usage >= elementCurrent->usage) { - elementPrevious = elementCurrent; - elementCurrent = elementCurrent->pNext; - } - if (elementPrevious) { - elementPrevious->pNext = element; - } else { - *headElement = element; - } - - element->elementRef = refElement; - element->usagePage = usagePage; - element->usage = usage; - element->pNext = elementCurrent; - - element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement); - element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement); - element->cookie = IOHIDElementGetCookie(refElement); - - pDevice->elements++; - } - } -} - -static bool -GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) -{ - const uint16_t BUS_USB = 0x03; - const uint16_t BUS_BLUETOOTH = 0x05; - int32_t vendor = 0; - int32_t product = 0; - int32_t version = 0; - CFTypeRef refCF = NULL; - CFArrayRef array = NULL; - uint16_t *guid16 = (uint16_t *)pDevice->guid.data; - - /* get usage page and usage */ - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey)); - if (refCF) { - CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage); - } - if (pDevice->usagePage != kHIDPage_GenericDesktop) { - return false; /* Filter device list to non-keyboard/mouse stuff */ - } - - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey)); - if (refCF) { - CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage); - } - - if ((pDevice->usage != kHIDUsage_GD_Joystick && - pDevice->usage != kHIDUsage_GD_GamePad && - pDevice->usage != kHIDUsage_GD_MultiAxisController)) { - return false; /* Filter device list to non-keyboard/mouse stuff */ - } - - pDevice->deviceRef = hidDevice; - - /* get device name */ - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey)); - if (!refCF) { - /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */ - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey)); - } - if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) { - strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product)); - } - - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey)); - if (refCF) { - CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor); - } - - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey)); - if (refCF) { - CFNumberGetValue(refCF, kCFNumberSInt32Type, &product); - } - - refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey)); - if (refCF) { - CFNumberGetValue(refCF, kCFNumberSInt32Type, &version); - } - - memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data)); - - if (vendor && product) { - *guid16++ = BUS_USB; - *guid16++ = 0; - *guid16++ = vendor; - *guid16++ = 0; - *guid16++ = product; - *guid16++ = 0; - *guid16++ = version; - *guid16++ = 0; - } else { - *guid16++ = BUS_BLUETOOTH; - *guid16++ = 0; - strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4); - } - - array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); - if (array) { - AddHIDElements(array, pDevice); - CFRelease(array); - } - - return true; -} - -void -SDL_SYS_JoystickUpdate(SDL_Joystick * joystick) -{ - recDevice *device = joystick->hwdata; - recElement *element; - SInt32 value; - int i; - - if (!device) { - return; - } - - if (device->removed) { /* device was unplugged; ignore it. */ - if (joystick->hwdata) { - joystick->force_recentering = true; - joystick->hwdata = NULL; - } - return; - } - - element = device->firstAxis; - i = 0; - while (element) { - value = GetHIDScaledCalibratedState(device, element, -32768, 32767); - SDL_PrivateJoystickAxis(joystick, i, value); - element = element->pNext; - ++i; - } - - element = device->firstButton; - i = 0; - while (element) { - value = GetHIDElementState(device, element); - if (value > 1) { /* handle pressure-sensitive buttons */ - value = 1; - } - SDL_PrivateJoystickButton(joystick, i, value); - element = element->pNext; - ++i; - } - - element = device->firstHat; - i = 0; - while (element) { - signed range = (element->max - element->min + 1); - value = GetHIDElementState(device, element) - element->min; - if (range == 4) { /* 4 position hatswitch - scale up value */ - value *= 2; - } else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */ - value = -1; - } - if ((unsigned)value >= 8) { - value = -1; - } - - SDL_PrivateJoystickHat(joystick, i, value); - - element = element->pNext; - ++i; - } - -} - -static void JoystickInputCallback( - SDL_Joystick * joystick, - IOReturn result, - void * _Nullable sender, - IOHIDReportType type, - uint32_t reportID, - uint8_t * report, - CFIndex reportLength) -{ - SDL_SYS_JoystickUpdate(joystick); -} - -static void -JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) -{ - recDevice *device; - io_service_t ioservice; - - if (res != kIOReturnSuccess) { - return; - } - - device = (recDevice *) calloc(1, sizeof(recDevice)); - - if (!device) { - abort(); - return; - } - - if (!GetDeviceInfo(ioHIDDeviceObject, device)) { - free(device); - return; /* not a device we care about, probably. */ - } - - SDL_Joystick *joystick = &device->joystick; - - joystick->instance_id = device->instance_id; - joystick->hwdata = device; - joystick->name = device->product; - - joystick->naxes = device->axes; - joystick->nbuttons = device->buttons; - joystick->nhats = device->hats; - - if (joystick->naxes > 0) { - joystick->axes = (SDL_JoystickAxisInfo *) calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo)); - } - if (joystick->nbuttons > 0) { - joystick->buttons = (uint8_t *) calloc(joystick->nbuttons, 1); - } - if (joystick->nhats > 0) { - joystick->hats = (uint8_t *) calloc(joystick->nhats, 1); - } - - /* Get notified when this device is disconnected. */ - IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device); - static uint8_t junk[80]; - IOHIDDeviceRegisterInputReportCallback(ioHIDDeviceObject, junk, sizeof(junk), (IOHIDReportCallback) JoystickInputCallback, joystick); - IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - - /* Allocate an instance ID for this device */ - device->instance_id = ++s_joystick_instance_id; - - /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */ - ioservice = IOHIDDeviceGetService(ioHIDDeviceObject); -} - -static bool -ConfigHIDManager(CFArrayRef matchingArray) -{ - CFRunLoopRef runloop = CFRunLoopGetCurrent(); - - if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { - return false; - } - - IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray); - IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL); - IOHIDManagerScheduleWithRunLoop(hidman, runloop, kCFRunLoopDefaultMode); - - return true; /* good to go. */ -} - - -static CFDictionaryRef -CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay) -{ - CFDictionaryRef retval = NULL; - CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); - CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); - const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) }; - const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef }; - - if (pageNumRef && usageNumRef) { - retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - } - - if (pageNumRef) { - CFRelease(pageNumRef); - } - if (usageNumRef) { - CFRelease(usageNumRef); - } - - if (!retval) { - *okay = 0; - } - - return retval; -} - -static bool -CreateHIDManager(void) -{ - bool retval = false; - int okay = 1; - const void *vals[] = { - (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), - (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), - (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), - }; - const size_t numElements = sizeof(vals) / sizeof(vals[0]); - CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL; - size_t i; - - for (i = 0; i < numElements; i++) { - if (vals[i]) { - CFRelease((CFTypeRef) vals[i]); - } - } - - if (array) { - hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (hidman != NULL) { - retval = ConfigHIDManager(array); - } - CFRelease(array); - } - - return retval; -} - - -void __attribute__((constructor)) SDL_SYS_JoystickInit(void) -{ - if (!CreateHIDManager()) { - fprintf(stderr, "Joystick: Couldn't initialize HID Manager"); - } -} diff --git a/Cocoa/main.m b/Cocoa/main.m index 8a6799b4..3eb7a378 100644 --- a/Cocoa/main.m +++ b/Cocoa/main.m @@ -1,5 +1,6 @@ #import -int main(int argc, const char * argv[]) { +int main(int argc, const char * argv[]) +{ return NSApplicationMain(argc, argv); } diff --git a/Core/debugger.c b/Core/debugger.c index df480f34..ab0097f9 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1448,7 +1448,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } if (cartridge->has_rumble) { - GB_log(gb, "Cart contains a rumble pak\n"); + GB_log(gb, "Cart contains a Rumble Pak\n"); } if (cartridge->has_rtc) { diff --git a/Core/display.c b/Core/display.c index 5c1935c6..aa2d610c 100644 --- a/Core/display.c +++ b/Core/display.c @@ -151,6 +151,13 @@ static void display_vblank(GB_gameboy_t *gb) } } + if (gb->rumble_callback) { + if (gb->rumble_on_cycles + gb->rumble_off_cycles) { + gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); + gb->rumble_on_cycles = gb->rumble_off_cycles = 0; + } + } + gb->vblank_callback(gb); GB_timing_sync(gb); } diff --git a/Core/gb.h b/Core/gb.h index dbd9e165..a66beedb 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -241,7 +241,7 @@ typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_a typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); -typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); +typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude); typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); @@ -614,6 +614,8 @@ struct GB_gameboy_internal_s { bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units double clock_multiplier; + uint32_t rumble_on_cycles; + uint32_t rumble_off_cycles; ); }; diff --git a/Core/memory.c b/Core/memory.c index c481ad2b..99943438 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -478,9 +478,6 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->cartridge_type->has_rumble) { if (!!(value & 8) != gb->rumble_state) { gb->rumble_state = !gb->rumble_state; - if (gb->rumble_callback) { - gb->rumble_callback(gb, gb->rumble_state); - } } value &= 7; } diff --git a/Core/save_state.c b/Core/save_state.c index 8ef99aed..ce844b9d 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -252,10 +252,6 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) errno = 0; - if (gb->cartridge_type->has_rumble && gb->rumble_callback) { - gb->rumble_callback(gb, gb->rumble_state); - } - for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, false, i * 2); GB_palette_changed(gb, true, i * 2); @@ -357,10 +353,6 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le memcpy(gb, &save, sizeof(save)); - if (gb->cartridge_type->has_rumble && gb->rumble_callback) { - gb->rumble_callback(gb, gb->rumble_state); - } - for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, false, i * 2); GB_palette_changed(gb, true, i * 2); diff --git a/Core/timing.c b/Core/timing.c index 283558c0..e5920802 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -232,6 +232,14 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_input_ir_change += cycles; gb->cycles_since_last_sync += cycles; gb->cycles_since_run += cycles; + + if (gb->rumble_state) { + gb->rumble_on_cycles++; + } + else { + gb->rumble_off_cycles++; + } + if (!gb->stopped) { // TODO: Verify what happens in STOP mode GB_dma_run(gb); GB_hdma_run(gb); diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc new file mode 100644 index 00000000..cdbdce35 --- /dev/null +++ b/JoyKit/ControllerConfiguration.inc @@ -0,0 +1,369 @@ +#define BUTTON(x) @(JOYButtonUsageGeneric0 + (x)) +#define AXIS(x) @(JOYAxisUsageGeneric0 + (x)) +#define AXES2D(x) @(JOYAxes2DUsageGeneric0 + (x)) + +hacksByManufacturer = @{ + @(0x045E): @{ // Microsoft + /* Generally untested, but Microsoft goes by the book when it comes to HID report descriptors, so + it should work out of the box. The hack is only here for automatic mapping */ + + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(2), + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(3), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageA), + BUTTON(2): @(JOYButtonUsageB), + BUTTON(3): @(JOYButtonUsageX), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(7): @(JOYButtonUsageLStick), + BUTTON(8): @(JOYButtonUsageRStick), + BUTTON(9): @(JOYButtonUsageStart), + BUTTON(10): @(JOYButtonUsageSelect), + BUTTON(11): @(JOYButtonUsageHome), + }, + + JOYAxisUsageMapping: @{ + AXIS(3): @(JOYAxisUsageL1), + AXIS(6): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + }, + + @(0x054C): @{ // Sony + /* Generally untested, but should work */ + + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(3), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageY), + BUTTON(2): @(JOYButtonUsageB), + BUTTON(3): @(JOYButtonUsageA), + BUTTON(4): @(JOYButtonUsageX), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(7): @(JOYButtonUsageL2), + BUTTON(8): @(JOYButtonUsageR2), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageHome), + BUTTON(14): @(JOYButtonUsageMisc), + }, + + JOYAxisUsageMapping: @{ + AXIS(4): @(JOYAxisUsageL1), + AXIS(5): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + } +}; + +hacksByName = @{ + @"WUP-028": @{ // Nintendo GameCube Controller Adapter + JOYReportIDFilters: @[@[@1], @[@2], @[@3], @[@4]], + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageA), + BUTTON(2): @(JOYButtonUsageB), + BUTTON(3): @(JOYButtonUsageX), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageStart), + BUTTON(6): @(JOYButtonUsageZ), + BUTTON(7): @(JOYButtonUsageR1), + BUTTON(8): @(JOYButtonUsageL1), + }, + + JOYAxisUsageMapping: @{ + AXIS(3): @(JOYAxisUsageL1), + AXIS(6): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(2), + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(3), + }, + + JOYRumbleUsage: @1, + JOYRumbleUsagePage: @0xFF00, + + JOYConnectedUsage: @2, + JOYConnectedUsagePage: @0xFF00, + + JOYSubElementStructs: @{ + + // Rumble + @(1364): @[ + @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(2), @"size":@1, @"offset":@1, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(3), @"size":@1, @"offset":@2, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(4), @"size":@1, @"offset":@3, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + ], + + @(11): @[ + + // Player 1 + + @{@"reportID": @(1), @"size":@1, @"offset":@4, @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + @{@"reportID": @(1), @"size":@1, @"offset":@8, @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(1), @"size":@1, @"offset":@9, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(1), @"size":@1, @"offset":@10, @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(1), @"size":@1, @"offset":@11, @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@14, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(1), @"size":@1, @"offset":@15, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(1), @"size":@1, @"offset":@16, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(1), @"size":@1, @"offset":@17, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(1), @"size":@1, @"offset":@18, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(1), @"size":@1, @"offset":@19, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(1), @"size":@8, @"offset":@24, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@32, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(1), @"size":@8, @"offset":@40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@48, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(1), @"size":@8, @"offset":@56, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@64, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + // Player 2 + + @{@"reportID": @(2), @"size":@1, @"offset":@(4 + 72), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + + @{@"reportID": @(2), @"size":@1, @"offset":@(8 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(2), @"size":@1, @"offset":@(9 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(2), @"size":@1, @"offset":@(10 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(2), @"size":@1, @"offset":@(11 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(2), @"size":@1, @"offset":@(12 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(2), @"size":@1, @"offset":@(13 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(2), @"size":@1, @"offset":@(14 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(2), @"size":@1, @"offset":@(15 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(2), @"size":@1, @"offset":@(16 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(2), @"size":@1, @"offset":@(17 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(2), @"size":@1, @"offset":@(18 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(2), @"size":@1, @"offset":@(19 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(2), @"size":@8, @"offset":@(24 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(2), @"size":@8, @"offset":@(32 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(2), @"size":@8, @"offset":@(40 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(2), @"size":@8, @"offset":@(48 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(2), @"size":@8, @"offset":@(56 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(2), @"size":@8, @"offset":@(64 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + // Player 3 + + @{@"reportID": @(3), @"size":@1, @"offset":@(4 + 144), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + @{@"reportID": @(3), @"size":@1, @"offset":@(8 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(3), @"size":@1, @"offset":@(9 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(3), @"size":@1, @"offset":@(10 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(3), @"size":@1, @"offset":@(11 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(3), @"size":@1, @"offset":@(12 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(3), @"size":@1, @"offset":@(13 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(3), @"size":@1, @"offset":@(14 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(3), @"size":@1, @"offset":@(15 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(3), @"size":@1, @"offset":@(16 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(3), @"size":@1, @"offset":@(17 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(3), @"size":@1, @"offset":@(18 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(3), @"size":@1, @"offset":@(19 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(3), @"size":@8, @"offset":@(24 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(3), @"size":@8, @"offset":@(32 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(3), @"size":@8, @"offset":@(40 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(3), @"size":@8, @"offset":@(48 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(3), @"size":@8, @"offset":@(56 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(3), @"size":@8, @"offset":@(64 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + // Player 4 + + @{@"reportID": @(4), @"size":@1, @"offset":@(4 + 216), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + @{@"reportID": @(4), @"size":@1, @"offset":@(8 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(4), @"size":@1, @"offset":@(9 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(4), @"size":@1, @"offset":@(10 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(4), @"size":@1, @"offset":@(11 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(4), @"size":@1, @"offset":@(12 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(4), @"size":@1, @"offset":@(13 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(4), @"size":@1, @"offset":@(14 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(4), @"size":@1, @"offset":@(15 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(4), @"size":@1, @"offset":@(16 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(4), @"size":@1, @"offset":@(17 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(4), @"size":@1, @"offset":@(18 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(4), @"size":@1, @"offset":@(19 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(4), @"size":@8, @"offset":@(24 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(4), @"size":@8, @"offset":@(32 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(4), @"size":@8, @"offset":@(40 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(4), @"size":@8, @"offset":@(48 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(4), @"size":@8, @"offset":@(56 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(4), @"size":@8, @"offset":@(64 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + + ]}, + }, + + @"GameCube Controller Adapter": @{ // GameCube Controller PC Adapter + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(3), + @(kHIDUsage_GD_Rz): @(1), + }, + JOYReportIDFilters: @[@[@1], @[@2], @[@3], @[@4]], + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageX), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageB), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(8): @(JOYButtonUsageZ), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(13): @(JOYButtonUsageDPadUp), + BUTTON(14): @(JOYButtonUsageDPadRight), + BUTTON(15): @(JOYButtonUsageDPadDown), + BUTTON(16): @(JOYButtonUsageDPadLeft), + }, + + JOYAxisUsageMapping: @{ + AXIS(4): @(JOYAxisUsageL1), + AXIS(5): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(3): @(JOYAxes2DUsageRightStick), + }, + + JOYRumbleUsage: @1, + JOYRumbleUsagePage: @0xFF00, + JOYRumbleMin: @0, + JOYRumbleMax: @255, + JOYSwapZRz: @YES, + }, + + @"Twin USB Joystick": @{ // DualShock PC Adapter + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(kHIDUsage_GD_Rz): @(1), + }, + JOYReportIDFilters: @[@[@1], @[@2]], + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageX), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageB), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageL2), + BUTTON(6): @(JOYButtonUsageR2), + BUTTON(7): @(JOYButtonUsageL1), + BUTTON(8): @(JOYButtonUsageR1), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageDPadUp), + BUTTON(14): @(JOYButtonUsageDPadRight), + BUTTON(15): @(JOYButtonUsageDPadDown), + BUTTON(16): @(JOYButtonUsageDPadLeft), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(6): @(JOYAxes2DUsageRightStick), + }, + + JOYSwapZRz: @YES, + }, + + @"Pro Controller": @{ // Switch Pro Controller + JOYIsSwitch: @YES, + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(0), + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageB), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageY), + BUTTON(4): @(JOYButtonUsageX), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(7): @(JOYButtonUsageL2), + BUTTON(8): @(JOYButtonUsageR2), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageHome), + BUTTON(14): @(JOYButtonUsageMisc), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + }, +}; diff --git a/JoyKit/JOYAxes2D.h b/JoyKit/JOYAxes2D.h new file mode 100644 index 00000000..b6f6d152 --- /dev/null +++ b/JoyKit/JOYAxes2D.h @@ -0,0 +1,24 @@ +#import + +typedef enum { + JOYAxes2DUsageNone, + JOYAxes2DUsageLeftStick, + JOYAxes2DUsageRightStick, + JOYAxes2DUsageMiddleStick, + JOYAxes2DUsagePointer, + JOYAxes2DUsageNonGenericMax, + + JOYAxes2DUsageGeneric0 = 0x10000, +} JOYAxes2DUsage; + +@interface JOYAxes2D : NSObject +- (NSString *)usageString; ++ (NSString *)usageToString: (JOYAxes2DUsage) usage; +- (uint64_t)uniqueID; +- (double)distance; +- (double)angle; +- (NSPoint)value; +@property JOYAxes2DUsage usage; +@end + + diff --git a/JoyKit/JOYAxes2D.m b/JoyKit/JOYAxes2D.m new file mode 100644 index 00000000..28ce16cb --- /dev/null +++ b/JoyKit/JOYAxes2D.m @@ -0,0 +1,168 @@ +#import "JOYAxes2D.h" +#import "JOYElement.h" + +@implementation JOYAxes2D +{ + JOYElement *_element1, *_element2; + double _state1, _state2; + int32_t initialX, initialY; + int32_t minX, minY; + int32_t maxX, maxY; + +} + ++ (NSString *)usageToString: (JOYAxes2DUsage) usage +{ + if (usage < JOYAxes2DUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"Left Stick", + @"Right Stick", + @"Middle Stick", + @"Pointer", + }[usage]; + } + if (usage >= JOYAxes2DUsageGeneric0) { + return [NSString stringWithFormat:@"Generic 2D Analog Control %d", usage - JOYAxes2DUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage 2D Axes %d", usage]; +} + +- (NSString *)usageString +{ + return [self.class usageToString:_usage]; +} + +- (uint64_t)uniqueID +{ + return _element1.uniqueID; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %.2f%%, %.2f degrees>", self.className, self, self.usageString, self.uniqueID, self.distance * 100, self.angle]; +} + +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 +{ + self = [super init]; + if (!self) return self; + + _element1 = element1; + _element2 = element2; + + + if (element1.usagePage == kHIDPage_GenericDesktop) { + uint16_t usage = element1.usage; + _usage = JOYAxes2DUsageGeneric0 + usage - kHIDUsage_GD_X + 1; + } + initialX = [_element1 value]; + initialY = [_element2 value]; + minX = element1.max; + minY = element2.max; + maxX = element1.min; + maxY = element2.min; + + return self; +} + +- (NSPoint)value +{ + return NSMakePoint(_state1, _state2); +} + +-(int32_t) effectiveMinX +{ + int32_t rawMin = _element1.min; + int32_t rawMax = _element1.max; + if (initialX == 0) return rawMin; + if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return minX; + if ((initialX - rawMin) < (rawMax - initialX)) return rawMin; + return initialX - (rawMax - initialX); +} + +-(int32_t) effectiveMinY +{ + int32_t rawMin = _element2.min; + int32_t rawMax = _element2.max; + if (initialY == 0) return rawMin; + if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return minY; + if ((initialY - rawMin) < (rawMax - initialY)) return rawMin; + return initialY - (rawMax - initialY); +} + +-(int32_t) effectiveMaxX +{ + int32_t rawMin = _element1.min; + int32_t rawMax = _element1.max; + if (initialX == 0) return rawMax; + if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return maxX; + if ((initialX - rawMin) > (rawMax - initialX)) return rawMax; + return initialX + (initialX - rawMin); +} + +-(int32_t) effectiveMaxY +{ + int32_t rawMin = _element2.min; + int32_t rawMax = _element2.max; + if (initialY == 0) return rawMax; + if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return maxY; + if ((initialY - rawMin) > (rawMax - initialY)) return rawMax; + return initialY + (initialY - rawMin); +} + +- (bool)updateState +{ + int32_t x = [_element1 value]; + int32_t y = [_element2 value]; + if (x == 0 && y == 0) return false; + + if (initialX == 0 && initialY == 0) { + initialX = x; + initialY = y; + } + + double old1 = _state1, old2 = _state2; + { + double min = [self effectiveMinX]; + double max = [self effectiveMaxX]; + if (min == max) return false; + int32_t value = x; + + if (initialX != 0) { + minX = MIN(value, minX); + maxX = MAX(value, maxX); + } + + _state1 = (value - min) / (max - min) * 2 - 1; + } + + { + double min = [self effectiveMinY]; + double max = [self effectiveMaxY]; + if (min == max) return false; + int32_t value = y; + + if (initialY != 0) { + minY = MIN(value, minY); + maxY = MAX(value, maxY); + } + + _state2 = (value - min) / (max - min) * 2 - 1; + } + + return old1 != _state1 || old2 != _state2; +} + +- (double)distance +{ + return MIN(sqrt(_state1 * _state1 + _state2 * _state2), 1.0); +} + +- (double)angle { + double temp = atan2(_state2, _state1) * 180 / M_PI; + if (temp >= 0) return temp; + return temp + 360; +} +@end diff --git a/JoyKit/JOYAxis.h b/JoyKit/JOYAxis.h new file mode 100644 index 00000000..5a4c1669 --- /dev/null +++ b/JoyKit/JOYAxis.h @@ -0,0 +1,29 @@ +#import + +typedef enum { + JOYAxisUsageNone, + JOYAxisUsageL1, + JOYAxisUsageL2, + JOYAxisUsageL3, + JOYAxisUsageR1, + JOYAxisUsageR2, + JOYAxisUsageR3, + JOYAxisUsageWheel, + JOYAxisUsageRudder, + JOYAxisUsageThrottle, + JOYAxisUsageAccelerator, + JOYAxisUsageBrake, + JOYAxisUsageNonGenericMax, + + JOYAxisUsageGeneric0 = 0x10000, +} JOYAxisUsage; + +@interface JOYAxis : NSObject +- (NSString *)usageString; ++ (NSString *)usageToString: (JOYAxisUsage) usage; +- (uint64_t)uniqueID; +- (double)value; +@property JOYAxisUsage usage; +@end + + diff --git a/JoyKit/JOYAxis.m b/JoyKit/JOYAxis.m new file mode 100644 index 00000000..169eaee8 --- /dev/null +++ b/JoyKit/JOYAxis.m @@ -0,0 +1,90 @@ +#import "JOYAxis.h" +#import "JOYElement.h" + +@implementation JOYAxis +{ + JOYElement *_element; + double _state; + double _min; +} + ++ (NSString *)usageToString: (JOYAxisUsage) usage +{ + if (usage < JOYAxisUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"Analog L1", + @"Analog L2", + @"Analog L3", + @"Analog R1", + @"Analog R2", + @"Analog R3", + @"Wheel", + @"Rudder", + @"Throttle", + @"Accelerator", + @"Brake", + }[usage]; + } + if (usage >= JOYAxisUsageGeneric0) { + return [NSString stringWithFormat:@"Generic Analog Control %d", usage - JOYAxisUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage Axis %d", usage]; +} + +- (NSString *)usageString +{ + return [self.class usageToString:_usage]; +} + +- (uint64_t)uniqueID +{ + return _element.uniqueID; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %f%%>", self.className, self, self.usageString, self.uniqueID, _state * 100]; +} + +- (instancetype)initWithElement:(JOYElement *)element +{ + self = [super init]; + if (!self) return self; + + _element = element; + + + if (element.usagePage == kHIDPage_GenericDesktop) { + uint16_t usage = element.usage; + _usage = JOYAxisUsageGeneric0 + usage - kHIDUsage_GD_X + 1; + } + + _min = 1.0; + + return self; +} + +- (double) value +{ + return _state; +} + +- (bool)updateState +{ + double min = _element.min; + double max = _element.max; + if (min == max) return false; + double old = _state; + double unnormalized = ([_element value] - min) / (max - min); + if (unnormalized < _min) { + _min = unnormalized; + } + if (_min != 1) { + _state = (unnormalized - _min) / (1 - _min); + } + return old != _state; +} + +@end diff --git a/JoyKit/JOYButton.h b/JoyKit/JOYButton.h new file mode 100644 index 00000000..f732c8e6 --- /dev/null +++ b/JoyKit/JOYButton.h @@ -0,0 +1,42 @@ +#import + + + +typedef enum { + JOYButtonUsageNone, + JOYButtonUsageA, + JOYButtonUsageB, + JOYButtonUsageC, + JOYButtonUsageX, + JOYButtonUsageY, + JOYButtonUsageZ, + JOYButtonUsageStart, + JOYButtonUsageSelect, + JOYButtonUsageHome, + JOYButtonUsageMisc, + JOYButtonUsageLStick, + JOYButtonUsageRStick, + JOYButtonUsageL1, + JOYButtonUsageL2, + JOYButtonUsageL3, + JOYButtonUsageR1, + JOYButtonUsageR2, + JOYButtonUsageR3, + JOYButtonUsageDPadLeft, + JOYButtonUsageDPadRight, + JOYButtonUsageDPadUp, + JOYButtonUsageDPadDown, + JOYButtonUsageNonGenericMax, + + JOYButtonUsageGeneric0 = 0x10000, +} JOYButtonUsage; + +@interface JOYButton : NSObject +- (NSString *)usageString; ++ (NSString *)usageToString: (JOYButtonUsage) usage; +- (uint64_t)uniqueID; +- (bool) isPressed; +@property JOYButtonUsage usage; +@end + + diff --git a/JoyKit/JOYButton.m b/JoyKit/JOYButton.m new file mode 100644 index 00000000..3e6026d1 --- /dev/null +++ b/JoyKit/JOYButton.m @@ -0,0 +1,102 @@ +#import "JOYButton.h" +#import "JOYElement.h" + +@implementation JOYButton +{ + JOYElement *_element; + bool _state; +} + ++ (NSString *)usageToString: (JOYButtonUsage) usage +{ + if (usage < JOYButtonUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"A", + @"B", + @"C", + @"X", + @"Y", + @"Z", + @"Start", + @"Select", + @"Home", + @"Misc", + @"Left Stick", + @"Right Stick", + @"L1", + @"L2", + @"L3", + @"R1", + @"R2", + @"R3", + @"D-Pad Left", + @"D-Pad Right", + @"D-Pad Up", + @"D-Pad Down", + }[usage]; + } + if (usage >= JOYButtonUsageGeneric0) { + return [NSString stringWithFormat:@"Generic Button %d", usage - JOYButtonUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage Button %d", usage]; +} + +- (NSString *)usageString +{ + return [self.class usageToString:_usage]; +} + +- (uint64_t)uniqueID +{ + return _element.uniqueID; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %s>", self.className, self, self.usageString, self.uniqueID, _state? "Presssed" : "Released"]; +} + +- (instancetype)initWithElement:(JOYElement *)element +{ + self = [super init]; + if (!self) return self; + + _element = element; + + if (element.usagePage == kHIDPage_Button) { + uint16_t usage = element.usage; + _usage = JOYButtonUsageGeneric0 + usage; + } + else if (element.usagePage == kHIDPage_GenericDesktop) { + switch (element.usage) { + case kHIDUsage_GD_DPadUp: _usage = JOYButtonUsageDPadUp; break; + case kHIDUsage_GD_DPadDown: _usage = JOYButtonUsageDPadDown; break; + case kHIDUsage_GD_DPadRight: _usage = JOYButtonUsageDPadRight; break; + case kHIDUsage_GD_DPadLeft: _usage = JOYButtonUsageDPadLeft; break; + case kHIDUsage_GD_Start: _usage = JOYButtonUsageStart; break; + case kHIDUsage_GD_Select: _usage = JOYButtonUsageSelect; break; + case kHIDUsage_GD_SystemMainMenu: _usage = JOYButtonUsageHome; break; + } + } + + return self; +} + +- (bool) isPressed +{ + return _state; +} + +- (bool)updateState +{ + bool state = [_element value]; + if (_state != state) { + _state = state; + return true; + } + return false; +} + +@end diff --git a/JoyKit/JOYController.h b/JoyKit/JOYController.h new file mode 100644 index 00000000..9ed7cf7b --- /dev/null +++ b/JoyKit/JOYController.h @@ -0,0 +1,41 @@ +#import +#import "JOYButton.h" +#import "JOYAxis.h" +#import "JOYAxes2D.h" +#import "JOYHat.h" + +static NSString const *JOYAxesEmulateButtonsKey = @"JOYAxesEmulateButtons"; +static NSString const *JOYAxes2DEmulateButtonsKey = @"JOYAxes2DEmulateButtons"; +static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; + +@class JOYController; + +@protocol JOYListener + +@optional +-(void) controllerConnected:(JOYController *)controller; +-(void) controllerDisconnected:(JOYController *)controller; +-(void) controller:(JOYController *)controller buttonChangedState:(JOYButton *)button; +-(void) controller:(JOYController *)controller movedAxis:(JOYAxis *)axis; +-(void) controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes; +-(void) controller:(JOYController *)controller movedHat:(JOYHat *)hat; + +@end + +@interface JOYController : NSObject ++ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options; ++ (NSArray *) allControllers; ++ (void) registerListener:(id)listener; ++ (void) unregisterListener:(id)listener; +- (NSString *)deviceName; +- (NSString *)uniqueID; +- (NSArray *) buttons; +- (NSArray *) axes; +- (NSArray *) axes2D; +- (NSArray *) hats; +- (void)setRumbleAmplitude:(double)amp; +- (void)setPlayerLEDs:(uint8_t)mask; +@property (readonly, getter=isConnected) bool connected; +@end + + diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m new file mode 100644 index 00000000..d5e1acbd --- /dev/null +++ b/JoyKit/JOYController.m @@ -0,0 +1,760 @@ +#import "JOYController.h" +#import "JOYMultiplayerController.h" +#import "JOYElement.h" +#import "JOYSubElement.h" +#import "JOYEmulatedButton.h" +#include + +static NSString const *JOYAxisGroups = @"JOYAxisGroups"; +static NSString const *JOYReportIDFilters = @"JOYReportIDFilters"; +static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping"; +static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping"; +static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping"; +static NSString const *JOYSubElementStructs = @"JOYSubElementStructs"; +static NSString const *JOYIsSwitch = @"JOYIsSwitch"; +static NSString const *JOYRumbleUsage = @"JOYRumbleUsage"; +static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage"; +static NSString const *JOYConnectedUsage = @"JOYConnectedUsage"; +static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage"; +static NSString const *JOYRumbleMin = @"JOYRumbleMin"; +static NSString const *JOYRumbleMax = @"JOYRumbleMax"; +static NSString const *JOYSwapZRz = @"JOYSwapZRz"; + + +static NSMutableDictionary *controllers; // Physical controllers +static NSMutableArray *exposedControllers; // Logical controllers + +static NSDictionary *hacksByName = nil; +static NSDictionary *hacksByManufacturer = nil; + +static NSMutableSet> *listeners = nil; + +static bool axesEmulateButtons = false; +static bool axes2DEmulateButtons = false; +static bool hatsEmulateButtons = false; + +@interface JOYController () ++ (void)controllerAdded:(IOHIDDeviceRef) device; ++ (void)controllerRemoved:(IOHIDDeviceRef) device; +- (void)elementChanged:(IOHIDElementRef) element; +@end + +@interface JOYButton () +- (instancetype)initWithElement:(JOYElement *)element; +- (bool)updateState; +@end + +@interface JOYAxis () +- (instancetype)initWithElement:(JOYElement *)element; +- (bool)updateState; +@end + +@interface JOYHat () +- (instancetype)initWithElement:(JOYElement *)element; +- (bool)updateState; +@end + +@interface JOYAxes2D () +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2; +- (bool)updateState; +@end + +static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage) +{ + return @{ + @kIOHIDDeviceUsagePageKey: @(page), + @kIOHIDDeviceUsageKey: @(usage), + }; +} + +static void HIDDeviceAdded(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) +{ + [JOYController controllerAdded:device]; +} + +static void HIDDeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) +{ + [JOYController controllerRemoved:device]; +} + +static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef value) +{ + [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)]; +} + + +typedef struct __attribute__((packed)) { + uint8_t reportID; + uint8_t sequence; + uint8_t rumbleData[8]; + uint8_t command; + uint8_t commandData[26]; +} JOYSwitchPacket; + +@implementation JOYController +{ + IOHIDDeviceRef _device; + NSMutableDictionary *_buttons; + NSMutableDictionary *_axes; + NSMutableDictionary *_axes2D; + NSMutableDictionary *_hats; + NSMutableDictionary *> *_multiElements; + + // Button emulation + NSMutableDictionary *_axisEmulatedButtons; + NSMutableDictionary *> *_axes2DEmulatedButtons; + NSMutableDictionary *> *_hatEmulatedButtons; + + JOYElement *_rumbleElement; + JOYElement *_connectedElement; + NSMutableDictionary *_iokitToJOY; + NSString *_serialSuffix; + bool _isSwitch; // Does thie controller use the Switch protocol? + JOYSwitchPacket _lastSwitchPacket; + NSCondition *_rumblePWMThreadLock; + volatile double _rumblePWMRatio; + bool _physicallyConnected; + bool _logicallyConnected; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device +{ + return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil]; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix +{ + self = [super init]; + if (!self) return self; + + _physicallyConnected = true; + _logicallyConnected = true; + _device = device; + _serialSuffix = suffix; + + IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self); + IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone)); + _buttons = [NSMutableDictionary dictionary]; + _axes = [NSMutableDictionary dictionary]; + _axes2D = [NSMutableDictionary dictionary]; + _hats = [NSMutableDictionary dictionary]; + _axisEmulatedButtons = [NSMutableDictionary dictionary]; + _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; + _hatEmulatedButtons = [NSMutableDictionary dictionary]; + _multiElements = [NSMutableDictionary dictionary]; + _iokitToJOY = [NSMutableDictionary dictionary]; + _rumblePWMThreadLock = [[NSCondition alloc] init]; + + + //NSMutableArray *axes3d = [NSMutableArray array]; + + NSDictionary *axisGroups = @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(kHIDUsage_GD_Rz): @(1), + }; + NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + NSDictionary *hacks = hacksByName[name]; + if (!hacks) { + hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))]; + } + axisGroups = hacks[JOYAxisGroups] ?: axisGroups; + _isSwitch = [hacks[JOYIsSwitch] boolValue]; + uint16_t rumbleUsagePage = (uint16_t)[hacks[JOYRumbleUsagePage] unsignedIntValue]; + uint16_t rumbleUsage = (uint16_t)[hacks[JOYRumbleUsage] unsignedIntValue]; + uint16_t connectedUsagePage = (uint16_t)[hacks[JOYConnectedUsagePage] unsignedIntValue]; + uint16_t connectedUsage = (uint16_t)[hacks[JOYConnectedUsage] unsignedIntValue]; + + JOYElement *previousAxisElement = nil; + id previous = nil; + for (id _element in array) { + if (_element == previous) continue; // Some elements are reported twice for some reason + previous = _element; + NSArray *elements = nil; + JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; + + NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)]; + + bool isOutput = false; + if (subElementDefs && element.uniqueID != element.parentID) { + elements = [NSMutableArray array]; + for (NSDictionary *virtualInput in subElementDefs) { + if (filter && virtualInput[@"reportID"] && ![filter containsObject:virtualInput[@"reportID"]]) continue; + [(NSMutableArray *)elements addObject:[[JOYSubElement alloc] initWithRealElement:element + size:virtualInput[@"size"].unsignedLongValue + offset:virtualInput[@"offset"].unsignedLongValue + usagePage:virtualInput[@"usagePage"].unsignedLongValue + usage:virtualInput[@"usage"].unsignedLongValue + min:virtualInput[@"min"].unsignedIntValue + max:virtualInput[@"max"].unsignedIntValue]]; + } + isOutput = IOHIDElementGetType((__bridge IOHIDElementRef)_element) == kIOHIDElementTypeOutput; + [_multiElements setObject:elements forKey:element]; + } + else { + if (filter && ![filter containsObject:@(element.reportID)]) continue; + + switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { + /* Handled */ + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + break; + case kIOHIDElementTypeOutput: + isOutput = true; + break; + /* Ignored */ + default: + case kIOHIDElementTypeInput_ScanCodes: + case kIOHIDElementTypeInput_NULL: + case kIOHIDElementTypeFeature: + case kIOHIDElementTypeCollection: + continue; + } + if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; + + elements = @[element]; + } + + _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element; + + for (JOYElement *element in elements) { + if (isOutput) { + if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { + if (hacks[JOYRumbleMin]) { + element.min = [hacks[JOYRumbleMin] unsignedIntValue]; + } + if (hacks[JOYRumbleMax]) { + element.max = [hacks[JOYRumbleMax] unsignedIntValue]; + } + _rumbleElement = element; + } + continue; + } + else { + if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { + _connectedElement = element; + _logicallyConnected = element.value != element.min; + continue; + } + } + + if (element.usagePage == kHIDPage_Button) { + button: { + JOYButton *button = [[JOYButton alloc] initWithElement: element]; + [_buttons setObject:button forKey:element]; + NSNumber *replacementUsage = hacks[JOYButtonUsageMapping][@(button.usage)]; + if (replacementUsage) { + button.usage = [replacementUsage unsignedIntValue]; + } + continue; + } + } + else if (element.usagePage == kHIDPage_GenericDesktop) { + switch (element.usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: { + + JOYElement *other = previousAxisElement; + previousAxisElement = element; + if (!other) goto single; + if (other.usage >= element.usage) goto single; + if (other.reportID != element.reportID) goto single; + if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; + if (other.parentID != element.parentID) goto single; + + JOYAxes2D *axes = nil; + if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [hacks[JOYSwapZRz] boolValue]) { + axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; + } + else { + axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; + } + NSNumber *replacementUsage = hacks[JOYAxes2DUsageMapping][@(axes.usage)]; + if (replacementUsage) { + axes.usage = [replacementUsage unsignedIntValue]; + } + + [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; + [_axes removeObjectForKey:other]; + previousAxisElement = nil; + _axes2D[other] = axes; + _axes2D[element] = axes; + + if (axes2DEmulateButtons) { + _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], + ]; + } + + /* + for (NSArray *group in axes2d) { + break; + IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; + IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; + if (IOHIDElementGetUsage(first) > element.usage) continue; + if (IOHIDElementGetUsage(second) > element.usage) continue; + if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; + if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; + if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; + + [axes2d removeObject:group]; + [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; + found = true; + break; + }*/ + break; + } + single: + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: { + JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; + [_axes setObject:axis forKey:element]; + + NSNumber *replacementUsage = hacks[JOYAxisUsageMapping][@(axis.usage)]; + if (replacementUsage) { + axis.usage = [replacementUsage unsignedIntValue]; + } + + if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID]; + } + + if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID]; + } + + break; + } + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + case kHIDUsage_GD_SystemMainMenu: + goto button; + + case kHIDUsage_GD_Hatswitch: { + JOYHat *hat = [[JOYHat alloc] initWithElement: element]; + [_hats setObject:hat forKey:element]; + if (hatsEmulateButtons) { + _hatEmulatedButtons[@(hat.uniqueID)] = @[ + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], + ]; + } + break; + } + } + } + } + } + + [exposedControllers addObject:self]; + if (_logicallyConnected) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerConnected:)]) { + [listener controllerConnected:self]; + } + } + } + + return self; +} + +- (NSString *)deviceName +{ + return IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey)); +} + +- (NSString *)uniqueID +{ + NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey)); + if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) { + serial = [NSString stringWithFormat:@"%04x%04x%08x", + [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDVendorIDKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]]; + } + if (_serialSuffix) { + return [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix]; + } + return serial; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@, %@>", self.className, self, self.deviceName, self.uniqueID]; +} + +- (NSArray *)buttons +{ + NSMutableArray *ret = [[_buttons allValues] mutableCopy]; + [ret addObjectsFromArray:_axisEmulatedButtons.allValues]; + for (NSArray *array in _axes2DEmulatedButtons.allValues) { + [ret addObjectsFromArray:array]; + } + for (NSArray *array in _hatEmulatedButtons.allValues) { + [ret addObjectsFromArray:array]; + } + return ret; +} + +- (NSArray *)axes +{ + return [_axes allValues]; +} + +- (NSArray *)axes2D +{ + return [[NSSet setWithArray:[_axes2D allValues]] allObjects]; +} + +- (NSArray *)hats +{ + return [_hats allValues]; +} + +- (void)elementChanged:(IOHIDElementRef)element +{ + JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))]; + if (_element) { + [self _elementChanged:_element]; + } + else { + //NSLog(@"Unhandled usage %x (Cookie: %x, Usage: %x)", IOHIDElementGetUsage(element), IOHIDElementGetCookie(element), IOHIDElementGetUsage(element)); + } +} + +- (void)_elementChanged:(JOYElement *)element +{ + if (element == _connectedElement) { + bool old = self.connected; + _logicallyConnected = _connectedElement.value != _connectedElement.min; + if (!old && self.connected) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerConnected:)]) { + [listener controllerConnected:self]; + } + } + } + else if (old && !self.connected) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { + [listener controllerDisconnected:self]; + } + } + } + } + + { + NSArray *subElements = _multiElements[element]; + if (subElements) { + for (JOYElement *subElement in subElements) { + [self _elementChanged:subElement]; + } + return; + } + } + + if (!self.connected) return; + { + JOYButton *button = _buttons[element]; + if (button) { + if ([button updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + return; + } + } + + + { + JOYAxis *axis = _axes[element]; + if (axis) { + if ([axis updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedAxis:)]) { + [listener controller:self movedAxis:axis]; + } + } + JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID)]; + if ([button updateStateFromAxis:axis]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + } + return; + } + } + + { + JOYAxes2D *axes = _axes2D[element]; + if (axes) { + if ([axes updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) { + [listener controller:self movedAxes2D:axes]; + } + } + NSArray *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)]; + for (JOYEmulatedButton *button in buttons) { + if ([button updateStateFromAxes2D:axes]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + } + } + return; + } + } + + { + JOYHat *hat = _hats[element]; + if (hat) { + if ([hat updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedHat:)]) { + [listener controller:self movedHat:hat]; + } + } + + NSArray *buttons = _hatEmulatedButtons[@(hat.uniqueID)]; + for (JOYEmulatedButton *button in buttons) { + if ([button updateStateFromHat:hat]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + } + } + return; + } + } +} + +- (void)disconnected +{ + if (_logicallyConnected && [exposedControllers containsObject:self]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { + [listener controllerDisconnected:self]; + } + } + } + _physicallyConnected = false; + [self setRumbleAmplitude:0]; // Stop the rumble thread. + [exposedControllers removeObject:self]; + _device = nil; +} + +- (void)sendReport:(NSData *)report +{ + if (!report.length) return; + IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length); +} + +- (void)setPlayerLEDs:(uint8_t)mask +{ + mask &= 0xF; + if (_isSwitch) { + _lastSwitchPacket.reportID = 0x1; // Rumble and LEDs + _lastSwitchPacket.sequence++; + _lastSwitchPacket.sequence &= 0xF; + _lastSwitchPacket.command = 0x30; // LED + _lastSwitchPacket.commandData[0] = mask; + [self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + } +} + +- (void)pwmThread +{ + while (_rumblePWMRatio != 0) { + [_rumbleElement setValue:1]; + [NSThread sleepForTimeInterval:_rumblePWMRatio / 10]; + [_rumbleElement setValue:0]; + [NSThread sleepForTimeInterval:(1 - _rumblePWMRatio) / 10]; + } + [_rumblePWMThreadLock lock]; + [_rumblePWMThreadLock signal]; + [_rumblePWMThreadLock unlock]; +} + +- (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ +{ + double frequency = 144; // I have no idea what I'm doing. + + if (amp < 0) amp = 0; + if (amp > 1) amp = 1; + if (_isSwitch) { + if (amp == 0) { + amp = 1; + frequency = 0; + } + + uint8_t highAmp = amp * 0x64; + uint8_t lowAmp = amp * 0x32 + 0x40; + if (frequency < 0) frequency = 0; + if (frequency > 1252) frequency = 1252; + uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); + + uint16_t highFreq = (encodedFrequency - 0x60) * 4; + uint8_t lowFreq = encodedFrequency - 0x40; + + //if (frequency < 82 || frequency > 312) { + if (amp) { + highAmp = 0; + } + + if (frequency < 40 || frequency > 626) { + lowAmp = 0; + } + + _lastSwitchPacket.rumbleData[0] = _lastSwitchPacket.rumbleData[4] = highFreq & 0xFF; + _lastSwitchPacket.rumbleData[1] = _lastSwitchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); + _lastSwitchPacket.rumbleData[2] = _lastSwitchPacket.rumbleData[6] = lowFreq; + _lastSwitchPacket.rumbleData[3] = _lastSwitchPacket.rumbleData[7] = lowAmp; + + + _lastSwitchPacket.reportID = 0x10; // Rumble only + _lastSwitchPacket.sequence++; + _lastSwitchPacket.sequence &= 0xF; + _lastSwitchPacket.command = 0; // LED + [self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + } + else { + if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { + if (_rumblePWMRatio == 0) { // PWM thread not running, start it. + if (amp != 0) { + _rumblePWMRatio = amp; + [self performSelectorInBackground:@selector(pwmThread) withObject:nil]; + } + } + else { + if (amp == 0) { // Thread is running, signal it to stop + [_rumblePWMThreadLock lock]; + _rumblePWMRatio = 0; + [_rumblePWMThreadLock wait]; + [_rumblePWMThreadLock unlock]; + } + else { + _rumblePWMRatio = amp; + } + } + } + else { + [_rumbleElement setValue:amp * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; + } + } +} + +- (bool)isConnected +{ + return _logicallyConnected && _physicallyConnected; +} + ++ (void)controllerAdded:(IOHIDDeviceRef) device +{ + NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + NSDictionary *hacks = hacksByName[name]; + NSArray *filters = hacks[JOYReportIDFilters]; + if (filters) { + JOYController *controller = [[JOYMultiplayerController alloc] initWithDevice:device + reportIDFilters:filters]; + [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; + } + else { + [controllers setObject:[[JOYController alloc] initWithDevice:device] forKey:[NSValue valueWithPointer:device]]; + } +} + ++ (void)controllerRemoved:(IOHIDDeviceRef) device +{ + [[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected]; + [controllers removeObjectForKey:[NSValue valueWithPointer:device]]; +} + ++ (NSArray *)allControllers +{ + return exposedControllers; +} + ++ (void)load +{ +#include "ControllerConfiguration.inc" +} + ++(void)registerListener:(id)listener +{ + [listeners addObject:listener]; +} + ++(void)unregisterListener:(id)listener +{ + [listeners removeObject:listener]; +} + ++ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options +{ + axesEmulateButtons = [options[JOYAxesEmulateButtonsKey] boolValue]; + axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue]; + hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue]; + + controllers = [NSMutableDictionary dictionary]; + exposedControllers = [NSMutableArray array]; + NSArray *array = @[ + CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), + CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), + CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController), + @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_Game)}, + ]; + + listeners = [NSMutableSet set]; + static IOHIDManagerRef manager = nil; + if (manager) { + CFRelease(manager); // Stop the previous session + } + manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + + if (!manager) return; + if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone)) { + CFRelease(manager); + return; + } + + IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)array); + IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceAdded, NULL); + IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL); + IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode); +} +@end diff --git a/JoyKit/JOYElement.h b/JoyKit/JOYElement.h new file mode 100644 index 00000000..860c2476 --- /dev/null +++ b/JoyKit/JOYElement.h @@ -0,0 +1,20 @@ +#import +#include + +@interface JOYElement : NSObject +- (instancetype)initWithElement:(IOHIDElementRef)element; +- (int32_t)value; +- (NSData *)dataValue; +- (void)setValue:(uint32_t)value; +- (void)setDataValue:(NSData *)value; +@property (readonly) uint16_t usage; +@property (readonly) uint16_t usagePage; +@property (readonly) uint32_t uniqueID; +@property int32_t min; +@property int32_t max; +@property (readonly) int32_t reportID; +@property (readonly) int32_t parentID; + +@end + + diff --git a/JoyKit/JOYElement.m b/JoyKit/JOYElement.m new file mode 100644 index 00000000..56fcb369 --- /dev/null +++ b/JoyKit/JOYElement.m @@ -0,0 +1,96 @@ +#import "JOYElement.h" +#include + +@implementation JOYElement +{ + id _element; + IOHIDDeviceRef _device; + int32_t _min, _max; +} + +- (int32_t)min +{ + return MIN(_min, _max); +} + +- (int32_t)max +{ + return MAX(_max, _min); +} + +-(void)setMin:(int32_t)min +{ + _min = min; +} + +- (void)setMax:(int32_t)max +{ + _max = max; +} + +- (instancetype)initWithElement:(IOHIDElementRef)element +{ + if ((self = [super init])) { + _element = (__bridge id)element; + _usage = IOHIDElementGetUsage(element); + _usagePage = IOHIDElementGetUsagePage(element); + _uniqueID = (uint32_t)IOHIDElementGetCookie(element); + _min = (int32_t) IOHIDElementGetLogicalMin(element); + _max = (int32_t) IOHIDElementGetLogicalMax(element); + _reportID = IOHIDElementGetReportID(element); + IOHIDElementRef parent = IOHIDElementGetParent(element); + _parentID = parent? (uint32_t)IOHIDElementGetCookie(parent) : -1; + _device = IOHIDElementGetDevice(element); + } + return self; +} + +- (int32_t)value +{ + IOHIDValueRef value = NULL; + IOHIDDeviceGetValue(_device, (__bridge IOHIDElementRef)_element, &value); + if (!value) return 0; + CFRelease(CFRetain(value)); // For some reason, this is required to prevent leaks. + return (int32_t)IOHIDValueGetIntegerValue(value); +} + +- (NSData *)dataValue +{ + IOHIDValueRef value = NULL; + IOHIDDeviceGetValue(_device, (__bridge IOHIDElementRef)_element, &value); + if (!value) return 0; + CFRelease(CFRetain(value)); // For some reason, this is required to prevent leaks. + return [NSData dataWithBytes:IOHIDValueGetBytePtr(value) length:IOHIDValueGetLength(value)]; +} + +- (void)setValue:(uint32_t)value +{ + IOHIDValueRef ivalue = IOHIDValueCreateWithIntegerValue(NULL, (__bridge IOHIDElementRef)_element, 0, value); + IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); + CFRelease(ivalue); +} + +- (void)setDataValue:(NSData *)value +{ + IOHIDValueRef ivalue = IOHIDValueCreateWithBytes(NULL, (__bridge IOHIDElementRef)_element, 0, value.bytes, value.length); + IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); + CFRelease(ivalue); +} + +/* For use as a dictionary key */ + +- (NSUInteger)hash +{ + return self.uniqueID; +} + +- (BOOL)isEqual:(id)object +{ + return self->_element == object; +} + +- (id)copyWithZone:(nullable NSZone *)zone; +{ + return self; +} +@end diff --git a/JoyKit/JOYEmulatedButton.h b/JoyKit/JOYEmulatedButton.h new file mode 100644 index 00000000..491e0c73 --- /dev/null +++ b/JoyKit/JOYEmulatedButton.h @@ -0,0 +1,11 @@ +#import "JOYButton.h" +#import "JOYAxis.h" +#import "JOYAxes2D.h" +#import "JOYHat.h" + +@interface JOYEmulatedButton : JOYButton +- (instancetype)initWithUsage:(JOYButtonUsage)usage uniqueID:(uint64_t)uniqueID; +- (bool)updateStateFromAxis:(JOYAxis *)axis; +- (bool)updateStateFromAxes2D:(JOYAxes2D *)axes; +- (bool)updateStateFromHat:(JOYHat *)hat; +@end diff --git a/JoyKit/JOYEmulatedButton.m b/JoyKit/JOYEmulatedButton.m new file mode 100644 index 00000000..1ebed3ae --- /dev/null +++ b/JoyKit/JOYEmulatedButton.m @@ -0,0 +1,91 @@ +#import "JOYEmulatedButton.h" + +@interface JOYButton () +{ + @public bool _state; +} +@end + +@implementation JOYEmulatedButton +{ + uint64_t _uniqueID; +} + +- (instancetype)initWithUsage:(JOYButtonUsage)usage uniqueID:(uint64_t)uniqueID; +{ + self = [super init]; + self.usage = usage; + _uniqueID = uniqueID; + + return self; +} + +- (uint64_t)uniqueID +{ + return _uniqueID; +} + +- (bool)updateStateFromAxis:(JOYAxis *)axis +{ + bool old = _state; + _state = [axis value] > 0.5; + return _state != old; +} + +- (bool)updateStateFromAxes2D:(JOYAxes2D *)axes +{ + bool old = _state; + if (axes.distance < 0.5) { + _state = false; + } + else { + unsigned direction = ((unsigned)round(axes.angle / 360 * 8)) & 7; + switch (self.usage) { + case JOYButtonUsageDPadLeft: + _state = direction >= 3 && direction <= 5; + break; + case JOYButtonUsageDPadRight: + _state = direction <= 1 || direction == 7; + break; + case JOYButtonUsageDPadUp: + _state = direction >= 5; + break; + case JOYButtonUsageDPadDown: + _state = direction <= 3 && direction >= 1; + break; + default: + break; + } + } + return _state != old; +} + +- (bool)updateStateFromHat:(JOYHat *)hat +{ + bool old = _state; + if (!hat.pressed) { + _state = false; + } + else { + unsigned direction = ((unsigned)round(hat.angle / 360 * 8)) & 7; + switch (self.usage) { + case JOYButtonUsageDPadLeft: + _state = direction >= 3 && direction <= 5; + break; + case JOYButtonUsageDPadRight: + _state = direction <= 1 || direction == 7; + break; + case JOYButtonUsageDPadUp: + _state = direction >= 5; + break; + case JOYButtonUsageDPadDown: + _state = direction <= 3 && direction >= 1; + break; + default: + break; + } + } + return _state != old; +} + +@end diff --git a/JoyKit/JOYHat.h b/JoyKit/JOYHat.h new file mode 100644 index 00000000..05a58292 --- /dev/null +++ b/JoyKit/JOYHat.h @@ -0,0 +1,11 @@ +#import + +@interface JOYHat : NSObject +- (uint64_t)uniqueID; +- (double)angle; +- (unsigned)resolution; +@property (readonly, getter=isPressed) bool pressed; + +@end + + diff --git a/JoyKit/JOYHat.m b/JoyKit/JOYHat.m new file mode 100644 index 00000000..743e49c3 --- /dev/null +++ b/JoyKit/JOYHat.m @@ -0,0 +1,60 @@ +#import "JOYHat.h" +#import "JOYElement.h" + +@implementation JOYHat +{ + JOYElement *_element; + double _state; +} + +- (uint64_t)uniqueID +{ + return _element.uniqueID; +} + +- (NSString *)description +{ + if (self.isPressed) { + return [NSString stringWithFormat:@"<%@: %p (%llu); State: %f degrees>", self.className, self, self.uniqueID, self.angle]; + } + return [NSString stringWithFormat:@"<%@: %p (%llu); State: released>", self.className, self, self.uniqueID]; + +} + +- (instancetype)initWithElement:(JOYElement *)element +{ + self = [super init]; + if (!self) return self; + + _element = element; + + return self; +} + +- (bool)isPressed +{ + return _state >= 0 && _state < 360; +} + +- (double)angle +{ + if (self.isPressed) return fmod((_state + 270), 360); + return -1; +} + +- (unsigned)resolution +{ + return _element.max - _element.min + 1; +} + +- (bool)updateState +{ + unsigned state = ([_element value] - _element.min) * 360.0 / self.resolution; + if (_state != state) { + _state = state; + return true; + } + return false; +} + +@end diff --git a/JoyKit/JOYMultiplayerController.h b/JoyKit/JOYMultiplayerController.h new file mode 100644 index 00000000..24004f54 --- /dev/null +++ b/JoyKit/JOYMultiplayerController.h @@ -0,0 +1,8 @@ +#import "JOYController.h" +#include + +@interface JOYMultiplayerController : JOYController +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters; +@end + + diff --git a/JoyKit/JOYMultiplayerController.m b/JoyKit/JOYMultiplayerController.m new file mode 100644 index 00000000..0952c840 --- /dev/null +++ b/JoyKit/JOYMultiplayerController.m @@ -0,0 +1,44 @@ +#import "JOYMultiplayerController.h" + +@interface JOYController () +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix; +- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value; +- (void)disconnected; +@end + +@implementation JOYMultiplayerController +{ + NSMutableArray *_children; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters +{ + self = [super init]; + if (!self) return self; + + _children = [NSMutableArray array]; + + unsigned index = 1; + for (NSArray *filter in reportIDFilters) { + JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index]]; + [_children addObject:controller]; + index++; + } + return self; +} + +- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value +{ + for (JOYController *child in _children) { + [child elementChanged:element toValue:value]; + } +} + +- (void)disconnected +{ + for (JOYController *child in _children) { + [child disconnected]; + } +} + +@end diff --git a/JoyKit/JOYSubElement.h b/JoyKit/JOYSubElement.h new file mode 100644 index 00000000..a13b5c76 --- /dev/null +++ b/JoyKit/JOYSubElement.h @@ -0,0 +1,14 @@ +#import "JOYElement.h" + +@interface JOYSubElement : JOYElement +- (instancetype)initWithRealElement:(JOYElement *)element + size:(size_t) size // in bits + offset:(size_t) offset // in bits + usagePage:(uint16_t)usagePage + usage:(uint16_t)usage + min:(int32_t)min + max:(int32_t)max; + +@end + + diff --git a/JoyKit/JOYSubElement.m b/JoyKit/JOYSubElement.m new file mode 100644 index 00000000..55e289e4 --- /dev/null +++ b/JoyKit/JOYSubElement.m @@ -0,0 +1,99 @@ +#import "JOYSubElement.h" + +@interface JOYElement () +{ + @public uint16_t _usage; + @public uint16_t _usagePage; + @public uint32_t _uniqueID; + @public int32_t _min; + @public int32_t _max; + @public int32_t _reportID; + @public int32_t _parentID; +} +@end + +@implementation JOYSubElement +{ + JOYElement *_parent; + size_t _size; // in bits + size_t _offset; // in bits +} + +- (instancetype)initWithRealElement:(JOYElement *)element + size:(size_t) size // in bits + offset:(size_t) offset // in bits + usagePage:(uint16_t)usagePage + usage:(uint16_t)usage + min:(int32_t)min + max:(int32_t)max +{ + if ((self = [super init])) { + _parent = element; + _size = size; + _offset = offset; + _usage = usage; + _usagePage = usagePage; + _uniqueID = (uint32_t)((_parent.uniqueID << 16) | offset); + _min = min; + _max = max; + _reportID = _parent.reportID; + _parentID = _parent.parentID; + } + return self; +} + +- (int32_t)value +{ + NSData *parentValue = [_parent dataValue]; + if (!parentValue) return 0; + if (_size > 32) return 0; + if (_size + (_offset % 8) > 32) return 0; + size_t parentLength = parentValue.length; + if (_size > parentLength * 8) return 0; + if (_size + _offset >= parentLength * 8) return 0; + const uint8_t *bytes = parentValue.bytes; + + uint8_t temp[4] = {0,}; + memcpy(temp, bytes + _offset / 8, (_offset + _size - 1) / 8 - _offset / 8 + 1); + uint32_t ret = (*(uint32_t *)temp) >> (_offset % 8); + ret &= (1 << _size) - 1; + + if (_max < _min) { + return _max + _min - ret; + } + + return ret; +} + +- (void)setValue: (uint32_t) value +{ + NSMutableData *dataValue = [[_parent dataValue] mutableCopy]; + if (!dataValue) return; + if (_size > 32) return; + if (_size + (_offset % 8) > 32) return; + size_t parentLength = dataValue.length; + if (_size > parentLength * 8) return; + if (_size + _offset >= parentLength * 8) return; + uint8_t *bytes = dataValue.mutableBytes; + + uint8_t temp[4] = {0,}; + memcpy(temp, bytes + _offset / 8, (_offset + _size - 1) / 8 - _offset / 8 + 1); + (*(uint32_t *)temp) &= ~((1 << (_size - 1)) << (_offset % 8)); + (*(uint32_t *)temp) |= (value) << (_offset % 8); + memcpy(bytes + _offset / 8, temp, (_offset + _size - 1) / 8 - _offset / 8 + 1); + [_parent setDataValue:dataValue]; +} + +- (NSData *)dataValue +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (void)setDataValue:(NSData *)data +{ + [self doesNotRecognizeSelector:_cmd]; +} + + +@end diff --git a/JoyKit/JoyKit.h b/JoyKit/JoyKit.h new file mode 100644 index 00000000..d56b5051 --- /dev/null +++ b/JoyKit/JoyKit.h @@ -0,0 +1,6 @@ +#ifndef JoyKit_h +#define JoyKit_h + +#include "JOYController.h" + +#endif diff --git a/Makefile b/Makefile index 79b5e146..cc6cae43 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) TESTER_SOURCES := $(shell ls Tester/*.c) ifeq ($(PLATFORM),Darwin) -COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) +COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) $(shell ls JoyKit/*.m) QUICKLOOK_SOURCES := $(shell ls QuickLook/*.m) $(shell ls QuickLook/*.c) endif diff --git a/libretro/libretro.c b/libretro/libretro.c index e075b2cf..6c1fcb6d 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -141,12 +141,17 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0, input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)); - if (gb->rumble_state) - rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535); - else - rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0); } +static void rumble_callback(GB_gameboy_t *gb, double amplitude) +{ + if (gb == &gameboy[0]) { + rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535 * amplitude); + } + else if (gb == &gameboy[1]) { + rumble.set_rumble_state(1, RETRO_RUMBLE_STRONG, 65535 * amplitude); + } +} static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { @@ -370,6 +375,8 @@ static void init_for_current_model(unsigned id) GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); GB_apu_set_sample_callback(&gameboy[i], audio_callback); + GB_set_rumble_callback(&gameboy[i], rumble_callback); + /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); From 70542137f2143e626aa2253f97d360b7e8bd46c5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 29 Oct 2019 20:31:20 +0200 Subject: [PATCH 0933/1216] Fix #214 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 79b5e146..dfab2486 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ ifeq ($(PLATFORM),Darwin) SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 -LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit +LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations From 719a92d8a468770a85777134130ad0de6e4cd5bf Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 2 Nov 2019 23:31:23 -0400 Subject: [PATCH 0934/1216] SDL2: Fix fullscreen viewport bug --- SDL/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index ba2a5fb1..2993d361 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -123,7 +123,7 @@ static void handle_events(GB_gameboy_t *gb) } case SDL_WINDOWEVENT: { - if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { update_viewport(); } break; From 139ae8cc08b2d882f1e1d21a7a0aafdc3f0e3824 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 2 Nov 2019 23:50:29 -0400 Subject: [PATCH 0935/1216] SDL2: Write battery to disk when ROMs are hot-swapped --- SDL/main.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index ba2a5fb1..cc3fd204 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -406,14 +406,12 @@ static bool handle_pending_command(void) return false; } - case GB_SDL_RESET_COMMAND: - GB_save_battery(&gb, battery_save_path_ptr); - return true; - case GB_SDL_NO_COMMAND: return false; + case GB_SDL_RESET_COMMAND: case GB_SDL_NEW_FILE_COMMAND: + GB_save_battery(&gb, battery_save_path_ptr); return true; case GB_SDL_QUIT_COMMAND: From 2f4a10913b2df3de5279aec58a3bf758b58f3194 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 2 Nov 2019 23:43:25 -0400 Subject: [PATCH 0936/1216] SDL2: Hide mouse cursor when menu is not active --- SDL/gui.c | 1 + SDL/main.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/SDL/gui.c b/SDL/gui.c index 6cffeaf0..b34bb1a7 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -792,6 +792,7 @@ void connect_joypad(void) void run_gui(bool is_running) { + SDL_ShowCursor(SDL_ENABLE); connect_joypad(); /* Draw the background screen */ diff --git a/SDL/main.c b/SDL/main.c index ba2a5fb1..d96ee1bb 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -98,6 +98,7 @@ static void open_menu(void) SDL_PauseAudioDevice(device_id, 1); } run_gui(true); + SDL_ShowCursor(SDL_DISABLE); if (audio_playing) { SDL_ClearQueuedAudio(device_id); SDL_PauseAudioDevice(device_id, 0); @@ -425,6 +426,7 @@ static bool handle_pending_command(void) static void run(void) { + SDL_ShowCursor(SDL_DISABLE); GB_model_t model; pending_command = GB_SDL_NO_COMMAND; restart: From 143e1f88a8dc3fee4a40dbebd843cbb8cfbd5e84 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Nov 2019 22:02:33 +0200 Subject: [PATCH 0937/1216] =?UTF-8?q?There=E2=80=99s=20not=20reason=20it?= =?UTF-8?q?=20must=20be=20an=20integer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/apu.c | 2 +- Core/apu.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 3be92d67..9afa5c93 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -1008,7 +1008,7 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate) GB_apu_update_cycles_per_sample(gb); } -void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, unsigned cycles_per_sample) +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample) { if (cycles_per_sample == 0) { diff --git a/Core/apu.h b/Core/apu.h index ee6055ba..885e0ce3 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -148,7 +148,7 @@ typedef struct { } GB_apu_output_t; void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); -void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, unsigned cycles_per_sample); /* Cycles are in 8MHz units */ +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); #ifdef GB_INTERNAL From c8023618009cca1f7f0707e8a6d1188aad574ee5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Nov 2019 16:14:16 +0200 Subject: [PATCH 0938/1216] Whoops, this function was missing --- Core/memory.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index c481ad2b..7e74a803 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -422,6 +422,11 @@ static GB_read_function_t * const read_map[] = read_ram, read_high_memory, /* EXXX FXXX */ }; +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback) +{ + gb->read_memory_callback = callback; +} + uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { if (gb->n_watchpoints) { From 31609319deaab51cea6113d976681b83f628bfb2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Nov 2019 14:45:38 +0200 Subject: [PATCH 0939/1216] Fix the set_joyp API --- Core/memory.c | 6 +++++- Core/sgb.c | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 7e74a803..003bb77f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -753,7 +753,11 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_JOYP: - if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { + if (gb->joyp_write_callback) { + gb->joyp_write_callback(gb, value); + GB_update_joyp(gb); + } + else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { GB_sgb_write(gb, value); gb->io_registers[GB_IO_JOYP] = value & 0xF0; GB_update_joyp(gb); diff --git a/Core/sgb.c b/Core/sgb.c index 8539238c..7ebeae07 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -385,11 +385,7 @@ static void command_ready(GB_gameboy_t *gb) } void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) -{ - if (gb->joyp_write_callback) { - gb->joyp_write_callback(gb, value); - } - +{ if (!GB_is_sgb(gb)) return; if (!GB_is_hle_sgb(gb)) { /* Notify via callback */ From bd9ac204c2cb95427c49d655b35921fb2d2da67b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 20 Nov 2019 22:40:03 +0200 Subject: [PATCH 0940/1216] Allow SameBoy to compile on 4-byte-bools platforms --- Core/gb.c | 11 +++++++++-- Core/gb.h | 4 ---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 93ac9d72..f29d400a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -18,6 +18,13 @@ #define GB_rewind_push(...) #endif + +static inline uint32_t state_magic(void) +{ + if (sizeof(bool) == 1) return 'SAME'; + return 'S4ME'; +} + void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { char *string = NULL; @@ -660,7 +667,7 @@ void GB_disconnect_serial(GB_gameboy_t *gb) bool GB_is_inited(GB_gameboy_t *gb) { - return gb->magic == 'SAME'; + return gb->magic == state_magic(); } bool GB_is_cgb(GB_gameboy_t *gb) @@ -929,7 +936,7 @@ void GB_reset(GB_gameboy_t *gb) gb->nontrivial_jump_state = NULL; } - gb->magic = (uintptr_t)'SAME'; + gb->magic = state_magic(); } void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) diff --git a/Core/gb.h b/Core/gb.h index dbd9e165..a5611116 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -278,10 +278,6 @@ typedef struct { This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64 bit platforms. */ -/* We make sure bool is 1 for cross-platform save state compatibility. */ -/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */ -_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1"); - #ifdef GB_INTERNAL struct GB_gameboy_s { #else From 436dc0b67a0e392ff0e0e1c05f16e9292f4245eb Mon Sep 17 00:00:00 2001 From: retro-wertz Date: Sat, 13 Jul 2019 22:11:29 +0800 Subject: [PATCH 0941/1216] Fix GBC memory map and add IO port range for cheevos --- libretro/libretro.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 54c79060..59dfdc44 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -425,21 +425,20 @@ static void init_for_current_model(unsigned id) descs[7].len = 0x4000; descs[7].flags = RETRO_MEMDESC_CONST; - descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); - descs[8].start = 0xFE00; - descs[8].select = 0xFFFFFF00; - descs[8].len = 0x00A0; + descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); + descs[8].start = 0xFE00; + descs[8].len = 0x00A0; + descs[8].select= 0xFFFFFF00; - descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ - descs[9].start = 0x10000; - descs[9].select = 0xFFFF0000; - descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ + descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ + descs[9].start = 0x10000; + descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ + descs[9].select= 0xFFFF0000; - descs[10].ptr = descs[8].ptr; - descs[10].offset = 0x100; - descs[10].start = 0xFF00; - descs[10].select = 0xFFFFFF00; - descs[10].len = 0x0080; + descs[10].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IO, &size, &bank); + descs[10].start = 0xFF00; + descs[10].len = 0x0080; + descs[10].select= 0xFFFFFF00; struct retro_memory_map mmaps; mmaps.descriptors = descs; From 7c9508ae961d9ddf6cdf8a8ff638261ddb4b5bb3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Bentley" Date: Tue, 25 Jun 2019 21:01:54 -0600 Subject: [PATCH 0942/1216] Include the canonical SDL2 path, which drops the SDL2/ prefix. Use pkg-config or sdl2-config to determine SDL and GL compilation flags. --- Makefile | 29 +++++++++++++++++++++++++++-- SDL/gui.c | 2 +- SDL/gui.h | 2 +- SDL/main.c | 2 +- SDL/opengl_compat.c | 2 +- SDL/opengl_compat.h | 4 ++-- SDL/utils.c | 2 +- 7 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index dfab2486..bc2b3718 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,11 @@ CC := clang endif endif +# Find libraries with pkg-config if available. +ifneq (, $(shell which pkg-config)) +PKG_CONFIG := pkg-config +endif + ifeq ($(PLATFORM),windows32) # To force use of the Unix version instead of the Windows version MKDIR := $(shell which mkdir) @@ -82,7 +87,19 @@ endif CFLAGS += -Werror -Wall -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES -SDL_LDFLAGS := -lSDL2 -lGL +ifeq (,$(PKG_CONFIG)) +SDL_CFLAGS := $(shell sdl2-config --cflags) +SDL_LDFLAGS := $(shell sdl2-config --libs) +else +SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) +SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) +endif +ifeq (,$(PKG_CONFIG)) +GL_LDFLAGS := -lGL +else +GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl) +GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL) +endif ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -Drandom=rand LDFLAGS += -lmsvcrt -lcomdlg32 -lSDL2main -Wl,/MANIFESTFILE:NUL @@ -169,6 +186,10 @@ ifneq ($(filter $(MAKECMDGOALS),cocoa),) endif endif +$(OBJ)/SDL/%.dep: SDL/% + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ + $(OBJ)/%.dep: % -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ @@ -179,6 +200,10 @@ $(OBJ)/Core/%.c.o: Core/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) -DGB_INTERNAL -c $< -o $@ +$(OBJ)/SDL/%.c.o: SDL/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ + $(OBJ)/%.c.o: %.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ @@ -256,7 +281,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs # Unix versions build only one binary $(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) ifeq ($(CONF), release) strip $@ endif diff --git a/SDL/gui.c b/SDL/gui.c index b34bb1a7..3bfd99ee 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1,5 +1,5 @@ -#include #include +#include #include #include #include diff --git a/SDL/gui.h b/SDL/gui.h index 6974849a..b22c74f2 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -1,7 +1,7 @@ #ifndef gui_h #define gui_h -#include +#include #include #include #include "shader.h" diff --git a/SDL/main.c b/SDL/main.c index 933237eb..a095064c 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include "utils.h" #include "gui.h" diff --git a/SDL/opengl_compat.c b/SDL/opengl_compat.c index aed2a769..af7ce6d7 100644 --- a/SDL/opengl_compat.c +++ b/SDL/opengl_compat.c @@ -1,5 +1,5 @@ #define GL_GLEXT_PROTOTYPES -#include +#include #ifndef __APPLE__ #define GL_COMPAT_NAME(func) gl_compat_##func diff --git a/SDL/opengl_compat.h b/SDL/opengl_compat.h index 9fd1ca92..15b2a176 100644 --- a/SDL/opengl_compat.h +++ b/SDL/opengl_compat.h @@ -2,8 +2,8 @@ #define opengl_compat_h #define GL_GLEXT_PROTOTYPES -#include -#include +#include +#include #ifndef __APPLE__ #define GL_COMPAT_NAME(func) gl_compat_##func diff --git a/SDL/utils.c b/SDL/utils.c index eee6ce65..8cdd00b9 100644 --- a/SDL/utils.c +++ b/SDL/utils.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include "utils.h" From 8a99d41c3199f5e1dcb2f53e51ec424aece5716e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 26 Dec 2019 02:00:58 +0200 Subject: [PATCH 0943/1216] Fix broken SDL builds on macOS and Windows --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index bc2b3718..1c23610e 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,8 @@ endif ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -Drandom=rand LDFLAGS += -lmsvcrt -lcomdlg32 -lSDL2main -Wl,/MANIFESTFILE:NUL -SDL_LDFLAGS := -lSDL2 -lopengl32 +SDL_LDFLAGS := -lSDL2 +GL_LDFLAGS := -lopengl32 else LDFLAGS += -lc -lm -ldl endif @@ -113,7 +114,8 @@ SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL +SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 +GL_LDFLAGS := -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations ifeq ($(PLATFORM),windows32) From 4c243235309dd52fd5bb4469065fedcf417fb7b9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 29 Dec 2019 17:34:43 +0100 Subject: [PATCH 0944/1216] Fix Game Boy Camera support in macOS Mojave and newer --- Cocoa/Document.m | 17 +++++++++++++++++ Cocoa/Info.plist | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index a520d62e..85dd728a 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1171,6 +1171,23 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @try { if (!cameraSession) { + if (@available(macOS 10.14, *)) { + switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) { + case AVAuthorizationStatusAuthorized: + break; + case AVAuthorizationStatusNotDetermined: { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + [self cameraRequestUpdate]; + }]; + return; + } + case AVAuthorizationStatusDenied: + case AVAuthorizationStatusRestricted: + GB_camera_updated(&gb); + return; + } + } + NSError *error; AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error]; diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index dd801cb2..d86b9fd6 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -2,6 +2,8 @@ + CFBundleDisplayName + SameBoy CFBundleDevelopmentRegion en CFBundleDocumentTypes @@ -116,6 +118,8 @@ + NSCameraUsageDescription + SameBoy needs to access your camera to emulate the Game Boy Camera NSSupportsAutomaticGraphicsSwitching From e434b625ea14ee8f9937e0cc6e1ea39ec5d040b8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 30 Dec 2019 16:19:06 +0100 Subject: [PATCH 0945/1216] Allow the fullscreen key combo to work while in the menu --- SDL/gui.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SDL/gui.c b/SDL/gui.c index 3bfd99ee..1664e749 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1012,6 +1012,15 @@ void run_gui(bool is_running) } case SDL_KEYDOWN: + if (event.key.keysym.scancode == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { + if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else { + SDL_SetWindowFullscreen(window, 0); + } + } + break; if (event.key.keysym.scancode == SDL_SCANCODE_O) { if (event.key.keysym.mod & MODIFIER) { char *filename = do_open_rom_dialog(); From 7929573dc170670a6cf1e43b6d11483d8c640920 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 1 Jan 2020 00:17:54 +0200 Subject: [PATCH 0946/1216] Refinements to the last commit --- SDL/gui.c | 2 +- SDL/main.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index 1664e749..db72c4dd 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1019,8 +1019,8 @@ void run_gui(bool is_running) else { SDL_SetWindowFullscreen(window, 0); } + update_viewport(); } - break; if (event.key.keysym.scancode == SDL_SCANCODE_O) { if (event.key.keysym.mod & MODIFIER) { char *filename = do_open_rom_dialog(); diff --git a/SDL/main.c b/SDL/main.c index a095064c..51e7c1a3 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -274,6 +274,7 @@ static void handle_events(GB_gameboy_t *gb) else { SDL_SetWindowFullscreen(window, 0); } + update_viewport(); } break; From 3882b1b4b9ab34361211cbf7840e2f11d888a16f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 1 Jan 2020 00:27:41 +0200 Subject: [PATCH 0947/1216] Fix Windows build, hopefully fix High DPI support on Windows 10 (fixes #202) --- Makefile | 6 +++--- SDL/main.c | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) mode change 100755 => 100644 SDL/main.c diff --git a/Makefile b/Makefile index 1c23610e..44175287 100644 --- a/Makefile +++ b/Makefile @@ -102,7 +102,7 @@ GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL) endif ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -Drandom=rand -LDFLAGS += -lmsvcrt -lcomdlg32 -lSDL2main -Wl,/MANIFESTFILE:NUL +LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lSDL2main -Wl,/MANIFESTFILE:NUL SDL_LDFLAGS := -lSDL2 GL_LDFLAGS := -lopengl32 else @@ -291,11 +291,11 @@ endif # Windows version builds two, one with a conole and one without it $(BIN)/SDL/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:windows + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) -Wl,/subsystem:windows $(BIN)/SDL/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:console + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) -Wl,/subsystem:console ifneq ($(USE_WINDRES),) $(OBJ)/%.o: %.rc diff --git a/SDL/main.c b/SDL/main.c old mode 100755 new mode 100644 index 51e7c1a3..96f4994b --- a/SDL/main.c +++ b/SDL/main.c @@ -10,6 +10,7 @@ #ifndef _WIN32 +#include #define AUDIO_FREQUENCY 96000 #else /* Windows (well, at least my VM) can't handle 96KHz sound well :( */ @@ -551,6 +552,9 @@ static bool get_arg_flag(const char *flag, int *argc, char **argv) int main(int argc, char **argv) { +#ifdef _WIN32 + SetProcessDPIAware(); +#endif #define str(x) #x #define xstr(x) str(x) fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); From e9f6667cf52fb55a935ef0620845e7b7ad795126 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 1 Jan 2020 22:57:24 +0200 Subject: [PATCH 0948/1216] Minor build cleanup --- Makefile | 29 +++++++++++++++++------------ QuickLook/exports.sym | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 44175287..bd31875d 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ endif # Use clang if it's available. ifeq ($(origin CC),default) ifneq (, $(shell which clang)) -CC := clang +CC := clang endif endif @@ -76,9 +76,11 @@ endif # Set compilation and linkage flags based on target, platform and configuration OPEN_DIALOG = OpenDialog/gtk.c +NULL := /dev/null ifeq ($(PLATFORM),windows32) OPEN_DIALOG = OpenDialog/windows.c +NULL := NUL endif ifeq ($(PLATFORM),Darwin) @@ -110,9 +112,9 @@ LDFLAGS += -lc -lm -ldl endif ifeq ($(PLATFORM),Darwin) -SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) -CFLAGS += -F/Library/Frameworks -OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 +SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> $(NULL)) +CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 +OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 GL_LDFLAGS := -framework OpenGL @@ -127,10 +129,16 @@ ifeq ($(CONF),debug) CFLAGS += -g else ifeq ($(CONF), release) CFLAGS += -O3 -DNDEBUG +STRIP := strip +ifeq ($(PLATFORM),Darwin) +LDFLAGS += -Wl,-exported_symbols_list,$(NULL) +STRIP := -@true +endif ifneq ($(PLATFORM),windows32) LDFLAGS += -flto CFLAGS += -flto endif + else $(error Invalid value for CONF: $(CONF). Use "debug", "release" or "native_release") endif @@ -247,7 +255,7 @@ $(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 -framework IOKit ifeq ($(CONF), release) - strip $@ + $(STRIP) $@ endif $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib @@ -267,10 +275,7 @@ $(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL # 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 + $(CC) $^ -o $@ $(LDFLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook # cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided # boot ROM directory. @@ -285,7 +290,7 @@ $(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) ifeq ($(CONF), release) - strip $@ + $(STRIP) $@ endif # Windows version builds two, one with a conole and one without it @@ -321,7 +326,7 @@ $(BIN)/tester/sameboy_tester: $(CORE_OBJECTS) $(TESTER_OBJECTS) -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) ifeq ($(CONF), release) - strip $@ + $(STRIP) $@ endif $(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) @@ -372,7 +377,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb8 -@$(MKDIR) -p $(dir $@) rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $< rgblink -o $@.tmp2 $@.tmp - dd if=$@.tmp2 of=$@ count=1 bs=$(if $(findstring dmg,$@)$(findstring sgb,$@),256,2304) + dd if=$@.tmp2 of=$@ count=1 bs=$(if $(findstring dmg,$@)$(findstring sgb,$@),256,2304) 2> $(NULL) @rm $@.tmp $@.tmp2 # Libretro Core (uses its own build system) diff --git a/QuickLook/exports.sym b/QuickLook/exports.sym index 778b2031..f9796877 100644 --- a/QuickLook/exports.sym +++ b/QuickLook/exports.sym @@ -1,3 +1,3 @@ _DeallocQuickLookGeneratorPluginType _QuickLookGeneratorQueryInterface -_QuickLookGeneratorPluginFactory \ No newline at end of file +_QuickLookGeneratorPluginFactory From 23c7fb28858677f6cef474fae754898c246de8b0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 1 Jan 2020 23:40:56 +0200 Subject: [PATCH 0949/1216] Update version, update copyright year --- Cocoa/Info.plist | 2 +- Cocoa/License.html | 2 +- LICENSE | 2 +- Makefile | 2 +- QuickLook/Info.plist | 2 +- Windows/resources.rc | Bin 1210 -> 1210 bytes 6 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index d86b9fd6..1c7bdb5b 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -72,7 +72,7 @@ LSMinimumSystemVersion 10.9 NSHumanReadableCopyright - Copyright © 2015-2019 Lior Halphon + Copyright © 2015-2020 Lior Halphon NSMainNibFile MainMenu NSPrincipalClass diff --git a/Cocoa/License.html b/Cocoa/License.html index 49851fd5..b21cf8d8 100644 --- a/Cocoa/License.html +++ b/Cocoa/License.html @@ -30,7 +30,7 @@

    SameBoy

    MIT License

    -

    Copyright © 2015-2019 Lior Halphon

    +

    Copyright © 2015-2020 Lior Halphon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index 94966be8..17619e99 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2019 Lior Halphon +Copyright (c) 2015-2020 Lior Halphon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index bd31875d..7c223755 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.12.2 +VERSION := 0.12.3 export VERSION CONF ?= debug diff --git a/QuickLook/Info.plist b/QuickLook/Info.plist index 95402149..2cff196d 100644 --- a/QuickLook/Info.plist +++ b/QuickLook/Info.plist @@ -47,7 +47,7 @@ CFPlugInUnloadFunction NSHumanReadableCopyright - Copyright © 2015-2019 Lior Halphon + Copyright © 2015-2020 Lior Halphon QLNeedsToBeRunInMainThread QLPreviewHeight diff --git a/Windows/resources.rc b/Windows/resources.rc index ffa8b3b93a3b02e11c7283a4c4764b7e27b9321f..73c12139e68ff25a1cf03c451e05e25df9d3c05f 100644 GIT binary patch delta 16 XcmdnRxr=i{6ce)%gTdxlrdTEbD~beZ delta 16 XcmdnRxr=i{6ce){gT>}prdTEbE3yP| From 5a1812f237cc3c5501187728f5758949e6a930e5 Mon Sep 17 00:00:00 2001 From: Pixelnarium Date: Thu, 2 Jan 2020 10:50:55 +0100 Subject: [PATCH 0950/1216] fix SDL build --- SDL/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index 96f4994b..1646d5c8 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -10,9 +10,9 @@ #ifndef _WIN32 -#include #define AUDIO_FREQUENCY 96000 #else +#include /* Windows (well, at least my VM) can't handle 96KHz sound well :( */ /* felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. From 95af00a7524fda0ba932f89f3aaf232fc20f56e7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 3 Jan 2020 21:11:45 +0200 Subject: [PATCH 0951/1216] speling is veri difikult --- Core/debugger.c | 2 +- Core/gb.h | 2 +- Core/sm83_cpu.c | 34 +++++++++++++++++----------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index df480f34..8b6d6cf4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -819,7 +819,7 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ (gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', - (gb->f & GB_SUBSTRACT_FLAG)? 'N' : '-', + (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); diff --git a/Core/gb.h b/Core/gb.h index a5611116..c27b6c63 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -98,7 +98,7 @@ enum { enum { GB_CARRY_FLAG = 16, GB_HALF_CARRY_FLAG = 32, - GB_SUBSTRACT_FLAG = 64, + GB_SUBTRACT_FLAG = 64, GB_ZERO_FLAG = 128, }; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 77248b51..0009a69c 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -318,7 +318,7 @@ static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; 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); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((gb->registers[register_id] & 0x0F00) == 0) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -334,7 +334,7 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) 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; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if ((gb->registers[register_id] & 0x0F00) == 0xF00) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -396,7 +396,7 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) 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); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_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) { @@ -432,7 +432,7 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) 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); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((gb->registers[register_id] & 0x0F) == 0) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -452,7 +452,7 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) 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; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if ((gb->registers[register_id] & 0x0F) == 0xF) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -533,7 +533,7 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG); - if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { + if (gb->registers[GB_REGISTER_AF] & GB_SUBTRACT_FLAG) { if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { result = (result - 0x06) & 0xFF; } @@ -567,19 +567,19 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) static void cpl(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] ^= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG; } static void scf(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); } static void ccf(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); } static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) @@ -610,7 +610,7 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1; cycle_write(gb, gb->registers[GB_REGISTER_HL], value); - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((value & 0x0F) == 0) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } @@ -627,7 +627,7 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) cycle_write(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; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if ((value & 0x0F) == 0x0F) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } @@ -763,7 +763,7 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value, a; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } @@ -781,7 +781,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) 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; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; if ((uint8_t) (a - value - carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; @@ -833,7 +833,7 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) 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; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } @@ -962,7 +962,7 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t value, a; value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } @@ -980,7 +980,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) value = cycle_read_inc_oam_bug(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; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; if ((uint8_t) (a - value - carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; @@ -1032,7 +1032,7 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } From 163a5ea20c061fe66452017ef8f1b613af3f352f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Jan 2020 14:19:11 +0200 Subject: [PATCH 0952/1216] Add DMG color palettes (Cocoa) --- Cocoa/Document.m | 28 ++++++++++++++++++++ Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 21 +++++++++++++++ Cocoa/Preferences.xib | 43 ++++++++++++++++++++++++++----- Core/display.c | 14 +++++++--- Core/gb.c | 51 ++++++++++++++++++++++--------------- Core/gb.h | 15 ++++++++++- 7 files changed, 143 insertions(+), 30 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 85dd728a..0c407763 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -184,6 +184,27 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } } +- (void) updatePalette +{ + switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) { + case 1: + GB_set_palette(&gb, &GB_PALETTE_DMG); + break; + + case 2: + GB_set_palette(&gb, &GB_PALETTE_MGB); + break; + + case 3: + GB_set_palette(&gb, &GB_PALETTE_GBL); + break; + + default: + GB_set_palette(&gb, &GB_PALETTE_GREY); + break; + } +} + - (void) initCommon { GB_init(&gb, [self internalModel]); @@ -193,6 +214,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput); GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); + [self updatePalette]; GB_set_rgb_encode_callback(&gb, rgbEncode); GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); @@ -452,6 +474,12 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) name:@"GBColorCorrectionChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updatePalette) + name:@"GBColorPaletteChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateRewindLength) name:@"GBRewindLengthChanged" diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 90eee545..b34aafa7 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -7,6 +7,7 @@ @property (strong) IBOutlet NSButton *aspectRatioCheckbox; @property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; @property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton; @property (strong) IBOutlet NSPopUpButton *rewindPopupButton; @property (strong) IBOutlet NSButton *configureJoypadButton; @property (strong) IBOutlet NSButton *skipButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index ecf03111..8d3e73d6 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -14,6 +14,7 @@ NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton; NSPopUpButton *_colorCorrectionPopupButton; + NSPopUpButton *_colorPalettePopupButton; NSPopUpButton *_rewindPopupButton; NSButton *_aspectRatioCheckbox; NSEventModifierFlags previousModifiers; @@ -84,6 +85,18 @@ return _colorCorrectionPopupButton; } +- (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton +{ + _colorPalettePopupButton = colorPalettePopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]; + [_colorPalettePopupButton selectItemAtIndex:mode]; +} + +- (NSPopUpButton *)colorPalettePopupButton +{ + return _colorPalettePopupButton; +} + - (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton { _rewindPopupButton = rewindPopupButton; @@ -199,6 +212,14 @@ } +- (IBAction)colorPaletteChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBColorPalette"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; + +} + - (IBAction)rewindLengthChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 8278ee16..2c05a647 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -63,6 +63,7 @@ + @@ -78,11 +79,11 @@ - + - + @@ -91,7 +92,7 @@ - + @@ -127,7 +128,7 @@ - + @@ -136,7 +137,7 @@ - + @@ -156,6 +157,36 @@ + + + + + + + + + + + + + + + +

    + + + + + + + + + + + + + + - + diff --git a/Core/display.c b/Core/display.c index 5c1935c6..f0e2ff21 100644 --- a/Core/display.c +++ b/Core/display.c @@ -142,9 +142,17 @@ static void display_vblank(GB_gameboy_t *gb) } } else { - uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? - gb->rgb_encode_callback(gb, 0, 0, 0) : - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + uint32_t color = 0; + if (GB_is_cgb(gb)) { + color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + gb->rgb_encode_callback(gb, 0, 0, 0) : + gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + } + else { + color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + gb->background_palettes_rgb[3] : + gb->background_palettes_rgb[4]; + } for (unsigned i = 0; i < WIDTH * LINES; i++) { gb ->screen[i] = color; } diff --git a/Core/gb.c b/Core/gb.c index f29d400a..d019fc48 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -573,21 +573,41 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) #endif } +const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff ,0xff, 0xff}, {0xff ,0xff, 0xff}}}; +const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2 ,0xe6 ,0xa6}}}; +const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}}; +const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}}; + +static void update_dmg_palette(GB_gameboy_t *gb) +{ + const GB_palette_t *palette = gb->dmg_palette ?: &GB_PALETTE_GREY; + if (gb->rgb_encode_callback && !GB_is_cgb(gb)) { + gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = + gb->rgb_encode_callback(gb, palette->colors[3].r, palette->colors[3].g, palette->colors[3].b); + gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = + gb->rgb_encode_callback(gb, palette->colors[2].r, palette->colors[2].g, palette->colors[2].b); + gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = + gb->rgb_encode_callback(gb, palette->colors[1].r, palette->colors[1].g, palette->colors[1].b); + gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = + gb->rgb_encode_callback(gb, palette->colors[0].r, palette->colors[0].g, palette->colors[0].b); + + // LCD off color + gb->background_palettes_rgb[4] = + gb->rgb_encode_callback(gb, palette->colors[4].r, palette->colors[4].g, palette->colors[4].b); + } +} + +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette) +{ + gb->dmg_palette = palette; + update_dmg_palette(gb); +} void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) { - if (!gb->rgb_encode_callback && !GB_is_cgb(gb)) { - gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = - callback(gb, 0xFF, 0xFF, 0xFF); - gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = - callback(gb, 0xAA, 0xAA, 0xAA); - gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = - callback(gb, 0x55, 0x55, 0x55); - gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = - callback(gb, 0, 0, 0); - } gb->rgb_encode_callback = callback; + update_dmg_palette(gb); for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, true, i * 2); @@ -882,16 +902,7 @@ void GB_reset(GB_gameboy_t *gb) gb->vram_size = 0x2000; memset(gb->vram, 0, gb->vram_size); - if (gb->rgb_encode_callback) { - gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); - gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = - gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); - gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = - gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); - gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = - gb->rgb_encode_callback(gb, 0, 0, 0); - } + update_dmg_palette(gb); } reset_ram(gb); diff --git a/Core/gb.h b/Core/gb.h index c27b6c63..7831cb76 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -50,6 +50,17 @@ #error Unable to detect endianess #endif +typedef struct { + struct { + uint8_t r,g,b; + } colors[5]; +} GB_palette_t; + +extern const GB_palette_t GB_PALETTE_GREY; +extern const GB_palette_t GB_PALETTE_DMG; +extern const GB_palette_t GB_PALETTE_MGB; +extern const GB_palette_t GB_PALETTE_GBL; + typedef union { struct { uint8_t seconds; @@ -61,7 +72,6 @@ typedef union { uint8_t data[5]; } GB_rtc_time_t; - typedef enum { // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, @@ -513,6 +523,7 @@ struct GB_gameboy_internal_s { uint32_t *screen; uint32_t background_palettes_rgb[0x20]; uint32_t sprite_palettes_rgb[0x20]; + const GB_palette_t *dmg_palette; GB_color_correction_mode_t color_correction_mode; bool keys[4][GB_KEY_MAX]; @@ -696,6 +707,8 @@ void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback); +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); + /* These APIs are used when using internal clock */ void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback); From 046b09052cd54562de710c1577df05e18713800a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Jan 2020 15:36:19 +0200 Subject: [PATCH 0953/1216] Add DMG color palettes (SDL), add scrolling to SDL menus --- SDL/gui.c | 81 +++++++++++++++++++++++++++++++++++++++++------------- SDL/gui.h | 3 ++ SDL/main.c | 22 +++++++++++++++ 3 files changed, 87 insertions(+), 19 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index db72c4dd..d4761754 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -175,8 +175,10 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne } } +static unsigned scroll = 0; static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color) { + y -= scroll; unsigned orig_x = x; while (*string) { if (*string == '\n') { @@ -297,6 +299,7 @@ static void return_to_root_menu(unsigned index) { current_menu = root_menu; current_selection = 0; + scroll = 0; } static void cycle_model(unsigned index) @@ -407,6 +410,7 @@ static void enter_emulation_menu(unsigned index) { current_menu = emulation_menu; current_selection = 0; + scroll = 0; } const char *current_scaling_mode(unsigned index) @@ -421,6 +425,12 @@ const char *current_color_correction_mode(unsigned index) [configuration.color_correction_mode]; } +const char *current_palette(unsigned index) +{ + return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"} + [configuration.dmg_palette]; +} + void cycle_scaling(unsigned index) { configuration.scaling_mode++; @@ -463,6 +473,26 @@ static void cycle_color_correction_backwards(unsigned index) } } +static void cycle_palette(unsigned index) +{ + if (configuration.dmg_palette == 3) { + configuration.dmg_palette = 0; + } + else { + configuration.dmg_palette++; + } +} + +static void cycle_palette_backwards(unsigned index) +{ + if (configuration.dmg_palette == 0) { + configuration.dmg_palette = 3; + } + else { + configuration.dmg_palette--; + } +} + struct shader_name { const char *file_name; const char *display_name; @@ -556,6 +586,7 @@ static const struct menu_item graphics_menu[] = { {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, + {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, {"Blend Frames:", toggle_blend_frames, blend_frames_string, toggle_blend_frames}, {"Back", return_to_root_menu}, {NULL,} @@ -565,6 +596,7 @@ static void enter_graphics_menu(unsigned index) { current_menu = graphics_menu; current_selection = 0; + scroll = 0; } const char *highpass_filter_string(unsigned index) @@ -601,6 +633,7 @@ static void enter_audio_menu(unsigned index) { current_menu = audio_menu; current_selection = 0; + scroll = 0; } static void modify_key(unsigned index) @@ -608,7 +641,6 @@ static void modify_key(unsigned index) gui_state = WAITING_FOR_KEY; } -static void enter_controls_menu_2(unsigned index); static const char *key_name(unsigned index); static const struct menu_item controls_menu[] = { @@ -620,12 +652,6 @@ static const struct menu_item controls_menu[] = { {"B:", modify_key, key_name,}, {"Select:", modify_key, key_name,}, {"Start:", modify_key, key_name,}, - {"Next Page", enter_controls_menu_2}, - {"Back", return_to_root_menu}, - {NULL,} -}; - -static const struct menu_item controls_menu_2[] = { {"Turbo:", modify_key, key_name,}, {"Rewind:", modify_key, key_name,}, {"Slow-Motion:", modify_key, key_name,}, @@ -635,11 +661,11 @@ static const struct menu_item controls_menu_2[] = { static const char *key_name(unsigned index) { - if (current_menu == controls_menu_2) { - if (index == 0) { + if (index >= 8) { + if (index == 8) { return SDL_GetScancodeName(configuration.keys[8]); } - return SDL_GetScancodeName(configuration.keys_2[index - 1]); + return SDL_GetScancodeName(configuration.keys_2[index - 9]); } return SDL_GetScancodeName(configuration.keys[index]); } @@ -648,12 +674,7 @@ static void enter_controls_menu(unsigned index) { current_menu = controls_menu; current_selection = 0; -} - -static void enter_controls_menu_2(unsigned index) -{ - current_menu = controls_menu_2; - current_selection = 0; + scroll = 0; } static unsigned joypad_index = 0; @@ -744,6 +765,7 @@ static void enter_joypad_menu(unsigned index) { current_menu = joypad_menu; current_selection = 0; + scroll = 0; } joypad_button_t get_joypad_button(uint8_t physical_button) @@ -826,6 +848,7 @@ void run_gui(bool is_running) bool should_render = true; current_menu = root_menu = is_running? paused_menu : nonpaused_menu; current_selection = 0; + scroll = 0; do { /* Convert Joypad and mouse events (We only generate down events) */ if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) { @@ -850,6 +873,7 @@ void run_gui(bool is_running) y = y * 8 / 7; y -= 144 / 16; } + y += scroll; if (x < 0 || x >= 160 || y < 24) { continue; @@ -1058,6 +1082,7 @@ void run_gui(bool is_running) gui_state = SHOWING_DROP_MESSAGE; } current_selection = 0; + scroll = 0; current_menu = root_menu; should_render = true; } @@ -1106,12 +1131,12 @@ void run_gui(bool is_running) should_render = true; } else if (gui_state == WAITING_FOR_KEY) { - if (current_menu == controls_menu_2) { - if (current_selection == 0) { + if (current_selection >= 8) { + if (current_selection == 8) { configuration.keys[8] = event.key.keysym.scancode; } else { - configuration.keys_2[current_selection - 1] = event.key.keysym.scancode; + configuration.keys_2[current_selection - 9] = event.key.keysym.scancode; } } else { @@ -1125,6 +1150,7 @@ void run_gui(bool is_running) if (should_render) { should_render = false; + rerender: if (width == 160 && height == 144) { memcpy(pixels, converted_background->pixels, sizeof(pixels)); } @@ -1144,6 +1170,16 @@ void run_gui(bool is_running) draw_text_centered(pixels, width, height, 8 + y_offset, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); unsigned i = 0, y = 24; for (const struct menu_item *item = current_menu; item->string; item++, i++) { + if (i == current_selection) { + if (y < scroll) { + scroll = y - 4; + goto rerender; + } + } + if (i == current_selection && i == 0 && scroll != 0) { + scroll = 0; + goto rerender; + } if (item->value_getter && !item->backwards_handler) { char line[25]; snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (int)strlen(item->string), item->value_getter(i)); @@ -1162,6 +1198,13 @@ void run_gui(bool is_running) y += 12; } } + if (i == current_selection) { + if (y > scroll + 144) { + scroll = y - 144; + goto rerender; + } + } + } break; case SHOWING_HELP: diff --git a/SDL/gui.h b/SDL/gui.h index b22c74f2..ccfdfb9b 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -100,6 +100,9 @@ typedef struct { SGB_2, SGB_MAX } sgb_revision; + + /* v0.13 */ + uint8_t dmg_palette; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 1646d5c8..e83bfd8c 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -92,6 +92,26 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit) return captured_log; } +static void update_palette(void) +{ + switch (configuration.dmg_palette) { + case 1: + GB_set_palette(&gb, &GB_PALETTE_DMG); + break; + + case 2: + GB_set_palette(&gb, &GB_PALETTE_MGB); + break; + + case 3: + GB_set_palette(&gb, &GB_PALETTE_GBL); + break; + + default: + GB_set_palette(&gb, &GB_PALETTE_GREY); + } +} + static void open_menu(void) { bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; @@ -105,6 +125,7 @@ static void open_menu(void) SDL_PauseAudioDevice(device_id, 0); } GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + update_palette(); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); } @@ -454,6 +475,7 @@ restart: GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_sample_rate(&gb, have_aspec.freq); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + update_palette(); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_update_input_hint_callback(&gb, handle_events); From 99d2c0258c4da19b6b7a135bd9d0472388c79c33 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Jan 2020 15:51:53 +0200 Subject: [PATCH 0954/1216] Add monochrome LCD shader --- Cocoa/GBPreferencesWindow.m | 1 + Cocoa/Preferences.xib | 15 ++++++++------- SDL/gui.c | 1 + Shaders/MonoLCD.fsh | 38 +++++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 Shaders/MonoLCD.fsh diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 8d3e73d6..5c5d3756 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -32,6 +32,7 @@ @"NearestNeighbor", @"Bilinear", @"SmoothBilinear", + @"MonoLCD", @"LCD", @"CRT", @"Scale2x", diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 2c05a647..1062daed 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,8 +1,8 @@ - + - + @@ -17,7 +17,7 @@ - + @@ -104,7 +104,8 @@ - + + @@ -428,7 +429,7 @@ - + @@ -472,11 +473,11 @@ - - + - + - + - + - + - + - - - - + + + + @@ -129,16 +130,16 @@ - + - + - + @@ -148,9 +149,9 @@ - - - + + + @@ -159,16 +160,16 @@ - + - + - + @@ -188,10 +189,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -233,7 +263,7 @@ - + @@ -251,12 +281,12 @@ - + - + @@ -275,7 +305,7 @@ - + @@ -303,7 +333,7 @@ - + @@ -333,7 +363,7 @@ - + @@ -371,16 +401,16 @@ - + - + - - + + @@ -391,7 +421,7 @@ - + @@ -429,7 +459,7 @@ - + diff --git a/Core/apu.h b/Core/apu.h index 011412e3..933f14e8 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -162,6 +162,7 @@ void GB_apu_div_event(GB_gameboy_t *gb); void GB_apu_init(GB_gameboy_t *gb); void GB_apu_run(GB_gameboy_t *gb); void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); +void GB_borrow_sgb_border(GB_gameboy_t *gb); #endif #endif /* apu_h */ diff --git a/Core/display.c b/Core/display.c index 7f3d23e0..b7ddee1e 100644 --- a/Core/display.c +++ b/Core/display.c @@ -99,6 +99,8 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe #define LINE_LENGTH (456) #define LINES (144) #define WIDTH (160) +#define BORDERED_WIDTH 256 +#define BORDERED_HEIGHT 224 #define FRAME_LENGTH (LCDC_PERIOD) #define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154 @@ -134,7 +136,7 @@ static void display_vblank(GB_gameboy_t *gb) } } - if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + if ((!gb->disable_rendering || gb->sgb) && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (gb->sgb) { for (unsigned i = 0; i < WIDTH * LINES; i++) { @@ -153,13 +155,70 @@ static void display_vblank(GB_gameboy_t *gb) gb->background_palettes_rgb[3] : gb->background_palettes_rgb[4]; } - for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb ->screen[i] = color; + if (gb->border_mode == GB_BORDER_ALWAYS) { + for (unsigned y = 0; y < LINES; y++) { + for (unsigned x = 0; x < WIDTH; x++) { + gb ->screen[x + y * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH] = color; + } + } + } + else { + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->screen[i] = color; + } + } + } + } + + if (gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) { + GB_borrow_sgb_border(gb); + uint32_t border_colors[16 * 4]; + + if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) { + static uint16_t colors[] = { + 0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648, + 0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C, + 0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964, + }; + unsigned index = gb->rom[0x14e] % 5; + gb->borrowed_border.palette[0] = colors[index]; + gb->borrowed_border.palette[10] = colors[5 + index]; + gb->borrowed_border.palette[14] = colors[10 + index]; + + } + + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = GB_convert_rgb15(gb, gb->borrowed_border.palette[i], true); + } + + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { + for (unsigned tile_x = 0; tile_x < 32; tile_x++) { + if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { + continue; + } + uint16_t tile = gb->borrowed_border.map[tile_x + tile_y * 32]; + uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; + uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; + uint8_t palette = (tile >> 10) & 3; + for (unsigned y = 0; y < 8; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = gb->borrowed_border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + uint32_t *output = gb->screen + tile_x * 8 + x + (tile_y * 8 + y) * 256; + if (color == 0) { + *output = border_colors[0]; + } + else { + *output = border_colors[color + palette * 16]; + } + } + } } } } - gb->vblank_callback(gb); + if (gb->vblank_callback) { + gb->vblank_callback(gb); + } GB_timing_sync(gb); } @@ -184,19 +243,19 @@ static inline uint8_t scale_channel_with_curve_sgb(uint8_t x) } -uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color) +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) { uint8_t r = (color) & 0x1F; uint8_t g = (color >> 5) & 0x1F; uint8_t b = (color >> 10) & 0x1F; - if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { + if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED || (for_border && !gb->has_sgb_border)) { r = scale_channel(r); g = scale_channel(g); b = scale_channel(b); } else { - if (GB_is_sgb(gb)) { + if (GB_is_sgb(gb) || for_border) { return gb->rgb_encode_callback(gb, scale_channel_with_curve_sgb(r), scale_channel_with_curve_sgb(g), @@ -253,7 +312,7 @@ void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); - (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color); + (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color, false); } void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode) @@ -393,7 +452,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } /* Drop pixels for scrollings */ - if (gb->position_in_line >= 160 || gb->disable_rendering) { + if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { gb->position_in_line++; return; } @@ -412,6 +471,16 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } uint8_t icd_pixel = 0; + uint32_t *dest = NULL; + if (!gb->sgb) { + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->position_in_line + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->position_in_line + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + } + { uint8_t pixel = bg_enabled? fifo_item->pixel : 0; if (pixel && bg_priority) { @@ -431,7 +500,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } } else { - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + *dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; } } @@ -453,7 +522,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } } else { - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + *dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; } } diff --git a/Core/display.h b/Core/display.h index 9d1d1b47..69e7905e 100644 --- a/Core/display.h +++ b/Core/display.h @@ -49,6 +49,6 @@ typedef enum { void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); -uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color); +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); #endif /* display_h */ diff --git a/Core/gb.c b/Core/gb.c index 4a9525cb..b0eaf68a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -99,6 +99,42 @@ static char *default_async_input_callback(GB_gameboy_t *gb) } #endif +static void load_default_border(GB_gameboy_t *gb) +{ + if (gb->has_sgb_border) return; + + #define LOAD_BORDER() do { \ + memcpy(gb->borrowed_border.map, tilemap, sizeof(tilemap));\ + memcpy(gb->borrowed_border.palette, palette, sizeof(palette));\ + \ + /* Expand tileset */\ + for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) {\ + for (unsigned y = 0; y < 8; y++) {\ + for (unsigned x = 0; x < 8; x++) {\ + gb->borrowed_border.tiles[tile * 8 * 8 + y * 8 + x] =\ + (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) |\ + (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) |\ + (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) |\ + (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0);\ + }\ + }\ + }\ + } while(false); + + if (gb->model == GB_MODEL_AGB) { + #include "graphics/agb_border.inc" + LOAD_BORDER(); + } + else if (GB_is_cgb(gb)) { + #include "graphics/cgb_border.inc" + LOAD_BORDER(); + } + else { + #include "graphics/dmg_border.inc" + LOAD_BORDER(); + } +} + void GB_init(GB_gameboy_t *gb, GB_model_t model) { memset(gb, 0, sizeof(*gb)); @@ -125,6 +161,7 @@ void GB_init(GB_gameboy_t *gb, GB_model_t model) } GB_reset(gb); + load_default_border(gb); } GB_model_t GB_get_model(GB_gameboy_t *gb) @@ -184,6 +221,46 @@ void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, memcpy(gb->boot_rom, buffer, size); } +void GB_borrow_sgb_border(GB_gameboy_t *gb) +{ + if (GB_is_sgb(gb)) return; + if (gb->border_mode != GB_BORDER_ALWAYS) return; + if (gb->tried_loading_sgb_border) return; + gb->tried_loading_sgb_border = true; + if (gb->rom[0x146] != 3) return; // Not an SGB game, nothing to borrow + if (!gb->boot_rom_load_callback) return; // Can't borrow a border without this callback + GB_gameboy_t sgb; + GB_init(&sgb, GB_MODEL_SGB); + sgb.cartridge_type = gb->cartridge_type; + sgb.rom = gb->rom; + sgb.rom_size = gb->rom_size; + sgb.turbo = true; + sgb.turbo_dont_skip = true; + // sgb.disable_rendering = true; + + /* Load the boot ROM using the existing gb object */ + typeof(gb->boot_rom) boot_rom_backup; + memcpy(boot_rom_backup, gb->boot_rom, sizeof(gb->boot_rom)); + gb->boot_rom_load_callback(gb, GB_BOOT_ROM_SGB); + memcpy(sgb.boot_rom, gb->boot_rom, sizeof(gb->boot_rom)); + memcpy(gb->boot_rom, boot_rom_backup, sizeof(gb->boot_rom)); + sgb.sgb->intro_animation = -1; + + for (unsigned i = 600; i--;) { + GB_run_frame(&sgb); + if (sgb.sgb->border_animation) { + gb->has_sgb_border = true; + memcpy(&gb->borrowed_border, &sgb.sgb->pending_border, sizeof(gb->borrowed_border)); + gb->borrowed_border.palette[0] = sgb.sgb->effective_palettes[0]; + break; + } + } + + sgb.rom = NULL; + sgb.rom_size = 0; + GB_free(&sgb); +} + int GB_load_rom(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "rb"); @@ -199,6 +276,9 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) gb->rom_size |= gb->rom_size >> 1; gb->rom_size++; } + if (gb->rom_size == 0) { + gb->rom_size = 0x8000; + } fseek(f, 0, SEEK_SET); if (gb->rom) { free(gb->rom); @@ -208,7 +288,6 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) fread(gb->rom, 1, gb->rom_size, f); fclose(f); GB_configure_cart(gb); - return 0; } @@ -219,6 +298,9 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz gb->rom_size |= gb->rom_size >> 1; gb->rom_size++; } + if (gb->rom_size == 0) { + gb->rom_size = 0x8000; + } if (gb->rom) { free(gb->rom); } @@ -994,6 +1076,7 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) } GB_rewind_free(gb); GB_reset(gb); + load_default_border(gb); } void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank) @@ -1079,14 +1162,36 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb) return CPU_FREQUENCY * gb->clock_multiplier; } +void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode) +{ + if (gb->border_mode > GB_BORDER_ALWAYS) return; + gb->border_mode = border_mode; +} + unsigned GB_get_screen_width(GB_gameboy_t *gb) { - return GB_is_hle_sgb(gb)? 256 : 160; + switch (gb->border_mode) { + default: + case GB_BORDER_SGB: + return GB_is_hle_sgb(gb)? 256 : 160; + case GB_BORDER_NEVER: + return 160; + case GB_BORDER_ALWAYS: + return 256; + } } unsigned GB_get_screen_height(GB_gameboy_t *gb) { - return GB_is_hle_sgb(gb)? 224 : 144; + switch (gb->border_mode) { + default: + case GB_BORDER_SGB: + return GB_is_hle_sgb(gb)? 224 : 144; + case GB_BORDER_NEVER: + return 144; + case GB_BORDER_ALWAYS: + return 224; + } } unsigned GB_get_player_count(GB_gameboy_t *gb) diff --git a/Core/gb.h b/Core/gb.h index 487269fc..e803f3c9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -112,6 +112,12 @@ enum { GB_ZERO_FLAG = 128, }; +typedef enum { + GB_BORDER_SGB, + GB_BORDER_NEVER, + GB_BORDER_ALWAYS, +} GB_border_mode_t; + #define GB_MAX_IR_QUEUE 256 enum { @@ -538,6 +544,10 @@ struct GB_gameboy_internal_s { const GB_palette_t *dmg_palette; GB_color_correction_mode_t color_correction_mode; bool keys[4][GB_KEY_MAX]; + GB_border_mode_t border_mode; + GB_sgb_border_t borrowed_border; + bool tried_loading_sgb_border; + bool has_sgb_border; /* Timing */ uint64_t last_sync; @@ -707,7 +717,8 @@ 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_pixels_output(GB_gameboy_t *gb, uint32_t *output); - +void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode); + void GB_set_infrared_input(GB_gameboy_t *gb, bool state); void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); /* In 8MHz units*/ diff --git a/Core/graphics/agb_border.inc b/Core/graphics/agb_border.inc new file mode 100644 index 00000000..dd4ebbe8 --- /dev/null +++ b/Core/graphics/agb_border.inc @@ -0,0 +1,522 @@ +static const uint16_t palette[] = { + 0x410A, 0x0421, 0x35AD, 0x4A52, 0x7FFF, 0x2D49, 0x0C42, 0x1484, + 0x18A5, 0x20C6, 0x6718, 0x5D6E, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const uint16_t tilemap[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, + 0x0004, 0x0004, 0x0004, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0004, 0x0004, 0x0004, 0x0004, + 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000, + 0x0000, 0x0006, 0x0007, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x4007, 0x4006, 0x0000, + 0x0000, 0x0009, 0x0008, 0x0008, 0x0008, 0x000A, 0x000B, 0x000B, + 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, + 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, + 0x000B, 0x000B, 0x400A, 0x0008, 0x0008, 0x0008, 0xC009, 0x0000, + 0x0000, 0x000C, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400C, 0x0000, + 0x0000, 0x000E, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC00E, 0x0000, + 0x0000, 0x000F, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400F, 0x0000, + 0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000, + 0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000, + 0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000, + 0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000, + 0x0000, 0x0012, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4012, 0x0000, + 0x0000, 0x0013, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC013, 0x0000, + 0x0014, 0x0015, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4015, 0x4014, + 0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016, + 0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016, + 0x0018, 0x0019, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4019, 0x4018, + 0x001A, 0x001B, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC01B, 0xC01A, + 0x001C, 0x001D, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x401D, 0x401C, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001F, 0x801D, 0x0008, 0x0008, 0x0008, 0x0020, 0x0021, 0x0022, + 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, + 0x002B, 0x002C, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, + 0x002E, 0x0021, 0x4020, 0x0008, 0x0008, 0x0008, 0xC01D, 0x401F, + 0x002F, 0x0030, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0031, + 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, + 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, + 0x0042, 0x0043, 0x0008, 0x0008, 0x0008, 0x0008, 0x4030, 0x402F, + 0x0044, 0x0045, 0x0046, 0x0047, 0x0008, 0x0008, 0x0048, 0x0049, + 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, + 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005A, 0x005B, 0x0008, 0x0008, 0x4047, 0x4046, 0x4045, 0x4044, + 0x0000, 0x0000, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, + 0x0061, 0x0062, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, + 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x4062, 0x0061, + 0x0061, 0x4060, 0x405F, 0x405E, 0x405D, 0x405C, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1F, 0x01, + 0x7F, 0x1F, 0xFF, 0x7E, 0xFF, 0xE1, 0xFF, 0x9F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x1E, + 0x1F, 0x60, 0x7F, 0x80, 0xFF, 0x00, 0xBF, 0x40, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x03, 0x01, 0x07, 0x03, 0x07, 0x03, 0x07, 0x06, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x01, 0x02, 0x03, 0x04, 0x03, 0x04, 0x06, 0x01, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, + 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, + 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, + 0x0F, 0x0E, 0x0F, 0x0E, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x0E, 0x01, 0x0E, 0x01, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xBF, 0x40, 0xBF, 0x40, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, + 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, + 0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, + 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0xFF, 0xE7, 0xF8, 0xDF, 0xE3, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x00, 0xE4, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0xCE, 0x3F, 0xF5, 0x8E, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0x00, 0x4E, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xEE, 0x1F, 0xB5, 0x4E, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0x00, 0x0E, 0xA0, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x07, 0xFF, 0xFB, 0x07, 0x04, 0x73, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x03, 0x88, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0xFF, 0x7F, 0x80, 0x82, 0x39, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x80, 0x00, 0x01, 0x44, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0xFE, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x83, 0x7C, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x42, 0x01, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xBB, 0x7C, 0x4F, 0xB0, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x00, 0xB1, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x07, 0xFF, 0xF9, 0x06, 0xE7, 0xF8, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x06, 0x00, 0x08, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0E, 0xFF, 0xF5, 0x0E, 0x9B, 0x74, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x0E, 0x00, 0x94, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xF7, 0x0F, 0xBF, 0x47, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0x00, 0xA7, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, + 0x03, 0x04, 0x01, 0x02, 0x01, 0x02, 0x00, 0x01, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xDF, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0xBF, 0x40, 0xBF, 0x40, 0xDF, 0x20, + 0xB0, 0xD8, 0xA0, 0xD3, 0x67, 0x84, 0x47, 0xA4, + 0x61, 0x81, 0xA0, 0xD0, 0xB4, 0xCA, 0x7E, 0x81, + 0xD7, 0x08, 0xCC, 0x13, 0x98, 0x20, 0x98, 0x00, + 0x9E, 0x20, 0xCF, 0x00, 0xCD, 0x02, 0x80, 0x01, + 0x32, 0x2D, 0x13, 0x6D, 0x34, 0x48, 0xFC, 0x02, + 0x7C, 0x00, 0x78, 0x05, 0x30, 0x49, 0x20, 0x50, + 0xCD, 0x00, 0xAC, 0x40, 0x49, 0x82, 0x01, 0x02, + 0x07, 0x80, 0xC2, 0x05, 0x86, 0x41, 0x9F, 0x40, + 0x15, 0x2E, 0x09, 0x06, 0x09, 0x16, 0x0B, 0xD4, + 0xC6, 0x49, 0x8E, 0x40, 0xCF, 0xC8, 0x06, 0x01, + 0xCE, 0x20, 0xE6, 0x10, 0xE6, 0x00, 0x24, 0xD0, + 0x39, 0x80, 0x38, 0x01, 0x31, 0x00, 0xF8, 0x00, + 0x0C, 0x8B, 0x85, 0x8A, 0x03, 0x84, 0x27, 0x20, + 0x22, 0x35, 0x12, 0x34, 0x20, 0x12, 0x10, 0x20, + 0x73, 0x00, 0x72, 0x08, 0x7C, 0x80, 0xDC, 0x01, + 0xC8, 0x11, 0xC9, 0x06, 0xCD, 0x22, 0xEF, 0x10, + 0x83, 0x44, 0x86, 0x01, 0x03, 0x85, 0x26, 0x21, + 0x46, 0x69, 0x46, 0x68, 0x8E, 0xCA, 0x86, 0x88, + 0x39, 0x40, 0x78, 0x84, 0x7C, 0x80, 0xD8, 0x01, + 0x90, 0x29, 0xD1, 0x28, 0x73, 0x00, 0xB3, 0x40, + 0x00, 0x01, 0x01, 0x00, 0x3F, 0x00, 0x3F, 0x40, + 0x03, 0x02, 0x01, 0x02, 0x41, 0x7C, 0x7F, 0x00, + 0xFE, 0x00, 0xFF, 0x00, 0xC0, 0x00, 0x80, 0x40, + 0xFC, 0x00, 0xFC, 0x00, 0x80, 0x02, 0xC0, 0x00, + 0xC0, 0x00, 0x80, 0x4C, 0xCC, 0x43, 0x8E, 0x52, + 0x80, 0x4C, 0x80, 0x00, 0x12, 0x1E, 0x9E, 0x00, + 0x7F, 0x00, 0x33, 0x0C, 0x32, 0x01, 0x23, 0x50, + 0x33, 0x4C, 0x7F, 0x00, 0x61, 0x80, 0xF1, 0x00, + 0x7C, 0x02, 0x30, 0x48, 0x31, 0x40, 0x61, 0x50, + 0x87, 0xE4, 0xE3, 0x84, 0x23, 0x44, 0x43, 0x44, + 0x85, 0x42, 0x87, 0x40, 0x8F, 0x50, 0x8C, 0x12, + 0x78, 0x00, 0x18, 0x20, 0xB8, 0x00, 0x98, 0x24, + 0x03, 0x04, 0x03, 0xE0, 0xF1, 0x12, 0xF0, 0x09, + 0xF9, 0x09, 0xF9, 0x08, 0xE1, 0x12, 0xF1, 0x12, + 0xF8, 0x00, 0x1E, 0xE0, 0x0C, 0x02, 0x07, 0x08, + 0x07, 0x00, 0x06, 0x00, 0x1C, 0x02, 0x0C, 0x00, + 0x9F, 0x91, 0x86, 0x88, 0xC4, 0x4C, 0x80, 0x4C, + 0xE1, 0x20, 0xC1, 0x22, 0x23, 0xD4, 0x22, 0xD5, + 0x60, 0x00, 0xFB, 0x00, 0x37, 0x00, 0x73, 0x0C, + 0x1F, 0x00, 0x3C, 0x00, 0xC8, 0x14, 0xC9, 0x14, + 0x16, 0x2F, 0x76, 0x4F, 0x2D, 0xDE, 0xDD, 0xBE, + 0xBA, 0x7D, 0x7A, 0xFD, 0x7A, 0xFD, 0xF4, 0xF8, + 0xCF, 0x00, 0x8F, 0x00, 0x5E, 0x80, 0xBE, 0x00, + 0x7D, 0x00, 0xFC, 0x00, 0xFC, 0x01, 0xF9, 0x02, + 0xFF, 0x00, 0xBF, 0x78, 0x86, 0x09, 0x86, 0x89, + 0x06, 0x25, 0x02, 0x25, 0x42, 0x45, 0x60, 0x11, + 0x00, 0x00, 0x09, 0x00, 0x70, 0x81, 0x70, 0x09, + 0xDC, 0x21, 0xD8, 0x01, 0x98, 0x25, 0xCC, 0x13, + 0xFF, 0x00, 0xF3, 0xF8, 0x02, 0x03, 0x01, 0x30, + 0x39, 0x09, 0x30, 0x09, 0x31, 0x09, 0x20, 0x19, + 0x00, 0x00, 0x01, 0x04, 0xFC, 0x00, 0xCF, 0x30, + 0xE6, 0x00, 0xE6, 0x01, 0xE6, 0x00, 0xF6, 0x08, + 0xFF, 0x00, 0xFA, 0xC7, 0x18, 0x21, 0x09, 0x10, + 0x88, 0x99, 0x93, 0x1A, 0x83, 0x11, 0xC2, 0x41, + 0x00, 0x00, 0x00, 0x20, 0xC6, 0x21, 0xFF, 0x00, + 0x67, 0x00, 0xE4, 0x08, 0x6F, 0x10, 0x3C, 0x00, + 0xFD, 0x02, 0xB5, 0x3A, 0xC7, 0x44, 0x03, 0x84, + 0x83, 0x24, 0x21, 0xB0, 0x21, 0x12, 0x21, 0x02, + 0x02, 0x00, 0x02, 0x40, 0x3C, 0x00, 0xF8, 0x00, + 0xD8, 0x24, 0x4C, 0x92, 0xEC, 0x00, 0xCC, 0x12, + 0xFF, 0x00, 0xFF, 0xF3, 0x1C, 0x14, 0x0C, 0x04, + 0x00, 0x0C, 0x04, 0x24, 0x00, 0x24, 0x10, 0x30, + 0x00, 0x00, 0x10, 0x04, 0xE3, 0x00, 0xFB, 0x00, + 0xF3, 0x08, 0xDB, 0x20, 0xDB, 0x04, 0xCF, 0x00, + 0xFF, 0x00, 0xEC, 0x3E, 0xC1, 0x01, 0x01, 0x8E, + 0x8F, 0x10, 0x0F, 0x90, 0x0F, 0x90, 0x0D, 0x09, + 0x00, 0x00, 0x20, 0x01, 0x7E, 0x00, 0xF1, 0x0E, + 0xE0, 0x10, 0x60, 0x10, 0x60, 0x10, 0x79, 0x82, + 0xFF, 0x00, 0x7F, 0xFC, 0x03, 0x82, 0x01, 0x9E, + 0x13, 0x80, 0x03, 0x80, 0x03, 0x9C, 0x0F, 0x90, + 0x00, 0x00, 0x02, 0x00, 0x7C, 0x80, 0x60, 0x9C, + 0x60, 0x9C, 0x7C, 0x80, 0x60, 0x9C, 0x70, 0x80, + 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xEF, 0xFF, 0xF7, 0x7F, 0x7B, 0x3F, 0x3C, + 0x1F, 0x1F, 0x0F, 0x0F, 0x03, 0x03, 0x00, 0x00, + 0xEF, 0x10, 0x77, 0x88, 0x3B, 0x44, 0x1C, 0x23, + 0x0F, 0x10, 0x03, 0x0C, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3F, 0xC0, 0xC3, 0x3C, 0xFC, 0x03, 0x3F, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC1, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xC0, 0xC1, 0x3E, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xEA, 0x14, 0xC0, 0x00, 0x80, 0x21, 0x7F, 0x92, + 0x9F, 0xE0, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x27, 0x18, 0x7F, 0x00, 0x1E, 0x61, 0x9A, 0x04, + 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x73, 0x53, 0x47, 0x44, 0x46, 0x25, 0xFD, 0x03, + 0xF9, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x00, 0xD8, 0x20, 0x1D, 0xA0, 0x03, 0x00, + 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0xE1, 0xE6, 0x05, 0x42, 0xA5, 0xBF, 0xC0, + 0x9F, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x18, 0x24, 0x38, 0x01, 0xB8, 0x05, 0xC0, 0x00, + 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x21, 0x11, 0x31, 0x49, 0x33, 0x4A, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xDE, 0x00, 0x87, 0x48, 0x84, 0x48, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xCC, 0x02, 0x8E, 0x4A, 0xCC, 0x42, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x71, 0x08, 0x39, 0x00, 0x31, 0x02, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3D, 0x40, 0x03, 0x02, 0x03, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBC, 0x02, 0xFC, 0x00, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x12, 0x82, 0x80, 0x80, 0x01, 0x83, 0xFF, 0x00, + 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x61, 0x1C, 0x7F, 0x00, 0x7C, 0x82, 0x00, 0x00, + 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x22, 0x52, 0x30, 0xC0, 0x58, 0xA4, 0x8F, 0x72, + 0x73, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x11, 0x4F, 0x90, 0xA3, 0x0C, 0x73, 0x00, + 0xFC, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x23, 0xA4, 0x06, 0x0D, 0x05, 0x1B, 0xBB, 0x07, + 0xE7, 0x1F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x98, 0x44, 0xF5, 0x08, 0xEB, 0x00, 0x87, 0x40, + 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x66, 0x85, 0xE2, 0xA5, 0x66, 0x81, 0xBF, 0xC1, + 0x99, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x99, 0x00, 0xB9, 0x00, 0x9D, 0x20, 0xC1, 0x00, + 0xE7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF6, 0xFA, 0xFC, 0xF2, 0xF7, 0xF8, 0xFB, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF9, 0x00, 0xF1, 0x02, 0xF8, 0x00, 0xFC, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x52, 0x53, 0x30, 0x23, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x21, 0xCC, 0x13, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x03, 0x06, 0xFE, 0x01, 0xF9, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFD, 0x02, 0xFA, 0x04, 0x01, 0x00, 0x07, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x86, 0x05, 0x46, 0xA0, 0x5F, 0xB8, 0xBF, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x38, 0x41, 0x99, 0x26, 0xB8, 0x00, 0xC0, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x30, 0x28, 0x09, 0x09, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC6, 0x09, 0xE6, 0x10, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x20, 0x38, 0x38, 0x20, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xD7, 0x08, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0x41, 0xA1, 0x61, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3E, 0x40, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x01, 0x82, 0x01, 0x82, 0xFF, 0x00, 0xFC, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7C, 0x82, 0x7C, 0x82, 0x00, 0x00, 0x03, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3F, 0x3F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x3C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFF, 0x3F, 0x3F, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0x01, 0x3F, 0xC0, 0x01, 0x3E, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, 0x3F, 0xC0, + 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, + 0x3F, 0xC0, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0xFC, 0x03, + 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, + 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, + 0xFC, 0x03, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, +}; diff --git a/Core/graphics/cgb_border.inc b/Core/graphics/cgb_border.inc new file mode 100644 index 00000000..755312a4 --- /dev/null +++ b/Core/graphics/cgb_border.inc @@ -0,0 +1,446 @@ +static const uint16_t palette[] = { + 0x7C1A, 0x0000, 0x0011, 0x3DEF, 0x6318, 0x7FFF, 0x1EBA, 0x19AF, + 0x1EAF, 0x4648, 0x7FC0, 0x2507, 0x1484, 0x5129, 0x5010, 0x2095, +}; + +static const uint16_t tilemap[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000, + 0x0000, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0007, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x4007, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x000A, 0x000B, 0x400A, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x800A, 0x000C, 0xC00A, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x000D, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x000E, + 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, + 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, + 0x001F, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x400D, 0x0000, + 0x0000, 0x0020, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, + 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, + 0x0032, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4020, 0x0000, + 0x0000, 0x0033, 0x0034, 0x0035, 0x0036, 0x0005, 0x0005, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0005, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, + 0x0047, 0x0005, 0x0005, 0x4036, 0x4035, 0x4034, 0x4033, 0x0000, + 0x0000, 0x0000, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, + 0x004E, 0x004E, 0x004F, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x404F, 0x004E, 0x004E, + 0x404D, 0x004C, 0x404B, 0x404A, 0x4049, 0x4048, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, + 0x01, 0x11, 0x06, 0x26, 0x04, 0x24, 0x08, 0x48, + 0x00, 0x00, 0x01, 0x01, 0x07, 0x07, 0x0F, 0x0F, + 0x1E, 0x1F, 0x39, 0x3F, 0x3B, 0x3F, 0x77, 0x7F, + 0x00, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x7F, 0x7F, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x08, 0x48, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x77, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, + 0xBD, 0xBD, 0x7E, 0x66, 0x7E, 0xFF, 0x7E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF, + 0x7E, 0xFF, 0xFF, 0xE7, 0x7E, 0x7E, 0x7E, 0x7E, + 0x7E, 0xFF, 0x3C, 0xFF, 0x81, 0xFF, 0xC3, 0xFF, + 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0x7E, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x81, + 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xDF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xC7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xE3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x9F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0xE1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xD8, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x27, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x9F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC2, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xE0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x84, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x08, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, + 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, + 0x08, 0x48, 0x04, 0x24, 0x04, 0x24, 0x02, 0x12, + 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, + 0x37, 0x7F, 0x1B, 0x3F, 0x1B, 0x3F, 0x0D, 0x1F, + 0x0F, 0x08, 0x0E, 0x00, 0x1E, 0x12, 0x1E, 0x12, + 0x1F, 0x10, 0x0F, 0x08, 0x02, 0x02, 0x00, 0x00, + 0xF7, 0xF8, 0xFF, 0xF0, 0xED, 0xE3, 0xED, 0xE1, + 0xEF, 0xE0, 0xF7, 0xF0, 0xFD, 0xFC, 0xFF, 0xFF, + 0xF0, 0x10, 0x40, 0x00, 0x41, 0x41, 0x00, 0x00, + 0x83, 0x82, 0xE3, 0x20, 0xC7, 0x04, 0xC7, 0x00, + 0xEF, 0x1F, 0xFF, 0x1F, 0xBE, 0xFF, 0xFF, 0xFE, + 0x7D, 0x7E, 0xDF, 0x3C, 0xFB, 0x18, 0xFF, 0x18, + 0x60, 0x00, 0x70, 0x00, 0xF8, 0x08, 0xB0, 0x00, + 0xD8, 0x40, 0x3C, 0x24, 0x5C, 0x44, 0xFC, 0x00, + 0xFF, 0x8F, 0xFF, 0x0F, 0xF7, 0x07, 0xFF, 0x07, + 0xBF, 0x47, 0xDB, 0x47, 0xBB, 0x03, 0xFF, 0x03, + 0x3C, 0x04, 0x78, 0x00, 0x78, 0x00, 0xEC, 0x80, + 0xFE, 0x92, 0xE7, 0x83, 0xE5, 0x80, 0x4F, 0x08, + 0xFB, 0x83, 0xFF, 0x83, 0xFF, 0x83, 0x7F, 0x83, + 0x6D, 0x93, 0x7C, 0x10, 0x7F, 0x10, 0xF7, 0x10, + 0x3C, 0x00, 0x7C, 0x40, 0x78, 0x00, 0xC8, 0x80, + 0xFC, 0x24, 0xBC, 0x24, 0xFD, 0x65, 0x3D, 0x25, + 0xFF, 0xC3, 0xBF, 0x83, 0xFF, 0x83, 0x7F, 0x03, + 0xDB, 0x23, 0xDB, 0x23, 0x9A, 0x67, 0xDA, 0x47, + 0xFF, 0x80, 0xFF, 0x80, 0xE0, 0x80, 0x40, 0x00, + 0xFF, 0x01, 0xFF, 0x01, 0xDF, 0x1F, 0xE0, 0x20, + 0x7F, 0x80, 0x7F, 0x00, 0x7F, 0x1F, 0xFF, 0x1F, + 0xFE, 0x00, 0xFE, 0x00, 0xE0, 0x01, 0xDF, 0x3F, + 0xBF, 0xA0, 0xB9, 0xA0, 0x10, 0x00, 0x11, 0x01, + 0x3B, 0x00, 0x3F, 0x00, 0x7E, 0x4E, 0x78, 0x48, + 0x5F, 0x40, 0x5F, 0xC0, 0xFF, 0xC7, 0xFE, 0xC7, + 0xFF, 0xC0, 0xFF, 0xC0, 0xB1, 0xC4, 0xB7, 0x8F, + 0xE3, 0x22, 0xC7, 0x04, 0xCE, 0x08, 0xE6, 0x20, + 0xCE, 0x42, 0xDE, 0x52, 0xFE, 0x32, 0xFC, 0x30, + 0xDD, 0x3E, 0xFB, 0x18, 0xF7, 0x18, 0xDF, 0x31, + 0xBD, 0x31, 0xAD, 0x23, 0xCD, 0x31, 0xCF, 0x11, + 0xFE, 0x02, 0x9E, 0x00, 0x86, 0x80, 0x06, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x07, 0x01, 0x07, 0x01, + 0xFD, 0x03, 0xFF, 0x01, 0x7F, 0xF0, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFE, 0xF8, 0xFE, 0xF1, + 0x38, 0x08, 0x71, 0x41, 0x1F, 0x06, 0x39, 0x20, + 0x0F, 0x00, 0x0F, 0x01, 0x04, 0x00, 0x0C, 0x00, + 0xF7, 0x87, 0xBE, 0xC6, 0xF9, 0xC6, 0xDF, 0xE0, + 0xFF, 0xE0, 0xFE, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, + 0x70, 0x10, 0xE0, 0x20, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEF, 0x1F, 0xDF, 0x1F, 0xFF, 0x3F, 0xFF, 0x7F, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x3F, 0x7F, 0x7F, 0x78, 0x78, 0xF0, 0xF0, + 0xF0, 0xF0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0xF0, + 0xDF, 0xFF, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + 0xE7, 0xE0, 0xFF, 0xF0, 0xFE, 0xF0, 0x1C, 0x00, + 0x3C, 0x20, 0x3C, 0x24, 0x3C, 0x24, 0x3C, 0x20, + 0xFF, 0xFF, 0xEF, 0xFF, 0x6F, 0xFF, 0xFF, 0xFF, + 0xDF, 0xFF, 0xDB, 0xFF, 0xDB, 0xFF, 0xDF, 0xFF, + 0xF8, 0x00, 0xFC, 0xC0, 0x1F, 0x03, 0x1F, 0x13, + 0x1F, 0x13, 0x1E, 0x02, 0x1E, 0x02, 0x3E, 0x02, + 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0xFF, 0xEC, 0xFF, + 0xED, 0xFE, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x21, 0x21, + 0x20, 0x21, 0x00, 0x01, 0x00, 0x01, 0x40, 0x41, + 0x8F, 0x7F, 0x1F, 0xFF, 0x1F, 0xFF, 0x3F, 0xDE, + 0x1F, 0xFE, 0x3F, 0xFE, 0x3F, 0xFE, 0x7F, 0xBE, + 0x40, 0x7F, 0x84, 0xFF, 0x11, 0xF1, 0x20, 0xE0, + 0x20, 0xE0, 0x01, 0xC1, 0x01, 0xC1, 0x22, 0xE3, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x0E, 0xFF, 0x1F, + 0xDF, 0x3F, 0xFE, 0x3F, 0xFF, 0x3E, 0xFD, 0x1E, + 0x47, 0xC0, 0x27, 0xE0, 0x2F, 0xE8, 0x0F, 0xE9, + 0x0F, 0xE1, 0x0F, 0xE0, 0x3F, 0xF0, 0x3F, 0xF1, + 0xF8, 0x3F, 0xF8, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, + 0xF0, 0x1F, 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, + 0xFC, 0x00, 0xFE, 0xE2, 0x1E, 0x12, 0x1E, 0x12, + 0x3E, 0x22, 0xFC, 0x00, 0xF8, 0x08, 0xF0, 0x10, + 0x03, 0xFF, 0x01, 0xFF, 0xE1, 0xFF, 0xE1, 0xFF, + 0xC1, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x0F, 0xFF, + 0x01, 0x11, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x1F, 0x07, 0x0F, 0x03, 0x07, 0x01, 0x03, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x40, 0x40, 0x30, 0x30, + 0x0C, 0x0C, 0x03, 0xC3, 0x00, 0x30, 0x00, 0x0C, + 0xFF, 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xCF, 0xFF, + 0xF3, 0xFF, 0x3C, 0xFF, 0x0F, 0x3F, 0x03, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0xC0, 0x3C, 0x3C, 0x03, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, + 0x15, 0x15, 0x3F, 0x20, 0x2F, 0x20, 0x06, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEA, 0xE6, 0xDF, 0xC0, 0xDF, 0xE0, 0xF9, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE6, 0x20, 0x9E, 0x12, 0x0C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x30, 0xED, 0x31, 0xFF, 0x63, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0E, 0x02, 0x1E, 0x12, 0x0D, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0x03, 0xED, 0xE1, 0xFE, 0xF1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4F, 0x08, 0xE6, 0x20, 0xE7, 0x21, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x18, 0xDF, 0x18, 0xDE, 0x18, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xB9, 0xA1, 0x11, 0x01, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5E, 0x46, 0xFE, 0xC6, 0xFF, 0xC6, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0x01, 0xFF, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC0, 0x01, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7E, 0x4E, 0x3F, 0x00, 0x3E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB1, 0x8E, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEE, 0x20, 0x8F, 0x08, 0x85, 0x84, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x30, 0xF7, 0x38, 0x7B, 0x7C, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xAE, 0xA2, 0xF8, 0x00, 0xE8, 0x08, 0xC0, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5D, 0xE1, 0xFF, 0x03, 0xF7, 0x0F, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0E, 0x02, 0x1E, 0x12, 0x1E, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0xF1, 0xED, 0xF1, 0xED, 0xE3, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xFB, 0x7F, 0x7F, 0x3F, 0x3F, 0x0C, 0x0C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xDF, 0xC1, 0xDF, 0xD0, 0x8F, 0x88, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xEF, 0xFF, 0x77, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFA, 0x82, 0xF8, 0x08, 0xE0, 0x00, 0x81, 0x81, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0xFD, 0xF4, 0xFF, 0xFC, 0xFF, 0x7E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7D, 0x7D, 0x02, 0x02, 0x02, 0x02, 0xFC, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFE, 0x01, 0xFF, 0x01, 0xFF, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x1C, 0xFF, 0x00, 0xFF, 0x41, 0x7F, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x08, 0xFF, 0x00, 0xFF, 0x80, 0xE7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x5E, 0xC2, 0x9C, 0x80, 0x1C, 0x04, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE1, 0x3F, 0xE3, 0x7F, 0xE3, 0xFF, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x80, 0x78, 0x08, 0x78, 0x48, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0xFF, 0x87, 0xFF, 0x87, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x3C, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0x03, 0x3F, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1C, 0x1C, 0x03, 0x03, 0x00, 0xE0, 0x00, 0x1C, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE3, 0xFF, 0xFC, 0xFF, 0x1F, 0xFF, 0x03, 0x1F, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0xFC, 0x03, 0x03, 0x00, 0x00, + 0x00, 0xFC, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x03, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, + 0x03, 0xFF, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, + 0xC0, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF +}; diff --git a/Core/graphics/dmg_border.inc b/Core/graphics/dmg_border.inc new file mode 100644 index 00000000..7db0673a --- /dev/null +++ b/Core/graphics/dmg_border.inc @@ -0,0 +1,558 @@ +static const uint16_t palette[] = { + 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6, + 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4A32, 0x2033, 0x20EC, 0x0000, +}; + +static const uint16_t tilemap[] = { + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0001, 0x0003, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x4003, 0x0001, + 0x0001, 0x0006, 0x0007, 0x0007, 0x0007, 0x0008, 0x0009, 0x000A, + 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, + 0x0013, 0x0014, 0x0015, 0x000E, 0x0016, 0x0017, 0x0018, 0x0019, + 0x001A, 0x001B, 0x001C, 0x0007, 0x0007, 0x0007, 0x4006, 0x0001, + 0x0001, 0x001D, 0x001E, 0x001E, 0x001E, 0x001F, 0x0020, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x4024, 0x0026, 0x0025, 0x0025, + 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, + 0x002F, 0x0030, 0x0031, 0x001E, 0x001E, 0x001E, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0034, 0x0035, 0x4034, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x8034, 0x0036, 0xC034, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0037, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0038, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0039, 0x003A, 0x0001, + 0x0001, 0x003B, 0x003C, 0x0032, 0x0032, 0xC03C, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0001, 0x0001, + 0x0001, 0x0042, 0x0043, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0045, 0x0046, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, + 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, + 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x0001, 0x006C, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7, + 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00, + 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00, + 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF, + 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9, + 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9, + 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22, + 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22, + 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF, + 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF, + 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00, + 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF, + 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, + 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41, + 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F, + 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7, + 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0, + 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB, + 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00, + 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF, + 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00, + 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72, + 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08, + 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27, + 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88, + 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00, + 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77, + 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88, + 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF, + 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00, + 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9, + 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02, + 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74, + 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89, + 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89, + 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7, + 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4, + 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10, + 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12, + 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF, + 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00, + 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF, + 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00, + 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F, + 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80, + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF, + 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, + 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81, + 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, + 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, + 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02, + 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, + 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F, + 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04, + 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7, + 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08, + 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7, + 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40, + 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF, + 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F, + 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0, + 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x21, 0xDE, 0x00, 0x7F, + 0x0C, 0xF3, 0x19, 0xE0, 0x10, 0xEE, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x3F, 0x80, 0xFF, + 0x00, 0xFF, 0x0E, 0xF7, 0x1F, 0xE1, 0x07, 0xF8, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xBF, + 0x40, 0xBE, 0x80, 0x3F, 0x02, 0xFD, 0x00, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, + 0x7F, 0x81, 0xFE, 0x41, 0xFC, 0x03, 0xFC, 0x07, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFF, + 0x00, 0xFB, 0x04, 0xFB, 0x24, 0xDB, 0x64, 0x9B, + 0xFF, 0x00, 0xFF, 0x00, 0x87, 0x78, 0x07, 0xF8, + 0x07, 0xFC, 0x07, 0xF8, 0x03, 0xFC, 0x43, 0xBC, + 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xDE, 0x20, 0xDF, + 0x20, 0xDF, 0x00, 0xFF, 0x04, 0xFB, 0x04, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, + 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x00, 0xFF, + 0x00, 0x77, 0x00, 0x7F, 0x80, 0x6F, 0x82, 0x7D, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF8, 0x07, + 0xF8, 0x8F, 0xF0, 0x8F, 0x70, 0x9F, 0x60, 0x9F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB, + 0x24, 0xDB, 0x20, 0xDF, 0x20, 0xDF, 0x00, 0xDF, + 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7, + 0x18, 0xE7, 0x18, 0xE7, 0x38, 0xC7, 0x38, 0xE7, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFC, + 0x7E, 0x81, 0x80, 0x01, 0x80, 0x7F, 0xF8, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0x01, 0xFE, 0x01, 0xFF, + 0x01, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x07, 0xFC, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF, + 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0E, 0xF1, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F, + 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x9F, 0x00, 0xFF, + 0x10, 0xEF, 0x90, 0x6F, 0x10, 0xEB, 0x14, 0xEB, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x1F, 0xE0, + 0x0E, 0xF1, 0x0C, 0xF3, 0x0C, 0xF7, 0x18, 0xE7, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0x9F, 0x00, 0xFF, + 0x0C, 0xF3, 0x31, 0xC0, 0x60, 0x9F, 0x40, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x00, 0xFF, + 0x00, 0xFF, 0x1E, 0xEF, 0x3F, 0xC0, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x77, 0x04, 0xDB, + 0x00, 0xFB, 0x10, 0xEF, 0x00, 0xFD, 0x80, 0x77, + 0xFF, 0x00, 0xFF, 0x00, 0x78, 0x8F, 0x38, 0xE7, + 0x1C, 0xE7, 0x0C, 0xF3, 0x0E, 0xF3, 0x0E, 0xF9, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0x00, 0xFF, + 0x40, 0xB7, 0x00, 0xEF, 0x01, 0xDE, 0x02, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x83, 0x78, 0x87, + 0x38, 0xCF, 0x30, 0xDF, 0x21, 0xFE, 0x03, 0xFD, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x60, 0x9F, + 0xC0, 0x3F, 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x3F, 0xC0, + 0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x01, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFE, 0x01, 0xFC, 0x03, 0xFC, 0x07, 0xFE, 0x03, + 0x00, 0xFF, 0x40, 0x3F, 0x30, 0x8F, 0x00, 0xF7, + 0x80, 0x7F, 0x30, 0xCF, 0x01, 0xFE, 0x87, 0x78, + 0x01, 0xFE, 0x80, 0xFF, 0xE0, 0x5F, 0xF8, 0x0F, + 0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFC, + 0x00, 0xFF, 0x08, 0xF7, 0x80, 0x6F, 0x80, 0x7F, + 0x80, 0x5F, 0x87, 0x78, 0x04, 0x7B, 0x08, 0x73, + 0xF8, 0x07, 0xF0, 0x0F, 0x70, 0x9F, 0x60, 0x9F, + 0x60, 0xBF, 0xC3, 0x3C, 0x87, 0xF8, 0x87, 0xFC, + 0xA0, 0x1F, 0x80, 0x7D, 0xE2, 0x1D, 0x02, 0xFD, + 0x02, 0xFD, 0xF0, 0x0F, 0x10, 0xEE, 0x11, 0xEE, + 0x43, 0xFC, 0xE3, 0x1E, 0x03, 0xFC, 0x01, 0xFE, + 0x01, 0xFE, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1E, + 0x44, 0xBB, 0x48, 0xB3, 0x48, 0xB7, 0x08, 0xF7, + 0x0A, 0xF5, 0x02, 0xF5, 0x80, 0x77, 0x90, 0x67, + 0x84, 0x7B, 0x84, 0x7F, 0x84, 0x7B, 0x84, 0x7B, + 0x8C, 0x73, 0x8C, 0x7B, 0x0E, 0xF9, 0x0E, 0xF9, + 0x86, 0x59, 0x06, 0xF9, 0x48, 0xB3, 0x08, 0xF7, + 0x10, 0xE7, 0x14, 0xEB, 0x24, 0xCB, 0x20, 0xDF, + 0x60, 0xBF, 0x44, 0xBB, 0x04, 0xFF, 0x0C, 0xF3, + 0x0C, 0xFB, 0x18, 0xE7, 0x18, 0xF7, 0x38, 0xC7, + 0x08, 0xD7, 0x48, 0x97, 0x48, 0xB7, 0x41, 0xBE, + 0x41, 0xBE, 0x01, 0xBE, 0x10, 0xAF, 0x90, 0x2F, + 0x30, 0xEF, 0x30, 0xEF, 0x30, 0xCF, 0x30, 0xCF, + 0x70, 0x8F, 0x70, 0xCF, 0x60, 0xDF, 0x60, 0xDF, + 0x04, 0xFB, 0x04, 0xFB, 0xFC, 0x03, 0x00, 0xFF, + 0x00, 0xFF, 0xF8, 0x02, 0x05, 0xFA, 0x05, 0xFA, + 0x03, 0xFC, 0x03, 0xFC, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x07, 0xFD, 0x02, 0xFD, 0x06, 0xF9, + 0x80, 0x7F, 0x80, 0x7F, 0x0F, 0xF0, 0x10, 0xE7, + 0x10, 0xEE, 0x1E, 0xE1, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x0E, 0xF1, 0x0F, 0xF8, + 0x0F, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x60, 0x8F, 0x00, 0xDF, 0x00, 0xFF, 0x00, 0xEF, + 0x04, 0xEB, 0x20, 0xCF, 0x22, 0xDD, 0xC1, 0x1E, + 0x38, 0xD7, 0x38, 0xE7, 0x18, 0xE7, 0x18, 0xF7, + 0x18, 0xF7, 0x1C, 0xF3, 0x3E, 0xC1, 0x7F, 0xA0, + 0x80, 0x3F, 0x80, 0x7F, 0x80, 0x7F, 0x01, 0xFE, + 0x00, 0xBD, 0x18, 0xE7, 0x00, 0xFF, 0x83, 0x7C, + 0x7F, 0xC0, 0x7F, 0x80, 0x7F, 0x80, 0x7E, 0x81, + 0x7E, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x81, 0x76, 0x80, 0x77, 0x10, 0xE7, 0x10, 0xEF, + 0x10, 0xEF, 0x21, 0xCE, 0x41, 0x9E, 0x81, 0x3E, + 0x0E, 0xF9, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF0, + 0x1F, 0xE0, 0x3E, 0xD1, 0x7E, 0xA1, 0xFE, 0x41, + 0x04, 0xF9, 0x08, 0xF3, 0x18, 0xE7, 0x10, 0xEF, + 0x10, 0xEF, 0x10, 0xEF, 0x00, 0xEF, 0x20, 0xCF, + 0x07, 0xFA, 0x07, 0xFC, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x1F, 0xE0, 0x1F, 0xF0, 0x1F, 0xF0, + 0x7C, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x70, 0x8E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC3, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x24, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x8F, 0x70, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF8, 0x03, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3E, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xC1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xE0, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, +}; diff --git a/Core/sgb_animation_logo.inc b/Core/graphics/sgb_animation_logo.inc similarity index 100% rename from Core/sgb_animation_logo.inc rename to Core/graphics/sgb_animation_logo.inc diff --git a/Core/sgb_border.inc b/Core/graphics/sgb_border.inc similarity index 100% rename from Core/sgb_border.inc rename to Core/graphics/sgb_border.inc diff --git a/Core/sgb.c b/Core/sgb.c index 7ebeae07..d712e27f 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -474,7 +474,7 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color) { - return GB_convert_rgb15(gb, color); + return GB_convert_rgb15(gb, color, false); } static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade) @@ -489,14 +489,17 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_ color = r | (g << 5) | (b << 10); - return GB_convert_rgb15(gb, color); + return GB_convert_rgb15(gb, color, false); } #include static void render_boot_animation (GB_gameboy_t *gb) { -#include "sgb_animation_logo.inc" - uint32_t *output = &gb->screen[48 + 40 * 256]; +#include "graphics/sgb_animation_logo.inc" + uint32_t *output = gb->screen; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 48 + 40 * 256; + } uint8_t *input = animation_logo; unsigned fade_blue = 0; unsigned fade_red = 0; @@ -544,7 +547,9 @@ static void render_boot_animation (GB_gameboy_t *gb) input++; } } - output += 256 - 160; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } } } @@ -556,14 +561,6 @@ void GB_sgb_render(GB_gameboy_t *gb) } if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; - - if (!gb->screen || !gb->rgb_encode_callback) return; - - if (gb->sgb->mask_mode != MASK_FREEZE) { - memcpy(gb->sgb->effective_screen_buffer, - gb->sgb->screen_buffer, - sizeof(gb->sgb->effective_screen_buffer)); - } if (gb->sgb->vram_transfer_countdown) { if (--gb->sgb->vram_transfer_countdown == 0) { @@ -626,16 +623,27 @@ void GB_sgb_render(GB_gameboy_t *gb) } } + if (!gb->screen || !gb->rgb_encode_callback || gb->disable_rendering) return; + uint32_t colors[4 * 4]; for (unsigned i = 0; i < 4 * 4; i++) { colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]); } + if (gb->sgb->mask_mode != MASK_FREEZE) { + memcpy(gb->sgb->effective_screen_buffer, + gb->sgb->screen_buffer, + sizeof(gb->sgb->effective_screen_buffer)); + } + if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { render_boot_animation(gb); } else { - uint32_t *output = &gb->screen[48 + 40 * 256]; + uint32_t *output = gb->screen; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 48 + 40 * 256; + } uint8_t *input = gb->sgb->effective_screen_buffer; switch ((mask_mode_t) gb->sgb->mask_mode) { case MASK_DISABLED: @@ -645,7 +653,9 @@ void GB_sgb_render(GB_gameboy_t *gb) uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; *(output++) = colors[(*(input++) & 3) + palette * 4]; } - output += 256 - 160; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } } break; } @@ -656,7 +666,9 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned x = 0; x < 160; x++) { *(output++) = black; } - output += 256 - 160; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } } break; } @@ -666,7 +678,9 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned x = 0; x < 160; x++) { *(output++) = colors[0]; } - output += 256 - 160; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } } break; } @@ -703,6 +717,9 @@ void GB_sgb_render(GB_gameboy_t *gb) if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { gb_area = true; } + else if (gb->border_mode == GB_BORDER_NEVER) { + continue; + } uint16_t tile = gb->sgb->border.map[tile_x + tile_y * 32]; uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; @@ -710,12 +727,19 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned y = 0; y < 8; y++) { for (unsigned x = 0; x < 8; x++) { uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; - if (color == 0) { - if (gb_area) continue; - gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = colors[0]; + uint32_t *output = gb->screen; + if (gb->border_mode == GB_BORDER_NEVER) { + output += (tile_x - 6) * 8 + x + ((tile_y - 5) * 8 + y) * 160; } else { - gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[color + palette * 16]; + output += tile_x * 8 + x + (tile_y * 8 + y) * 256; + } + if (color == 0) { + if (gb_area) continue; + *output = colors[0]; + } + else { + *output = border_colors[color + palette * 16]; } } } @@ -726,12 +750,12 @@ void GB_sgb_render(GB_gameboy_t *gb) void GB_sgb_load_default_data(GB_gameboy_t *gb) { -#include "sgb_border.inc" +#include "graphics/sgb_border.inc" memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); memcpy(gb->sgb->border.palette, palette, sizeof(palette)); - /* Expend tileset */ + /* Expand tileset */ for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { for (unsigned y = 0; y < 8; y++) { for (unsigned x = 0; x < 8; x++) { diff --git a/Core/sgb.h b/Core/sgb.h index df90253c..aae9f755 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -5,6 +5,16 @@ #include typedef struct GB_sgb_s GB_sgb_t; +typedef struct { + uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ + union { + struct { + uint16_t map[32 * 32]; + uint16_t palette[16 * 4]; + }; + uint16_t raw_data[0x440]; + }; +} GB_sgb_border_t; #ifdef GB_INTERNAL struct GB_sgb_s { @@ -29,16 +39,7 @@ struct GB_sgb_s { uint8_t vram_transfer_countdown, transfer_dest; /* Border */ - struct { - uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ - union { - struct { - uint16_t map[32 * 32]; - uint16_t palette[16 * 4]; - }; - uint16_t raw_data[0x440]; - }; - } border, pending_border; + GB_sgb_border_t border, pending_border; uint8_t border_animation; /* Colorization */ diff --git a/SDL/gui.c b/SDL/gui.c index 4e06a75c..6646a172 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -429,7 +429,13 @@ const char *current_color_correction_mode(unsigned index) const char *current_palette(unsigned index) { return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"} - [configuration.dmg_palette]; + [configuration.dmg_palette]; +} + +const char *current_border_mode(unsigned index) +{ + return (const char *[]){"SGB Only", "Never", "Always"} + [configuration.border_mode]; } void cycle_scaling(unsigned index) @@ -494,6 +500,26 @@ static void cycle_palette_backwards(unsigned index) } } +static void cycle_border_mode(unsigned index) +{ + if (configuration.border_mode == GB_BORDER_ALWAYS) { + configuration.border_mode = GB_BORDER_SGB; + } + else { + configuration.border_mode++; + } +} + +static void cycle_border_mode_backwards(unsigned index) +{ + if (configuration.border_mode == GB_BORDER_SGB) { + configuration.border_mode = GB_BORDER_ALWAYS; + } + else { + configuration.border_mode--; + } +} + struct shader_name { const char *file_name; const char *display_name; @@ -589,6 +615,7 @@ static const struct menu_item graphics_menu[] = { {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, + {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, {"Blend Frames:", toggle_blend_frames, blend_frames_string, toggle_blend_frames}, {"Back", return_to_root_menu}, {NULL,} diff --git a/SDL/gui.h b/SDL/gui.h index ccfdfb9b..41e9bf2a 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -103,6 +103,7 @@ typedef struct { /* v0.13 */ uint8_t dmg_palette; + GB_border_mode_t border_mode; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index cb9d00f3..06159c97 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -112,12 +112,24 @@ static void update_palette(void) } } +static void screen_size_changed(void) +{ + SDL_DestroyTexture(texture); + texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, + GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + + SDL_SetWindowMinimumSize(window, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + + update_viewport(); +} + static void open_menu(void) { bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; if (audio_playing) { SDL_PauseAudioDevice(device_id, 1); } + size_t previous_width = GB_get_screen_width(&gb); run_gui(true); SDL_ShowCursor(SDL_DISABLE); if (audio_playing) { @@ -125,8 +137,12 @@ static void open_menu(void) SDL_PauseAudioDevice(device_id, 0); } GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + GB_set_border_mode(&gb, configuration.border_mode); update_palette(); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); + if (previous_width != GB_get_screen_width(&gb)) { + screen_size_changed(); + } } static void handle_events(GB_gameboy_t *gb) @@ -495,19 +511,15 @@ restart: GB_set_sample_rate(&gb, have_aspec.freq); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); update_palette(); + if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) { + GB_set_border_mode(&gb, configuration.border_mode); + } GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_update_input_hint_callback(&gb, handle_events); GB_apu_set_sample_callback(&gb, gb_audio_callback); } - - SDL_DestroyTexture(texture); - texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, - GB_get_screen_width(&gb), GB_get_screen_height(&gb)); - - SDL_SetWindowMinimumSize(window, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); - bool error = false; start_capturing_logs(); error = GB_load_rom(&gb, filename); @@ -528,7 +540,7 @@ restart: replace_extension(filename, path_length, symbols_path, ".sym"); GB_debugger_load_symbol_file(&gb, symbols_path); - update_viewport(); + screen_size_changed(); /* Run emulation */ while (true) { From dcb3f6db9e13e5b3510d0eadee326f3cf4ddd27b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 8 Feb 2020 14:38:04 +0200 Subject: [PATCH 0961/1216] Fix minimum window size in the Cocoa frontend --- Cocoa/Document.m | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 6b9915c8..f7349cc8 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -239,6 +239,15 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_apu_set_sample_callback(&gb, audioCallback); } +- (void) updateMinSize +{ + self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) || + self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) { + [self.mainWindow zoom:nil]; + } +} + - (void) vblank { [self.view flip]; @@ -248,6 +257,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); if (GB_get_screen_width(&gb) != previous_width) { [self.view screenSizeChanged]; + [self updateMinSize]; } }); borderModeChanged = false; @@ -405,11 +415,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [self.view screenSizeChanged]; } - self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb)); - if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) || - self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) { - [self.mainWindow zoom:nil]; - } + [self updateMinSize]; if ([sender tag] != 0) { /* User explictly selected a model, save the preference */ From 804b9bec63a16d8f94068bcc1fb5c1f30e4e9f34 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 10 Feb 2020 00:21:33 +0200 Subject: [PATCH 0962/1216] Fixed a bug where HDMA begins in the middle of an instruction while cycles are pending to be flushed. Fixes #230 --- Core/sm83_cpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 0009a69c..c9da208f 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1512,10 +1512,11 @@ void GB_cpu_run(GB_gameboy_t *gb) opcodes[gb->last_opcode_read](gb, gb->last_opcode_read); } + flush_pending_cycles(gb); + if (gb->hdma_starting) { gb->hdma_starting = false; gb->hdma_on = true; gb->hdma_cycles = -8; } - flush_pending_cycles(gb); } From 8b7805b95de34aec0ece1b482ce1e2f3b016f453 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 10 Feb 2020 20:19:37 +0200 Subject: [PATCH 0963/1216] Hit ^T --- Core/sm83_cpu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index c9da208f..713bb665 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1454,19 +1454,19 @@ void GB_cpu_run(GB_gameboy_t *gb) } gb->just_halted = false; - bool effecitve_ime = gb->ime; + bool effective_ime = gb->ime; if (gb->ime_toggle) { gb->ime = !gb->ime; gb->ime_toggle = false; } /* Wake up from HALT mode without calling interrupt code. */ - if (gb->halted && !effecitve_ime && interrupt_queue) { + if (gb->halted && !effective_ime && interrupt_queue) { gb->halted = false; } /* Call interrupt */ - else if (effecitve_ime && interrupt_queue) { + else if (effective_ime && interrupt_queue) { gb->halted = false; uint16_t call_addr = gb->pc; From 0677b1d099fd7ae6bf8eec83fae80df93e33a012 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 11 Feb 2020 00:11:17 +0200 Subject: [PATCH 0964/1216] Update the automation to not use internel APIs for input --- Tester/main.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 03b99850..e3b662ca 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -73,38 +73,38 @@ static void handle_buttons(GB_gameboy_t *gb) frames) % combo_length + (start_is_bad? 20 : 0) ) { case 0: if (!limit_start || frames < 20 * 60) { - gb->keys[0][push_right? 0 : 7] = true; // Start (Or right) down + GB_set_key_state(gb, push_right? GB_KEY_RIGHT: GB_KEY_START, true); } if (pointer_control) { - gb->keys[0][1] = true; // left - gb->keys[0][2] = true; // up + GB_set_key_state(gb, GB_KEY_LEFT, true); + GB_set_key_state(gb, GB_KEY_UP, true); } break; case 10: - gb->keys[0][push_right? 0 : 7] = false; // Start (Or right) up + GB_set_key_state(gb, push_right? GB_KEY_RIGHT: GB_KEY_START, false); if (pointer_control) { - gb->keys[0][1] = false; // left - gb->keys[0][2] = false; // up + GB_set_key_state(gb, GB_KEY_LEFT, false); + GB_set_key_state(gb, GB_KEY_UP, false); } break; case 20: - gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, true); break; case 30: - gb->keys[0][b_is_confirm? 5: 4] = false; // A up (or B) + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, false); break; case 40: if (push_a_twice) { - gb->keys[0][b_is_confirm? 5: 4] = true; // A down (or B) + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, true); } else if (gb->boot_rom_finished) { - gb->keys[0][3] = true; // D-Pad Down down + GB_set_key_state(gb, GB_KEY_DOWN, true); } break; case 50: - gb->keys[0][b_is_confirm? 5: 4] = false; // A down (or B) - gb->keys[0][3] = false; // D-Pad Down up + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, false); + GB_set_key_state(gb, GB_KEY_DOWN, false); break; } } From f550360f1ae8bf470dbb51a0eb879377b351322e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Feb 2020 13:21:21 +0200 Subject: [PATCH 0965/1216] More accurate CGB color correction curve --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index b7ddee1e..842c21f7 100644 --- a/Core/display.c +++ b/Core/display.c @@ -229,7 +229,7 @@ static inline uint8_t scale_channel(uint8_t x) static inline uint8_t scale_channel_with_curve(uint8_t x) { - return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255}[x]; + return (uint8_t[]){0,5,8,11,16,22,28,36,43,51,59,67,77,87,97,107,119,130,141,153,166,177,188,200,209,221,230,238,245,249,252,255}[x]; } static inline uint8_t scale_channel_with_curve_agb(uint8_t x) From 08eb2f3d987cf7a3775c2ecf1b00b74dd4bea7c2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Feb 2020 15:32:06 +0200 Subject: [PATCH 0966/1216] Correct emulation of FF6C (Turns out it controls object priority) --- Core/display.c | 2 +- Core/display.h | 7 +++++++ Core/gb.c | 2 ++ Core/gb.h | 7 +++---- Core/memory.c | 20 +++++++++++++++----- Core/save_state.c | 8 ++++++++ Misc/registers.sym | 4 ++-- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index 842c21f7..521a3861 100644 --- a/Core/display.c +++ b/Core/display.c @@ -939,7 +939,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram[line_address + 1], palette, object->flags & 0x80, - gb->cgb_mode? gb->visible_objs[gb->n_visible_objs - 1] : 0, + gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, object->flags & 0x20); gb->n_visible_objs--; diff --git a/Core/display.h b/Core/display.h index 69e7905e..b9e31494 100644 --- a/Core/display.h +++ b/Core/display.h @@ -11,6 +11,13 @@ void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value); void GB_STAT_update(GB_gameboy_t *gb); void GB_lcd_off(GB_gameboy_t *gb); + +enum { + GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility + GB_OBJECT_PRIORITY_X, + GB_OBJECT_PRIORITY_INDEX, +}; + #endif typedef enum { diff --git a/Core/gb.c b/Core/gb.c index b0eaf68a..31fff0d5 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1008,11 +1008,13 @@ void GB_reset(GB_gameboy_t *gb) gb->vram_size = 0x2000 * 2; memset(gb->vram, 0, gb->vram_size); gb->cgb_mode = true; + gb->object_priority = GB_OBJECT_PRIORITY_INDEX; } else { gb->ram_size = 0x2000; gb->vram_size = 0x2000; memset(gb->vram, 0, gb->vram_size); + gb->object_priority = GB_OBJECT_PRIORITY_X; update_dmg_palette(gb); } diff --git a/Core/gb.h b/Core/gb.h index e803f3c9..b413fe5f 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -185,7 +185,7 @@ enum { // Unfortunately it is not readable or writable after boot has finished, so research of this // register is quite limited. The value written to this register, however, can be controlled // in some cases. - GB_IO_DMG_EMULATION = 0x4c, + GB_IO_MODE = 0x4c, /* General CGB features */ GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch @@ -212,9 +212,7 @@ enum { 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 - - // 1 is written for DMG ROMs on a CGB. Does not appear to have an effect. - GB_IO_DMG_EMULATION_INDICATION = 0x6c, // (FEh) Bit 0 (Read/Write) + GB_IO_OBJECT_PRIORITY = 0x6c, // Affects object priority (X based or index based) /* Missing */ @@ -516,6 +514,7 @@ struct GB_gameboy_internal_s { bool cgb_palettes_blocked; uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. uint32_t cycles_in_stop_mode; + uint8_t object_priority; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index 003bb77f..8e254bc1 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -295,11 +295,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->io_registers[GB_IO_TAC] | 0xF8; case GB_IO_STAT: return gb->io_registers[GB_IO_STAT] | 0x80; - case GB_IO_DMG_EMULATION_INDICATION: - if (!gb->cgb_mode) { + case GB_IO_OBJECT_PRIORITY: + if (!GB_is_cgb(gb)) { return 0xFF; } - return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE; + return gb->io_registers[GB_IO_OBJECT_PRIORITY] | 0xFE; case GB_IO_PCM_12: if (!GB_is_cgb(gb)) return 0xFF; @@ -656,13 +656,22 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_OBP1: case GB_IO_WY: case GB_IO_SB: - case GB_IO_DMG_EMULATION_INDICATION: case GB_IO_UNKNOWN2: case GB_IO_UNKNOWN3: case GB_IO_UNKNOWN4: case GB_IO_UNKNOWN5: gb->io_registers[addr & 0xFF] = value; return; + case GB_IO_OBJECT_PRIORITY: + if ((!gb->boot_rom_finished || (gb->io_registers[GB_IO_MODE] & 8)) && GB_is_cgb(gb)) { + gb->io_registers[addr & 0xFF] = value; + gb->object_priority = (value & 1) ? GB_OBJECT_PRIORITY_X : GB_OBJECT_PRIORITY_INDEX; + } + else if (gb->cgb_mode) { + gb->io_registers[addr & 0xFF] = value; + + } + return; case GB_IO_LYC: /* TODO: Probably completely wrong in double speed mode */ @@ -768,9 +777,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->boot_rom_finished = true; return; - case GB_IO_DMG_EMULATION: + case GB_IO_MODE: if (GB_is_cgb(gb) && !gb->boot_rom_finished) { gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */ + gb->io_registers[GB_IO_MODE] = value; } return; diff --git a/Core/save_state.c b/Core/save_state.c index 8ef99aed..931a3190 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -266,6 +266,10 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; + if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { + gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; + } + error: fclose(f); return errno; @@ -371,6 +375,10 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; + if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { + gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; + } + return 0; } diff --git a/Misc/registers.sym b/Misc/registers.sym index 3bc95bb1..ea76ab3e 100644 --- a/Misc/registers.sym +++ b/Misc/registers.sym @@ -55,7 +55,7 @@ 00:FF69 IO_BGPD 00:FF6A IO_OBPI 00:FF6B IO_OBPD -00:FF6C IO_DMG_EMULATION_INDICATION +00:FF6C IO_OBJECT_PRIORITY 00:FF70 IO_SVBK 00:FF72 IO_UNKNOWN2 00:FF73 IO_UNKNOWN3 @@ -64,4 +64,4 @@ 00:FF76 IO_PCM_12 00:FF77 IO_PCM_34 00:FF7F IO_UNKNOWN8 -00:FFFF IO_IE \ No newline at end of file +00:FFFF IO_IE From bec09a012cae8f1efe5723afac401084420d59d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Feb 2020 19:21:43 +0200 Subject: [PATCH 0967/1216] More accurate emulation of STOP mode --- Core/display.c | 56 +++++++++++++++++++++++++++++++++---------------- Core/gb.h | 3 +++ Core/sm83_cpu.c | 33 +++++++++++++++++++++-------- Core/timing.c | 7 ++++++- 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/Core/display.c b/Core/display.c index 521a3861..d5f3c748 100644 --- a/Core/display.c +++ b/Core/display.c @@ -136,23 +136,18 @@ static void display_vblank(GB_gameboy_t *gb) } } - if ((!gb->disable_rendering || gb->sgb) && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; + + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ - if (gb->sgb) { - for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb->sgb->screen_buffer[i] = 0x0; - } - } - else { + if (!GB_is_sgb(gb)) { uint32_t color = 0; if (GB_is_cgb(gb)) { - color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? - gb->rgb_encode_callback(gb, 0, 0, 0) : - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + color = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); } else { - color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? - gb->background_palettes_rgb[3] : + color = is_ppu_stopped ? + gb->background_palettes_rgb[0] : gb->background_palettes_rgb[4]; } if (gb->border_mode == GB_BORDER_ALWAYS) { @@ -412,6 +407,10 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) { return; } + + if (gb->oam_ppu_blocked) { + return; + } /* This reverse sorts the visible objects by location and priority */ GB_object_t *objects = (GB_object_t *) &gb->oam; @@ -499,6 +498,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) icd_pixel = pixel; } } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } else { *dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; } @@ -521,6 +523,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) //gb->icd_pixel_callback(gb, pixel); } } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } else { *dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; } @@ -585,10 +590,16 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_y = y; } gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; + if (gb->vram_ppu_blocked) { + gb->current_tile = 0xFF; + } if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. This probably means the CGB has a 16-bit data bus for the VRAM. */ gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; + if (gb->vram_ppu_blocked) { + gb->current_tile_attributes = 0xFF; + } } gb->fetcher_x++; gb->fetcher_x &= 0x1f; @@ -615,7 +626,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) y_flip = 0x7; } gb->current_tile_data[0] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[0] = 0xFF; + } } gb->fetcher_state++; break; @@ -642,7 +656,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) y_flip = 0x7; } gb->current_tile_data[1] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[1] = 0xFF; + } } gb->fetcher_state++; break; @@ -913,7 +930,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += 6; GB_SLEEP(gb, display, 20, 6); /* TODO: what does the PPU read if DMA is active? */ - GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; + const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; + if (gb->oam_ppu_blocked) { + static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; + object = &blocked; + } bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); @@ -933,10 +954,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->cgb_mode) { palette = object->flags & 0x7; } - fifo_overlay_object_row(&gb->oam_fifo, - gb->vram[line_address], - gb->vram[line_address + 1], + gb->vram_ppu_blocked? 0xFF : gb->vram[line_address], + gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1], palette, object->flags & 0x80, gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, diff --git a/Core/gb.h b/Core/gb.h index b413fe5f..56b0a9af 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -515,6 +515,9 @@ struct GB_gameboy_internal_s { uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. uint32_t cycles_in_stop_mode; uint8_t object_priority; + bool oam_ppu_blocked; + bool vram_ppu_blocked; + bool cgb_palettes_ppu_blocked; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 713bb665..f30443dd 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -236,6 +236,26 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode) { } +static void enter_stop_mode(GB_gameboy_t *gb) +{ + gb->stopped = true; + gb->oam_ppu_blocked = !gb->oam_read_blocked; + gb->vram_ppu_blocked = !gb->vram_read_blocked; + gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked; +} + +static void leave_stop_mode(GB_gameboy_t *gb) +{ + /* The CPU takes more time to wake up then the other components */ + for (unsigned i = 0x200; i--;) { + GB_advance_cycles(gb, 0x10); + } + gb->stopped = false; + gb->oam_ppu_blocked = false; + gb->vram_ppu_blocked = false; + gb->cgb_palettes_ppu_blocked = false; +} + static void stop(GB_gameboy_t *gb, uint8_t opcode) { if (gb->io_registers[GB_IO_KEY1] & 0x1) { @@ -252,9 +272,8 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) gb->cgb_double_speed ^= true; gb->io_registers[GB_IO_KEY1] = 0; - for (unsigned i = 0x800; i--;) { - GB_advance_cycles(gb, 0x40); - } + enter_stop_mode(gb); + leave_stop_mode(gb); if (!needs_alignment) { GB_advance_cycles(gb, 0x4); @@ -270,7 +289,7 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) gb->halted = true; } else { - gb->stopped = true; + enter_stop_mode(gb); } } @@ -1429,11 +1448,7 @@ void GB_cpu_run(GB_gameboy_t *gb) GB_timing_sync(gb); GB_advance_cycles(gb, 4); if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { - gb->stopped = false; - /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x800; i--;) { - GB_advance_cycles(gb, 0x40); - } + leave_stop_mode(gb); GB_advance_cycles(gb, 8); } return; diff --git a/Core/timing.c b/Core/timing.c index 283558c0..1f3f654e 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -139,6 +139,11 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) { + if (gb->stopped) { + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + return; + } + GB_STATE_MACHINE(gb, div, cycles, 1) { GB_STATE(gb, div, 1); GB_STATE(gb, div, 2); @@ -213,8 +218,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Affected by speed boost gb->dma_cycles += cycles; + GB_timers_run(gb, cycles); if (!gb->stopped) { - GB_timers_run(gb, cycles); advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode } From bf32ae66c60d0401ccbb49031e19cd47e5890fff Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Feb 2020 19:23:04 +0200 Subject: [PATCH 0968/1216] Another attemp to fix Cocoa deadlocking --- Cocoa/Document.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index f7349cc8..df158066 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -374,7 +374,9 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) if (GB_debugger_is_stopped(&gb)) { [self interruptDebugInputRead]; } + [audioLock lock]; stopping = true; + [audioLock unlock]; running = false; while (stopping); GB_debugger_set_disabled(&gb, false); From 0290e704451742741c90b466e9ee34eeadd4ebcc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 17 Feb 2020 23:05:11 +0200 Subject: [PATCH 0969/1216] Improvements to AGB color correction --- Core/display.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index d5f3c748..e0c1a536 100644 --- a/Core/display.c +++ b/Core/display.c @@ -264,15 +264,13 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) { uint8_t new_r, new_g, new_b; if (agb) { - new_r = (r * 7 + g * 1) / 8; - new_g = (g * 3 + b * 1) / 4; - new_b = (b * 7 + r * 1) / 8; + new_g = (g * 6 + b * 1) / 7; } else { new_g = (g * 3 + b) / 4; - new_r = r; - new_b = b; } + new_r = r; + new_b = b; if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { uint8_t old_max = MAX(r, MAX(g, b)); uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); From a8f63aea3ce47b72805bc59b5d4f35d112d39bad Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 02:55:07 +0200 Subject: [PATCH 0970/1216] Emulate DMG LCDC write conflicts correctly. This might vary between individual units. --- Core/sm83_cpu.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index f30443dd..5c491f8f 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -18,6 +18,7 @@ typedef enum { GB_CONFLICT_STAT_DMG, GB_CONFLICT_PALETTE_DMG, GB_CONFLICT_PALETTE_CGB, + GB_CONFLICT_READ_DMG_LCDC, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -37,7 +38,7 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, - [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, + [GB_IO_LCDC] = GB_CONFLICT_READ_DMG_LCDC, [GB_IO_SCY] = GB_CONFLICT_READ_NEW, [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, @@ -192,6 +193,17 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->pending_cycles = 6; return; } + + case GB_CONFLICT_READ_DMG_LCDC: { + /* Seems to be affected by screen? Both my DMG (B, blob) and Game Boy Light behave this way though. */ + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, old_value | (value & 1)); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + } } } From 56118d2a6701f422d002a6da123768ef409820a9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 14:22:25 +0200 Subject: [PATCH 0971/1216] Move improvements to LCDC conflicts --- Core/display.c | 2 -- Core/sm83_cpu.c | 9 ++++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index e0c1a536..c09764a3 100644 --- a/Core/display.c +++ b/Core/display.c @@ -441,7 +441,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) { oam_fifo_item = fifo_pop(&gb->oam_fifo); - /* Todo: Verify access timings */ if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { draw_oam = true; bg_priority |= oam_fifo_item->bg_priority; @@ -457,7 +456,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Mixing */ - /* Todo: Verify access timings */ if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { if (gb->cgb_mode) { bg_priority = false; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 5c491f8f..53bca480 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -18,7 +18,7 @@ typedef enum { GB_CONFLICT_STAT_DMG, GB_CONFLICT_PALETTE_DMG, GB_CONFLICT_PALETTE_CGB, - GB_CONFLICT_READ_DMG_LCDC, + GB_CONFLICT_DMG_LCDC, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -38,7 +38,7 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, - [GB_IO_LCDC] = GB_CONFLICT_READ_DMG_LCDC, + [GB_IO_LCDC] = GB_CONFLICT_DMG_LCDC, [GB_IO_SCY] = GB_CONFLICT_READ_NEW, [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, @@ -194,10 +194,13 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - case GB_CONFLICT_READ_DMG_LCDC: { + case GB_CONFLICT_DMG_LCDC: { /* Seems to be affected by screen? Both my DMG (B, blob) and Game Boy Light behave this way though. */ uint8_t old_value = GB_read_memory(gb, addr); GB_advance_cycles(gb, gb->pending_cycles - 2); + if (/* gb->model != GB_MODEL_MGB && */ gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { + old_value &= ~2; + } GB_write_memory(gb, addr, old_value | (value & 1)); GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); From 91404edd138a6a44b4b2057ffc2300c4bfeab910 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 15:14:33 +0200 Subject: [PATCH 0972/1216] Disgusting hacks to emulate disabling objects while an object is being fetched --- Core/display.c | 12 ++++++++++++ Core/gb.h | 1 + Core/sm83_cpu.c | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index c09764a3..99b8185d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -912,6 +912,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) advance_fetcher_state_machine(gb); gb->cycles_for_line++; GB_SLEEP(gb, display, 27, 1); + if (!(gb->io_registers[GB_IO_LCDC] & 2) && !GB_is_cgb(gb)) { + goto abort_fetching_object; + } } /* Todo: Measure if penalty occurs before or after waiting for the fetcher. */ @@ -920,11 +923,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0; GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0); gb->extra_penalty_for_sprite_at_0 = 0; + if (gb->object_fetch_aborted) { + gb->object_fetch_aborted = false; + goto abort_fetching_object; + } } } gb->cycles_for_line += 6; GB_SLEEP(gb, display, 20, 6); + if (gb->object_fetch_aborted) { + gb->object_fetch_aborted = false; + goto abort_fetching_object; + } /* TODO: what does the PPU read if DMA is active? */ const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; if (gb->oam_ppu_blocked) { @@ -961,6 +972,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->n_visible_objs--; } +abort_fetching_object: /* Handle window */ /* Todo: Timing (Including penalty and access timings) not verified by test ROM */ if (!gb->in_window && window_enabled(gb) && diff --git a/Core/gb.h b/Core/gb.h index 56b0a9af..4e31e00d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -518,6 +518,7 @@ struct GB_gameboy_internal_s { bool oam_ppu_blocked; bool vram_ppu_blocked; bool cgb_palettes_ppu_blocked; + bool object_fetch_aborted; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 53bca480..1dbfc5ef 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -195,14 +195,51 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } case GB_CONFLICT_DMG_LCDC: { - /* Seems to be affected by screen? Both my DMG (B, blob) and Game Boy Light behave this way though. */ + /* Similar to the palette registers, these interact directly with the LCD, so they appear to be affected by it. Both my DMG (B, blob) and Game Boy Light behave this way though. + + Additionally, LCDC.1 is very nasty because on the it is read both by the FIFO when popping pixels, + and the sprite-fetching state machine, and both behave differently when it comes to access conflicts. + Hacks ahead. + */ + + + uint8_t old_value = GB_read_memory(gb, addr); GB_advance_cycles(gb, gb->pending_cycles - 2); + + if (gb->current_lcd_line == 108) { + + } + + /* Handle disabling objects while already fetching an object */ + if ((old_value & 2) && !(value & 2)) { + if (gb->display_state == 27) { + old_value &= ~2; + } + else if (gb->display_state == 20 || gb->display_state == 28) { + gb->cycles_for_line -= gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; + } + } + if (/* gb->model != GB_MODEL_MGB && */ gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { old_value &= ~2; } + GB_write_memory(gb, addr, old_value | (value & 1)); GB_advance_cycles(gb, 1); + /* Handle disabling objects while already fetching an object */ + if ((old_value & 2) && !(value & 2)) { + if (gb->display_state == 27) { + old_value &= ~2; + } + else if (gb->display_state == 20 || gb->display_state == 28) { + gb->cycles_for_line -= gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; + } + } GB_write_memory(gb, addr, value); gb->pending_cycles = 5; return; From 7d51ba3d9745917f7b5855bcc53e28dd7b5d2be2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 16:16:02 +0200 Subject: [PATCH 0973/1216] More fixes, SGB emulation of the same quirk --- Core/display.c | 19 +++++++++++++------ Core/gb.h | 1 + Core/sm83_cpu.c | 42 ++++++++++++++++++++++++++++++------------ 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/Core/display.c b/Core/display.c index 99b8185d..7523f6de 100644 --- a/Core/display.c +++ b/Core/display.c @@ -735,6 +735,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 36); GB_STATE(gb, display, 37); GB_STATE(gb, display, 38); + GB_STATE(gb, display, 39); } @@ -904,15 +905,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { gb->n_visible_objs--; } + + gb->during_object_fetch = true; while (gb->n_visible_objs != 0 && (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { - while (gb->fetcher_state < 5) { advance_fetcher_state_machine(gb); gb->cycles_for_line++; GB_SLEEP(gb, display, 27, 1); - if (!(gb->io_registers[GB_IO_LCDC] & 2) && !GB_is_cgb(gb)) { + if (gb->object_fetch_aborted) { goto abort_fetching_object; } } @@ -924,18 +926,20 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0); gb->extra_penalty_for_sprite_at_0 = 0; if (gb->object_fetch_aborted) { - gb->object_fetch_aborted = false; goto abort_fetching_object; } } } - gb->cycles_for_line += 6; - GB_SLEEP(gb, display, 20, 6); + gb->cycles_for_line += 5; + GB_SLEEP(gb, display, 20, 5); if (gb->object_fetch_aborted) { - gb->object_fetch_aborted = false; goto abort_fetching_object; } + gb->during_object_fetch = false; + gb->cycles_for_line++; + GB_SLEEP(gb, display, 39, 1); + /* TODO: what does the PPU read if DMA is active? */ const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; if (gb->oam_ppu_blocked) { @@ -973,6 +977,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } abort_fetching_object: + gb->object_fetch_aborted = false; + gb->during_object_fetch = false; + /* Handle window */ /* Todo: Timing (Including penalty and access timings) not verified by test ROM */ if (!gb->in_window && window_enabled(gb) && diff --git a/Core/gb.h b/Core/gb.h index 4e31e00d..11bd10ef 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -519,6 +519,7 @@ struct GB_gameboy_internal_s { bool vram_ppu_blocked; bool cgb_palettes_ppu_blocked; bool object_fetch_aborted; + bool during_object_fetch; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 1dbfc5ef..4caaa34a 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -19,6 +19,7 @@ typedef enum { GB_CONFLICT_PALETTE_DMG, GB_CONFLICT_PALETTE_CGB, GB_CONFLICT_DMG_LCDC, + GB_CONFLICT_SGB_LCDC, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -56,7 +57,7 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { static const GB_conflict_t sgb_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, - [GB_IO_LCDC] = GB_CONFLICT_READ_NEW, + [GB_IO_LCDC] = GB_CONFLICT_SGB_LCDC, [GB_IO_SCY] = GB_CONFLICT_READ_NEW, [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, @@ -207,16 +208,9 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t old_value = GB_read_memory(gb, addr); GB_advance_cycles(gb, gb->pending_cycles - 2); - if (gb->current_lcd_line == 108) { - - } - /* Handle disabling objects while already fetching an object */ if ((old_value & 2) && !(value & 2)) { - if (gb->display_state == 27) { - old_value &= ~2; - } - else if (gb->display_state == 20 || gb->display_state == 28) { + if (gb->during_object_fetch) { gb->cycles_for_line -= gb->display_cycles; gb->display_cycles = 0; gb->object_fetch_aborted = true; @@ -231,10 +225,34 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); /* Handle disabling objects while already fetching an object */ if ((old_value & 2) && !(value & 2)) { - if (gb->display_state == 27) { - old_value &= ~2; + if (gb->during_object_fetch) { + gb->cycles_for_line -= gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; } - else if (gb->display_state == 20 || gb->display_state == 28) { + } + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + } + + case GB_CONFLICT_SGB_LCDC: { + /* Simplified version of the above */ + + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles - 2); + /* Handle disabling objects while already fetching an object */ + if ((old_value & 2) && !(value & 2)) { + if (gb->during_object_fetch) { + gb->cycles_for_line -= gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; + } + } + GB_advance_cycles(gb, 1); + /* Handle disabling objects while already fetching an object */ + if ((old_value & 2) && !(value & 2)) { + if (gb->during_object_fetch) { gb->cycles_for_line -= gb->display_cycles; gb->display_cycles = 0; gb->object_fetch_aborted = true; From 8409d3bcfb345f0c510ec2975266b6e22c562a9e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 16:43:51 +0200 Subject: [PATCH 0974/1216] Emulate changing sprite height mid-fetch --- Core/display.c | 61 ++++++++++++++++++++++++++++++----------------- Core/gb.h | 1 + Core/save_state.c | 2 ++ 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/Core/display.c b/Core/display.c index 7523f6de..c79e8bd0 100644 --- a/Core/display.c +++ b/Core/display.c @@ -680,6 +680,30 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state &= 7; } +static uint16_t get_object_line_address(GB_gameboy_t *gb, const GB_object_t *object) +{ + /* TODO: what does the PPU read if DMA is active? */ + if (gb->oam_ppu_blocked) { + static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; + object = &blocked; + } + + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ + uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); + + if (object->flags & 0x40) { /* Flip Y */ + tile_y ^= height_16? 0xF : 7; + } + + /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ + uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; + + if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */ + line_address += 0x2000; + } + return line_address; +} + /* TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles. The PPU logic can be greatly simplified if that delay is simply emulated. @@ -736,6 +760,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 37); GB_STATE(gb, display, 38); GB_STATE(gb, display, 39); + GB_STATE(gb, display, 40); } @@ -931,42 +956,34 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } } - gb->cycles_for_line += 5; - GB_SLEEP(gb, display, 20, 5); + gb->cycles_for_line += 4; + GB_SLEEP(gb, display, 20, 4); if (gb->object_fetch_aborted) { goto abort_fetching_object; } - gb->during_object_fetch = false; + + gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]); + gb->cycles_for_line++; GB_SLEEP(gb, display, 39, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + + gb->during_object_fetch = false; + gb->cycles_for_line++; + GB_SLEEP(gb, display, 40, 1); - /* TODO: what does the PPU read if DMA is active? */ const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; - if (gb->oam_ppu_blocked) { - static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; - object = &blocked; - } - bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ - uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); - - if (object->flags & 0x40) { /* Flip Y */ - tile_y ^= height_16? 0xF : 7; - } - - /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ - uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; - - if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */ - line_address += 0x2000; - } + uint16_t line_address = get_object_line_address(gb, object); uint8_t palette = (object->flags & 0x10) ? 1 : 0; if (gb->cgb_mode) { palette = object->flags & 0x7; } fifo_overlay_object_row(&gb->oam_fifo, - gb->vram_ppu_blocked? 0xFF : gb->vram[line_address], + gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address], gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1], palette, object->flags & 0x80, diff --git a/Core/gb.h b/Core/gb.h index 11bd10ef..f44b9679 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -520,6 +520,7 @@ struct GB_gameboy_internal_s { bool cgb_palettes_ppu_blocked; bool object_fetch_aborted; bool during_object_fetch; + uint16_t object_low_line_address; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/save_state.c b/Core/save_state.c index 931a3190..a0d88c27 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -265,6 +265,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->bg_fifo.write_end &= 0xF; gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; + gb->object_low_line_address &= gb->vram_size & ~1; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; @@ -374,6 +375,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->bg_fifo.write_end &= 0xF; gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; + gb->object_low_line_address &= gb->vram_size & ~1; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; From f86e682d2c225b44d30e71b02485c8116bde93fb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 17:22:57 +0200 Subject: [PATCH 0975/1216] Fix sign --- Core/sm83_cpu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 4caaa34a..49115c7c 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -211,7 +211,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Handle disabling objects while already fetching an object */ if ((old_value & 2) && !(value & 2)) { if (gb->during_object_fetch) { - gb->cycles_for_line -= gb->display_cycles; + gb->cycles_for_line += gb->display_cycles; gb->display_cycles = 0; gb->object_fetch_aborted = true; } @@ -226,7 +226,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Handle disabling objects while already fetching an object */ if ((old_value & 2) && !(value & 2)) { if (gb->during_object_fetch) { - gb->cycles_for_line -= gb->display_cycles; + gb->cycles_for_line += gb->display_cycles; gb->display_cycles = 0; gb->object_fetch_aborted = true; } @@ -244,7 +244,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Handle disabling objects while already fetching an object */ if ((old_value & 2) && !(value & 2)) { if (gb->during_object_fetch) { - gb->cycles_for_line -= gb->display_cycles; + gb->cycles_for_line += gb->display_cycles; gb->display_cycles = 0; gb->object_fetch_aborted = true; } @@ -253,7 +253,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Handle disabling objects while already fetching an object */ if ((old_value & 2) && !(value & 2)) { if (gb->during_object_fetch) { - gb->cycles_for_line -= gb->display_cycles; + gb->cycles_for_line += gb->display_cycles; gb->display_cycles = 0; gb->object_fetch_aborted = true; } From ea2f32b255aa1a31abab207f824c72a6f6d20639 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 21:44:44 +0200 Subject: [PATCH 0976/1216] The fetcher state machine advances even while handling an object --- Core/display.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index c79e8bd0..13bd955f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -761,6 +761,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 38); GB_STATE(gb, display, 39); GB_STATE(gb, display, 40); + GB_STATE(gb, display, 41); } @@ -956,8 +957,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } } - gb->cycles_for_line += 4; - GB_SLEEP(gb, display, 20, 4); + advance_fetcher_state_machine(gb); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 41, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + advance_fetcher_state_machine(gb); + + gb->cycles_for_line += 3; + GB_SLEEP(gb, display, 20, 3); if (gb->object_fetch_aborted) { goto abort_fetching_object; } From 39b88d546ba4c180683e32367e47ab895a9ba192 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Feb 2020 21:59:03 +0200 Subject: [PATCH 0977/1216] The upper bits of SCX might mid-line --- Core/display.c | 6 ++++-- Core/save_state.c | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 13bd955f..62e2acd6 100644 --- a/Core/display.c +++ b/Core/display.c @@ -581,18 +581,20 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); + uint8_t x = gb->in_window? gb->fetcher_x : + (((gb->io_registers[GB_IO_SCX] / 8) + (gb->position_in_line / 8) + 1) & 0x1F); if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; } - gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; + gb->current_tile = gb->vram[map + x + y / 8 * 32]; if (gb->vram_ppu_blocked) { gb->current_tile = 0xFF; } if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. This probably means the CGB has a 16-bit data bus for the VRAM. */ - gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; + gb->current_tile_attributes = gb->vram[map + x + y / 8 * 32 + 0x2000]; if (gb->vram_ppu_blocked) { gb->current_tile_attributes = 0xFF; } diff --git a/Core/save_state.c b/Core/save_state.c index a0d88c27..8f101528 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -266,6 +266,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; gb->object_low_line_address &= gb->vram_size & ~1; + gb->fetcher_x &= 0x1f; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; @@ -376,6 +377,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; gb->object_low_line_address &= gb->vram_size & ~1; + gb->fetcher_x &= 0x1f; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; From d8282fe3c9d79533b4feefd3e39db189519c3784 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 22 Feb 2020 00:45:52 +0200 Subject: [PATCH 0978/1216] Please pretend the last commit never happened --- Core/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 62e2acd6..14071b11 100644 --- a/Core/display.c +++ b/Core/display.c @@ -582,7 +582,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); uint8_t x = gb->in_window? gb->fetcher_x : - (((gb->io_registers[GB_IO_SCX] / 8) + (gb->position_in_line / 8) + 1) & 0x1F); + (((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F); if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; @@ -914,7 +914,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; - gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f; + gb->fetcher_x = 0; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); From 83ea4edce295b467f9d00fb42acf7da29bd366eb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 23 Feb 2020 00:16:15 +0200 Subject: [PATCH 0979/1216] Shut up, annoying log message --- Cocoa/Document.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index df158066..0418c566 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1005,7 +1005,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) { CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); - CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; + CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast; CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; CGImageRef iref = CGImageCreate(width, From 2be58439bfba66e89655188cdac9aec9ed57ba54 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 23 Feb 2020 00:38:47 +0200 Subject: [PATCH 0980/1216] =?UTF-8?q?Starting=20over=20=E2=80=93=20removin?= =?UTF-8?q?g=20all=20window=20related=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 68 +++++--------------------------------------------- Core/display.h | 1 - Core/gb.h | 6 ++--- Core/memory.c | 5 +--- 4 files changed, 10 insertions(+), 70 deletions(-) diff --git a/Core/display.c b/Core/display.c index 14071b11..956bf804 100644 --- a/Core/display.c +++ b/Core/display.c @@ -111,16 +111,6 @@ typedef struct __attribute__((packed)) { uint8_t flags; } GB_object_t; -static bool window_enabled(GB_gameboy_t *gb) -{ - if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { - if (!gb->cgb_mode) { - return false; - } - } - return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167; -} - static void display_vblank(GB_gameboy_t *gb) { gb->vblank_just_occured = true; @@ -388,9 +378,6 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->vram_write_blocked = false; gb->cgb_palettes_blocked = false; - /* Reset window rendering state */ - gb->wy_diff = 0; - gb->window_disabled_while_active = false; gb->current_line = 0; gb->ly_for_comparison = 0; @@ -543,7 +530,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) static inline uint8_t fetcher_y(GB_gameboy_t *gb) { - return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]); + return gb->current_line + gb->io_registers[GB_IO_SCY]; } static void advance_fetcher_state_machine(GB_gameboy_t *gb) @@ -572,17 +559,16 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) uint16_t map = 0x1800; /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) { + if (gb->io_registers[GB_IO_LCDC] & 0x08 /* && !gb->in_window */) { map = 0x1C00; } - else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { + /* else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { map = 0x1C00; - } + } */ /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); - uint8_t x = gb->in_window? gb->fetcher_x : - (((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F); + uint8_t x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F; if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; @@ -922,7 +908,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_state = 0; gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; - gb->in_window = false; while (true) { /* Handle objects */ /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. @@ -1009,17 +994,7 @@ abort_fetching_object: gb->during_object_fetch = false; /* Handle window */ - /* Todo: Timing (Including penalty and access timings) not verified by test ROM */ - if (!gb->in_window && window_enabled(gb) && - gb->current_line >= gb->io_registers[GB_IO_WY] + gb->wy_diff && - (uint8_t)(gb->position_in_line + 7) == gb->io_registers[GB_IO_WX]) { - gb->in_window = true; - fifo_clear(&gb->bg_fifo); - gb->bg_fifo_paused = true; - gb->oam_fifo_paused = true; - gb->fetcher_x = 0; - gb->fetcher_state = 0; - } + /* TBD */ render_pixel_if_possible(gb); advance_fetcher_state_machine(gb); @@ -1154,9 +1129,6 @@ abort_fetching_object: GB_SLEEP(gb, display, 17, LINE_LENGTH - 24); - /* Reset window rendering state */ - gb->wy_diff = 0; - gb->window_disabled_while_active = false; gb->current_line = 0; // TODO: not the correct timing gb->current_lcd_line = 0; @@ -1343,31 +1315,3 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } return count; } - -/* Called when a write might enable or disable the window */ -void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value) -{ - bool before = window_enabled(gb); - gb->io_registers[addr] = value; - bool after = window_enabled(gb); - - if (before != after && gb->current_line < LINES) { - /* Window was disabled or enabled outside of vblank */ - if (gb->current_line >= gb->io_registers[GB_IO_WY]) { - if (after) { - if (!gb->window_disabled_while_active) { - /* Window was turned on for the first time this frame while LY > WY, - should start window in the next line */ - gb->wy_diff = gb->current_line + 1 - gb->io_registers[GB_IO_WY]; - } - else { - gb->wy_diff += gb->current_line; - } - } - else { - gb->wy_diff -= gb->current_line; - gb->window_disabled_while_active = true; - } - } - } -} diff --git a/Core/display.h b/Core/display.h index b9e31494..d5398817 100644 --- a/Core/display.h +++ b/Core/display.h @@ -8,7 +8,6 @@ #ifdef GB_INTERNAL void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); -void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value); void GB_STAT_update(GB_gameboy_t *gb); void GB_lcd_off(GB_gameboy_t *gb); diff --git a/Core/gb.h b/Core/gb.h index f44b9679..ccced990 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -472,7 +472,7 @@ struct GB_gameboy_internal_s { uint8_t position_in_line; bool stat_interrupt_line; uint8_t effective_scx; - uint8_t wy_diff; + GB_PADDING(uint8_t,wy_diff); /* The LCDC will skip the first frame it renders after turning it on. On the CGB, a frame is not skipped if the previous frame was skipped as well. See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ @@ -489,7 +489,7 @@ struct GB_gameboy_internal_s { bool vram_read_blocked; bool oam_write_blocked; bool vram_write_blocked; - bool window_disabled_while_active; + GB_PADDING(bool, window_disabled_while_active); uint8_t current_line; uint16_t ly_for_comparison; GB_fifo_t bg_fifo, oam_fifo; @@ -502,7 +502,7 @@ struct GB_gameboy_internal_s { uint8_t fetcher_state; bool bg_fifo_paused; bool oam_fifo_paused; - bool in_window; + GB_PADDING(bool, in_window); uint8_t visible_objs[10]; uint8_t obj_comparators[10]; uint8_t n_visible_objs; diff --git a/Core/memory.c b/Core/memory.c index 8e254bc1..e1dd2d14 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -646,8 +646,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Hardware registers */ switch (addr & 0xFF) { case GB_IO_WX: - GB_window_related_write(gb, addr & 0xFF, value); - break; case GB_IO_IF: case GB_IO_SCX: case GB_IO_SCY: @@ -740,8 +738,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_timing_sync(gb); GB_lcd_off(gb); } - /* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */ - GB_window_related_write(gb, addr & 0xFF, value); + gb->io_registers[GB_IO_LCDC] = value; return; case GB_IO_STAT: From c0ba898ef2ae7197796fa6b0aaf64a19b108fbfb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 23 Feb 2020 23:16:45 +0200 Subject: [PATCH 0981/1216] Basic window implementation --- Core/display.c | 78 ++++++++++++++++++++++++++++++++++++++++++++------ Core/gb.h | 6 ++-- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/Core/display.c b/Core/display.c index 956bf804..cfbcb716 100644 --- a/Core/display.c +++ b/Core/display.c @@ -530,7 +530,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) static inline uint8_t fetcher_y(GB_gameboy_t *gb) { - return gb->current_line + gb->io_registers[GB_IO_SCY]; + return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY]; } static void advance_fetcher_state_machine(GB_gameboy_t *gb) @@ -539,6 +539,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_GET_TILE, GB_FETCHER_GET_TILE_DATA_LOWER, GB_FETCHER_GET_TILE_DATA_HIGH, + GB_ADVANCE_TILES, GB_FETCHER_PUSH, GB_FETCHER_SLEEP, } fetcher_step_t; @@ -550,7 +551,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_GET_TILE_DATA_LOWER, GB_FETCHER_SLEEP, GB_FETCHER_GET_TILE_DATA_HIGH, - GB_FETCHER_SLEEP, + GB_ADVANCE_TILES, GB_FETCHER_PUSH, }; @@ -558,17 +559,27 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; + if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) { + gb->wx_triggered = false; + } + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & 0x08 /* && !gb->in_window */) { + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->wx_triggered) { map = 0x1C00; } - /* else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) { + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->wx_triggered) { map = 0x1C00; - } */ + } /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); - uint8_t x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F; + uint8_t x = 0; + if (gb->wx_triggered) { + x = gb->window_tile_x; + } + else { + x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F; + } if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; @@ -585,8 +596,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->current_tile_attributes = 0xFF; } } - gb->fetcher_x++; - gb->fetcher_x &= 0x1f; } gb->fetcher_state++; break; @@ -648,6 +657,19 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state++; break; + case GB_ADVANCE_TILES: { + if (gb->wx_triggered) { + gb->window_tile_x++; + gb->window_tile_x &= 0x1f; + } + else { + gb->fetcher_x++; + gb->fetcher_x &= 0x1f; + } + gb->fetcher_state++; + } + + case GB_FETCHER_PUSH: { if (fifo_size(&gb->bg_fifo) > 0) break; fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], @@ -767,6 +789,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Handle mode 2 on the very first line 0 */ gb->current_line = 0; + gb->window_y = -1; + /* Todo: verify timings */ + if (gb->io_registers[GB_IO_WY] == 0) { + gb->wy_triggered = true; + } + else { + gb->wy_triggered = false; + } + gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; gb->mode_for_interrupt = -1; @@ -813,7 +844,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Lines 0 - 143 */ + gb->window_y = -1; for (; gb->current_line < LINES; gb->current_line++) { + gb->wx_triggered = false; + /* Todo: verify timings */ + if ((gb->io_registers[GB_IO_WY] == gb->current_line || + (gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) { + gb->wy_triggered = true; + } + gb->window_tile_x = 0; + gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed; gb->accessed_oam_row = 0; @@ -994,7 +1034,18 @@ abort_fetching_object: gb->during_object_fetch = false; /* Handle window */ - /* TBD */ + /* Todo: verify timings */ + if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { + if (gb->io_registers[GB_IO_WX] == gb->position_in_line + 7 || + gb->io_registers[GB_IO_WX] == gb->position_in_line + 6) { + gb->wx_triggered = true; + gb->window_y++; + fifo_clear(&gb->bg_fifo); + gb->bg_fifo_paused = true; + gb->oam_fifo_paused = true; + gb->fetcher_state = 0; + } + } render_pixel_if_possible(gb); advance_fetcher_state_machine(gb); @@ -1130,6 +1181,15 @@ abort_fetching_object: gb->current_line = 0; + /* Todo: verify timings */ + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + (gb->io_registers[GB_IO_WY] == 0)) { + gb->wy_triggered = true; + } + else { + gb->wy_triggered = false; + } + // TODO: not the correct timing gb->current_lcd_line = 0; if (gb->icd_vreset_callback) { diff --git a/Core/gb.h b/Core/gb.h index ccced990..e02c7ad5 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -472,7 +472,7 @@ struct GB_gameboy_internal_s { uint8_t position_in_line; bool stat_interrupt_line; uint8_t effective_scx; - GB_PADDING(uint8_t,wy_diff); + uint8_t window_y; /* The LCDC will skip the first frame it renders after turning it on. On the CGB, a frame is not skipped if the previous frame was skipped as well. See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ @@ -502,7 +502,7 @@ struct GB_gameboy_internal_s { uint8_t fetcher_state; bool bg_fifo_paused; bool oam_fifo_paused; - GB_PADDING(bool, in_window); + bool wx_triggered; uint8_t visible_objs[10]; uint8_t obj_comparators[10]; uint8_t n_visible_objs; @@ -521,6 +521,8 @@ struct GB_gameboy_internal_s { bool object_fetch_aborted; bool during_object_fetch; uint16_t object_low_line_address; + bool wy_triggered; + uint8_t window_tile_x; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From c22611c7010caf470156a0d1494c7dc454e4b346 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 23 Feb 2020 23:48:08 +0200 Subject: [PATCH 0982/1216] Minor bugfix --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index cfbcb716..6ecbf95c 100644 --- a/Core/display.c +++ b/Core/display.c @@ -839,8 +839,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = true; gb->vram_write_blocked = true; + gb->wx_triggered = false; goto mode_3_start; - while (true) { /* Lines 0 - 143 */ From 3864ff37e100041e7787920012a286f7509557b6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 24 Feb 2020 00:20:58 +0200 Subject: [PATCH 0983/1216] Timing improvements --- Core/display.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Core/display.c b/Core/display.c index 6ecbf95c..018b5814 100644 --- a/Core/display.c +++ b/Core/display.c @@ -539,7 +539,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_GET_TILE, GB_FETCHER_GET_TILE_DATA_LOWER, GB_FETCHER_GET_TILE_DATA_HIGH, - GB_ADVANCE_TILES, GB_FETCHER_PUSH, GB_FETCHER_SLEEP, } fetcher_step_t; @@ -551,8 +550,8 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_GET_TILE_DATA_LOWER, GB_FETCHER_SLEEP, GB_FETCHER_GET_TILE_DATA_HIGH, - GB_ADVANCE_TILES, GB_FETCHER_PUSH, + GB_FETCHER_PUSH, // Compatibility }; switch (fetcher_state_machine[gb->fetcher_state]) { @@ -657,7 +656,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state++; break; - case GB_ADVANCE_TILES: { + + case GB_FETCHER_PUSH: { + if (fifo_size(&gb->bg_fifo) > 0) break; + if (gb->wx_triggered) { gb->window_tile_x++; gb->window_tile_x &= 0x1f; @@ -666,17 +668,12 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_x++; gb->fetcher_x &= 0x1f; } - gb->fetcher_state++; - } - - case GB_FETCHER_PUSH: { - if (fifo_size(&gb->bg_fifo) > 0) break; fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; - gb->fetcher_state++; + gb->fetcher_state = 0; } break; @@ -984,12 +981,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } } + /* TODO: Can this be deleted? { */ advance_fetcher_state_machine(gb); gb->cycles_for_line++; GB_SLEEP(gb, display, 41, 1); if (gb->object_fetch_aborted) { goto abort_fetching_object; } + /* } */ + advance_fetcher_state_machine(gb); gb->cycles_for_line += 3; @@ -1036,8 +1036,11 @@ abort_fetching_object: /* Handle window */ /* Todo: verify timings */ if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { - if (gb->io_registers[GB_IO_WX] == gb->position_in_line + 7 || - gb->io_registers[GB_IO_WX] == gb->position_in_line + 6) { + if (gb->io_registers[GB_IO_WX] >= 166) { + // Too late to enable the window + } + else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || + gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6)) { gb->wx_triggered = true; gb->window_y++; fifo_clear(&gb->bg_fifo); From 25b51362e9fcfd1b70615051cea05541be750e74 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 24 Feb 2020 00:33:45 +0200 Subject: [PATCH 0984/1216] Safety first --- Core/save_state.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/save_state.c b/Core/save_state.c index 8f101528..fd7d814e 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -267,6 +267,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->oam_fifo.write_end &= 0xF; gb->object_low_line_address &= gb->vram_size & ~1; gb->fetcher_x &= 0x1f; + gb->fetcher_state &= 7; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; @@ -378,6 +379,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->oam_fifo.write_end &= 0xF; gb->object_low_line_address &= gb->vram_size & ~1; gb->fetcher_x &= 0x1f; + gb->fetcher_state &= 7; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; From 248e7bc332eb3799d082e3c41f94a982009a6dec Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 24 Feb 2020 20:46:00 +0200 Subject: [PATCH 0985/1216] Timing improvements --- Core/display.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 018b5814..b4d9f835 100644 --- a/Core/display.c +++ b/Core/display.c @@ -831,8 +831,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 37, 2); gb->cgb_palettes_blocked = true; - gb->cycles_for_line += 2; - GB_SLEEP(gb, display, 38, 2); + gb->cycles_for_line += 3; + GB_SLEEP(gb, display, 38, 3); gb->vram_read_blocked = true; gb->vram_write_blocked = true; @@ -1046,7 +1046,7 @@ abort_fetching_object: fifo_clear(&gb->bg_fifo); gb->bg_fifo_paused = true; gb->oam_fifo_paused = true; - gb->fetcher_state = 0; + gb->fetcher_state = 1; } } From 7456beb7b9ee08465ac13400d73aebbc408ce413 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 24 Feb 2020 21:23:06 +0200 Subject: [PATCH 0986/1216] Better emulation of negative WX positions --- Core/display.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index b4d9f835..2161328f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -434,12 +434,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } } + if (gb->bg_fifo_paused) return; + /* Drop pixels for scrollings */ if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { gb->position_in_line++; return; } - if (gb->bg_fifo_paused) return; /* Mixing */ From b37a0b285a4c31013df7d0f1990a97391687ae0f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 24 Feb 2020 23:59:18 +0200 Subject: [PATCH 0987/1216] Window Y still advances if WX=166 --- Core/display.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Core/display.c b/Core/display.c index 2161328f..c9ca8ce8 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1037,17 +1037,19 @@ abort_fetching_object: /* Handle window */ /* Todo: verify timings */ if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { - if (gb->io_registers[GB_IO_WX] >= 166) { + if (gb->io_registers[GB_IO_WX] >= 167) { // Too late to enable the window } else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6)) { - gb->wx_triggered = true; + gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6)) { gb->window_y++; - fifo_clear(&gb->bg_fifo); - gb->bg_fifo_paused = true; - gb->oam_fifo_paused = true; - gb->fetcher_state = 1; + if (gb->io_registers[GB_IO_WX] != 166) { + gb->wx_triggered = true; + fifo_clear(&gb->bg_fifo); + gb->bg_fifo_paused = true; + gb->oam_fifo_paused = true; + gb->fetcher_state = 1; + } } } From 9c7a8fdb1bf2e2fdf09aa2f0831ff406107ff271 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 26 Feb 2020 22:24:08 +0200 Subject: [PATCH 0988/1216] WY is tested every cycle --- Core/display.c | 1 + Core/memory.c | 5 ++++- Core/sm83_cpu.c | 9 ++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index c9ca8ce8..908c5518 100644 --- a/Core/display.c +++ b/Core/display.c @@ -382,6 +382,7 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->ly_for_comparison = 0; gb->accessed_oam_row = -1; + gb->wy_triggered = false; } static void add_object_from_index(GB_gameboy_t *gb, unsigned index) diff --git a/Core/memory.c b/Core/memory.c index e1dd2d14..631b71f2 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -645,6 +645,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (addr < 0xFF80) { /* Hardware registers */ switch (addr & 0xFF) { + case GB_IO_WY: + if (value == gb->current_line) { + gb->wy_triggered = true; + } case GB_IO_WX: case GB_IO_IF: case GB_IO_SCX: @@ -652,7 +656,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_BGP: case GB_IO_OBP0: case GB_IO_OBP1: - case GB_IO_WY: case GB_IO_SB: case GB_IO_UNKNOWN2: case GB_IO_UNKNOWN3: diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 49115c7c..b0e28f48 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -31,7 +31,6 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB, [GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB, - /* Todo: most values not verified, and probably differ between revisions */ }; @@ -46,9 +45,9 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG, [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG, [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG, - + [GB_IO_WY] = GB_CONFLICT_READ_OLD, + /* Todo: these were not verified at all */ - [GB_IO_WY] = GB_CONFLICT_READ_NEW, [GB_IO_WX] = GB_CONFLICT_READ_NEW, [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; @@ -64,9 +63,9 @@ static const GB_conflict_t sgb_conflict_map[0x80] = { [GB_IO_BGP] = GB_CONFLICT_READ_NEW, [GB_IO_OBP0] = GB_CONFLICT_READ_NEW, [GB_IO_OBP1] = GB_CONFLICT_READ_NEW, - + [GB_IO_WY] = GB_CONFLICT_READ_OLD, + /* Todo: these were not verified at all */ - [GB_IO_WY] = GB_CONFLICT_READ_NEW, [GB_IO_WX] = GB_CONFLICT_READ_NEW, [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; From 89303ab04652fe147559772ed2b54016e08f1655 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 27 Feb 2020 00:12:42 +0200 Subject: [PATCH 0989/1216] WX access conflicts --- Core/display.c | 2 +- Core/gb.h | 3 +++ Core/sm83_cpu.c | 14 ++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index 908c5518..256d9814 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1042,7 +1042,7 @@ abort_fetching_object: // Too late to enable the window } else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6)) { + (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) { gb->window_y++; if (gb->io_registers[GB_IO_WX] != 166) { gb->wx_triggered = true; diff --git a/Core/gb.h b/Core/gb.h index e02c7ad5..b8cbd978 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -651,6 +651,9 @@ struct GB_gameboy_internal_s { bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units double clock_multiplier; + + /* Temporary state */ + bool wx_just_changed; ); }; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index b0e28f48..f25e14bd 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -20,6 +20,7 @@ typedef enum { GB_CONFLICT_PALETTE_CGB, GB_CONFLICT_DMG_LCDC, GB_CONFLICT_SGB_LCDC, + GB_CONFLICT_WX, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -46,9 +47,9 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG, [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG, [GB_IO_WY] = GB_CONFLICT_READ_OLD, + [GB_IO_WX] = GB_CONFLICT_WX, /* Todo: these were not verified at all */ - [GB_IO_WX] = GB_CONFLICT_READ_NEW, [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; @@ -64,9 +65,9 @@ static const GB_conflict_t sgb_conflict_map[0x80] = { [GB_IO_OBP0] = GB_CONFLICT_READ_NEW, [GB_IO_OBP1] = GB_CONFLICT_READ_NEW, [GB_IO_WY] = GB_CONFLICT_READ_OLD, + [GB_IO_WX] = GB_CONFLICT_WX, /* Todo: these were not verified at all */ - [GB_IO_WX] = GB_CONFLICT_READ_NEW, [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; @@ -261,6 +262,15 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->pending_cycles = 5; return; } + + case GB_CONFLICT_WX: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->wx_just_changed = true; + GB_advance_cycles(gb, 1); + gb->wx_just_changed = false; + gb->pending_cycles = 3; + return; } } From 67d5a53503eafae61f4639bf7d4cfb24499f496e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Thu, 27 Feb 2020 18:11:10 +0100 Subject: [PATCH 0990/1216] Spell "length" properly --- Core/apu.c | 20 ++++++++++---------- Core/apu.h | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 17aa4528..c3be5338 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -230,13 +230,13 @@ static void render(GB_gameboy_t *gb) gb->apu_output.sample_callback(gb, &filtered_output); } -static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb) +static uint16_t new_sweep_sample_length(GB_gameboy_t *gb) { - uint16_t delta = gb->apu.shadow_sweep_sample_legnth >> (gb->io_registers[GB_IO_NR10] & 7); + uint16_t delta = gb->apu.shadow_sweep_sample_length >> (gb->io_registers[GB_IO_NR10] & 7); if (gb->io_registers[GB_IO_NR10] & 8) { - return gb->apu.shadow_sweep_sample_legnth - delta; + return gb->apu.shadow_sweep_sample_length - delta; } - return gb->apu.shadow_sweep_sample_legnth + delta; + return gb->apu.shadow_sweep_sample_length + delta; } static void update_square_sample(GB_gameboy_t *gb, unsigned index) @@ -400,8 +400,8 @@ void GB_apu_div_event(GB_gameboy_t *gb) if (!--gb->apu.square_sweep_countdown) { if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) { gb->apu.square_channels[GB_SQUARE_1].sample_length = - gb->apu.shadow_sweep_sample_legnth = - gb->apu.new_sweep_sample_legnth; + gb->apu.shadow_sweep_sample_length = + gb->apu.new_sweep_sample_length; } if (gb->io_registers[GB_IO_NR10] & 0x70) { @@ -435,8 +435,8 @@ void GB_apu_run(GB_gameboy_t *gb) } else { /* APU bug: sweep frequency is checked after adding the sweep delta twice */ - gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb); - if (gb->apu.new_sweep_sample_legnth > 0x7ff) { + gb->apu.new_sweep_sample_length = new_sweep_sample_length(gb); + if (gb->apu.new_sweep_sample_length > 0x7ff) { gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); gb->apu.sweep_enabled = false; @@ -724,8 +724,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].sample_length &= 0xFF; gb->apu.square_channels[index].sample_length |= (value & 7) << 8; if (index == GB_SQUARE_1) { - gb->apu.shadow_sweep_sample_legnth = - gb->apu.new_sweep_sample_legnth = + gb->apu.shadow_sweep_sample_length = + gb->apu.new_sweep_sample_length = gb->apu.square_channels[0].sample_length; } if (value & 0x80) { diff --git a/Core/apu.h b/Core/apu.h index 933f14e8..398b903a 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -64,8 +64,8 @@ typedef struct uint8_t square_sweep_countdown; // In 128Hz uint8_t square_sweep_calculate_countdown; // In 2 MHz - uint16_t new_sweep_sample_legnth; - uint16_t shadow_sweep_sample_legnth; + uint16_t new_sweep_sample_length; + uint16_t shadow_sweep_sample_length; bool sweep_enabled; bool sweep_decreasing; From 0c716bd970540e57c35868eec6e33b2ad96e3ee6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 27 Feb 2020 22:49:34 +0200 Subject: [PATCH 0991/1216] More accurate timing emulation of window-objects interaction --- Core/display.c | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/Core/display.c b/Core/display.c index 256d9814..b4455fd4 100644 --- a/Core/display.c +++ b/Core/display.c @@ -729,6 +729,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) return; } GB_object_t *objects = (GB_object_t *) &gb->oam; + bool window_got_activated = false; GB_STATE_MACHINE(gb, display, cycles, 2) { GB_STATE(gb, display, 1); @@ -771,6 +772,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 39); GB_STATE(gb, display, 40); GB_STATE(gb, display, 41); + GB_STATE(gb, display, 42); } @@ -948,6 +950,26 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->bg_fifo_paused = false; gb->oam_fifo_paused = false; while (true) { + /* Handle window */ + /* Todo: verify timings */ + if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { + if (gb->io_registers[GB_IO_WX] >= 167) { + // Too late to enable the window + } + else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || + (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) { + gb->window_y++; + if (gb->io_registers[GB_IO_WX] != 166) { + gb->wx_triggered = true; + fifo_clear(&gb->bg_fifo); + gb->bg_fifo_paused = true; + gb->oam_fifo_paused = true; + gb->fetcher_state = 1; + window_got_activated = true; + } + } + } + /* Handle objects */ /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ @@ -962,6 +984,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (gb->n_visible_objs != 0 && (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { + + /* TODO: This is wrong. It is only correct for a single object, not for more than one. */ + if (window_got_activated) { + window_got_activated = false; + gb->fetcher_state = 0; + gb->cycles_for_line += 6; + GB_SLEEP(gb, display, 42, 6); + } + while (gb->fetcher_state < 5) { advance_fetcher_state_machine(gb); gb->cycles_for_line++; @@ -1035,25 +1066,6 @@ abort_fetching_object: gb->object_fetch_aborted = false; gb->during_object_fetch = false; - /* Handle window */ - /* Todo: verify timings */ - if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { - if (gb->io_registers[GB_IO_WX] >= 167) { - // Too late to enable the window - } - else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) { - gb->window_y++; - if (gb->io_registers[GB_IO_WX] != 166) { - gb->wx_triggered = true; - fifo_clear(&gb->bg_fifo); - gb->bg_fifo_paused = true; - gb->oam_fifo_paused = true; - gb->fetcher_state = 1; - } - } - } - render_pixel_if_possible(gb); advance_fetcher_state_machine(gb); From 40868df7595af9d5e468307337a0e1f688965997 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 28 Feb 2020 14:05:29 +0200 Subject: [PATCH 0992/1216] Fix this bug again --- Cocoa/Document.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 0418c566..03d4acd8 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -309,16 +309,17 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { [audioLock lock]; - if (stopping) { - memset(buffer, 0, nFrames * sizeof(*buffer)); - [audioLock unlock]; - } if (audioBufferPosition < nFrames) { audioBufferNeeded = nFrames; [audioLock wait]; } + if (stopping) { + memset(buffer, 0, nFrames * sizeof(*buffer)); + [audioLock unlock]; + } + if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) { memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer)); memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer)); @@ -376,6 +377,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } [audioLock lock]; stopping = true; + [audioLock signal]; [audioLock unlock]; running = false; while (stopping); From 2a8f15c68be9b159d8536958ed0830fe58355189 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 28 Feb 2020 18:10:09 +0200 Subject: [PATCH 0993/1216] The fetcher pushes pixels to the FIFO as soon as it's empty --- Core/display.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/display.c b/Core/display.c index b4455fd4..d6acaf32 100644 --- a/Core/display.c +++ b/Core/display.c @@ -656,7 +656,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) } } gb->fetcher_state++; - break; + // fallthrough case GB_FETCHER_PUSH: { @@ -964,7 +964,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_clear(&gb->bg_fifo); gb->bg_fifo_paused = true; gb->oam_fifo_paused = true; - gb->fetcher_state = 1; + gb->fetcher_state = 0; window_got_activated = true; } } @@ -988,7 +988,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* TODO: This is wrong. It is only correct for a single object, not for more than one. */ if (window_got_activated) { window_got_activated = false; - gb->fetcher_state = 0; gb->cycles_for_line += 6; GB_SLEEP(gb, display, 42, 6); } From e29246fd914b340b2408ae85059c61293c76f7fd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 28 Feb 2020 18:28:47 +0200 Subject: [PATCH 0994/1216] Window tile is reset on WX trigger --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index d6acaf32..9aaf40bf 100644 --- a/Core/display.c +++ b/Core/display.c @@ -853,7 +853,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) (gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) { gb->wy_triggered = true; } - gb->window_tile_x = 0; gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed; gb->accessed_oam_row = 0; @@ -961,6 +960,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->window_y++; if (gb->io_registers[GB_IO_WX] != 166) { gb->wx_triggered = true; + gb->window_tile_x = 0; fifo_clear(&gb->bg_fifo); gb->bg_fifo_paused = true; gb->oam_fifo_paused = true; From 955860b4631d4dac7393c0317b6fd22d42fad391 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 28 Feb 2020 22:36:51 +0200 Subject: [PATCH 0995/1216] Get rid of the FIFO pause flags --- Core/display.c | 26 ++++++++++---------------- Core/gb.h | 6 +++--- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Core/display.c b/Core/display.c index 9aaf40bf..85fc858b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -422,20 +422,21 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bool draw_oam = false; bool bg_enabled = true, bg_priority = false; - if (!gb->bg_fifo_paused) { + if (fifo_size(&gb->bg_fifo)) { fifo_item = fifo_pop(&gb->bg_fifo); bg_priority = fifo_item->bg_priority; - } - - if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) { - oam_fifo_item = fifo_pop(&gb->oam_fifo); - if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { - draw_oam = true; - bg_priority |= oam_fifo_item->bg_priority; + + if (fifo_size(&gb->oam_fifo)) { + oam_fifo_item = fifo_pop(&gb->oam_fifo); + if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { + draw_oam = true; + bg_priority |= oam_fifo_item->bg_priority; + } } } - if (gb->bg_fifo_paused) return; + + if (!fifo_item) return; /* Drop pixels for scrollings */ if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { @@ -658,7 +659,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state++; // fallthrough - case GB_FETCHER_PUSH: { if (fifo_size(&gb->bg_fifo) > 0) break; @@ -673,8 +673,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); - gb->bg_fifo_paused = false; - gb->oam_fifo_paused = false; gb->fetcher_state = 0; } break; @@ -946,8 +944,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The actual rendering cycle */ gb->fetcher_state = 0; - gb->bg_fifo_paused = false; - gb->oam_fifo_paused = false; while (true) { /* Handle window */ /* Todo: verify timings */ @@ -962,8 +958,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->wx_triggered = true; gb->window_tile_x = 0; fifo_clear(&gb->bg_fifo); - gb->bg_fifo_paused = true; - gb->oam_fifo_paused = true; gb->fetcher_state = 0; window_got_activated = true; } diff --git a/Core/gb.h b/Core/gb.h index b8cbd978..65cfb4c9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -489,7 +489,7 @@ struct GB_gameboy_internal_s { bool vram_read_blocked; bool oam_write_blocked; bool vram_write_blocked; - GB_PADDING(bool, window_disabled_while_active); + bool fifo_insertion_glitch; uint8_t current_line; uint16_t ly_for_comparison; GB_fifo_t bg_fifo, oam_fifo; @@ -500,8 +500,8 @@ struct GB_gameboy_internal_s { uint8_t current_tile_attributes; uint8_t current_tile_data[2]; uint8_t fetcher_state; - bool bg_fifo_paused; - bool oam_fifo_paused; + GB_PADDING(bool,bg_fifo_paused); + GB_PADDING(bool,oam_fifo_paused); bool wx_triggered; uint8_t visible_objs[10]; uint8_t obj_comparators[10]; From 39b999a68b1b7e34d03efebe0d6bbb931c84de22 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 29 Feb 2020 17:06:08 +0200 Subject: [PATCH 0996/1216] Emulate the FIFO insertion glitch (WX variant) --- Core/display.c | 11 +++++++++++ Core/gb.h | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 85fc858b..e01c7896 100644 --- a/Core/display.c +++ b/Core/display.c @@ -524,6 +524,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } gb->position_in_line++; + gb->window_is_being_fetched = false; } /* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have @@ -960,9 +961,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_clear(&gb->bg_fifo); gb->fetcher_state = 0; window_got_activated = true; + gb->window_is_being_fetched = true; } } } + + if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched && + gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) ) { + // Insert a pixel right at the FIFO's end + gb->bg_fifo.read_end--; + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,}; + gb->window_is_being_fetched = false; + } /* Handle objects */ /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. diff --git a/Core/gb.h b/Core/gb.h index 65cfb4c9..15c6442e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -500,7 +500,7 @@ struct GB_gameboy_internal_s { uint8_t current_tile_attributes; uint8_t current_tile_data[2]; uint8_t fetcher_state; - GB_PADDING(bool,bg_fifo_paused); + bool window_is_being_fetched; GB_PADDING(bool,oam_fifo_paused); bool wx_triggered; uint8_t visible_objs[10]; From 5ca602fbd2bb8c76d4635082ab97a4bcd2ad169d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 29 Feb 2020 18:26:16 +0200 Subject: [PATCH 0997/1216] WX=0 emulation --- Core/display.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index e01c7896..32501031 100644 --- a/Core/display.c +++ b/Core/display.c @@ -772,7 +772,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 40); GB_STATE(gb, display, 41); GB_STATE(gb, display, 42); - + GB_STATE(gb, display, 43); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -948,14 +948,21 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { /* Handle window */ /* Todo: verify timings */ + static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14}; if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { if (gb->io_registers[GB_IO_WX] >= 167) { // Too late to enable the window } else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) { + (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) || + (gb->io_registers[GB_IO_WX] == 0 && (gb->position_in_line) == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7])) { gb->window_y++; if (gb->io_registers[GB_IO_WX] != 166) { + /* TODO: Verify fetcher access timings in this case */ + if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 43, 1); + } gb->wx_triggered = true; gb->window_tile_x = 0; fifo_clear(&gb->bg_fifo); From b7194402eb531fdea0fe83eaabc1c814337c3057 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 1 Mar 2020 00:17:45 +0200 Subject: [PATCH 0998/1216] Accurately emulate Window X = Object X --- Core/display.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index 32501031..33106859 100644 --- a/Core/display.c +++ b/Core/display.c @@ -728,7 +728,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) return; } GB_object_t *objects = (GB_object_t *) &gb->oam; - bool window_got_activated = false; GB_STATE_MACHINE(gb, display, cycles, 2) { GB_STATE(gb, display, 1); @@ -772,7 +771,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 40); GB_STATE(gb, display, 41); GB_STATE(gb, display, 42); - GB_STATE(gb, display, 43); } if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { @@ -961,13 +959,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* TODO: Verify fetcher access timings in this case */ if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { gb->cycles_for_line++; - GB_SLEEP(gb, display, 43, 1); + GB_SLEEP(gb, display, 42, 1); } gb->wx_triggered = true; gb->window_tile_x = 0; fifo_clear(&gb->bg_fifo); gb->fetcher_state = 0; - window_got_activated = true; gb->window_is_being_fetched = true; } } @@ -997,14 +994,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { - /* TODO: This is wrong. It is only correct for a single object, not for more than one. */ - if (window_got_activated) { - window_got_activated = false; - gb->cycles_for_line += 6; - GB_SLEEP(gb, display, 42, 6); - } - - while (gb->fetcher_state < 5) { + while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) { advance_fetcher_state_machine(gb); gb->cycles_for_line++; GB_SLEEP(gb, display, 27, 1); From 2a8b26d5e6210bb8c956a25b288344342f5324ca Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 1 Mar 2020 00:23:50 +0200 Subject: [PATCH 0999/1216] Add TODO --- Core/display.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/display.c b/Core/display.c index 33106859..fef764d8 100644 --- a/Core/display.c +++ b/Core/display.c @@ -970,6 +970,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } } + /* TODO: What happens when WX=0? */ if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched && gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) ) { // Insert a pixel right at the FIFO's end From e846f4f3b0ab2836e5f16501d547d7fc5e9aea53 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 1 Mar 2020 23:58:28 +0200 Subject: [PATCH 1000/1216] Hacky, but correct emulation of WX=166 --- Core/display.c | 72 +++++++++++++++++++++++++++++++++++--------------- Core/gb.h | 2 +- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/Core/display.c b/Core/display.c index fef764d8..ce9f74f4 100644 --- a/Core/display.c +++ b/Core/display.c @@ -564,6 +564,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) { gb->wx_triggered = false; + gb->wx166_glitch = false; } /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ @@ -838,13 +839,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_read_blocked = true; gb->vram_write_blocked = true; gb->wx_triggered = false; + gb->wx166_glitch = false; goto mode_3_start; while (true) { /* Lines 0 - 143 */ gb->window_y = -1; for (; gb->current_line < LINES; gb->current_line++) { - gb->wx_triggered = false; /* Todo: verify timings */ if ((gb->io_registers[GB_IO_WY] == gb->current_line || (gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) { @@ -945,29 +946,48 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_state = 0; while (true) { /* Handle window */ - /* Todo: verify timings */ - static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14}; + /* TODO: It appears that WX checks if the window beings *next* pixel, not *this* pixel. For this reason, + WX=167 has no effect at all (It checks if the PPU X position is 161, which never happens) and WX=166 + has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at + that point. The code should be updated to represent this, and this will fix the time travel hack in + WX's access conflict code. */ + if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { - if (gb->io_registers[GB_IO_WX] >= 167) { - // Too late to enable the window - } - else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) || - (gb->io_registers[GB_IO_WX] == 0 && (gb->position_in_line) == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7])) { - gb->window_y++; - if (gb->io_registers[GB_IO_WX] != 166) { - /* TODO: Verify fetcher access timings in this case */ - if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { - gb->cycles_for_line++; - GB_SLEEP(gb, display, 42, 1); - } - gb->wx_triggered = true; - gb->window_tile_x = 0; - fifo_clear(&gb->bg_fifo); - gb->fetcher_state = 0; - gb->window_is_being_fetched = true; + bool should_activate_window = false; + if (gb->io_registers[GB_IO_WX] == 0) { + static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14}; + if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + should_activate_window = true; } } + else if (gb->wx166_glitch) { + static const uint8_t scx_to_wx166_comparisons[] = {-8, -9, -10, -11, -12, -13, -14, -15}; + if (gb->position_in_line == scx_to_wx166_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + should_activate_window = true; + } + } + else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { + if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || + (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) + should_activate_window = true; + } + + if (should_activate_window) { + gb->window_y++; + /* TODO: Verify fetcher access timings in this case */ + if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 42, 1); + } + gb->wx_triggered = true; + gb->window_tile_x = 0; + fifo_clear(&gb->bg_fifo); + gb->fetcher_state = 0; + gb->window_is_being_fetched = true; + } + else if (!GB_is_cgb(gb) && gb->io_registers[GB_IO_WX] == 166 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { + gb->window_y++; + } } /* TODO: What happens when WX=0? */ @@ -1075,6 +1095,14 @@ abort_fetching_object: gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); } + /* TODO: Verify timing */ + if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) { + gb->wx166_glitch = true; + } + else { + gb->wx166_glitch = false; + } + gb->wx_triggered = false; if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { gb->cycles_for_line++; @@ -1129,7 +1157,7 @@ abort_fetching_object: gb->icd_hreset_callback(gb); } } - + gb->wx166_glitch = false; /* Lines 144 - 152 */ for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { gb->io_registers[GB_IO_LY] = gb->current_line; diff --git a/Core/gb.h b/Core/gb.h index 15c6442e..db99608f 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -501,7 +501,7 @@ struct GB_gameboy_internal_s { uint8_t current_tile_data[2]; uint8_t fetcher_state; bool window_is_being_fetched; - GB_PADDING(bool,oam_fifo_paused); + bool wx166_glitch; bool wx_triggered; uint8_t visible_objs[10]; uint8_t obj_comparators[10]; From 409ab2a6d4ae64a3211a568acdb9a7f42a249fcd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 3 Mar 2020 02:21:19 +0200 Subject: [PATCH 1001/1216] Accurate emulation of tilemap advancement timings --- Core/display.c | 34 ++++++++++++++++++++-------------- Core/memory.c | 4 ++++ Core/save_state.c | 2 -- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Core/display.c b/Core/display.c index ce9f74f4..cbf41963 100644 --- a/Core/display.c +++ b/Core/display.c @@ -547,7 +547,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_SLEEP, } fetcher_step_t; - fetcher_step_t fetcher_state_machine [8] = { + fetcher_step_t fetcher_state_machine [9] = { GB_FETCHER_SLEEP, GB_FETCHER_GET_TILE, GB_FETCHER_SLEEP, @@ -555,9 +555,13 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_SLEEP, GB_FETCHER_GET_TILE_DATA_HIGH, GB_FETCHER_PUSH, - GB_FETCHER_PUSH, // Compatibility + GB_FETCHER_PUSH, + GB_FETCHER_PUSH, }; - + + if (gb->fetcher_state >= sizeof(fetcher_state_machine)) { + gb->fetcher_state = 0; + } switch (fetcher_state_machine[gb->fetcher_state]) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; @@ -659,19 +663,23 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) } } gb->fetcher_state++; + if (gb->wx_triggered) { + gb->window_tile_x++; + gb->window_tile_x &= 0x1f; + } + // fallthrough - case GB_FETCHER_PUSH: { - if (fifo_size(&gb->bg_fifo) > 0) break; - - if (gb->wx_triggered) { - gb->window_tile_x++; - gb->window_tile_x &= 0x1f; - } - else { + if (gb->fetcher_state == 7) { + /* The background map index increase at this specific point. If this state is not reached, + it will simply not increase. */ gb->fetcher_x++; gb->fetcher_x &= 0x1f; } + if (gb->fetcher_state < 8) { + gb->fetcher_state++; + } + if (fifo_size(&gb->bg_fifo) > 0) break; fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); @@ -685,8 +693,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) } break; } - - gb->fetcher_state &= 7; } static uint16_t get_object_line_address(GB_gameboy_t *gb, const GB_object_t *object) @@ -946,7 +952,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->fetcher_state = 0; while (true) { /* Handle window */ - /* TODO: It appears that WX checks if the window beings *next* pixel, not *this* pixel. For this reason, + /* TODO: It appears that WX checks if the window begins *next* pixel, not *this* pixel. For this reason, WX=167 has no effect at all (It checks if the PPU X position is 161, which never happens) and WX=166 has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at that point. The code should be updated to represent this, and this will fix the time travel hack in diff --git a/Core/memory.c b/Core/memory.c index 631b71f2..abad063e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -742,6 +742,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_lcd_off(gb); } gb->io_registers[GB_IO_LCDC] = value; + if (!(value & 0x20)) { + gb->wx_triggered = false; + gb->wx166_glitch = false; + } return; case GB_IO_STAT: diff --git a/Core/save_state.c b/Core/save_state.c index fd7d814e..8f101528 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -267,7 +267,6 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) gb->oam_fifo.write_end &= 0xF; gb->object_low_line_address &= gb->vram_size & ~1; gb->fetcher_x &= 0x1f; - gb->fetcher_state &= 7; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; @@ -379,7 +378,6 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le gb->oam_fifo.write_end &= 0xF; gb->object_low_line_address &= gb->vram_size & ~1; gb->fetcher_x &= 0x1f; - gb->fetcher_state &= 7; if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; From 1c7351fc8511592454bd8f15e2f28ae836db586a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 4 Mar 2020 23:34:36 +0200 Subject: [PATCH 1002/1216] Missing braces --- Core/display.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index cbf41963..0ec436d5 100644 --- a/Core/display.c +++ b/Core/display.c @@ -974,8 +974,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) - should_activate_window = true; + (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) { + should_activate_window = true; + } } if (should_activate_window) { From 4d2f56c42db1ddb6df8498d1dfd140a7794c6cb7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 4 Mar 2020 23:43:05 +0200 Subject: [PATCH 1003/1216] Minor bug fix --- Core/display.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Core/display.c b/Core/display.c index 0ec436d5..392cd93f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -547,7 +547,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_SLEEP, } fetcher_step_t; - fetcher_step_t fetcher_state_machine [9] = { + fetcher_step_t fetcher_state_machine [8] = { GB_FETCHER_SLEEP, GB_FETCHER_GET_TILE, GB_FETCHER_SLEEP, @@ -556,13 +556,8 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_GET_TILE_DATA_HIGH, GB_FETCHER_PUSH, GB_FETCHER_PUSH, - GB_FETCHER_PUSH, }; - - if (gb->fetcher_state >= sizeof(fetcher_state_machine)) { - gb->fetcher_state = 0; - } - switch (fetcher_state_machine[gb->fetcher_state]) { + switch (fetcher_state_machine[gb->fetcher_state & 7]) { case GB_FETCHER_GET_TILE: { uint16_t map = 0x1800; @@ -662,7 +657,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->current_tile_data[1] = 0xFF; } } - gb->fetcher_state++; if (gb->wx_triggered) { gb->window_tile_x++; gb->window_tile_x &= 0x1f; @@ -670,13 +664,13 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) // fallthrough case GB_FETCHER_PUSH: { - if (gb->fetcher_state == 7) { + if (gb->fetcher_state == 6) { /* The background map index increase at this specific point. If this state is not reached, it will simply not increase. */ gb->fetcher_x++; gb->fetcher_x &= 0x1f; } - if (gb->fetcher_state < 8) { + if (gb->fetcher_state < 7) { gb->fetcher_state++; } if (fifo_size(&gb->bg_fifo) > 0) break; From c6f9d051240cd83de0772f8112da118cde7ee930 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 14:41:13 +0200 Subject: [PATCH 1004/1216] Emulate LCD-PPU horizontal desync on DMGs --- Core/display.c | 38 ++++++++++++++++++++++----- Core/gb.h | 1 + Core/save_state.c | 66 ++++++++++++++++++++--------------------------- 3 files changed, 61 insertions(+), 44 deletions(-) diff --git a/Core/display.c b/Core/display.c index 392cd93f..8072f376 100644 --- a/Core/display.c +++ b/Core/display.c @@ -459,10 +459,10 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) uint32_t *dest = NULL; if (!gb->sgb) { if (gb->border_mode != GB_BORDER_ALWAYS) { - dest = gb->screen + gb->position_in_line + gb->current_line * WIDTH; + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; } else { - dest = gb->screen + gb->position_in_line + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; } } @@ -476,7 +476,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } if (gb->sgb) { if (gb->current_lcd_line < LINES) { - gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; + gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; } } else if (gb->model & GB_MODEL_NO_SFC_BIT) { @@ -500,7 +500,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } if (gb->sgb) { if (gb->current_lcd_line < LINES) { - gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; + gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; } } else if (gb->model & GB_MODEL_NO_SFC_BIT) { @@ -524,6 +524,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } gb->position_in_line++; + gb->lcd_x++; gb->window_is_being_fetched = false; } @@ -937,6 +938,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); /* Todo: find out actual access time of SCX */ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; + gb->lcd_x = 0; gb->fetcher_x = 0; gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); @@ -967,10 +969,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } } else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { - if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || - (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) { + if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { should_activate_window = true; } + else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) { + should_activate_window = true; + /* LCD-PPU horizontal desync! It only appears to happen on DMGs, but not all of them. + This doesn't seem to be CPU revision dependent, but most revisions */ + if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY && !GB_is_sgb(gb)) { + if (gb->lcd_x > 0) { + gb->lcd_x--; + } + } + } } if (should_activate_window) { @@ -1096,6 +1107,21 @@ abort_fetching_object: gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); } + + while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) { + /* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */ + uint32_t *dest = NULL; + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + *dest = gb->background_palettes_rgb[0]; + gb->lcd_x++; + + } + /* TODO: Verify timing */ if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) { gb->wx166_glitch = true; diff --git a/Core/gb.h b/Core/gb.h index db99608f..88e2991b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -523,6 +523,7 @@ struct GB_gameboy_internal_s { uint16_t object_low_line_address; bool wy_triggered; uint8_t window_tile_x; + uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/save_state.c b/Core/save_state.c index 8f101528..a53ccba5 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -180,6 +180,32 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) return true; } +static void sanitize_state(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->has_rumble && gb->rumble_callback) { + gb->rumble_callback(gb, gb->rumble_state); + } + + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + + gb->bg_fifo.read_end &= 0xF; + gb->bg_fifo.write_end &= 0xF; + gb->oam_fifo.read_end &= 0xF; + gb->oam_fifo.write_end &= 0xF; + gb->object_low_line_address &= gb->vram_size & ~1; + gb->fetcher_x &= 0x1f; + if (gb->lcd_x > gb->position_in_line) { + gb->lcd_x = gb->position_in_line; + } + + if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { + gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; + } +} + #define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) int GB_load_state(GB_gameboy_t *gb, const char *path) @@ -252,25 +278,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) errno = 0; - if (gb->cartridge_type->has_rumble && gb->rumble_callback) { - gb->rumble_callback(gb, gb->rumble_state); - } - - for (unsigned i = 0; i < 32; i++) { - GB_palette_changed(gb, false, i * 2); - GB_palette_changed(gb, true, i * 2); - } - - gb->bg_fifo.read_end &= 0xF; - gb->bg_fifo.write_end &= 0xF; - gb->oam_fifo.read_end &= 0xF; - gb->oam_fifo.write_end &= 0xF; - gb->object_low_line_address &= gb->vram_size & ~1; - gb->fetcher_x &= 0x1f; - - if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { - gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; - } + sanitize_state(gb); error: fclose(f); @@ -363,25 +371,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le memcpy(gb, &save, sizeof(save)); - if (gb->cartridge_type->has_rumble && gb->rumble_callback) { - gb->rumble_callback(gb, gb->rumble_state); - } - - for (unsigned i = 0; i < 32; i++) { - GB_palette_changed(gb, false, i * 2); - GB_palette_changed(gb, true, i * 2); - } - - gb->bg_fifo.read_end &= 0xF; - gb->bg_fifo.write_end &= 0xF; - gb->oam_fifo.read_end &= 0xF; - gb->oam_fifo.write_end &= 0xF; - gb->object_low_line_address &= gb->vram_size & ~1; - gb->fetcher_x &= 0x1f; - - if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { - gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; - } + sanitize_state(gb); return 0; } From 78b552fe822e54140f4d52942fb955fe995e7b34 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 17:37:04 +0200 Subject: [PATCH 1005/1216] More attempts to fix this bug --- Cocoa/Document.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 03d4acd8..8264fb3c 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -318,6 +318,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) if (stopping) { memset(buffer, 0, nFrames * sizeof(*buffer)); [audioLock unlock]; + return; } if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) { @@ -380,7 +381,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [audioLock signal]; [audioLock unlock]; running = false; - while (stopping); + while (stopping) { + [audioLock lock]; + [audioLock signal]; + [audioLock unlock]; + } GB_debugger_set_disabled(&gb, false); } From ee939a378258b647fa95a6c61aaf3314d6b69f42 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 17:37:18 +0200 Subject: [PATCH 1006/1216] New boot ROM animation in the DMG boot ROM --- BootROMs/dmg_boot.asm | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm index 6fb74fb5..97a12e7c 100644 --- a/BootROMs/dmg_boot.asm +++ b/BootROMs/dmg_boot.asm @@ -24,7 +24,7 @@ Start: ldh [$24], a ; Init BG palette - ld a, $fc + ld a, $54 ldh [$47], a ; Load logo from ROM. @@ -69,14 +69,36 @@ Start: jr .tilemapLoop .tilemapDone + ld a, 30 + ldh [$ff42], a + ; Turn on LCD ld a, $91 ldh [$40], a -; Wait ~0.75 seconds - ld b, 45 - call WaitBFrames - + ld d, (-119) & $FF + ld c, 15 + +.animate + call WaitFrame + ld a, d + sra a + sra a + ldh [$ff42], a + ld a, d + add c + ld d, a + ld a, c + cp 8 + jr nz, .noPaletteChange + ld a, $A8 + ldh [$47], a +.noPaletteChange + dec c + jr nz, .animate + ld a, $fc + ldh [$47], a + ; Play first sound ld a, $83 call PlaySound @@ -85,9 +107,11 @@ Start: ; Play second sound ld a, $c1 call PlaySound + -; Wait ~1.15 seconds - ld b, 70 + +; Wait ~1 second + ld b, 60 call WaitBFrames ; Set registers to match the original DMG boot From 4963ec4cc43529adec95efe79a5e24d9cd5aaf65 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 17:37:29 +0200 Subject: [PATCH 1007/1216] Gamma correction in the CRT shader --- Shaders/CRT.fsh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Shaders/CRT.fsh b/Shaders/CRT.fsh index 86844515..c1ae7ef6 100644 --- a/Shaders/CRT.fsh +++ b/Shaders/CRT.fsh @@ -158,6 +158,8 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou ret *= output_resolution.y - pixel_position.y; } + // Gamma correction + ret = pow(ret, 0.72); return ret; } From fe7667a00c92eca2089d1b8058123eb452443e51 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 17:37:47 +0200 Subject: [PATCH 1008/1216] Add drop shadows to the Monochrome LCD shader --- Shaders/MonoLCD.fsh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Shaders/MonoLCD.fsh b/Shaders/MonoLCD.fsh index 1a641afc..009e1db1 100644 --- a/Shaders/MonoLCD.fsh +++ b/Shaders/MonoLCD.fsh @@ -34,5 +34,17 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou multiplier *= (1.0 - sub_pos.x) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); } - return mix(texture(image, position) * multiplier, mix(r1, r2, s.y), BLOOM); + vec4 pre_shadow = mix(texture(image, position) * multiplier, mix(r1, r2, s.y), BLOOM); + pixel += vec2(-0.6, -0.8); + + q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + r1 = mix(q11, q21, fract(pixel.x)); + r2 = mix(q12, q22, fract(pixel.x)); + + vec4 shadow = mix(r1, r2, fract(pixel.y)); + return mix(min(shadow, pre_shadow), pre_shadow, 0.75); } From 34cf0f558da9f716b277aff5a0bbea6730c1e08c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 18:56:51 +0200 Subject: [PATCH 1009/1216] It's more reasonable to do it this way --- Core/memory.c | 8 ++++++++ Core/sm83_cpu.c | 17 ----------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index abad063e..fccfd86e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -741,6 +741,14 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_timing_sync(gb); GB_lcd_off(gb); } + /* Handle disabling objects while already fetching an object */ + if ((gb->io_registers[GB_IO_LCDC] & 2) && !(value & 2)) { + if (gb->during_object_fetch) { + gb->cycles_for_line += gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; + } + } gb->io_registers[GB_IO_LCDC] = value; if (!(value & 0x20)) { gb->wx_triggered = false; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index f25e14bd..73e2ee1f 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -208,29 +208,12 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t old_value = GB_read_memory(gb, addr); GB_advance_cycles(gb, gb->pending_cycles - 2); - /* Handle disabling objects while already fetching an object */ - if ((old_value & 2) && !(value & 2)) { - if (gb->during_object_fetch) { - gb->cycles_for_line += gb->display_cycles; - gb->display_cycles = 0; - gb->object_fetch_aborted = true; - } - } - if (/* gb->model != GB_MODEL_MGB && */ gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { old_value &= ~2; } GB_write_memory(gb, addr, old_value | (value & 1)); GB_advance_cycles(gb, 1); - /* Handle disabling objects while already fetching an object */ - if ((old_value & 2) && !(value & 2)) { - if (gb->during_object_fetch) { - gb->cycles_for_line += gb->display_cycles; - gb->display_cycles = 0; - gb->object_fetch_aborted = true; - } - } GB_write_memory(gb, addr, value); gb->pending_cycles = 5; return; From e7f6ac8828dc021fcb11389dc787cca1beb84412 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 6 Mar 2020 21:19:53 +0200 Subject: [PATCH 1010/1216] Do the same for SGB --- Core/sm83_cpu.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 73e2ee1f..da980eb0 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -224,23 +224,10 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t old_value = GB_read_memory(gb, addr); GB_advance_cycles(gb, gb->pending_cycles - 2); - /* Handle disabling objects while already fetching an object */ - if ((old_value & 2) && !(value & 2)) { - if (gb->during_object_fetch) { - gb->cycles_for_line += gb->display_cycles; - gb->display_cycles = 0; - gb->object_fetch_aborted = true; - } - } + /* Hack to force aborting object fetch */ + GB_write_memory(gb, addr, value); + GB_write_memory(gb, addr, old_value); GB_advance_cycles(gb, 1); - /* Handle disabling objects while already fetching an object */ - if ((old_value & 2) && !(value & 2)) { - if (gb->during_object_fetch) { - gb->cycles_for_line += gb->display_cycles; - gb->display_cycles = 0; - gb->object_fetch_aborted = true; - } - } GB_write_memory(gb, addr, value); gb->pending_cycles = 5; return; From 84e8e45b7beaf15d67b32fe2e7bd01f60746e482 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Mar 2020 13:35:54 +0200 Subject: [PATCH 1011/1216] Implement ATTR_CHR --- Core/sgb.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Core/sgb.c b/Core/sgb.c index d712e27f..271b6817 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -17,6 +17,7 @@ enum { ATTR_BLK = 0x04, ATTR_LIN = 0x05, ATTR_DIV = 0x06, + ATTR_CHR = 0x07, PAL_SET = 0x0A, PAL_TRN = 0x0B, DATA_SND = 0x0F, @@ -254,6 +255,52 @@ static void command_ready(GB_gameboy_t *gb) } break; } + case ATTR_CHR: { + struct __attribute__((packed)) { + uint8_t x, y; + uint16_t length; + uint8_t direction; + uint8_t data[]; + } *command = (void *)(gb->sgb->command + 1); + + uint16_t count = command->length; +#ifdef GB_BIG_ENDIAN + count = __builtin_bswap16(count); +#endif + uint8_t x = command->x; + uint8_t y = command->y; + if (x >= 20 || y >= 18 || (count + 3) / 4 > sizeof(gb->sgb->command) - sizeof(*command) - 1) { + /* TODO: Verify with the SFC BIOS */ + break; + } + + for (unsigned i = 0; i < count; i++) { + uint8_t palette = (command->data[i / 4] >> (((~i) & 3) << 1)) & 3; + gb->sgb->attribute_map[x + 20 * y] = palette; + if (command->direction) { + y++; + if (y == 18) { + x++; + y = 0; + if (x == 20) { + x = 0; + } + } + } + else { + x++; + if (x == 20) { + y++; + x = 0; + if (y == 18) { + y = 0; + } + } + } + } + + break; + } case ATTR_LIN: { struct { uint8_t count; From e94e7cc501fea44c4d4a37cfd5091bed88cfbc98 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 25 Mar 2020 20:33:13 +0200 Subject: [PATCH 1012/1216] Add another color correction mode --- Cocoa/Preferences.xib | 5 +++-- Core/display.c | 18 ++++++++++++++++-- Core/display.h | 1 + SDL/gui.c | 6 +++--- libretro/libretro.c | 9 ++++++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index e7d96aa2..7eb05879 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -152,6 +152,7 @@ + @@ -203,7 +204,7 @@ - + @@ -459,7 +460,7 @@ - + diff --git a/Core/display.c b/Core/display.c index 8072f376..0ed973ed 100644 --- a/Core/display.c +++ b/Core/display.c @@ -133,7 +133,7 @@ static void display_vblank(GB_gameboy_t *gb) if (!GB_is_sgb(gb)) { uint32_t color = 0; if (GB_is_cgb(gb)) { - color = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + color = GB_convert_rgb15(gb, 0x7FFF, false); } else { color = is_ppu_stopped ? @@ -261,7 +261,21 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) } new_r = r; new_b = b; - if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + if (gb->color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { + r = new_r; + g = new_r; + b = new_r; + + new_r = new_r * 7 / 8 + ( g + b) / 16; + new_g = new_g * 7 / 8 + (r + b) / 16; + new_b = new_b * 7 / 8 + (r + g ) / 16; + + + new_r = new_r * (224 - 32) / 255 + 32; + new_g = new_g * (220 - 36) / 255 + 36; + new_b = new_b * (216 - 40) / 255 + 40; + } + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { uint8_t old_max = MAX(r, MAX(g, b)); uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); diff --git a/Core/display.h b/Core/display.h index d5398817..4c37f99a 100644 --- a/Core/display.h +++ b/Core/display.h @@ -50,6 +50,7 @@ typedef enum { GB_COLOR_CORRECTION_CORRECT_CURVES, GB_COLOR_CORRECTION_EMULATE_HARDWARE, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS, + GB_COLOR_CORRECTION_REDUCE_CONTRAST, } GB_color_correction_mode_t; void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); diff --git a/SDL/gui.c b/SDL/gui.c index 6646a172..b6b0f038 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -422,7 +422,7 @@ const char *current_scaling_mode(unsigned index) const char *current_color_correction_mode(unsigned index) { - return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"} + return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast"} [configuration.color_correction_mode]; } @@ -462,7 +462,7 @@ void cycle_scaling_backwards(unsigned index) static void cycle_color_correction(unsigned index) { - if (configuration.color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + if (configuration.color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED; } else { @@ -473,7 +473,7 @@ static void cycle_color_correction(unsigned index) static void cycle_color_correction_backwards(unsigned index) { if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { - configuration.color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS; + configuration.color_correction_mode = GB_COLOR_CORRECTION_REDUCE_CONTRAST; } else { configuration.color_correction_mode--; diff --git a/libretro/libretro.c b/libretro/libretro.c index 59dfdc44..8cd13244 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -202,7 +202,7 @@ static retro_environment_t environ_cb; /* variables for single cart mode */ static const struct retro_variable vars_single[] = { - { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness" }, + { "sameboy_color_correction_mode", "Color correction; off|correct curves|emulate hardware|preserve brightness|reduce contrast" }, { "sameboy_high_pass_filter_mode", "High-pass filter; off|accurate|remove dc offset" }, { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { "sameboy_border", "Super Game Boy border; enabled|disabled" }, @@ -497,6 +497,8 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + else if (strcmp(var.value, "reduce_contrast") == 0) + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } var.key = "sameboy_high_pass_filter_mode"; @@ -561,6 +563,8 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + else if (strcmp(var.value, "reduce_contrast") == 0) + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } var.key = "sameboy_color_correction_mode_2"; @@ -575,6 +579,9 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); else if (strcmp(var.value, "preserve brightness") == 0) GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + else if (strcmp(var.value, "reduce_contrast") == 0) + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_REDUCE_CONTRAST); + } var.key = "sameboy_high_pass_filter_mode_1"; From 5ecb8456624642e18328450864a6a9cbd015a7ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 26 Mar 2020 20:54:18 +0200 Subject: [PATCH 1013/1216] Add accurate frame blending option --- Cocoa/AppDelegate.m | 2 ++ Cocoa/Document.m | 21 +++++++++-------- Cocoa/GBGLShader.h | 3 ++- Cocoa/GBGLShader.m | 10 ++++---- Cocoa/GBOpenGLView.m | 5 ++-- Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 22 ++++++++++++++++-- Cocoa/GBView.h | 10 +++++++- Cocoa/GBView.m | 21 +++++++++++++---- Cocoa/GBViewMetal.m | 20 ++++++++-------- Cocoa/Preferences.xib | 44 +++++++++++++++++++++++++++++------ Core/display.c | 10 ++++++++ Core/display.h | 1 + Core/gb.h | 1 + SDL/gui.c | 43 ++++++++++++++++++++++++++++------ SDL/gui.h | 2 +- SDL/main.c | 2 +- SDL/shader.c | 7 +++--- SDL/shader.h | 13 +++++++++-- Shaders/MasterShader.fsh | 46 +++++++++++++++++++++++++++++++------ Shaders/MasterShader.metal | 43 ++++++++++++++++++++++++++++++---- 21 files changed, 258 insertions(+), 69 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index bbaa3aee..b941eb46 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -1,5 +1,6 @@ #import "AppDelegate.h" #include "GBButtons.h" +#include "GBView.h" #include #import @@ -36,6 +37,7 @@ @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), @"GBRewindLength": @(10), + @"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE), @"GBDMGModel": @(GB_MODEL_DMG_B), @"GBCGBModel": @(GB_MODEL_CGB_E), diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 8264fb3c..b8352338 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -492,7 +492,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.consoleOutput.textContainerInset = NSMakeSize(4, 4); [self.view becomeFirstResponder]; - self.view.shouldBlendFrameWithPrevious = ![[NSUserDefaults standardUserDefaults] boolForKey:@"DisableFrameBlending"]; + self.view.frameBlendingMode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; CGRect window_frame = self.mainWindow.frame; window_frame.size.width = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowWidth"], window_frame.size.width); @@ -521,6 +521,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) name:@"GBColorCorrectionChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateFrameBlendingMode) + name:@"GBFrameBlendingModeChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePalette) name:@"GBColorPaletteChanged" @@ -677,12 +682,6 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [[NSUserDefaults standardUserDefaults] setBool:!self.audioClient.isPlaying forKey:@"Mute"]; } -- (IBAction)toggleBlend:(id)sender -{ - self.view.shouldBlendFrameWithPrevious ^= YES; - [[NSUserDefaults standardUserDefaults] setBool:!self.view.shouldBlendFrameWithPrevious forKey:@"DisableFrameBlending"]; -} - - (BOOL)validateUserInterfaceItem:(id)anItem { if([anItem action] == @selector(mute:)) { @@ -695,9 +694,6 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) { [(NSMenuItem*)anItem setState:anItem.tag == current_model]; } - else if ([anItem action] == @selector(toggleBlend:)) { - [(NSMenuItem*)anItem setState:self.view.shouldBlendFrameWithPrevious]; - } else if ([anItem action] == @selector(interrupt:)) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { return false; @@ -1617,6 +1613,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } } +- (void) updateFrameBlendingMode +{ + self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; +} + - (void) updateRewindLength { [self performAtomicBlock:^{ diff --git a/Cocoa/GBGLShader.h b/Cocoa/GBGLShader.h index 1a126177..8e46f93f 100644 --- a/Cocoa/GBGLShader.h +++ b/Cocoa/GBGLShader.h @@ -1,6 +1,7 @@ #import +#import "GBView.h" @interface GBGLShader : NSObject - (instancetype)initWithName:(NSString *) shaderName; -- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode: (GB_frame_blending_mode_t)blendingMode; @end diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index fe636f87..d57f43d1 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -21,7 +21,7 @@ void main(void) {\n\ GLuint resolution_uniform; GLuint texture_uniform; GLuint previous_texture_uniform; - GLuint mix_previous_uniform; + GLuint frame_blending_mode_uniform; GLuint position_attribute; GLuint texture; @@ -70,7 +70,7 @@ void main(void) {\n\ glBindTexture(GL_TEXTURE_2D, 0); previous_texture_uniform = glGetUniformLocation(program, "previous_image"); - mix_previous_uniform = glGetUniformLocation(program, "mix_previous"); + frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode"); // Configure OpenGL [self configureOpenGL]; @@ -79,7 +79,7 @@ void main(void) {\n\ return self; } -- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode:(GB_frame_blending_mode_t)blendingMode { glUseProgram(program); glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale); @@ -87,8 +87,8 @@ void main(void) {\n\ glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); glUniform1i(texture_uniform, 0); - glUniform1i(mix_previous_uniform, previous != NULL); - if (previous) { + glUniform1i(frame_blending_mode_uniform, blendingMode); + if (blendingMode) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, previous_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 67a9f8d0..fd845e15 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -14,10 +14,11 @@ glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); [self.shader renderBitmap:gbview.currentBuffer - previous:gbview.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL + previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) inSize:self.bounds.size - scale:scale]; + scale:scale + withBlendingMode:gbview.frameBlendingMode]; glFlush(); } diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 2d6b0fc6..27b5aa5c 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -7,6 +7,7 @@ @property (strong) IBOutlet NSButton *aspectRatioCheckbox; @property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; @property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property (strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton; @property (strong) IBOutlet NSPopUpButton *colorPalettePopupButton; @property (strong) IBOutlet NSPopUpButton *displayBorderPopupButton; @property (strong) IBOutlet NSPopUpButton *rewindPopupButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 03037718..1b5aa4f0 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -14,6 +14,7 @@ NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton; NSPopUpButton *_colorCorrectionPopupButton; + NSPopUpButton *_frameBlendingModePopupButton; NSPopUpButton *_colorPalettePopupButton; NSPopUpButton *_displayBorderPopupButton; NSPopUpButton *_rewindPopupButton; @@ -87,6 +88,18 @@ return _colorCorrectionPopupButton; } +- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton +{ + _frameBlendingModePopupButton = frameBlendingModePopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; + [_frameBlendingModePopupButton selectItemAtIndex:mode]; +} + +- (NSPopUpButton *)frameBlendingModePopupButton +{ + return _frameBlendingModePopupButton; +} + - (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton { _colorPalettePopupButton = colorPalettePopupButton; @@ -223,7 +236,14 @@ [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBColorCorrection"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; +} +- (IBAction)franeBlendingModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBFrameBlendingMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil]; + } - (IBAction)colorPaletteChanged:(id)sender @@ -231,7 +251,6 @@ [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBColorPalette"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; - } - (IBAction)displayBorderChanged:(id)sender @@ -239,7 +258,6 @@ [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) forKey:@"GBBorderMode"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil]; - } - (IBAction)rewindLengthChanged:(id)sender diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index f4c5e449..474e3c7b 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -2,11 +2,19 @@ #include #import "GBJoystickListener.h" +typedef enum { + GB_FRAME_BLENDING_MODE_DISABLED, + GB_FRAME_BLENDING_MODE_SIMPLE, + GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_ODD, +} GB_frame_blending_mode_t; + @interface GBView : NSView - (void) flip; - (uint32_t *) pixels; @property GB_gameboy_t *gb; -@property (nonatomic) BOOL shouldBlendFrameWithPrevious; +@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property bool isRewinding; @property NSView *internalView; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index e3026fdf..0267344a 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -19,6 +19,7 @@ bool underclockKeyDown; double clockMultiplier; NSEventModifierFlags previousModifiers; + GB_frame_blending_mode_t _frameBlendingMode; } + (instancetype)alloc @@ -43,8 +44,7 @@ } - (void) _init -{ - _shouldBlendFrameWithPrevious = 1; +{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect @@ -79,15 +79,26 @@ [self setFrame:self.superview.frame]; } -- (void) setShouldBlendFrameWithPrevious:(BOOL)shouldBlendFrameWithPrevious +- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode { - _shouldBlendFrameWithPrevious = shouldBlendFrameWithPrevious; + _frameBlendingMode = frameBlendingMode; [self setNeedsDisplay:YES]; } + +- (GB_frame_blending_mode_t)frameBlendingMode +{ + if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) { + if (GB_is_sgb(_gb)) { + return GB_FRAME_BLENDING_MODE_SIMPLE; + } + return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + return _frameBlendingMode; +} - (unsigned char) numberOfBuffers { - return _shouldBlendFrameWithPrevious? 3 : 2; + return _frameBlendingMode? 3 : 2; } - (void)dealloc diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index fde4b7e0..093ed2a3 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -15,7 +15,7 @@ static const vector_float2 rect[] = id vertices; id pipeline_state; id command_queue; - id mix_previous_buffer; + id frame_blending_mode_buffer; id output_resolution_buffer; vector_float2 output_resolution; } @@ -23,7 +23,7 @@ static const vector_float2 rect[] = + (bool)isSupported { if (MTLCopyAllDevices) { - return [MTLCopyAllDevices() count]; + return false; //[MTLCopyAllDevices() count]; } return false; } @@ -56,10 +56,10 @@ static const vector_float2 rect[] = length:sizeof(rect) options:MTLResourceStorageModeShared]; - static const bool default_mix_value = false; - mix_previous_buffer = [device newBufferWithBytes:&default_mix_value - length:sizeof(default_mix_value) - options:MTLResourceStorageModeShared]; + static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; + frame_blending_mode_buffer = [device newBufferWithBytes:&default_blending_mode + length:sizeof(default_blending_mode) + options:MTLResourceStorageModeShared]; output_resolution_buffer = [device newBufferWithBytes:&output_resolution length:sizeof(output_resolution) @@ -147,7 +147,7 @@ static const vector_float2 rect[] = mipmapLevel:0 withBytes:[self currentBuffer] bytesPerRow:texture.width * 4]; - if ([self shouldBlendFrameWithPrevious]) { + if ([self frameBlendingMode]) { [previous_texture replaceRegion:region mipmapLevel:0 withBytes:[self previousBuffer] @@ -157,9 +157,9 @@ static const vector_float2 rect[] = MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; id command_buffer = [command_queue commandBuffer]; - if(render_pass_descriptor != nil) + if (render_pass_descriptor != nil) { - *(bool *)[mix_previous_buffer contents] = [self shouldBlendFrameWithPrevious]; + *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode]; *(vector_float2 *)[output_resolution_buffer contents] = output_resolution; id render_encoder = @@ -176,7 +176,7 @@ static const vector_float2 rect[] = offset:0 atIndex:0]; - [render_encoder setFragmentBuffer:mix_previous_buffer + [render_encoder setFragmentBuffer:frame_blending_mode_buffer offset:0 atIndex:0]; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 7eb05879..eeb1cf2f 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -69,6 +69,7 @@ + @@ -80,11 +81,11 @@ - + - + @@ -93,7 +94,7 @@ - + @@ -130,7 +131,7 @@ - + @@ -139,7 +140,7 @@ - + @@ -160,6 +161,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -231,7 +261,7 @@ - + @@ -460,7 +490,7 @@ - + diff --git a/Core/display.c b/Core/display.c index 0ed973ed..a7dda7d8 100644 --- a/Core/display.c +++ b/Core/display.c @@ -797,6 +797,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) return; } + gb->is_odd_frame = false; + if (!GB_is_cgb(gb)) { GB_SLEEP(gb, display, 23, 1); } @@ -1228,6 +1230,7 @@ abort_fetching_object: } else { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; display_vblank(gb); } gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; @@ -1236,6 +1239,7 @@ abort_fetching_object: else { gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; display_vblank(gb); } } @@ -1465,3 +1469,9 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } return count; } + + +bool GB_is_odd_frame(GB_gameboy_t *gb) +{ + return gb->is_odd_frame; +} diff --git a/Core/display.h b/Core/display.h index 4c37f99a..5bdeba8d 100644 --- a/Core/display.h +++ b/Core/display.h @@ -58,4 +58,5 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); +bool GB_is_odd_frame(GB_gameboy_t *gb); #endif /* display_h */ diff --git a/Core/gb.h b/Core/gb.h index 88e2991b..b4ef6580 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -524,6 +524,7 @@ struct GB_gameboy_internal_s { bool wy_triggered; uint8_t window_tile_x; uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. + bool is_odd_frame; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/SDL/gui.c b/SDL/gui.c index b6b0f038..e34028d7 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -46,9 +46,22 @@ void render_texture(void *pixels, void *previous) } glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); + GB_frame_blending_mode_t mode = configuration.blending_mode; + if (!previous) { + mode = GB_FRAME_BLENDING_MODE_DISABLED; + } + else if (mode == GB_FRAME_BLENDING_MODE_ACCURATE) { + if (GB_is_sgb(&gb)) { + mode = GB_FRAME_BLENDING_MODE_SIMPLE; + } + else { + mode = GB_is_odd_frame(&gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + } render_bitmap_with_shader(&shader, _pixels, previous, GB_get_screen_width(&gb), GB_get_screen_height(&gb), - rect.x, rect.y, rect.w, rect.h); + rect.x, rect.y, rect.w, rect.h, + mode); SDL_GL_SwapWindow(window); } } @@ -91,7 +104,7 @@ configuration_t configuration = .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, .highpass_mode = GB_HIGHPASS_ACCURATE, .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, - .blend_frames = true, + .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, .rewind_length = 60 * 2, .model = MODEL_CGB }; @@ -600,23 +613,39 @@ const char *current_filter_name(unsigned index) return shaders[i].display_name; } -static void toggle_blend_frames(unsigned index) +static void cycle_blending_mode(unsigned index) { - configuration.blend_frames ^= true; + if (configuration.blending_mode == GB_FRAME_BLENDING_MODE_ACCURATE) { + configuration.blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; + } + else { + configuration.blending_mode++; + } } -const char *blend_frames_string(unsigned index) +static void cycle_blending_mode_backwards(unsigned index) { - return configuration.blend_frames? "Enabled" : "Disabled"; + if (configuration.blending_mode == GB_FRAME_BLENDING_MODE_DISABLED) { + configuration.blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE; + } + else { + configuration.blending_mode--; + } +} + +const char *blending_mode_string(unsigned index) +{ + return (const char *[]){"Disabled", "Simple", "Accurate"} + [configuration.blending_mode]; } static const struct menu_item graphics_menu[] = { {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, + {"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards}, {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, - {"Blend Frames:", toggle_blend_frames, blend_frames_string, toggle_blend_frames}, {"Back", return_to_root_menu}, {NULL,} }; diff --git a/SDL/gui.h b/SDL/gui.h index 41e9bf2a..8ef2a683 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -70,7 +70,7 @@ typedef struct { SDL_Scancode keys[9]; GB_color_correction_mode_t color_correction_mode; enum scaling_mode scaling_mode; - bool blend_frames; + uint8_t blending_mode; GB_highpass_mode_t highpass_mode; diff --git a/SDL/main.c b/SDL/main.c index 06159c97..c2b5549b 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -372,7 +372,7 @@ static void vblank(GB_gameboy_t *gb) clock_mutliplier += 1.0/16; GB_set_clock_multiplier(gb, clock_mutliplier); } - if (configuration.blend_frames) { + if (configuration.blending_mode) { render_texture(active_pixel_buffer, previous_pixel_buffer); uint32_t *temp = active_pixel_buffer; active_pixel_buffer = previous_pixel_buffer; diff --git a/SDL/shader.c b/SDL/shader.c index 37e5be7e..250046b1 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -130,7 +130,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) glBindTexture(GL_TEXTURE_2D, 0); shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image"); - shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous"); + shader->blending_mode_uniform = glGetUniformLocation(shader->program, "frame_blending_mode"); // Program @@ -164,7 +164,8 @@ bool init_shader_with_name(shader_t *shader, const char *name) void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, unsigned source_width, unsigned source_height, - unsigned x, unsigned y, unsigned w, unsigned h) + unsigned x, unsigned y, unsigned w, unsigned h, + GB_frame_blending_mode_t blending_mode) { glUseProgram(shader->program); glUniform2f(shader->origin_uniform, x, y); @@ -173,7 +174,7 @@ void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, glBindTexture(GL_TEXTURE_2D, shader->texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); glUniform1i(shader->texture_uniform, 0); - glUniform1i(shader->mix_previous_uniform, previous != NULL); + glUniform1i(shader->blending_mode_uniform, previous? blending_mode : GB_FRAME_BLENDING_MODE_DISABLED); if (previous) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, shader->previous_texture); diff --git a/SDL/shader.h b/SDL/shader.h index 3a1c3040..149958d5 100644 --- a/SDL/shader.h +++ b/SDL/shader.h @@ -8,7 +8,7 @@ typedef struct shader_s { GLuint origin_uniform; GLuint texture_uniform; GLuint previous_texture_uniform; - GLuint mix_previous_uniform; + GLuint blending_mode_uniform; GLuint position_attribute; GLuint texture; @@ -16,10 +16,19 @@ typedef struct shader_s { GLuint program; } shader_t; +typedef enum { + GB_FRAME_BLENDING_MODE_DISABLED, + GB_FRAME_BLENDING_MODE_SIMPLE, + GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_ODD, +} GB_frame_blending_mode_t; + bool init_shader_with_name(shader_t *shader, const char *name); void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, unsigned source_width, unsigned source_height, - unsigned x, unsigned y, unsigned w, unsigned h); + unsigned x, unsigned y, unsigned w, unsigned h, + GB_frame_blending_mode_t blending_mode); void free_shader(struct shader_s *shader); #endif /* shader_h */ diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index cd569c2d..729ab5f2 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -1,7 +1,7 @@ #version 150 uniform sampler2D image; uniform sampler2D previous_image; -uniform bool mix_previous; +uniform int frame_blending_mode; uniform vec2 output_resolution; uniform vec2 origin; @@ -15,6 +15,15 @@ out vec4 frag_color; #line 1 {filter} + +#define BLEND_BIAS (1.0/3.0) + +#define DISABLED 0 +#define SIMPLE 1 +#define ACCURATE 2 +#define ACCURATE_EVEN ACCURATE +#define ACCURATE_ODD 3 + void main() { vec2 position = gl_FragCoord.xy - origin; @@ -22,11 +31,34 @@ void main() position.y = 1 - position.y; vec2 input_resolution = textureSize(image, 0); - if (mix_previous) { - frag_color = mix(scale(image, position, input_resolution, output_resolution), - scale(previous_image, position, input_resolution, output_resolution), 0.5); - } - else { - frag_color = scale(image, position, input_resolution, output_resolution); + float ratio; + switch (frame_blending_mode) { + default: + case DISABLED: + frag_color = scale(image, position, input_resolution, output_resolution); + return; + case SIMPLE: + ratio = 0.5; + break; + case ACCURATE_EVEN: + if ((int(position.y * input_resolution.y) & 1) == 0) { + ratio = BLEND_BIAS; + } + else { + ratio = 1 - BLEND_BIAS; + } + break; + case ACCURATE_ODD: + if ((int(position.y * input_resolution.y) & 1) == 0) { + ratio = 1 - BLEND_BIAS; + } + else { + ratio = BLEND_BIAS; + } + break; } + + frag_color = mix(scale(image, position, input_resolution, output_resolution), + scale(previous_image, position, input_resolution, output_resolution), ratio); + } diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 4cae3ae0..ee8dec9a 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -42,19 +42,52 @@ static inline float4 texture(texture2d texture, float2 pos) #line 1 {filter} +#define BLEND_BIAS (1.0/3.0) + +enum frame_blending_mode { + DISABLED, + SIMPLE, + ACCURATE, + ACCURATE_EVEN = ACCURATE, + ACCURATE_ODD, +}; + fragment float4 fragment_shader(rasterizer_data in [[stage_in]], texture2d image [[ texture(0) ]], texture2d previous_image [[ texture(1) ]], - constant bool *mix_previous [[ buffer(0) ]], + constant enum frame_blending_mode *frame_blending_mode [[ buffer(0) ]], constant float2 *output_resolution [[ buffer(1) ]]) { float2 input_resolution = float2(image.get_width(), image.get_height()); in.texcoords.y = 1 - in.texcoords.y; - if (*mix_previous) { - return mix(scale(image, in.texcoords, input_resolution, *output_resolution), - scale(previous_image, in.texcoords, input_resolution, *output_resolution), 0.5); + float ratio; + switch (*frame_blending_mode) { + default: + case DISABLED: + return scale(image, in.texcoords, input_resolution, *output_resolution); + case SIMPLE: + ratio = 0.5; + break; + case ACCURATE_EVEN: + if (((int)(in.texcoords.y * input_resolution.y) & 1) == 0) { + ratio = BLEND_BIAS; + } + else { + ratio = 1 - BLEND_BIAS; + } + break; + case ACCURATE_ODD: + if (((int)(in.texcoords.y * input_resolution.y) & 1) == 0) { + ratio = 1 - BLEND_BIAS; + } + else { + ratio = BLEND_BIAS; + } + break; } - return scale(image, in.texcoords, input_resolution, *output_resolution); + + return mix(scale(image, in.texcoords, input_resolution, *output_resolution), + scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio); } From 7a807f5cae4ab208a9993d0bce68610994c46214 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 26 Mar 2020 22:18:31 +0200 Subject: [PATCH 1014/1216] Fix #243 --- HexFiend/HFRepresenterTextViewCallout.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HexFiend/HFRepresenterTextViewCallout.m b/HexFiend/HFRepresenterTextViewCallout.m index ae46bd88..bb4b58e8 100644 --- a/HexFiend/HFRepresenterTextViewCallout.m +++ b/HexFiend/HFRepresenterTextViewCallout.m @@ -432,7 +432,7 @@ static double distanceMod1(double a, double b) { // Compute the vertical offset CGFloat textYOffset = (glyphCount == 1 ? 4 : 5); // LOL - if ([_label isEqualToString:@"6"] || [_label isEqualToString:@"7"] == 7) textYOffset -= 1; + if ([_label isEqualToString:@"6"]) textYOffset -= 1; // Apply this text matrix From fa1c84f18fb25dfa987690364b667ae9fb4afc99 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 15:43:30 +0300 Subject: [PATCH 1015/1216] Remove the Blend Frames menu item --- Cocoa/MainMenu.xib | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 844aa0c9..ee989ee5 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -342,13 +342,6 @@ - - - - - - - @@ -454,6 +447,7 @@
    +
    From 4cb56dc76fd7858aef8a558bf5e917433b470359 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 16:35:36 +0300 Subject: [PATCH 1016/1216] Improve MBC2 emulation. Fixes #238 --- Core/memory.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index fccfd86e..d3d9aaa6 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -456,9 +456,9 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } break; case GB_MBC2: - switch (addr & 0xF000) { - case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = (value & 0xF) == 0xA; break; - case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; + switch (addr & 0x4100) { + case 0x0000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0100: gb->mbc2.rom_bank = value; break; } break; case GB_MBC3: From 588c0734a9bf366aee9520cf6696520b6a823458 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 17:22:50 +0300 Subject: [PATCH 1017/1216] Fix a crash --- Cocoa/GBView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0267344a..0e528111 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -89,7 +89,7 @@ - (GB_frame_blending_mode_t)frameBlendingMode { if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) { - if (GB_is_sgb(_gb)) { + if (!_gb || GB_is_sgb(_gb)) { return GB_FRAME_BLENDING_MODE_SIMPLE; } return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; From 876b36ac1cf18dff51fd3e2c51b103c94a46cb72 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 17:26:51 +0300 Subject: [PATCH 1018/1216] More crash fixes, restore Metal support --- Cocoa/GBOpenGLView.m | 14 ++++++++------ Cocoa/GBViewMetal.m | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index fd845e15..8831b625 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -13,12 +13,14 @@ double scale = self.window.backingScaleFactor; glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); - [self.shader renderBitmap:gbview.currentBuffer - previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL - sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) - inSize:self.bounds.size - scale:scale - withBlendingMode:gbview.frameBlendingMode]; + if (gbview.gb) { + [self.shader renderBitmap:gbview.currentBuffer + previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL + sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) + inSize:self.bounds.size + scale:scale + withBlendingMode:gbview.frameBlendingMode]; + } glFlush(); } diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 093ed2a3..4c8a5d5e 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -23,7 +23,7 @@ static const vector_float2 rect[] = + (bool)isSupported { if (MTLCopyAllDevices) { - return false; //[MTLCopyAllDevices() count]; + return [MTLCopyAllDevices() count]; } return false; } From 05403d3a56883b8acee642c5b85e801281ed66d1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 17:36:55 +0300 Subject: [PATCH 1019/1216] Fix the Joypad interrupt. Fixes #237 --- Core/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index d3d9aaa6..148c9867 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -780,7 +780,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { GB_sgb_write(gb, value); - gb->io_registers[GB_IO_JOYP] = value & 0xF0; + gb->io_registers[GB_IO_JOYP] = (value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); GB_update_joyp(gb); } return; From 1a3572316f0f9e2fc6b35c4cf0d99b3cfd279b8e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 17:49:14 +0300 Subject: [PATCH 1020/1216] next now skips over halt, closes #233 --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 8b6d6cf4..e7f6881f 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -2059,7 +2059,7 @@ void GB_debugger_run(GB_gameboy_t *gb) if (gb->debug_disable) return; char *input = NULL; - if (gb->debug_next_command && gb->debug_call_depth <= 0) { + if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) { gb->debug_stopped = true; } if (gb->debug_fin_command && gb->debug_call_depth == -1) { From 2f1b8e5b5785395ccf79eab33c0123ff7fa0a454 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 18:56:47 +0300 Subject: [PATCH 1021/1216] IME is now available under the registers command --- Core/debugger.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index e7f6881f..71f0861e 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -816,16 +816,17 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const } - GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ + GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ (gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); - GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); - GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); - GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); - GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); - GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); + GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); + GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); + GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); + GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); + GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); + GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); return true; } From 9f3bffd4ddfd4422391d83c83a67268deea555b2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 27 Mar 2020 19:10:42 +0300 Subject: [PATCH 1022/1216] Add volume control to SDL --- SDL/gui.c | 27 ++++++++++++++++++++++++++- SDL/gui.h | 1 + SDL/main.c | 5 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index e34028d7..8bc65518 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -106,7 +106,8 @@ configuration_t configuration = .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, .rewind_length = 60 * 2, - .model = MODEL_CGB + .model = MODEL_CGB, + .volume = 100, }; @@ -681,8 +682,32 @@ void cycle_highpass_filter_backwards(unsigned index) } } +const char *volume_string(unsigned index) +{ + static char ret[5]; + sprintf(ret, "%d%%", configuration.volume); + return ret; +} + +void increase_volume(unsigned index) +{ + configuration.volume += 5; + if (configuration.volume > 100) { + configuration.volume = 100; + } +} + +void decrease_volume(unsigned index) +{ + configuration.volume -= 5; + if (configuration.volume > 100) { + configuration.volume = 0; + } +} + static const struct menu_item audio_menu[] = { {"Highpass Filter:", cycle_highpass_filter, highpass_filter_string, cycle_highpass_filter_backwards}, + {"Volume:", increase_volume, volume_string, decrease_volume}, {"Back", return_to_root_menu}, {NULL,} }; diff --git a/SDL/gui.h b/SDL/gui.h index 8ef2a683..4a3b55fc 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -104,6 +104,7 @@ typedef struct { /* v0.13 */ uint8_t dmg_palette; GB_border_mode_t border_mode; + uint8_t volume; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index c2b5549b..35c9a75e 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -420,6 +420,11 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) return; } + if (configuration.volume != 100) { + sample->left = sample->left * configuration.volume / 100; + sample->right = sample->right * configuration.volume / 100; + } + SDL_QueueAudio(device_id, sample, sizeof(*sample)); } From d75b7c00232834cea1b7a756c9e62db1b5d422a0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 28 Mar 2020 22:56:19 +0300 Subject: [PATCH 1023/1216] Feature request; allow loading prefs.bin relatively --- SDL/main.c | 10 +++++++--- Windows/stdio.h | 6 +++++- Windows/utf8_compat.c | 12 +++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 35c9a75e..4b6afeaa 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -11,6 +11,7 @@ #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 +#include #else #include /* Windows (well, at least my VM) can't handle 96KHz sound well :( */ @@ -686,9 +687,12 @@ int main(int argc, char **argv) SDL_EventState(SDL_DROPFILE, SDL_ENABLE); - char *prefs_dir = SDL_GetPrefPath("", "SameBoy"); - snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir); - SDL_free(prefs_dir); + strcpy(prefs_path, resource_path("prefs.bin")); + if (access(prefs_path, R_OK | W_OK) != 0) { + char *prefs_dir = SDL_GetPrefPath("", "SameBoy"); + snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir); + SDL_free(prefs_dir); + } FILE *prefs_file = fopen(prefs_path, "rb"); if (prefs_file) { diff --git a/Windows/stdio.h b/Windows/stdio.h index d68c9569..0595b016 100755 --- a/Windows/stdio.h +++ b/Windows/stdio.h @@ -2,6 +2,10 @@ #include_next #include +int access(const char *filename, int mode); +#define R_OK 2 +#define W_OK 4 + #ifndef __MINGW32__ #ifndef __LIBRETRO__ static inline int vasprintf(char **str, const char *fmt, va_list args) @@ -72,4 +76,4 @@ static inline size_t getline(char **lineptr, size_t *n, FILE *stream) { return p - bufptr - 1; } -#define snprintf _snprintf \ No newline at end of file +#define snprintf _snprintf diff --git a/Windows/utf8_compat.c b/Windows/utf8_compat.c index 1005f22a..03472115 100755 --- a/Windows/utf8_compat.c +++ b/Windows/utf8_compat.c @@ -1,6 +1,7 @@ #include #include #include +#include FILE *fopen(const char *filename, const char *mode) { @@ -11,4 +12,13 @@ FILE *fopen(const char *filename, const char *mode) MultiByteToWideChar(CP_UTF8, 0, mode, -1, w_mode, sizeof(w_mode) / sizeof(w_mode[0])); return _wfopen(w_filename, w_mode); -} \ No newline at end of file +} + +int access(const char *filename, int mode) +{ + wchar_t w_filename[MAX_PATH] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename) / sizeof(w_filename[0])); + + return _waccess(w_filename, mode); +} + From 0ed5cf6b3879c0b7d6adfe769354e55fe04d1d1b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 8 Apr 2020 19:07:29 +0300 Subject: [PATCH 1024/1216] Proper MBC30 support, more accurate MBC3 emulation. Fixes #244 --- Core/debugger.c | 23 ++++++++++++++--------- Core/gb.h | 6 +++--- Core/mbc.c | 11 +++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 71f0861e..8e7151b4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1420,15 +1420,20 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } if (cartridge->mbc_type) { - static const char * const mapper_names[] = { - [GB_MBC1] = "MBC1", - [GB_MBC2] = "MBC2", - [GB_MBC3] = "MBC3", - [GB_MBC5] = "MBC5", - [GB_HUC1] = "HUC1", - [GB_HUC3] = "HUC3", - }; - GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); + if (gb->is_mbc30) { + GB_log(gb, "MBC30\n"); + } + else { + static const char *const mapper_names[] = { + [GB_MBC1] = "MBC1", + [GB_MBC2] = "MBC2", + [GB_MBC3] = "MBC3", + [GB_MBC5] = "MBC5", + [GB_HUC1] = "HUC1", + [GB_HUC3] = "HUC3", + }; + GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); + } GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); if (cartridge->has_ram) { GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); diff --git a/Core/gb.h b/Core/gb.h index b4ef6580..0b188d62 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -403,9 +403,8 @@ struct GB_gameboy_internal_s { } mbc2; struct { - uint8_t rom_bank:7; - uint8_t padding:1; - uint8_t ram_bank:4; + uint8_t rom_bank:8; + uint8_t ram_bank:3; } mbc3; struct { @@ -538,6 +537,7 @@ struct GB_gameboy_internal_s { GB_STANDARD_MBC1_WIRING, GB_MBC1M_WIRING, } mbc1_wiring; + bool is_mbc30; unsigned pending_cycles; diff --git a/Core/mbc.c b/Core/mbc.c index d3791a18..2a96219d 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -86,6 +86,10 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) case GB_MBC3: gb->mbc_rom_bank = gb->mbc3.rom_bank; gb->mbc_ram_bank = gb->mbc3.ram_bank; + if (!gb->is_mbc30) { + gb->mbc_rom_bank &= 0x7F; + gb->mbc_ram_bank &= 0x3; + } if (gb->mbc_rom_bank == 0) { gb->mbc_rom_bank = 1; } @@ -147,6 +151,13 @@ void GB_configure_cart(GB_gameboy_t *gb) } } + /* Detect MBC30 */ + if (gb->cartridge_type->mbc_type == GB_MBC3) { + if (gb->rom_size > 0x200000 || gb->mbc_ram_size > 0x8000) { + gb->is_mbc30 = true; + } + } + /* Set MBC5's bank to 1 correctly */ if (gb->cartridge_type->mbc_type == GB_MBC5) { gb->mbc5.rom_bank_low = 1; From d8e89f5114cb58b754baee4e1941bef21d42b861 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 8 Apr 2020 19:17:45 +0300 Subject: [PATCH 1025/1216] Fix banked 16-bit assignments; fixes #245 --- Core/debugger.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 8e7151b4..65d98857 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -220,7 +220,8 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) banking_state_t state; save_banking_state(gb, &state); switch_banking_state(gb, lvalue.memory_address.bank); - value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | + (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); restore_banking_state(gb, &state); return r; } @@ -261,6 +262,7 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) save_banking_state(gb, &state); switch_banking_state(gb, lvalue.memory_address.bank); GB_write_memory(gb, lvalue.memory_address.value, value); + GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8); restore_banking_state(gb, &state); return; } From 92d6cc6394f5c7f8114842af271e2b47ab98ff29 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Apr 2020 02:36:27 +0300 Subject: [PATCH 1026/1216] Use official register names --- Core/gb.h | 6 +++--- Core/memory.c | 14 +++++++------- Misc/registers.sym | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 0b188d62..3b83b6d2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -185,7 +185,7 @@ enum { // Unfortunately it is not readable or writable after boot has finished, so research of this // register is quite limited. The value written to this register, however, can be controlled // in some cases. - GB_IO_MODE = 0x4c, + GB_IO_KEY0 = 0x4c, /* General CGB features */ GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch @@ -193,7 +193,7 @@ enum { /* Missing */ GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank - GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping + GB_IO_BANK = 0x50, // Write to disable the BIOS mapping /* CGB DMA */ GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High @@ -212,7 +212,7 @@ enum { 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_OBJECT_PRIORITY = 0x6c, // Affects object priority (X based or index based) + GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based) /* Missing */ diff --git a/Core/memory.c b/Core/memory.c index 148c9867..27f58e18 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -295,11 +295,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->io_registers[GB_IO_TAC] | 0xF8; case GB_IO_STAT: return gb->io_registers[GB_IO_STAT] | 0x80; - case GB_IO_OBJECT_PRIORITY: + case GB_IO_OPRI: if (!GB_is_cgb(gb)) { return 0xFF; } - return gb->io_registers[GB_IO_OBJECT_PRIORITY] | 0xFE; + return gb->io_registers[GB_IO_OPRI] | 0xFE; case GB_IO_PCM_12: if (!GB_is_cgb(gb)) return 0xFF; @@ -663,8 +663,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_UNKNOWN5: gb->io_registers[addr & 0xFF] = value; return; - case GB_IO_OBJECT_PRIORITY: - if ((!gb->boot_rom_finished || (gb->io_registers[GB_IO_MODE] & 8)) && GB_is_cgb(gb)) { + case GB_IO_OPRI: + if ((!gb->boot_rom_finished || (gb->io_registers[GB_IO_KEY0] & 8)) && GB_is_cgb(gb)) { gb->io_registers[addr & 0xFF] = value; gb->object_priority = (value & 1) ? GB_OBJECT_PRIORITY_X : GB_OBJECT_PRIORITY_INDEX; } @@ -785,14 +785,14 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } return; - case GB_IO_BIOS: + case GB_IO_BANK: gb->boot_rom_finished = true; return; - case GB_IO_MODE: + case GB_IO_KEY0: if (GB_is_cgb(gb) && !gb->boot_rom_finished) { gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */ - gb->io_registers[GB_IO_MODE] = value; + gb->io_registers[GB_IO_KEY0] = value; } return; diff --git a/Misc/registers.sym b/Misc/registers.sym index ea76ab3e..3b31b745 100644 --- a/Misc/registers.sym +++ b/Misc/registers.sym @@ -41,10 +41,10 @@ 00:FF49 IO_OBP1 00:FF4A IO_WY 00:FF4B IO_WX -00:FF4C IO_DMG_EMULATION +00:FF4C IO_KEY0 00:FF4D IO_KEY1 00:FF4F IO_VBK -00:FF50 IO_BIOS +00:FF50 IO_BANK 00:FF51 IO_HDMA1 00:FF52 IO_HDMA2 00:FF53 IO_HDMA3 @@ -55,7 +55,7 @@ 00:FF69 IO_BGPD 00:FF6A IO_OBPI 00:FF6B IO_OBPD -00:FF6C IO_OBJECT_PRIORITY +00:FF6C IO_OPRI 00:FF70 IO_SVBK 00:FF72 IO_UNKNOWN2 00:FF73 IO_UNKNOWN3 From a9cd3f2c1195e874f6368792d057aa61ee1353ba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Apr 2020 14:21:07 +0300 Subject: [PATCH 1027/1216] Fix operator priorities, fix parsing debugger bug --- Core/debugger.c | 56 ++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 65d98857..7d2ae897 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -334,7 +334,7 @@ static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.va static struct { const char *string; - char priority; + int8_t priority; value_t (*operator)(value_t, value_t); value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); } operators[] = @@ -352,15 +352,15 @@ static struct { {"&", 1, and}, {"^", 1, xor}, {"<<", 2, shleft}, - {"<=", -1, lower_equals}, - {"<", -1, lower}, + {"<=", 3, lower_equals}, + {"<", 3, lower}, {">>", 2, shright}, - {">=", -1, greater_equals}, - {">", -1, greater}, - {"==", -1, equals}, - {"=", -2, NULL, assign}, - {"!=", -1, different}, - {":", 3, bank}, + {">=", 3, greater_equals}, + {">", 3, greater}, + {"==", 3, equals}, + {"=", -1, NULL, assign}, + {"!=", 3, different}, + {":", 4, bank}, }; value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, @@ -388,8 +388,8 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, } if (string[0] == '(' && string[length - 1] == ')') { // Attempt to strip parentheses - signed int depth = 0; - for (int i = 0; i < length; i++) { + signed depth = 0; + for (unsigned i = 0; i < length; i++) { if (string[i] == '(') depth++; if (depth == 0) { // First and last are not matching @@ -402,8 +402,8 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, } 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++) { + signed depth = 0; + for (unsigned i = 0; i < length; i++) { if (string[i] == '[') depth++; if (depth == 0) { // First and last are not matching @@ -418,8 +418,8 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, } else if (string[0] == '{' && string[length - 1] == '}') { // Attempt to strip curly parentheses (memory dereference) - signed int depth = 0; - for (int i = 0; i < length; i++) { + signed depth = 0; + for (unsigned i = 0; i < length; i++) { if (string[i] == '{') depth++; if (depth == 0) { // First and last are not matching @@ -495,8 +495,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (string[0] == '(' && string[length - 1] == ')') { // Attempt to strip parentheses - signed int depth = 0; - for (int i = 0; i < length; i++) { + signed depth = 0; + for (unsigned i = 0; i < length; i++) { if (string[i] == '(') depth++; if (depth == 0) { // First and last are not matching @@ -512,8 +512,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } 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++) { + signed depth = 0; + for (unsigned i = 0; i < length; i++) { if (string[i] == '[') depth++; if (depth == 0) { // First and last are not matching @@ -539,8 +539,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } else if (string[0] == '{' && string[length - 1] == '}') { // Attempt to strip curly parentheses (memory dereference) - signed int depth = 0; - for (int i = 0; i < length; i++) { + signed depth = 0; + for (unsigned i = 0; i < length; i++) { if (string[i] == '{') depth++; if (depth == 0) { // First and last are not matching @@ -565,7 +565,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } } // Search for lowest priority operator - signed int depth = 0; + signed depth = 0; unsigned operator_index = -1; unsigned operator_pos = 0; for (int i = 0; i < length; i++) { @@ -574,15 +574,15 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, 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. - unsigned long operator_length = strlen(operators[j].string); + for (unsigned j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) { + unsigned operator_length = strlen(operators[j].string); + if (operator_length > length - i) continue; // Operator too long + if (memcmp(string + i, operators[j].string, operator_length) == 0) { if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) { /* for supporting = vs ==, etc*/ i += operator_length - 1; - continue; + break; } // Found an operator! operator_pos = i; @@ -669,7 +669,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } char *end; - int base = 10; + unsigned base = 10; if (string[0] == '$') { string++; base = 16; From a6567d9ee16141d7e2e39f9b93251564613a99f8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Apr 2020 14:32:52 +0300 Subject: [PATCH 1028/1216] Update old coding style --- Cocoa/Document.m | 10 +++++----- Core/apu.c | 2 +- Core/camera.c | 6 +++--- Core/debugger.c | 20 ++++++++++---------- Core/display.c | 2 +- Core/gb.c | 2 +- Core/gb.h | 14 +++++++------- Core/mbc.c | 2 +- Core/sm83_cpu.c | 14 +++++++------- Core/symbol_hash.c | 8 ++++---- SDL/gui.c | 6 +++--- SDL/shader.c | 2 +- 12 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index b8352338..92d5f973 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -731,8 +731,8 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) NSRect rect = window.contentView.frame; - int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; - int step = width / [[window screen] backingScaleFactor]; + unsigned titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; + unsigned step = width / [[window screen] backingScaleFactor]; rect.size.width = floor(rect.size.width / step) * step + step; rect.size.height = rect.size.width * height / width + titlebarSize; @@ -1468,7 +1468,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; if (tableView == self.paletteTableView) { if (columnIndex == 0) { - return [NSString stringWithFormat:@"%s %d", row >=8 ? "Object" : "Background", (int)(row & 7)]; + return [NSString stringWithFormat:@"%s %u", row >=8 ? "Object" : "Background", (unsigned)(row & 7)]; } uint8_t *palette_data = GB_get_direct_access(&gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, NULL, NULL); @@ -1486,9 +1486,9 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) height:oamHeight scale:16.0/oamHeight]; case 1: - return @((int)oamInfo[row].x - 8); + return @((unsigned)oamInfo[row].x - 8); case 2: - return @((int)oamInfo[row].y - 16); + return @((unsigned)oamInfo[row].y - 16); case 3: return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile]; case 4: diff --git a/Core/apu.c b/Core/apu.c index c3be5338..feda3c89 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -548,7 +548,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) { if (reg == GB_IO_NR52) { uint8_t value = 0; - for (int i = 0; i < GB_N_CHANNELS; i++) { + for (unsigned i = 0; i < GB_N_CHANNELS; i++) { value >>= 1; if (gb->apu.is_active[i]) { value |= 0x8; diff --git a/Core/camera.c b/Core/camera.c index 9b34998a..bef84890 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -1,17 +1,17 @@ #include "gb.h" -static int noise_seed = 0; +static signed noise_seed = 0; /* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */ static uint8_t generate_noise(uint8_t x, uint8_t y) { - int value = (x + y * 128 + noise_seed); + signed value = (x + y * 128 + noise_seed); uint8_t *data = (uint8_t *) &value; unsigned hash = 0; - while ((int *) data != &value + 1) { + while ((signed *) data != &value + 1) { hash ^= (*data << 8); if (hash & 0x8000) { hash ^= 0x8a00; diff --git a/Core/debugger.c b/Core/debugger.c index 7d2ae897..4e3bd639 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -568,7 +568,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, signed depth = 0; unsigned operator_index = -1; unsigned operator_pos = 0; - for (int i = 0; i < length; i++) { + for (unsigned i = 0; i < length; i++) { if (string[i] == '(') depth++; else if (string[i] == ')') depth--; else if (string[i] == '[') depth++; @@ -841,8 +841,8 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) uint32_t key = BP_KEY(addr); - int min = 0; - int max = gb->n_breakpoints; + unsigned min = 0; + unsigned max = gb->n_breakpoints; while (min < max) { uint16_t pivot = (min + max) / 2; if (gb->breakpoints[pivot].key == key) return pivot; @@ -1008,8 +1008,8 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) return 0; } uint32_t key = WP_KEY(addr); - int min = 0; - int max = gb->n_watchpoints; + unsigned min = 0; + unsigned max = gb->n_watchpoints; while (min < max) { uint16_t pivot = (min + max) / 2; if (gb->watchpoints[pivot].key == key) return pivot; @@ -1342,7 +1342,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de while (count) { GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); - for (int i = 0; i < 16 && count; i++) { + for (unsigned i = 0; i < 16 && count; i++) { GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); count--; } @@ -1355,7 +1355,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de else { while (count) { GB_log(gb, "%04x: ", addr.value); - for (int i = 0; i < 16 && count; i++) { + for (unsigned i = 0; i < 16 && count; i++) { GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); count--; } @@ -1493,7 +1493,7 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu return true; } - GB_log(gb, "Ticks: %lu. (Resetting)\n", gb->debugger_ticks); + GB_log(gb, "Ticks: %llu. (Resetting)\n", (unsigned long long)gb->debugger_ticks); gb->debugger_ticks = 0; return true; @@ -2197,13 +2197,13 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) void GB_debugger_clear_symbols(GB_gameboy_t *gb) { - for (int i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) { + for (unsigned i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) { if (gb->bank_symbols[i]) { GB_map_free(gb->bank_symbols[i]); gb->bank_symbols[i] = 0; } } - for (int i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) { + for (unsigned i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) { while (gb->reversed_symbol_map.buckets[i]) { GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next; free(gb->reversed_symbol_map.buckets[i]); diff --git a/Core/display.c b/Core/display.c index a7dda7d8..356e7425 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1421,7 +1421,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h GB_object_t *sprite = (GB_object_t *) &gb->oam; uint8_t sprites_in_line = 0; for (uint8_t i = 0; i < 40; i++, sprite++) { - int sprite_y = sprite->y - 16; + signed sprite_y = sprite->y - 16; bool obscured = false; // Is sprite not in this line? if (sprite_y > y || sprite_y + *sprite_height <= y) continue; diff --git a/Core/gb.c b/Core/gb.c index 31fff0d5..ba3f20a5 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -709,7 +709,7 @@ void GB_set_infrared_input(GB_gameboy_t *gb, bool state) gb->ir_queue_length = 0; } -void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change) +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change) { if (gb->ir_queue_length == GB_MAX_IR_QUEUE) { GB_log(gb, "IR Queue is full\n"); diff --git a/Core/gb.h b/Core/gb.h index 3b83b6d2..1f3bacfb 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -265,7 +265,7 @@ 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, uint8_t r, uint8_t g, uint8_t b); -typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); +typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update); typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); @@ -278,7 +278,7 @@ typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type typedef struct { bool state; - long delay; + uint64_t delay; } GB_ir_queue_item_t; struct GB_breakpoint_s; @@ -587,8 +587,8 @@ struct GB_gameboy_internal_s { GB_boot_rom_load_callback_t boot_rom_load_callback; /* IR */ - long cycles_since_ir_change; // In 8MHz units - long cycles_since_input_ir_change; // In 8MHz units + uint64_t cycles_since_ir_change; // In 8MHz units + uint64_t cycles_since_input_ir_change; // In 8MHz units GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; size_t ir_queue_length; @@ -605,7 +605,7 @@ struct GB_gameboy_internal_s { /* SLD (Todo: merge with backtrace) */ bool stack_leak_detection; - int debug_call_depth; + signed debug_call_depth; uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ uint16_t addr_for_call_depth[0x200]; @@ -626,7 +626,7 @@ struct GB_gameboy_internal_s { GB_reversed_symbol_map_t reversed_symbol_map; /* Ticks command */ - unsigned long debugger_ticks; + uint64_t debugger_ticks; /* Rewind */ #define GB_REWIND_FRAMES_PER_KEY 255 @@ -732,7 +732,7 @@ void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode); void GB_set_infrared_input(GB_gameboy_t *gb, bool state); -void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); /* In 8MHz units*/ +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change); /* In 8MHz units*/ 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); diff --git a/Core/mbc.c b/Core/mbc.c index 2a96219d..2ee53e81 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -132,7 +132,7 @@ void GB_configure_cart(GB_gameboy_t *gb) gb->mbc_ram_size = 0x200; } else { - static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; + static const unsigned 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); diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index da980eb0..2b7b1dd6 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -471,7 +471,7 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if ( ((unsigned long) hl + (unsigned long) rr) & 0x10000) { + if ( ((unsigned) hl + (unsigned) rr) & 0x10000) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } @@ -802,7 +802,7 @@ static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) if ((a & 0xF) + (value & 0xF) > 0x0F) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + if (((unsigned) a) + ((unsigned) value) > 0xFF) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } @@ -821,7 +821,7 @@ static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) 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) { + if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } @@ -857,7 +857,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) if ((a & 0xF) < (value & 0xF) + carry) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } @@ -1001,7 +1001,7 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) if ((a & 0xF) + (value & 0xF) > 0x0F) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + if (((unsigned) a) + ((unsigned) value) > 0xFF) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } @@ -1020,7 +1020,7 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) 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) { + if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } @@ -1056,7 +1056,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) if ((a & 0xF) < (value & 0xF) + carry) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } - if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; } } diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 208e72d6..33e33996 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -71,9 +71,9 @@ void GB_map_free(GB_symbol_map_t *map) free(map); } -static int hash_name(const char *name) +static unsigned hash_name(const char *name) { - int r = 0; + unsigned r = 0; while (*name) { r <<= 1; if (r & 0x400) { @@ -87,7 +87,7 @@ static int hash_name(const char *name) void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol) { - int hash = hash_name(bank_symbol->name); + unsigned hash = hash_name(bank_symbol->name); GB_symbol_t *symbol = malloc(sizeof(*symbol)); symbol->name = bank_symbol->name; symbol->addr = bank_symbol->addr; @@ -98,7 +98,7 @@ void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name) { - int hash = hash_name(name); + unsigned hash = hash_name(name); GB_symbol_t *symbol = map->buckets[hash]; while (symbol) { diff --git a/SDL/gui.c b/SDL/gui.c index 8bc65518..201452a3 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -142,8 +142,8 @@ void update_viewport(void) double y_factor = win_height / (double) GB_get_screen_height(&gb); if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { - x_factor = (int)(x_factor); - y_factor = (int)(y_factor); + x_factor = (unsigned)(x_factor); + y_factor = (unsigned)(y_factor); } if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { @@ -1265,7 +1265,7 @@ void run_gui(bool is_running) } if (item->value_getter && !item->backwards_handler) { char line[25]; - snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (int)strlen(item->string), item->value_getter(i)); + snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (unsigned)strlen(item->string), item->value_getter(i)); draw_text_centered(pixels, width, height, y + y_offset, line, gui_palette_native[3], gui_palette_native[0], i == current_selection ? DECORATION_SELECTION : DECORATION_NONE); y += 12; diff --git a/SDL/shader.c b/SDL/shader.c index 250046b1..de2ba564 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -75,7 +75,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) static char master_shader_code[0x801] = {0,}; static char shader_code[0x10001] = {0,}; static char final_shader_code[0x10801] = {0,}; - static signed long filter_token_location = 0; + static ssize_t filter_token_location = 0; if (!master_shader_code[0]) { FILE *master_shader_f = fopen(resource_path("Shaders/MasterShader.fsh"), "r"); From 4a21dd323226272b2c66ae2cc4236e13f98e2514 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Apr 2020 15:29:49 +0300 Subject: [PATCH 1029/1216] The Cocoa sidebar is now resizeable and collapseable --- Cocoa/Document.h | 5 +- Cocoa/Document.m | 38 ++++++ Cocoa/Document.xib | 304 ++++++++++++++++++++++++-------------------- Cocoa/GBSplitView.h | 15 +++ Cocoa/GBSplitView.m | 28 ++++ 5 files changed, 251 insertions(+), 139 deletions(-) create mode 100644 Cocoa/GBSplitView.h create mode 100644 Cocoa/GBSplitView.m diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 412e6ffd..0a7f7e82 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -1,8 +1,9 @@ #import #include "GBView.h" #include "GBImageView.h" +#include "GBSplitView.h" -@interface Document : NSDocument +@interface Document : NSDocument @property (strong) IBOutlet GBView *view; @property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSPanel *consoleWindow; @@ -30,6 +31,8 @@ @property (strong) IBOutlet NSButton *feedSaveButton; @property (strong) IBOutlet NSTextView *debuggerSideViewInput; @property (strong) IBOutlet NSTextView *debuggerSideView; +@property (strong) IBOutlet GBSplitView *debuggerSplitView; +@property (strong) IBOutlet NSBox *debuggerVerticalLine; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 92d5f973..b7b56cf1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -505,6 +505,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [self.feedSaveButton removeFromSuperview]; self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[self.fileURL path] lastPathComponent]]; + self.debuggerSplitView.dividerColor = [NSColor clearColor]; /* contentView.superview.subviews.lastObject is the titlebar view */ NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject; @@ -1661,4 +1662,41 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } +- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview; +{ + if ([[splitView arrangedSubviews] lastObject] == subview) { + return YES; + } + return NO; +} + +- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex +{ + return 600; +} + +- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { + return splitView.frame.size.width - 321; +} + +- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { + if ([[splitView arrangedSubviews] lastObject] == view) { + return NO; + } + return YES; +} + +- (void)splitViewDidResizeSubviews:(NSNotification *)notification +{ + NSSplitView *splitview = notification.object; + if ([[[splitview arrangedSubviews] firstObject] frame].size.width < 600) { + [splitview setPosition:600 ofDividerAtIndex:0]; + } + /* NSSplitView renders its separator without the proper vibrancy, so we made it transparent and move an + NSBox-based separator that renders properly so it acts like the split view's separator. */ + NSRect rect = self.debuggerVerticalLine.frame; + rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 1; + self.debuggerVerticalLine.frame = rect; +} + @end diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index ae9cf909..c680df51 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -1,8 +1,8 @@ - + - + @@ -14,6 +14,8 @@ + + @@ -39,7 +41,7 @@ - + @@ -50,11 +52,11 @@ - + - + @@ -67,7 +69,7 @@ - + @@ -78,38 +80,7 @@ - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - + @@ -124,83 +95,140 @@ - + - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + - + @@ -208,7 +236,7 @@ - + @@ -248,7 +276,7 @@ - + @@ -265,7 +293,7 @@ - + @@ -284,7 +312,7 @@ - + @@ -292,13 +320,13 @@ - + - + - + @@ -307,17 +335,17 @@ - + - + - + @@ -326,7 +354,7 @@ - + @@ -358,7 +386,7 @@ - - + @@ -397,7 +425,7 @@ - + @@ -430,7 +458,7 @@ - + @@ -448,7 +476,7 @@ - + @@ -474,10 +502,10 @@ - + - + @@ -597,11 +625,11 @@ - - + @@ -765,7 +793,7 @@ - + diff --git a/Cocoa/GBSplitView.h b/Cocoa/GBSplitView.h new file mode 100644 index 00000000..143b8f63 --- /dev/null +++ b/Cocoa/GBSplitView.h @@ -0,0 +1,15 @@ +// +// GBSplitView.h +// SameBoySDL +// +// Created by Lior Halphon on 9/4/20. +// Copyright © 2020 Lior Halphon. All rights reserved. +// + +#import + +@interface GBSplitView : NSSplitView + +-(void) setDividerColor:(NSColor *)color; + +@end diff --git a/Cocoa/GBSplitView.m b/Cocoa/GBSplitView.m new file mode 100644 index 00000000..425d9654 --- /dev/null +++ b/Cocoa/GBSplitView.m @@ -0,0 +1,28 @@ +// +// GBSplitView.m +// SameBoySDL +// +// Created by Lior Halphon on 9/4/20. +// Copyright © 2020 Lior Halphon. All rights reserved. +// + +#import "GBSplitView.h" + +@implementation GBSplitView +{ + NSColor *_dividerColor; +} + +- (void)setDividerColor:(NSColor *)color { + _dividerColor = color; + [self setNeedsDisplay:YES]; +} + +- (NSColor *)dividerColor { + if (_dividerColor) { + return _dividerColor; + } + return [super dividerColor]; +} + +@end From 1d80c185d85314c7d23b3e07d821ab1c3f88ea3b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Apr 2020 17:25:14 +0300 Subject: [PATCH 1030/1216] Remove IDE comment --- Cocoa/GBSplitView.h | 8 -------- Cocoa/GBSplitView.m | 8 -------- 2 files changed, 16 deletions(-) diff --git a/Cocoa/GBSplitView.h b/Cocoa/GBSplitView.h index 143b8f63..7b2faa2c 100644 --- a/Cocoa/GBSplitView.h +++ b/Cocoa/GBSplitView.h @@ -1,11 +1,3 @@ -// -// GBSplitView.h -// SameBoySDL -// -// Created by Lior Halphon on 9/4/20. -// Copyright © 2020 Lior Halphon. All rights reserved. -// - #import @interface GBSplitView : NSSplitView diff --git a/Cocoa/GBSplitView.m b/Cocoa/GBSplitView.m index 425d9654..0a30fe0c 100644 --- a/Cocoa/GBSplitView.m +++ b/Cocoa/GBSplitView.m @@ -1,11 +1,3 @@ -// -// GBSplitView.m -// SameBoySDL -// -// Created by Lior Halphon on 9/4/20. -// Copyright © 2020 Lior Halphon. All rights reserved. -// - #import "GBSplitView.h" @implementation GBSplitView From 337e74352d5666c20ebe4a5982fa3424a5c8406b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 9 Apr 2020 20:11:55 +0300 Subject: [PATCH 1031/1216] Add cheats API, with GameShark and GameGenie import --- Core/cheats.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++ Core/cheats.h | 33 ++++++++++ Core/gb.c | 3 + Core/gb.h | 6 ++ Core/memory.c | 6 +- 5 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 Core/cheats.c create mode 100644 Core/cheats.h diff --git a/Core/cheats.c b/Core/cheats.c new file mode 100644 index 00000000..36b4afb7 --- /dev/null +++ b/Core/cheats.c @@ -0,0 +1,173 @@ +#include "gb.h" +#include "cheats.h" +#include + +static inline uint8_t hash_addr(uint16_t addr) +{ + return addr; +} + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} + +void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) +{ + const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)]; + if (hash) { + for (unsigned i = 0; i < hash->size; i++) { + GB_cheat_t *cheat = hash->cheats[i]; + if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) { + if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) { + *value = cheat->value; + break; + } + } + } + } +} + +void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) +{ + GB_cheat_t *cheat = malloc(sizeof(*cheat)); + cheat->address = address; + cheat->bank = bank; + cheat->value = value; + cheat->old_value = old_value; + cheat->use_old_value = use_old_value; + cheat->enabled = enabled; + strncpy(cheat->description, description, sizeof(cheat->description)); + cheat->description[sizeof(cheat->description) - 1] = 0; + gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(*cheat)); + gb->cheats[gb->cheat_count - 1] = cheat; + + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)]; + if (!*hash) { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat)); + (*hash)->size = 1; + (*hash)->cheats[0] = cheat; + } + else { + (*hash)->size++; + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + (*hash)->cheats[(*hash)->size - 1] = cheat; + } +} + +const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size) +{ + *size = gb->cheat_count; + return (void *)gb->cheats; +} +void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat) +{ + for (unsigned i = 0; i < gb->cheat_count; i++) { + if (gb->cheats[i] == cheat) { + gb->cheats[i] = gb->cheats[--gb->cheat_count]; + if (gb->cheat_count == 0) { + free(gb->cheats); + gb->cheats = NULL; + } + else { + gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat)); + } + break; + } + } + + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; + for (unsigned i = 0; i < (*hash)->size; i++) { + if ((*hash)->cheats[i] == cheat) { + (*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; + if ((*hash)->size == 0) { + free(*hash); + *hash = NULL; + } + else { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + } + break; + } + } + + free((void *)cheat); +} + +bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled) +{ + uint8_t dummy; + /* GameShark */ + { + uint8_t bank; + uint8_t value; + uint16_t address; + if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) { + if (address > 0x7FFF) { + return false; + } + if (bank >= 0x80) { + bank &= 0xF; + } + GB_add_cheat(gb, description, address, bank, value, 0, false, enabled); + return true; + } + } + + /* GameGnie */ + { + char stripped_cheat[10] = {0,}; + for (unsigned i = 0; i < 9 && *cheat; i++) { + stripped_cheat[i] = *(cheat++); + while (*cheat == '-') { + cheat++; + } + } + + // Delete the 7th character; + stripped_cheat[7] = stripped_cheat[8]; + stripped_cheat[8] = 0; + + uint8_t old_value; + uint8_t value; + uint16_t address; + if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) { + address = (uint16_t)(address >> 4) | (uint16_t)(address << 12); + address ^= 0xF000; + if (address > 0x7FFF) { + return false; + } + old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6); + old_value ^= 0xBA; + GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled); + return true; + } + + if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) { + address = (uint16_t)(address >> 4) | (uint16_t)(address << 12); + address ^= 0xF000; + if (address > 0x7FFF) { + return false; + } + GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled); + return true; + } + } + return false; +} diff --git a/Core/cheats.h b/Core/cheats.h new file mode 100644 index 00000000..c461f22e --- /dev/null +++ b/Core/cheats.h @@ -0,0 +1,33 @@ +#ifndef cheats_h +#define cheats_h +#include "gb_struct_def.h" + +#define GB_CHEAT_ANY_BANK 0xFFFF + +typedef struct GB_cheat_s GB_cheat_t; + +void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); +bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled); +const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size); +void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat); + +#ifdef GB_INTERNAL +void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); +#endif + +typedef struct { + size_t size; + GB_cheat_t *cheats[]; +} GB_cheat_hash_t; + +struct GB_cheat_s { + uint16_t address; + uint16_t bank; + uint8_t value; + uint8_t old_value; + bool use_old_value; + bool enabled; + char description[128]; +}; + +#endif diff --git a/Core/gb.c b/Core/gb.c index ba3f20a5..75538e13 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -197,6 +197,9 @@ void GB_free(GB_gameboy_t *gb) GB_debugger_clear_symbols(gb); #endif GB_rewind_free(gb); + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } memset(gb, 0, sizeof(*gb)); } diff --git a/Core/gb.h b/Core/gb.h index 1f3bacfb..8a60770e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -21,6 +21,7 @@ #include "sm83_cpu.h" #include "symbol_hash.h" #include "sgb.h" +#include "cheats.h" #define GB_STRUCT_VERSION 13 @@ -644,6 +645,11 @@ struct GB_gameboy_internal_s { double sgb_intro_jingle_phases[7]; double sgb_intro_sweep_phase; double sgb_intro_sweep_previous_sample; + + /* Cheats */ + size_t cheat_count; + GB_cheat_t **cheats; + GB_cheat_hash_t *cheat_hash[256]; /* Misc */ bool turbo; diff --git a/Core/memory.c b/Core/memory.c index 27f58e18..58b96bb4 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -435,12 +435,12 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) if (is_addr_in_dma_use(gb, addr)) { addr = gb->dma_current_src; } + uint8_t data = read_map[addr >> 12](gb, addr); + GB_apply_cheat(gb, addr, &data); if (gb->read_memory_callback) { - uint8_t data = read_map[addr >> 12](gb, addr); data = gb->read_memory_callback(gb, addr, data); - return data; } - return read_map[addr >> 12](gb, addr); + return data; } static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) From 852a6997ed869d14a5d92d97e62165dd3532cee5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Apr 2020 18:03:10 +0300 Subject: [PATCH 1032/1216] Add cheats UI to Cocoa --- Cocoa/Document.h | 1 + Cocoa/Document.m | 13 ++ Cocoa/Document.xib | 293 +++++++++++++++++++++++++++-- Cocoa/GBCheatTextFieldCell.h | 5 + Cocoa/GBCheatTextFieldCell.m | 121 ++++++++++++ Cocoa/GBCheatWindowController.h | 17 ++ Cocoa/GBCheatWindowController.m | 234 +++++++++++++++++++++++ Cocoa/GBImageCell.m | 2 +- Cocoa/GBImageView.m | 2 +- Cocoa/GBOptionalVisualEffectView.h | 6 + Cocoa/GBOptionalVisualEffectView.m | 18 ++ Cocoa/MainMenu.xib | 18 ++ Core/cheats.c | 69 ++++++- Core/cheats.h | 3 + Core/gb.h | 1 + Makefile | 2 +- 16 files changed, 782 insertions(+), 23 deletions(-) create mode 100644 Cocoa/GBCheatTextFieldCell.h create mode 100644 Cocoa/GBCheatTextFieldCell.m create mode 100644 Cocoa/GBCheatWindowController.h create mode 100644 Cocoa/GBCheatWindowController.m create mode 100644 Cocoa/GBOptionalVisualEffectView.h create mode 100644 Cocoa/GBOptionalVisualEffectView.m diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 0a7f7e82..1117c26f 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -33,6 +33,7 @@ @property (strong) IBOutlet NSTextView *debuggerSideView; @property (strong) IBOutlet GBSplitView *debuggerSplitView; @property (strong) IBOutlet NSBox *debuggerVerticalLine; +@property (strong) IBOutlet NSPanel *cheatsWindow; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index b7b56cf1..71436d33 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -706,6 +706,9 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) else if ([anItem action] == @selector(connectPrinter:)) { [(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter]; } + else if ([anItem action] == @selector(toggleCheats:)) { + [(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)]; + } return [super validateUserInterfaceItem:anItem]; } @@ -1699,4 +1702,14 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.debuggerVerticalLine.frame = rect; } +- (IBAction)showCheats:(id)sender +{ + [self.cheatsWindow makeKeyAndOrderFront:nil]; +} + +- (IBAction)toggleCheats:(id)sender +{ + GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb)); +} + @end diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index c680df51..338650bc 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -9,6 +9,7 @@ + @@ -44,8 +45,7 @@ - - + @@ -69,11 +69,10 @@ - + - - + @@ -103,7 +102,7 @@ - + @@ -228,11 +227,10 @@ - - + + - - + @@ -312,11 +310,10 @@ - - + + - - + @@ -784,10 +781,9 @@ - - - - + + + @@ -803,7 +799,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/GBCheatTextFieldCell.h b/Cocoa/GBCheatTextFieldCell.h new file mode 100644 index 00000000..473e0f30 --- /dev/null +++ b/Cocoa/GBCheatTextFieldCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBCheatTextFieldCell : NSTextFieldCell +@property bool usesAddressFormat; +@end diff --git a/Cocoa/GBCheatTextFieldCell.m b/Cocoa/GBCheatTextFieldCell.m new file mode 100644 index 00000000..611cadeb --- /dev/null +++ b/Cocoa/GBCheatTextFieldCell.m @@ -0,0 +1,121 @@ +#import "GBCheatTextFieldCell.h" + +@interface GBCheatTextView : NSTextView +@property bool usesAddressFormat; +@end + +@implementation GBCheatTextView + +- (bool)_insertText:(NSString *)string replacementRange:(NSRange)range +{ + if (range.location == NSNotFound) { + range = self.selectedRange; + } + + NSString *new = [self.string stringByReplacingCharactersInRange:range withString:string]; + if (!self.usesAddressFormat) { + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,2}|[0-9]{1,3})$" options:0 error:NULL]; + if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) { + [super insertText:string replacementRange:range]; + return true; + } + if ([regex numberOfMatchesInString:[@"$" stringByAppendingString:new] options:0 range:NSMakeRange(0, new.length + 1)]) { + [super insertText:string replacementRange:range]; + [super insertText:@"$" replacementRange:NSMakeRange(0, 0)]; + return true; + } + if ([new isEqualToString:@"$"] || [string length] == 0) { + self.string = @"$00"; + self.selectedRange = NSMakeRange(1, 2); + return true; + } + } + else { + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,3}:)?\\$[0-9a-fA-F]{1,4}$" options:0 error:NULL]; + if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) { + [super insertText:string replacementRange:range]; + return true; + } + if ([string length] == 0) { + NSUInteger index = [new rangeOfString:@":"].location; + if (index != NSNotFound) { + if (range.location > index) { + self.string = [[new componentsSeparatedByString:@":"] firstObject]; + self.selectedRange = NSMakeRange(self.string.length, 0); + return true; + } + self.string = [[new componentsSeparatedByString:@":"] lastObject]; + self.selectedRange = NSMakeRange(0, 0); + return true; + } + else if ([[self.string substringWithRange:range] isEqualToString:@":"]) { + self.string = [[self.string componentsSeparatedByString:@":"] lastObject]; + self.selectedRange = NSMakeRange(0, 0); + return true; + } + } + if ([new isEqualToString:@"$"] || [string length] == 0) { + self.string = @"$0000"; + self.selectedRange = NSMakeRange(1, 4); + return true; + } + if (([string isEqualToString:@"$"] || [string isEqualToString:@":"]) && range.length == 0 && range.location == 0) { + if ([self _insertText:@"$00:" replacementRange:range]) { + self.selectedRange = NSMakeRange(1, 2); + return true; + } + } + if ([string isEqualToString:@":"] && range.length + range.location == self.string.length) { + if ([self _insertText:@":$0" replacementRange:range]) { + self.selectedRange = NSMakeRange(self.string.length - 2, 2); + return true; + } + } + if ([string isEqualToString:@"$"]) { + if ([self _insertText:@"$0" replacementRange:range]) { + self.selectedRange = NSMakeRange(range.location + 1, 1); + return true; + } + } + } + return false; +} + +- (NSUndoManager *)undoManager +{ + return nil; +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + if (![self _insertText:string replacementRange:replacementRange]) { + NSBeep(); + } +} + +/* Private API, don't tell the police! */ +- (void)_userReplaceRange:(NSRange)range withString:(NSString *)string +{ + [self insertText:string replacementRange:range]; +} + +@end + +@implementation GBCheatTextFieldCell +{ + bool _drawing, _editing; + GBCheatTextView *_fieldEditor; +} + +- (NSTextView *)fieldEditorForView:(NSView *)controlView +{ + if (_fieldEditor) { + _fieldEditor.usesAddressFormat = self.usesAddressFormat; + return _fieldEditor; + } + _fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame]; + _fieldEditor.fieldEditor = YES; + _fieldEditor.usesAddressFormat = self.usesAddressFormat; + return _fieldEditor; +} +@end diff --git a/Cocoa/GBCheatWindowController.h b/Cocoa/GBCheatWindowController.h new file mode 100644 index 00000000..adb0bf80 --- /dev/null +++ b/Cocoa/GBCheatWindowController.h @@ -0,0 +1,17 @@ +#import +#import +#import "Document.h" + +@interface GBCheatWindowController : NSObject +@property (weak) IBOutlet NSTableView *cheatsTable; +@property (weak) IBOutlet NSTextField *addressField; +@property (weak) IBOutlet NSTextField *valueField; +@property (weak) IBOutlet NSTextField *oldValueField; +@property (weak) IBOutlet NSButton *oldValueCheckbox; +@property (weak) IBOutlet NSTextField *descriptionField; +@property (weak) IBOutlet NSTextField *importCodeField; +@property (weak) IBOutlet NSTextField *importDescriptionField; +@property (weak) IBOutlet Document *document; + +@end + diff --git a/Cocoa/GBCheatWindowController.m b/Cocoa/GBCheatWindowController.m new file mode 100644 index 00000000..994d5e1b --- /dev/null +++ b/Cocoa/GBCheatWindowController.m @@ -0,0 +1,234 @@ +#import "GBCheatWindowController.h" +#import "GBWarningPopover.h" +#import "GBCheatTextFieldCell.h" + +@implementation GBCheatWindowController + ++ (NSString *)addressStringFromCheat:(const GB_cheat_t *)cheat +{ + if (cheat->bank != GB_CHEAT_ANY_BANK) { + return [NSString stringWithFormat:@"$%x:$%04x", cheat->bank, cheat->address]; + } + return [NSString stringWithFormat:@"$%04x", cheat->address]; +} + ++ (NSString *)actionDescriptionForCheat:(const GB_cheat_t *)cheat +{ + if (cheat->use_old_value) { + return [NSString stringWithFormat:@"[%@]($%02x) = $%02x", [self addressStringFromCheat:cheat], cheat->old_value, cheat->value]; + } + return [NSString stringWithFormat:@"[%@] = $%02x", [self addressStringFromCheat:cheat], cheat->value]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return 0; + size_t cheatCount; + GB_get_cheats(gb, &cheatCount); + return cheatCount + 1; +} + +- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return nil; + size_t cheatCount; + GB_get_cheats(gb, &cheatCount); + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (row >= cheatCount && columnIndex == 0) { + return [[NSCell alloc] init]; + } + return nil; +} + +- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row +{ + size_t cheatCount; + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return nil; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (row >= cheatCount) { + switch (columnIndex) { + case 0: + return @(YES); + + case 1: + return @NO; + + case 2: + return @"Add Cheat..."; + + case 3: + return @""; + } + } + + switch (columnIndex) { + case 0: + return @(NO); + + case 1: + return @(cheats[row]->enabled); + + case 2: + return @(cheats[row]->description); + + case 3: + return [GBCheatWindowController actionDescriptionForCheat:cheats[row]]; + } + + return nil; +} + +- (IBAction)importCheat:(id)sender +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + + [self.document performAtomicBlock:^{ + if (GB_import_cheat(gb, + self.importCodeField.stringValue.UTF8String, + self.importDescriptionField.stringValue.UTF8String, + true)) { + self.importCodeField.stringValue = @""; + self.importDescriptionField.stringValue = @""; + [self.cheatsTable reloadData]; + [self tableViewSelectionDidChange:nil]; + } + else { + NSBeep(); + [GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField]; + } + }]; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + size_t cheatCount; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + [self.document performAtomicBlock:^{ + if (columnIndex == 1) { + if (row >= cheatCount) { + GB_add_cheat(gb, "New Cheat", 0, 0, 0, 0, false, true); + } + else { + GB_update_cheat(gb, cheats[row], cheats[row]->description, cheats[row]->address, cheats[row]->bank, cheats[row]->value, cheats[row]->old_value, cheats[row]->use_old_value, !cheats[row]->enabled); + } + } + else if (row < cheatCount) { + GB_remove_cheat(gb, cheats[row]); + } + }]; + [self.cheatsTable reloadData]; + [self tableViewSelectionDidChange:nil]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + + size_t cheatCount; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + unsigned row = self.cheatsTable.selectedRow; + const GB_cheat_t *cheat = NULL; + if (row >= cheatCount) { + static const GB_cheat_t template = { + .address = 0, + .bank = 0, + .value = 0, + .old_value = 0, + .use_old_value = false, + .enabled = false, + .description = "New Cheat", + }; + cheat = &template; + } + else { + cheat = cheats[row]; + } + + self.addressField.stringValue = [GBCheatWindowController addressStringFromCheat:cheat]; + self.valueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->value]; + self.oldValueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->old_value]; + self.oldValueCheckbox.state = cheat->use_old_value; + self.descriptionField.stringValue = @(cheat->description); +} + +- (void)awakeFromNib +{ + [self tableViewSelectionDidChange:nil]; + ((GBCheatTextFieldCell *)self.addressField.cell).usesAddressFormat = true; +} + +- (void)controlTextDidChange:(NSNotification *)obj +{ + [self updateCheat:nil]; +} + +- (IBAction)updateCheat:(id)sender +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + + uint16_t address = 0; + uint16_t bank = GB_CHEAT_ANY_BANK; + if ([self.addressField.stringValue rangeOfString:@":"].location != NSNotFound) { + sscanf(self.addressField.stringValue.UTF8String, "$%hx:$%hx", &bank, &address); + } + else { + sscanf(self.addressField.stringValue.UTF8String, "$%hx", &address); + } + + uint8_t value = 0; + if ([self.valueField.stringValue characterAtIndex:0] == '$') { + sscanf(self.valueField.stringValue.UTF8String, "$%02hhx", &value); + } + else { + sscanf(self.valueField.stringValue.UTF8String, "%hhd", &value); + } + + uint8_t oldValue = 0; + if ([self.oldValueField.stringValue characterAtIndex:0] == '$') { + sscanf(self.oldValueField.stringValue.UTF8String, "$%02hhx", &oldValue); + } + else { + sscanf(self.oldValueField.stringValue.UTF8String, "%hhd", &oldValue); + } + + size_t cheatCount; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + unsigned row = self.cheatsTable.selectedRow; + + [self.document performAtomicBlock:^{ + if (row >= cheatCount) { + GB_add_cheat(gb, + self.descriptionField.stringValue.UTF8String, + address, + bank, + value, + oldValue, + self.oldValueCheckbox.state, + false); + } + else { + GB_update_cheat(gb, + cheats[row], + self.descriptionField.stringValue.UTF8String, + address, + bank, + value, + oldValue, + self.oldValueCheckbox.state, + cheats[row]->enabled); + } + }]; + [self.cheatsTable reloadData]; +} + +@end diff --git a/Cocoa/GBImageCell.m b/Cocoa/GBImageCell.m index 6f54ec83..de75e0e9 100644 --- a/Cocoa/GBImageCell.m +++ b/Cocoa/GBImageCell.m @@ -3,7 +3,7 @@ @implementation GBImageCell - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { - CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGContextSetInterpolationQuality(context, kCGInterpolationNone); [super drawWithFrame:cellFrame inView:controlView]; } diff --git a/Cocoa/GBImageView.m b/Cocoa/GBImageView.m index 973625ee..47efa006 100644 --- a/Cocoa/GBImageView.m +++ b/Cocoa/GBImageView.m @@ -16,7 +16,7 @@ } - (void)drawRect:(NSRect)dirtyRect { - CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGContextSetInterpolationQuality(context, kCGInterpolationNone); [super drawRect:dirtyRect]; CGFloat y_ratio = self.frame.size.height / self.image.size.height; diff --git a/Cocoa/GBOptionalVisualEffectView.h b/Cocoa/GBOptionalVisualEffectView.h new file mode 100644 index 00000000..13550715 --- /dev/null +++ b/Cocoa/GBOptionalVisualEffectView.h @@ -0,0 +1,6 @@ +#import + +/* Fake interface so the compiler assumes it conforms to NSVisualEffectView */ +@interface GBOptionalVisualEffectView : NSVisualEffectView + +@end diff --git a/Cocoa/GBOptionalVisualEffectView.m b/Cocoa/GBOptionalVisualEffectView.m new file mode 100644 index 00000000..c28eb595 --- /dev/null +++ b/Cocoa/GBOptionalVisualEffectView.m @@ -0,0 +1,18 @@ +#import + +@interface GBOptionalVisualEffectView : NSView + +@end + +@implementation GBOptionalVisualEffectView + ++ (instancetype)allocWithZone:(struct _NSZone *)zone +{ + Class NSVisualEffectView = NSClassFromString(@"NSVisualEffectView"); + if (NSVisualEffectView) { + return (id)[NSVisualEffectView alloc]; + } + return [super allocWithZone:zone]; +} + +@end diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index ee989ee5..e56b4a05 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -345,6 +345,24 @@

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 D-^az{ literal 0 HcmV?d00001 diff --git a/Cocoa/ColorCartridge.icns b/Cocoa/ColorCartridge.icns new file mode 100644 index 0000000000000000000000000000000000000000..95dada16575547e9aba2b34b64d738ebdf64cad0 GIT binary patch literal 366311 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