Add 'bsnes/gb/' from commit '919a88ec23f8011dd0389a4abceb62b3d0c83e00'
git-subtree-dir: bsnes/gb git-subtree-mainline:844e23d0f4
git-subtree-split:919a88ec23
6
bsnes/gb/.gitattributes
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
HexFiend/* linguist-vendored
|
||||
*.inc linguist-language=C
|
||||
Core/*.h linguist-language=C
|
||||
SDL/*.h linguist-language=C
|
||||
Windows/*.h linguist-language=C
|
||||
Cocoa/*.h linguist-language=Objective-C
|
25
bsnes/gb/.github/actions/LICENSE
vendored
Normal 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
bsnes/gb/.github/actions/cgb-acid2.gbc
vendored
Normal file
BIN
bsnes/gb/.github/actions/cgb_sound.gb
vendored
Normal file
BIN
bsnes/gb/.github/actions/dmg-acid2.gb
vendored
Normal file
BIN
bsnes/gb/.github/actions/dmg_sound-2.gb
vendored
Executable file
23
bsnes/gb/.github/actions/install_deps.sh
vendored
Executable 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
bsnes/gb/.github/actions/oam_bug-2.gb
vendored
Executable file
33
bsnes/gb/.github/actions/sanity_tests.sh
vendored
Executable 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
bsnes/gb/.github/workflows/sanity.yml
vendored
Normal 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
|
1
bsnes/gb/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build
|
BIN
bsnes/gb/BootROMs/SameBoyLogo.png
Normal file
After Width: | Height: | Size: 479 B |
2
bsnes/gb/BootROMs/agb_boot.asm
Normal file
@ -0,0 +1,2 @@
|
||||
AGB EQU 1
|
||||
include "cgb_boot.asm"
|
1239
bsnes/gb/BootROMs/cgb_boot.asm
Normal file
2
bsnes/gb/BootROMs/cgb_boot_fast.asm
Normal file
@ -0,0 +1,2 @@
|
||||
FAST EQU 1
|
||||
include "cgb_boot.asm"
|
177
bsnes/gb/BootROMs/dmg_boot.asm
Normal file
@ -0,0 +1,177 @@
|
||||
; SameBoy DMG bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
ld sp, $fffe
|
||||
|
||||
; Clear memory VRAM
|
||||
ld hl, $8000
|
||||
|
||||
.clearVRAMLoop
|
||||
ldi [hl], a
|
||||
bit 5, h
|
||||
jr z, .clearVRAMLoop
|
||||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
|
||||
; Init BG palette
|
||||
ld a, $54
|
||||
ldh [$47], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
; Tiles are ordered left to right, top to bottom.
|
||||
ld de, $104 ; Logo start
|
||||
ld hl, $8010 ; This is where we load the tiles in VRAM
|
||||
|
||||
.loadLogoLoop
|
||||
ld a, [de] ; Read 2 rows
|
||||
ld b, a
|
||||
call DoubleBitsAndWriteRow
|
||||
call DoubleBitsAndWriteRow
|
||||
inc de
|
||||
ld a, e
|
||||
xor $34 ; End of logo
|
||||
jr nz, .loadLogoLoop
|
||||
|
||||
; Load trademark symbol
|
||||
ld de, TrademarkSymbol
|
||||
ld c,$08
|
||||
.loadTrademarkSymbolLoop:
|
||||
ld a,[de]
|
||||
inc de
|
||||
ldi [hl],a
|
||||
inc hl
|
||||
dec c
|
||||
jr nz, .loadTrademarkSymbolLoop
|
||||
|
||||
; Set up tilemap
|
||||
ld a,$19 ; Trademark symbol
|
||||
ld [$9910], a ; ... put in the superscript position
|
||||
ld hl,$992f ; Bottom right corner of the logo
|
||||
ld c,$c ; Tiles in a logo row
|
||||
.tilemapLoop
|
||||
dec a
|
||||
jr z, .tilemapDone
|
||||
ldd [hl], a
|
||||
dec c
|
||||
jr nz, .tilemapLoop
|
||||
ld l,$0f ; Jump to top row
|
||||
jr .tilemapLoop
|
||||
.tilemapDone
|
||||
|
||||
ld a, 30
|
||||
ldh [$ff42], a
|
||||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
|
||||
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
|
||||
ld b, 5
|
||||
call WaitBFrames
|
||||
; Play second sound
|
||||
ld a, $c1
|
||||
call PlaySound
|
||||
|
||||
|
||||
|
||||
; Wait ~1 second
|
||||
ld b, 60
|
||||
call WaitBFrames
|
||||
|
||||
; Set registers to match the original DMG boot
|
||||
ld hl, $01B0
|
||||
push hl
|
||||
pop af
|
||||
ld hl, $014D
|
||||
ld bc, $0013
|
||||
ld de, $00D8
|
||||
|
||||
; Boot the game
|
||||
jp BootGame
|
||||
|
||||
|
||||
DoubleBitsAndWriteRow:
|
||||
; Double the most significant 4 bits, b is shifted by 4
|
||||
ld a, 4
|
||||
ld c, 0
|
||||
.doubleCurrentBit
|
||||
sla b
|
||||
push af
|
||||
rl c
|
||||
pop af
|
||||
rl c
|
||||
dec a
|
||||
jr nz, .doubleCurrentBit
|
||||
ld a, c
|
||||
; Write as two rows
|
||||
ldi [hl], a
|
||||
inc hl
|
||||
ldi [hl], a
|
||||
inc hl
|
||||
ret
|
||||
|
||||
WaitFrame:
|
||||
push hl
|
||||
ld hl, $FF0F
|
||||
res 0, [hl]
|
||||
.wait
|
||||
bit 0, [hl]
|
||||
jr z, .wait
|
||||
pop hl
|
||||
ret
|
||||
|
||||
WaitBFrames:
|
||||
call WaitFrame
|
||||
dec b
|
||||
jr nz, WaitBFrames
|
||||
ret
|
||||
|
||||
PlaySound:
|
||||
ldh [$13], a
|
||||
ld a, $87
|
||||
ldh [$14], a
|
||||
ret
|
||||
|
||||
|
||||
TrademarkSymbol:
|
||||
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
|
||||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
BootGame:
|
||||
ldh [$50], a
|
102
bsnes/gb/BootROMs/pb12.c
Normal file
@ -0,0 +1,102 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
void write_all(int fd, const void *buf, size_t count) {
|
||||
while (count) {
|
||||
ssize_t written = write(fd, buf, count);
|
||||
if (written < 0) {
|
||||
fprintf(stderr, "write");
|
||||
exit(1);
|
||||
}
|
||||
count -= written;
|
||||
buf += written;
|
||||
}
|
||||
}
|
||||
|
||||
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_all(STDOUT_FILENO, &outctl, 1);
|
||||
write_all(STDOUT_FILENO, literals, literals_size);
|
||||
bits -= 8;
|
||||
control &= (1 << bits) - 1;
|
||||
literals_size = 0;
|
||||
}
|
||||
}
|
||||
uint8_t end_byte = 1;
|
||||
write_all(STDOUT_FILENO, &end_byte, 1);
|
||||
|
||||
return 0;
|
||||
}
|
2
bsnes/gb/BootROMs/sgb2_boot.asm
Normal file
@ -0,0 +1,2 @@
|
||||
SGB2 EQU 1
|
||||
include "sgb_boot.asm"
|
213
bsnes/gb/BootROMs/sgb_boot.asm
Normal file
@ -0,0 +1,213 @@
|
||||
; SameBoy SGB bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
ld sp, $fffe
|
||||
|
||||
; Clear memory VRAM
|
||||
ld hl, $8000
|
||||
|
||||
.clearVRAMLoop
|
||||
ldi [hl], a
|
||||
bit 5, h
|
||||
jr z, .clearVRAMLoop
|
||||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
|
||||
; Init BG palette to white
|
||||
ld a, $0
|
||||
ldh [$47], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
; Tiles are ordered left to right, top to bottom.
|
||||
ld de, $104 ; Logo start
|
||||
ld hl, $8010 ; This is where we load the tiles in VRAM
|
||||
|
||||
.loadLogoLoop
|
||||
ld a, [de] ; Read 2 rows
|
||||
ld b, a
|
||||
call DoubleBitsAndWriteRow
|
||||
call DoubleBitsAndWriteRow
|
||||
inc de
|
||||
ld a, e
|
||||
xor $34 ; End of logo
|
||||
jr nz, .loadLogoLoop
|
||||
|
||||
; Load trademark symbol
|
||||
ld de, TrademarkSymbol
|
||||
ld c,$08
|
||||
.loadTrademarkSymbolLoop:
|
||||
ld a,[de]
|
||||
inc de
|
||||
ldi [hl],a
|
||||
inc hl
|
||||
dec c
|
||||
jr nz, .loadTrademarkSymbolLoop
|
||||
|
||||
; Set up tilemap
|
||||
ld a,$19 ; Trademark symbol
|
||||
ld [$9910], a ; ... put in the superscript position
|
||||
ld hl,$992f ; Bottom right corner of the logo
|
||||
ld c,$c ; Tiles in a logo row
|
||||
.tilemapLoop
|
||||
dec a
|
||||
jr z, .tilemapDone
|
||||
ldd [hl], a
|
||||
dec c
|
||||
jr nz, .tilemapLoop
|
||||
ld l,$0f ; Jump to top row
|
||||
jr .tilemapLoop
|
||||
.tilemapDone
|
||||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
|
||||
ld a, $f1 ; Packet magic, increases by 2 for every packet
|
||||
ldh [$80], a
|
||||
ld hl, $104 ; Header start
|
||||
|
||||
xor a
|
||||
ld c, a ; JOYP
|
||||
|
||||
.sendCommand
|
||||
xor a
|
||||
ld [c], a
|
||||
ld a, $30
|
||||
ld [c], a
|
||||
|
||||
ldh a, [$80]
|
||||
call SendByte
|
||||
push hl
|
||||
ld b, $e
|
||||
ld d, 0
|
||||
|
||||
.checksumLoop
|
||||
call ReadHeaderByte
|
||||
add d
|
||||
ld d, a
|
||||
dec b
|
||||
jr nz, .checksumLoop
|
||||
|
||||
; Send checksum
|
||||
call SendByte
|
||||
pop hl
|
||||
|
||||
ld b, $e
|
||||
.sendLoop
|
||||
call ReadHeaderByte
|
||||
call SendByte
|
||||
dec b
|
||||
jr nz, .sendLoop
|
||||
|
||||
; Done bit
|
||||
ld a, $20
|
||||
ld [c], a
|
||||
ld a, $30
|
||||
ld [c], a
|
||||
|
||||
; Update command
|
||||
ldh a, [$80]
|
||||
add 2
|
||||
ldh [$80], a
|
||||
|
||||
ld a, $58
|
||||
cp l
|
||||
jr nz, .sendCommand
|
||||
|
||||
; Write to sound registers for DMG compatibility
|
||||
ld c, $13
|
||||
ld a, $c1
|
||||
ld [c], a
|
||||
inc c
|
||||
ld a, 7
|
||||
ld [c], a
|
||||
|
||||
; Init BG palette
|
||||
ld a, $fc
|
||||
ldh [$47], a
|
||||
|
||||
; Set registers to match the original SGB boot
|
||||
IF DEF(SGB2)
|
||||
ld a, $FF
|
||||
ELSE
|
||||
ld a, 1
|
||||
ENDC
|
||||
ld hl, $c060
|
||||
|
||||
; Boot the game
|
||||
jp BootGame
|
||||
|
||||
ReadHeaderByte:
|
||||
ld a, $4F
|
||||
cp l
|
||||
jr c, .zero
|
||||
ld a, [hli]
|
||||
ret
|
||||
.zero:
|
||||
inc hl
|
||||
xor a
|
||||
ret
|
||||
|
||||
SendByte:
|
||||
ld e, a
|
||||
ld d, 8
|
||||
.loop
|
||||
ld a, $10
|
||||
rr e
|
||||
jr c, .zeroBit
|
||||
add a ; 10 -> 20
|
||||
.zeroBit
|
||||
ld [c], a
|
||||
ld a, $30
|
||||
ld [c], a
|
||||
dec d
|
||||
ret z
|
||||
jr .loop
|
||||
|
||||
DoubleBitsAndWriteRow:
|
||||
; Double the most significant 4 bits, b is shifted by 4
|
||||
ld a, 4
|
||||
ld c, 0
|
||||
.doubleCurrentBit
|
||||
sla b
|
||||
push af
|
||||
rl c
|
||||
pop af
|
||||
rl c
|
||||
dec a
|
||||
jr nz, .doubleCurrentBit
|
||||
ld a, c
|
||||
; Write as two rows
|
||||
ldi [hl], a
|
||||
inc hl
|
||||
ldi [hl], a
|
||||
inc hl
|
||||
ret
|
||||
|
||||
WaitFrame:
|
||||
push hl
|
||||
ld hl, $FF0F
|
||||
res 0, [hl]
|
||||
.wait
|
||||
bit 0, [hl]
|
||||
jr z, .wait
|
||||
pop hl
|
||||
ret
|
||||
|
||||
TrademarkSymbol:
|
||||
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
|
||||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
BootGame:
|
||||
ldh [$50], a
|
1
bsnes/gb/CHANGES.md
Normal file
@ -0,0 +1 @@
|
||||
See https://sameboy.github.io/changelog/
|
79
bsnes/gb/CONTRIBUTING.md
Normal file
@ -0,0 +1,79 @@
|
||||
# SameBoy Coding and Contribution Guidelines
|
||||
|
||||
## Issues
|
||||
|
||||
GitHub Issues are the most effective way to report a bug or request a feature in SameBoy. When reporting a bug, make sure you use the latest stable release, and make sure you mention the SameBoy frontend (Cocoa, SDL, Libretro) and operating system you're using. If you're using Linux/BSD/etc, or you build your own copy of SameBoy for another reason, give as much details as possible on your environment.
|
||||
|
||||
If your bug involves a crash, please attach a crash log or a core dump. If you're using Linux/BSD/etc, or if you're using the Libretro core, please attach the `sameboy` binary (or `libretro_sameboy` library) in that case.
|
||||
|
||||
If your bug is a regression, it'd be extremely helpful if you can report the the first affected version. You get extra credits if you use `git bisect` to point the exact breaking commit.
|
||||
|
||||
If your bug is an emulation bug (Such as a failing test ROM), and you have access to a Game Boy you can test on, please confirm SameBoy is indeed behaving differently from hardware, and report both the emulated model and revision in SameBoy, and the hardware revision you're testing on.
|
||||
|
||||
If your issue is a feature request, demonstrating use cases can help me better prioritize it.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
To allow quicker integration into SameBoy's master branch, contributors are asked to follow SameBoy's style and coding guidelines. Keep in mind that despite the seemingly strict guidelines, all pull requests are welcome – not following the guidelines does not mean your pull request will not be accepted, but it will require manual tweaks from my side for integrating.
|
||||
|
||||
### Languages and Compilers
|
||||
|
||||
SameBoy's core, SDL frontend, Libretro frontend, and automatic tester (Folders `Core`, `SDL` & `OpenDialog`, `libretro`, and `Tester`; respectively) are all written in C11. The Cocoa frontend, SameBoy's fork of Hex Fiend, JoyKit and the Quick Look previewer (Folders `Cocoa`, `HexFiend`, `JoyKit` and `QuickLook`; respectively) are all written in ARC-enabled Objective-C. The SameBoot ROMs (Under `BootROMs`) are written in rgbds-flavor SM83 assembly, with build tools in C11. The shaders (inside `Shaders`) are written in a polyglot GLSL and Metal style, with a few GLSL- and Metal-specific sources. The build system uses standalone Make, in the GNU flavor. Avoid adding new languages (C++, Swift, Python, CMake...) to any of the existing sub-projects.
|
||||
|
||||
SameBoy's main target compiler is Clang, but GCC is also supported when targeting Linux and Libretro. Other compilers (e.g. MSVC) are not supported, and unless there's a good reason, there's no need to go out of your way to add specific support for them. Extensions that are supported by both compilers (Such as `typeof`) may be used if it makes sense. It's OK if you can't test one of these compilers yourself; once you push a commit, the CI bot will let you know if you broke something.
|
||||
|
||||
### Third Party Libraries and Tools
|
||||
|
||||
Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its MIT license.
|
||||
|
||||
### Spacing, Indentation and Formatting
|
||||
|
||||
In all files and languages (Other than Makefiles when required), 4 spaces are used for indentation. Unix line endings (`\n`) are used exclusively, even in Windows-specific source files. (`\r` and `\t` shouldn't appear in any source file). Opening braces belong on the same line as their control flow directive, and on their own line when following a function prototype. The `else` keyword always starts on its own line. The `case` keyword is indented relative to its `switch` block, and the code inside a `case` is indented relative to its label. A control flow keyword should have a space between it and the following `(`, commas should follow a space, and operator (except `.` and `->`) should be surrounded by spaces.
|
||||
|
||||
Control flow statements must use `{}`, with the exception of `if` statements that only contain a single `break`, `continue`, or trivial `return` statements. If `{}`s are omitted, the statement must be on the same line as the `if` condition. Functions that do not have any argument must be specified as `(void)`, as mandated by the C standard. The `sizeof` and `typeof` operators should be used as if they're functions (With `()`). `*`, when used to declare pointer types (including functions that return pointers), and when used to dereference a pointer, is attached to the right side (The variable name) – not to the left, and not with spaces on both sides.
|
||||
|
||||
No strict limitations on a line's maximum width, but use your best judgement if you think a statement would benefit from an additional line break.
|
||||
|
||||
Well formatted code example:
|
||||
|
||||
```
|
||||
static void my_function(void)
|
||||
{
|
||||
GB_something_t *thing = GB_function(&gb, GB_FLAG_ONE | GB_FLAG_TWO, sizeof(thing));
|
||||
if (GB_is_thing(thing)) return;
|
||||
|
||||
switch (*thing) {
|
||||
case GB_QUACK:
|
||||
// Something
|
||||
case GB_DUCK:
|
||||
// Something else
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Badly formatted code example:
|
||||
```
|
||||
static void my_function(){
|
||||
GB_something_t* thing=GB_function(&gb , GB_FLAG_ONE|GB_FLAG_TWO , sizeof thing);
|
||||
if( GB_is_thing ( thing ) )
|
||||
return;
|
||||
|
||||
switch(* thing)
|
||||
{
|
||||
case GB_QUACK:
|
||||
// Something
|
||||
case GB_DUCK:
|
||||
// Something else
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Other Coding Conventions
|
||||
|
||||
The primitive types to be used in SameBoy are `unsigned` and `signed` (Without the `int` keyword), the `(u)int*_t` types, `char *` for UTF-8 strings, `double` for non-integer numbers, and `bool` for booleans (Including in Objective-C code, avoid `BOOL`). As long as it's not mandated by a 3rd-party API (e.g. `int` when using file descriptors), avoid using other primitive types. Use `const` whenever possible.
|
||||
|
||||
Most C names should be `lower_case_snake_case`. Constants and macros use `UPPER_CASE_SNAKE_CASE`. Type definitions use a `_t` suffix. Type definitions, as well as non-static (exported) core symbols, should be prefixed with `GB_` (SameBoy's core is intended to be used as a library, so it shouldn't contaminate the global namespace without prefixes). Exported symbols that are only meant to be used by other parts of the core should still get the `GB_` prefix, but their header definition should be inside `#ifdef GB_INTERNAL`.
|
||||
|
||||
For Objective-C naming conventions, use Apple's conventions (Some old Objective-C code mixes these with the C naming convention; new code should use Apple's convention exclusively). The name prefix for SameBoy classes and constants is `GB`. JoyKit's prefix is `JOY`, and Hex Fiend's prefix is `HF`.
|
||||
|
||||
In all languages, prefer long, unambiguous names over short ambiguous ones.
|
15
bsnes/gb/Cocoa/AppDelegate.h
Normal file
@ -0,0 +1,15 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate>
|
||||
|
||||
@property IBOutlet NSWindow *preferencesWindow;
|
||||
@property (strong) IBOutlet NSView *graphicsTab;
|
||||
@property (strong) IBOutlet NSView *emulationTab;
|
||||
@property (strong) IBOutlet NSView *audioTab;
|
||||
@property (strong) IBOutlet NSView *controlsTab;
|
||||
- (IBAction)showPreferences: (id) sender;
|
||||
- (IBAction)toggleDeveloperMode:(id)sender;
|
||||
- (IBAction)switchPreferencesTab:(id)sender;
|
||||
|
||||
@end
|
||||
|
112
bsnes/gb/Cocoa/AppDelegate.m
Normal file
@ -0,0 +1,112 @@
|
||||
#import "AppDelegate.h"
|
||||
#include "GBButtons.h"
|
||||
#include "GBView.h"
|
||||
#include <Core/gb.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
{
|
||||
NSWindow *preferences_window;
|
||||
NSArray<NSView *> *preferences_tabs;
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
for (unsigned i = 0; i < GBButtonCount; i++) {
|
||||
if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) {
|
||||
[defaults removeObjectForKey:button_to_preference_name(i, 0)];
|
||||
}
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
|
||||
@"GBRight": @(kVK_RightArrow),
|
||||
@"GBLeft": @(kVK_LeftArrow),
|
||||
@"GBUp": @(kVK_UpArrow),
|
||||
@"GBDown": @(kVK_DownArrow),
|
||||
|
||||
@"GBA": @(kVK_ANSI_X),
|
||||
@"GBB": @(kVK_ANSI_Z),
|
||||
@"GBSelect": @(kVK_Delete),
|
||||
@"GBStart": @(kVK_Return),
|
||||
|
||||
@"GBTurbo": @(kVK_Space),
|
||||
@"GBRewind": @(kVK_Tab),
|
||||
@"GBSlow-Motion": @(kVK_Shift),
|
||||
|
||||
@"GBFilter": @"NearestNeighbor",
|
||||
@"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,
|
||||
}];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleDeveloperMode:(id)sender
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"];
|
||||
}
|
||||
|
||||
- (IBAction)switchPreferencesTab:(id)sender
|
||||
{
|
||||
for (NSView *view in preferences_tabs) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
NSView *tab = preferences_tabs[[sender tag]];
|
||||
NSRect old = [_preferencesWindow frame];
|
||||
NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame];
|
||||
new.origin.x = old.origin.x;
|
||||
new.origin.y = old.origin.y + (old.size.height - new.size.height);
|
||||
[_preferencesWindow setFrame:new display:YES animate:_preferencesWindow.visible];
|
||||
[_preferencesWindow.contentView addSubview:tab];
|
||||
}
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)anItem
|
||||
{
|
||||
if ([anItem action] == @selector(toggleDeveloperMode:)) {
|
||||
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (IBAction) showPreferences: (id) sender
|
||||
{
|
||||
NSArray *objects;
|
||||
if (!_preferencesWindow) {
|
||||
[[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects];
|
||||
NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject];
|
||||
_preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier];
|
||||
preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab];
|
||||
[self switchPreferencesTab:first_toolbar_item];
|
||||
[_preferencesWindow center];
|
||||
}
|
||||
[_preferencesWindow makeKeyAndOrderFront:self];
|
||||
}
|
||||
|
||||
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
|
||||
{
|
||||
[[NSDocumentController sharedDocumentController] openDocument:self];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
|
||||
{
|
||||
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
|
||||
}
|
||||
@end
|
BIN
bsnes/gb/Cocoa/AppIcon.icns
Normal file
30
bsnes/gb/Cocoa/BigSurToolbar.h
Normal file
@ -0,0 +1,30 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#ifndef BigSurToolbar_h
|
||||
#define BigSurToolbar_h
|
||||
|
||||
/* Backport the toolbarStyle property to allow compilation with older SDKs*/
|
||||
#ifndef __MAC_10_16
|
||||
typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) {
|
||||
// The default value. The style will be determined by the window's given configuration
|
||||
NSWindowToolbarStyleAutomatic,
|
||||
// The toolbar will appear below the window title
|
||||
NSWindowToolbarStyleExpanded,
|
||||
// The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible
|
||||
NSWindowToolbarStylePreference,
|
||||
// The window title will appear inline with the toolbar when visible
|
||||
NSWindowToolbarStyleUnified,
|
||||
// Same as NSWindowToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window
|
||||
NSWindowToolbarStyleUnifiedCompact
|
||||
} API_AVAILABLE(macos(11.0));
|
||||
|
||||
@interface NSWindow (toolbarStyle)
|
||||
@property NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0));
|
||||
@end
|
||||
|
||||
@interface NSImage (SFSymbols)
|
||||
+ (instancetype)imageWithSystemSymbolName:(NSString *)symbolName accessibilityDescription:(NSString *)description API_AVAILABLE(macos(11.0));
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
BIN
bsnes/gb/Cocoa/CPU.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
bsnes/gb/Cocoa/CPU@2x.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
bsnes/gb/Cocoa/Cartridge.icns
Normal file
BIN
bsnes/gb/Cocoa/ColorCartridge.icns
Normal file
BIN
bsnes/gb/Cocoa/Display.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
bsnes/gb/Cocoa/Display@2x.png
Normal file
After Width: | Height: | Size: 20 KiB |
45
bsnes/gb/Cocoa/Document.h
Normal file
@ -0,0 +1,45 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "GBView.h"
|
||||
#include "GBImageView.h"
|
||||
#include "GBSplitView.h"
|
||||
|
||||
@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;
|
||||
@property (strong) IBOutlet NSTextField *consoleInput;
|
||||
@property (strong) IBOutlet NSWindow *mainWindow;
|
||||
@property (strong) IBOutlet NSView *memoryView;
|
||||
@property (strong) IBOutlet NSPanel *memoryWindow;
|
||||
@property (readonly) GB_gameboy_t *gameboy;
|
||||
@property (strong) IBOutlet NSTextField *memoryBankInput;
|
||||
@property (strong) IBOutlet NSToolbarItem *memoryBankItem;
|
||||
@property (strong) IBOutlet GBImageView *tilesetImageView;
|
||||
@property (strong) IBOutlet NSPopUpButton *tilesetPaletteButton;
|
||||
@property (strong) IBOutlet GBImageView *tilemapImageView;
|
||||
@property (strong) IBOutlet NSPopUpButton *tilemapPaletteButton;
|
||||
@property (strong) IBOutlet NSPopUpButton *tilemapMapButton;
|
||||
@property (strong) IBOutlet NSPopUpButton *TilemapSetButton;
|
||||
@property (strong) IBOutlet NSButton *gridButton;
|
||||
@property (strong) IBOutlet NSTabView *vramTabView;
|
||||
@property (strong) IBOutlet NSPanel *vramWindow;
|
||||
@property (strong) IBOutlet NSTextField *vramStatusLabel;
|
||||
@property (strong) IBOutlet NSTableView *paletteTableView;
|
||||
@property (strong) IBOutlet NSTableView *spritesTableView;
|
||||
@property (strong) IBOutlet NSPanel *printerFeedWindow;
|
||||
@property (strong) IBOutlet NSImageView *feedImageView;
|
||||
@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;
|
||||
-(void) performAtomicBlock: (void (^)())block;
|
||||
|
||||
@end
|
||||
|
1835
bsnes/gb/Cocoa/Document.m
Normal file
1080
bsnes/gb/Cocoa/Document.xib
Normal file
12
bsnes/gb/Cocoa/GBAudioClient.h
Normal file
@ -0,0 +1,12 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Core/gb.h>
|
||||
|
||||
@interface GBAudioClient : NSObject
|
||||
@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer);
|
||||
@property (readonly) UInt32 rate;
|
||||
@property (readonly, getter=isPlaying) bool playing;
|
||||
-(void) start;
|
||||
-(void) stop;
|
||||
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
|
||||
andSampleRate:(UInt32) rate;
|
||||
@end
|
111
bsnes/gb/Cocoa/GBAudioClient.m
Normal file
@ -0,0 +1,111 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import "GBAudioClient.h"
|
||||
|
||||
static OSStatus render(
|
||||
GBAudioClient *self,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData)
|
||||
|
||||
{
|
||||
GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData;
|
||||
|
||||
self.renderBlock(self.rate, inNumberFrames, buffer);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@implementation GBAudioClient
|
||||
{
|
||||
AudioComponentInstance audioUnit;
|
||||
}
|
||||
|
||||
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
|
||||
andSampleRate:(UInt32) rate
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Configure the search parameters to find the default playback output unit
|
||||
// (called the kAudioUnitSubType_RemoteIO on iOS but
|
||||
// kAudioUnitSubType_DefaultOutput on Mac OS X)
|
||||
AudioComponentDescription defaultOutputDescription;
|
||||
defaultOutputDescription.componentType = kAudioUnitType_Output;
|
||||
defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
defaultOutputDescription.componentFlags = 0;
|
||||
defaultOutputDescription.componentFlagsMask = 0;
|
||||
|
||||
// Get the default playback output unit
|
||||
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
|
||||
NSAssert(defaultOutput, @"Can't find default output");
|
||||
|
||||
// Create a new unit based on this that we'll use for output
|
||||
OSErr err = AudioComponentInstanceNew(defaultOutput, &audioUnit);
|
||||
NSAssert1(audioUnit, @"Error creating unit: %hd", err);
|
||||
|
||||
// Set our tone rendering function on the unit
|
||||
AURenderCallbackStruct input;
|
||||
input.inputProc = (void*)render;
|
||||
input.inputProcRefCon = (__bridge void * _Nullable)(self);
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&input,
|
||||
sizeof(input));
|
||||
NSAssert1(err == noErr, @"Error setting callback: %hd", err);
|
||||
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
streamFormat.mSampleRate = rate;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags =
|
||||
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
streamFormat.mBytesPerPacket = 4;
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mBytesPerFrame = 4;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
streamFormat.mBitsPerChannel = 2 * 8;
|
||||
err = AudioUnitSetProperty (audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&streamFormat,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
NSAssert1(err == noErr, @"Error setting stream format: %hd", err);
|
||||
err = AudioUnitInitialize(audioUnit);
|
||||
NSAssert1(err == noErr, @"Error initializing unit: %hd", err);
|
||||
|
||||
self.renderBlock = block;
|
||||
_rate = rate;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) start
|
||||
{
|
||||
OSErr err = AudioOutputUnitStart(audioUnit);
|
||||
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
|
||||
_playing = YES;
|
||||
|
||||
}
|
||||
|
||||
|
||||
-(void) stop
|
||||
{
|
||||
AudioOutputUnitStop(audioUnit);
|
||||
_playing = NO;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
[self stop];
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
}
|
||||
|
||||
@end
|
5
bsnes/gb/Cocoa/GBBorderView.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBBorderView : NSView
|
||||
|
||||
@end
|
26
bsnes/gb/Cocoa/GBBorderView.m
Normal file
@ -0,0 +1,26 @@
|
||||
#import "GBBorderView.h"
|
||||
|
||||
@implementation GBBorderView
|
||||
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
self.wantsLayer = YES;
|
||||
}
|
||||
|
||||
- (BOOL)wantsUpdateLayer
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateLayer
|
||||
{
|
||||
/* Wonderful, wonderful windowserver(?) bug. Using 0,0,0 here would cause it to render garbage
|
||||
on fullscreen windows on some High Sierra machines. Any other value, including the one used
|
||||
here (which is rendered exactly the same due to rounding) works around this bug. */
|
||||
self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0
|
||||
green:0
|
||||
blue:1.0 / 1024.0
|
||||
alpha:1.0].CGColor;
|
||||
}
|
||||
@end
|
35
bsnes/gb/Cocoa/GBButtons.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef GBButtons_h
|
||||
#define GBButtons_h
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
GBRight,
|
||||
GBLeft,
|
||||
GBUp,
|
||||
GBDown,
|
||||
GBA,
|
||||
GBB,
|
||||
GBSelect,
|
||||
GBStart,
|
||||
GBTurbo,
|
||||
GBRewind,
|
||||
GBUnderclock,
|
||||
GBButtonCount,
|
||||
GBGameBoyButtonCount = GBStart + 1,
|
||||
} GBButton;
|
||||
|
||||
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) {
|
||||
return [NSString stringWithFormat:@"GBPlayer%d%@", player + 1, GBButtonNames[button]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"GB%@", GBButtonNames[button]];
|
||||
}
|
||||
|
||||
#endif
|
4
bsnes/gb/Cocoa/GBButtons.m
Normal file
@ -0,0 +1,4 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "GBButtons.h"
|
||||
|
||||
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"};
|
5
bsnes/gb/Cocoa/GBCheatTextFieldCell.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBCheatTextFieldCell : NSTextFieldCell
|
||||
@property bool usesAddressFormat;
|
||||
@end
|
121
bsnes/gb/Cocoa/GBCheatTextFieldCell.m
Normal 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
|
17
bsnes/gb/Cocoa/GBCheatWindowController.h
Normal 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
|
||||
|
240
bsnes/gb/Cocoa/GBCheatWindowController.m
Normal 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
|
5
bsnes/gb/Cocoa/GBColorCell.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBColorCell : NSTextFieldCell
|
||||
|
||||
@end
|
48
bsnes/gb/Cocoa/GBColorCell.m
Normal file
@ -0,0 +1,48 @@
|
||||
#import "GBColorCell.h"
|
||||
|
||||
static inline double scale_channel(uint8_t x)
|
||||
{
|
||||
x &= 0x1f;
|
||||
return x / 31.0;
|
||||
}
|
||||
|
||||
@implementation GBColorCell
|
||||
{
|
||||
NSInteger _integerValue;
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)objectValue
|
||||
{
|
||||
|
||||
_integerValue = [objectValue integerValue];
|
||||
uint8_t r = _integerValue & 0x1F,
|
||||
g = (_integerValue >> 5) & 0x1F,
|
||||
b = (_integerValue >> 10) & 0x1F;
|
||||
super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{
|
||||
NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor]
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSInteger)integerValue
|
||||
{
|
||||
return _integerValue;
|
||||
}
|
||||
|
||||
- (int)intValue
|
||||
{
|
||||
return (int)_integerValue;
|
||||
}
|
||||
|
||||
|
||||
- (NSColor *) backgroundColor
|
||||
{
|
||||
uint16_t color = self.integerValue;
|
||||
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
|
||||
}
|
||||
|
||||
- (BOOL)drawsBackground
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
7
bsnes/gb/Cocoa/GBCompleteByteSlice.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import "Document.h"
|
||||
#import "HexFiend/HexFiend.h"
|
||||
#import "HexFiend/HFByteSlice.h"
|
||||
|
||||
@interface GBCompleteByteSlice : HFByteSlice
|
||||
- (instancetype) initWithByteArray:(HFByteArray *)array;
|
||||
@end
|
26
bsnes/gb/Cocoa/GBCompleteByteSlice.m
Normal file
@ -0,0 +1,26 @@
|
||||
#import "GBCompleteByteSlice.h"
|
||||
|
||||
@implementation GBCompleteByteSlice
|
||||
{
|
||||
HFByteArray *_array;
|
||||
}
|
||||
|
||||
- (instancetype) initWithByteArray:(HFByteArray *)array
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_array = array;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (unsigned long long)length
|
||||
{
|
||||
return [_array length];
|
||||
}
|
||||
|
||||
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
|
||||
{
|
||||
[_array copyBytes:dst range:range];
|
||||
}
|
||||
|
||||
@end
|
7
bsnes/gb/Cocoa/GBGLShader.h
Normal file
@ -0,0 +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 withBlendingMode: (GB_frame_blending_mode_t)blendingMode;
|
||||
@end
|
190
bsnes/gb/Cocoa/GBGLShader.m
Normal file
@ -0,0 +1,190 @@
|
||||
#import "GBGLShader.h"
|
||||
#import <OpenGL/gl3.h>
|
||||
|
||||
/*
|
||||
Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial
|
||||
|
||||
This code probably makes no sense after I upgraded it to OpenGL 3, since OpenGL makes aboslute no sense and has zero
|
||||
helpful documentation.
|
||||
*/
|
||||
|
||||
static NSString * const vertex_shader = @"\n\
|
||||
#version 150 \n\
|
||||
in vec4 aPosition;\n\
|
||||
void main(void) {\n\
|
||||
gl_Position = aPosition;\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
@implementation GBGLShader
|
||||
{
|
||||
GLuint resolution_uniform;
|
||||
GLuint texture_uniform;
|
||||
GLuint previous_texture_uniform;
|
||||
GLuint frame_blending_mode_uniform;
|
||||
|
||||
GLuint position_attribute;
|
||||
GLuint texture;
|
||||
GLuint previous_texture;
|
||||
GLuint program;
|
||||
}
|
||||
|
||||
+ (NSString *) shaderSourceForName:(NSString *) name
|
||||
{
|
||||
return [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name
|
||||
ofType:@"fsh"
|
||||
inDirectory:@"Shaders"]
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *) shaderName
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// Program
|
||||
NSString *fragment_shader = [[self class] shaderSourceForName:@"MasterShader"];
|
||||
fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"{filter}"
|
||||
withString:[[self class] shaderSourceForName:shaderName]];
|
||||
program = [[self class] programWithVertexShader:vertex_shader fragmentShader:fragment_shader];
|
||||
// Attributes
|
||||
position_attribute = glGetAttribLocation(program, "aPosition");
|
||||
// Uniforms
|
||||
resolution_uniform = glGetUniformLocation(program, "output_resolution");
|
||||
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
texture_uniform = glGetUniformLocation(program, "image");
|
||||
|
||||
glGenTextures(1, &previous_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, previous_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
previous_texture_uniform = glGetUniformLocation(program, "previous_image");
|
||||
|
||||
frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode");
|
||||
|
||||
// Configure OpenGL
|
||||
[self configureOpenGL];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
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(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);
|
||||
glUniform1i(previous_texture_uniform, 1);
|
||||
}
|
||||
glBindFragDataLocation(program, 0, "frag_color");
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
- (void)configureOpenGL
|
||||
{
|
||||
// Program
|
||||
|
||||
glUseProgram(program);
|
||||
|
||||
GLuint vao;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
GLuint vbo;
|
||||
glGenBuffers(1, &vbo);
|
||||
|
||||
// Attributes
|
||||
|
||||
|
||||
static GLfloat const quad[16] = {
|
||||
-1.f, -1.f, 0, 1,
|
||||
-1.f, +1.f, 0, 1,
|
||||
+1.f, -1.f, 0, 1,
|
||||
+1.f, +1.f, 0, 1,
|
||||
};
|
||||
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(position_attribute);
|
||||
glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
}
|
||||
|
||||
+ (GLuint)programWithVertexShader:(NSString*)vsh fragmentShader:(NSString*)fsh
|
||||
{
|
||||
// Build shaders
|
||||
GLuint vertex_shader = [self shaderWithContents:vsh type:GL_VERTEX_SHADER];
|
||||
GLuint fragment_shader = [self shaderWithContents:fsh type:GL_FRAGMENT_SHADER];
|
||||
// Create program
|
||||
GLuint program = glCreateProgram();
|
||||
// Attach shaders
|
||||
glAttachShader(program, vertex_shader);
|
||||
glAttachShader(program, fragment_shader);
|
||||
// Link program
|
||||
glLinkProgram(program);
|
||||
// Check for errors
|
||||
GLint status;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
GLchar messages[1024];
|
||||
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
|
||||
NSLog(@"%@:- GLSL Program Error: %s", self, messages);
|
||||
}
|
||||
// Delete shaders
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
glDeleteProgram(program);
|
||||
glDeleteTextures(1, &texture);
|
||||
glDeleteTextures(1, &previous_texture);
|
||||
|
||||
/* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */
|
||||
/* We're probably not freeing thing in the right place. */
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
|
||||
}
|
||||
|
||||
+ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type
|
||||
{
|
||||
|
||||
const GLchar *source = [contents UTF8String];
|
||||
// Create the shader object
|
||||
GLuint shader = glCreateShader(type);
|
||||
// Load the shader source
|
||||
glShaderSource(shader, 1, &source, 0);
|
||||
// Compile the shader
|
||||
glCompileShader(shader);
|
||||
// Check for errors
|
||||
GLint status = 0;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
GLchar messages[1024];
|
||||
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
|
||||
NSLog(@"%@:- GLSL Shader Error: %s", self, messages);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
@end
|
5
bsnes/gb/Cocoa/GBImageCell.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBImageCell : NSImageCell
|
||||
|
||||
@end
|
10
bsnes/gb/Cocoa/GBImageCell.m
Normal file
@ -0,0 +1,10 @@
|
||||
#import "GBImageCell.h"
|
||||
|
||||
@implementation GBImageCell
|
||||
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
|
||||
{
|
||||
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
[super drawWithFrame:cellFrame inView:controlView];
|
||||
}
|
||||
@end
|
23
bsnes/gb/Cocoa/GBImageView.h
Normal file
@ -0,0 +1,23 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@protocol GBImageViewDelegate;
|
||||
|
||||
@interface GBImageViewGridConfiguration : NSObject
|
||||
@property NSColor *color;
|
||||
@property NSUInteger size;
|
||||
- (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size;
|
||||
@end
|
||||
|
||||
@interface GBImageView : NSImageView
|
||||
@property (nonatomic) NSArray *horizontalGrids;
|
||||
@property (nonatomic) NSArray *verticalGrids;
|
||||
@property (nonatomic) bool displayScrollRect;
|
||||
@property NSRect scrollRect;
|
||||
@property (weak) IBOutlet id<GBImageViewDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol GBImageViewDelegate <NSObject>
|
||||
@optional
|
||||
- (void) mouseDidLeaveImageView: (GBImageView *)view;
|
||||
- (void) imageView: (GBImageView *)view mouseMovedToX:(NSUInteger) x Y:(NSUInteger) y;
|
||||
@end
|
127
bsnes/gb/Cocoa/GBImageView.m
Normal file
@ -0,0 +1,127 @@
|
||||
#import "GBImageView.h"
|
||||
|
||||
@implementation GBImageViewGridConfiguration
|
||||
- (instancetype)initWithColor:(NSColor *)color size:(NSUInteger)size
|
||||
{
|
||||
self = [super init];
|
||||
self.color = color;
|
||||
self.size = size;
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation GBImageView
|
||||
{
|
||||
NSTrackingArea *trackingArea;
|
||||
}
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
[super drawRect:dirtyRect];
|
||||
CGFloat y_ratio = self.frame.size.height / self.image.size.height;
|
||||
CGFloat x_ratio = self.frame.size.width / self.image.size.width;
|
||||
for (GBImageViewGridConfiguration *conf in self.verticalGrids) {
|
||||
[conf.color set];
|
||||
for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) {
|
||||
NSBezierPath *line = [NSBezierPath bezierPath];
|
||||
[line moveToPoint:NSMakePoint(0, y - 0.5)];
|
||||
[line lineToPoint:NSMakePoint(self.frame.size.width, y - 0.5)];
|
||||
[line setLineWidth:1.0];
|
||||
[line stroke];
|
||||
}
|
||||
}
|
||||
|
||||
for (GBImageViewGridConfiguration *conf in self.horizontalGrids) {
|
||||
[conf.color set];
|
||||
for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) {
|
||||
NSBezierPath *line = [NSBezierPath bezierPath];
|
||||
[line moveToPoint:NSMakePoint(x + 0.5, 0)];
|
||||
[line lineToPoint:NSMakePoint(x + 0.5, self.frame.size.height)];
|
||||
[line setLineWidth:1.0];
|
||||
[line stroke];
|
||||
}
|
||||
}
|
||||
|
||||
if (self.displayScrollRect) {
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite];
|
||||
for (unsigned x = 0; x < 2; x++) {
|
||||
for (unsigned y = 0; y < 2; y++) {
|
||||
NSRect rect = self.scrollRect;
|
||||
rect.origin.x *= x_ratio;
|
||||
rect.origin.y *= y_ratio;
|
||||
rect.size.width *= x_ratio;
|
||||
rect.size.height *= y_ratio;
|
||||
rect.origin.y = self.frame.size.height - rect.origin.y - rect.size.height;
|
||||
|
||||
rect.origin.x -= self.frame.size.width * x;
|
||||
rect.origin.y += self.frame.size.height * y;
|
||||
|
||||
|
||||
NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect];
|
||||
[path appendBezierPath:subpath];
|
||||
}
|
||||
}
|
||||
[path setWindingRule:NSEvenOddWindingRule];
|
||||
[path setLineWidth:4.0];
|
||||
[path setLineJoinStyle:NSRoundLineJoinStyle];
|
||||
[[NSColor colorWithWhite:0.2 alpha:0.5] set];
|
||||
[path fill];
|
||||
[path addClip];
|
||||
[[NSColor colorWithWhite:0.0 alpha:0.6] set];
|
||||
[path stroke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHorizontalGrids:(NSArray *)horizontalGrids
|
||||
{
|
||||
self->_horizontalGrids = horizontalGrids;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setVerticalGrids:(NSArray *)verticalGrids
|
||||
{
|
||||
self->_verticalGrids = verticalGrids;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setDisplayScrollRect:(bool)displayScrollRect
|
||||
{
|
||||
self->_displayScrollRect = displayScrollRect;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (trackingArea != nil) {
|
||||
[self removeTrackingArea:trackingArea];
|
||||
}
|
||||
|
||||
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:trackingArea];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(mouseDidLeaveImageView:)]) {
|
||||
[self.delegate mouseDidLeaveImageView:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)theEvent
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(imageView:mouseMovedToX:Y:)]) {
|
||||
NSPoint location = [self convertPoint:theEvent.locationInWindow fromView:nil];
|
||||
location.x /= self.bounds.size.width;
|
||||
location.y /= self.bounds.size.height;
|
||||
location.y = 1 - location.y;
|
||||
location.x *= self.image.size.width;
|
||||
location.y *= self.image.size.height;
|
||||
[self.delegate imageView:self mouseMovedToX:(NSUInteger)location.x Y:(NSUInteger)location.y];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
17
bsnes/gb/Cocoa/GBMemoryByteArray.h
Normal file
@ -0,0 +1,17 @@
|
||||
#import "Document.h"
|
||||
#import "HexFiend/HexFiend.h"
|
||||
#import "HexFiend/HFByteArray.h"
|
||||
|
||||
typedef enum {
|
||||
GBMemoryEntireSpace,
|
||||
GBMemoryROM,
|
||||
GBMemoryVRAM,
|
||||
GBMemoryExternalRAM,
|
||||
GBMemoryRAM
|
||||
} GB_memory_mode_t;
|
||||
|
||||
@interface GBMemoryByteArray : HFByteArray
|
||||
- (instancetype) initWithDocument:(Document *)document;
|
||||
@property uint16_t selectedBank;
|
||||
@property GB_memory_mode_t mode;
|
||||
@end
|
177
bsnes/gb/Cocoa/GBMemoryByteArray.m
Normal file
@ -0,0 +1,177 @@
|
||||
#define GB_INTERNAL // Todo: Some memory accesses are being done using the struct directly
|
||||
#import "GBMemoryByteArray.h"
|
||||
#import "GBCompleteByteSlice.h"
|
||||
|
||||
|
||||
@implementation GBMemoryByteArray
|
||||
{
|
||||
Document *_document;
|
||||
}
|
||||
|
||||
- (instancetype) initWithDocument:(Document *)document
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_document = document;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (unsigned long long)length
|
||||
{
|
||||
switch (_mode) {
|
||||
case GBMemoryEntireSpace:
|
||||
return 0x10000;
|
||||
case GBMemoryROM:
|
||||
return 0x8000;
|
||||
case GBMemoryVRAM:
|
||||
return 0x2000;
|
||||
case GBMemoryExternalRAM:
|
||||
return 0x2000;
|
||||
case GBMemoryRAM:
|
||||
return 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
|
||||
{
|
||||
__block uint16_t addr = (uint16_t) range.location;
|
||||
__block unsigned long long length = range.length;
|
||||
if (_mode == GBMemoryEntireSpace) {
|
||||
while (length) {
|
||||
*(dst++) = [_document readMemory:addr++];
|
||||
length--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
[_document performAtomicBlock:^{
|
||||
unsigned char *_dst = dst;
|
||||
uint16_t bank_backup = 0;
|
||||
GB_gameboy_t *gb = _document.gameboy;
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
bank_backup = gb->mbc_rom_bank;
|
||||
gb->mbc_rom_bank = self.selectedBank;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
bank_backup = gb->cgb_vram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_vram_bank = self.selectedBank;
|
||||
}
|
||||
addr += 0x8000;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
bank_backup = gb->mbc_ram_bank;
|
||||
gb->mbc_ram_bank = self.selectedBank;
|
||||
addr += 0xA000;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
bank_backup = gb->cgb_ram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_ram_bank = self.selectedBank;
|
||||
}
|
||||
addr += 0xC000;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
while (length) {
|
||||
*(_dst++) = [_document readMemory:addr++];
|
||||
length--;
|
||||
}
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
gb->mbc_rom_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
gb->cgb_vram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
gb->mbc_ram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
gb->cgb_ram_bank = bank_backup;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)byteSlices
|
||||
{
|
||||
return @[[[GBCompleteByteSlice alloc] initWithByteArray:self]];
|
||||
}
|
||||
|
||||
- (HFByteArray *)subarrayWithRange:(HFRange)range
|
||||
{
|
||||
unsigned char arr[range.length];
|
||||
[self copyBytes:arr range:range];
|
||||
HFByteArray *ret = [[HFBTreeByteArray alloc] init];
|
||||
HFFullMemoryByteSlice *slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData dataWithBytes:arr length:range.length]];
|
||||
[ret insertByteSlice:slice inRange:HFRangeMake(0, 0)];
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange
|
||||
{
|
||||
if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */
|
||||
[_document performAtomicBlock:^{
|
||||
uint16_t addr = (uint16_t) lrange.location;
|
||||
uint16_t bank_backup = 0;
|
||||
GB_gameboy_t *gb = _document.gameboy;
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
bank_backup = gb->mbc_rom_bank;
|
||||
gb->mbc_rom_bank = self.selectedBank;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
bank_backup = gb->cgb_vram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_vram_bank = self.selectedBank;
|
||||
}
|
||||
addr += 0x8000;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
bank_backup = gb->mbc_ram_bank;
|
||||
gb->mbc_ram_bank = self.selectedBank;
|
||||
addr += 0xA000;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
bank_backup = gb->cgb_ram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_ram_bank = self.selectedBank;
|
||||
}
|
||||
addr += 0xC000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint8_t values[lrange.length];
|
||||
[slice copyBytes:values range:HFRangeMake(0, lrange.length)];
|
||||
uint8_t *src = values;
|
||||
unsigned long long length = lrange.length;
|
||||
while (length) {
|
||||
[_document writeMemory:addr++ value:*(src++)];
|
||||
length--;
|
||||
}
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
gb->mbc_rom_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
gb->cgb_vram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
gb->mbc_ram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
gb->cgb_ram_bank = bank_backup;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
6
bsnes/gb/Cocoa/GBOpenGLView.h
Normal file
@ -0,0 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "GBGLShader.h"
|
||||
|
||||
@interface GBOpenGLView : NSOpenGLView
|
||||
@property GBGLShader *shader;
|
||||
@end
|
39
bsnes/gb/Cocoa/GBOpenGLView.m
Normal file
@ -0,0 +1,39 @@
|
||||
#import "GBOpenGLView.h"
|
||||
#import "GBView.h"
|
||||
#include <OpenGL/gl.h>
|
||||
|
||||
@implementation GBOpenGLView
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
if (!self.shader) {
|
||||
self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]];
|
||||
}
|
||||
|
||||
GBView *gbview = (GBView *)self.superview;
|
||||
double scale = self.window.backingScaleFactor;
|
||||
glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
|
||||
|
||||
if (gbview.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();
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil];
|
||||
return [super initWithFrame:frameRect pixelFormat:format];
|
||||
}
|
||||
|
||||
- (void) filterChanged
|
||||
{
|
||||
self.shader = nil;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
@end
|
6
bsnes/gb/Cocoa/GBOptionalVisualEffectView.h
Normal file
@ -0,0 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
/* Fake interface so the compiler assumes it conforms to NSVisualEffectView */
|
||||
@interface GBOptionalVisualEffectView : NSVisualEffectView
|
||||
|
||||
@end
|
18
bsnes/gb/Cocoa/GBOptionalVisualEffectView.m
Normal 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
|
27
bsnes/gb/Cocoa/GBPreferencesWindow.h
Normal file
@ -0,0 +1,27 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@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;
|
||||
@property (weak) IBOutlet NSPopUpButton *cgbPopupButton;
|
||||
@property (weak) IBOutlet NSPopUpButton *preferredJoypadButton;
|
||||
@property (weak) IBOutlet NSPopUpButton *playerListButton;
|
||||
|
||||
@end
|
647
bsnes/gb/Cocoa/GBPreferencesWindow.m
Normal file
@ -0,0 +1,647 @@
|
||||
#import "GBPreferencesWindow.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "GBButtons.h"
|
||||
#import "BigSurToolbar.h"
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
@implementation GBPreferencesWindow
|
||||
{
|
||||
bool is_button_being_modified;
|
||||
NSInteger button_being_modified;
|
||||
signed joystick_configuration_state;
|
||||
NSString *joystick_being_configured;
|
||||
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
|
||||
{
|
||||
/* The filter list as ordered in the popup button */
|
||||
static NSArray * filters = nil;
|
||||
if (!filters) {
|
||||
filters = @[
|
||||
@"NearestNeighbor",
|
||||
@"Bilinear",
|
||||
@"SmoothBilinear",
|
||||
@"MonoLCD",
|
||||
@"LCD",
|
||||
@"CRT",
|
||||
@"Scale2x",
|
||||
@"Scale4x",
|
||||
@"AAScale2x",
|
||||
@"AAScale4x",
|
||||
@"HQ2x",
|
||||
@"OmniScale",
|
||||
@"OmniScaleLegacy",
|
||||
@"AAOmniScaleLegacy",
|
||||
];
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (NSWindowToolbarStyle)toolbarStyle
|
||||
{
|
||||
return NSWindowToolbarStylePreference;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
joystick_configuration_state = -1;
|
||||
[self.configureJoypadButton setEnabled:YES];
|
||||
[self.skipButton setEnabled:NO];
|
||||
[self.configureJoypadButton setTitle:@"Configure Controller"];
|
||||
[super close];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)graphicsFilterPopupButton
|
||||
{
|
||||
return _graphicsFilterPopupButton;
|
||||
}
|
||||
|
||||
- (void)setGraphicsFilterPopupButton:(NSPopUpButton *)graphicsFilterPopupButton
|
||||
{
|
||||
_graphicsFilterPopupButton = graphicsFilterPopupButton;
|
||||
NSString *filter = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
|
||||
[_graphicsFilterPopupButton selectItemAtIndex:[[[self class] filterList] indexOfObject:filter]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)highpassFilterPopupButton
|
||||
{
|
||||
return _highpassFilterPopupButton;
|
||||
}
|
||||
|
||||
- (void)setColorCorrectionPopupButton:(NSPopUpButton *)colorCorrectionPopupButton
|
||||
{
|
||||
_colorCorrectionPopupButton = colorCorrectionPopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"];
|
||||
[_colorCorrectionPopupButton selectItemAtIndex:mode];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)colorCorrectionPopupButton
|
||||
{
|
||||
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;
|
||||
NSInteger length = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"];
|
||||
[_rewindPopupButton selectItemWithTag:length];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)rewindPopupButton
|
||||
{
|
||||
return _rewindPopupButton;
|
||||
}
|
||||
|
||||
- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton
|
||||
{
|
||||
_highpassFilterPopupButton = highpassFilterPopupButton;
|
||||
[_highpassFilterPopupButton selectItemAtIndex:[[[NSUserDefaults standardUserDefaults] objectForKey:@"GBHighpassFilter"] unsignedIntegerValue]];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
{
|
||||
if (self.playerListButton.selectedTag == 0) {
|
||||
return GBButtonCount;
|
||||
}
|
||||
return GBGameBoyButtonCount;
|
||||
}
|
||||
|
||||
- (unsigned) usesForKey:(unsigned) key
|
||||
{
|
||||
unsigned ret = 0;
|
||||
for (unsigned player = 4; player--;) {
|
||||
for (unsigned button = player == 0? GBButtonCount:GBGameBoyButtonCount; button--;) {
|
||||
NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)];
|
||||
if (other && [other unsignedIntValue] == key) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
if ([tableColumn.identifier isEqualToString:@"keyName"]) {
|
||||
return GBButtonNames[row];
|
||||
}
|
||||
|
||||
if (is_button_being_modified && button_being_modified == row) {
|
||||
return @"Select a new key...";
|
||||
}
|
||||
|
||||
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
|
||||
if (key) {
|
||||
if ([self usesForKey:[key unsignedIntValue]] > 1) {
|
||||
return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]]
|
||||
attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0],
|
||||
NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
|
||||
}];
|
||||
}
|
||||
return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
|
||||
}
|
||||
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
is_button_being_modified = true;
|
||||
button_being_modified = row;
|
||||
tableView.enabled = NO;
|
||||
self.playerListButton.enabled = NO;
|
||||
[tableView reloadData];
|
||||
[self makeFirstResponder:self];
|
||||
});
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(void)keyDown:(NSEvent *)theEvent
|
||||
{
|
||||
if (!is_button_being_modified) {
|
||||
if (self.firstResponder != self.controlsTableView && [theEvent type] != NSEventTypeFlagsChanged) {
|
||||
[super keyDown:theEvent];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
is_button_being_modified = false;
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode
|
||||
forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)];
|
||||
self.controlsTableView.enabled = YES;
|
||||
self.playerListButton.enabled = YES;
|
||||
[self.controlsTableView reloadData];
|
||||
[self makeFirstResponder:self.controlsTableView];
|
||||
}
|
||||
|
||||
- (void) flagsChanged:(NSEvent *)event
|
||||
{
|
||||
if (event.modifierFlags > previousModifiers) {
|
||||
[self keyDown:event];
|
||||
}
|
||||
|
||||
previousModifiers = event.modifierFlags;
|
||||
}
|
||||
|
||||
- (IBAction)graphicFilterChanged:(NSPopUpButton *)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]]
|
||||
forKey:@"GBFilter"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)highpassFilterChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
forKey:@"GBHighpassFilter"];
|
||||
[[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
|
||||
forKey:@"GBAspectRatioUnkept"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)colorCorrectionChanged:(id)sender
|
||||
{
|
||||
[[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
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBRewindLength"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction) configureJoypad:(id)sender
|
||||
{
|
||||
[self.configureJoypadButton setEnabled:NO];
|
||||
[self.skipButton setEnabled:YES];
|
||||
joystick_being_configured = nil;
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (IBAction) skipButton:(id)sender
|
||||
{
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (void) advanceConfigurationStateMachine
|
||||
{
|
||||
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:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
|
||||
}
|
||||
else {
|
||||
joystick_configuration_state = -1;
|
||||
[self.configureJoypadButton setEnabled:YES];
|
||||
[self.skipButton setEnabled:NO];
|
||||
[self.configureJoypadButton setTitle:@"Configure Joypad"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
/* 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 = controller.uniqueID;
|
||||
}
|
||||
else if (![joystick_being_configured isEqualToString:controller.uniqueID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy];
|
||||
|
||||
NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy];
|
||||
|
||||
|
||||
if (!instance_mappings) {
|
||||
instance_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
if (!name_mappings) {
|
||||
name_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSMutableDictionary *mapping = nil;
|
||||
if (joystick_configuration_state != 0) {
|
||||
mapping = [instance_mappings[controller.uniqueID] mutableCopy];
|
||||
}
|
||||
else {
|
||||
mapping = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
- (NSButton *)analogControlsCheckbox
|
||||
{
|
||||
return _analogControlsCheckbox;
|
||||
}
|
||||
|
||||
- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox
|
||||
{
|
||||
_analogControlsCheckbox = analogControlsCheckbox;
|
||||
[_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]];
|
||||
}
|
||||
|
||||
- (NSButton *)aspectRatioCheckbox
|
||||
{
|
||||
return _aspectRatioCheckbox;
|
||||
}
|
||||
|
||||
- (void)setAspectRatioCheckbox:(NSButton *)aspectRatioCheckbox
|
||||
{
|
||||
_aspectRatioCheckbox = aspectRatioCheckbox;
|
||||
[_aspectRatioCheckbox setState: ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[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];
|
||||
}
|
||||
|
||||
- (IBAction)selectOtherBootROMFolder:(id)sender
|
||||
{
|
||||
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
|
||||
[panel setCanChooseDirectories:YES];
|
||||
[panel setCanChooseFiles:NO];
|
||||
[panel setPrompt:@"Select"];
|
||||
[panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]];
|
||||
[panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) {
|
||||
if (result == NSModalResponseOK) {
|
||||
NSURL *url = [[panel URLs] firstObject];
|
||||
[[NSUserDefaults standardUserDefaults] setURL:url forKey:@"GBBootROMsFolder"];
|
||||
}
|
||||
[self updateBootROMFolderButton];
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void) updateBootROMFolderButton
|
||||
{
|
||||
NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"];
|
||||
BOOL is_dir = false;
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&is_dir];
|
||||
if (!is_dir) url = nil;
|
||||
|
||||
if (url) {
|
||||
[self.bootROMsFolderItem setTitle:[url lastPathComponent]];
|
||||
NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
|
||||
[icon setSize:NSMakeSize(16, 16)];
|
||||
[self.bootROMsFolderItem setHidden:NO];
|
||||
[self.bootROMsFolderItem setImage:icon];
|
||||
[self.bootROMsButton selectItemAtIndex:1];
|
||||
}
|
||||
else {
|
||||
[self.bootROMsFolderItem setHidden:YES];
|
||||
[self.bootROMsButton selectItemAtIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)useBuiltinBootROMs:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setURL:nil forKey:@"GBBootROMsFolder"];
|
||||
[self updateBootROMFolderButton];
|
||||
}
|
||||
|
||||
- (void)setDmgPopupButton:(NSPopUpButton *)dmgPopupButton
|
||||
{
|
||||
_dmgPopupButton = dmgPopupButton;
|
||||
[_dmgPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)dmgPopupButton
|
||||
{
|
||||
return _dmgPopupButton;
|
||||
}
|
||||
|
||||
- (void)setSgbPopupButton:(NSPopUpButton *)sgbPopupButton
|
||||
{
|
||||
_sgbPopupButton = sgbPopupButton;
|
||||
[_sgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)sgbPopupButton
|
||||
{
|
||||
return _sgbPopupButton;
|
||||
}
|
||||
|
||||
- (void)setCgbPopupButton:(NSPopUpButton *)cgbPopupButton
|
||||
{
|
||||
_cgbPopupButton = cgbPopupButton;
|
||||
[_cgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)cgbPopupButton
|
||||
{
|
||||
return _cgbPopupButton;
|
||||
}
|
||||
|
||||
- (IBAction)dmgModelChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBDMGModel"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBDMGModelChanged" object:nil];
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)sgbModelChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBSGBModel"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBSGBModelChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)cgbModelChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBCGBModel"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)reloadButtonsData:(id)sender
|
||||
{
|
||||
[self.controlsTableView reloadData];
|
||||
}
|
||||
|
||||
- (void)setPreferredJoypadButton:(NSPopUpButton *)preferredJoypadButton
|
||||
{
|
||||
_preferredJoypadButton = preferredJoypadButton;
|
||||
[self refreshJoypadMenu:nil];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)preferredJoypadButton
|
||||
{
|
||||
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
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
||||
if (!selected_controller) {
|
||||
[self.preferredJoypadButton selectItemWithTitle:@"None"];
|
||||
}
|
||||
[self.controlsTableView reloadData];
|
||||
}
|
||||
|
||||
- (IBAction)changeDefaultJoypad:(id)sender
|
||||
{
|
||||
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy];
|
||||
if (!default_joypads) {
|
||||
default_joypads = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSString *player_string = n2s(self.playerListButton.selectedTag);
|
||||
if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) {
|
||||
[default_joypads removeObjectForKey:player_string];
|
||||
}
|
||||
else {
|
||||
default_joypads[player_string] = [[sender selectedItem] identifier];
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
|
||||
}
|
||||
@end
|
7
bsnes/gb/Cocoa/GBSplitView.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBSplitView : NSSplitView
|
||||
|
||||
-(void) setDividerColor:(NSColor *)color;
|
||||
- (NSArray<NSView *> *)arrangedSubviews;
|
||||
@end
|
33
bsnes/gb/Cocoa/GBSplitView.m
Normal 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
|
6
bsnes/gb/Cocoa/GBTerminalTextFieldCell.h
Normal file
@ -0,0 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
|
||||
@interface GBTerminalTextFieldCell : NSTextFieldCell
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
231
bsnes/gb/Cocoa/GBTerminalTextFieldCell.m
Normal file
@ -0,0 +1,231 @@
|
||||
#import <Carbon/Carbon.h>
|
||||
#import "GBTerminalTextFieldCell.h"
|
||||
|
||||
@interface GBTerminalTextView : NSTextView
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
||||
|
||||
@implementation GBTerminalTextFieldCell
|
||||
{
|
||||
GBTerminalTextView *field_editor;
|
||||
}
|
||||
|
||||
- (NSTextView *)fieldEditorForView:(NSView *)controlView
|
||||
{
|
||||
if (field_editor) {
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
}
|
||||
field_editor = [[GBTerminalTextView alloc] init];
|
||||
[field_editor setFieldEditor:YES];
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GBTerminalTextView
|
||||
{
|
||||
NSMutableOrderedSet *lines;
|
||||
NSUInteger current_line;
|
||||
bool reverse_search_mode;
|
||||
NSRange auto_complete_range;
|
||||
uintptr_t auto_complete_context;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return NULL;
|
||||
}
|
||||
lines = [[NSMutableOrderedSet alloc] init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)moveUp:(id)sender
|
||||
{
|
||||
reverse_search_mode = false;
|
||||
if (current_line != 0) {
|
||||
current_line--;
|
||||
[self setString:[lines objectAtIndex:current_line]];
|
||||
}
|
||||
else {
|
||||
[self setSelectedRange:NSMakeRange(0, 0)];
|
||||
NSBeep();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveDown:(id)sender
|
||||
{
|
||||
reverse_search_mode = false;
|
||||
if (current_line == [lines count]) {
|
||||
[self setString:@""];
|
||||
NSBeep();
|
||||
return;
|
||||
}
|
||||
current_line++;
|
||||
if (current_line == [lines count]) {
|
||||
[self setString:@""];
|
||||
}
|
||||
else {
|
||||
[self setString:[lines objectAtIndex:current_line]];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)insertNewline:(id)sender
|
||||
{
|
||||
if ([self.string length]) {
|
||||
NSString *string = [self.string copy];
|
||||
[lines removeObject:string];
|
||||
[lines addObject:string];
|
||||
}
|
||||
[super insertNewline:sender];
|
||||
current_line = [lines count];
|
||||
reverse_search_mode = false;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)event
|
||||
{
|
||||
if (event.keyCode == kVK_ANSI_R && (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagControl) {
|
||||
if ([lines count] == 0) {
|
||||
NSBeep();
|
||||
return;
|
||||
}
|
||||
if (!reverse_search_mode) {
|
||||
[self selectAll:self];
|
||||
current_line = [lines count] - 1;
|
||||
}
|
||||
else {
|
||||
if (current_line != 0) {
|
||||
current_line--;
|
||||
}
|
||||
else {
|
||||
NSBeep();
|
||||
}
|
||||
}
|
||||
|
||||
if (self.string.length) {
|
||||
[self updateReverseSearch];
|
||||
}
|
||||
else {
|
||||
[self setNeedsDisplay:YES];
|
||||
reverse_search_mode = true;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
[super keyDown:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateReverseSearch
|
||||
{
|
||||
NSUInteger old_line = current_line;
|
||||
reverse_search_mode = false;
|
||||
NSString *substring = [self.string substringWithRange:self.selectedRange];
|
||||
do {
|
||||
NSString *line = [lines objectAtIndex:current_line];
|
||||
NSRange range = [line rangeOfString:substring];
|
||||
if (range.location != NSNotFound) {
|
||||
self.string = line;
|
||||
[self setSelectedRange:range];
|
||||
reverse_search_mode = true;
|
||||
return;
|
||||
}
|
||||
} while (current_line--);
|
||||
current_line = old_line;
|
||||
reverse_search_mode = true;
|
||||
NSBeep();
|
||||
}
|
||||
|
||||
- (void) insertText:(NSString *)string replacementRange:(NSRange)range
|
||||
{
|
||||
if (reverse_search_mode) {
|
||||
range = self.selectedRange;
|
||||
self.string = [[self.string substringWithRange:range] stringByAppendingString:string];
|
||||
[self selectAll:nil];
|
||||
[self updateReverseSearch];
|
||||
}
|
||||
else {
|
||||
[super insertText:string replacementRange:range];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)deleteBackward:(id)sender
|
||||
{
|
||||
if (reverse_search_mode && self.string.length) {
|
||||
NSRange range = self.selectedRange;
|
||||
range.length--;
|
||||
self.string = [self.string substringWithRange:range];
|
||||
if (range.length) {
|
||||
[self selectAll:nil];
|
||||
[self updateReverseSearch];
|
||||
}
|
||||
else {
|
||||
reverse_search_mode = true;
|
||||
current_line = [lines count] - 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
[super deleteBackward:sender];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
|
||||
{
|
||||
reverse_search_mode = false;
|
||||
auto_complete_context = 0;
|
||||
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
reverse_search_mode = false;
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
|
||||
-(void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
[super drawRect:dirtyRect];
|
||||
if (reverse_search_mode && [super string].length == 0) {
|
||||
NSMutableDictionary *attributes = [self.typingAttributes mutableCopy];
|
||||
NSColor *color = [attributes[NSForegroundColorAttributeName] colorWithAlphaComponent:0.5];
|
||||
[attributes setObject:color forKey:NSForegroundColorAttributeName];
|
||||
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
|
||||
}
|
||||
}
|
||||
|
||||
/* Todo: lazy design, make it use a delegate instead of having a gb reference*/
|
||||
|
||||
- (void)insertTab:(id)sender
|
||||
{
|
||||
if (auto_complete_context == 0) {
|
||||
NSRange selection = self.selectedRange;
|
||||
if (selection.length) {
|
||||
[self delete:nil];
|
||||
}
|
||||
auto_complete_range = NSMakeRange(selection.location, 0);
|
||||
}
|
||||
char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String);
|
||||
uintptr_t context = auto_complete_context;
|
||||
char *completion = GB_debugger_complete_substring(self.gb, substring, &context);
|
||||
free(substring);
|
||||
if (completion) {
|
||||
NSString *ns_completion = @(completion);
|
||||
free(completion);
|
||||
if (!ns_completion) {
|
||||
goto error;
|
||||
}
|
||||
self.selectedRange = auto_complete_range;
|
||||
auto_complete_range.length = ns_completion.length;
|
||||
[self replaceCharactersInRange:self.selectedRange withString:ns_completion];
|
||||
auto_complete_context = context;
|
||||
return;
|
||||
}
|
||||
error:
|
||||
auto_complete_context = context;
|
||||
NSBeep();
|
||||
}
|
||||
|
||||
@end
|
26
bsnes/gb/Cocoa/GBView.h
Normal file
@ -0,0 +1,26 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
typedef enum {
|
||||
GB_FRAME_BLENDING_MODE_DISABLED,
|
||||
GB_FRAME_BLENDING_MODE_SIMPLE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE_ODD,
|
||||
} GB_frame_blending_mode_t;
|
||||
|
||||
@interface GBView : NSView<JOYListener>
|
||||
- (void) flip;
|
||||
- (uint32_t *) pixels;
|
||||
@property GB_gameboy_t *gb;
|
||||
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
|
||||
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;
|
||||
@property bool isRewinding;
|
||||
@property NSView *internalView;
|
||||
- (void) createInternalView;
|
||||
- (uint32_t *)currentBuffer;
|
||||
- (uint32_t *)previousBuffer;
|
||||
- (void)screenSizeChanged;
|
||||
- (void)setRumble: (double)amp;
|
||||
@end
|
553
bsnes/gb/Cocoa/GBView.m
Normal file
@ -0,0 +1,553 @@
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#import "GBView.h"
|
||||
#import "GBViewGL.h"
|
||||
#import "GBViewMetal.h"
|
||||
#import "GBButtons.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
|
||||
#define JOYSTICK_HIGH 0x4000
|
||||
#define JOYSTICK_LOW 0x3800
|
||||
|
||||
static const uint8_t workboy_ascii_to_key[] = {
|
||||
['0'] = GB_WORKBOY_0,
|
||||
['`'] = GB_WORKBOY_UMLAUT,
|
||||
['1'] = GB_WORKBOY_1,
|
||||
['2'] = GB_WORKBOY_2,
|
||||
['3'] = GB_WORKBOY_3,
|
||||
['4'] = GB_WORKBOY_4,
|
||||
['5'] = GB_WORKBOY_5,
|
||||
['6'] = GB_WORKBOY_6,
|
||||
['7'] = GB_WORKBOY_7,
|
||||
['8'] = GB_WORKBOY_8,
|
||||
['9'] = GB_WORKBOY_9,
|
||||
|
||||
['\r'] = GB_WORKBOY_ENTER,
|
||||
[3] = GB_WORKBOY_ENTER,
|
||||
|
||||
['!'] = GB_WORKBOY_EXCLAMATION_MARK,
|
||||
['$'] = GB_WORKBOY_DOLLAR,
|
||||
['#'] = GB_WORKBOY_HASH,
|
||||
['~'] = GB_WORKBOY_TILDE,
|
||||
['*'] = GB_WORKBOY_ASTERISK,
|
||||
['+'] = GB_WORKBOY_PLUS,
|
||||
['-'] = GB_WORKBOY_MINUS,
|
||||
['('] = GB_WORKBOY_LEFT_PARENTHESIS,
|
||||
[')'] = GB_WORKBOY_RIGHT_PARENTHESIS,
|
||||
[';'] = GB_WORKBOY_SEMICOLON,
|
||||
[':'] = GB_WORKBOY_COLON,
|
||||
['%'] = GB_WORKBOY_PERCENT,
|
||||
['='] = GB_WORKBOY_EQUAL,
|
||||
[','] = GB_WORKBOY_COMMA,
|
||||
['<'] = GB_WORKBOY_LT,
|
||||
['.'] = GB_WORKBOY_DOT,
|
||||
['>'] = GB_WORKBOY_GT,
|
||||
['/'] = GB_WORKBOY_SLASH,
|
||||
['?'] = GB_WORKBOY_QUESTION_MARK,
|
||||
[' '] = GB_WORKBOY_SPACE,
|
||||
['\''] = GB_WORKBOY_QUOTE,
|
||||
['@'] = GB_WORKBOY_AT,
|
||||
|
||||
['q'] = GB_WORKBOY_Q,
|
||||
['w'] = GB_WORKBOY_W,
|
||||
['e'] = GB_WORKBOY_E,
|
||||
['r'] = GB_WORKBOY_R,
|
||||
['t'] = GB_WORKBOY_T,
|
||||
['y'] = GB_WORKBOY_Y,
|
||||
['u'] = GB_WORKBOY_U,
|
||||
['i'] = GB_WORKBOY_I,
|
||||
['o'] = GB_WORKBOY_O,
|
||||
['p'] = GB_WORKBOY_P,
|
||||
['a'] = GB_WORKBOY_A,
|
||||
['s'] = GB_WORKBOY_S,
|
||||
['d'] = GB_WORKBOY_D,
|
||||
['f'] = GB_WORKBOY_F,
|
||||
['g'] = GB_WORKBOY_G,
|
||||
['h'] = GB_WORKBOY_H,
|
||||
['j'] = GB_WORKBOY_J,
|
||||
['k'] = GB_WORKBOY_K,
|
||||
['l'] = GB_WORKBOY_L,
|
||||
['z'] = GB_WORKBOY_Z,
|
||||
['x'] = GB_WORKBOY_X,
|
||||
['c'] = GB_WORKBOY_C,
|
||||
['v'] = GB_WORKBOY_V,
|
||||
['b'] = GB_WORKBOY_B,
|
||||
['n'] = GB_WORKBOY_N,
|
||||
['m'] = GB_WORKBOY_M,
|
||||
};
|
||||
|
||||
static const uint8_t workboy_vk_to_key[] = {
|
||||
[kVK_F1] = GB_WORKBOY_CLOCK,
|
||||
[kVK_F2] = GB_WORKBOY_TEMPERATURE,
|
||||
[kVK_F3] = GB_WORKBOY_MONEY,
|
||||
[kVK_F4] = GB_WORKBOY_CALCULATOR,
|
||||
[kVK_F5] = GB_WORKBOY_DATE,
|
||||
[kVK_F6] = GB_WORKBOY_CONVERSION,
|
||||
[kVK_F7] = GB_WORKBOY_RECORD,
|
||||
[kVK_F8] = GB_WORKBOY_WORLD,
|
||||
[kVK_F9] = GB_WORKBOY_PHONE,
|
||||
[kVK_F10] = GB_WORKBOY_UNKNOWN,
|
||||
[kVK_Delete] = GB_WORKBOY_BACKSPACE,
|
||||
[kVK_Shift] = GB_WORKBOY_SHIFT_DOWN,
|
||||
[kVK_RightShift] = GB_WORKBOY_SHIFT_DOWN,
|
||||
[kVK_UpArrow] = GB_WORKBOY_UP,
|
||||
[kVK_DownArrow] = GB_WORKBOY_DOWN,
|
||||
[kVK_LeftArrow] = GB_WORKBOY_LEFT,
|
||||
[kVK_RightArrow] = GB_WORKBOY_RIGHT,
|
||||
[kVK_Escape] = GB_WORKBOY_ESCAPE,
|
||||
[kVK_ANSI_KeypadDecimal] = GB_WORKBOY_DECIMAL_POINT,
|
||||
[kVK_ANSI_KeypadClear] = GB_WORKBOY_M,
|
||||
[kVK_ANSI_KeypadMultiply] = GB_WORKBOY_H,
|
||||
[kVK_ANSI_KeypadDivide] = GB_WORKBOY_J,
|
||||
};
|
||||
|
||||
@implementation GBView
|
||||
{
|
||||
uint32_t *image_buffers[3];
|
||||
unsigned char current_buffer;
|
||||
BOOL mouse_hidden;
|
||||
NSTrackingArea *tracking_area;
|
||||
BOOL _mouseHidingEnabled;
|
||||
bool axisActive[2];
|
||||
bool underclockKeyDown;
|
||||
double clockMultiplier;
|
||||
double analogClockMultiplier;
|
||||
bool analogClockMultiplierValid;
|
||||
NSEventModifierFlags previousModifiers;
|
||||
JOYController *lastController;
|
||||
GB_frame_blending_mode_t _frameBlendingMode;
|
||||
}
|
||||
|
||||
+ (instancetype)alloc
|
||||
{
|
||||
return [self allocWithZone:NULL];
|
||||
}
|
||||
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone
|
||||
{
|
||||
if (self == [GBView class]) {
|
||||
if ([GBViewMetal isSupported]) {
|
||||
return [GBViewMetal allocWithZone: zone];
|
||||
}
|
||||
return [GBViewGL allocWithZone: zone];
|
||||
}
|
||||
return [super allocWithZone:zone];
|
||||
}
|
||||
|
||||
- (void) createInternalView
|
||||
{
|
||||
assert(false && "createInternalView must not be inherited");
|
||||
}
|
||||
|
||||
- (void) _init
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
|
||||
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:tracking_area];
|
||||
clockMultiplier = 1.0;
|
||||
[self createInternalView];
|
||||
[self addSubview:self.internalView];
|
||||
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[JOYController registerListener:self];
|
||||
}
|
||||
|
||||
- (void)screenSizeChanged
|
||||
{
|
||||
if (image_buffers[0]) free(image_buffers[0]);
|
||||
if (image_buffers[1]) free(image_buffers[1]);
|
||||
if (image_buffers[2]) free(image_buffers[2]);
|
||||
|
||||
size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb);
|
||||
|
||||
image_buffers[0] = 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];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) ratioKeepingChanged
|
||||
{
|
||||
[self setFrame:self.superview.frame];
|
||||
}
|
||||
|
||||
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
|
||||
{
|
||||
_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 _frameBlendingMode? 3 : 2;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
free(image_buffers[0]);
|
||||
free(image_buffers[1]);
|
||||
free(image_buffers[2]);
|
||||
if (mouse_hidden) {
|
||||
mouse_hidden = false;
|
||||
[NSCursor unhide];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[self setRumble:0];
|
||||
[JOYController unregisterListener:self];
|
||||
}
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if (!(self = [super initWithCoder:coder])) {
|
||||
return self;
|
||||
}
|
||||
[self _init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
{
|
||||
if (!(self = [super initWithFrame:frameRect])) {
|
||||
return self;
|
||||
}
|
||||
[self _init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFrame:(NSRect)frame
|
||||
{
|
||||
frame = self.superview.frame;
|
||||
if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) {
|
||||
double ratio = frame.size.width / frame.size.height;
|
||||
double width = GB_get_screen_width(_gb);
|
||||
double height = GB_get_screen_height(_gb);
|
||||
if (ratio >= width / height) {
|
||||
double new_width = round(frame.size.height / height * width);
|
||||
frame.origin.x = floor((frame.size.width - new_width) / 2);
|
||||
frame.size.width = new_width;
|
||||
frame.origin.y = 0;
|
||||
}
|
||||
else {
|
||||
double new_height = round(frame.size.width / width * height);
|
||||
frame.origin.y = floor((frame.size.height - new_height) / 2);
|
||||
frame.size.height = new_height;
|
||||
frame.origin.x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[super setFrame:frame];
|
||||
}
|
||||
|
||||
- (void) flip
|
||||
{
|
||||
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
|
||||
GB_set_clock_multiplier(_gb, analogClockMultiplier);
|
||||
if (analogClockMultiplier == 1.0) {
|
||||
analogClockMultiplierValid = false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
- (uint32_t *) pixels
|
||||
{
|
||||
return image_buffers[(current_buffer + 1) % self.numberOfBuffers];
|
||||
}
|
||||
|
||||
-(void)keyDown:(NSEvent *)theEvent
|
||||
{
|
||||
if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return;
|
||||
unsigned short keyCode = theEvent.keyCode;
|
||||
if (GB_workboy_is_enabled(_gb)) {
|
||||
if (theEvent.keyCode < sizeof(workboy_vk_to_key) && workboy_vk_to_key[theEvent.keyCode]) {
|
||||
GB_workboy_set_key(_gb, workboy_vk_to_key[theEvent.keyCode]);
|
||||
return;
|
||||
}
|
||||
unichar c = [theEvent type] != NSEventTypeFlagsChanged? [theEvent.charactersIgnoringModifiers.lowercaseString characterAtIndex:0] : 0;
|
||||
if (c < sizeof(workboy_ascii_to_key) && workboy_ascii_to_key[c]) {
|
||||
GB_workboy_set_key(_gb, workboy_ascii_to_key[c]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
for (GBButton button = 0; button < GBButtonCount; button++) {
|
||||
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
|
||||
if (!key) continue;
|
||||
|
||||
if (key.unsignedShortValue == keyCode) {
|
||||
handled = true;
|
||||
switch (button) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, true, self.isRewinding);
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRewind:
|
||||
self.isRewinding = true;
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
break;
|
||||
|
||||
case GBUnderclock:
|
||||
underclockKeyDown = true;
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled && [theEvent type] != NSEventTypeFlagsChanged) {
|
||||
[super keyDown:theEvent];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)keyUp:(NSEvent *)theEvent
|
||||
{
|
||||
unsigned short keyCode = theEvent.keyCode;
|
||||
if (GB_workboy_is_enabled(_gb)) {
|
||||
if (keyCode == kVK_Shift || keyCode == kVK_RightShift) {
|
||||
GB_workboy_set_key(_gb, GB_WORKBOY_SHIFT_UP);
|
||||
}
|
||||
else {
|
||||
GB_workboy_set_key(_gb, GB_WORKBOY_NONE);
|
||||
}
|
||||
|
||||
}
|
||||
bool handled = false;
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
for (GBButton button = 0; button < GBButtonCount; button++) {
|
||||
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
|
||||
if (!key) continue;
|
||||
|
||||
if (key.unsignedShortValue == keyCode) {
|
||||
handled = true;
|
||||
switch (button) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRewind:
|
||||
self.isRewinding = false;
|
||||
break;
|
||||
|
||||
case GBUnderclock:
|
||||
underclockKeyDown = false;
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!handled && [theEvent type] != NSEventTypeFlagsChanged) {
|
||||
[super keyUp:theEvent];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRumble:(double)amp
|
||||
{
|
||||
[lastController setRumbleAmplitude:amp];
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis
|
||||
{
|
||||
if (![self.window isMainWindow]) return;
|
||||
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
}
|
||||
|
||||
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:@"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:controller.uniqueID]) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)theEvent
|
||||
{
|
||||
if (!mouse_hidden) {
|
||||
mouse_hidden = true;
|
||||
if (_mouseHidingEnabled) {
|
||||
[NSCursor hide];
|
||||
}
|
||||
}
|
||||
[super mouseEntered:theEvent];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
{
|
||||
if (mouse_hidden) {
|
||||
mouse_hidden = false;
|
||||
if (_mouseHidingEnabled) {
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
[super mouseExited:theEvent];
|
||||
}
|
||||
|
||||
- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled
|
||||
{
|
||||
if (mouseHidingEnabled == _mouseHidingEnabled) return;
|
||||
|
||||
_mouseHidingEnabled = mouseHidingEnabled;
|
||||
|
||||
if (mouse_hidden && _mouseHidingEnabled) {
|
||||
[NSCursor hide];
|
||||
}
|
||||
|
||||
if (mouse_hidden && !_mouseHidingEnabled) {
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isMouseHidingEnabled
|
||||
{
|
||||
return _mouseHidingEnabled;
|
||||
}
|
||||
|
||||
- (void) flagsChanged:(NSEvent *)event
|
||||
{
|
||||
if (event.modifierFlags > previousModifiers) {
|
||||
[self keyDown:event];
|
||||
}
|
||||
else {
|
||||
[self keyUp:event];
|
||||
}
|
||||
|
||||
previousModifiers = event.modifierFlags;
|
||||
}
|
||||
|
||||
- (uint32_t *)currentBuffer
|
||||
{
|
||||
return image_buffers[current_buffer];
|
||||
}
|
||||
|
||||
- (uint32_t *)previousBuffer
|
||||
{
|
||||
return image_buffers[(current_buffer + 2) % self.numberOfBuffers];
|
||||
}
|
||||
|
||||
@end
|
5
bsnes/gb/Cocoa/GBViewGL.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import "GBView.h"
|
||||
|
||||
@interface GBViewGL : GBView
|
||||
|
||||
@end
|
35
bsnes/gb/Cocoa/GBViewGL.m
Normal file
@ -0,0 +1,35 @@
|
||||
#import "GBViewGL.h"
|
||||
#import "GBOpenGLView.h"
|
||||
|
||||
@implementation GBViewGL
|
||||
|
||||
- (void)createInternalView
|
||||
{
|
||||
NSOpenGLPixelFormatAttribute attrs[] =
|
||||
{
|
||||
NSOpenGLPFAOpenGLProfile,
|
||||
NSOpenGLProfileVersion3_2Core,
|
||||
0
|
||||
};
|
||||
|
||||
NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
|
||||
|
||||
assert(pf);
|
||||
|
||||
NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
|
||||
|
||||
self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf];
|
||||
((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = YES;
|
||||
((GBOpenGLView *)self.internalView).openGLContext = context;
|
||||
}
|
||||
|
||||
- (void)flip
|
||||
{
|
||||
[super flip];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.internalView setNeedsDisplay:YES];
|
||||
[self setNeedsDisplay:YES];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
7
bsnes/gb/Cocoa/GBViewMetal.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <MetalKit/MetalKit.h>
|
||||
#import "GBView.h"
|
||||
|
||||
@interface GBViewMetal : GBView<MTKViewDelegate>
|
||||
+ (bool) isSupported;
|
||||
@end
|
215
bsnes/gb/Cocoa/GBViewMetal.m
Normal file
@ -0,0 +1,215 @@
|
||||
#import "GBViewMetal.h"
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
|
||||
|
||||
static const vector_float2 rect[] =
|
||||
{
|
||||
{-1, -1},
|
||||
{ 1, -1},
|
||||
{-1, 1},
|
||||
{ 1, 1},
|
||||
};
|
||||
|
||||
@implementation GBViewMetal
|
||||
{
|
||||
id<MTLDevice> device;
|
||||
id<MTLTexture> texture, previous_texture;
|
||||
id<MTLBuffer> vertices;
|
||||
id<MTLRenderPipelineState> pipeline_state;
|
||||
id<MTLCommandQueue> command_queue;
|
||||
id<MTLBuffer> frame_blending_mode_buffer;
|
||||
id<MTLBuffer> output_resolution_buffer;
|
||||
vector_float2 output_resolution;
|
||||
}
|
||||
|
||||
+ (bool)isSupported
|
||||
{
|
||||
if (MTLCopyAllDevices) {
|
||||
return [MTLCopyAllDevices() count];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
- (void) allocateTextures
|
||||
{
|
||||
if (!device) return;
|
||||
|
||||
MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init];
|
||||
|
||||
texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
|
||||
|
||||
texture_descriptor.width = GB_get_screen_width(self.gb);
|
||||
texture_descriptor.height = GB_get_screen_height(self.gb);
|
||||
|
||||
texture = [device newTextureWithDescriptor:texture_descriptor];
|
||||
previous_texture = [device newTextureWithDescriptor:texture_descriptor];
|
||||
|
||||
}
|
||||
|
||||
- (void)createInternalView
|
||||
{
|
||||
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
|
||||
view.delegate = self;
|
||||
self.internalView = view;
|
||||
view.paused = YES;
|
||||
view.enableSetNeedsDisplay = YES;
|
||||
|
||||
vertices = [device newBufferWithBytes:rect
|
||||
length:sizeof(rect)
|
||||
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)
|
||||
options:MTLResourceStorageModeShared];
|
||||
|
||||
output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height};
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil];
|
||||
[self loadShader];
|
||||
}
|
||||
|
||||
- (void) loadShader
|
||||
{
|
||||
NSError *error = nil;
|
||||
NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader"
|
||||
ofType:@"metal"
|
||||
inDirectory:@"Shaders"]
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
|
||||
NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
|
||||
NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name
|
||||
ofType:@"fsh"
|
||||
inDirectory:@"Shaders"]
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
|
||||
shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}"
|
||||
withString:scaler_source];
|
||||
|
||||
MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
|
||||
options.fastMathEnabled = YES;
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:shader_source
|
||||
options:options
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error: %@", error);
|
||||
if (!library) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLFunction> vertex_function = [library newFunctionWithName:@"vertex_shader"];
|
||||
id<MTLFunction> fragment_function = [library newFunctionWithName:@"fragment_shader"];
|
||||
|
||||
// Set up a descriptor for creating a pipeline state object
|
||||
MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
pipeline_state_descriptor.vertexFunction = vertex_function;
|
||||
pipeline_state_descriptor.fragmentFunction = fragment_function;
|
||||
pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat;
|
||||
|
||||
error = nil;
|
||||
pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Failed to created pipeline state, error %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
command_queue = [device newCommandQueue];
|
||||
}
|
||||
|
||||
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
|
||||
{
|
||||
output_resolution = (vector_float2){size.width, size.height};
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[(MTKView *)self.internalView draw];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)drawInMTKView:(nonnull MTKView *)view
|
||||
{
|
||||
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
|
||||
if (!self.gb) return;
|
||||
if (texture.width != GB_get_screen_width(self.gb) ||
|
||||
texture.height != GB_get_screen_height(self.gb)) {
|
||||
[self allocateTextures];
|
||||
}
|
||||
|
||||
MTLRegion region = {
|
||||
{0, 0, 0}, // MTLOrigin
|
||||
{texture.width, texture.height, 1} // MTLSize
|
||||
};
|
||||
|
||||
[texture replaceRegion:region
|
||||
mipmapLevel:0
|
||||
withBytes:[self currentBuffer]
|
||||
bytesPerRow:texture.width * 4];
|
||||
if ([self frameBlendingMode]) {
|
||||
[previous_texture replaceRegion:region
|
||||
mipmapLevel:0
|
||||
withBytes:[self previousBuffer]
|
||||
bytesPerRow:texture.width * 4];
|
||||
}
|
||||
|
||||
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
|
||||
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
|
||||
|
||||
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 =
|
||||
[command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];
|
||||
|
||||
[render_encoder setViewport:(MTLViewport){0.0, 0.0,
|
||||
output_resolution.x,
|
||||
output_resolution.y,
|
||||
-1.0, 1.0}];
|
||||
|
||||
[render_encoder setRenderPipelineState:pipeline_state];
|
||||
|
||||
[render_encoder setVertexBuffer:vertices
|
||||
offset:0
|
||||
atIndex:0];
|
||||
|
||||
[render_encoder setFragmentBuffer:frame_blending_mode_buffer
|
||||
offset:0
|
||||
atIndex:0];
|
||||
|
||||
[render_encoder setFragmentBuffer:output_resolution_buffer
|
||||
offset:0
|
||||
atIndex:1];
|
||||
|
||||
[render_encoder setFragmentTexture:texture
|
||||
atIndex:0];
|
||||
|
||||
[render_encoder setFragmentTexture:previous_texture
|
||||
atIndex:1];
|
||||
|
||||
[render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
|
||||
vertexStart:0
|
||||
vertexCount:4];
|
||||
|
||||
[render_encoder endEncoding];
|
||||
|
||||
[command_buffer presentDrawable:view.currentDrawable];
|
||||
}
|
||||
|
||||
|
||||
[command_buffer commit];
|
||||
}
|
||||
|
||||
- (void)flip
|
||||
{
|
||||
[super flip];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[(MTKView *)self.internalView setNeedsDisplay:YES];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
8
bsnes/gb/Cocoa/GBWarningPopover.h
Normal file
@ -0,0 +1,8 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBWarningPopover : NSPopover
|
||||
|
||||
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view;
|
||||
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window;
|
||||
|
||||
@end
|
46
bsnes/gb/Cocoa/GBWarningPopover.m
Normal file
@ -0,0 +1,46 @@
|
||||
#import "GBWarningPopover.h"
|
||||
|
||||
static GBWarningPopover *lastPopover;
|
||||
|
||||
@implementation GBWarningPopover
|
||||
|
||||
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view
|
||||
{
|
||||
[lastPopover close];
|
||||
lastPopover = [[self alloc] init];
|
||||
|
||||
[lastPopover setBehavior:NSPopoverBehaviorApplicationDefined];
|
||||
[lastPopover setAnimates:YES];
|
||||
lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil];
|
||||
NSTextField *field = (NSTextField *)lastPopover.contentViewController.view;
|
||||
[field setStringValue:contents];
|
||||
NSSize textSize = [field.cell cellSizeForBounds:[field.cell drawingRectForBounds:NSMakeRect(0, 0, 240, CGFLOAT_MAX)]];
|
||||
textSize.width = ceil(textSize.width) + 16;
|
||||
textSize.height = ceil(textSize.height) + 12;
|
||||
[lastPopover setContentSize:textSize];
|
||||
|
||||
if (!view.window.isVisible) {
|
||||
[view.window setIsVisible:YES];
|
||||
}
|
||||
|
||||
[lastPopover showRelativeToRect:view.bounds
|
||||
ofView:view
|
||||
preferredEdge:NSMinYEdge];
|
||||
|
||||
NSRect frame = field.frame;
|
||||
frame.origin.x += 8;
|
||||
frame.origin.y -= 6;
|
||||
field.frame = frame;
|
||||
|
||||
|
||||
[lastPopover performSelector:@selector(close) withObject:nil afterDelay:3.0];
|
||||
|
||||
return lastPopover;
|
||||
}
|
||||
|
||||
+ (GBWarningPopover *)popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window
|
||||
{
|
||||
return [self popoverWithContents:contents onView:window.contentView.superview.subviews.lastObject];
|
||||
}
|
||||
|
||||
@end
|
165
bsnes/gb/Cocoa/Info.plist
Normal file
@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-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>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>gb</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>Cartridge</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Game Boy Game</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.github.liji32.sameboy.gb</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<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 Color Game</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.github.liji32.sameboy.gbc</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<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>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.liji32.sameboy</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SameBoy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>Version @VERSION</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.9</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2015-2020 Lior Halphon</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Game Boy Game</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>Cartridge</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.github.liji32.sameboy.gb</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>gb</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Game Boy Color Game</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>ColorCartridge</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.github.liji32.sameboy.gbc</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>gbc</string>
|
||||
</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>
|
||||
</plist>
|
BIN
bsnes/gb/Cocoa/Joypad.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
bsnes/gb/Cocoa/Joypad@2x.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
bsnes/gb/Cocoa/Joypad~dark.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
bsnes/gb/Cocoa/Joypad~dark@2x.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
26
bsnes/gb/Cocoa/KeyboardShortcutPrivateAPIs.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef KeyboardShortcutPrivateAPIs_h
|
||||
#define KeyboardShortcutPrivateAPIs_h
|
||||
|
||||
/* These are private APIs, but they are a very simple and comprehensive way
|
||||
to convert a key equivalent to its display name. */
|
||||
|
||||
@interface NSKeyboardShortcut : NSObject <NSCopying>
|
||||
|
||||
+ (id)shortcutWithPreferencesEncoding:(NSString *)encoding;
|
||||
+ (id)shortcutWithKeyEquivalent:(NSString *)key_equivalent modifierMask:(unsigned long long)mask;
|
||||
- (id)initWithKeyEquivalent:(NSString *)key_equivalent modifierMask:(unsigned long long)mask;
|
||||
|
||||
@property(readonly) unsigned long long modifierMask;
|
||||
@property(readonly) NSString *keyEquivalent;
|
||||
@property(readonly) NSString *preferencesEncoding;
|
||||
@property(readonly) NSString *localizedModifierMaskDisplayName;
|
||||
@property(readonly) NSString *localizedKeyEquivalentDisplayName;
|
||||
@property(readonly) NSString *localizedDisplayName;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSPrefPaneUtils : NSObject
|
||||
+ (id)stringForVirtualKey:(unsigned int)key modifiers:(unsigned int)flags;
|
||||
@end
|
||||
|
||||
#endif
|
80
bsnes/gb/Cocoa/License.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCYTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Helvetica, sans-serif;
|
||||
text-align: justify;
|
||||
font-size: 12px;
|
||||
}
|
||||
h1 {
|
||||
text-align:center;
|
||||
font-size: 10px;
|
||||
}
|
||||
h2 {
|
||||
text-align:center;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h3 {
|
||||
text-align:center;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>SameBoy</h1>
|
||||
<h2>MIT License</h2>
|
||||
<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
|
||||
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:</p>
|
||||
|
||||
<p>The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.</p>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<h1>Third-party Libraries</h1>
|
||||
<h2>HexFiend</h2>
|
||||
<h3>Copyright © 2005-2009, Peter Ammon
|
||||
All rights reserved.</h3>
|
||||
|
||||
<p>Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:</p>
|
||||
|
||||
<ul>
|
||||
<li>Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.</li>
|
||||
<li>Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.</li>
|
||||
</ul>
|
||||
|
||||
<p>THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE</p>
|
||||
</body>
|
||||
</html>
|
477
bsnes/gb/Cocoa/MainMenu.xib
Normal file
@ -0,0 +1,477 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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="14868"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="SameBoy" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="SameBoy" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About SameBoy" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW">
|
||||
<connections>
|
||||
<action selector="showPreferences:" target="Voe-Tx-rLC" id="RcX-51-nzq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide SameBoy" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit SameBoy" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="File" id="dMs-cI-mzQ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="File" id="bib-Uj-vzu">
|
||||
<items>
|
||||
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
|
||||
<connections>
|
||||
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Open Recent" id="tXI-mr-wws">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
|
||||
<items>
|
||||
<menuItem title="Clear Menu" id="vNY-rz-j42">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
|
||||
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
|
||||
<connections>
|
||||
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="cGb-fc-V1Y">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="rwF-GI-mkw">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="0Ff-de-rjb">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="XQH-Wy-wlr"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="Pef-QL-e9D">
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="5DQ-yl-4ds"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gYa-mS-zMS"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="c0j-SN-BK3">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="DCn-sI-ibs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="kRM-zo-IsI">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="lgN-ca-tGx"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="tPP-KM-W2x">
|
||||
<connections>
|
||||
<action selector="paste:" target="-1" id="zLc-RU-lUk"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="CvF-7s-jyR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="zQk-RN-64A"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="tha-Q5-MNs">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="IfU-4s-7bE"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="Aru-vr-frG"/>
|
||||
<menuItem title="Find" id="efg-jw-GVP">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="4R6-IU-Jq6">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="tos-1K-NFk">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="nTo-u6-2Ne"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="aey-0H-CqY">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="DJo-3G-DNV"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="Ex6-6J-WlY">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="6Zf-xR-ur5"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="qgQ-0P-lLO">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="l2m-8O-aDP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="Ujj-LE-V19">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="GhX-po-5RK"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Emulation" id="H8h-7b-M4v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Emulation" id="HyV-fh-RgO">
|
||||
<items>
|
||||
<menuItem title="Reset" keyEquivalent="r" id="p0i-Lt-sTg">
|
||||
<connections>
|
||||
<action selector="reset:" target="-1" id="DKW-Bd-h3v"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Pause" keyEquivalent="p" id="4K4-hw-R7Q">
|
||||
<connections>
|
||||
<action selector="togglePause:" target="-1" id="osW-wt-QAa"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="QIS-av-Byy"/>
|
||||
<menuItem title="Save State" id="Hdz-ut-okE">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Save State" id="Mxx-u1-M9D">
|
||||
<items>
|
||||
<menuItem title="Slot 1" tag="1" keyEquivalent="1" id="MKg-h9-jfo">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="UZR-bP-ogO"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 2" tag="2" keyEquivalent="2" id="vkn-Zh-eJS">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="Pmj-2O-z6U"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 3" tag="3" keyEquivalent="3" id="9mj-UU-bHY">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="BhO-2h-gyQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 4" tag="4" keyEquivalent="4" id="NYY-aj-BHb">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="xlY-3q-JsO"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 5" tag="5" keyEquivalent="5" id="UNN-Yv-1II">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="Kbx-JS-3v5"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 6" tag="6" keyEquivalent="6" id="Io5-NV-GN5">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="SAo-ej-RBG"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 7" tag="7" keyEquivalent="7" id="en2-Uu-Eps">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="MRR-4I-z8l"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 8" tag="8" keyEquivalent="8" id="BHl-sg-rA2">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="WSz-gz-mlZ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 9" tag="9" keyEquivalent="9" id="vSH-S9-ExZ">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="FOt-UK-jT9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 10" tag="10" keyEquivalent="0" id="mAB-fq-BJy">
|
||||
<connections>
|
||||
<action selector="saveState:" target="-1" id="KQi-wO-F6M"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Load State" id="EXD-SL-cz4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Load State" id="l9D-Ej-sh2">
|
||||
<items>
|
||||
<menuItem title="Slot 1" tag="1" keyEquivalent="1" id="aEJ-6V-7sk">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="rOy-Ve-UUM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 2" tag="2" keyEquivalent="2" id="EWM-vK-sZm">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="M7f-wx-xt2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 3" tag="3" keyEquivalent="3" id="YEd-gG-G6p">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="ALD-3X-pJ6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 4" tag="4" keyEquivalent="4" id="Xgn-pa-LcM">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="I0n-4q-CmW"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 5" tag="5" keyEquivalent="5" id="XIA-qE-emo">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="SAP-0t-CGM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 6" tag="6" keyEquivalent="6" id="0CQ-w6-dSd">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="CFz-7P-jTJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 7" tag="7" keyEquivalent="7" id="sdG-Dc-QNU">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="B49-vL-qN7"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 8" tag="8" keyEquivalent="8" id="pPH-D9-4MJ">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="TZl-ug-0ae"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 9" tag="9" keyEquivalent="9" id="1Uy-yl-ITg">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="Hk5-Pz-VC5"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Slot 10" tag="10" keyEquivalent="0" id="dpk-UF-vN2">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="loadState:" target="-1" id="GEt-4l-90e"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/>
|
||||
<menuItem title="Game Boy" tag="1" id="g7C-LA-VAr">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="reset:" target="-1" id="rxG-cz-s1S"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Super Game Boy" tag="4" id="vc7-yy-ARW">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="reset:" target="-1" id="E4M-QG-ua9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Game Boy Color" tag="2" id="hdG-Bl-8nJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="reset:" target="-1" id="xAz-cr-0u2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Game Boy Advance" tag="3" id="7jw-B1-tf5">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="reset:" target="-1" id="xQk-4e-kd7"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="DPb-Sh-5tg"/>
|
||||
<menuItem title="Mute Sound" keyEquivalent="m" id="1UK-8n-QPP">
|
||||
<connections>
|
||||
<action selector="mute:" target="-1" id="YE5-mi-Yzd"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</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="showCheats:" target="-1" id="tfr-qM-q8X"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Connectivity" id="IcW-ZC-4wb">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Connectivity" id="BDM-Cv-BOm">
|
||||
<items>
|
||||
<menuItem title="None" id="SiH-Q4-OBY">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Game Boy Printer" id="zHR-Ha-pOR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Workboy" id="lo9-CX-BJj">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Develop" id="IwX-DJ-dBk">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Develop" id="UVb-cc-at0">
|
||||
<items>
|
||||
<menuItem title="Developer Mode" id="Qx6-Tt-zZR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleDeveloperMode:" target="Voe-Tx-rLC" id="PIc-o3-bzb"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="66c-T0-8pW"/>
|
||||
<menuItem title="Show Console" id="Wse-UY-Y9l">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showConsoleWindow:" target="-1" id="mFf-4i-jLG"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Clear Console" keyEquivalent="k" id="MyO-VS-MKZ">
|
||||
<connections>
|
||||
<action selector="clearConsole:" target="-1" id="1UW-8J-Uwl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="3If-Yf-U7B"/>
|
||||
<menuItem title="Break Debugger" keyEquivalent="c" id="uBD-GY-Doi">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES"/>
|
||||
<connections>
|
||||
<action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="M6n-8G-LZS"/>
|
||||
<menuItem title="Show Memory" id="UIa-n7-LSa">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showMemory:" target="-1" id="Ngn-2u-n12"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show VRAM Viewer" id="EYc-et-cG0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showVRAMViewer:" target="-1" id="nhw-4h-Sl4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="SameBoy Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
<point key="canvasLocation" x="140" y="260"/>
|
||||
</menu>
|
||||
</objects>
|
||||
</document>
|
42
bsnes/gb/Cocoa/NSImageNamedDarkSupport.m
Normal file
@ -0,0 +1,42 @@
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface NSImageRep(PrivateAPI)
|
||||
@property(setter=_setAppearanceName:) NSString *_appearanceName;
|
||||
@end
|
||||
|
||||
static NSImage * (*imageNamed)(Class self, SEL _cmd, NSString *name);
|
||||
|
||||
@implementation NSImage(DarkHooks)
|
||||
|
||||
+ (NSImage *)imageNamedWithDark:(NSImageName)name
|
||||
{
|
||||
NSImage *light = imageNamed(self, _cmd, name);
|
||||
if (@available(macOS 10.14, *)) {
|
||||
NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]);
|
||||
if (!dark) {
|
||||
return light;
|
||||
}
|
||||
NSImage *ret = [[NSImage alloc] initWithSize:light.size];
|
||||
for (NSImageRep *rep in light.representations) {
|
||||
[rep _setAppearanceName:NSAppearanceNameAqua];
|
||||
[ret addRepresentation:rep];
|
||||
}
|
||||
for (NSImageRep *rep in dark.representations) {
|
||||
[rep _setAppearanceName:NSAppearanceNameDarkAqua];
|
||||
[ret addRepresentation:rep];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return light;
|
||||
}
|
||||
|
||||
+(void)load
|
||||
{
|
||||
if (@available(macOS 10.14, *)) {
|
||||
imageNamed = (void *)[self methodForSelector:@selector(imageNamed:)];
|
||||
method_setImplementation(class_getClassMethod(self, @selector(imageNamed:)),
|
||||
[self methodForSelector:@selector(imageNamedWithDark:)]);
|
||||
}
|
||||
}
|
||||
@end
|
7
bsnes/gb/Cocoa/NSObject+MavericksCompat.m
Normal file
@ -0,0 +1,7 @@
|
||||
#import <AppKit/AppKit.h>
|
||||
@implementation NSObject (MavericksCompat)
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
return [self init];
|
||||
}
|
||||
@end
|
6
bsnes/gb/Cocoa/NSString+StringForKey.h
Normal file
@ -0,0 +1,6 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSString (StringForKey)
|
||||
+ (NSString *) displayStringForKeyString: (NSString *)key_string;
|
||||
+ (NSString *) displayStringForKeyCode:(unsigned short) keyCode;
|
||||
@end
|
46
bsnes/gb/Cocoa/NSString+StringForKey.m
Normal file
@ -0,0 +1,46 @@
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "KeyboardShortcutPrivateAPIs.h"
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
@implementation NSString (StringForKey)
|
||||
|
||||
+ (NSString *) displayStringForKeyString: (NSString *)key_string
|
||||
{
|
||||
return [[NSKeyboardShortcut shortcutWithKeyEquivalent:key_string modifierMask:0] localizedDisplayName];
|
||||
}
|
||||
|
||||
+ (NSString *) displayStringForKeyCode:(unsigned short) keyCode
|
||||
{
|
||||
/* These cases are not handled by stringForVirtualKey */
|
||||
switch (keyCode) {
|
||||
|
||||
case kVK_Home: return @"↖";
|
||||
case kVK_End: return @"↘";
|
||||
case kVK_PageUp: return @"⇞";
|
||||
case kVK_PageDown: return @"⇟";
|
||||
case kVK_Delete: return @"⌫";
|
||||
case kVK_ForwardDelete: return @"⌦";
|
||||
case kVK_ANSI_KeypadEnter: return @"⌤";
|
||||
case kVK_CapsLock: return @"⇪";
|
||||
case kVK_Shift: return @"Left ⇧";
|
||||
case kVK_Control: return @"Left ⌃";
|
||||
case kVK_Option: return @"Left ⌥";
|
||||
case kVK_Command: return @"Left ⌘";
|
||||
case kVK_RightShift: return @"Right ⇧";
|
||||
case kVK_RightControl: return @"Right ⌃";
|
||||
case kVK_RightOption: return @"Right ⌥";
|
||||
case kVK_RightCommand: return @"Right ⌘";
|
||||
case kVK_Function: return @"fn";
|
||||
|
||||
/* Label Keypad buttons accordingly */
|
||||
default:
|
||||
if ((keyCode < kVK_ANSI_Keypad0 || keyCode > kVK_ANSI_Keypad9)) {
|
||||
return [NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0];
|
||||
}
|
||||
|
||||
case kVK_ANSI_KeypadDecimal: case kVK_ANSI_KeypadMultiply: case kVK_ANSI_KeypadPlus: case kVK_ANSI_KeypadDivide: case kVK_ANSI_KeypadMinus: case kVK_ANSI_KeypadEquals:
|
||||
return [@"Keypad " stringByAppendingString:[NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
1
bsnes/gb/Cocoa/PkgInfo
Normal file
@ -0,0 +1 @@
|
||||
APPL????
|
27
bsnes/gb/Cocoa/PopoverView.xib
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
|
||||
<connections>
|
||||
<outlet property="view" destination="oUc-bq-d5t" id="FQR-Ty-0Ar"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="oUc-bq-d5t">
|
||||
<rect key="frame" x="0.0" y="0.0" width="66" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="left" title="Test" id="xyx-iy-kse">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<point key="canvasLocation" x="-93" y="211.5"/>
|
||||
</textField>
|
||||
</objects>
|
||||
</document>
|
664
bsnes/gb/Cocoa/Preferences.xib
Normal file
@ -0,0 +1,664 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AppDelegate">
|
||||
<connections>
|
||||
<outlet property="audioTab" destination="Zn1-Y5-RbR" id="PCn-a5-RzH"/>
|
||||
<outlet property="controlsTab" destination="8TU-6J-NCg" id="BFd-im-2Pw"/>
|
||||
<outlet property="emulationTab" destination="ymk-46-SX7" id="ofG-ca-a5C"/>
|
||||
<outlet property="graphicsTab" destination="sRK-wO-K6R" id="pfP-Di-i0Q"/>
|
||||
<outlet property="preferencesWindow" destination="QvC-M9-y7g" id="kBg-fq-rZh"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<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"/>
|
||||
<rect key="contentRect" x="196" y="240" width="292" height="224"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="224"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<toolbar key="toolbar" implicitIdentifier="689472FF-3BCD-4B1F-98F8-989CCB01AE27" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="pYZ-Pe-8hq">
|
||||
<allowedToolbarItems>
|
||||
<toolbarItem implicitItemIdentifier="4E86DC78-64E2-4ABB-ACB7-7CC8B34DC3F1" label="Emulation" paletteLabel="Emulation" image="CPU" autovalidates="NO" selectable="YES" id="zdp-Z7-yZM">
|
||||
<connections>
|
||||
<action selector="switchPreferencesTab:" target="-2" id="AK1-Qj-JOU"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="279C2F03-535F-4760-8E10-08B3CD1C717F" label="Video" paletteLabel="Video" tag="1" image="Display" autovalidates="NO" selectable="YES" id="fCd-4a-SKR">
|
||||
<connections>
|
||||
<action selector="switchPreferencesTab:" target="-2" id="wck-Sv-EsJ"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="FB9201F6-0B4D-46BA-8826-66E0C4C99A19" label="Audio" paletteLabel="Audio" tag="2" image="Speaker" autovalidates="NO" selectable="YES" id="EMp-g1-eKu">
|
||||
<connections>
|
||||
<action selector="switchPreferencesTab:" target="-2" id="UrT-PP-tQV"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="D997D835-98C7-4A25-8C40-5416D974F3B9" label="Controls" paletteLabel="Controls" tag="3" image="Joypad" autovalidates="NO" selectable="YES" id="uNZ-j1-Dfx">
|
||||
<connections>
|
||||
<action selector="switchPreferencesTab:" target="-2" id="Tio-D7-PaA"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="zdp-Z7-yZM"/>
|
||||
<toolbarItem reference="fCd-4a-SKR"/>
|
||||
<toolbarItem reference="EMp-g1-eKu"/>
|
||||
<toolbarItem reference="uNZ-j1-Dfx"/>
|
||||
</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="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="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">
|
||||
<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="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">
|
||||
<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">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Bilinear (Blurry)" id="iDe-si-atu"/>
|
||||
<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"/>
|
||||
<menuItem title="Anti-aliased Scale4x" id="zJR-ER-Ygo">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="HQ2x" id="gxB-Uj-wp2"/>
|
||||
<menuItem title="OmniScale (Any factor)" id="z6q-Jp-Z8R"/>
|
||||
<menuItem title="OmniScale Legacy" id="doe-kf-quG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Anti-aliased OmniScale Legacy" id="uZy-BK-VyB">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="graphicFilterChanged:" target="QvC-M9-y7g" id="n87-t4-fbV"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
|
||||
<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">
|
||||
<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="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"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="6go-Lb-a4m">
|
||||
<items>
|
||||
<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="Reduce contrast" id="wuO-Xv-0mQ"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<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">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeAspectRatio:" target="QvC-M9-y7g" id="mQG-Ib-1jN"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR">
|
||||
<rect key="frame" x="30" y="193" 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="lxQ-4n-kEv" id="lvb-QF-0Ht">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="lbS-Lw-kQX">
|
||||
<items>
|
||||
<menuItem title="Disabled" state="on" id="lxQ-4n-kEv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="10 Seconds" tag="10" id="bPU-vT-d5z"/>
|
||||
<menuItem title="30 Seconds" tag="30" id="aR8-IU-fFh"/>
|
||||
<menuItem title="1 Minute" tag="60" id="E0R-mf-Hdl"/>
|
||||
<menuItem title="2 Minutes" tag="120" id="zb2-uh-lvj"/>
|
||||
<menuItem title="5 Minutes" tag="300" id="6Jj-EI-f6k"/>
|
||||
<menuItem title="10 Minutes" tag="600" id="DOL-qL-Caz"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="rewindLengthChanged:" target="QvC-M9-y7g" id="5NQ-1T-RNc"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<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">
|
||||
<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>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M">
|
||||
<rect key="frame" x="18" y="283" width="256" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA">
|
||||
<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="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">
|
||||
<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">
|
||||
<connections>
|
||||
<action selector="useBuiltinBootROMs:" target="QvC-M9-y7g" id="Kmo-wz-ZtB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="Dzv-Gc-zoL"/>
|
||||
<menuItem isSeparatorItem="YES" id="BqG-uI-xqE"/>
|
||||
<menuItem title="Other..." id="ikb-cd-hZe">
|
||||
<connections>
|
||||
<action selector="selectOtherBootROMFolder:" target="QvC-M9-y7g" id="ofn-ll-0bo"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<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">
|
||||
<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="LFw-Uk-cPR">
|
||||
<rect key="frame" x="30" y="125" width="234" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="DMG-CPU B" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="aXT-sE-m5Z" id="FuX-Hc-uO7">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" autoenablesItems="NO" id="R0h-k6-Q5l">
|
||||
<items>
|
||||
<menuItem title="DMG-CPU 0" enabled="NO" id="pvp-Mo-9S8"/>
|
||||
<menuItem title="DMG-CPU A" tag="1" enabled="NO" id="QLn-5i-Vlg"/>
|
||||
<menuItem title="DMG-CPU B" state="on" tag="2" id="aXT-sE-m5Z"/>
|
||||
<menuItem title="DMG-CPU C" tag="3" enabled="NO" id="Ecl-YM-oAA"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="dmgModelChanged:" target="QvC-M9-y7g" id="FIe-Wz-aSc"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<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">
|
||||
<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="dlD-sk-SHO">
|
||||
<rect key="frame" x="30" y="17" width="234" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPU-CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" autoenablesItems="NO" id="bbF-hB-Hv7">
|
||||
<items>
|
||||
<menuItem title="CPU-CGB 0" tag="512" enabled="NO" id="2Uk-u3-6Gw"/>
|
||||
<menuItem title="CPU-CGB A" tag="513" enabled="NO" id="axv-yk-RWM"/>
|
||||
<menuItem title="CPU-CGB B" tag="514" enabled="NO" id="NtJ-oo-IM2"/>
|
||||
<menuItem title="CPU-CGB C (Experimental)" tag="515" id="9YL-u8-12z"/>
|
||||
<menuItem title="CPU-CGB D" tag="516" enabled="NO" id="c76-oF-fkU"/>
|
||||
<menuItem title="CPU-CGB E" state="on" tag="517" id="3lF-1Q-2SS"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="cgbModelChanged:" target="QvC-M9-y7g" id="SY1-oj-VWQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<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">
|
||||
<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>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1">
|
||||
<rect key="frame" x="12" y="180" width="268" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX">
|
||||
<rect key="frame" x="30" y="71" width="234" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Super Game Boy (NTSC)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="x5A-7f-ef9" id="2Mt-ci-bB0">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" autoenablesItems="NO" id="czw-Hi-jyM">
|
||||
<items>
|
||||
<menuItem title="Super Game Boy (NTSC)" state="on" tag="4" id="x5A-7f-ef9"/>
|
||||
<menuItem title="Super Game Boy (PAL)" tag="4100" id="Cix-n2-l4L"/>
|
||||
<menuItem title="Super Game Boy 2" tag="257" id="gZG-1g-KF0"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="sgbModelChanged:" target="QvC-M9-y7g" id="ybk-EV-WPS"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="-176" y="848"/>
|
||||
</customView>
|
||||
<customView id="Zn1-Y5-RbR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="86"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<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">
|
||||
<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">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Accurate (Emulate hardware)" id="82j-Vv-nE6"/>
|
||||
<menuItem title="Preserve waveform" id="iUF-c2-fgt"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="highpassFilterChanged:" target="QvC-M9-y7g" id="CYt-0v-sw0"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<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">
|
||||
<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>
|
||||
</subviews>
|
||||
<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="467"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
|
||||
<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"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<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="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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="238" height="209"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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 identifier="keyName" width="116" minWidth="40" maxWidth="1000" id="73A-gb-pzd">
|
||||
<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>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mqT-jD-eXS">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="116" minWidth="40" maxWidth="1000" id="5VG-zV-WM6">
|
||||
<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>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" identifier="keyValue" title="Text Cell" id="tn8-0i-1q1">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="QvC-M9-y7g" id="Msa-aU-MtO"/>
|
||||
<outlet property="delegate" destination="QvC-M9-y7g" id="CfR-lh-CPe"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<nil key="backgroundColor"/>
|
||||
</clipView>
|
||||
<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" 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="183" width="203" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<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="152" width="208" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<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">
|
||||
<items>
|
||||
<menuItem title="None" state="on" id="hy8-cr-RrE"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeDefaultJoypad:" target="QvC-M9-y7g" id="TP2-Ug-Jpy"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="VEc-Ed-Z6f">
|
||||
<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="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"/>
|
||||
<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="gWx-7h-0xq">
|
||||
<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"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="cud-XE-BOw">
|
||||
<items>
|
||||
<menuItem title="Player 1" state="on" id="TO3-R7-9HN"/>
|
||||
<menuItem title="Player 2" tag="1" id="Yt0-SK-pCn"/>
|
||||
<menuItem title="Player 3" tag="2" id="16s-31-Xbi"/>
|
||||
<menuItem title="Player 4" tag="3" id="zrh-HJ-yml"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="refreshJoypadMenu:" target="QvC-M9-y7g" id="5hY-tg-9VE"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<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"/>
|
||||
<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>
|
||||
<connections>
|
||||
<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="1161.5"/>
|
||||
</customView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="CPU" width="32" height="32"/>
|
||||
<image name="Display" width="32" height="32"/>
|
||||
<image name="Joypad" width="32" height="32"/>
|
||||
<image name="Speaker" width="32" height="32"/>
|
||||
</resources>
|
||||
</document>
|
BIN
bsnes/gb/Cocoa/Speaker.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
bsnes/gb/Cocoa/Speaker@2x.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
bsnes/gb/Cocoa/Speaker~dark.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
bsnes/gb/Cocoa/Speaker~dark@2x.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
6
bsnes/gb/Cocoa/main.m
Normal file
@ -0,0 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
return NSApplicationMain(argc, argv);
|
||||
}
|
1064
bsnes/gb/Core/apu.c
Normal file
169
bsnes/gb/Core/apu.h
Normal file
@ -0,0 +1,169 @@
|
||||
#ifndef apu_h
|
||||
#define apu_h
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "gb_struct_def.h"
|
||||
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
/* Speed = 1 / Length (in seconds) */
|
||||
#define DAC_DECAY_SPEED 20000
|
||||
#define DAC_ATTACK_SPEED 20000
|
||||
|
||||
|
||||
/* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */
|
||||
#ifdef WIIU
|
||||
/* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/
|
||||
#define MAX_CH_AMP (0xFF0 / 2)
|
||||
#else
|
||||
#define MAX_CH_AMP 0xFF0
|
||||
#endif
|
||||
#define CH_STEP (MAX_CH_AMP/0xF/8)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* APU ticks are 2MHz, triggered by an internal APU clock. */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int16_t left;
|
||||
int16_t right;
|
||||
} GB_sample_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double left;
|
||||
double right;
|
||||
} GB_double_sample_t;
|
||||
|
||||
enum GB_CHANNELS {
|
||||
GB_SQUARE_1,
|
||||
GB_SQUARE_2,
|
||||
GB_WAVE,
|
||||
GB_NOISE,
|
||||
GB_N_CHANNELS
|
||||
};
|
||||
|
||||
typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool global_enable;
|
||||
uint8_t apu_cycles;
|
||||
|
||||
uint8_t samples[GB_N_CHANNELS];
|
||||
bool is_active[GB_N_CHANNELS];
|
||||
|
||||
uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided
|
||||
// once more to generate 128Hz and 64Hz clocks
|
||||
|
||||
uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide
|
||||
// need to divide the signal.
|
||||
|
||||
uint8_t square_sweep_countdown; // In 128Hz
|
||||
uint8_t square_sweep_calculate_countdown; // In 2 MHz
|
||||
uint16_t new_sweep_sample_length;
|
||||
uint16_t shadow_sweep_sample_length;
|
||||
bool sweep_enabled;
|
||||
bool sweep_decreasing;
|
||||
|
||||
struct {
|
||||
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks
|
||||
uint8_t current_volume; // Reloaded from NRX2
|
||||
uint8_t volume_countdown; // Reloaded from NRX2
|
||||
uint8_t current_sample_index; /* For save state compatibility,
|
||||
highest bit is reused (See NR14/NR24's
|
||||
write code)*/
|
||||
|
||||
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
|
||||
uint16_t sample_length; // From NRX3, NRX4, in APU ticks
|
||||
bool length_enabled; // NRX4
|
||||
|
||||
} square_channels[2];
|
||||
|
||||
struct {
|
||||
bool enable; // NR30
|
||||
uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks
|
||||
uint8_t shift; // NR32
|
||||
uint16_t sample_length; // NR33, NR34, in APU ticks
|
||||
bool length_enabled; // NR34
|
||||
|
||||
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
|
||||
uint8_t current_sample_index;
|
||||
uint8_t current_sample; // Current sample before shifting.
|
||||
|
||||
int8_t wave_form[32];
|
||||
bool wave_form_just_read;
|
||||
} wave_channel;
|
||||
|
||||
struct {
|
||||
uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks
|
||||
uint8_t current_volume; // Reloaded from NR42
|
||||
uint8_t volume_countdown; // Reloaded from NR42
|
||||
uint16_t lfsr;
|
||||
bool narrow;
|
||||
|
||||
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length)
|
||||
uint16_t sample_length; // From NR43, in APU ticks
|
||||
bool length_enabled; // NR44
|
||||
|
||||
uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of
|
||||
// 1MHz. This variable keeps track of the alignment.
|
||||
|
||||
} noise_channel;
|
||||
|
||||
#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 {
|
||||
GB_HIGHPASS_OFF, // Do not apply any filter, keep DC offset
|
||||
GB_HIGHPASS_ACCURATE, // Apply a highpass filter similar to the one used on hardware
|
||||
GB_HIGHPASS_REMOVE_DC_OFFSET, // Remove DC Offset without affecting the waveform
|
||||
GB_HIGHPASS_MAX
|
||||
} GB_highpass_mode_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned sample_rate;
|
||||
|
||||
double sample_cycles; // In 8 MHz units
|
||||
double cycles_per_sample;
|
||||
|
||||
// Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
|
||||
unsigned cycles_since_render;
|
||||
unsigned last_update[GB_N_CHANNELS];
|
||||
GB_sample_t current_sample[GB_N_CHANNELS];
|
||||
GB_sample_t summed_samples[GB_N_CHANNELS];
|
||||
double dac_discharge[GB_N_CHANNELS];
|
||||
|
||||
GB_highpass_mode_t highpass_mode;
|
||||
double highpass_rate;
|
||||
GB_double_sample_t highpass_diff;
|
||||
|
||||
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
|
||||
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
|
||||
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
|
||||
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
|
||||
void GB_apu_div_event(GB_gameboy_t *gb);
|
||||
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 */
|
149
bsnes/gb/Core/camera.c
Normal file
@ -0,0 +1,149 @@
|
||||
#include "gb.h"
|
||||
|
||||
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)
|
||||
{
|
||||
signed value = (x + y * 128 + noise_seed);
|
||||
uint8_t *data = (uint8_t *) &value;
|
||||
unsigned hash = 0;
|
||||
|
||||
while ((signed *) data != &value + 1) {
|
||||
hash ^= (*data << 8);
|
||||
if (hash & 0x8000) {
|
||||
hash ^= 0x8a00;
|
||||
hash ^= *data;
|
||||
}
|
||||
data++;
|
||||
hash <<= 1;
|
||||
}
|
||||
return (hash >> 8);
|
||||
}
|
||||
|
||||
static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y)
|
||||
{
|
||||
if (x >= 128) {
|
||||
x = 0;
|
||||
}
|
||||
if (y >= 112) {
|
||||
y = 0;
|
||||
}
|
||||
|
||||
long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y));
|
||||
|
||||
static const double gain_values[] =
|
||||
{0.8809390, 0.9149149, 0.9457498, 0.9739758,
|
||||
1.0000000, 1.0241412, 1.0466537, 1.0677433,
|
||||
1.0875793, 1.1240310, 1.1568911, 1.1868043,
|
||||
1.2142561, 1.2396208, 1.2743837, 1.3157323,
|
||||
1.3525190, 1.3856512, 1.4157897, 1.4434309,
|
||||
1.4689574, 1.4926697, 1.5148087, 1.5355703,
|
||||
1.5551159, 1.5735801, 1.5910762, 1.6077008,
|
||||
1.6235366, 1.6386550, 1.6531183, 1.6669808};
|
||||
/* Multiply color by gain value */
|
||||
color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F];
|
||||
|
||||
|
||||
/* Color is multiplied by the exposure register to simulate exposure. */
|
||||
color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) {
|
||||
/* Forbid reading the image while the camera is busy. */
|
||||
return 0xFF;
|
||||
}
|
||||
uint8_t tile_x = addr / 0x10 % 0x10;
|
||||
uint8_t tile_y = addr / 0x10 / 0x10;
|
||||
|
||||
uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8;
|
||||
uint8_t bit = addr & 1;
|
||||
|
||||
uint8_t ret = 0;
|
||||
|
||||
for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) {
|
||||
|
||||
long color = get_processed_color(gb, x, y);
|
||||
|
||||
static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5};
|
||||
double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7];
|
||||
if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) {
|
||||
color += (color * 4) * edge_enhancement_ratio;
|
||||
color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio;
|
||||
color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio;
|
||||
color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio;
|
||||
color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio;
|
||||
}
|
||||
|
||||
|
||||
/* The camera's registers are used as a threshold pattern, which defines the dithering */
|
||||
uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START;
|
||||
|
||||
if (color < gb->camera_registers[pattern_base]) {
|
||||
color = 3;
|
||||
}
|
||||
else if (color < gb->camera_registers[pattern_base + 1]) {
|
||||
color = 2;
|
||||
}
|
||||
else if (color < gb->camera_registers[pattern_base + 2]) {
|
||||
color = 1;
|
||||
}
|
||||
else {
|
||||
color = 0;
|
||||
}
|
||||
|
||||
ret <<= 1;
|
||||
ret |= (color >> bit) & 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback)
|
||||
{
|
||||
gb->camera_get_pixel_callback = callback;
|
||||
}
|
||||
|
||||
void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback)
|
||||
{
|
||||
gb->camera_update_request_callback = callback;
|
||||
}
|
||||
|
||||
void GB_camera_updated(GB_gameboy_t *gb)
|
||||
{
|
||||
gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1;
|
||||
}
|
||||
|
||||
void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
{
|
||||
addr &= 0x7F;
|
||||
if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) {
|
||||
value &= 0x7;
|
||||
noise_seed = rand();
|
||||
if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) {
|
||||
/* If no callback is set, ignore the write as if the camera is instantly done */
|
||||
gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1;
|
||||
gb->camera_update_request_callback(gb);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (addr >= 0x36) {
|
||||
GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value);
|
||||
return;
|
||||
}
|
||||
gb->camera_registers[addr] = value;
|
||||
}
|
||||
}
|
||||
uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
if ((addr & 0x7F) == 0) {
|
||||
return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS];
|
||||
}
|
||||
return 0;
|
||||
}
|
29
bsnes/gb/Core/camera.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef camera_h
|
||||
#define camera_h
|
||||
#include <stdint.h>
|
||||
#include "gb_struct_def.h"
|
||||
|
||||
typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y);
|
||||
typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb);
|
||||
|
||||
enum {
|
||||
GB_CAMERA_SHOOT_AND_1D_FLAGS = 0,
|
||||
GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1,
|
||||
GB_CAMERA_EXPOSURE_HIGH = 2,
|
||||
GB_CAMERA_EXPOSURE_LOW = 3,
|
||||
GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4,
|
||||
GB_CAMERA_DITHERING_PATTERN_START = 6,
|
||||
GB_CAMERA_DITHERING_PATTERN_END = 0x35,
|
||||
};
|
||||
|
||||
uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr);
|
||||
|
||||
void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback);
|
||||
void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback);
|
||||
|
||||
void GB_camera_updated(GB_gameboy_t *gb);
|
||||
|
||||
void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
|
||||
uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr);
|
||||
|
||||
#endif
|
313
bsnes/gb/Core/cheats.c
Normal 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
bsnes/gb/Core/cheats.h
Normal 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
|