Add 'bsnes/gb/' from commit '919a88ec23f8011dd0389a4abceb62b3d0c83e00'

git-subtree-dir: bsnes/gb
git-subtree-mainline: 844e23d0f4
git-subtree-split: 919a88ec23
This commit is contained in:
Tim Allen 2020-10-12 16:43:40 +11:00
commit ec18efcb04
293 changed files with 55739 additions and 0 deletions

6
bsnes/gb/.gitattributes vendored Normal file
View 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
View File

@ -0,0 +1,25 @@
Blargg's Test ROMs by Shay Green <gblargg@gmail.com>
Acid2 tests by Matt Currie under MIT:
MIT License
Copyright (c) 2020 Matt Currie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

@ -0,0 +1,23 @@
case `echo $1 | cut -d '-' -f 1` in
ubuntu)
sudo apt-get -qq update
sudo apt-get install -yq bison libpng-dev pkg-config libsdl2-dev
(
cd `mktemp -d`
curl -L https://github.com/rednex/rgbds/archive/v0.4.0.zip > rgbds.zip
unzip rgbds.zip
cd rgbds-*
make -sj
sudo make install
cd ..
rm -rf *
)
;;
macos)
brew install rgbds sdl2
;;
*)
echo "Unsupported OS"
exit 1
;;
esac

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

Binary file not shown.

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

@ -0,0 +1,33 @@
set -e
./build/bin/tester/sameboy_tester --jobs 5 \
--length 40 .github/actions/cgb_sound.gb \
--length 10 .github/actions/cgb-acid2.gbc \
--length 10 .github/actions/dmg-acid2.gb \
--dmg --length 40 .github/actions/dmg_sound-2.gb \
--dmg --length 20 .github/actions/oam_bug-2.gb
mv .github/actions/dmg{,-mode}-acid2.bmp
./build/bin/tester/sameboy_tester \
--dmg --length 10 .github/actions/dmg-acid2.gb
set +e
FAILED_TESTS=`
shasum .github/actions/*.bmp | grep -q -E -v \(\
44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\
dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\
0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\
c50daed36c57a8170ff362042694786676350997\ \ .github/actions/dmg-mode-acid2.bmp\|\
c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\
f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\
\)`
if [ -n "$FAILED_TESTS" ] ; then
echo "Failed the following tests:"
echo $FAILED_TESTS | tr " " "\n" | grep -q -o -E "[^/]+\.bmp" | sed s/.bmp// | sort
exit 1
fi
echo Passed all tests

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

@ -0,0 +1,36 @@
name: "Bulidability and Sanity"
on: push
jobs:
sanity:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, ubuntu-16.04]
cc: [gcc, clang]
include:
- os: macos-latest
cc: clang
extra_target: cocoa
exclude:
- os: macos-latest
cc: gcc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Install deps
shell: bash
run: |
./.github/actions/install_deps.sh ${{ matrix.os }}
- name: Build
run: |
${{ matrix.cc }} -v; (make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} || (echo "==== Build Failed ==="; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }}))
- name: Sanity tests
shell: bash
run: |
./.github/actions/sanity_tests.sh
- name: Upload binaries
uses: actions/upload-artifact@v1
with:
name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }}
path: build/bin

1
bsnes/gb/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

View File

@ -0,0 +1,2 @@
AGB EQU 1
include "cgb_boot.asm"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
FAST EQU 1
include "cgb_boot.asm"

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

View File

@ -0,0 +1,2 @@
SGB2 EQU 1
include "sgb_boot.asm"

View 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
View File

@ -0,0 +1 @@
See https://sameboy.github.io/changelog/

79
bsnes/gb/CONTRIBUTING.md Normal file
View 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.

View 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

View 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

Binary file not shown.

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
bsnes/gb/Cocoa/CPU@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

BIN
bsnes/gb/Cocoa/Display.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

45
bsnes/gb/Cocoa/Document.h Normal file
View 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

File diff suppressed because it is too large Load Diff

1080
bsnes/gb/Cocoa/Document.xib Normal file

