Merge remote-tracking branch 'origin/master' into wasm

This commit is contained in:
Maximilian Mader 2020-06-03 06:30:42 +02:00
commit 8f03312ad6
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
141 changed files with 10577 additions and 2814 deletions

25
.github/actions/LICENSE vendored Normal file
View File

@ -0,0 +1,25 @@
Blargg's Test ROMs by Shay Green <gblargg@gmail.com>
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.

BIN
.github/actions/cgb-acid2.gbc vendored Normal file

Binary file not shown.

BIN
.github/actions/cgb_sound.gb vendored Normal file

Binary file not shown.

BIN
.github/actions/dmg-acid2.gb vendored Normal file

Binary file not shown.

BIN
.github/actions/dmg_sound-2.gb vendored Executable file

Binary file not shown.

23
.github/actions/install_deps.sh vendored Executable file
View File

@ -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

BIN
.github/actions/oam_bug-2.gb vendored Executable file

Binary file not shown.

33
.github/actions/sanity_tests.sh vendored Executable file
View File

@ -0,0 +1,33 @@
set -e
./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
set +e
FAILED_TESTS=`
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\|\
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 -q -o -E "[^/]+\.bmp" | sed s/.bmp// | sort
exit 1
fi
echo Passed all tests

36
.github/workflows/sanity.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: "Bulidability and Sanity"
on: push
jobs:
sanity:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, ubuntu-16.04]
cc: [gcc, clang]
include:
- os: macos-latest
cc: clang
extra_target: cocoa
exclude:
- os: macos-latest
cc: gcc
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: |
${{ 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: |
./.github/actions/sanity_tests.sh
- name: Upload binaries
uses: actions/upload-artifact@v1
with:
name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }}
path: build/bin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 479 B

View File

@ -6,22 +6,15 @@ Start:
ld sp, $fffe
; Clear memory VRAM
ld hl, $8000
call ClearMemoryPage
call ClearMemoryPage8000
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
; Clear chosen input palette
ldh [InputPalette], a
; Clear title checksum
ldh [TitleChecksum], a
; Clear OAM
ld h, $fe
ld c, $a0
@ -30,7 +23,19 @@ Start:
dec c
jr nz, .clearOAMLoop
; Init Audio
; 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
ld a, $80
ldh [$26], a
ldh [$11], a
@ -39,8 +44,7 @@ Start:
ldh [$25], a
ld a, $77
ldh [$24], a
call InitWaveform
ld hl, $FF30
; Init BG palette
ld a, $fc
@ -67,9 +71,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
@ -87,20 +89,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 +122,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
@ -125,43 +144,44 @@ ENDC
ld hl, BgPalettes
xor a
.expandPalettesLoop:
IF !DEF(FAST)
cpl
ENDC
; One white
ldi [hl], a
ldi [hl], a
ld [hli], a
ld [hli], 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
; Mixed with white
ld a, [de]
inc de
ldi [hl], a
inc e
or $20
ld b, a
ld a, [de]
inc de
ldi [hl], a
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
ld a, [de]
inc e
ld [hli], a
ld a, [de]
inc e
ld [hli], a
xor a
; Two blacks
ldi [hl], a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ENDC
dec c
jr nz, .expandPalettesLoop
ld hl, BgPalettes
call LoadBGPalettes64
call LoadPalettesFromHRAM
; Turn on LCD
ld a, $91
@ -170,8 +190,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
@ -183,10 +205,6 @@ IF !DEF(FAST)
ld a, $c1
call PlaySound
; Wait ~0.5 seconds
ld a, 30
ldh [WaitLoopCounter], a
.waitLoop
call GetInputPaletteIndex
call WaitFrame
@ -198,6 +216,9 @@ ELSE
call PlaySound
ENDC
call Preboot
IF DEF(AGB)
ld b, 1
ENDC
; Will be filled with NOPs
@ -206,7 +227,6 @@ BootGame:
ldh [$50], a
SECTION "MoreStuff", ROM0[$200]
; Game Palettes Data
TitleChecksums:
db $00 ; Default
@ -509,30 +529,31 @@ Palettes:
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
KeyCombinationPalettes:
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.rle"
incbin "SameBoyLogo.pb12"
AnimationColors:
dw $7FFF ; White
@ -545,9 +566,6 @@ AnimationColors:
dw $7102 ; Blue
AnimationColorsEnd:
DMGPalettes:
dw $7FFF, $32BF, $00D0, $0000
; Helper Functions
DoubleBitsAndWriteRowTwice:
call .twice
@ -594,8 +612,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
@ -634,43 +655,82 @@ ReadCGBLogoHalfTile:
ld a, e
ret
; 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:
; 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
ld hl, SameBoyLogo
ld de, SameBoyLogo_dst - 1
ld c, SameBoyLogo_length
.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...
; 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
; reaches the carry, B becomes 0 and the byte is over
scf
rl b
.loop
; If not a repeat, load a literal byte
jr c, .simple_repeat
sla b
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
.repeatLoop
ldi [hl], a
inc hl
dec c
jr nz, .repeatLoop
jr .sameboyLogoLoop
; 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
@ -697,29 +757,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
@ -759,39 +796,82 @@ 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 LoadPalettesFromHRAM
call WaitFrame
ld hl, BgPalettes
call LoadBGPalettes64
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
ld a, $ff
ldh [$00], a
call ClearVRAMViaHDMA
; Final values for CGB mode
ld de, $ff56
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
@ -804,7 +884,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
@ -822,6 +902,14 @@ ENDC
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
ldh [$6C], a ; DMG Emulation
@ -833,11 +921,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
@ -846,8 +930,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
@ -920,16 +1003,16 @@ GetPaletteIndex:
xor a
ret
LoadPalettesFromIndex: ; a = index of combination
ld b, a
; Multiply by 3
add b
add b
; optimizations in callers rely on this returning with b = 0
GetPaletteCombo:
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
@ -937,11 +1020,12 @@ 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
call LoadObjPalettes
ld c, $6A
call LoadPalettes
pop hl
bit 3, e
jr nz, .loadBGPalette
@ -949,84 +1033,48 @@ 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
jp LoadBGPalettes
jr LoadBGPalettes
BrightenColor:
LoadPalettesFromHRAM:
ld hl, BgPalettes
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 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
ld [c], a
dec d
jr nz, .loop
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
; clobbers AF and HL
GetInputPaletteIndex:
ld a, $20 ; Select directions
ldh [$00], a
@ -1034,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
@ -1051,40 +1098,24 @@ 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:
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]
call GetKeyComboPalette
call GetPaletteCombo
inc l
inc l
ld c, [hl]
ld hl, Palettes + 1
ld b, 0
ld c, a
add hl, bc
ld a, [hld]
cp $7F ; Is white color?
@ -1094,58 +1125,83 @@ 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
; Mixing code by ISSOtm
ldh a, [BgPalettes + 7 * 8 + 2]
and ~$21
ld b, a
ld a, [hli]
ldh [BgPalettes + 7 * 8 + 2], a ; Second color, 7th palette
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 + 3], a ; Second color, 7th palette
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, [hl]
ld a, [hli]
ldh [BgPalettes + 5], a ; Third color, first palette
call WaitFrame
ld hl, BgPalettes
call LoadBGPalettes64
call LoadPalettesFromHRAM
; Delay the wait loop while the user is selecting a palette
ld a, 30
ld a, 45
ldh [WaitLoopCounter], a
pop de
pop bc
pop hl
pop af
ret
ReplaceColorInAllPalettes:
ld de, 8
ld c, 8
ld c, e
.loop
ld [hl], a
add hl, de
dec c
jr nz, .loop
ret
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
@ -1155,27 +1211,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
InitWaveform:
ld hl, $FF30
; Init waveform
xor a
ld c, $10
.waveformLoop
ldi [hl], a
cpl
dec c
jr nz, .waveformLoop
ret
HDMAData:
db $88, $00, $98, $A0, $12
db $88, $00, $80, $00, $40
SECTION "ROMMax", ROM0[$900]
; Prevent us from overflowing
ds 1
BootEnd:
IF BootEnd > $900
FAIL "BootROM overflowed: {BootEnd}"
ENDC
SECTION "HRAM", HRAM[$FF80]
TitleChecksum:

View File

@ -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

View File

@ -1,62 +0,0 @@
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#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);
}

90
BootROMs/pb12.c Normal file
View File

@ -0,0 +1,90 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
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[8];
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[literals_size++] = 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);
return 0;
}

View File

@ -1,6 +1,6 @@
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate>
@property IBOutlet NSWindow *preferencesWindow;
@property (strong) IBOutlet NSView *graphicsTab;

View File

@ -1,7 +1,9 @@
#import "AppDelegate.h"
#include "GBButtons.h"
#include "GBView.h"
#include <Core/gb.h>
#import <Carbon/Carbon.h>
#import <JoyKit/JoyKit.h>
@implementation AppDelegate
{
@ -36,11 +38,20 @@
@"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),
@"GBSGBModel": @(GB_MODEL_SGB2),
@"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY),
}];
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
JOYAxes2DEmulateButtonsKey: @YES,
JOYHatsEmulateButtonsKey: @YES,
}];
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
}
- (IBAction)toggleDeveloperMode:(id)sender
@ -92,4 +103,8 @@
return YES;
}
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
}
@end

View File

@ -1,8 +1,11 @@
#import <Cocoa/Cocoa.h>
#include "GBView.h"
#include "GBImageView.h"
#include "GBSplitView.h"
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate>
@class GBCheatWindowController;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@property (strong) IBOutlet GBView *view;
@property (strong) IBOutlet NSTextView *consoleOutput;
@property (strong) IBOutlet NSPanel *consoleWindow;
@ -30,6 +33,10 @@
@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;
@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;

View File

@ -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!!! */
@ -61,6 +62,8 @@ enum model {
size_t audioBufferSize;
size_t audioBufferPosition;
size_t audioBufferNeeded;
bool borderModeChanged;
}
@property GBAudioClient *audioClient;
@ -74,8 +77,16 @@ enum model {
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
exposure:(unsigned) exposure;
- (void) gotNewSample:(GB_sample_t *)sample;
- (void) rumbleChanged:(double)amp;
- (void) loadBootROM:(GB_boot_rom_t)type;
@end
static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self loadBootROM: type];
}
static void vblank(GB_gameboy_t *gb)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
@ -131,6 +142,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;
@ -140,7 +157,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];
@ -184,26 +202,82 @@ 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) updateBorderMode
{
borderModeChanged = true;
}
- (void) updateRumbleMode
{
GB_set_rumble_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]);
}
- (void) initCommon
{
GB_init(&gb, [self internalModel]);
GB_set_user_data(&gb, (__bridge void *)(self));
GB_set_boot_rom_load_callback(&gb, (GB_boot_rom_load_callback_t)boot_rom_load);
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_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
[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);
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);
[self updateRumbleMode];
}
- (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];
if (borderModeChanged) {
dispatch_sync(dispatch_get_main_queue(), ^{
size_t previous_width = GB_get_screen_width(&gb);
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;
}
GB_set_pixels_output(&gb, self.view.pixels);
if (self.vramWindow.isVisible) {
dispatch_async(dispatch_get_main_queue(), ^{
@ -244,6 +318,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;
@ -257,6 +336,12 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
[audioLock wait];
}
if (stopping) {
memset(buffer, 0, nFrames * sizeof(*buffer));
[audioLock unlock];
return;
}
if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) {
memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer));
memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer));
@ -273,6 +358,25 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
}
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 */
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;
}
}
}
while (running) {
if (rewind) {
rewind = false;
@ -295,6 +399,25 @@ 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]);
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;
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"];
}
[_view setRumble:0];
stopping = false;
}
@ -312,21 +435,32 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
if (GB_debugger_is_stopped(&gb)) {
[self interruptDebugInputRead];
}
[audioLock lock];
stopping = true;
[audioLock signal];
[audioLock unlock];
running = false;
while (stopping);
while (stopping) {
[audioLock lock];
[audioLock signal];
[audioLock unlock];
}
GB_debugger_set_disabled(&gb, false);
}
- (void) loadBootROM
- (void) loadBootROM: (GB_boot_rom_t)type
{
static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"};
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]);
}
static NSString *const names[] = {
[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_load_boot_rom(&gb, [[self bootROMPathForName:names[type]] UTF8String]);
}
- (IBAction)reset:(id)sender
@ -338,8 +472,6 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
current_model = (enum model)[sender tag];
}
[self loadBootROM];
if (!modelsChanging && [sender tag] == MODEL_NONE) {
GB_reset(&gb);
}
@ -351,11 +483,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 */
@ -389,6 +517,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);
@ -398,9 +527,11 @@ 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];
/* Close Open Panels, if any */
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
if ([window isKindOfClass:[NSOpenPanel class]]) {
@ -422,7 +553,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);
@ -435,6 +566,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;
@ -451,6 +583,26 @@ 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"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateBorderMode)
name:@"GBBorderModeChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateRumbleMode)
name:@"GBRumbleModeChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateRewindLength)
name:@"GBRewindLengthChanged"
@ -536,11 +688,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";
@ -554,9 +708,18 @@ 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_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
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]);
}
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_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]);
GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]);
}];
@ -597,15 +760,9 @@ 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<NSValidatedUserInterfaceItem>)anItem
{
if([anItem action] == @selector(mute:)) {
if ([anItem action] == @selector(mute:)) {
[(NSMenuItem*)anItem setState:!self.audioClient.isPlaying];
}
else if ([anItem action] == @selector(togglePause:)) {
@ -615,9 +772,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;
@ -629,6 +783,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];
}
@ -655,8 +812,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;
@ -737,9 +894,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];
}
@ -754,7 +909,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;
@ -932,7 +1088,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,
@ -1170,6 +1326,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];
@ -1375,7 +1548,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);
@ -1393,9 +1566,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:
@ -1472,7 +1645,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
@ -1520,6 +1693,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:^{
@ -1563,4 +1741,52 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
}
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
{
if ([[splitView arrangedSubviews] lastObject] == subview) {
return YES;
}
return NO;
}
- (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex
{
return 600;
}
- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex
{
return splitView.frame.size.width - 321;
}
- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view
{
if ([[splitView arrangedSubviews] lastObject] == view) {
return NO;
}
return YES;
}
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
{
GBSplitView *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;
}
- (IBAction)showCheats:(id)sender
{
[self.cheatsWindow makeKeyAndOrderFront:nil];
}
- (IBAction)toggleCheats:(id)sender
{
GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb));
}
@end

View File

@ -1,19 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Document">
<connections>
<outlet property="TilemapSetButton" destination="k4c-Vg-MBu" id="Ak1-6d-B1N"/>
<outlet property="cheatWindowController" destination="v7q-gT-jHT" id="UNn-g4-2kP"/>
<outlet property="cheatsWindow" destination="4Yb-Np-JrF" id="YCZ-cj-gn5"/>
<outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/>
<outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/>
<outlet property="consoleWindow" destination="21F-Ah-yHX" id="eQ4-ug-LsT"/>
<outlet property="debuggerSideView" destination="JgV-7E-iwp" id="RaA-fw-3i1"/>
<outlet property="debuggerSideViewInput" destination="w0g-eK-jM4" id="GBf-WK-ryI"/>
<outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/>
<outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/>
<outlet property="feedImageView" destination="Ar0-nN-eop" id="wHa-St-o4G"/>
<outlet property="feedSaveButton" destination="RLc-0I-sYZ" id="Yy9-dG-xXY"/>
<outlet property="gridButton" destination="fL6-2S-Rgd" id="jtV-jh-GHC"/>
@ -39,22 +43,21 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="133" y="235" width="160" height="144"/>
<rect key="contentRect" x="0.0" y="0.0" width="160" height="144"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="160" height="144"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView id="KTk-4M-J7t" customClass="GBBorderView">
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KTk-4M-J7t" customClass="GBBorderView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view id="uqf-pe-VAF" customClass="GBView">
<view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
@ -67,49 +70,17 @@
</connections>
<point key="canvasLocation" x="293" y="347"/>
</window>
<window title="Debug Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="21F-Ah-yHX" customClass="NSPanel">
<window title="Debug Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="21F-Ah-yHX" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" HUD="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="921" height="400"/>
<rect key="contentRect" x="0.0" y="0.0" width="921" height="400"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="921" height="400"/>
<view key="contentView" id="dCP-E5-7Fi">
<rect key="frame" x="0.0" y="0.0" width="921" height="400"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView misplaced="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="oTo-zx-o6N">
<rect key="frame" x="0.0" y="25" width="600" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="600" height="376"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="600" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.14901960784313725" green="0.14901960784313725" blue="0.14901960784313725" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="600" height="376"/>
<size key="maxSize" width="1160" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
<color key="backgroundColor" red="0.16470588235294117" green="0.16470588235294117" blue="0.16470588235294117" alpha="1" colorSpace="calibratedRGB"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="3fZ-tl-Zi7">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="cwi-6E-rbh">
<rect key="frame" x="584" y="0.0" width="16" height="376"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField focusRingType="none" verticalHuggingPriority="750" mirrorLayoutDirectionWhenInternationalizing="never" id="l22-S8-uji">
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="l22-S8-uji">
<rect key="frame" x="0.0" y="0.0" width="921" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" focusRingType="none" id="p3j-nS-44f" customClass="GBTerminalTextFieldCell">
@ -124,91 +95,147 @@
<action selector="consoleInput:" target="-2" id="ylQ-vw-ARS"/>
</connections>
</textField>
<box verticalHuggingPriority="750" boxType="separator" id="960-dL-7ZY">
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="960-dL-7ZY">
<rect key="frame" x="0.0" y="23" width="921" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</box>
<scrollView misplaced="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="vts-CC-ZjQ">
<rect key="frame" x="601" y="25" width="320" height="328"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg">
<rect key="frame" x="0.0" y="0.0" width="320" height="328"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp">
<rect key="frame" x="0.0" y="0.0" width="320" height="328"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.14901960780000001" green="0.14901960780000001" blue="0.14901960780000001" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="320" height="328"/>
<size key="maxSize" width="1160" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
<color key="backgroundColor" red="0.1647058824" green="0.1647058824" blue="0.1647058824" alpha="1" colorSpace="calibratedRGB"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="J2i-lz-QJW">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="4jm-Gm-D2R">
<rect key="frame" x="304" y="0.0" width="16" height="328"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<scrollView misplaced="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" scrollerKnobStyle="dark" id="V9U-Ua-F4z">
<rect key="frame" x="601" y="354" width="320" height="47"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
<rect key="frame" x="0.0" y="0.0" width="320" height="47"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4">
<rect key="frame" x="0.0" y="0.0" width="320" height="47"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<size key="minSize" width="320" height="47"/>
<size key="maxSize" width="463" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="24d-1i-uBk">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="qgN-F8-fdB">
<rect key="frame" x="304" y="0.0" width="16" height="47"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<box verticalHuggingPriority="750" misplaced="YES" boxType="separator" id="5qI-qZ-nkh">
<rect key="frame" x="603" y="352" width="318" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</box>
<box horizontalHuggingPriority="750" misplaced="YES" boxType="separator" id="Onx-Oe-fBx">
<rect key="frame" x="600" y="25" width="5" height="376"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/>
<box horizontalHuggingPriority="750" boxType="separator" id="7bR-gM-1At">
<rect key="frame" x="590" y="25" width="5" height="376"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" heightSizable="YES"/>
</box>
<splitView dividerStyle="thin" vertical="YES" id="pUc-ZN-vl5" customClass="GBSplitView">
<rect key="frame" x="0.0" y="25" width="921" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView fixedFrame="YES" id="2rj-7i-kxc">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oTo-zx-o6N">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.14901960784313725" green="0.14901960784313725" blue="0.14901960784313725" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="591" height="376"/>
<size key="maxSize" width="1160" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
<color key="backgroundColor" red="0.16470588235294117" green="0.16470588235294117" blue="0.16470588235294117" alpha="1" colorSpace="calibratedRGB"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="3fZ-tl-Zi7">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="cwi-6E-rbh">
<rect key="frame" x="575" y="0.0" width="16" height="376"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
</customView>
<customView fixedFrame="YES" id="4Z2-33-dYY">
<rect key="frame" x="592" y="0.0" width="329" height="376"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" scrollerKnobStyle="dark" translatesAutoresizingMaskIntoConstraints="NO" id="V9U-Ua-F4z">
<rect key="frame" x="0.0" y="329" width="329" height="47"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView ambiguous="YES" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4">
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="329" height="47"/>
<size key="maxSize" width="463" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="24d-1i-uBk">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="qgN-F8-fdB">
<rect key="frame" x="313" y="0.0" width="16" height="47"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="5qI-qZ-nkh">
<rect key="frame" x="0.0" y="327" width="327" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vts-CC-ZjQ">
<rect key="frame" x="0.0" y="2" width="329" height="328"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg">
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp">
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="0.14901960780000001" green="0.14901960780000001" blue="0.14901960780000001" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="329" height="328"/>
<size key="maxSize" width="1160" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
<color key="backgroundColor" red="0.1647058824" green="0.1647058824" blue="0.1647058824" alpha="1" colorSpace="calibratedRGB"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="J2i-lz-QJW">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="4jm-Gm-D2R">
<rect key="frame" x="313" y="0.0" width="16" height="328"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
</customView>
</subviews>
<holdingPriorities>
<real value="250"/>
<real value="250"/>
</holdingPriorities>
<connections>
<outlet property="delegate" destination="-2" id="c8R-36-VRD"/>
</connections>
</splitView>
</subviews>
</view>
<point key="canvasLocation" x="347.5" y="-29"/>
</window>
<window title="Memory" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="mRm-dL-mCj" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<window title="Memory" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="mRm-dL-mCj" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="528" height="320"/>
<rect key="contentRect" x="0.0" y="0.0" width="528" height="320"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="8hr-8o-3rN">
<rect key="frame" x="0.0" y="0.0" width="528" height="320"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<toolbar key="toolbar" implicitIdentifier="D857E961-E523-4295-83F8-0849316E827C" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="82v-uB-RPi">
<allowedToolbarItems>
@ -248,7 +275,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" id="JCn-Y1-eHS">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@ -265,7 +292,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" bezelStyle="round" id="vg5-Nn-abb">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@ -284,21 +311,20 @@
<point key="canvasLocation" x="-185" y="61"/>
</window>
<menuItem title="Cartridge RAM" id="ylM-ah-PNQ"/>
<window title="VRAM Viewer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="mbr-db-iZh" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<window title="VRAM Viewer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="mbr-db-iZh" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="283" y="305" width="512" height="432"/>
<rect key="contentRect" x="0.0" y="0.0" width="512" height="432"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="GYW-dv-Um1">
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<box verticalHuggingPriority="750" boxType="separator" id="ucG-cD-wfs">
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="ucG-cD-wfs">
<rect key="frame" x="0.0" y="406" width="512" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="6vK-IP-PmP">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6vK-IP-PmP">
<rect key="frame" x="-2" y="4" width="516" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="umk-4r-VNg">
@ -307,17 +333,17 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<tabView drawsBackground="NO" type="noTabsNoBorder" initialItem="pXb-od-Wb1" id="AZz-Mh-rPA">
<tabView fixedFrame="YES" drawsBackground="NO" type="noTabsNoBorder" initialItem="pXb-od-Wb1" translatesAutoresizingMaskIntoConstraints="NO" id="AZz-Mh-rPA">
<rect key="frame" x="0.0" y="24" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Tileset" identifier="1" id="pXb-od-Wb1">
<view key="view" id="lCG-Gt-XMF">
<view key="view" ambiguous="YES" id="lCG-Gt-XMF">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="QcQ-ex-36R" customClass="GBImageView">
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QcQ-ex-36R" customClass="GBImageView">
<rect key="frame" x="0.0" y="0.0" width="512" height="384"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" id="pXc-O8-jg5"/>
@ -326,7 +352,7 @@
<outlet property="delegate" destination="-2" id="xoo-Uo-872"/>
</connections>
</imageView>
<popUpButton focusRingType="none" verticalHuggingPriority="750" id="TLv-xS-X5K">
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TLv-xS-X5K">
<rect key="frame" x="4" y="388" width="128" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="None" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="G8p-CH-PlV" id="1jI-s4-4YY">
@ -358,7 +384,7 @@
<action selector="reloadVRAMData:" target="-2" id="Qtf-p4-Rqh"/>
</connections>
</popUpButton>
<button verticalHuggingPriority="750" id="fL6-2S-Rgd">
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fL6-2S-Rgd">
<rect key="frame" x="452" y="388" width="56" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundRect" title="Grid" bezelStyle="roundedRect" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="pDn-9a-Se6">
@ -377,7 +403,7 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" misplaced="YES" id="DhM-Em-hj7">
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DhM-Em-hj7">
<rect key="frame" x="385" y="388" width="63" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundRect" title="Scrolling" bezelStyle="roundedRect" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="P2E-5t-BN9">
@ -388,7 +414,7 @@
<action selector="toggleScrollingDisplay:" target="-2" id="VhQ-9W-sjU"/>
</connections>
</button>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="LlK-tV-bjv" customClass="GBImageView">
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LlK-tV-bjv" customClass="GBImageView">
<rect key="frame" x="0.0" y="0.0" width="512" height="384"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" id="RvP-El-ILj"/>
@ -397,7 +423,7 @@
<outlet property="delegate" destination="-2" id="EAG-Zh-oRi"/>
</connections>
</imageView>
<popUpButton focusRingType="none" verticalHuggingPriority="750" misplaced="YES" id="loB-0k-Qff">
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="loB-0k-Qff">
<rect key="frame" x="4" y="388" width="128" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Palettes" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="oUH-Sa-Ec1" id="Eij-Cp-URa">
@ -430,7 +456,7 @@
<action selector="reloadVRAMData:" target="-2" id="BmB-JE-W8g"/>
</connections>
</popUpButton>
<popUpButton focusRingType="none" verticalHuggingPriority="750" misplaced="YES" id="YIJ-Qc-SIZ">
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YIJ-Qc-SIZ">
<rect key="frame" x="135" y="388" width="96" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Tilemap" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="XRF-Vj-3gs" id="3W1-Db-wDn">
@ -448,7 +474,7 @@
<action selector="reloadVRAMData:" target="-2" id="xwp-Ju-p00"/>
</connections>
</popUpButton>
<popUpButton focusRingType="none" verticalHuggingPriority="750" misplaced="YES" id="k4c-Vg-MBu">
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k4c-Vg-MBu">
<rect key="frame" x="235" y="388" width="96" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Tileset" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="CRe-dX-rzY" id="h53-sb-Odg">
@ -474,10 +500,10 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="krD-gH-o5I">
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="krD-gH-o5I">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" id="3VT-AA-xVT">
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="3VT-AA-xVT">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
@ -597,11 +623,11 @@
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="n53-qA-NpY">
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="n53-qA-NpY">
<rect key="frame" x="0.0" y="392" width="512" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="mqp-NY-g8d">
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="mqp-NY-g8d">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
@ -618,12 +644,12 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView borderType="none" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" id="iSs-Ow-wwb">
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="iSs-Ow-wwb">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" id="bP9-su-zQw">
<clipView key="contentView" ambiguous="YES" id="bP9-su-zQw">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" id="gfC-d3-dmq">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
@ -659,7 +685,7 @@
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn identifier="" width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="syl-os-nSf">
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="syl-os-nSf">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -672,7 +698,7 @@
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn identifier="" width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="Qw3-u2-c1s">
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="Qw3-u2-c1s">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -685,7 +711,7 @@
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn identifier="" width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="gTl-gN-qLn">
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="gTl-gN-qLn">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -706,11 +732,11 @@
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="OS3-sw-bjv">
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="OS3-sw-bjv">
<rect key="frame" x="-100" y="-100" width="510" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="4HA-2m-8TZ">
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="4HA-2m-8TZ">
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
@ -756,16 +782,15 @@
<contentBorderThickness minY="24"/>
<point key="canvasLocation" x="182" y="760"/>
</window>
<window title="Printer Feed" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="NdE-0B-WCf" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="320" height="288"/>
<window title="Printer Feed" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="NdE-0B-WCf" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="RRS-aa-bPT">
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="Ar0-nN-eop" customClass="GBImageView">
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ar0-nN-eop" customClass="GBImageView">
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageAlignment="topLeft" id="sff-hk-4nM"/>
@ -775,7 +800,7 @@
<point key="canvasLocation" x="-159" y="356"/>
</window>
<button verticalHuggingPriority="750" id="RLc-0I-sYZ">
<rect key="frame" x="0.0" y="0.0" width="48" height="25"/>
<rect key="frame" x="0.5" y="0.0" width="48" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" title="Save" bezelStyle="texturedRounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="shw-MJ-B3T">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -786,5 +811,264 @@
</connections>
<point key="canvasLocation" x="-507" y="397"/>
</button>
<window title="Cheats" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="4Yb-Np-JrF" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="gBP-5p-BTh">
<rect key="frame" x="0.0" y="0.0" width="692" height="272"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view id="fWr-0i-K1d" customClass="GBOptionalVisualEffectView">
<rect key="frame" x="0.0" y="0.0" width="294" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="hqi-ob-NW9">
<rect key="frame" x="16" y="174" width="154" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="To value:" id="Ycx-oE-aA4">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kq8-6F-9GK">
<rect key="frame" x="16" y="142" width="154" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Only if old value was: " bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="LkB-WQ-9Qd">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="updateCheat:" target="v7q-gT-jHT" id="kNc-cj-bmF"/>
</connections>
</button>
<box verticalHuggingPriority="750" boxType="separator" id="D6k-Pe-23u">
<rect key="frame" x="10" y="129" width="274" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r5T-ol-Dod">
<rect key="frame" x="16" y="107" width="270" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Import GameShark or GameGenie cheat:" id="0mf-EN-cKc">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X7K-nJ-alF">
<rect key="frame" x="39" y="78" width="233" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" focusRingType="none" placeholderString="Code" drawsBackground="YES" usesSingleLineMode="YES" id="2bz-dT-7Fi">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
<connections>
<action selector="selectText:" target="KHj-uX-Wbk" id="11z-0U-tMA"/>
</connections>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KHj-uX-Wbk">
<rect key="frame" x="39" y="47" width="233" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" focusRingType="none" placeholderString="Description" drawsBackground="YES" usesSingleLineMode="YES" id="50d-va-Cen">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="performClick:" target="C3V-Ep-bMj" id="kIN-jl-A8d"/>
</connections>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C6E-oI-hDC">
<rect key="frame" x="20" y="233" width="252" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" focusRingType="none" placeholderString="Description" drawsBackground="YES" usesSingleLineMode="YES" id="2uR-9N-hBb">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<outlet property="delegate" destination="v7q-gT-jHT" id="zyw-h0-hRP"/>
</connections>
</textField>
<button verticalHuggingPriority="750" id="C3V-Ep-bMj">
<rect key="frame" x="202.5" y="12" width="82" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" title="Import" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mMP-KW-YNy">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="importCheat:" target="v7q-gT-jHT" id="lkX-N5-wD1"/>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="qHx-1z-daR">
<rect key="frame" x="176" y="204" width="96" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" focusRingType="none" drawsBackground="YES" usesSingleLineMode="YES" id="edq-46-JeP" customClass="GBCheatTextFieldCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
<connections>
<action selector="consoleInput:" target="-2" id="sMf-aM-OvR"/>
<outlet property="delegate" destination="v7q-gT-jHT" id="79v-33-R1X"/>
</connections>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="N3I-PP-X85">
<rect key="frame" x="176" y="174" width="96" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" focusRingType="none" drawsBackground="YES" usesSingleLineMode="YES" id="CV2-D9-WsB" customClass="GBCheatTextFieldCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
<connections>
<action selector="consoleInput:" target="-2" id="SYC-cW-RjC"/>
<outlet property="delegate" destination="v7q-gT-jHT" id="P69-nT-oOt"/>
</connections>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="S6O-LB-gSj">
<rect key="frame" x="176" y="144" width="96" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" focusRingType="none" drawsBackground="YES" usesSingleLineMode="YES" id="tpM-ys-MEO" customClass="GBCheatTextFieldCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
<connections>
<action selector="consoleInput:" target="-2" id="io6-ha-QNb"/>
<outlet property="delegate" destination="v7q-gT-jHT" id="6RH-dg-SL7"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="uFo-ly-Veq">
<rect key="frame" x="16" y="204" width="152" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Change byte at address:" id="xwa-TF-eY1">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="6rU-Xg-KHc">
<rect key="frame" x="293" y="-1" width="400" height="275"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/>
<clipView key="contentView" id="mzf-yu-RID">
<rect key="frame" x="1" y="0.0" width="398" height="274"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" headerView="pvX-uJ-qK5" id="tA3-8T-bxb">
<rect key="frame" x="0.0" y="0.0" width="398" height="249"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="32" minWidth="32" maxWidth="1000" id="JNd-HW-LvS">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<buttonCell key="dataCell" type="bevel" bezelStyle="regularSquare" image="NSStopProgressFreestandingTemplate" imagePosition="only" inset="2" id="5xh-hN-jHH">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="50" minWidth="40" maxWidth="1000" id="9DZ-oW-Scx">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Enabled">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<buttonCell key="dataCell" type="check" bezelStyle="regularSquare" imagePosition="only" inset="2" id="SdJ-Xw-UAG">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn editable="NO" width="160" minWidth="40" maxWidth="1000" id="4Qa-FQ-QWY">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Description">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Description" id="1hX-Sr-bGz">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn editable="NO" width="144" minWidth="40" maxWidth="1000" id="ACq-gU-K36">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Action">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Action" id="8Sq-h9-eV7">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="v7q-gT-jHT" id="3ns-bk-yQI"/>
<outlet property="delegate" destination="v7q-gT-jHT" id="fVI-Z9-gTJ"/>
</connections>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="3Hg-LL-VqH">
<rect key="frame" x="1" y="258" width="398" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zET-KH-qF4">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="pvX-uJ-qK5">
<rect key="frame" x="0.0" y="0.0" width="398" height="25"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
</subviews>
</view>
<point key="canvasLocation" x="254" y="-463"/>
</window>
<customObject id="v7q-gT-jHT" customClass="GBCheatWindowController">
<connections>
<outlet property="addressField" destination="qHx-1z-daR" id="FWo-4u-Qse"/>
<outlet property="cheatsTable" destination="tA3-8T-bxb" id="Z2r-AQ-5th"/>
<outlet property="descriptionField" destination="C6E-oI-hDC" id="tc0-gI-FBa"/>
<outlet property="document" destination="-2" id="5NX-N4-5Rt"/>
<outlet property="importCodeField" destination="X7K-nJ-alF" id="ni0-zH-XDU"/>
<outlet property="importDescriptionField" destination="KHj-uX-Wbk" id="W0E-ec-BXk"/>
<outlet property="oldValueCheckbox" destination="Kq8-6F-9GK" id="A4a-nz-KMN"/>
<outlet property="oldValueField" destination="S6O-LB-gSj" id="fAc-OW-ZC9"/>
<outlet property="valueField" destination="N3I-PP-X85" id="0zP-9x-4LQ"/>
</connections>
</customObject>
</objects>
<resources>
<image name="NSStopProgressFreestandingTemplate" width="14" height="14"/>
</resources>
</document>

View File

@ -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);

View File

@ -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) {

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBCheatTextFieldCell : NSTextFieldCell
@property bool usesAddressFormat;
@end

View File

@ -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

View File

@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "Document.h"
@interface GBCheatWindowController : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate>
@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;
- (void)cheatsUpdated;
@end

View File

@ -0,0 +1,240 @@
#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];
}
- (void)cheatsUpdated
{
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
@end

View File

@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>
#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

View File

@ -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);
@ -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

View File

@ -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];
}

View File

@ -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;
@ -93,7 +93,7 @@
- (void)updateTrackingAreas
{
if(trackingArea != nil) {
if (trackingArea != nil) {
[self removeTrackingArea:trackingArea];
}

View File

@ -1,9 +0,0 @@
#import <AppKit/AppKit.h>
@protocol GBJoystickListener <NSObject>
- (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

View File

@ -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"]];
}
@ -13,11 +14,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.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL
sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb))
inSize:self.bounds.size
scale:scale];
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();
}

View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
/* Fake interface so the compiler assumes it conforms to NSVisualEffectView */
@interface GBOptionalVisualEffectView : NSVisualEffectView
@end

View File

@ -0,0 +1,18 @@
#import <Cocoa/Cocoa.h>
@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

View File

@ -1,17 +1,22 @@
#import <Cocoa/Cocoa.h>
#import "GBJoystickListener.h"
#import <JoyKit/JoyKit.h>
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, GBJoystickListener>
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, JOYListener>
@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;
@property (strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton;
@property (strong) IBOutlet NSPopUpButton *colorPalettePopupButton;
@property (strong) IBOutlet NSPopUpButton *displayBorderPopupButton;
@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;
@property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
@property (weak) IBOutlet NSPopUpButton *dmgPopupButton;
@property (weak) IBOutlet NSPopUpButton *sgbPopupButton;

View File

@ -9,17 +9,22 @@
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 *_frameBlendingModePopupButton;
NSPopUpButton *_colorPalettePopupButton;
NSPopUpButton *_displayBorderPopupButton;
NSPopUpButton *_rewindPopupButton;
NSButton *_aspectRatioCheckbox;
NSButton *_analogControlsCheckbox;
NSEventModifierFlags previousModifiers;
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
NSPopUpButton *_preferredJoypadButton;
NSPopUpButton *_rumbleModePopupButton;
}
+ (NSArray *)filterList
@ -31,6 +36,7 @@
@"NearestNeighbor",
@"Bilinear",
@"SmoothBilinear",
@"MonoLCD",
@"LCD",
@"CRT",
@"Scale2x",
@ -51,7 +57,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];
}
@ -84,6 +90,54 @@
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;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
[_colorPalettePopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)colorPalettePopupButton
{
return _colorPalettePopupButton;
}
- (void)setDisplayBorderPopupButton:(NSPopUpButton *)displayBorderPopupButton
{
_displayBorderPopupButton = displayBorderPopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"];
[_displayBorderPopupButton selectItemWithTag:mode];
}
- (NSPopUpButton *)displayBorderPopupButton
{
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;
@ -184,6 +238,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
@ -196,7 +256,35 @@
[[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
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBColorPalette"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
}
- (IBAction)displayBorderChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBBorderMode"];
[[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
@ -212,7 +300,6 @@
[self.skipButton setEnabled:YES];
joystick_being_configured = nil;
[self advanceConfigurationStateMachine];
last_axis = -1;
}
- (IBAction) skipButton:(id)sender
@ -223,11 +310,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 +324,95 @@
}
}
- (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;
});
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 +431,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 +556,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 +604,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

7
Cocoa/GBSplitView.h Normal file
View File

@ -0,0 +1,7 @@
#import <Cocoa/Cocoa.h>
@interface GBSplitView : NSSplitView
-(void) setDividerColor:(NSColor *)color;
- (NSArray<NSView *> *)arrangedSubviews;
@end

33
Cocoa/GBSplitView.m Normal file
View File

@ -0,0 +1,33 @@
#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];
}
/* Mavericks comaptibility */
- (NSArray<NSView *> *)arrangedSubviews
{
if (@available(macOS 10.11, *)) {
return [super arrangedSubviews];
}
else {
return [self subviews];
}
}
@end

View File

@ -173,7 +173,8 @@
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}
- (BOOL)resignFirstResponder {
- (BOOL)resignFirstResponder
{
reverse_search_mode = false;
return [super resignFirstResponder];
}

View File

@ -1,12 +1,20 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
#import "GBJoystickListener.h"
#import <JoyKit/JoyKit.h>
@interface GBView<GBJoystickListener> : NSView
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<JOYListener>
- (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;
@ -14,4 +22,5 @@
- (uint32_t *)currentBuffer;
- (uint32_t *)previousBuffer;
- (void)screenSizeChanged;
- (void)setRumble: (double)amp;
@end

View File

@ -1,4 +1,4 @@
#import <Carbon/Carbon.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
#import "GBView.h"
#import "GBViewGL.h"
#import "GBViewMetal.h"
@ -18,7 +18,11 @@
bool axisActive[2];
bool underclockKeyDown;
double clockMultiplier;
double analogClockMultiplier;
bool analogClockMultiplierValid;
NSEventModifierFlags previousModifiers;
JOYController *lastController;
GB_frame_blending_mode_t _frameBlendingMode;
}
+ (instancetype)alloc
@ -43,8 +47,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
@ -55,6 +58,7 @@
[self createInternalView];
[self addSubview:self.internalView];
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[JOYController registerListener:self];
}
- (void)screenSizeChanged
@ -65,9 +69,9 @@
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);
image_buffers[0] = calloc(1, buffer_size);
image_buffers[1] = calloc(1, buffer_size);
image_buffers[2] = calloc(1, buffer_size);
dispatch_async(dispatch_get_main_queue(), ^{
[self setFrame:self.superview.frame];
@ -79,15 +83,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 || 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
@ -100,11 +115,12 @@
[NSCursor unhide];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self setRumble:0];
[JOYController unregisterListener:self];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
if (!(self = [super initWithCoder:coder]))
{
if (!(self = [super initWithCoder:coder])) {
return self;
}
[self _init];
@ -113,8 +129,7 @@
- (instancetype)initWithFrame:(NSRect)frameRect
{
if (!(self = [super initWithFrame:frameRect]))
{
if (!(self = [super initWithFrame:frameRect])) {
return self;
}
[self _init];
@ -147,13 +162,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 +203,7 @@
switch (button) {
case GBTurbo:
GB_set_turbo_mode(_gb, true, self.isRewinding);
analogClockMultiplierValid = false;
break;
case GBRewind:
@ -189,6 +213,7 @@
case GBUnderclock:
underclockKeyDown = true;
analogClockMultiplierValid = false;
break;
default:
@ -221,6 +246,7 @@
switch (button) {
case GBTurbo:
GB_set_turbo_mode(_gb, false, false);
analogClockMultiplierValid = false;
break;
case GBRewind:
@ -229,6 +255,7 @@
case GBUnderclock:
underclockKeyDown = false;
analogClockMultiplierValid = false;
break;
default:
@ -243,123 +270,99 @@
}
}
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
- (void)setRumble:(double)amp
{
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:amp];
}
- (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) {
[self setRumble: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]);
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];
}
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;
}
}
}

View File

@ -1,4 +1,6 @@
#import "GBViewMetal.h"
#pragma clang diagnostic ignored "-Wpartial-availability"
static const vector_float2 rect[] =
{
@ -15,7 +17,7 @@ static const vector_float2 rect[] =
id<MTLBuffer> vertices;
id<MTLRenderPipelineState> pipeline_state;
id<MTLCommandQueue> command_queue;
id<MTLBuffer> mix_previous_buffer;
id<MTLBuffer> frame_blending_mode_buffer;
id<MTLBuffer> output_resolution_buffer;
vector_float2 output_resolution;
}
@ -50,15 +52,16 @@ static const vector_float2 rect[] =
view.delegate = self;
self.internalView = view;
view.paused = YES;
view.enableSetNeedsDisplay = YES;
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];
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)
@ -131,6 +134,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];
@ -145,7 +149,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]
@ -155,9 +159,8 @@ static const vector_float2 rect[] =
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
if(render_pass_descriptor != nil)
{
*(bool *)[mix_previous_buffer contents] = [self shouldBlendFrameWithPrevious];
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;
id<MTLRenderCommandEncoder> render_encoder =
@ -174,7 +177,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];
@ -205,7 +208,7 @@ static const vector_float2 rect[] =
{
[super flip];
dispatch_async(dispatch_get_main_queue(), ^{
[(MTKView *)self.internalView draw];
[(MTKView *)self.internalView setNeedsDisplay:YES];
});
}

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>SameBoy</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDocumentTypes</key>
@ -14,7 +16,7 @@
<key>CFBundleTypeIconFile</key>
<string>Cartridge</string>
<key>CFBundleTypeName</key>
<string>GameBoy Game</string>
<string>Game Boy Game</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
@ -34,7 +36,7 @@
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>GameBoy Color Game</string>
<string>Game Boy Color Game</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
@ -46,6 +48,26 @@
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbc</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy ISX File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.isx</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>SameBoy</string>
@ -70,7 +92,7 @@
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015-2019 Lior Halphon</string>
<string>Copyright © 2015-2020 Lior Halphon</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
@ -83,7 +105,7 @@
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>GameBoy Game</string>
<string>Game Boy Game</string>
<key>UTTypeIconFile</key>
<string>Cartridge</string>
<key>UTTypeIdentifier</key>
@ -102,7 +124,7 @@
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>GameBoy Color Game</string>
<string>Game Boy Color Game</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
@ -115,7 +137,28 @@
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy ISX File</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.isx</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>isx</string>
</array>
</dict>
</dict>
</array>
<key>NSCameraUsageDescription</key>
<string>SameBoy needs to access your camera to emulate the Game Boy Camera</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>

View File

@ -30,7 +30,7 @@
<h1>SameBoy</h1>
<h2>MIT License</h2>
<h3>Copyright © 2015-2019 Lior Halphon</h3>
<h3>Copyright © 2015-2020 Lior Halphon</h3>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -342,11 +342,22 @@
<action selector="mute:" target="-1" id="YE5-mi-Yzd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="YIZ-pz-N4V"/>
<menuItem title="Blend Frames" id="AWj-r8-L6U">
</items>
</menu>
</menuItem>
<menuItem title="Cheats" id="8ld-Ad-nvc">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Cheats" id="Ucc-Hm-TVA">
<items>
<menuItem title="Enable Cheats" keyEquivalent="C" id="vtx-LG-v6y">
<connections>
<action selector="toggleCheats:" target="-1" id="gsw-UY-fhu"/>
</connections>
</menuItem>
<menuItem title="Show Cheats" id="LZV-QK-YXi">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleBlend:" target="-1" id="TjO-ce-UxL"/>
<action selector="showCheats:" target="-1" id="tfr-qM-q8X"/>
</connections>
</menuItem>
</items>
@ -371,9 +382,9 @@
</items>
</menu>
</menuItem>
<menuItem title="Developer" id="IwX-DJ-dBk">
<menuItem title="Develop" id="IwX-DJ-dBk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Developer" id="UVb-cc-at0">
<menu key="submenu" title="Develop" id="UVb-cc-at0">
<items>
<menuItem title="Developer Mode" id="Qx6-Tt-zZR">
<modifierMask key="keyEquivalentModifierMask"/>
@ -454,6 +465,7 @@
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="140" y="260"/>
</menu>
</objects>
</document>

View File

@ -0,0 +1,7 @@
#import <AppKit/AppKit.h>
@implementation NSObject (MavericksCompat)
- (instancetype)initWithCoder:(NSCoder *)coder
{
return [self init];
}
@end

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -17,7 +17,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="GBPreferencesWindow">
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="GBPreferencesWindow">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@ -58,53 +58,59 @@
</defaultToolbarItems>
</toolbar>
<connections>
<outlet property="analogControlsCheckbox" destination="RuW-Db-dzW" id="FRE-hI-mnU"/>
<outlet property="aspectRatioCheckbox" destination="Vfj-tg-7OP" id="Yw0-xS-DBr"/>
<outlet property="bootROMsButton" destination="T3Y-Ln-Onl" id="tdL-Yv-E2K"/>
<outlet property="bootROMsFolderItem" destination="Dzv-Gc-zoL" id="yhV-ZI-avD"/>
<outlet property="cgbPopupButton" destination="dlD-sk-SHO" id="4tg-SR-e17"/>
<outlet property="colorCorrectionPopupButton" destination="VEz-N4-uP6" id="EO2-Vt-JFJ"/>
<outlet property="colorPalettePopupButton" destination="Iwr-eI-SD1" id="Xzc-RZ-JtV"/>
<outlet property="configureJoypadButton" destination="Qa7-Z7-yfO" id="RaX-P3-oCX"/>
<outlet property="controlsTableView" destination="UDd-IJ-fxX" id="a1D-Md-yXv"/>
<outlet property="delegate" destination="-2" id="ASc-vN-Zbq"/>
<outlet property="displayBorderPopupButton" destination="R9D-FV-bpd" id="VfO-b4-gqH"/>
<outlet property="dmgPopupButton" destination="LFw-Uk-cPR" id="KDw-i2-k4u"/>
<outlet property="frameBlendingModePopupButton" destination="lxk-db-Sxv" id="wzt-uo-TE6"/>
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
<outlet property="playerListButton" destination="gWx-7h-0xq" id="zo6-82-JId"/>
<outlet property="preferredJoypadButton" destination="0Az-0R-oNw" id="7JM-tw-BhK"/>
<outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/>
<outlet property="rumbleModePopupButton" destination="Ogs-xG-b4b" id="vuw-VN-MTp"/>
<outlet property="sgbPopupButton" destination="dza-T7-RkX" id="B0o-Nb-pIH"/>
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
</connections>
<point key="canvasLocation" x="183" y="354"/>
</window>
<customView id="sRK-wO-K6R">
<rect key="frame" x="0.0" y="0.0" width="292" height="166"/>
<rect key="frame" x="0.0" y="0.0" width="292" height="323"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="129" width="256" height="17"/>
<rect key="frame" x="18" y="286" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics Filter:" id="pXg-WY-8Q5">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="97" width="234" height="26"/>
<rect key="frame" x="30" y="254" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Nearest Neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="xDC-0T-Qg9">
<items>
<menuItem title="Nearest Neighbor (Pixelated)" state="on" id="neN-eo-LA7">
<menuItem title="Nearest neighbor (Pixelated)" state="on" id="neN-eo-LA7">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Bilinear (Blurry)" id="iDe-si-atu"/>
<menuItem title="Smooth Bilinear (Less blurry)" id="1jN-pO-1iD"/>
<menuItem title="LCD Display" id="b8u-LZ-UQf"/>
<menuItem title="CRT Display" id="FT9-FT-RZu"/>
<menuItem title="Smooth bilinear (Less blurry)" id="1jN-pO-1iD"/>
<menuItem title="Monochrome LCD display" id="b8u-LZ-UQf"/>
<menuItem title="LCD display" id="pj3-Jt-bNU"/>
<menuItem title="CRT display" id="FT9-FT-RZu"/>
<menuItem title="Scale2x" id="C1I-L2-Up1"/>
<menuItem title="Scale4x" id="uWA-Zp-JY9"/>
<menuItem title="Anti-aliased Scale2x" id="iP6-DJ-CVH"/>
@ -127,16 +133,16 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
<rect key="frame" x="18" y="75" width="256" height="17"/>
<rect key="frame" x="18" y="232" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color Correction:" id="5Si-hz-EK3">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
<rect key="frame" x="30" y="43" width="234" height="26"/>
<rect key="frame" x="30" y="200" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -146,9 +152,10 @@
<menuItem title="Disabled" state="on" id="D2J-wV-1vu">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Correct Color Curves" id="B3Q-x1-VRl"/>
<menuItem title="Emulate Hardware" id="XBL-hS-7ke"/>
<menuItem title="Preserve Brightness" id="tlx-WB-Bkw"/>
<menuItem title="Correct color curves" id="B3Q-x1-VRl"/>
<menuItem title="Emulate hardware" id="XBL-hS-7ke"/>
<menuItem title="Preserve brightness" id="tlx-WB-Bkw"/>
<menuItem title="Reduce contrast" id="wuO-Xv-0mQ"/>
</items>
</menu>
</popUpButtonCell>
@ -156,10 +163,98 @@
<action selector="colorCorrectionChanged:" target="QvC-M9-y7g" id="Oq4-B5-nO6"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MLC-Rx-FgO">
<rect key="frame" x="20" y="178" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Frame blending" id="UCa-EO-tzh">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
<rect key="frame" x="32" y="149" width="229" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Q9L-qo-kF4">
<items>
<menuItem title="Disabled" state="on" id="iHP-Yz-fiH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Simple" id="Hxy-jw-x6E"/>
<menuItem title="Accurate" id="Aaq-uy-Csa"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="franeBlendingModeChanged:" target="QvC-M9-y7g" id="kE1-pm-MIp"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8fG-zm-hpr">
<rect key="frame" x="18" y="122" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color palette for monochrome models:" id="LAN-8Y-T7H">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Iwr-eI-SD1">
<rect key="frame" x="30" y="93" width="234" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Greyscale" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Ajr-5r-iIk" id="rEU-jh-m3j">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="dHJ-3R-Ora">
<items>
<menuItem title="Greyscale" state="on" id="Ajr-5r-iIk">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Lime (Game Boy)" id="snU-ht-fQq"/>
<menuItem title="Olive (Pocket)" id="MQi-yt-nsT"/>
<menuItem title="Teal (Light)" id="xlg-6i-Fhl"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="colorPaletteChanged:" target="QvC-M9-y7g" id="ui3-rg-PTs"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Kz-cf-5X6">
<rect key="frame" x="18" y="71" width="248" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Display border:" id="HZd-qi-yyk">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R9D-FV-bpd">
<rect key="frame" x="30" y="39" width="234" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="heL-AV-0az" id="DY9-2D-h1L">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Rgf-mF-K9q">
<items>
<menuItem title="Never" state="on" tag="1" id="heL-AV-0az">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Super Game Boy only" id="bBJ-Vn-5rk"/>
<menuItem title="Always" tag="2" id="JUs-gW-qcM"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="displayBorderChanged:" target="QvC-M9-y7g" id="GoA-BU-v3h"/>
</connections>
</popUpButton>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfj-tg-7OP">
<rect key="frame" x="18" y="18" width="256" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Keep Aspect Ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6">
<buttonCell key="cell" type="check" title="Keep aspect ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
@ -168,7 +263,7 @@
</connections>
</button>
</subviews>
<point key="canvasLocation" x="-176" y="589"/>
<point key="canvasLocation" x="-176" y="667.5"/>
</customView>
<customView id="ymk-46-SX7">
<rect key="frame" x="0.0" y="0.0" width="292" height="320"/>
@ -201,7 +296,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB">
<rect key="frame" x="18" y="225" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding Duration:" id="JaO-5h-ugl">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -219,12 +314,12 @@
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ">
<rect key="frame" x="30" y="251" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Use Built-in Boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl">
<popUpButtonCell key="cell" type="push" title="Use built-in boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="edo-AN-gRh">
<items>
<menuItem title="Use Built-in Boot ROMs" state="on" id="Tnm-SR-ZEm">
<menuItem title="Use built-in boot ROMs" state="on" id="Tnm-SR-ZEm">
<connections>
<action selector="useBuiltinBootROMs:" target="QvC-M9-y7g" id="Kmo-wz-ZtB"/>
</connections>
@ -243,7 +338,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9">
<rect key="frame" x="18" y="157" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Revision:" id="GIA-ep-SBi">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -271,7 +366,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo">
<rect key="frame" x="18" y="49" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color Revision:" id="edD-t7-vwk">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color revision:" id="edD-t7-vwk">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -301,7 +396,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP">
<rect key="frame" x="18" y="103" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy Model:" id="d0g-rk-FK0">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy model:" id="d0g-rk-FK0">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -339,16 +434,16 @@
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
<rect key="frame" x="30" y="17" width="233" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC Offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="VCM-zy-2Dd">
<items>
<menuItem title="Disabled (Keep DC Offset)" state="on" id="Fgo-0S-zUG">
<menuItem title="Disabled (Keep DC offset)" state="on" id="Fgo-0S-zUG">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Accurate (Emulate Hardware)" id="82j-Vv-nE6"/>
<menuItem title="Preserve Waveform" id="iUF-c2-fgt"/>
<menuItem title="Accurate (Emulate hardware)" id="82j-Vv-nE6"/>
<menuItem title="Preserve waveform" id="iUF-c2-fgt"/>
</items>
</menu>
</popUpButtonCell>
@ -359,7 +454,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
<rect key="frame" x="18" y="49" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass Filter:" id="YLF-RL-b2D">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass filter:" id="YLF-RL-b2D">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -369,22 +464,11 @@
<point key="canvasLocation" x="-176" y="890"/>
</customView>
<customView id="8TU-6J-NCg">
<rect key="frame" x="0.0" y="0.0" width="292" height="376"/>
<rect key="frame" x="0.0" y="0.0" width="292" height="467"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
<rect key="frame" x="20" y="9" width="188" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Configure a Joypad" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
<rect key="frame" x="10" y="339" width="122" height="17"/>
<rect key="frame" x="10" y="430" width="122" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Control settings for" id="YqW-Ds-VIC">
<font key="font" metaFont="system"/>
@ -392,8 +476,17 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DZu-ts-deW">
<rect key="frame" x="18" y="87" width="105" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enable rumble:" id="QMX-3p-s1Z">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
<rect key="frame" x="32" y="117" width="240" height="211"/>
<rect key="frame" x="32" y="208" width="240" height="211"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
<rect key="frame" x="1" y="1" width="238" height="209"/>
@ -441,28 +534,28 @@
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="31h-at-Znm">
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="31h-at-Znm">
<rect key="frame" x="-100" y="-100" width="210" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="JkP-U1-jdy">
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="JkP-U1-jdy">
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fcF-wc-KwM">
<rect key="frame" x="30" y="92" width="187" height="17"/>
<rect key="frame" x="30" y="183" width="203" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Joypad for multiplayer games:" id="AJA-9b-VKI">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Controller for multiplayer games:" id="AJA-9b-VKI">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Az-0R-oNw">
<rect key="frame" x="42" y="61" width="208" height="26"/>
<rect key="frame" x="42" y="152" width="208" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hy8-cr-RrE" id="uEC-vN-8Jq">
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingMiddle" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hy8-cr-RrE" id="uEC-vN-8Jq">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="vzY-GQ-t9J">
@ -476,11 +569,11 @@
</connections>
</popUpButton>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="VEc-Ed-Z6f">
<rect key="frame" x="12" y="48" width="268" height="5"/>
<rect key="frame" x="12" y="139" width="268" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ReM-uo-H0r">
<rect key="frame" x="215" y="339" width="8" height="17"/>
<rect key="frame" x="215" y="430" width="8" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title=":" id="VhO-3T-glt">
<font key="font" metaFont="system"/>
@ -489,7 +582,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gWx-7h-0xq">
<rect key="frame" x="131" y="332" width="87" height="26"/>
<rect key="frame" x="131" y="423" width="87" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Player 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="TO3-R7-9HN" id="pbt-Lr-bU1">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -507,10 +600,39 @@
<action selector="refreshJoypadMenu:" target="QvC-M9-y7g" id="5hY-tg-9VE"/>
</connections>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
<rect key="frame" x="212" y="9" width="60" height="32"/>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b">
<rect key="frame" x="30" y="58" width="245" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8p7-je-0Fh">
<items>
<menuItem title="Never" state="on" id="jki-7x-bnM"/>
<menuItem title="For rumble-enabled Game Paks" tag="1" id="58e-Tp-TWd"/>
<menuItem title="Always" tag="2" id="qVe-2b-W1P"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="rumbleModeChanged:" target="QvC-M9-y7g" id="AQe-vQ-mSl"/>
</connections>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RuW-Db-dzW">
<rect key="frame" x="18" y="110" width="264" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Analog turbo and slow-motion controls" bezelStyle="regularSquare" imagePosition="left" lineBreakMode="charWrapping" inset="2" id="Mvp-oc-N3t">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeAnalogControls:" target="QvC-M9-y7g" id="1xR-gY-WKo"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
<rect key="frame" x="206" y="13" width="72" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Clear" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
@ -518,8 +640,19 @@
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
<rect key="frame" x="18" y="13" width="188" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Configure a Controller" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
</connections>
</button>
</subviews>
<point key="canvasLocation" x="-159" y="1116"/>
<point key="canvasLocation" x="-159" y="1161.5"/>
</customView>
</objects>
<resources>

View File

@ -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 <slouken@libsdl.org>
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 <AppKit/AppKit.h>
#include <stdint.h>
#include <stdbool.h>
#include <IOKit/hid/IOHIDLib.h>
#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<GBJoystickListener> *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<GBJoystickListener> *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<GBJoystickListener> *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");
}
}

View File

@ -1,5 +1,6 @@
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
int main(int argc, const char * argv[])
{
return NSApplicationMain(argc, argv);
}

View File

@ -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;
@ -120,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++) {
@ -211,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)
@ -264,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++;
}
@ -286,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++;
}
@ -310,11 +341,16 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
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;
if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIP) {
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIPPED;
return;
}
gb->apu.div_divider++;
if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIPPED) {
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_INACTIVE;
}
else {
gb->apu.div_divider++;
}
if ((gb->apu.div_divider & 1) == 0) {
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
@ -376,8 +412,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) {
@ -399,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;
@ -411,8 +447,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;
@ -431,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);
}
@ -469,7 +508,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;
@ -483,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,
@ -506,11 +548,17 @@ 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. */
if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) {
gb->apu.skip_div_event = true;
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP;
gb->apu.div_divider = 1;
}
}
@ -518,7 +566,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;
@ -556,14 +604,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;
}
@ -676,11 +724,26 @@ 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) {
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) {
@ -862,7 +925,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 ?
@ -965,9 +1028,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, double 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;
@ -980,6 +1057,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 */
}

View File

@ -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;
@ -114,8 +114,12 @@ typedef struct
} noise_channel;
bool skip_div_event;
#define GB_SKIP_DIV_EVENT_INACTIVE 0
#define GB_SKIP_DIV_EVENT_SKIPPED 1
#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 {
@ -143,9 +147,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, 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
@ -156,6 +163,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 */

View File

@ -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;

313
Core/cheats.c Normal file
View File

@ -0,0 +1,313 @@
#include "gb.h"
#include "cheats.h"
#include <stdio.h>
#include <assert.h>
#include <errno.h>
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)
{
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++) {
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;
}
}
}
}
}
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));
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 = realloc(*hash, 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 (bank >= 0x80) {
bank &= 0xF;
}
GB_add_cheat(gb, description, address, bank, value, 0, false, enabled);
return true;
}
}
/* GameGenie */
{
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;
}
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;
}
}
#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 <gb->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;
}

42
Core/cheats.h Normal file
View File

@ -0,0 +1,42 @@
#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);
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);
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;
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

View File

@ -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;
}
@ -296,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);
}
@ -332,7 +336,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[] =
@ -350,15 +354,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,
@ -378,16 +382,15 @@ 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,};
}
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
@ -400,8 +403,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
@ -416,8 +419,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
@ -485,16 +488,15 @@ 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;
}
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
@ -510,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
@ -537,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
@ -563,24 +565,24 @@ 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++) {
for (unsigned 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.
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;
@ -667,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;
@ -816,16 +818,34 @@ 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_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));
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;
}
/* 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 || !strlen(lstrip(arguments))) {
gb->has_software_breakpoints = true;
}
else if (strcmp(lstrip(arguments), "off") == 0) {
gb->has_software_breakpoints = false;
}
else {
print_usage(gb, command);
}
return true;
}
@ -838,8 +858,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;
@ -1005,8 +1025,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;
@ -1164,7 +1184,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;
@ -1213,7 +1233,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' : '-');
}
@ -1339,7 +1359,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--;
}
@ -1352,7 +1372,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--;
}
@ -1419,19 +1439,26 @@ 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] = "HUC-1",
[GB_HUC3] = "HUC-3",
};
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);
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");
@ -1448,7 +1475,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) {
@ -1485,7 +1512,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;
@ -1573,7 +1600,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;
@ -1722,7 +1749,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;
@ -1758,10 +1785,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 */
@ -1771,6 +1799,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
@ -1817,7 +1846,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)
@ -2059,7 +2088,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) {
@ -2151,6 +2180,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");
@ -2172,15 +2214,8 @@ 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) {
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);
}
if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) {
GB_debugger_add_symbol(gb, bank, address, symbol);
}
}
free(line);
@ -2189,13 +2224,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]);

View File

@ -7,13 +7,15 @@
#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
#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,7 +24,8 @@ 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 */
void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol);
#endif /* GB_DISABLE_DEBUGGER */
#endif
#ifdef GB_INTERNAL

View File

@ -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
@ -109,22 +111,12 @@ 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;
/* 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);
}
@ -134,25 +126,85 @@ 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)) {
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->cgb_repeated_a_frame)) {
/* 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;
if (!GB_is_sgb(gb)) {
uint32_t color = 0;
if (GB_is_cgb(gb)) {
color = GB_convert_rgb15(gb, 0x7FFF, false);
}
}
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);
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb ->screen[i] = color;
else {
color = is_ppu_stopped ?
gb->background_palettes_rgb[0] :
gb->background_palettes_rgb[4];
}
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? 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];
gb->vblank_callback(gb);
}
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_handle_rumble(gb);
if (gb->vblank_callback) {
gb->vblank_callback(gb);
}
GB_timing_sync(gb);
}
@ -163,29 +215,68 @@ 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];
}
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
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, 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 {
r = scale_channel_with_curve(r);
g = scale_channel_with_curve(g);
b = scale_channel_with_curve(b);
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),
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;
if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
uint8_t new_r, new_g, new_b;
if (agb) {
new_g = (g * 6 + b * 1) / 7;
}
else {
new_g = (g * 3 + b) / 4;
}
new_r = r;
new_b = b;
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));
@ -201,7 +292,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;
@ -219,7 +310,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)
@ -302,13 +393,11 @@ 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;
gb->accessed_oam_row = -1;
gb->wy_triggered = false;
}
static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
@ -319,6 +408,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;
@ -344,30 +437,30 @@ 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);
/* 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;
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 (!fifo_item) return;
/* 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;
}
if (gb->bg_fifo_paused) return;
/* Mixing */
/* Todo: Verify access timings */
if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
if (gb->cgb_mode) {
bg_priority = false;
@ -376,6 +469,17 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
bg_enabled = false;
}
}
uint8_t icd_pixel = 0;
uint32_t *dest = NULL;
if (!gb->sgb) {
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;
}
}
{
uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
@ -387,11 +491,19 @@ 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->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
}
}
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
}
}
else if (gb->cgb_palettes_ppu_blocked) {
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
}
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];
}
}
@ -403,15 +515,32 @@ 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->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
}
}
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
//gb->icd_pixel_callback(gb, pixel);
}
}
else if (gb->cgb_palettes_ppu_blocked) {
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
}
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];
}
}
if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
gb->icd_pixel_callback(gb, icd_pixel);
}
}
gb->position_in_line++;
gb->lcd_x++;
gb->window_is_being_fetched = false;
}
/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have
@ -421,7 +550,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->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY];
}
static void advance_fetcher_state_machine(GB_gameboy_t *gb)
@ -441,36 +570,52 @@ 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_FETCHER_PUSH,
GB_FETCHER_PUSH,
};
switch (fetcher_state_machine[gb->fetcher_state]) {
switch (fetcher_state_machine[gb->fetcher_state & 7]) {
case GB_FETCHER_GET_TILE: {
uint16_t map = 0x1800;
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. */
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 = 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;
}
gb->current_tile = gb->vram[map + gb->fetcher_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 + gb->fetcher_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;
}
}
gb->fetcher_x++;
gb->fetcher_x &= 0x1f;
}
gb->fetcher_state++;
break;
@ -494,7 +639,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;
@ -520,19 +668,34 @@ 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;
}
}
if (gb->wx_triggered) {
gb->window_tile_x++;
gb->window_tile_x &= 0x1f;
}
gb->fetcher_state++;
break;
// fallthrough
case GB_FETCHER_PUSH: {
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 < 7) {
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);
gb->bg_fifo_paused = false;
gb->oam_fifo_paused = false;
gb->fetcher_state++;
gb->fetcher_state = 0;
}
break;
@ -542,8 +705,30 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
}
break;
}
}
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;
}
gb->fetcher_state &= 7;
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;
}
/*
@ -601,23 +786,38 @@ 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);
GB_STATE(gb, display, 40);
GB_STATE(gb, display, 41);
GB_STATE(gb, display, 42);
}
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
while (true) {
GB_SLEEP(gb, display, 1, LCDC_PERIOD);
display_vblank(gb);
gb->cgb_repeated_a_frame = true;
}
return;
}
gb->is_odd_frame = false;
if (!GB_is_cgb(gb)) {
GB_SLEEP(gb, display, 23, 1);
}
/* 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;
@ -654,17 +854,25 @@ 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 += (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;
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++) {
/* 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->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed;
gb->accessed_oam_row = 0;
@ -750,20 +958,80 @@ 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 && GB_is_sgb(gb)) {
display_vblank(gb);
}
gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f;
gb->lcd_x = 0;
gb->fetcher_x = 0;
gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7);
/* The actual rendering cycle */
gb->fetcher_state = 0;
gb->bg_fifo_paused = false;
gb->oam_fifo_paused = false;
gb->in_window = false;
while (true) {
/* Handle window */
/* 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
WX's access conflict code. */
if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) {
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)) {
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) {
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? */
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.
On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */
@ -773,14 +1041,19 @@ 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) {
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);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
}
/* Todo: Measure if penalty occurs before or after waiting for the fetcher. */
@ -789,56 +1062,63 @@ 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) {
goto abort_fetching_object;
}
}
}
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]];
/* 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;
}
/* } */
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);
advance_fetcher_state_machine(gb);
if (object->flags & 0x40) { /* Flip Y */
tile_y ^= height_16? 0xF : 7;
gb->cycles_for_line += 3;
GB_SLEEP(gb, display, 20, 3);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
/* 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;
gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]);
if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
line_address += 0x2000;
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);
const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
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[line_address],
gb->vram[line_address + 1],
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,
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--;
}
/* 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;
}
abort_fetching_object:
gb->object_fetch_aborted = false;
gb->during_object_fetch = false;
render_pixel_if_possible(gb);
advance_fetcher_state_machine(gb);
@ -848,6 +1128,29 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
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;
}
else {
gb->wx166_glitch = false;
}
gb->wx_triggered = false;
if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) {
gb->cycles_for_line++;
GB_SLEEP(gb, display, 30, 1);
@ -890,8 +1193,18 @@ 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);
}
if (gb->icd_hreset_callback) {
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;
@ -915,16 +1228,30 @@ 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);
if (GB_is_cgb(gb)) {
GB_timing_sync(gb);
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
}
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;
}
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) {
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;
}
}
}
@ -957,11 +1284,21 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
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;
gb->current_lcd_line = -1; // TODO: not the correct timing
/* 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) {
gb->icd_vreset_callback(gb);
}
}
}
@ -1094,7 +1431,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;
@ -1143,30 +1480,8 @@ 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 GB_is_odd_frame(GB_gameboy_t *gb)
{
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;
}
}
}
return gb->is_odd_frame;
}

View File

@ -8,9 +8,15 @@
#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);
enum {
GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility
GB_OBJECT_PRIORITY_X,
GB_OBJECT_PRIORITY_INDEX,
};
#endif
typedef enum {
@ -44,11 +50,13 @@ 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);
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);
bool GB_is_odd_frame(GB_gameboy_t *gb);
#endif /* display_h */

801
Core/gb.c

File diff suppressed because it is too large Load Diff

187
Core/gb.h
View File

@ -1,5 +1,6 @@
#ifndef GB_h
#define GB_h
#define typeof __typeof__
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
@ -20,19 +21,22 @@
#include "sm83_cpu.h"
#include "symbol_hash.h"
#include "sgb.h"
#include "cheats.h"
#include "rumble.h"
#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
#define GB_MODEL_CGB_FAMILY 0x200
#define GB_MODEL_PAL_BIT 0x1000
#define GB_MODEL_NO_SFC_BIT 0x2000
#ifdef GB_INTERNAL
#if __clang__
#define UNROLL _Pragma("unroll")
#elif __GNUC__
#elif __GNUC__ >= 8
#define UNROLL _Pragma("GCC unroll 8")
#else
#define UNROLL
@ -48,6 +52,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;
@ -59,7 +74,6 @@ typedef union {
uint8_t data[5];
} GB_rtc_time_t;
typedef enum {
// GB_MODEL_DMG_0 = 0x000,
// GB_MODEL_DMG_A = 0x001,
@ -67,9 +81,13 @@ 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_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,
// GB_MODEL_CGB_0 = 0x200,
// GB_MODEL_CGB_A = 0x201,
// GB_MODEL_CGB_B = 0x202,
@ -92,10 +110,16 @@ enum {
enum {
GB_CARRY_FLAG = 16,
GB_HALF_CARRY_FLAG = 32,
GB_SUBSTRACT_FLAG = 64,
GB_SUBTRACT_FLAG = 64,
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 {
@ -163,7 +187,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_KEY0 = 0x4c,
/* General CGB features */
GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
@ -171,7 +195,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
@ -190,9 +214,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_OPRI = 0x6c, // Affects object priority (X based or index based)
/* Missing */
@ -213,6 +235,17 @@ typedef enum {
GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE
} GB_log_attributes;
typedef enum {
GB_BOOT_ROM_DMG0,
GB_BOOT_ROM_DMG,
GB_BOOT_ROM_MGB,
GB_BOOT_ROM_SGB,
GB_BOOT_ROM_SGB2,
GB_BOOT_ROM_CGB0,
GB_BOOT_ROM_CGB,
GB_BOOT_ROM_AGB,
} GB_boot_rom_t;
#ifdef GB_INTERNAL
#define LCDC_PERIOD 70224
#define CPU_FREQUENCY 0x400000
@ -222,11 +255,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
@ -234,15 +267,20 @@ 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_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on);
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, 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);
typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value);
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 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;
@ -268,10 +306,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
@ -371,9 +405,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 {
@ -385,18 +418,30 @@ struct GB_gameboy_internal_s {
struct {
uint8_t bank_low:6;
uint8_t bank_high:3;
uint8_t mode:1;
bool mode:1;
bool ir_mode:1;
} 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. */
bool camera_registers_mapped;
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;
uint16_t huc3_alarm_minutes, huc3_alarm_days;
bool huc3_alarm_enabled;
uint8_t huc3_read;
uint8_t huc3_access_flags;
);
@ -440,11 +485,12 @@ struct GB_gameboy_internal_s {
uint8_t position_in_line;
bool stat_interrupt_line;
uint8_t effective_scx;
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/ */
/* 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
@ -456,7 +502,7 @@ struct GB_gameboy_internal_s {
bool vram_read_blocked;
bool oam_write_blocked;
bool vram_write_blocked;
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;
@ -467,9 +513,9 @@ 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;
bool in_window;
bool window_is_being_fetched;
bool wx166_glitch;
bool wx_triggered;
uint8_t visible_objs[10];
uint8_t obj_comparators[10];
uint8_t n_visible_objs;
@ -481,6 +527,20 @@ 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;
bool oam_ppu_blocked;
bool vram_ppu_blocked;
bool cgb_palettes_ppu_blocked;
bool object_fetch_aborted;
bool during_object_fetch;
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.
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 */
@ -494,6 +554,7 @@ struct GB_gameboy_internal_s {
GB_STANDARD_MBC1_WIRING,
GB_MBC1M_WIRING,
} mbc1_wiring;
bool is_mbc30;
unsigned pending_cycles;
@ -506,8 +567,13 @@ 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];
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;
@ -530,10 +596,16 @@ 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;
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;
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;
@ -544,13 +616,13 @@ 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;
/* 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];
@ -571,7 +643,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
@ -589,6 +661,12 @@ struct GB_gameboy_internal_s {
double sgb_intro_jingle_phases[7];
double sgb_intro_sweep_phase;
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];
/* Misc */
bool turbo;
@ -598,6 +676,12 @@ 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;
/* Temporary state */
bool wx_just_changed;
);
};
@ -617,13 +701,14 @@ __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);
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);
@ -654,8 +739,14 @@ 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_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);
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);
@ -665,9 +756,10 @@ 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*/
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);
@ -677,6 +769,10 @@ 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_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);
/* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */
void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_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);
@ -687,7 +783,16 @@ 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 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);
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
uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif

View File

@ -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,
};

View File

@ -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
};

View File

@ -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,
};

View File

@ -3,6 +3,8 @@
void GB_update_joyp(GB_gameboy_t *gb)
{
if (gb->model & GB_MODEL_NO_SFC_BIT) return;
uint8_t key_selection = 0;
uint8_t previous_state = 0;
@ -10,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) & 3) : 0;
switch (key_selection) {
case 3:
if (gb->sgb && gb->sgb->player_count > 1) {
@ -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;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
}
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)
{
assert(index >= 0 && index < GB_KEY_MAX);

View File

@ -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);

View File

@ -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)
@ -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;
}
@ -128,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);
@ -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;

View File

@ -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 {

View File

@ -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) {
@ -141,16 +146,56 @@ 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];
}
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->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
return 0xc0 | effective_ir_input(gb);
}
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];
@ -273,8 +318,11 @@ 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_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
;
case GB_MODEL_SGB2_NO_SFC:
break;
}
}
@ -292,20 +340,20 @@ 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_OPRI:
if (!GB_is_cgb(gb)) {
return 0xFF;
}
return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE;
return gb->io_registers[GB_IO_OPRI] | 0xFE;
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:
@ -376,9 +424,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);
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;
@ -419,6 +466,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) {
@ -427,7 +479,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;
}
return read_map[addr >> 12](gb, addr);
uint8_t data = read_map[addr >> 12](gb, addr);
GB_apply_cheat(gb, addr, &data);
if (gb->read_memory_callback) {
data = gb->read_memory_callback(gb, addr, data);
}
return data;
}
static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
@ -443,9 +500,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:
@ -470,9 +527,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;
}
@ -483,7 +537,7 @@ 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.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;
@ -491,7 +545,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;
}
@ -506,17 +563,135 @@ 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;
}
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) {
if (huc3_write(gb, value)) return;
}
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->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.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;
}
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;
@ -584,7 +759,10 @@ 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_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:
case GB_MODEL_AGB:
break;
@ -629,24 +807,34 @@ 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:
GB_window_related_write(gb, addr & 0xFF, value);
break;
case GB_IO_IF:
case GB_IO_SCX:
case GB_IO_SCY:
case GB_IO_BGP:
case GB_IO_OBP0:
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_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;
}
else if (gb->cgb_mode) {
gb->io_registers[addr & 0xFF] = value;
}
return;
case GB_IO_LYC:
/* TODO: Probably completely wrong in double speed mode */
@ -663,7 +851,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)) {
@ -715,8 +903,19 @@ 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);
/* 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;
gb->wx166_glitch = false;
}
return;
case GB_IO_STAT:
@ -737,18 +936,25 @@ 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_update_joyp(gb);
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->io_registers[GB_IO_JOYP] & 0x0F);
GB_update_joyp(gb);
}
return;
case GB_IO_BIOS:
case GB_IO_BANK:
gb->boot_rom_finished = true;
return;
case GB_IO_DMG_EMULATION:
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_KEY0] = value;
}
return;
@ -890,13 +1096,15 @@ 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) {
if (gb->infrared_callback) {
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
}
gb->cycles_since_ir_change = 0;
}
return;
}

View File

@ -3,6 +3,9 @@
#include "gb_struct_def.h"
#include <stdint.h>
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

53
Core/rumble.c Normal file
View File

@ -0,0 +1,53 @@
#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 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));
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));
}
}
}

17
Core/rumble.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef rumble_h
#define rumble_h
#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 */

View File

@ -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;
}
@ -180,6 +180,28 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
return true;
}
static void sanitize_state(GB_gameboy_t *gb)
{
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)
@ -223,7 +245,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;
}
@ -252,19 +274,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;
sanitize_state(gb);
error:
fclose(f);
@ -334,7 +344,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;
}
@ -347,7 +357,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;
}
@ -357,19 +367,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;
sanitize_state(gb);
return 0;
}

View File

@ -3,6 +3,10 @@
#include <math.h>
#include <assert.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define INTRO_ANIMATION_LENGTH 200
enum {
@ -13,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,
@ -68,6 +73,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 +151,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 +162,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;
@ -162,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;
}
}
@ -170,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;
@ -253,8 +384,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];
gb->sgb->current_player = gb->sgb->player_count - 1;
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;
@ -294,23 +432,33 @@ 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;
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;
}
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;
/* 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) == 0 && !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;
@ -371,22 +519,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, false);
}
static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade)
@ -399,18 +534,19 @@ 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, false);
}
#include <stdio.h>
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;
@ -458,7 +594,9 @@ static void render_boot_animation (GB_gameboy_t *gb)
input++;
}
}
output += 256 - 160;
if (gb->border_mode != GB_BORDER_NEVER) {
output += 256 - 160;
}
}
}
@ -470,14 +608,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) {
@ -540,16 +670,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:
@ -559,7 +700,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;
}
@ -570,7 +713,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;
}
@ -580,7 +725,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;
}
@ -617,6 +764,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;
@ -624,12 +774,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];
}
}
}
@ -640,12 +797,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++) {
@ -668,10 +825,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)

View File

@ -5,6 +5,16 @@
#include <stdbool.h>
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 */
@ -49,6 +50,12 @@ struct GB_sgb_s {
/* Intro */
int16_t intro_animation;
/* GB Header */
uint8_t received_header[0x54];
/* Multiplayer (cont) */
bool mlt_lock;
};
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value);

View File

@ -18,6 +18,9 @@ typedef enum {
GB_CONFLICT_STAT_DMG,
GB_CONFLICT_PALETTE_DMG,
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? */
@ -29,25 +32,42 @@ 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 */
};
/* 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,
[GB_IO_LCDC] = GB_CONFLICT_READ_NEW,
[GB_IO_LCDC] = GB_CONFLICT_DMG_LCDC,
[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,
[GB_IO_WY] = GB_CONFLICT_READ_OLD,
[GB_IO_WX] = GB_CONFLICT_WX,
/* Todo: these were not verified at all */
[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_SGB_LCDC,
[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,
[GB_IO_WY] = GB_CONFLICT_READ_OLD,
[GB_IO_WX] = GB_CONFLICT_WX,
/* 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,
};
@ -92,7 +112,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:
@ -164,6 +194,53 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->pending_cycles = 6;
return;
}
case GB_CONFLICT_DMG_LCDC: {
/* 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->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);
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);
/* Hack to force aborting object fetch */
GB_write_memory(gb, addr, value);
GB_write_memory(gb, addr, old_value);
GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, 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;
}
}
@ -208,6 +285,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) {
@ -224,9 +321,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);
@ -242,7 +338,7 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
gb->halted = true;
}
else {
gb->stopped = true;
enter_stop_mode(gb);
}
}
@ -290,7 +386,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;
@ -306,7 +402,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;
@ -356,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)
@ -368,14 +464,14 @@ 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) {
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;
}
}
@ -404,7 +500,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;
@ -424,7 +520,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;
@ -505,7 +601,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;
}
@ -539,19 +635,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)
@ -582,7 +678,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;
}
@ -599,7 +695,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;
}
@ -693,6 +789,13 @@ 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)
// 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) {
gb->debug_stopped = true;
}
}
static void add_a_r(GB_gameboy_t *gb, uint8_t opcode)
{
@ -706,7 +809,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;
}
}
@ -725,7 +828,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;
}
}
@ -735,7 +838,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;
}
@ -753,7 +856,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;
@ -761,7 +864,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;
}
}
@ -805,7 +908,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;
}
@ -821,10 +924,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. */
@ -905,7 +1005,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;
}
}
@ -924,7 +1024,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;
}
}
@ -934,7 +1034,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;
}
@ -952,7 +1052,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;
@ -960,7 +1060,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;
}
}
@ -1004,7 +1104,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;
}
@ -1126,7 +1226,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;
}
@ -1314,10 +1414,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);
}
}
@ -1366,7 +1466,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,
@ -1401,11 +1501,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;
@ -1426,19 +1522,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;
@ -1475,7 +1571,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--;
@ -1484,10 +1580,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);
}

View File

@ -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)++);

View File

@ -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;
@ -71,9 +69,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 +85,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 +96,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) {

View File

@ -3,14 +3,14 @@
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif
#include <Windows.h>
#include <windows.h>
#else
#include <sys/time.h>
#endif
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
@ -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);
@ -209,12 +214,13 @@ 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;
GB_timers_run(gb, cycles);
if (!gb->stopped) {
GB_timers_run(gb, cycles);
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
}
@ -232,6 +238,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);
@ -265,23 +279,30 @@ 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) {
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;

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,477 @@
#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,
JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1],
JOYCustomReports: @{
// Rumble
@(-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},
],
@(33): @[
// 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),
},
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},
],
},
},
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,
},
};

24
JoyKit/JOYAxes2D.h Normal file
View File

@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
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

181
JoyKit/JOYAxes2D.m Normal file
View File

@ -0,0 +1,181 @@
#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 = 0;
initialY = 0;
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;
{
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;
}
{
int32_t value = y;
if (initialY != 0) {
minY = MIN(value, minY);
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;
}
- (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

29
JoyKit/JOYAxis.h Normal file
View File

@ -0,0 +1,29 @@
#import <Foundation/Foundation.h>
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

90
JoyKit/JOYAxis.m Normal file
View File

@ -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

42
JoyKit/JOYButton.h Normal file
View File

@ -0,0 +1,42 @@
#import <Foundation/Foundation.h>
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

102
JoyKit/JOYButton.m Normal file
View File

@ -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

41
JoyKit/JOYController.h Normal file
View File

@ -0,0 +1,41 @@
#import <Foundation/Foundation.h>
#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 <NSObject>
@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<JOYController *> *) allControllers;
+ (void) registerListener:(id<JOYListener>)listener;
+ (void) unregisterListener:(id<JOYListener>)listener;
- (NSString *)deviceName;
- (NSString *)uniqueID;
- (NSArray<JOYButton *> *) buttons;
- (NSArray<JOYAxis *> *) axes;
- (NSArray<JOYAxes2D *> *) axes2D;
- (NSArray<JOYHat *> *) hats;
- (void)setRumbleAmplitude:(double)amp;
- (void)setPlayerLEDs:(uint8_t)mask;
@property (readonly, getter=isConnected) bool connected;
@end

913
JoyKit/JOYController.m Normal file
View File

@ -0,0 +1,913 @@
#import "JOYController.h"
#import "JOYMultiplayerController.h"
#import "JOYElement.h"
#import "JOYSubElement.h"
#import "JOYFullReportElement.h"
#import "JOYEmulatedButton.h"
#include <IOKit/hid/IOHIDLib.h>
#define PWM_RESOLUTION 16
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 *JOYCustomReports = @"JOYCustomReports";
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 NSString const *JOYActivationReport = @"JOYActivationReport";
static NSString const *JOYIgnoredReports = @"JOYIgnoredReports";
static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3";
static NSMutableDictionary<id, JOYController *> *controllers; // Physical controllers
static NSMutableArray<JOYController *> *exposedControllers; // Logical controllers
static NSDictionary *hacksByName = nil;
static NSDictionary *hacksByManufacturer = nil;
static NSMutableSet<id<JOYListener>> *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;
- (void)gotReport:(NSData *)report;
@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)];
}
static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type,
uint32_t reportID, uint8_t *report, CFIndex reportLength)
{
if (reportLength) {
[(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]];
}
}
typedef struct __attribute__((packed)) {
uint8_t reportID;
uint8_t sequence;
uint8_t rumbleData[8];
uint8_t command;
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 dutyOn;
} __attribute__((packed)) led[5];
uint8_t padding3[13];
} JOYDualShock3Output;
typedef union {
JOYSwitchPacket switchPacket;
JOYDualShock3Output ds3Output;
} JOYVendorSpecificOutput;
@implementation JOYController
{
IOHIDDeviceRef _device;
NSMutableDictionary<JOYElement *, JOYButton *> *_buttons;
NSMutableDictionary<JOYElement *, JOYAxis *> *_axes;
NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D;
NSMutableDictionary<JOYElement *, JOYHat *> *_hats;
NSMutableDictionary<NSNumber *, JOYFullReportElement *> *_fullReportElements;
NSMutableDictionary<JOYFullReportElement *, NSArray<JOYElement *> *> *_multiElements;
// Button emulation
NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons;
NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_axes2DEmulatedButtons;
NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_hatEmulatedButtons;
JOYElement *_rumbleElement;
JOYElement *_connectedElement;
NSMutableDictionary<NSValue *, JOYElement *> *_iokitToJOY;
NSString *_serialSuffix;
bool _isSwitch; // Does this controller use the Switch protocol?
bool _isDualShock3; // Does this controller use DS3 outputs?
JOYVendorSpecificOutput _lastVendorSpecificOutput;
volatile double _rumbleAmplitude;
bool _physicallyConnected;
bool _logicallyConnected;
NSDictionary *_hacks;
NSMutableData *_lastReport;
// Used when creating inputs
JOYElement *_previousAxisElement;
uint8_t _playerLEDs;
double _sentRumbleAmp;
unsigned _rumbleCounter;
bool _deviceCantSendReports;
}
- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks
{
return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks];
}
-(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 <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks
{
self = [super init];
if (!self) return self;
_physicallyConnected = true;
_logicallyConnected = true;
_device = (IOHIDDeviceRef)CFRetain(device);
_serialSuffix = suffix;
_playerLEDs = -1;
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];
_iokitToJOY = [NSMutableDictionary dictionary];
//NSMutableArray *axes3d = [NSMutableArray array];
_hacks = hacks;
_isSwitch = [_hacks[JOYIsSwitch] boolValue];
_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];
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 <NSString *,NSNumber *> *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;
}
}
}
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;
JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element];
bool isOutput = false;
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 && [ignoredReports containsObject:@(element.reportID)]) ||
(isOutput && [ignoredReports containsObject:@(-element.reportID)])) continue;
if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue;
if (isOutput) {
[self createOutputForElement:element];
}
else {
[self createInputForElement:element];
}
_iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element;
}
[exposedControllers addObject:self];
if (_logicallyConnected) {
for (id<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controllerConnected:)]) {
[listener controllerConnected:self];
}
}
}
if (_hacks[JOYActivationReport]) {
[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]];
}
if (_isDualShock3) {
_lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){
.reportID = 1,
.led = {
{.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},
}
};
}
return self;
}
- (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",
[(__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<JOYButton *> *)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<JOYAxis *> *)axes
{
return [_axes allValues];
}
- (NSArray<JOYAxes2D *> *)axes2D
{
return [[NSSet setWithArray:[_axes2D allValues]] allObjects];
}
- (NSArray<JOYHat *> *)hats
{
return [_hats allValues];
}
- (void)gotReport:(NSData *)report
{
JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)];
if (element) {
[element updateValue:report];
NSArray<JOYElement *> *subElements = _multiElements[element];
if (subElements) {
for (JOYElement *subElement in subElements) {
[self _elementChanged:subElement];
}
}
}
[self updateRumble];
}
- (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<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controllerConnected:)]) {
[listener controllerConnected:self];
}
}
}
else if (old && !self.connected) {
for (id<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
[listener controllerDisconnected:self];
}
}
}
}
if (!self.connected) return;
{
JOYButton *button = _buttons[element];
if (button) {
if ([button updateState]) {
for (id<JOYListener> 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<JOYListener> 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<JOYListener> 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<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) {
[listener controller:self movedAxes2D:axes];
}
}
NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)];
for (JOYEmulatedButton *button in buttons) {
if ([button updateStateFromAxes2D:axes]) {
for (id<JOYListener> 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<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controller:movedHat:)]) {
[listener controller:self movedHat:hat];
}
}
NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID)];
for (JOYEmulatedButton *button in buttons) {
if ([button updateStateFromHat:hat]) {
for (id<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
[listener controller:self buttonChangedState:button];
}
}
}
}
}
return;
}
}
}
- (void)disconnected
{
if (_logicallyConnected && [exposedControllers containsObject:self]) {
for (id<JOYListener> listener in listeners) {
if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
[listener controllerDisconnected:self];
}
}
}
_physicallyConnected = false;
[exposedControllers removeObject:self];
[self setRumbleAmplitude:0];
[self updateRumble];
_device = nil;
}
- (void)sendReport:(NSData *)report
{
if (!report.length) return;
if (!_device) return;
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
{
mask &= 0xF;
if (mask == _playerLEDs) {
return;
}
_playerLEDs = mask;
if (_isSwitch) {
_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:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
}
else if (_isDualShock3) {
_lastVendorSpecificOutput.ds3Output.reportID = 1;
_lastVendorSpecificOutput.ds3Output.ledsEnabled = mask << 1;
[self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
}
}
- (void)updateRumble
{
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) {
[_rumbleElement setValue:ampToSend];
_sentRumbleAmp = ampToSend;
}
_rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION);
if (_rumbleCounter >= PWM_RESOLUTION) {
_rumbleCounter -= PWM_RESOLUTION;
}
}
else {
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];
}
}
}
- (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */
{
if (amp < 0) amp = 0;
if (amp > 1) amp = 1;
_rumbleAmplitude = amp;
}
- (bool)isConnected
{
return _logicallyConnected && _physicallyConnected;
}
+ (void)controllerAdded:(IOHIDDeviceRef) device
{
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) {
controller = [[JOYMultiplayerController alloc] initWithDevice:device
reportIDFilters:filters
hacks:hacks];
}
else {
controller = [[JOYController alloc] initWithDevice:device hacks:hacks];
}
[controllers setObject:controller forKey:[NSValue valueWithPointer:device]];
}
+ (void)controllerRemoved:(IOHIDDeviceRef) device
{
[[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected];
[controllers removeObjectForKey:[NSValue valueWithPointer:device]];
}
+ (NSArray<JOYController *> *)allControllers
{
return exposedControllers;
}
+ (void)load
{
#include "ControllerConfiguration.inc"
}
+(void)registerListener:(id<JOYListener>)listener
{
[listeners addObject:listener];
}
+(void)unregisterListener:(id<JOYListener>)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);
}
- (void)dealloc
{
if (_device) {
CFRelease(_device);
_device = NULL;
}
}
@end

20
JoyKit/JOYElement.h Normal file
View File

@ -0,0 +1,20 @@
#import <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDLib.h>
@interface JOYElement : NSObject<NSCopying>
- (instancetype)initWithElement:(IOHIDElementRef)element;
- (int32_t)value;
- (NSData *)dataValue;
- (IOReturn)setValue:(uint32_t)value;
- (IOReturn)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

133
JoyKit/JOYElement.m Normal file
View File

@ -0,0 +1,133 @@
#import "JOYElement.h"
#include <IOKit/hid/IOHIDLib.h>
#include <objc/runtime.h>
@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;
}
/* 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])) {
_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);
/* 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;
for (NSNumber *none in [JOYElement cookiesToSkipForDevice:_device]) {
if (none.unsignedIntValue < _uniqueID) {
cookieShift++;
}
if (none.unsignedIntValue < (int32_t)_parentID) {
parentCookieShift++;
}
}
_uniqueID -= cookieShift;
_parentID -= parentCookieShift;
}
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)];
}
- (IOReturn)setValue:(uint32_t)value
{
IOHIDValueRef ivalue = IOHIDValueCreateWithIntegerValue(NULL, (__bridge IOHIDElementRef)_element, 0, value);
IOReturn ret = IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue);
CFRelease(ivalue);
return ret;
}
- (IOReturn)setDataValue:(NSData *)value
{
IOHIDValueRef ivalue = IOHIDValueCreateWithBytes(NULL, (__bridge IOHIDElementRef)_element, 0, value.bytes, value.length);
IOReturn ret = IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue);
CFRelease(ivalue);
return ret;
}
/* 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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDLib.h>
#include "JOYElement.h"
@interface JOYFullReportElement : JOYElement<NSCopying>
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID;
- (void)updateValue:(NSData *)value;
@end

View File

@ -0,0 +1,73 @@
#import "JOYFullReportElement.h"
#include <IOKit/hid/IOHIDLib.h>
@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

11
JoyKit/JOYHat.h Normal file
View File

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
@interface JOYHat : NSObject
- (uint64_t)uniqueID;
- (double)angle;
- (unsigned)resolution;
@property (readonly, getter=isPressed) bool pressed;
@end

60
JoyKit/JOYHat.m Normal file
View File

@ -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

View File

@ -0,0 +1,8 @@
#import "JOYController.h"
#include <IOKit/hid/IOHIDLib.h>
@interface JOYMultiplayerController : JOYController
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters hacks:hacks;
@end

View File

@ -0,0 +1,50 @@
#import "JOYMultiplayerController.h"
@interface JOYController ()
- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks;
- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value;
- (void)disconnected;
- (void)sendReport:(NSData *)report;
@end
@implementation JOYMultiplayerController
{
NSMutableArray <JOYController *> *_children;
}
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters hacks:(NSDictionary *)hacks;
{
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] hacks:hacks];
[_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];
}
}
- (void)sendReport:(NSData *)report
{
[[_children firstObject] sendReport:report];
}
@end

Some files were not shown because too many files have changed in this diff Show More