File diff suppressed because it is too large Load Diff

View 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

View 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

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBBorderView : NSView
@end

View 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

View 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

View 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"};

View File

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

View File

@ -0,0 +1,121 @@
#import "GBCheatTextFieldCell.h"
@interface GBCheatTextView : NSTextView
@property bool usesAddressFormat;
@end
@implementation GBCheatTextView
- (bool)_insertText:(NSString *)string replacementRange:(NSRange)range
{
if (range.location == NSNotFound) {
range = self.selectedRange;
}
NSString *new = [self.string stringByReplacingCharactersInRange:range withString:string];
if (!self.usesAddressFormat) {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,2}|[0-9]{1,3})$" options:0 error:NULL];
if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) {
[super insertText:string replacementRange:range];
return true;
}
if ([regex numberOfMatchesInString:[@"$" stringByAppendingString:new] options:0 range:NSMakeRange(0, new.length + 1)]) {
[super insertText:string replacementRange:range];
[super insertText:@"$" replacementRange:NSMakeRange(0, 0)];
return true;
}
if ([new isEqualToString:@"$"] || [string length] == 0) {
self.string = @"$00";
self.selectedRange = NSMakeRange(1, 2);
return true;
}
}
else {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,3}:)?\\$[0-9a-fA-F]{1,4}$" options:0 error:NULL];
if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) {
[super insertText:string replacementRange:range];
return true;
}
if ([string length] == 0) {
NSUInteger index = [new rangeOfString:@":"].location;
if (index != NSNotFound) {
if (range.location > index) {
self.string = [[new componentsSeparatedByString:@":"] firstObject];
self.selectedRange = NSMakeRange(self.string.length, 0);
return true;
}
self.string = [[new componentsSeparatedByString:@":"] lastObject];
self.selectedRange = NSMakeRange(0, 0);
return true;
}
else if ([[self.string substringWithRange:range] isEqualToString:@":"]) {
self.string = [[self.string componentsSeparatedByString:@":"] lastObject];
self.selectedRange = NSMakeRange(0, 0);
return true;
}
}
if ([new isEqualToString:@"$"] || [string length] == 0) {
self.string = @"$0000";
self.selectedRange = NSMakeRange(1, 4);
return true;
}
if (([string isEqualToString:@"$"] || [string isEqualToString:@":"]) && range.length == 0 && range.location == 0) {
if ([self _insertText:@"$00:" replacementRange:range]) {
self.selectedRange = NSMakeRange(1, 2);
return true;
}
}
if ([string isEqualToString:@":"] && range.length + range.location == self.string.length) {
if ([self _insertText:@":$0" replacementRange:range]) {
self.selectedRange = NSMakeRange(self.string.length - 2, 2);
return true;
}
}
if ([string isEqualToString:@"$"]) {
if ([self _insertText:@"$0" replacementRange:range]) {
self.selectedRange = NSMakeRange(range.location + 1, 1);
return true;
}
}
}
return false;
}
- (NSUndoManager *)undoManager
{
return nil;
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
if (![self _insertText:string replacementRange:replacementRange]) {
NSBeep();
}
}
/* Private API, don't tell the police! */
- (void)_userReplaceRange:(NSRange)range withString:(NSString *)string
{
[self insertText:string replacementRange:range];
}
@end
@implementation GBCheatTextFieldCell
{
bool _drawing, _editing;
GBCheatTextView *_fieldEditor;
}
- (NSTextView *)fieldEditorForView:(NSView *)controlView
{
if (_fieldEditor) {
_fieldEditor.usesAddressFormat = self.usesAddressFormat;
return _fieldEditor;
}
_fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame];
_fieldEditor.fieldEditor = YES;
_fieldEditor.usesAddressFormat = self.usesAddressFormat;
return _fieldEditor;
}
@end

View File

@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "Document.h"
@interface GBCheatWindowController : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate>
@property (weak) IBOutlet NSTableView *cheatsTable;
@property (weak) IBOutlet NSTextField *addressField;
@property (weak) IBOutlet NSTextField *valueField;
@property (weak) IBOutlet NSTextField *oldValueField;
@property (weak) IBOutlet NSButton *oldValueCheckbox;
@property (weak) IBOutlet NSTextField *descriptionField;
@property (weak) IBOutlet NSTextField *importCodeField;
@property (weak) IBOutlet NSTextField *importDescriptionField;
@property (weak) IBOutlet Document *document;
- (void)cheatsUpdated;
@end

View File

@ -0,0 +1,240 @@
#import "GBCheatWindowController.h"
#import "GBWarningPopover.h"
#import "GBCheatTextFieldCell.h"
@implementation GBCheatWindowController
+ (NSString *)addressStringFromCheat:(const GB_cheat_t *)cheat
{
if (cheat->bank != GB_CHEAT_ANY_BANK) {
return [NSString stringWithFormat:@"$%x:$%04x", cheat->bank, cheat->address];
}
return [NSString stringWithFormat:@"$%04x", cheat->address];
}
+ (NSString *)actionDescriptionForCheat:(const GB_cheat_t *)cheat
{
if (cheat->use_old_value) {
return [NSString stringWithFormat:@"[%@]($%02x) = $%02x", [self addressStringFromCheat:cheat], cheat->old_value, cheat->value];
}
return [NSString stringWithFormat:@"[%@] = $%02x", [self addressStringFromCheat:cheat], cheat->value];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return 0;
size_t cheatCount;
GB_get_cheats(gb, &cheatCount);
return cheatCount + 1;
}
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return nil;
size_t cheatCount;
GB_get_cheats(gb, &cheatCount);
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (row >= cheatCount && columnIndex == 0) {
return [[NSCell alloc] init];
}
return nil;
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
size_t cheatCount;
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return nil;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (row >= cheatCount) {
switch (columnIndex) {
case 0:
return @(YES);
case 1:
return @NO;
case 2:
return @"Add Cheat...";
case 3:
return @"";
}
}
switch (columnIndex) {
case 0:
return @(NO);
case 1:
return @(cheats[row]->enabled);
case 2:
return @(cheats[row]->description);
case 3:
return [GBCheatWindowController actionDescriptionForCheat:cheats[row]];
}
return nil;
}
- (IBAction)importCheat:(id)sender
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
[self.document performAtomicBlock:^{
if (GB_import_cheat(gb,
self.importCodeField.stringValue.UTF8String,
self.importDescriptionField.stringValue.UTF8String,
true)) {
self.importCodeField.stringValue = @"";
self.importDescriptionField.stringValue = @"";
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
else {
NSBeep();
[GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField];
}
}];
}
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
size_t cheatCount;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
[self.document performAtomicBlock:^{
if (columnIndex == 1) {
if (row >= cheatCount) {
GB_add_cheat(gb, "New Cheat", 0, 0, 0, 0, false, true);
}
else {
GB_update_cheat(gb, cheats[row], cheats[row]->description, cheats[row]->address, cheats[row]->bank, cheats[row]->value, cheats[row]->old_value, cheats[row]->use_old_value, !cheats[row]->enabled);
}
}
else if (row < cheatCount) {
GB_remove_cheat(gb, cheats[row]);
}
}];
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
size_t cheatCount;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
unsigned row = self.cheatsTable.selectedRow;
const GB_cheat_t *cheat = NULL;
if (row >= cheatCount) {
static const GB_cheat_t template = {
.address = 0,
.bank = 0,
.value = 0,
.old_value = 0,
.use_old_value = false,
.enabled = false,
.description = "New Cheat",
};
cheat = &template;
}
else {
cheat = cheats[row];
}
self.addressField.stringValue = [GBCheatWindowController addressStringFromCheat:cheat];
self.valueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->value];
self.oldValueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->old_value];
self.oldValueCheckbox.state = cheat->use_old_value;
self.descriptionField.stringValue = @(cheat->description);
}
- (void)awakeFromNib
{
[self tableViewSelectionDidChange:nil];
((GBCheatTextFieldCell *)self.addressField.cell).usesAddressFormat = true;
}
- (void)controlTextDidChange:(NSNotification *)obj
{
[self updateCheat:nil];
}
- (IBAction)updateCheat:(id)sender
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
uint16_t address = 0;
uint16_t bank = GB_CHEAT_ANY_BANK;
if ([self.addressField.stringValue rangeOfString:@":"].location != NSNotFound) {
sscanf(self.addressField.stringValue.UTF8String, "$%hx:$%hx", &bank, &address);
}
else {
sscanf(self.addressField.stringValue.UTF8String, "$%hx", &address);
}
uint8_t value = 0;
if ([self.valueField.stringValue characterAtIndex:0] == '$') {
sscanf(self.valueField.stringValue.UTF8String, "$%02hhx", &value);
}
else {
sscanf(self.valueField.stringValue.UTF8String, "%hhd", &value);
}
uint8_t oldValue = 0;
if ([self.oldValueField.stringValue characterAtIndex:0] == '$') {
sscanf(self.oldValueField.stringValue.UTF8String, "$%02hhx", &oldValue);
}
else {
sscanf(self.oldValueField.stringValue.UTF8String, "%hhd", &oldValue);
}
size_t cheatCount;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
unsigned row = self.cheatsTable.selectedRow;
[self.document performAtomicBlock:^{
if (row >= cheatCount) {
GB_add_cheat(gb,
self.descriptionField.stringValue.UTF8String,
address,
bank,
value,
oldValue,
self.oldValueCheckbox.state,
false);
}
else {
GB_update_cheat(gb,
cheats[row],
self.descriptionField.stringValue.UTF8String,
address,
bank,
value,
oldValue,
self.oldValueCheckbox.state,
cheats[row]->enabled);
}
}];
[self.cheatsTable reloadData];
}
- (void)cheatsUpdated
{
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
@end

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBColorCell : NSTextFieldCell
@end

View 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

View File

@ -0,0 +1,7 @@
#import "Document.h"
#import "HexFiend/HexFiend.h"
#import "HexFiend/HFByteSlice.h"
@interface GBCompleteByteSlice : HFByteSlice
- (instancetype) initWithByteArray:(HFByteArray *)array;
@end

View 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

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

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBImageCell : NSImageCell
@end

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
#import "GBGLShader.h"
@interface GBOpenGLView : NSOpenGLView
@property GBGLShader *shader;
@end

View 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

View File

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

View File

@ -0,0 +1,18 @@
#import <Cocoa/Cocoa.h>
@interface GBOptionalVisualEffectView : NSView
@end
@implementation GBOptionalVisualEffectView
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
Class NSVisualEffectView = NSClassFromString(@"NSVisualEffectView");
if (NSVisualEffectView) {
return (id)[NSVisualEffectView alloc];
}
return [super allocWithZone:zone];
}
@end

View File

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

View 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

View File

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

View File

@ -0,0 +1,33 @@
#import "GBSplitView.h"
@implementation GBSplitView
{
NSColor *_dividerColor;
}
- (void)setDividerColor:(NSColor *)color
{
_dividerColor = color;
[self setNeedsDisplay:YES];
}
- (NSColor *)dividerColor
{
if (_dividerColor) {
return _dividerColor;
}
return [super dividerColor];
}
/* Mavericks comaptibility */
- (NSArray<NSView *> *)arrangedSubviews
{
if (@available(macOS 10.11, *)) {
return [super arrangedSubviews];
}
else {
return [self subviews];
}
}
@end

View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
@interface GBTerminalTextFieldCell : NSTextFieldCell
@property GB_gameboy_t *gb;
@end

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

View File

@ -0,0 +1,5 @@
#import "GBView.h"
@interface GBViewGL : GBView
@end

35
bsnes/gb/Cocoa/GBViewGL.m Normal file
View 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

View File

@ -0,0 +1,7 @@
#import <Cocoa/Cocoa.h>
#import <MetalKit/MetalKit.h>
#import "GBView.h"
@interface GBViewMetal : GBView<MTKViewDelegate>
+ (bool) isSupported;
@end

View 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

View 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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View 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

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

View 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

View File

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

View File

@ -0,0 +1,6 @@
#import <Foundation/Foundation.h>
@interface NSString (StringForKey)
+ (NSString *) displayStringForKeyString: (NSString *)key_string;
+ (NSString *) displayStringForKeyCode:(unsigned short) keyCode;
@end

View 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
View File

@ -0,0 +1 @@
APPL????

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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

6
bsnes/gb/Cocoa/main.m Normal file
View 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

File diff suppressed because it is too large Load Diff

169
bsnes/gb/Core/apu.h Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,313 @@
#include "gb.h"
#include "cheats.h"
#include <stdio.h>
#include <assert.h>
#include <errno.h>
static inline uint8_t hash_addr(uint16_t addr)
{
return addr;
}
static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x4000) {
return gb->mbc_rom0_bank;
}
if (addr < 0x8000) {
return gb->mbc_rom_bank;
}
if (addr < 0xD000) {
return 0;
}
if (addr < 0xE000) {
return gb->cgb_ram_bank;
}
return 0;
}
void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
{
if (!gb->cheat_enabled) return;
if (!gb->boot_rom_finished) return;
const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)];
if (hash) {
for (unsigned i = 0; i < hash->size; i++) {
GB_cheat_t *cheat = hash->cheats[i];
if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) {
if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) {
*value = cheat->value;
break;
}
}
}
}
}
bool GB_cheats_enabled(GB_gameboy_t *gb)
{
return gb->cheat_enabled;
}
void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled)
{
gb->cheat_enabled = enabled;
}
void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
{
GB_cheat_t *cheat = malloc(sizeof(*cheat));
cheat->address = address;
cheat->bank = bank;
cheat->value = value;
cheat->old_value = old_value;
cheat->use_old_value = use_old_value;
cheat->enabled = enabled;
strncpy(cheat->description, description, sizeof(cheat->description));
cheat->description[sizeof(cheat->description) - 1] = 0;
gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(*cheat));
gb->cheats[gb->cheat_count - 1] = cheat;
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
if (!*hash) {
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
(*hash)->size = 1;
(*hash)->cheats[0] = cheat;
}
else {
(*hash)->size++;
*hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
(*hash)->cheats[(*hash)->size - 1] = cheat;
}
}
const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size)
{
*size = gb->cheat_count;
return (void *)gb->cheats;
}
void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
{
for (unsigned i = 0; i < gb->cheat_count; i++) {
if (gb->cheats[i] == cheat) {
gb->cheats[i] = gb->cheats[--gb->cheat_count];
if (gb->cheat_count == 0) {
free(gb->cheats);
gb->cheats = NULL;
}
else {
gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat));
}
break;
}
}
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
for (unsigned i = 0; i < (*hash)->size; i++) {
if ((*hash)->cheats[i] == cheat) {
(*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--];
if ((*hash)->size == 0) {
free(*hash);
*hash = NULL;
}
else {
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
}
break;
}
}
free((void *)cheat);
}
bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
{
uint8_t dummy;
/* GameShark */
{
uint8_t bank;
uint8_t value;
uint16_t address;
if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
if (bank >= 0x80) {
bank &= 0xF;
}
GB_add_cheat(gb, description, address, bank, value, 0, false, enabled);
return true;
}
}
/* GameGenie */
{
char stripped_cheat[10] = {0,};
for (unsigned i = 0; i < 9 && *cheat; i++) {
stripped_cheat[i] = *(cheat++);
while (*cheat == '-') {
cheat++;
}
}
// Delete the 7th character;
stripped_cheat[7] = stripped_cheat[8];
stripped_cheat[8] = 0;
uint8_t old_value;
uint8_t value;
uint16_t address;
if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) {
address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
address ^= 0xF000;
if (address > 0x7FFF) {
return false;
}
old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6);
old_value ^= 0xBA;
GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled);
return true;
}
if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) {
address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
address ^= 0xF000;
if (address > 0x7FFF) {
return false;
}
GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled);
return true;
}
}
return false;
}
void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
{
GB_cheat_t *cheat = NULL;
for (unsigned i = 0; i < gb->cheat_count; i++) {
if (gb->cheats[i] == _cheat) {
cheat = gb->cheats[i];
break;
}
}
assert(cheat);
if (cheat->address != address) {
/* Remove from old bucket */
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
for (unsigned i = 0; i < (*hash)->size; i++) {
if ((*hash)->cheats[i] == cheat) {
(*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--];
if ((*hash)->size == 0) {
free(*hash);
*hash = NULL;
}
else {
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
}
break;
}
}
cheat->address = address;
/* Add to new bucket */
hash = &gb->cheat_hash[hash_addr(address)];
if (!*hash) {
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
(*hash)->size = 1;
(*hash)->cheats[0] = cheat;
}
else {
(*hash)->size++;
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
(*hash)->cheats[(*hash)->size - 1] = cheat;
}
}
cheat->bank = bank;
cheat->value = value;
cheat->old_value = old_value;
cheat->use_old_value = use_old_value;
cheat->enabled = enabled;
if (description != cheat->description) {
strncpy(cheat->description, description, sizeof(cheat->description));
cheat->description[sizeof(cheat->description) - 1] = 0;
}
}
#define CHEAT_MAGIC 'SBCh'
void GB_load_cheats(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "rb");
if (!f) {
return;
}
uint32_t magic = 0;
uint32_t struct_size = 0;
fread(&magic, sizeof(magic), 1, f);
fread(&struct_size, sizeof(struct_size), 1, f);
if (magic != CHEAT_MAGIC && magic != __builtin_bswap32(CHEAT_MAGIC)) {
GB_log(gb, "The file is not a SameBoy cheat database");
return;
}
if (struct_size != sizeof(GB_cheat_t)) {
GB_log(gb, "This cheat database is not compatible with this version of SameBoy");
return;
}
// Remove all cheats first
while (gb->cheats) {
GB_remove_cheat(gb, gb->cheats[0]);
}
GB_cheat_t cheat;
while (fread(&cheat, sizeof(cheat), 1, f)) {
if (magic == __builtin_bswap32(CHEAT_MAGIC)) {
cheat.address = __builtin_bswap16(cheat.address);
cheat.bank = __builtin_bswap16(cheat.bank);
}
cheat.description[sizeof(cheat.description) - 1] = 0;
GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled);
}
return;
}
int GB_save_cheats(GB_gameboy_t *gb, const char *path)
{
if (!gb->cheat_count) return 0; // Nothing to save.
FILE *f = fopen(path, "wb");
if (!f) {
GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno));
return errno;
}
uint32_t magic = CHEAT_MAGIC;
uint32_t struct_size = sizeof(GB_cheat_t);
if (fwrite(&magic, sizeof(magic), 1, f) != 1) {
fclose(f);
return EIO;
}
if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) {
fclose(f);
return EIO;
}
for (size_t i = 0; i <gb->cheat_count; i++) {
if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) {
fclose(f);
return EIO;
}
}
errno = 0;
fclose(f);
return errno;
}

42
bsnes/gb/Core/cheats.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef cheats_h
#define cheats_h
#include "gb_struct_def.h"
#define GB_CHEAT_ANY_BANK 0xFFFF
typedef struct GB_cheat_s GB_cheat_t;
void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled);
void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled);
bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled);
const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size);
void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat);
bool GB_cheats_enabled(GB_gameboy_t *gb);
void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled);
void GB_load_cheats(GB_gameboy_t *gb, const char *path);
int GB_save_cheats(GB_gameboy_t *gb, const char *path);
#ifdef GB_INTERNAL
#ifdef GB_DISABLE_CHEATS
#define GB_apply_cheat(...)
#else
void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value);
#endif
#endif
typedef struct {
size_t size;
GB_cheat_t *cheats[];
} GB_cheat_hash_t;
struct GB_cheat_s {
uint16_t address;
uint16_t bank;
uint8_t value;
uint8_t old_value;
bool use_old_value;
bool enabled;
char description[128];
};
#endif

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