Merge tag 'v0.13.2' into GnomeThumbnailer

This commit is contained in:
Maximilian Mader 2020-06-17 20:45:39 +02:00
commit 3960df15b0
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
163 changed files with 12640 additions and 3438 deletions

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

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

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

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

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

Binary file not shown.

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

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

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

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

BIN
BootROMs/SameBoyLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
; Sameboy CGB bootstrap ROM
; SameBoy DMG bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
SECTION "BootCode", ROM0[$0]
Start:
@ -24,7 +24,7 @@ Start:
ldh [$24], a
; Init BG palette
ld a, $fc
ld a, $54
ldh [$47], a
; Load logo from ROM.
@ -69,13 +69,35 @@ Start:
jr .tilemapLoop
.tilemapDone
ld a, 30
ldh [$ff42], a
; Turn on LCD
ld a, $91
ldh [$40], a
; Wait ~0.75 seconds
ld b, 45
call WaitBFrames
ld d, (-119) & $FF
ld c, 15
.animate
call WaitFrame
ld a, d
sra a
sra a
ldh [$ff42], a
ld a, d
add c
ld d, a
ld a, c
cp 8
jr nz, .noPaletteChange
ld a, $A8
ldh [$47], a
.noPaletteChange
dec c
jr nz, .animate
ld a, $fc
ldh [$47], a
; Play first sound
ld a, $83
@ -86,8 +108,10 @@ Start:
ld a, $c1
call PlaySound
; Wait ~1.15 seconds
ld b, 70
; Wait ~1 second
ld b, 60
call WaitBFrames
; Set registers to match the original DMG boot

90
BootROMs/pb12.c Normal file
View File

@ -0,0 +1,90 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
void opts(uint8_t byte, uint8_t *options)
{
*(options++) = byte | ((byte << 1) & 0xff);
*(options++) = byte & (byte << 1);
*(options++) = byte | ((byte >> 1) & 0xff);
*(options++) = byte & (byte >> 1);
}
int main()
{
static uint8_t source[0x4000];
size_t size = read(STDIN_FILENO, &source, sizeof(source));
unsigned pos = 0;
assert(size <= 0x4000);
while (size && source[size - 1] == 0) {
size--;
}
uint8_t literals[8];
size_t literals_size = 0;
unsigned bits = 0;
unsigned control = 0;
unsigned prev[2] = {-1, -1}; // Unsigned to allow "not set" values
while (true) {
uint8_t byte = 0;
if (pos == size){
if (bits == 0) break;
}
else {
byte = source[pos++];
}
if (byte == prev[0] || byte == prev[1]) {
bits += 2;
control <<= 1;
control |= 1;
control <<= 1;
if (byte == prev[1]) {
control |= 1;
}
}
else {
bits += 2;
control <<= 2;
uint8_t options[4];
opts(prev[1], options);
bool found = false;
for (unsigned i = 0; i < 4; i++) {
if (options[i] == byte) {
// 01 = modify
control |= 1;
bits += 2;
control <<= 2;
control |= i;
found = true;
break;
}
}
if (!found) {
literals[literals_size++] = byte;
}
}
prev[0] = prev[1];
prev[1] = byte;
if (bits >= 8) {
uint8_t outctl = control >> (bits - 8);
assert(outctl != 1);
write(STDOUT_FILENO, &outctl, 1);
write(STDOUT_FILENO, literals, literals_size);
bits -= 8;
control &= (1 << bits) - 1;
literals_size = 0;
}
}
uint8_t end_byte = 1;
write(STDOUT_FILENO, &end_byte, 1);
return 0;
}

View File

@ -1,4 +1,4 @@
; Sameboy CGB bootstrap ROM
; SameBoy SGB bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
SECTION "BootCode", ROM0[$0]
Start:
@ -82,9 +82,9 @@ Start:
.sendCommand
xor a
ldh [c], a
ld [c], a
ld a, $30
ldh [c], a
ld [c], a
ldh a, [$80]
call SendByte
@ -112,9 +112,9 @@ Start:
; Done bit
ld a, $20
ldh [c], a
ld [c], a
ld a, $30
ldh [c], a
ld [c], a
; Update command
ldh a, [$80]
@ -128,10 +128,10 @@ Start:
; Write to sound registers for DMG compatibility
ld c, $13
ld a, $c1
ldh [c], a
ld [c], a
inc c
ld a, 7
ldh [c], a
ld [c], a
; Init BG palette
ld a, $fc
@ -168,9 +168,9 @@ SendByte:
jr c, .zeroBit
add a ; 10 -> 20
.zeroBit
ldh [c], a
ld [c], a
ld a, $30
ldh [c], a
ld [c], a
dec d
ret z
jr .loop

View File

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

View File

@ -1,7 +1,9 @@
#import "AppDelegate.h"
#include "GBButtons.h"
#include "GBView.h"
#include <Core/gb.h>
#import <Carbon/Carbon.h>
#import <JoyKit/JoyKit.h>
@implementation AppDelegate
{
@ -36,11 +38,20 @@
@"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE),
@"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET),
@"GBRewindLength": @(10),
@"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE),
@"GBDMGModel": @(GB_MODEL_DMG_B),
@"GBCGBModel": @(GB_MODEL_CGB_E),
@"GBSGBModel": @(GB_MODEL_SGB2),
@"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY),
}];
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
JOYAxes2DEmulateButtonsKey: @YES,
JOYHatsEmulateButtonsKey: @YES,
}];
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
}
- (IBAction)toggleDeveloperMode:(id)sender
@ -92,4 +103,8 @@
return YES;
}
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
}
@end

View File

@ -1,8 +1,11 @@
#import <Cocoa/Cocoa.h>
#include "GBView.h"
#include "GBImageView.h"
#include "GBSplitView.h"
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate>
@class GBCheatWindowController;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@property (strong) IBOutlet GBView *view;
@property (strong) IBOutlet NSTextView *consoleOutput;
@property (strong) IBOutlet NSPanel *consoleWindow;
@ -27,9 +30,12 @@
@property (strong) IBOutlet NSTableView *spritesTableView;
@property (strong) IBOutlet NSPanel *printerFeedWindow;
@property (strong) IBOutlet NSImageView *feedImageView;
@property (strong) IBOutlet NSButton *feedSaveButton;
@property (strong) IBOutlet NSTextView *debuggerSideViewInput;
@property (strong) IBOutlet NSTextView *debuggerSideView;
@property (strong) IBOutlet GBSplitView *debuggerSplitView;
@property (strong) IBOutlet NSBox *debuggerVerticalLine;
@property (strong) IBOutlet NSPanel *cheatsWindow;
@property (strong) IBOutlet GBCheatWindowController *cheatWindowController;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;

View File

@ -7,6 +7,8 @@
#include "HexFiend/HexFiend.h"
#include "GBMemoryByteArray.h"
#include "GBWarningPopover.h"
#include "GBCheatWindowController.h"
#include "GBTerminalTextFieldCell.h"
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
/* Todo: Split into category files! This is so messy!!! */
@ -55,6 +57,14 @@ enum model {
bool rewind;
bool modelsChanging;
NSCondition *audioLock;
GB_sample_t *audioBuffer;
size_t audioBufferSize;
size_t audioBufferPosition;
size_t audioBufferNeeded;
bool borderModeChanged;
}
@property GBAudioClient *audioClient;
@ -67,8 +77,17 @@ enum model {
- (void) printImage:(uint32_t *)image height:(unsigned) height
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
exposure:(unsigned) exposure;
- (void) gotNewSample:(GB_sample_t *)sample;
- (void) rumbleChanged:(double)amp;
- (void) loadBootROM:(GB_boot_rom_t)type;
@end
static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self loadBootROM: type];
}
static void vblank(GB_gameboy_t *gb)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
@ -118,6 +137,18 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure];
}
static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self gotNewSample:sample];
}
static void rumbleCallback(GB_gameboy_t *gb, double amp)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self rumbleChanged:amp];
}
@implementation Document
{
GB_gameboy_t gb;
@ -127,12 +158,14 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
NSMutableArray *debugger_input_queue;
}
- (instancetype)init {
- (instancetype)init
{
self = [super init];
if (self) {
has_debugger_input = [[NSConditionLock alloc] initWithCondition:0];
debugger_input_queue = [[NSMutableArray alloc] init];
console_output_lock = [[NSRecursiveLock alloc] init];
audioLock = [[NSCondition alloc] init];
}
return self;
}
@ -170,25 +203,82 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
}
- (void) updatePalette
{
switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) {
case 1:
GB_set_palette(&gb, &GB_PALETTE_DMG);
break;
case 2:
GB_set_palette(&gb, &GB_PALETTE_MGB);
break;
case 3:
GB_set_palette(&gb, &GB_PALETTE_GBL);
break;
default:
GB_set_palette(&gb, &GB_PALETTE_GREY);
break;
}
}
- (void) updateBorderMode
{
borderModeChanged = true;
}
- (void) updateRumbleMode
{
GB_set_rumble_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]);
}
- (void) initCommon
{
GB_init(&gb, [self internalModel]);
GB_set_user_data(&gb, (__bridge void *)(self));
GB_set_boot_rom_load_callback(&gb, (GB_boot_rom_load_callback_t)boot_rom_load);
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
[self updatePalette];
GB_set_rgb_encode_callback(&gb, rgbEncode);
GB_set_camera_get_pixel_callback(&gb, cameraGetPixel);
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
GB_apu_set_sample_callback(&gb, audioCallback);
GB_set_rumble_callback(&gb, rumbleCallback);
[self updateRumbleMode];
}
- (void) updateMinSize
{
self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb));
if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) ||
self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) {
[self.mainWindow zoom:nil];
}
}
- (void) vblank
{
[self.view flip];
if (borderModeChanged) {
dispatch_sync(dispatch_get_main_queue(), ^{
size_t previous_width = GB_get_screen_width(&gb);
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
if (GB_get_screen_width(&gb) != previous_width) {
[self.view screenSizeChanged];
[self updateMinSize];
}
});
borderModeChanged = false;
}
GB_set_pixels_output(&gb, self.view.pixels);
if (self.vramWindow.isVisible) {
dispatch_async(dispatch_get_main_queue(), ^{
@ -201,19 +291,93 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
}
- (void)gotNewSample:(GB_sample_t *)sample
{
[audioLock lock];
if (self.audioClient.isPlaying) {
if (audioBufferPosition == audioBufferSize) {
if (audioBufferSize >= 0x4000) {
audioBufferPosition = 0;
[audioLock unlock];
return;
}
if (audioBufferSize == 0) {
audioBufferSize = 512;
}
else {
audioBufferSize += audioBufferSize >> 2;
}
audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize);
}
audioBuffer[audioBufferPosition++] = *sample;
}
if (audioBufferPosition == audioBufferNeeded) {
[audioLock signal];
audioBufferNeeded = 0;
}
[audioLock unlock];
}
- (void)rumbleChanged:(double)amp
{
[_view setRumble:amp];
}
- (void) run
{
running = true;
GB_set_pixels_output(&gb, self.view.pixels);
GB_set_sample_rate(&gb, 96000);
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
GB_apu_copy_buffer(&gb, buffer, nFrames);
[audioLock lock];
if (audioBufferPosition < nFrames) {
audioBufferNeeded = nFrames;
[audioLock wait];
}
if (stopping) {
memset(buffer, 0, nFrames * sizeof(*buffer));
[audioLock unlock];
return;
}
if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) {
memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer));
memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer));
audioBufferPosition = audioBufferPosition - nFrames;
}
else {
memcpy(buffer, audioBuffer + (audioBufferPosition - nFrames), nFrames * sizeof(*buffer));
audioBufferPosition = 0;
}
[audioLock unlock];
} andSampleRate:96000];
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
[self.audioClient start];
}
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
/* Clear pending alarms, don't play alarms while playing */
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
for (NSUserNotification *notification in [center scheduledNotifications]) {
if ([notification.identifier isEqualToString:self.fileName]) {
[center removeScheduledNotification:notification];
break;
}
}
for (NSUserNotification *notification in [center deliveredNotifications]) {
if ([notification.identifier isEqualToString:self.fileName]) {
[center removeDeliveredNotification:notification];
break;
}
}
}
while (running) {
if (rewind) {
rewind = false;
@ -227,10 +391,34 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
}
[hex_timer invalidate];
[audioLock lock];
memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer));
audioBufferPosition = audioBufferNeeded;
[audioLock signal];
[audioLock unlock];
[self.audioClient stop];
self.audioClient = nil;
self.view.mouseHidingEnabled = NO;
GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]);
unsigned time_to_alarm = GB_time_to_alarm(&gb);
if (time_to_alarm) {
NSUserNotification *notification = [[NSUserNotification alloc] init];
NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil];
friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""];
friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName];
notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName];
notification.identifier = self.fileName;
notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm];
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"];
}
[_view setRumble:0];
stopping = false;
}
@ -248,21 +436,32 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
if (GB_debugger_is_stopped(&gb)) {
[self interruptDebugInputRead];
}
[audioLock lock];
stopping = true;
[audioLock signal];
[audioLock unlock];
running = false;
while (stopping);
while (stopping) {
[audioLock lock];
[audioLock signal];
[audioLock unlock];
}
GB_debugger_set_disabled(&gb, false);
}
- (void) loadBootROM
- (void) loadBootROM: (GB_boot_rom_t)type
{
static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"};
if ([self internalModel] == GB_MODEL_SGB2) {
GB_load_boot_rom(&gb, [[self bootROMPathForName:@"sgb2_boot"] UTF8String]);
}
else {
GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]);
}
static NSString *const names[] = {
[GB_BOOT_ROM_DMG0] = @"dmg0_boot",
[GB_BOOT_ROM_DMG] = @"dmg_boot",
[GB_BOOT_ROM_MGB] = @"mgb_boot",
[GB_BOOT_ROM_SGB] = @"sgb_boot",
[GB_BOOT_ROM_SGB2] = @"sgb2_boot",
[GB_BOOT_ROM_CGB0] = @"cgb0_boot",
[GB_BOOT_ROM_CGB] = @"cgb_boot",
[GB_BOOT_ROM_AGB] = @"agb_boot",
};
GB_load_boot_rom(&gb, [[self bootROMPathForName:names[type]] UTF8String]);
}
- (IBAction)reset:(id)sender
@ -274,8 +473,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
current_model = (enum model)[sender tag];
}
[self loadBootROM];
if (!modelsChanging && [sender tag] == MODEL_NONE) {
GB_reset(&gb);
}
@ -287,11 +484,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[self.view screenSizeChanged];
}
self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb));
if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) ||
self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) {
[self.mainWindow zoom:nil];
}
[self updateMinSize];
if ([sender tag] != 0) {
/* User explictly selected a model, save the preference */
@ -325,15 +518,21 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
- (void)dealloc
{
[cameraSession stopRunning];
self.view.gb = NULL;
GB_free(&gb);
if (cameraImage) {
CVBufferRelease(cameraImage);
}
if (audioBuffer) {
free(audioBuffer);
}
}
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
// Interface Builder bug?
[self.consoleWindow setContentSize:self.consoleWindow.minSize];
/* Close Open Panels, if any */
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
if ([window isKindOfClass:[NSOpenPanel class]]) {
@ -348,6 +547,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateSideView)
name:NSTextDidChangeNotification
@ -355,7 +555,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
self.consoleOutput.textContainerInset = NSMakeSize(4, 4);
[self.view becomeFirstResponder];
self.view.shouldBlendFrameWithPrevious = ![[NSUserDefaults standardUserDefaults] boolForKey:@"DisableFrameBlending"];
self.view.frameBlendingMode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
CGRect window_frame = self.mainWindow.frame;
window_frame.size.width = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowWidth"],
window_frame.size.width);
@ -365,14 +565,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
[self.feedSaveButton removeFromSuperview];
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [[self.fileURL path] lastPathComponent]];
/* contentView.superview.subviews.lastObject is the titlebar view */
NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject;
[titleView addSubview: self.feedSaveButton];
self.feedSaveButton.frame = (NSRect){{268, 2}, {48, 17}};
self.debuggerSplitView.dividerColor = [NSColor clearColor];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateHighpassFilter)
@ -384,6 +579,26 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
name:@"GBColorCorrectionChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateFrameBlendingMode)
name:@"GBFrameBlendingModeChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updatePalette)
name:@"GBColorPaletteChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateBorderMode)
name:@"GBBorderModeChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateRumbleMode)
name:@"GBRumbleModeChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateRewindLength)
name:@"GBRewindLengthChanged"
@ -426,7 +641,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
{
hex_controller = [[HFController alloc] init];
[hex_controller setBytesPerColumn:1];
[hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]];
[hex_controller setEditMode:HFOverwriteMode];
[hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]];
@ -469,11 +683,13 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
self.memoryBankItem.enabled = false;
}
+ (BOOL)autosavesInPlace {
+ (BOOL)autosavesInPlace
{
return YES;
}
- (NSString *)windowNibName {
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return @"Document";
@ -487,9 +703,18 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
- (void) loadROM
{
NSString *rom_warnings = [self captureOutputForBlock:^{
GB_load_rom(&gb, [self.fileName UTF8String]);
GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
GB_debugger_clear_symbols(&gb);
if ([[self.fileType pathExtension] isEqualToString:@"isx"]) {
GB_load_isx(&gb, [self.fileName UTF8String]);
GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"ram"] UTF8String]);
}
else {
GB_load_rom(&gb, [self.fileName UTF8String]);
}
GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
GB_load_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]);
[self.cheatWindowController cheatsUpdated];
GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]);
GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]);
}];
@ -530,15 +755,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[[NSUserDefaults standardUserDefaults] setBool:!self.audioClient.isPlaying forKey:@"Mute"];
}
- (IBAction)toggleBlend:(id)sender
{
self.view.shouldBlendFrameWithPrevious ^= YES;
[[NSUserDefaults standardUserDefaults] setBool:!self.view.shouldBlendFrameWithPrevious forKey:@"DisableFrameBlending"];
}
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
if([anItem action] == @selector(mute:)) {
if ([anItem action] == @selector(mute:)) {
[(NSMenuItem*)anItem setState:!self.audioClient.isPlaying];
}
else if ([anItem action] == @selector(togglePause:)) {
@ -548,9 +767,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
[(NSMenuItem*)anItem setState:anItem.tag == current_model];
}
else if ([anItem action] == @selector(toggleBlend:)) {
[(NSMenuItem*)anItem setState:self.view.shouldBlendFrameWithPrevious];
}
else if ([anItem action] == @selector(interrupt:)) {
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
return false;
@ -562,6 +778,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
else if ([anItem action] == @selector(connectPrinter:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter];
}
else if ([anItem action] == @selector(toggleCheats:)) {
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
}
return [super validateUserInterfaceItem:anItem];
}
@ -588,8 +807,8 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
NSRect rect = window.contentView.frame;
int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height;
int step = width / [[window screen] backingScaleFactor];
unsigned titlebarSize = window.contentView.superview.frame.size.height - rect.size.height;
unsigned step = width / [[window screen] backingScaleFactor];
rect.size.width = floor(rect.size.width / step) * step + step;
rect.size.height = rect.size.width * height / width + titlebarSize;
@ -670,9 +889,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
if (![console_output_timer isValid]) {
console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 repeats:NO block:^(NSTimer * _Nonnull timer) {
[self appendPendingOutput];
}];
console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode];
}
@ -687,7 +904,8 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[self.consoleWindow orderBack:nil];
}
- (IBAction)consoleInput:(NSTextField *)sender {
- (IBAction)consoleInput:(NSTextField *)sender
{
NSString *line = [sender stringValue];
if ([line isEqualToString:@""] && lastConsoleInput) {
line = lastConsoleInput;
@ -785,6 +1003,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[debugger_input_queue removeObjectAtIndex:0];
}
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
if ((id)input == [NSNull null]) {
return NULL;
}
return input? strdup([input UTF8String]): NULL;
}
@ -865,7 +1086,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
{
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef iref = CGImageCreate(width,
@ -936,6 +1157,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
(GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem,
(GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem);
self.tilemapImageView.scrollRect = NSMakeRect(GB_read_memory(&gb, 0xFF00 | GB_IO_SCX),
GB_read_memory(&gb, 0xFF00 | GB_IO_SCY),
160, 144);
self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0];
self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest;
}
@ -1100,6 +1324,23 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@try {
if (!cameraSession) {
if (@available(macOS 10.14, *)) {
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
case AVAuthorizationStatusAuthorized:
break;
case AVAuthorizationStatusNotDetermined: {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
[self cameraRequestUpdate];
}];
return;
}
case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
GB_camera_updated(&gb);
return;
}
}
NSError *error;
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error];
@ -1192,6 +1433,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
}
- (IBAction)toggleScrollingDisplay:(NSButton *)sender
{
self.tilemapImageView.displayScrollRect = sender.state;
}
- (IBAction)vramTabChanged:(NSSegmentedControl *)sender
{
[self.vramTabView selectTabViewItemAtIndex:[sender selectedSegment]];
@ -1300,7 +1546,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (tableView == self.paletteTableView) {
if (columnIndex == 0) {
return [NSString stringWithFormat:@"%s %d", row >=8 ? "Object" : "Background", (int)(row & 7)];
return [NSString stringWithFormat:@"%s %u", row >= 8 ? "Object" : "Background", (unsigned)(row & 7)];
}
uint8_t *palette_data = GB_get_direct_access(&gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, NULL, NULL);
@ -1318,9 +1564,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
height:oamHeight
scale:16.0/oamHeight];
case 1:
return @((int)oamInfo[row].x - 8);
return @((unsigned)oamInfo[row].x - 8);
case 2:
return @((int)oamInfo[row].y - 16);
return @((unsigned)oamInfo[row].y - 16);
case 3:
return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile];
case 4:
@ -1384,20 +1630,31 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
scale:2.0];
NSRect frame = self.printerFeedWindow.frame;
frame.size = self.feedImageView.image.size;
[self.printerFeedWindow setContentMaxSize:frame.size];
frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height;
[self.printerFeedWindow setMaxSize:frame.size];
[self.printerFeedWindow setFrame:frame display:NO animate: self.printerFeedWindow.isVisible];
[self.printerFeedWindow orderFront:NULL];
});
}
- (void)printDocument:(id)sender
{
if (self.feedImageView.image.size.height == 0) {
NSBeep(); return;
}
NSImageView *view = [[NSImageView alloc] initWithFrame:(NSRect){{0,0}, self.feedImageView.image.size}];
view.image = self.feedImageView.image;
[[NSPrintOperation printOperationWithView:view] runOperationModalForWindow:self.printerFeedWindow delegate:nil didRunSelector:NULL contextInfo:NULL];
}
- (IBAction)savePrinterFeed:(id)sender
{
bool shouldResume = running;
[self stop];
NSSavePanel * savePanel = [NSSavePanel savePanel];
[savePanel setAllowedFileTypes:@[@"png"]];
[savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result){
[savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
[savePanel orderOut:self];
CGImageRef cgRef = [self.feedImageView.image CGImageForProposedRect:NULL
@ -1445,6 +1702,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
}
- (void) updateFrameBlendingMode
{
self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
}
- (void) updateRewindLength
{
[self performAtomicBlock:^{
@ -1488,4 +1750,52 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
}
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
{
if ([[splitView arrangedSubviews] lastObject] == subview) {
return YES;
}
return NO;
}
- (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex
{
return 600;
}
- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex
{
return splitView.frame.size.width - 321;
}
- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view
{
if ([[splitView arrangedSubviews] lastObject] == view) {
return NO;
}
return YES;
}
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
{
GBSplitView *splitview = notification.object;
if ([[[splitview arrangedSubviews] firstObject] frame].size.width < 600) {
[splitview setPosition:600 ofDividerAtIndex:0];
}
/* NSSplitView renders its separator without the proper vibrancy, so we made it transparent and move an
NSBox-based separator that renders properly so it acts like the split view's separator. */
NSRect rect = self.debuggerVerticalLine.frame;
rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 1;
self.debuggerVerticalLine.frame = rect;
}
- (IBAction)showCheats:(id)sender
{
[self.cheatsWindow makeKeyAndOrderFront:nil];
}
- (IBAction)toggleCheats:(id)sender
{
GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb));
}
@end

View File

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

View File

@ -26,8 +26,7 @@ static OSStatus render(
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
andSampleRate:(UInt32) rate
{
if(!(self = [super init]))
{
if (!(self = [super init])) {
return nil;
}
@ -102,7 +101,8 @@ static OSStatus render(
_playing = NO;
}
-(void) dealloc {
-(void) dealloc
{
[self stop];
AudioUnitUninitialize(audioUnit);
AudioComponentInstanceDispose(audioUnit);

View File

@ -19,6 +19,11 @@ typedef enum : NSUInteger {
extern NSString const *GBButtonNames[GBButtonCount];
static inline NSString *n2s(uint64_t number)
{
return [NSString stringWithFormat:@"%llx", number];
}
static inline NSString *button_to_preference_name(GBButton button, unsigned player)
{
if (player) {

View File

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

View File

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

View File

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

View File

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

View File

@ -13,8 +13,14 @@ static inline double scale_channel(uint8_t x)
- (void)setObjectValue:(id)objectValue
{
_integerValue = [objectValue integerValue];
super.objectValue = [NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)];
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

View File

@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>
#import "GBView.h"
@interface GBGLShader : NSObject
- (instancetype)initWithName:(NSString *) shaderName;
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale;
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode: (GB_frame_blending_mode_t)blendingMode;
@end

View File

@ -21,7 +21,7 @@ void main(void) {\n\
GLuint resolution_uniform;
GLuint texture_uniform;
GLuint previous_texture_uniform;
GLuint mix_previous_uniform;
GLuint frame_blending_mode_uniform;
GLuint position_attribute;
GLuint texture;
@ -70,7 +70,7 @@ void main(void) {\n\
glBindTexture(GL_TEXTURE_2D, 0);
previous_texture_uniform = glGetUniformLocation(program, "previous_image");
mix_previous_uniform = glGetUniformLocation(program, "mix_previous");
frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode");
// Configure OpenGL
[self configureOpenGL];
@ -79,7 +79,7 @@ void main(void) {\n\
return self;
}
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode:(GB_frame_blending_mode_t)blendingMode
{
glUseProgram(program);
glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale);
@ -87,8 +87,8 @@ void main(void) {\n\
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
glUniform1i(texture_uniform, 0);
glUniform1i(mix_previous_uniform, previous != NULL);
if (previous) {
glUniform1i(frame_blending_mode_uniform, blendingMode);
if (blendingMode) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, previous_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous);
@ -169,7 +169,7 @@ void main(void) {\n\
+ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type
{
const GLchar* source = [contents UTF8String];
const GLchar *source = [contents UTF8String];
// Create the shader object
GLuint shader = glCreateShader(type);
// Load the shader source

View File

@ -3,7 +3,7 @@
@implementation GBImageCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
CGContextRef context = [[NSGraphicsContext currentContext] CGContext];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
[super drawWithFrame:cellFrame inView:controlView];
}

View File

@ -11,6 +11,8 @@
@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

View File

@ -16,7 +16,7 @@
}
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = [[NSGraphicsContext currentContext] CGContext];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
[super drawRect:dirtyRect];
CGFloat y_ratio = self.frame.size.height / self.image.size.height;
@ -25,8 +25,8 @@
[conf.color set];
for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) {
NSBezierPath *line = [NSBezierPath bezierPath];
[line moveToPoint:NSMakePoint(0, y + 0.5)];
[line lineToPoint:NSMakePoint(self.frame.size.width, y + 0.5)];
[line moveToPoint:NSMakePoint(0, y - 0.5)];
[line lineToPoint:NSMakePoint(self.frame.size.width, y - 0.5)];
[line setLineWidth:1.0];
[line stroke];
}
@ -42,6 +42,35 @@
[line stroke];
}
}
if (self.displayScrollRect) {
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite];
for (unsigned x = 0; x < 2; x++) {
for (unsigned y = 0; y < 2; y++) {
NSRect rect = self.scrollRect;
rect.origin.x *= x_ratio;
rect.origin.y *= y_ratio;
rect.size.width *= x_ratio;
rect.size.height *= y_ratio;
rect.origin.y = self.frame.size.height - rect.origin.y - rect.size.height;
rect.origin.x -= self.frame.size.width * x;
rect.origin.y += self.frame.size.height * y;
NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect];
[path appendBezierPath:subpath];
}
}
[path setWindingRule:NSEvenOddWindingRule];
[path setLineWidth:4.0];
[path setLineJoinStyle:NSRoundLineJoinStyle];
[[NSColor colorWithWhite:0.2 alpha:0.5] set];
[path fill];
[path addClip];
[[NSColor colorWithWhite:0.0 alpha:0.6] set];
[path stroke];
}
}
- (void)setHorizontalGrids:(NSArray *)horizontalGrids
@ -56,9 +85,15 @@
[self setNeedsDisplay];
}
- (void)setDisplayScrollRect:(bool)displayScrollRect
{
self->_displayScrollRect = displayScrollRect;
[self setNeedsDisplay];
}
- (void)updateTrackingAreas
{
if(trackingArea != nil) {
if (trackingArea != nil) {
[self removeTrackingArea:trackingArea];
}

View File

@ -1,8 +0,0 @@
#import <AppKit/AppKit.h>
@protocol GBJoystickListener <NSObject>
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state;
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value;
@end

View File

@ -4,7 +4,8 @@
@implementation GBOpenGLView
- (void)drawRect:(NSRect)dirtyRect {
- (void)drawRect:(NSRect)dirtyRect
{
if (!self.shader) {
self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]];
}
@ -13,11 +14,14 @@
double scale = self.window.backingScaleFactor;
glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
if (gbview.gb) {
[self.shader renderBitmap:gbview.currentBuffer
previous:gbview.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL
previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL
sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb))
inSize:self.bounds.size
scale:scale];
scale:scale
withBlendingMode:gbview.frameBlendingMode];
}
glFlush();
}

View File

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

View File

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

View File

@ -1,17 +1,22 @@
#import <Cocoa/Cocoa.h>
#import "GBJoystickListener.h"
#import <JoyKit/JoyKit.h>
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, GBJoystickListener>
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, JOYListener>
@property IBOutlet NSTableView *controlsTableView;
@property IBOutlet NSPopUpButton *graphicsFilterPopupButton;
@property (strong) IBOutlet NSButton *analogControlsCheckbox;
@property (strong) IBOutlet NSButton *aspectRatioCheckbox;
@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
@property (strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton;
@property (strong) IBOutlet NSPopUpButton *colorPalettePopupButton;
@property (strong) IBOutlet NSPopUpButton *displayBorderPopupButton;
@property (strong) IBOutlet NSPopUpButton *rewindPopupButton;
@property (strong) IBOutlet NSButton *configureJoypadButton;
@property (strong) IBOutlet NSButton *skipButton;
@property (strong) IBOutlet NSMenuItem *bootROMsFolderItem;
@property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
@property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
@property (weak) IBOutlet NSPopUpButton *dmgPopupButton;
@property (weak) IBOutlet NSPopUpButton *sgbPopupButton;

View File

@ -9,17 +9,22 @@
NSInteger button_being_modified;
signed joystick_configuration_state;
NSString *joystick_being_configured;
signed last_axis;
bool joypad_wait;
NSPopUpButton *_graphicsFilterPopupButton;
NSPopUpButton *_highpassFilterPopupButton;
NSPopUpButton *_colorCorrectionPopupButton;
NSPopUpButton *_frameBlendingModePopupButton;
NSPopUpButton *_colorPalettePopupButton;
NSPopUpButton *_displayBorderPopupButton;
NSPopUpButton *_rewindPopupButton;
NSButton *_aspectRatioCheckbox;
NSButton *_analogControlsCheckbox;
NSEventModifierFlags previousModifiers;
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
NSPopUpButton *_preferredJoypadButton;
NSPopUpButton *_rumbleModePopupButton;
}
+ (NSArray *)filterList
@ -31,6 +36,7 @@
@"NearestNeighbor",
@"Bilinear",
@"SmoothBilinear",
@"MonoLCD",
@"LCD",
@"CRT",
@"Scale2x",
@ -51,7 +57,7 @@
joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:YES];
[self.skipButton setEnabled:NO];
[self.configureJoypadButton setTitle:@"Configure Joypad"];
[self.configureJoypadButton setTitle:@"Configure Controller"];
[super close];
}
@ -84,6 +90,54 @@
return _colorCorrectionPopupButton;
}
- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton
{
_frameBlendingModePopupButton = frameBlendingModePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
[_frameBlendingModePopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)frameBlendingModePopupButton
{
return _frameBlendingModePopupButton;
}
- (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton
{
_colorPalettePopupButton = colorPalettePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
[_colorPalettePopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)colorPalettePopupButton
{
return _colorPalettePopupButton;
}
- (void)setDisplayBorderPopupButton:(NSPopUpButton *)displayBorderPopupButton
{
_displayBorderPopupButton = displayBorderPopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"];
[_displayBorderPopupButton selectItemWithTag:mode];
}
- (NSPopUpButton *)displayBorderPopupButton
{
return _displayBorderPopupButton;
}
- (void)setRumbleModePopupButton:(NSPopUpButton *)rumbleModePopupButton
{
_rumbleModePopupButton = rumbleModePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"];
[_rumbleModePopupButton selectItemWithTag:mode];
}
- (NSPopUpButton *)rumbleModePopupButton
{
return _rumbleModePopupButton;
}
- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton
{
_rewindPopupButton = rewindPopupButton;
@ -110,6 +164,20 @@
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"]) {
@ -122,6 +190,12 @@
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]];
}
@ -184,6 +258,12 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil];
}
- (IBAction)changeAnalogControls:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBAnalogControls"];
}
- (IBAction)changeAspectRatio:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState
@ -196,7 +276,35 @@
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBColorCorrection"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil];
}
- (IBAction)franeBlendingModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBFrameBlendingMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil];
}
- (IBAction)colorPaletteChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBColorPalette"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
}
- (IBAction)displayBorderChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBBorderMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil];
}
- (IBAction)rumbleModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBRumbleMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil];
}
- (IBAction)rewindLengthChanged:(id)sender
@ -212,7 +320,6 @@
[self.skipButton setEnabled:YES];
joystick_being_configured = nil;
[self advanceConfigurationStateMachine];
last_axis = -1;
}
- (IBAction) skipButton:(id)sender
@ -223,11 +330,11 @@
- (void) advanceConfigurationStateMachine
{
joystick_configuration_state++;
if (joystick_configuration_state < GBButtonCount) {
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
if (joystick_configuration_state == GBUnderclock) {
[self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :<
}
else if (joystick_configuration_state == GBButtonCount) {
[self.configureJoypadButton setTitle:@"Move the Analog Stick"];
else if (joystick_configuration_state < GBButtonCount) {
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
}
else {
joystick_configuration_state = -1;
@ -237,76 +344,95 @@
}
}
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{
if (!state) return;
/* Debounce */
if (joypad_wait) return;
joypad_wait = true;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
joypad_wait = false;
});
if (!button.isPressed) return;
if (joystick_configuration_state == -1) return;
if (joystick_configuration_state == GBButtonCount) return;
if (!joystick_being_configured) {
joystick_being_configured = joystick_name;
joystick_being_configured = controller.uniqueID;
}
else if (![joystick_being_configured isEqualToString:joystick_name]) {
else if (![joystick_being_configured isEqualToString:controller.uniqueID]) {
return;
}
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy];
if (!all_mappings) {
all_mappings = [[NSMutableDictionary alloc] init];
NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy];
if (!instance_mappings) {
instance_mappings = [[NSMutableDictionary alloc] init];
}
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
if (!name_mappings) {
name_mappings = [[NSMutableDictionary alloc] init];
}
if (!mapping) {
NSMutableDictionary *mapping = nil;
if (joystick_configuration_state != 0) {
mapping = [instance_mappings[controller.uniqueID] mutableCopy];
}
else {
mapping = [[NSMutableDictionary alloc] init];
}
mapping[GBButtonNames[joystick_configuration_state]] = @(button);
all_mappings[joystick_name] = mapping;
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
[self refreshJoypadMenu:nil];
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];
}
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
- (NSButton *)analogControlsCheckbox
{
if (abs(value) < 0x4000) return;
if (joystick_configuration_state != GBButtonCount) return;
if (!joystick_being_configured) {
joystick_being_configured = joystick_name;
}
else if (![joystick_being_configured isEqualToString:joystick_name]) {
return;
}
return _analogControlsCheckbox;
}
if (last_axis == -1) {
last_axis = axis;
return;
}
if (axis == last_axis) {
return;
}
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
if (!all_mappings) {
all_mappings = [[NSMutableDictionary alloc] init];
}
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
if (!mapping) {
mapping = [[NSMutableDictionary alloc] init];
}
mapping[@"XAxis"] = @(MIN(axis, last_axis));
mapping[@"YAxis"] = @(MAX(axis, last_axis));
all_mappings[joystick_name] = mapping;
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
[self advanceConfigurationStateMachine];
- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox
{
_analogControlsCheckbox = analogControlsCheckbox;
[_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]];
}
- (NSButton *)aspectRatioCheckbox
@ -325,10 +451,13 @@
[super awakeFromNib];
[self updateBootROMFolderButton];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil];
[JOYController registerListener:self];
joystick_configuration_state = -1;
}
- (void)dealloc
{
[JOYController unregisterListener:self];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView];
}
@ -447,21 +576,47 @@
return _preferredJoypadButton;
}
- (void)controllerConnected:(JOYController *)controller
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self refreshJoypadMenu:nil];
});
}
- (void)controllerDisconnected:(JOYController *)controller
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self refreshJoypadMenu:nil];
});
}
- (IBAction)refreshJoypadMenu:(id)sender
{
NSArray *joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] allKeys];
for (NSString *joypad in joypads) {
if ([self.preferredJoypadButton indexOfItemWithTitle:joypad] == -1) {
[self.preferredJoypadButton addItemWithTitle:joypad];
bool preferred_is_connected = false;
NSString *player_string = n2s(self.playerListButton.selectedTag);
NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string];
[self.preferredJoypadButton removeAllItems];
[self.preferredJoypadButton addItemWithTitle:@"None"];
for (JOYController *controller in [JOYController allControllers]) {
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]];
self.preferredJoypadButton.lastItem.identifier = controller.uniqueID;
if ([controller.uniqueID isEqualToString:selected_controller]) {
preferred_is_connected = true;
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
}
}
NSString *player_string = [NSString stringWithFormat: @"%ld", (long)self.playerListButton.selectedTag];
NSString *selected_joypad = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"][player_string];
if (selected_joypad && [self.preferredJoypadButton indexOfItemWithTitle:selected_joypad] != -1) {
[self.preferredJoypadButton selectItemWithTitle:selected_joypad];
if (!preferred_is_connected && selected_controller) {
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]];
self.preferredJoypadButton.lastItem.identifier = selected_controller;
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
}
else {
if (!selected_controller) {
[self.preferredJoypadButton selectItemWithTitle:@"None"];
}
[self.controlsTableView reloadData];
@ -469,18 +624,18 @@
- (IBAction)changeDefaultJoypad:(id)sender
{
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] mutableCopy];
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy];
if (!default_joypads) {
default_joypads = [[NSMutableDictionary alloc] init];
}
NSString *player_string = [NSString stringWithFormat: @"%ld", self.playerListButton.selectedTag];
NSString *player_string = n2s(self.playerListButton.selectedTag);
if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) {
[default_joypads removeObjectForKey:player_string];
}
else {
default_joypads[player_string] = [sender titleOfSelectedItem];
default_joypads[player_string] = [[sender selectedItem] identifier];
}
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"GBDefaultJoypads"];
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
}
@end

7
Cocoa/GBSplitView.h Normal file
View File

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

33
Cocoa/GBSplitView.m Normal file
View File

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

View File

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

View File

@ -2,6 +2,7 @@
#import "GBTerminalTextFieldCell.h"
@interface GBTerminalTextView : NSTextView
@property GB_gameboy_t *gb;
@end
@implementation GBTerminalTextFieldCell
@ -12,10 +13,12 @@
- (NSTextView *)fieldEditorForView:(NSView *)controlView
{
if (field_editor) {
field_editor.gb = self.gb;
return field_editor;
}
field_editor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:YES];
field_editor.gb = self.gb;
return field_editor;
}
@ -26,6 +29,8 @@
NSMutableOrderedSet *lines;
NSUInteger current_line;
bool reverse_search_mode;
NSRange auto_complete_range;
uintptr_t auto_complete_context;
}
- (instancetype)init
@ -170,10 +175,12 @@
-(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 {
- (BOOL)resignFirstResponder
{
reverse_search_mode = false;
return [super resignFirstResponder];
}
@ -187,6 +194,38 @@
[attributes setObject:color forKey:NSForegroundColorAttributeName];
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
}
}
/* Todo: lazy design, make it use a delegate instead of having a gb reference*/
- (void)insertTab:(id)sender
{
if (auto_complete_context == 0) {
NSRange selection = self.selectedRange;
if (selection.length) {
[self delete:nil];
}
auto_complete_range = NSMakeRange(selection.location, 0);
}
char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String);
uintptr_t context = auto_complete_context;
char *completion = GB_debugger_complete_substring(self.gb, substring, &context);
free(substring);
if (completion) {
NSString *ns_completion = @(completion);
free(completion);
if (!ns_completion) {
goto error;
}
self.selectedRange = auto_complete_range;
auto_complete_range.length = ns_completion.length;
[self replaceCharactersInRange:self.selectedRange withString:ns_completion];
auto_complete_context = context;
return;
}
error:
auto_complete_context = context;
NSBeep();
}
@end

View File

@ -1,12 +1,20 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
#import "GBJoystickListener.h"
#import <JoyKit/JoyKit.h>
@interface GBView<GBJoystickListener> : NSView
typedef enum {
GB_FRAME_BLENDING_MODE_DISABLED,
GB_FRAME_BLENDING_MODE_SIMPLE,
GB_FRAME_BLENDING_MODE_ACCURATE,
GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE,
GB_FRAME_BLENDING_MODE_ACCURATE_ODD,
} GB_frame_blending_mode_t;
@interface GBView : NSView<JOYListener>
- (void) flip;
- (uint32_t *) pixels;
@property GB_gameboy_t *gb;
@property (nonatomic) BOOL shouldBlendFrameWithPrevious;
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;
@property bool isRewinding;
@property NSView *internalView;
@ -14,4 +22,5 @@
- (uint32_t *)currentBuffer;
- (uint32_t *)previousBuffer;
- (void)screenSizeChanged;
- (void)setRumble: (double)amp;
@end

View File

@ -1,4 +1,4 @@
#import <Carbon/Carbon.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
#import "GBView.h"
#import "GBViewGL.h"
#import "GBViewMetal.h"
@ -18,7 +18,11 @@
bool axisActive[2];
bool underclockKeyDown;
double clockMultiplier;
double analogClockMultiplier;
bool analogClockMultiplierValid;
NSEventModifierFlags previousModifiers;
JOYController *lastController;
GB_frame_blending_mode_t _frameBlendingMode;
}
+ (instancetype)alloc
@ -44,7 +48,6 @@
- (void) _init
{
_shouldBlendFrameWithPrevious = 1;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
@ -55,6 +58,7 @@
[self createInternalView];
[self addSubview:self.internalView];
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[JOYController registerListener:self];
}
- (void)screenSizeChanged
@ -65,9 +69,9 @@
size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb);
image_buffers[0] = malloc(buffer_size);
image_buffers[1] = malloc(buffer_size);
image_buffers[2] = malloc(buffer_size);
image_buffers[0] = calloc(1, buffer_size);
image_buffers[1] = calloc(1, buffer_size);
image_buffers[2] = calloc(1, buffer_size);
dispatch_async(dispatch_get_main_queue(), ^{
[self setFrame:self.superview.frame];
@ -79,15 +83,26 @@
[self setFrame:self.superview.frame];
}
- (void) setShouldBlendFrameWithPrevious:(BOOL)shouldBlendFrameWithPrevious
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
{
_shouldBlendFrameWithPrevious = shouldBlendFrameWithPrevious;
_frameBlendingMode = frameBlendingMode;
[self setNeedsDisplay:YES];
}
- (GB_frame_blending_mode_t)frameBlendingMode
{
if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) {
if (!_gb || GB_is_sgb(_gb)) {
return GB_FRAME_BLENDING_MODE_SIMPLE;
}
return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN;
}
return _frameBlendingMode;
}
- (unsigned char) numberOfBuffers
{
return _shouldBlendFrameWithPrevious? 3 : 2;
return _frameBlendingMode? 3 : 2;
}
- (void)dealloc
@ -100,11 +115,12 @@
[NSCursor unhide];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self setRumble:0];
[JOYController unregisterListener:self];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
if (!(self = [super initWithCoder:coder]))
{
if (!(self = [super initWithCoder:coder])) {
return self;
}
[self _init];
@ -113,8 +129,7 @@
- (instancetype)initWithFrame:(NSRect)frameRect
{
if (!(self = [super initWithFrame:frameRect]))
{
if (!(self = [super initWithFrame:frameRect])) {
return self;
}
[self _init];
@ -147,14 +162,22 @@
- (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 -= 0.1;
clockMultiplier -= 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier);
}
if (!underclockKeyDown && clockMultiplier < 1.0) {
clockMultiplier += 0.1;
clockMultiplier += 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier);
}
}
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
}
@ -180,6 +203,7 @@
switch (button) {
case GBTurbo:
GB_set_turbo_mode(_gb, true, self.isRewinding);
analogClockMultiplierValid = false;
break;
case GBRewind:
@ -189,6 +213,7 @@
case GBUnderclock:
underclockKeyDown = true;
analogClockMultiplierValid = false;
break;
default:
@ -221,6 +246,7 @@
switch (button) {
case GBTurbo:
GB_set_turbo_mode(_gb, false, false);
analogClockMultiplierValid = false;
break;
case GBRewind:
@ -229,6 +255,7 @@
case GBUnderclock:
underclockKeyDown = false;
analogClockMultiplierValid = false;
break;
default:
@ -243,105 +270,102 @@
}
}
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
- (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);
UpdateSystemActivity(UsrActivity);
IOPMAssertionID assertionID;
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
for (unsigned player = 0; player < player_count; player++) {
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
objectForKey:[NSString stringWithFormat:@"%u", player]];
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
objectForKey:n2s(player)];
if (player_count != 1 && // Single player, accpet inputs from all joypads
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
![preferred_joypad isEqualToString:joystick_name]) {
![preferred_joypad isEqualToString:controller.uniqueID]) {
continue;
}
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
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];
}
for (GBButton i = 0; i < GBButtonCount; i++) {
NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]];
if (mapped_button && [mapped_button integerValue] == button) {
switch (i) {
case GBTurbo:
GB_set_turbo_mode(_gb, state, state && self.isRewinding);
break;
JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage;
if (!mapping && usage >= JOYButtonUsageGeneric0) {
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
}
case GBRewind:
self.isRewinding = state;
if (state) {
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 GBUnderclock:
underclockKeyDown = state;
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:
GB_set_key_state_for_player(_gb, (GB_key_t)i, player, state);
break;
}
}
}
}
}
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
{
unsigned player_count = GB_get_player_count(_gb);
UpdateSystemActivity(UsrActivity);
for (unsigned player = 0; player < player_count; player++) {
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
objectForKey:[NSString stringWithFormat:@"%u", player]];
if (player_count != 1 && // Single player, accpet inputs from all joypads
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
![preferred_joypad isEqualToString:joystick_name]) {
continue;
}
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
NSNumber *x_axis = [mapping objectForKey:@"XAxis"];
NSNumber *y_axis = [mapping objectForKey:@"YAxis"];
if (axis == [x_axis integerValue]) {
if (value > JOYSTICK_HIGH) {
axisActive[0] = true;
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, true);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false);
}
else if (value < -JOYSTICK_HIGH) {
axisActive[0] = true;
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, true);
}
else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) {
axisActive[0] = false;
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false);
}
}
else if (axis == [y_axis integerValue]) {
if (value > JOYSTICK_HIGH) {
axisActive[1] = true;
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, true);
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false);
}
else if (value < -JOYSTICK_HIGH) {
axisActive[1] = true;
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, true);
}
else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) {
axisActive[1] = false;
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false);
}
}
}
}
- (BOOL)acceptsFirstResponder
{
return YES;

View File

@ -1,4 +1,6 @@
#import "GBViewMetal.h"
#pragma clang diagnostic ignored "-Wpartial-availability"
static const vector_float2 rect[] =
{
@ -15,7 +17,7 @@ static const vector_float2 rect[] =
id<MTLBuffer> vertices;
id<MTLRenderPipelineState> pipeline_state;
id<MTLCommandQueue> command_queue;
id<MTLBuffer> mix_previous_buffer;
id<MTLBuffer> frame_blending_mode_buffer;
id<MTLBuffer> output_resolution_buffer;
vector_float2 output_resolution;
}
@ -50,14 +52,15 @@ static const vector_float2 rect[] =
view.delegate = self;
self.internalView = view;
view.paused = YES;
view.enableSetNeedsDisplay = YES;
vertices = [device newBufferWithBytes:rect
length:sizeof(rect)
options:MTLResourceStorageModeShared];
static const bool default_mix_value = false;
mix_previous_buffer = [device newBufferWithBytes:&default_mix_value
length:sizeof(default_mix_value)
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
@ -131,6 +134,7 @@ static const vector_float2 rect[] =
- (void)drawInMTKView:(nonnull MTKView *)view
{
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
if (!self.gb) return;
if (texture.width != GB_get_screen_width(self.gb) ||
texture.height != GB_get_screen_height(self.gb)) {
[self allocateTextures];
@ -145,7 +149,7 @@ static const vector_float2 rect[] =
mipmapLevel:0
withBytes:[self currentBuffer]
bytesPerRow:texture.width * 4];
if ([self shouldBlendFrameWithPrevious]) {
if ([self frameBlendingMode]) {
[previous_texture replaceRegion:region
mipmapLevel:0
withBytes:[self previousBuffer]
@ -155,9 +159,8 @@ static const vector_float2 rect[] =
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
if(render_pass_descriptor != nil)
{
*(bool *)[mix_previous_buffer contents] = [self shouldBlendFrameWithPrevious];
if (render_pass_descriptor != nil) {
*(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode];
*(vector_float2 *)[output_resolution_buffer contents] = output_resolution;
id<MTLRenderCommandEncoder> render_encoder =
@ -174,7 +177,7 @@ static const vector_float2 rect[] =
offset:0
atIndex:0];
[render_encoder setFragmentBuffer:mix_previous_buffer
[render_encoder setFragmentBuffer:frame_blending_mode_buffer
offset:0
atIndex:0];
@ -205,7 +208,7 @@ static const vector_float2 rect[] =
{
[super flip];
dispatch_async(dispatch_get_main_queue(), ^{
[(MTKView *)self.internalView draw];
[(MTKView *)self.internalView setNeedsDisplay:YES];
});
}

View File

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

BIN
Cocoa/Joypad~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
Cocoa/Joypad~dark@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

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

View File

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

View File

@ -0,0 +1,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

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

BIN
Cocoa/Speaker~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
Cocoa/Speaker~dark@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1,684 +0,0 @@
/*
Joypad support is based on a stripped-down version of SDL's Darwin implementation
of the Joystick API, under the following license:
*/
/*
Simple DirectMedia Layer
Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <AppKit/AppKit.h>
#include <stdint.h>
#include <stdbool.h>
#include <IOKit/hid/IOHIDLib.h>
#include "GBJoystickListener.h"
typedef signed SDL_JoystickID;
typedef struct _SDL_Joystick SDL_Joystick;
typedef struct _SDL_JoystickAxisInfo
{
int16_t initial_value; /* Initial axis state */
int16_t value; /* Current axis state */
int16_t zero; /* Zero point on the axis (-32768 for triggers) */
bool has_initial_value; /* Whether we've seen a value on the axis yet */
bool sent_initial_value; /* Whether we've sent the initial axis value */
} SDL_JoystickAxisInfo;
struct _SDL_Joystick
{
SDL_JoystickID instance_id; /* Device instance, monotonically increasing from 0 */
char *name; /* Joystick name - system dependent */
int naxes; /* Number of axis controls on the joystick */
SDL_JoystickAxisInfo *axes;
int nbuttons; /* Number of buttons on the joystick */
uint8_t *buttons; /* Current button states */
struct joystick_hwdata *hwdata; /* Driver dependent information */
int ref_count; /* Reference count for multiple opens */
bool is_game_controller;
bool force_recentering; /* SDL_TRUE if this device needs to have its state reset to 0 */
struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */
};
typedef struct {
uint8_t data[16];
} SDL_JoystickGUID;
struct recElement
{
IOHIDElementRef elementRef;
IOHIDElementCookie cookie;
uint32_t usagePage, usage; /* HID usage */
SInt32 min; /* reported min value possible */
SInt32 max; /* reported max value possible */
/* runtime variables used for auto-calibration */
SInt32 minReport; /* min returned value */
SInt32 maxReport; /* max returned value */
struct recElement *pNext; /* next element in list */
};
typedef struct recElement recElement;
struct joystick_hwdata
{
IOHIDDeviceRef deviceRef; /* HIDManager device handle */
io_service_t ffservice; /* Interface for force feedback, 0 = no ff */
char product[256]; /* name of product */
uint32_t usage; /* usage page from IOUSBHID Parser.h which defines general usage */
uint32_t usagePage; /* usage within above page from IOUSBHID Parser.h which defines specific usage */
int axes; /* number of axis (calculated, not reported by device) */
int buttons; /* number of buttons (calculated, not reported by device) */
int elements; /* number of total elements (should be total of above) (calculated, not reported by device) */
recElement *firstAxis;
recElement *firstButton;
bool removed;
int instance_id;
SDL_JoystickGUID guid;
SDL_Joystick joystick;
};
typedef struct joystick_hwdata recDevice;
/* The base object of the HID Manager API */
static IOHIDManagerRef hidman = NULL;
/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
static int s_joystick_instance_id = -1;
#define SDL_JOYSTICK_AXIS_MAX 32767
void SDL_PrivateJoystickAxis(SDL_Joystick * joystick, uint8_t axis, int16_t value)
{
/* Make sure we're not getting garbage or duplicate events */
if (axis >= joystick->naxes) {
return;
}
if (!joystick->axes[axis].has_initial_value) {
joystick->axes[axis].initial_value = value;
joystick->axes[axis].value = value;
joystick->axes[axis].zero = value;
joystick->axes[axis].has_initial_value = true;
}
if (value == joystick->axes[axis].value) {
return;
}
if (!joystick->axes[axis].sent_initial_value) {
/* Make sure we don't send motion until there's real activity on this axis */
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 controller needed 96 */
if (abs(value - joystick->axes[axis].value) <= MAX_ALLOWED_JITTER) {
return;
}
joystick->axes[axis].sent_initial_value = true;
joystick->axes[axis].value = value; /* Just so we pass the check above */
SDL_PrivateJoystickAxis(joystick, axis, joystick->axes[axis].initial_value);
}
/* Update internal joystick state */
joystick->axes[axis].value = value;
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
while (responder) {
if ([responder respondsToSelector:@selector(joystick:axis:movedTo:)]) {
[responder joystick:@(joystick->name) axis:axis movedTo:value];
break;
}
responder = (typeof(responder)) [responder nextResponder];
}
}
void SDL_PrivateJoystickButton(SDL_Joystick *joystick, uint8_t button, uint8_t state)
{
/* Make sure we're not getting garbage or duplicate events */
if (button >= joystick->nbuttons) {
return;
}
if (state == joystick->buttons[button]) {
return;
}
/* Update internal joystick state */
joystick->buttons[button] = state;
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
while (responder) {
if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) {
[responder joystick:@(joystick->name) button:button changedState:state];
break;
}
responder = (typeof(responder)) [responder nextResponder];
}
}
static void
FreeElementList(recElement *pElement)
{
while (pElement) {
recElement *pElementNext = pElement->pNext;
free(pElement);
pElement = pElementNext;
}
}
static recDevice *
FreeDevice(recDevice *removeDevice)
{
recDevice *pDeviceNext = NULL;
if (removeDevice) {
if (removeDevice->deviceRef) {
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
removeDevice->deviceRef = NULL;
}
/* free element lists */
FreeElementList(removeDevice->firstAxis);
FreeElementList(removeDevice->firstButton);
free(removeDevice);
}
return pDeviceNext;
}
static SInt32
GetHIDElementState(recDevice *pDevice, recElement *pElement)
{
SInt32 value = 0;
if (pDevice && pElement) {
IOHIDValueRef valueRef;
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
/* record min and max for auto calibration */
if (value < pElement->minReport) {
pElement->minReport = value;
}
if (value > pElement->maxReport) {
pElement->maxReport = value;
}
}
}
return value;
}
static SInt32
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
{
const float deviceScale = max - min;
const float readScale = pElement->maxReport - pElement->minReport;
const SInt32 value = GetHIDElementState(pDevice, pElement);
if (readScale == 0) {
return value; /* no scaling at all */
}
return ((value - pElement->minReport) * deviceScale / readScale) + min;
}
static void
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
{
recDevice *device = (recDevice *) ctx;
device->removed = true;
device->deviceRef = NULL; // deviceRef was invalidated due to the remove
FreeDevice(device);
}
static void AddHIDElement(const void *value, void *parameter);
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
static void
AddHIDElements(CFArrayRef array, recDevice *pDevice)
{
const CFRange range = { 0, CFArrayGetCount(array) };
CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
}
static bool
ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
while (listitem) {
if (listitem->cookie == cookie) {
return true;
}
listitem = listitem->pNext;
}
return false;
}
/* See if we care about this HID element, and if so, note it in our recDevice. */
static void
AddHIDElement(const void *value, void *parameter)
{
recDevice *pDevice = (recDevice *) parameter;
IOHIDElementRef refElement = (IOHIDElementRef) value;
const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
const uint32_t usage = IOHIDElementGetUsage(refElement);
recElement *element = NULL;
recElement **headElement = NULL;
/* look at types of interest */
switch (IOHIDElementGetType(refElement)) {
case kIOHIDElementTypeInput_Misc:
case kIOHIDElementTypeInput_Button:
case kIOHIDElementTypeInput_Axis: {
switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
case kHIDUsage_GD_Y:
case kHIDUsage_GD_Z:
case kHIDUsage_GD_Rx:
case kHIDUsage_GD_Ry:
case kHIDUsage_GD_Rz:
case kHIDUsage_GD_Slider:
case kHIDUsage_GD_Dial:
case kHIDUsage_GD_Wheel:
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->axes++;
headElement = &(pDevice->firstAxis);
}
}
break;
case kHIDUsage_GD_DPadUp:
case kHIDUsage_GD_DPadDown:
case kHIDUsage_GD_DPadRight:
case kHIDUsage_GD_DPadLeft:
case kHIDUsage_GD_Start:
case kHIDUsage_GD_Select:
case kHIDUsage_GD_SystemMainMenu:
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->buttons++;
headElement = &(pDevice->firstButton);
}
}
break;
}
break;
case kHIDPage_Simulation:
switch (usage) {
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Throttle:
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->axes++;
headElement = &(pDevice->firstAxis);
}
}
break;
default:
break;
}
break;
case kHIDPage_Button:
case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->buttons++;
headElement = &(pDevice->firstButton);
}
}
break;
default:
break;
}
}
break;
case kIOHIDElementTypeCollection: {
CFArrayRef array = IOHIDElementGetChildren(refElement);
if (array) {
AddHIDElements(array, pDevice);
}
}
break;
default:
break;
}
if (element && headElement) { /* add to list */
recElement *elementPrevious = NULL;
recElement *elementCurrent = *headElement;
while (elementCurrent && usage >= elementCurrent->usage) {
elementPrevious = elementCurrent;
elementCurrent = elementCurrent->pNext;
}
if (elementPrevious) {
elementPrevious->pNext = element;
} else {
*headElement = element;
}
element->elementRef = refElement;
element->usagePage = usagePage;
element->usage = usage;
element->pNext = elementCurrent;
element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
element->cookie = IOHIDElementGetCookie(refElement);
pDevice->elements++;
}
}
}
static bool
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
{
const uint16_t BUS_USB = 0x03;
const uint16_t BUS_BLUETOOTH = 0x05;
int32_t vendor = 0;
int32_t product = 0;
int32_t version = 0;
CFTypeRef refCF = NULL;
CFArrayRef array = NULL;
uint16_t *guid16 = (uint16_t *)pDevice->guid.data;
/* get usage page and usage */
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
}
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
return false; /* Filter device list to non-keyboard/mouse stuff */
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
}
if ((pDevice->usage != kHIDUsage_GD_Joystick &&
pDevice->usage != kHIDUsage_GD_GamePad &&
pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
return false; /* Filter device list to non-keyboard/mouse stuff */
}
pDevice->deviceRef = hidDevice;
/* get device name */
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
if (!refCF) {
/* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
}
if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
}
memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
if (vendor && product) {
*guid16++ = BUS_USB;
*guid16++ = 0;
*guid16++ = vendor;
*guid16++ = 0;
*guid16++ = product;
*guid16++ = 0;
*guid16++ = version;
*guid16++ = 0;
} else {
*guid16++ = BUS_BLUETOOTH;
*guid16++ = 0;
strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
}
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
if (array) {
AddHIDElements(array, pDevice);
CFRelease(array);
}
return true;
}
void
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
{
recDevice *device = joystick->hwdata;
recElement *element;
SInt32 value;
int i;
if (!device) {
return;
}
if (device->removed) { /* device was unplugged; ignore it. */
if (joystick->hwdata) {
joystick->force_recentering = true;
joystick->hwdata = NULL;
}
return;
}
element = device->firstAxis;
i = 0;
while (element) {
value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
SDL_PrivateJoystickAxis(joystick, i, value);
element = element->pNext;
++i;
}
element = device->firstButton;
i = 0;
while (element) {
value = GetHIDElementState(device, element);
if (value > 1) { /* handle pressure-sensitive buttons */
value = 1;
}
SDL_PrivateJoystickButton(joystick, i, value);
element = element->pNext;
++i;
}
}
static void JoystickInputCallback(
SDL_Joystick * joystick,
IOReturn result,
void * _Nullable sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t * report,
CFIndex reportLength)
{
SDL_SYS_JoystickUpdate(joystick);
}
static void
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
{
recDevice *device;
io_service_t ioservice;
if (res != kIOReturnSuccess) {
return;
}
device = (recDevice *) calloc(1, sizeof(recDevice));
if (!device) {
abort();
return;
}
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
free(device);
return; /* not a device we care about, probably. */
}
SDL_Joystick *joystick = &device->joystick;
joystick->instance_id = device->instance_id;
joystick->hwdata = device;
joystick->name = device->product;
joystick->naxes = device->axes;
joystick->nbuttons = device->buttons;
if (joystick->naxes > 0) {
joystick->axes = (SDL_JoystickAxisInfo *) calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo));
}
if (joystick->nbuttons > 0) {
joystick->buttons = (uint8_t *) calloc(joystick->nbuttons, 1);
}
/* Get notified when this device is disconnected. */
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
static uint8_t junk[80];
IOHIDDeviceRegisterInputReportCallback(ioHIDDeviceObject, junk, sizeof(junk), (IOHIDReportCallback) JoystickInputCallback, joystick);
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
/* Allocate an instance ID for this device */
device->instance_id = ++s_joystick_instance_id;
/* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
}
static bool
ConfigHIDManager(CFArrayRef matchingArray)
{
CFRunLoopRef runloop = CFRunLoopGetCurrent();
if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
return false;
}
IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
IOHIDManagerScheduleWithRunLoop(hidman, runloop, kCFRunLoopDefaultMode);
return true; /* good to go. */
}
static CFDictionaryRef
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
{
CFDictionaryRef retval = NULL;
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
if (pageNumRef && usageNumRef) {
retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
if (pageNumRef) {
CFRelease(pageNumRef);
}
if (usageNumRef) {
CFRelease(usageNumRef);
}
if (!retval) {
*okay = 0;
}
return retval;
}
static bool
CreateHIDManager(void)
{
bool retval = false;
int okay = 1;
const void *vals[] = {
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
};
const size_t numElements = sizeof(vals) / sizeof(vals[0]);
CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
size_t i;
for (i = 0; i < numElements; i++) {
if (vals[i]) {
CFRelease((CFTypeRef) vals[i]);
}
}
if (array) {
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (hidman != NULL) {
retval = ConfigHIDManager(array);
}
CFRelease(array);
}
return retval;
}
void __attribute__((constructor)) SDL_SYS_JoystickInit(void)
{
if (!CreateHIDManager()) {
fprintf(stderr, "Joystick: Couldn't initialize HID Manager");
}
}

View File

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

View File

@ -1,6 +1,7 @@
#include <stdint.h>
#include <math.h>
#include <string.h>
#include <assert.h>
#include "gb.h"
#define likely(x) __builtin_expect((x), 1)
@ -21,8 +22,15 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of
gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset;
}
static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
{
if (gb->model >= GB_MODEL_AGB) {
/* On the AGB, mixing is done digitally, so there are no per-channel
DACs. Instead, all channels are summed digital regardless of
whatever the DAC state would be on a CGB or earlier model. */
return true;
}
switch (index) {
case GB_SQUARE_1:
return gb->io_registers[GB_IO_NR12] & 0xF8;
@ -37,12 +45,70 @@ static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
return gb->io_registers[GB_IO_NR42] & 0xF8;
}
return false;
}
static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index)
{
if (!gb->apu.is_active[index]) return 0;
switch (index) {
case GB_SQUARE_1:
return gb->apu.square_channels[GB_SQUARE_1].current_volume;
case GB_SQUARE_2:
return gb->apu.square_channels[GB_SQUARE_2].current_volume;
case GB_WAVE:
return 0;
case GB_NOISE:
return gb->apu.noise_channel.current_volume;
}
return 0;
}
static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset)
{
if (!is_DAC_enabled(gb, index)) {
if (gb->model >= GB_MODEL_AGB) {
/* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different.
A channel that is not connected to a terminal is idenitcal to a connected channel
playing PCM sample 0. */
gb->apu.samples[index] = value;
if (gb->apu_output.sample_rate) {
unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1;
unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
if (index == GB_WAVE) {
/* For some reason, channel 3 is inverted on the AGB */
value ^= 0xF;
}
GB_sample_t output;
uint8_t bias = agb_bias_for_channel(gb, index);
if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
output.right = (0xf - value * 2 + bias) * right_volume;
}
else {
output.right = 0xf * right_volume;
}
if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
output.left = (0xf - value * 2 + bias) * left_volume;
}
else {
output.left = 0xf * left_volume;
}
if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
refresh_channel(gb, index, cycles_offset);
gb->apu_output.current_sample[index] = output;
}
}
return;
}
if (!GB_apu_is_DAC_enabled(gb, index)) {
value = gb->apu.samples[index];
}
else {
@ -66,20 +132,28 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
}
}
static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
static double smooth(double x)
{
GB_sample_t output = {0,0};
#pragma unroll
for (unsigned i = GB_N_CHANNELS; i--;) {
return 3*x*x - 2*x*x*x;
}
static void render(GB_gameboy_t *gb)
{
GB_sample_t output = {0, 0};
UNROLL
for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
double multiplier = CH_STEP;
if (!is_DAC_enabled(gb, i)) {
if (gb->model < GB_MODEL_AGB) {
if (!GB_apu_is_DAC_enabled(gb, i)) {
gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate;
if (gb->apu_output.dac_discharge[i] < 0) {
multiplier = 0;
gb->apu_output.dac_discharge[i] = 0;
}
else {
multiplier *= gb->apu_output.dac_discharge[i];
multiplier *= smooth(gb->apu_output.dac_discharge[i]);
}
}
else {
@ -88,11 +162,12 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
gb->apu_output.dac_discharge[i] = 1;
}
else {
multiplier *= gb->apu_output.dac_discharge[i];
multiplier *= smooth(gb->apu_output.dac_discharge[i]);
}
}
}
if (likely(gb->apu_output.last_update[i] == 0 || no_downsampling)) {
if (likely(gb->apu_output.last_update[i] == 0)) {
output.left += gb->apu_output.current_sample[i].left * multiplier;
output.right += gb->apu_output.current_sample[i].right * multiplier;
}
@ -126,7 +201,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
unsigned mask = gb->io_registers[GB_IO_NR51];
unsigned left_volume = 0;
unsigned right_volume = 0;
#pragma unroll
UNROLL
for (unsigned i = GB_N_CHANNELS; i--;) {
if (gb->apu.is_active[i]) {
if (mask & 1) {
@ -150,26 +225,18 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
}
}
if (dest) {
*dest = filtered_output;
return;
}
while (gb->apu_output.copy_in_progress);
while (!__sync_bool_compare_and_swap(&gb->apu_output.lock, false, true));
if (gb->apu_output.buffer_position < gb->apu_output.buffer_size) {
gb->apu_output.buffer[gb->apu_output.buffer_position++] = filtered_output;
}
gb->apu_output.lock = false;
assert(gb->apu_output.sample_callback);
gb->apu_output.sample_callback(gb, &filtered_output);
}
static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb)
static uint16_t new_sweep_sample_length(GB_gameboy_t *gb)
{
uint16_t delta = gb->apu.shadow_sweep_sample_legnth >> (gb->io_registers[GB_IO_NR10] & 7);
uint16_t delta = gb->apu.shadow_sweep_sample_length >> (gb->io_registers[GB_IO_NR10] & 7);
if (gb->io_registers[GB_IO_NR10] & 8) {
return gb->apu.shadow_sweep_sample_legnth - delta;
return gb->apu.shadow_sweep_sample_length - delta;
}
return gb->apu.shadow_sweep_sample_legnth + delta;
return gb->apu.shadow_sweep_sample_length + delta;
}
static void update_square_sample(GB_gameboy_t *gb, unsigned index)
@ -216,6 +283,15 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index)
if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) {
if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) {
if (gb->cgb_double_speed) {
if (index == GB_SQUARE_1) {
gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1;
}
else {
gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F;
}
}
if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) {
gb->apu.square_channels[index].current_volume++;
}
@ -238,7 +314,10 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
uint8_t nr42 = gb->io_registers[GB_IO_NR42];
if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) {
if (!--gb->apu.noise_channel.volume_countdown) {
if (!gb->apu.noise_channel.volume_countdown || !--gb->apu.noise_channel.volume_countdown) {
if (gb->cgb_double_speed) {
gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F;
}
if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) {
gb->apu.noise_channel.current_volume++;
}
@ -262,11 +341,16 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
void GB_apu_div_event(GB_gameboy_t *gb)
{
if (!gb->apu.global_enable) return;
if (gb->apu.skip_div_event) {
gb->apu.skip_div_event = false;
if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIP) {
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIPPED;
return;
}
if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIPPED) {
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_INACTIVE;
}
else {
gb->apu.div_divider++;
}
if ((gb->apu.div_divider & 1) == 0) {
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
@ -305,7 +389,6 @@ void GB_apu_div_event(GB_gameboy_t *gb)
if (gb->apu.wave_channel.pulse_length) {
if (!--gb->apu.wave_channel.pulse_length) {
gb->apu.is_active[GB_WAVE] = false;
gb->apu.wave_channel.current_sample = 0;
update_sample(gb, GB_WAVE, 0, 0);
}
}
@ -329,8 +412,8 @@ void GB_apu_div_event(GB_gameboy_t *gb)
if (!--gb->apu.square_sweep_countdown) {
if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) {
gb->apu.square_channels[GB_SQUARE_1].sample_length =
gb->apu.shadow_sweep_sample_legnth =
gb->apu.new_sweep_sample_legnth;
gb->apu.shadow_sweep_sample_length =
gb->apu.new_sweep_sample_length;
}
if (gb->io_registers[GB_IO_NR10] & 0x70) {
@ -353,6 +436,7 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.apu_cycles = 0;
if (!cycles) return;
if (likely(!gb->stopped || GB_is_cgb(gb))) {
/* To align the square signal to 1MHz */
gb->apu.lf_div ^= cycles & 1;
gb->apu.noise_channel.alignment += cycles;
@ -363,8 +447,8 @@ void GB_apu_run(GB_gameboy_t *gb)
}
else {
/* APU bug: sweep frequency is checked after adding the sweep delta twice */
gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb);
if (gb->apu.new_sweep_sample_legnth > 0x7ff) {
gb->apu.new_sweep_sample_length = new_sweep_sample_length(gb);
if (gb->apu.new_sweep_sample_length > 0x7ff) {
gb->apu.is_active[GB_SQUARE_1] = false;
update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles);
gb->apu.sweep_enabled = false;
@ -374,8 +458,8 @@ void GB_apu_run(GB_gameboy_t *gb)
}
}
#pragma unroll
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
UNROLL
for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) {
if (gb->apu.is_active[i]) {
uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) {
@ -383,6 +467,9 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1;
gb->apu.square_channels[i].current_sample_index++;
gb->apu.square_channels[i].current_sample_index &= 0x7;
if (cycles_left == 0 && gb->apu.samples[i] == 0) {
gb->apu.pcm_mask[0] &= i == GB_SQUARE_1? 0xF0 : 0x0F;
}
update_square_sample(gb, i);
}
@ -421,7 +508,6 @@ void GB_apu_run(GB_gameboy_t *gb)
/* Step LFSR */
unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
/* Todo: is this formula is different on a GBA? */
bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1;
gb->apu.noise_channel.lfsr >>= 1;
@ -434,16 +520,10 @@ void GB_apu_run(GB_gameboy_t *gb)
}
gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
if (gb->model == GB_MODEL_CGB_C) {
/* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models.
Because this degrades audio quality, and testing this on a pre-CGB device requires audio records,
I'll assume these devices are innocent until proven guilty.
Also happens on CGB-B, but not on CGB-D.
*/
gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample;
if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) {
gb->apu.pcm_mask[1] &= 0x0F;
}
gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
@ -454,68 +534,31 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.noise_channel.sample_countdown -= cycles_left;
}
}
}
if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_since_render += cycles;
if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) {
if (gb->apu_output.sample_cycles >= gb->apu_output.cycles_per_sample) {
gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample;
render(gb, false, NULL);
render(gb);
}
}
}
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
{
if (gb->sgb) {
if (GB_sgb_render_jingle(gb, dest, count)) return;
}
gb->apu_output.copy_in_progress = true;
/* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */
size_t buffer_position = gb->apu_output.buffer_position;
if (!gb->apu_output.stream_started) {
// Intentionally fail the first copy to sync the stream with the Gameboy.
gb->apu_output.stream_started = true;
gb->apu_output.buffer_position = 0;
buffer_position = 0;
}
if (count > buffer_position) {
// GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position);
GB_sample_t output;
render(gb, true, &output);
for (unsigned i = 0; i < count - buffer_position; i++) {
dest[buffer_position + i] = output;
}
if (buffer_position) {
if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) {
gb->apu_output.buffer_size += count - buffer_position;
gb->apu_output.buffer = realloc(gb->apu_output.buffer,
gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer));
gb->apu_output.stream_started = false;
}
}
count = buffer_position;
}
memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer));
memmove(gb->apu_output.buffer, gb->apu_output.buffer + count, (buffer_position - count) * sizeof(*gb->apu_output.buffer));
gb->apu_output.buffer_position -= count;
gb->apu_output.copy_in_progress = false;
}
void GB_apu_init(GB_gameboy_t *gb)
{
memset(&gb->apu, 0, sizeof(gb->apu));
/* Restore the wave form */
for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) {
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4;
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF;
}
gb->apu.lf_div = 1;
/* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on,
the first DIV/APU event is skipped. */
if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) {
gb->apu.skip_div_event = true;
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP;
gb->apu.div_divider = 1;
}
}
@ -523,7 +566,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
{
if (reg == GB_IO_NR52) {
uint8_t value = 0;
for (int i = 0; i < GB_N_CHANNELS; i++) {
for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
value >>= 1;
if (gb->apu.is_active[i]) {
value |= 0x8;
@ -561,7 +604,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
{
if (!gb->apu.global_enable && reg != GB_IO_NR52 && (GB_is_cgb(gb) ||
if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) ||
(
reg != GB_IO_NR11 &&
reg != GB_IO_NR21 &&
@ -681,11 +724,26 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR14:
case GB_IO_NR24: {
unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1;
/* TODO: When the sample length changes right before being updated, the countdown should change to the
old length, but the current sample should not change. Because our write timing isn't accurate to
the T-cycle, we hack around it by stepping the sample index backwards. */
if ((value & 0x80) == 0 && gb->apu.is_active[index]) {
/* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
double speed. */
if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */ || gb->apu.square_channels[index].sample_countdown & 1) {
if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
gb->apu.square_channels[index].current_sample_index--;
gb->apu.square_channels[index].current_sample_index &= 7;
}
}
}
gb->apu.square_channels[index].sample_length &= 0xFF;
gb->apu.square_channels[index].sample_length |= (value & 7) << 8;
if (index == GB_SQUARE_1) {
gb->apu.shadow_sweep_sample_legnth =
gb->apu.new_sweep_sample_legnth =
gb->apu.shadow_sweep_sample_length =
gb->apu.new_sweep_sample_length =
gb->apu.square_channels[0].sample_length;
}
if (value & 0x80) {
@ -761,7 +819,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.wave_channel.enable = value & 0x80;
if (!gb->apu.wave_channel.enable) {
gb->apu.is_active[GB_WAVE] = false;
gb->apu.wave_channel.current_sample = 0;
update_sample(gb, GB_WAVE, 0, 0);
}
break;
@ -770,7 +827,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
break;
case GB_IO_NR32:
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
if (gb->apu.is_active[GB_WAVE]) {
update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0);
}
break;
case GB_IO_NR33:
gb->apu.wave_channel.sample_length &= ~0xFF;
@ -811,7 +870,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
if (!gb->apu.is_active[GB_WAVE]) {
gb->apu.is_active[GB_WAVE] = true;
update_sample(gb, GB_WAVE, 0, 0);
update_sample(gb, GB_WAVE,
gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift,
0);
}
gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3;
gb->apu.wave_channel.current_sample_index = 0;
@ -820,11 +881,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.wave_channel.length_enabled = false;
}
/* Note that we don't change the sample just yet! This was verified on hardware. */
/* Todo: The first sample might *not* beskipped on the DMG, this could be a bug
introduced on the CGB. It appears that the bug was fixed on the AGB, but it's
not reflected by PCM34. This should be probably verified as this could just
mean differences in the DACs. */
/* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */
}
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
@ -869,7 +925,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.is_active[GB_NOISE] = false;
update_sample(gb, GB_NOISE, 0, 0);
}
else if (gb->apu.is_active[GB_NOISE]){
else if (gb->apu.is_active[GB_NOISE]) {
nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]);
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
@ -965,26 +1021,35 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->io_registers[reg] = value;
}
size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb)
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate)
{
return gb->apu_output.buffer_position;
}
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate)
{
if (gb->apu_output.buffer) {
free(gb->apu_output.buffer);
}
gb->apu_output.buffer_size = sample_rate / 25; // 40ms delay
gb->apu_output.buffer = malloc(gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer));
gb->apu_output.sample_rate = sample_rate;
gb->apu_output.buffer_position = 0;
if (sample_rate) {
gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate);
}
gb->apu_output.rate_set_in_clocks = false;
GB_apu_update_cycles_per_sample(gb);
}
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample)
{
if (cycles_per_sample == 0) {
GB_set_sample_rate(gb, 0);
return;
}
gb->apu_output.cycles_per_sample = cycles_per_sample;
gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2;
gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample);
gb->apu_output.rate_set_in_clocks = true;
}
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback)
{
gb->apu_output.sample_callback = callback;
}
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode)
{
gb->apu_output.highpass_mode = mode;
@ -992,6 +1057,7 @@ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode)
void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb)
{
if (gb->apu_output.rate_set_in_clocks) return;
if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */
}

View File

@ -8,12 +8,8 @@
#ifdef GB_INTERNAL
/* Speed = 1 / Length (in seconds) */
/* Todo: Measure these and find the actual curve shapes.
They are known to be incorrect (Some analog test ROM sound different),
but are good enough approximations to fix Cannon Fodder's terrible audio.
It also varies by model. */
#define DAC_DECAY_SPEED 50000
#define DAC_ATTACK_SPEED 1000
#define DAC_DECAY_SPEED 20000
#define DAC_ATTACK_SPEED 20000
/* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */
@ -50,6 +46,8 @@ enum GB_CHANNELS {
GB_N_CHANNELS
};
typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample);
typedef struct
{
bool global_enable;
@ -66,8 +64,8 @@ typedef struct
uint8_t square_sweep_countdown; // In 128Hz
uint8_t square_sweep_calculate_countdown; // In 2 MHz
uint16_t new_sweep_sample_legnth;
uint16_t shadow_sweep_sample_legnth;
uint16_t new_sweep_sample_length;
uint16_t shadow_sweep_sample_length;
bool sweep_enabled;
bool sweep_decreasing;
@ -116,9 +114,12 @@ typedef struct
} noise_channel;
bool skip_div_event;
#define GB_SKIP_DIV_EVENT_INACTIVE 0
#define GB_SKIP_DIV_EVENT_SKIPPED 1
#define GB_SKIP_DIV_EVENT_SKIP 2
uint8_t skip_div_event;
bool current_lfsr_sample;
bool previous_lfsr_sample;
uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch
} GB_apu_t;
typedef enum {
@ -131,14 +132,6 @@ typedef enum {
typedef struct {
unsigned sample_rate;
GB_sample_t *buffer;
size_t buffer_size;
size_t buffer_position;
bool stream_started; /* detects first copy request to minimize lag */
volatile bool copy_in_progress;
volatile bool lock;
double sample_cycles; // In 8 MHz units
double cycles_per_sample;
@ -152,20 +145,25 @@ typedef struct {
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 int sample_rate);
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count);
size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb);
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
void GB_set_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 */

View File

@ -1,17 +1,17 @@
#include "gb.h"
static int noise_seed = 0;
static signed noise_seed = 0;
/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported.
We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */
static uint8_t generate_noise(uint8_t x, uint8_t y)
{
int value = (x + y * 128 + noise_seed);
signed value = (x + y * 128 + noise_seed);
uint8_t *data = (uint8_t *) &value;
unsigned hash = 0;
while ((int *) data != &value + 1) {
while ((signed *) data != &value + 1) {
hash ^= (*data << 8);
if (hash & 0x8000) {
hash ^= 0x8a00;

313
Core/cheats.c Normal file
View File

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

42
Core/cheats.h Normal file
View File

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

View File

@ -220,7 +220,8 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue)
banking_state_t state;
save_banking_state(gb, &state);
switch_banking_state(gb, lvalue.memory_address.bank);
value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value));
value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) |
(GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100));
restore_banking_state(gb, &state);
return r;
}
@ -261,6 +262,7 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value)
save_banking_state(gb, &state);
switch_banking_state(gb, lvalue.memory_address.bank);
GB_write_memory(gb, lvalue.memory_address.value, value);
GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8);
restore_banking_state(gb, &state);
return;
}
@ -296,13 +298,15 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value)
static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);}
static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);}
static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);}
static value_t _div(value_t a, value_t b) {
static value_t _div(value_t a, value_t b)
{
if (b.value == 0) {
return FIX_BANK(0);
}
return FIX_BANK(a.value / b.value);
};
static value_t mod(value_t a, value_t b) {
static value_t mod(value_t a, value_t b)
{
if (b.value == 0) {
return FIX_BANK(0);
}
@ -332,7 +336,7 @@ static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.va
static struct {
const char *string;
char priority;
int8_t priority;
value_t (*operator)(value_t, value_t);
value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t);
} operators[] =
@ -378,16 +382,15 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
length--;
}
if (length == 0)
{
if (length == 0) {
GB_log(gb, "Expected expression.\n");
*error = true;
return (lvalue_t){0,};
}
if (string[0] == '(' && string[length - 1] == ')') {
// Attempt to strip parentheses
signed int depth = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '(') depth++;
if (depth == 0) {
// First and last are not matching
@ -400,8 +403,8 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
}
else if (string[0] == '[' && string[length - 1] == ']') {
// Attempt to strip square parentheses (memory dereference)
signed int depth = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '[') depth++;
if (depth == 0) {
// First and last are not matching
@ -416,8 +419,8 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
}
else if (string[0] == '{' && string[length - 1] == '}') {
// Attempt to strip curly parentheses (memory dereference)
signed int depth = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '{') depth++;
if (depth == 0) {
// First and last are not matching
@ -455,12 +458,12 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc};
}
}
GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string);
GB_log(gb, "Unknown register: %.*s\n", (unsigned) length, string);
*error = true;
return (lvalue_t){0,};
}
GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string);
GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned) length, string);
*error = true;
return (lvalue_t){0,};
}
@ -485,16 +488,15 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
length--;
}
if (length == 0)
{
if (length == 0) {
GB_log(gb, "Expected expression.\n");
*error = true;
goto exit;
}
if (string[0] == '(' && string[length - 1] == ')') {
// Attempt to strip parentheses
signed int depth = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '(') depth++;
if (depth == 0) {
// First and last are not matching
@ -510,8 +512,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
}
else if (string[0] == '[' && string[length - 1] == ']') {
// Attempt to strip square parentheses (memory dereference)
signed int depth = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '[') depth++;
if (depth == 0) {
// First and last are not matching
@ -537,8 +539,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
}
else if (string[0] == '{' && string[length - 1] == '}') {
// Attempt to strip curly parentheses (memory dereference)
signed int depth = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '{') depth++;
if (depth == 0) {
// First and last are not matching
@ -563,21 +565,25 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
}
}
// Search for lowest priority operator
signed int depth = 0;
unsigned int operator_index = -1;
unsigned int operator_pos = 0;
for (int i = 0; i < length; i++) {
signed depth = 0;
unsigned operator_index = -1;
unsigned operator_pos = 0;
for (unsigned i = 0; i < length; i++) {
if (string[i] == '(') depth++;
else if (string[i] == ')') depth--;
else if (string[i] == '[') depth++;
else if (string[i] == ']') depth--;
else if (depth == 0) {
for (int j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) {
if (strlen(operators[j].string) > length - i) continue; // Operator too big.
// Priority higher than what we already have.
if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) continue;
unsigned long operator_length = strlen(operators[j].string);
for (unsigned j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) {
unsigned operator_length = strlen(operators[j].string);
if (operator_length > length - i) continue; // Operator too long
if (memcmp(string + i, operators[j].string, operator_length) == 0) {
if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) {
/* for supporting = vs ==, etc*/
i += operator_length - 1;
break;
}
// Found an operator!
operator_pos = i;
operator_index = j;
@ -589,7 +595,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
}
}
if (operator_index != -1) {
unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string));
unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string));
value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value);
if (*error) goto exit;
if (operators[operator_index].lvalue_operator) {
@ -657,13 +663,13 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
goto exit;
}
GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string);
GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned) length, string);
*error = true;
goto exit;
}
char *end;
int base = 10;
unsigned base = 10;
if (string[0] == '$') {
string++;
base = 16;
@ -671,7 +677,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
}
uint16_t literal = (uint16_t) (strtol(string, &end, base));
if (end != string + length) {
GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string);
GB_log(gb, "Failed to parse: %.*s\n", (unsigned) length, string);
*error = true;
goto exit;
}
@ -683,6 +689,7 @@ exit:
struct debugger_command_s;
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command);
typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context);
typedef struct debugger_command_s {
const char *command;
@ -691,6 +698,8 @@ typedef struct debugger_command_s {
const char *help_string; // Null if should not appear in help
const char *arguments_format; // For usage message
const char *modifiers_format; // For usage message
debugger_completer_imp_t *argument_completer;
debugger_completer_imp_t *modifiers_completer;
} debugger_command_t;
static const char *lstrip(const char *str)
@ -811,12 +820,48 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
return true;
}
GB_log(gb, "AF = $%04x\n", gb->registers[GB_REGISTER_AF]); /* AF can't really be an address */
GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */
(gb->f & GB_CARRY_FLAG)? 'C' : '-',
(gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-',
(gb->f & GB_SUBTRACT_FLAG)? 'N' : '-',
(gb->f & GB_ZERO_FLAG)? 'Z' : '-');
GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false));
GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false));
GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false));
GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false));
GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false));
GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled");
return true;
}
static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"on", "off"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
/* Enable or disable software breakpoints */
static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) {
gb->has_software_breakpoints = true;
}
else if (strcmp(lstrip(arguments), "off") == 0) {
gb->has_software_breakpoints = false;
}
else {
print_usage(gb, command);
}
return true;
}
@ -829,8 +874,8 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
uint32_t key = BP_KEY(addr);
int min = 0;
int max = gb->n_breakpoints;
unsigned min = 0;
unsigned max = gb->n_breakpoints;
while (min < max) {
uint16_t pivot = (min + max) / 2;
if (gb->breakpoints[pivot].key == key) return pivot;
@ -844,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min;
}
static inline bool is_legal_symbol_char(char c)
{
if (c >= '0' && c <= '9') return true;
if (c >= 'A' && c <= 'Z') return true;
if (c >= 'a' && c <= 'z') return true;
if (c == '_') return true;
if (c == '.') return true;
return false;
}
static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context)
{
const char *symbol_prefix = string;
while (*string) {
if (!is_legal_symbol_char(*string)) {
symbol_prefix = string + 1;
}
string++;
}
if (*symbol_prefix == '$') {
return NULL;
}
struct {
uint16_t bank;
uint32_t symbol;
} *context = (void *)_context;
size_t length = strlen(symbol_prefix);
while (context->bank < 0x200) {
if (gb->bank_symbols[context->bank] == NULL ||
context->symbol >= gb->bank_symbols[context->bank]->n_symbols) {
context->bank++;
context->symbol = 0;
continue;
}
const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name;
if (memcmp(symbol_prefix, candidate, length) == 0) {
return strdup(candidate + length);
}
}
return NULL;
}
static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"j"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
bool is_jump_to = true;
@ -871,13 +975,13 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
condition += strlen(" if ");
/* Verify condition is sane (Todo: This might have side effects!) */
bool error;
debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, NULL, NULL);
debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL);
if (error) return true;
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = BP_KEY(result);
if (error) return true;
@ -940,7 +1044,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = BP_KEY(result);
if (error) return true;
@ -996,8 +1100,8 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
return 0;
}
uint32_t key = WP_KEY(addr);
int min = 0;
int max = gb->n_watchpoints;
unsigned min = 0;
unsigned max = gb->n_watchpoints;
while (min < max) {
uint16_t pivot = (min + max) / 2;
if (gb->watchpoints[pivot].key == key) return pivot;
@ -1011,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min;
}
static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"r", "rw", "w"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
@ -1056,13 +1173,13 @@ print_usage:
/* To make $new and $old legal */
uint16_t dummy = 0;
uint8_t dummy2 = 0;
debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, &dummy, &dummy2);
debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2);
if (error) return true;
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = WP_KEY(result);
if (error) return true;
@ -1123,7 +1240,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = WP_KEY(result);
if (error) return true;
@ -1155,7 +1272,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0]));
gb->n_watchpoints--;
gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0]));
gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints *sizeof(gb->watchpoints[0]));
GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true));
return true;
@ -1204,7 +1321,7 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
gb->watchpoints[i].condition);
}
else {
GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb,addr, addr.has_bank),
GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank),
(gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-',
(gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-');
}
@ -1225,7 +1342,7 @@ static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to)
}
bool error;
bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition,
(unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value;
(unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
@ -1248,6 +1365,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
return _should_break(gb, full_addr, jump_to);
}
static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"a", "b", "d", "o", "x"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
@ -1264,7 +1394,7 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
if (!error) {
switch (modifiers[0]) {
case 'a':
@ -1310,7 +1440,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
}
bool error;
value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint16_t count = 32;
if (modifiers) {
@ -1330,7 +1460,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
while (count) {
GB_log(gb, "%02x:%04x: ", addr.bank, addr.value);
for (int i = 0; i < 16 && count; i++) {
for (unsigned i = 0; i < 16 && count; i++) {
GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i));
count--;
}
@ -1343,7 +1473,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
else {
while (count) {
GB_log(gb, "%04x: ", addr.value);
for (int i = 0; i < 16 && count; i++) {
for (unsigned i = 0; i < 16 && count; i++) {
GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i));
count--;
}
@ -1362,7 +1492,7 @@ static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, cons
}
bool error;
value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint16_t count = 5;
if (modifiers) {
@ -1410,20 +1540,27 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
}
if (cartridge->mbc_type) {
static const char * const mapper_names[] = {
if (gb->is_mbc30) {
GB_log(gb, "MBC30\n");
}
else {
static const char *const mapper_names[] = {
[GB_MBC1] = "MBC1",
[GB_MBC2] = "MBC2",
[GB_MBC3] = "MBC3",
[GB_MBC5] = "MBC5",
[GB_HUC1] = "HUC1",
[GB_HUC3] = "HUC3",
[GB_HUC1] = "HUC-1",
[GB_HUC3] = "HUC-3",
};
GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]);
}
GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank);
if (cartridge->has_ram) {
GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
if (gb->cartridge_type->mbc_type != GB_HUC1) {
GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled");
}
}
if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) {
GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM");
}
@ -1439,7 +1576,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
}
if (cartridge->has_rumble) {
GB_log(gb, "Cart contains a rumble pak\n");
GB_log(gb, "Cart contains a Rumble Pak\n");
}
if (cartridge->has_rtc) {
@ -1459,7 +1596,7 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const
}
GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true));
for (unsigned int i = gb->backtrace_size; i--;) {
for (unsigned i = gb->backtrace_size; i--;) {
GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true));
}
@ -1476,7 +1613,7 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
return true;
}
GB_log(gb, "Ticks: %lu. (Resetting)\n", gb->debugger_ticks);
GB_log(gb, "Ticks: %llu. (Resetting)\n", (unsigned long long)gb->debugger_ticks);
gb->debugger_ticks = 0;
return true;
@ -1524,7 +1661,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
}
GB_log(gb, "LCDC:\n");
GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled");
GB_log(gb, " %s: %s\n", GB_is_cgb(gb)? (gb->cgb_mode? "Sprite priority flags" : "Background and Window") : "Background",
GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Sprite priority flags" : "Background and Window"),
(gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled");
GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled");
GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8");
@ -1564,12 +1701,194 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
}
GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]);
GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]);
GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]);
GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7, gb->io_registers[GB_IO_WY]);
GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off");
return true;
}
static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
}
GB_log(gb, "Current state: ");
if (!gb->apu.global_enable) {
GB_log(gb, "Disabled\n");
}
else {
GB_log(gb, "Enabled\n");
for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
gb->apu.is_active[channel] ? "active " : "inactive",
GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
gb->apu.samples[channel]);
}
}
GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
if (gb->io_registers[GB_IO_NR51] & 0x0f) {
for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if (gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
}
}
}
else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
if (gb->io_registers[GB_IO_NR51] & 0xf0) {
for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if (gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
}
}
}
else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) {
GB_log(gb, "\nCH%u:\n", channel + 1);
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.square_channels[channel].current_volume,
(gb->apu.square_channels[channel].sample_length ^ 0x7FF) * 2 + 1,
gb->apu.square_channels[channel].sample_countdown);
uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.square_channels[channel].volume_countdown,
nrx2 & 8 ? "in" : "de",
nrx2 & 7);
uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n",
duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty],
duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty],
gb->apu.square_channels[channel].current_sample_index & 0x7f,
gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : "");
if (channel == GB_SQUARE_1) {
GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n",
gb->apu.sweep_enabled? "active" : "inactive",
gb->apu.sweep_decreasing? "decreasing" : "increasing",
gb->apu.square_sweep_calculate_countdown);
}
if (gb->apu.square_channels[channel].length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.square_channels[channel].pulse_length);
}
}
GB_log(gb, "\nCH3:\n");
GB_log(gb, " Wave:");
for (uint8_t i = 0; i < 32; i++) {
GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]);
}
GB_log(gb, "\n");
GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
GB_log(gb, " Volume %s (right-shifted %u times)\n",
gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift],
gb->apu.wave_channel.shift);
GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.wave_channel.sample_length ^ 0x7ff,
gb->apu.wave_channel.sample_countdown);
if (gb->apu.wave_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.wave_channel.pulse_length);
}
GB_log(gb, "\nCH4:\n");
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.noise_channel.current_volume,
gb->apu.noise_channel.sample_length * 4 + 3,
gb->apu.noise_channel.sample_countdown);
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.noise_channel.volume_countdown,
gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
gb->io_registers[GB_IO_NR42] & 7);
GB_log(gb, " LFSR in %u-step mode, current value ",
gb->apu.noise_channel.narrow? 7 : 15);
for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
}
if (gb->apu.noise_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.noise_channel.pulse_length);
}
GB_log(gb, "\n\nReminder: APU ticks are @ 2 MiHz\n");
return true;
}
static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"c", "f", "l"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
print_usage(gb, command);
return true;
}
uint8_t shift_amount = 1, mask;
if (modifiers) {
switch (modifiers[0]) {
case 'c':
shift_amount = 2;
break;
case 'l':
shift_amount = 8;
break;
}
}
mask = (0xf << (shift_amount - 1)) & 0xf;
for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) {
for (uint8_t i = 0; i < 32; i++) {
if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) {
GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]);
}
else {
GB_log(gb, "%c", i%4 == 2 ? '-' : ' ');
}
}
GB_log(gb, "\n");
}
return true;
}
static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command);
#define HELP_NEWLINE "\n "
@ -1580,35 +1899,44 @@ static const debugger_command_t commands[] = {
{"next", 1, next, "Run the next instruction, skipping over function calls"},
{"step", 1, step, "Run the next instruction, stepping into function calls"},
{"finish", 1, finish, "Run until the current function returns"},
{"backtrace", 2, backtrace, "Display the current call stack"},
{"backtrace", 2, backtrace, "Displays the current call stack"},
{"bt", 2, }, /* Alias */
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"},
{"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used"},
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
"used"},
{"registers", 1, registers, "Print values of processor registers and other important registers"},
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
{"mbc", 3, }, /* Alias */
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Displays the current CGB palettes"},
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
"jumping to the target.",
"<expression>[ if <condition expression>]", "(j)"},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"},
"<expression>[ if <condition expression>]", "j",
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
"Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)"},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"},
"<expression>[ if <condition expression>]", "(r|w|rw)",
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
{"list", 1, list, "List all set breakpoints and watchpoints"},
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format"},
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
{"eval", 2, }, /* Alias */
{"examine", 2, examine, "Examine values at address", "<expression>", "count"},
{"examine", 2, examine, "Examine values at address", "<expression>", "count", .argument_completer = symbol_completer},
{"x", 1, }, /* Alias */
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count"},
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count", .argument_completer = symbol_completer},
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
@ -1635,7 +1963,7 @@ static const debugger_command_t *find_command(const char *string)
static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command)
{
GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command);
GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length);
GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command + command->min_length);
}
static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command)
@ -1755,7 +2083,7 @@ static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, u
}
bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value;
(unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
@ -1800,7 +2128,7 @@ static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr)
}
bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value;
(unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
@ -1864,6 +2192,63 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
}
}
/* Returns true if debugger waits for more commands */
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context)
{
char *command_string = input;
char *arguments = strchr(input, ' ');
if (arguments) {
/* Actually "split" the string. */
arguments[0] = 0;
arguments++;
}
char *modifiers = strchr(command_string, '/');
if (modifiers) {
/* Actually "split" the string. */
modifiers[0] = 0;
modifiers++;
}
const debugger_command_t *command = find_command(command_string);
if (command && command->implementation == help && arguments) {
command_string = arguments;
arguments = NULL;
}
/* No commands and no modifiers, complete the command */
if (!arguments && !modifiers) {
size_t length = strlen(command_string);
if (*context >= sizeof(commands) / sizeof(commands[0])) {
return NULL;
}
for (const debugger_command_t *command = &commands[*context]; command->command; command++) {
(*context)++;
if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */
return strdup(command->command + length);
}
}
return NULL;
}
if (command) {
if (arguments) {
if (command->argument_completer) {
return command->argument_completer(gb, arguments, context);
}
return NULL;
}
if (modifiers) {
if (command->modifiers_completer) {
return command->modifiers_completer(gb, modifiers, context);
}
return NULL;
}
}
return NULL;
}
typedef enum {
JUMP_TO_NONE,
JUMP_TO_BREAK,
@ -1877,7 +2262,7 @@ void GB_debugger_run(GB_gameboy_t *gb)
if (gb->debug_disable) return;
char *input = NULL;
if (gb->debug_next_command && gb->debug_call_depth <= 0) {
if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) {
gb->debug_stopped = true;
}
if (gb->debug_fin_command && gb->debug_call_depth == -1) {
@ -1969,6 +2354,19 @@ void GB_debugger_handle_async_commands(GB_gameboy_t *gb)
}
}
void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol)
{
bank &= 0x1FF;
if (!gb->bank_symbols[bank]) {
gb->bank_symbols[bank] = GB_map_alloc();
}
GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol);
if (allocated_symbol) {
GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol);
}
}
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "r");
@ -1987,18 +2385,11 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
}
if (length == 0) continue;
unsigned int bank, address;
unsigned bank, address;
char symbol[length];
if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) {
bank &= 0x1FF;
if (!gb->bank_symbols[bank]) {
gb->bank_symbols[bank] = GB_map_alloc();
}
GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol);
if (allocated_symbol) {
GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol);
}
if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) {
GB_debugger_add_symbol(gb, bank, address, symbol);
}
}
free(line);
@ -2007,13 +2398,13 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
void GB_debugger_clear_symbols(GB_gameboy_t *gb)
{
for (int i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) {
for (unsigned i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) {
if (gb->bank_symbols[i]) {
GB_map_free(gb->bank_symbols[i]);
gb->bank_symbols[i] = 0;
}
}
for (int i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) {
for (unsigned i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) {
while (gb->reversed_symbol_map.buckets[i]) {
GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next;
free(gb->reversed_symbol_map.buckets[i]);

View File

@ -7,13 +7,15 @@
#ifdef GB_INTERNAL
#ifdef DISABLE_DEBUGGER
#ifdef GB_DISABLE_DEBUGGER
#define GB_debugger_run(gb) (void)0
#define GB_debugger_handle_async_commands(gb) (void)0
#define GB_debugger_ret_hook(gb) (void)0
#define GB_debugger_call_hook(gb, addr) (void)addr
#define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value)
#define GB_debugger_test_read_watchpoint(gb, addr) (void)addr
#define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol)
#else
void GB_debugger_run(GB_gameboy_t *gb);
void GB_debugger_handle_async_commands(GB_gameboy_t *gb);
@ -22,7 +24,8 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb);
void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr);
const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr);
#endif /* DISABLE_DEBUGGER */
void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol);
#endif /* GB_DISABLE_DEBUGGER */
#endif
#ifdef GB_INTERNAL
@ -31,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
void
#endif
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);

View File

@ -27,7 +27,7 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo)
static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x)
{
if (!flip_x) {
#pragma unroll
UNROLL
for (unsigned i = 8; i--;) {
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
(lower >> 7) | ((upper >> 7) << 1),
@ -43,7 +43,7 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint
}
}
else {
#pragma unroll
UNROLL
for (unsigned i = 8; i--;) {
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
(lower & 1) | ((upper & 1) << 1),
@ -70,7 +70,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe
uint8_t flip_xor = flip_x? 0: 0x7;
#pragma unroll
UNROLL
for (unsigned i = 8; i--;) {
uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1);
GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)];
@ -99,6 +99,8 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe
#define LINE_LENGTH (456)
#define LINES (144)
#define WIDTH (160)
#define BORDERED_WIDTH 256
#define BORDERED_HEIGHT 224
#define FRAME_LENGTH (LCDC_PERIOD)
#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154
@ -109,22 +111,12 @@ typedef struct __attribute__((packed)) {
uint8_t flags;
} GB_object_t;
static bool window_enabled(GB_gameboy_t *gb)
{
if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
if (!gb->cgb_mode && GB_is_cgb(gb)) {
return false;
}
}
return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167;
}
static void display_vblank(GB_gameboy_t *gb)
{
gb->vblank_just_occured = true;
/* TODO: Slow in trubo mode! */
if (GB_is_sgb(gb)) {
/* TODO: Slow in turbo mode! */
if (GB_is_hle_sgb(gb)) {
GB_sgb_render(gb);
}
@ -134,25 +126,85 @@ static void display_vblank(GB_gameboy_t *gb)
}
}
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80;
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) {
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
if (gb->sgb) {
uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0x3 : 0x0;
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb->sgb->screen_buffer[i] = color;
if (!GB_is_sgb(gb)) {
uint32_t color = 0;
if (GB_is_cgb(gb)) {
color = GB_convert_rgb15(gb, 0x7FFF, false);
}
else {
color = is_ppu_stopped ?
gb->background_palettes_rgb[0] :
gb->background_palettes_rgb[4];
}
if (gb->border_mode == GB_BORDER_ALWAYS) {
for (unsigned y = 0; y < LINES; y++) {
for (unsigned x = 0; x < WIDTH; x++) {
gb ->screen[x + y * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH] = color;
}
}
}
else {
uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ?
gb->rgb_encode_callback(gb, 0, 0, 0) :
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb ->screen[i] = color;
}
}
}
}
if (gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) {
GB_borrow_sgb_border(gb);
uint32_t border_colors[16 * 4];
if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) {
static uint16_t colors[] = {
0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648,
0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C,
0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964,
};
unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0;
gb->borrowed_border.palette[0] = colors[index];
gb->borrowed_border.palette[10] = colors[5 + index];
gb->borrowed_border.palette[14] = colors[10 + index];
}
for (unsigned i = 0; i < 16 * 4; i++) {
border_colors[i] = GB_convert_rgb15(gb, gb->borrowed_border.palette[i], true);
}
for (unsigned tile_y = 0; tile_y < 28; tile_y++) {
for (unsigned tile_x = 0; tile_x < 32; tile_x++) {
if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) {
continue;
}
uint16_t tile = gb->borrowed_border.map[tile_x + tile_y * 32];
uint8_t flip_x = (tile & 0x4000)? 0x7 : 0;
uint8_t flip_y = (tile & 0x8000)? 0x7 : 0;
uint8_t palette = (tile >> 10) & 3;
for (unsigned y = 0; y < 8; y++) {
for (unsigned x = 0; x < 8; x++) {
uint8_t color = gb->borrowed_border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF;
uint32_t *output = gb->screen + tile_x * 8 + x + (tile_y * 8 + y) * 256;
if (color == 0) {
*output = border_colors[0];
}
else {
*output = border_colors[color + palette * 16];
}
}
}
}
}
}
GB_handle_rumble(gb);
if (gb->vblank_callback) {
gb->vblank_callback(gb);
}
GB_timing_sync(gb);
}
@ -163,29 +215,68 @@ static inline uint8_t scale_channel(uint8_t x)
static inline uint8_t scale_channel_with_curve(uint8_t x)
{
return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255,}[x];
return (uint8_t[]){0,5,8,11,16,22,28,36,43,51,59,67,77,87,97,107,119,130,141,153,166,177,188,200,209,221,230,238,245,249,252,255}[x];
}
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
static inline uint8_t scale_channel_with_curve_agb(uint8_t x)
{
return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x];
}
static inline uint8_t scale_channel_with_curve_sgb(uint8_t x)
{
return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x];
}
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
{
uint8_t r = (color) & 0x1F;
uint8_t g = (color >> 5) & 0x1F;
uint8_t b = (color >> 10) & 0x1F;
if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED) {
if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED || (for_border && !gb->has_sgb_border)) {
r = scale_channel(r);
g = scale_channel(g);
b = scale_channel(b);
}
else {
r = scale_channel_with_curve(r);
g = scale_channel_with_curve(g);
b = scale_channel_with_curve(b);
if (GB_is_sgb(gb) || for_border) {
return gb->rgb_encode_callback(gb,
scale_channel_with_curve_sgb(r),
scale_channel_with_curve_sgb(g),
scale_channel_with_curve_sgb(b));
}
bool agb = gb->model == GB_MODEL_AGB;
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b);
if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) {
uint8_t new_g = (g * 3 + b) / 4;
uint8_t new_r = r, new_b = b;
if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
uint8_t new_r, new_g, new_b;
if (agb) {
new_g = (g * 6 + b * 1) / 7;
}
else {
new_g = (g * 3 + b) / 4;
}
new_r = r;
new_b = b;
if (gb->color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) {
r = new_r;
g = new_r;
b = new_r;
new_r = new_r * 7 / 8 + ( g + b) / 16;
new_g = new_g * 7 / 8 + (r + b) / 16;
new_b = new_b * 7 / 8 + (r + g ) / 16;
new_r = new_r * (224 - 32) / 255 + 32;
new_g = new_g * (220 - 36) / 255 + 36;
new_b = new_b * (216 - 40) / 255 + 40;
}
else if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
uint8_t old_max = MAX(r, MAX(g, b));
uint8_t new_max = MAX(new_r, MAX(new_g, new_b));
@ -201,7 +292,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
if (new_min != 0xff) {
new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min);
new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min);
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);;
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);
}
}
r = new_r;
@ -219,7 +310,7 @@ void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index
uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data;
uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8);
(background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color);
(background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color, false);
}
void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode)
@ -302,13 +393,11 @@ void GB_lcd_off(GB_gameboy_t *gb)
gb->vram_write_blocked = false;
gb->cgb_palettes_blocked = false;
/* Reset window rendering state */
gb->wy_diff = 0;
gb->window_disabled_while_active = false;
gb->current_line = 0;
gb->ly_for_comparison = 0;
gb->accessed_oam_row = -1;
gb->wy_triggered = false;
}
static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
@ -320,6 +409,10 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
return;
}
if (gb->oam_ppu_blocked) {
return;
}
/* This reverse sorts the visible objects by location and priority */
GB_object_t *objects = (GB_object_t *) &gb->oam;
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
@ -344,30 +437,30 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
bool draw_oam = false;
bool bg_enabled = true, bg_priority = false;
if (!gb->bg_fifo_paused) {
if (fifo_size(&gb->bg_fifo)) {
fifo_item = fifo_pop(&gb->bg_fifo);
bg_priority = fifo_item->bg_priority;
}
if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) {
if (fifo_size(&gb->oam_fifo)) {
oam_fifo_item = fifo_pop(&gb->oam_fifo);
/* Todo: Verify access timings */
if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) {
draw_oam = true;
bg_priority |= oam_fifo_item->bg_priority;
}
}
}
if (!fifo_item) return;
/* Drop pixels for scrollings */
if (gb->position_in_line >= 160 || gb->disable_rendering) {
if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) {
gb->position_in_line++;
return;
}
if (gb->bg_fifo_paused) return;
/* Mixing */
/* Todo: Verify access timings */
if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
if (gb->cgb_mode) {
bg_priority = false;
@ -376,8 +469,16 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
bg_enabled = false;
}
}
if (!GB_is_cgb(gb) && gb->in_window) {
bg_enabled = true;
uint8_t icd_pixel = 0;
uint32_t *dest = NULL;
if (!gb->sgb) {
if (gb->border_mode != GB_BORDER_ALWAYS) {
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
}
else {
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
}
}
{
@ -390,11 +491,19 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
if (gb->sgb) {
if (gb->current_lcd_line < LINES) {
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel;
gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
}
}
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
}
}
else if (gb->cgb_palettes_ppu_blocked) {
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
}
else {
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel];
*dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel];
}
}
@ -406,15 +515,32 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
if (gb->sgb) {
if (gb->current_lcd_line < LINES) {
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel;
gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
}
}
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
//gb->icd_pixel_callback(gb, pixel);
}
}
else if (gb->cgb_palettes_ppu_blocked) {
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
}
else {
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
*dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
}
}
if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
gb->icd_pixel_callback(gb, icd_pixel);
}
}
gb->position_in_line++;
gb->lcd_x++;
gb->window_is_being_fetched = false;
}
/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have
@ -424,7 +550,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
static inline uint8_t fetcher_y(GB_gameboy_t *gb)
{
return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]);
return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY];
}
static void advance_fetcher_state_machine(GB_gameboy_t *gb)
@ -444,36 +570,52 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
GB_FETCHER_GET_TILE_DATA_LOWER,
GB_FETCHER_SLEEP,
GB_FETCHER_GET_TILE_DATA_HIGH,
GB_FETCHER_SLEEP,
GB_FETCHER_PUSH,
GB_FETCHER_PUSH,
};
switch (fetcher_state_machine[gb->fetcher_state]) {
switch (fetcher_state_machine[gb->fetcher_state & 7]) {
case GB_FETCHER_GET_TILE: {
uint16_t map = 0x1800;
if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) {
gb->wx_triggered = false;
gb->wx166_glitch = false;
}
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) {
if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->wx_triggered) {
map = 0x1C00;
}
else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) {
else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->wx_triggered) {
map = 0x1C00;
}
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
uint8_t y = fetcher_y(gb);
uint8_t x = 0;
if (gb->wx_triggered) {
x = gb->window_tile_x;
}
else {
x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F;
}
if (gb->model > GB_MODEL_CGB_C) {
/* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */
gb->fetcher_y = y;
}
gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32];
gb->last_tile_index_address = map + x + y / 8 * 32;
gb->current_tile = gb->vram[gb->last_tile_index_address];
if (gb->vram_ppu_blocked) {
gb->current_tile = 0xFF;
}
if (GB_is_cgb(gb)) {
/* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
This probably means the CGB has a 16-bit data bus for the VRAM. */
gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000];
gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000];
if (gb->vram_ppu_blocked) {
gb->current_tile_attributes = 0xFF;
}
}
gb->fetcher_x++;
gb->fetcher_x &= 0x1f;
}
gb->fetcher_state++;
break;
@ -498,6 +640,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
}
gb->current_tile_data[0] =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[0] = 0xFF;
}
}
gb->fetcher_state++;
break;
@ -523,19 +668,34 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
if (gb->current_tile_attributes & 0x40) {
y_flip = 0x7;
}
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1;
gb->current_tile_data[1] =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1];
gb->vram[gb->last_tile_data_address];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[1] = 0xFF;
}
}
if (gb->wx_triggered) {
gb->window_tile_x++;
gb->window_tile_x &= 0x1f;
}
gb->fetcher_state++;
break;
// fallthrough
case GB_FETCHER_PUSH: {
if (gb->fetcher_state == 6) {
/* The background map index increase at this specific point. If this state is not reached,
it will simply not increase. */
gb->fetcher_x++;
gb->fetcher_x &= 0x1f;
}
if (gb->fetcher_state < 7) {
gb->fetcher_state++;
}
if (fifo_size(&gb->bg_fifo) > 0) break;
fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1],
gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20);
gb->bg_fifo_paused = false;
gb->oam_fifo_paused = false;
gb->fetcher_state++;
gb->fetcher_state = 0;
}
break;
@ -545,8 +705,30 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
}
break;
}
}
gb->fetcher_state &= 7;
static uint16_t get_object_line_address(GB_gameboy_t *gb, const GB_object_t *object)
{
/* TODO: what does the PPU read if DMA is active? */
if (gb->oam_ppu_blocked) {
static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF};
object = &blocked;
}
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
if (object->flags & 0x40) { /* Flip Y */
tile_y ^= height_16? 0xF : 7;
}
/* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */
uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2;
if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
line_address += 0x2000;
}
return line_address;
}
/*
@ -555,6 +737,15 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
*/
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
{
/* The PPU does not advance while in STOP mode on the DMG */
if (gb->stopped && !GB_is_cgb(gb)) {
gb->cycles_in_stop_mode += cycles;
if (gb->cycles_in_stop_mode >= LCDC_PERIOD) {
gb->cycles_in_stop_mode -= LCDC_PERIOD;
display_vblank(gb);
}
return;
}
GB_object_t *objects = (GB_object_t *) &gb->oam;
GB_STATE_MACHINE(gb, display, cycles, 2) {
@ -595,23 +786,38 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_STATE(gb, display, 36);
GB_STATE(gb, display, 37);
GB_STATE(gb, display, 38);
GB_STATE(gb, display, 39);
GB_STATE(gb, display, 40);
GB_STATE(gb, display, 41);
GB_STATE(gb, display, 42);
}
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
while (true) {
GB_SLEEP(gb, display, 1, LCDC_PERIOD);
display_vblank(gb);
gb->cgb_repeated_a_frame = true;
}
return;
}
gb->is_odd_frame = false;
if (!GB_is_cgb(gb)) {
GB_SLEEP(gb, display, 23, 1);
}
/* Handle mode 2 on the very first line 0 */
gb->current_line = 0;
gb->window_y = -1;
/* Todo: verify timings */
if (gb->io_registers[GB_IO_WY] == 0) {
gb->wy_triggered = true;
}
else {
gb->wy_triggered = false;
}
gb->ly_for_comparison = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->mode_for_interrupt = -1;
@ -648,17 +854,25 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_SLEEP(gb, display, 37, 2);
gb->cgb_palettes_blocked = true;
gb->cycles_for_line += 2;
GB_SLEEP(gb, display, 38, 2);
gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3;
GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3);
gb->vram_read_blocked = true;
gb->vram_write_blocked = true;
gb->wx_triggered = false;
gb->wx166_glitch = false;
goto mode_3_start;
while (true) {
/* Lines 0 - 143 */
gb->window_y = -1;
for (; gb->current_line < LINES; gb->current_line++) {
/* Todo: verify timings */
if ((gb->io_registers[GB_IO_WY] == gb->current_line ||
(gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) {
gb->wy_triggered = true;
}
gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed;
gb->accessed_oam_row = 0;
@ -696,7 +910,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
if (GB_is_cgb(gb)) {
add_object_from_index(gb, gb->oam_search_index);
/* The CGB does not care about the accessed OAM row as there's no OAM bug*/
/* The CGB does not care about the accessed OAM row as there's no OAM bug */
}
GB_SLEEP(gb, display, 8, 2);
if (!GB_is_cgb(gb)) {
@ -744,20 +958,80 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
/* Todo: find out actual access time of SCX */
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
gb->current_lcd_line++; // Todo: unverified timing
if (gb->current_lcd_line == LINES) {
display_vblank(gb);
}
gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f;
gb->lcd_x = 0;
gb->fetcher_x = 0;
gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7);
/* The actual rendering cycle */
gb->fetcher_state = 0;
gb->bg_fifo_paused = false;
gb->oam_fifo_paused = false;
gb->in_window = false;
while (true) {
/* Handle window */
/* TODO: It appears that WX checks if the window begins *next* pixel, not *this* pixel. For this reason,
WX=167 has no effect at all (It checks if the PPU X position is 161, which never happens) and WX=166
has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at
that point. The code should be updated to represent this, and this will fix the time travel hack in
WX's access conflict code. */
if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) {
bool should_activate_window = false;
if (gb->io_registers[GB_IO_WX] == 0) {
static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14};
if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) {
should_activate_window = true;
}
}
else if (gb->wx166_glitch) {
static const uint8_t scx_to_wx166_comparisons[] = {-8, -9, -10, -11, -12, -13, -14, -15};
if (gb->position_in_line == scx_to_wx166_comparisons[gb->io_registers[GB_IO_SCX] & 7]) {
should_activate_window = true;
}
}
else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) {
if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) {
should_activate_window = true;
}
else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) {
should_activate_window = true;
/* LCD-PPU horizontal desync! It only appears to happen on DMGs, but not all of them.
This doesn't seem to be CPU revision dependent, but most revisions */
if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY && !GB_is_sgb(gb)) {
if (gb->lcd_x > 0) {
gb->lcd_x--;
}
}
}
}
if (should_activate_window) {
gb->window_y++;
/* TODO: Verify fetcher access timings in this case */
if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) {
gb->cycles_for_line++;
GB_SLEEP(gb, display, 42, 1);
}
gb->wx_triggered = true;
gb->window_tile_x = 0;
fifo_clear(&gb->bg_fifo);
gb->fetcher_state = 0;
gb->window_is_being_fetched = true;
}
else if (!GB_is_cgb(gb) && gb->io_registers[GB_IO_WX] == 166 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) {
gb->window_y++;
}
}
/* TODO: What happens when WX=0? */
if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched &&
gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) ) {
// Insert a pixel right at the FIFO's end
gb->bg_fifo.read_end--;
gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1;
gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,};
gb->window_is_being_fetched = false;
}
/* Handle objects */
/* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB.
On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */
@ -767,14 +1041,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) {
gb->n_visible_objs--;
}
gb->during_object_fetch = true;
while (gb->n_visible_objs != 0 &&
(gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) {
while (gb->fetcher_state < 5) {
while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) {
advance_fetcher_state_machine(gb);
gb->cycles_for_line++;
GB_SLEEP(gb, display, 27, 1);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
}
/* Todo: Measure if penalty occurs before or after waiting for the fetcher. */
@ -783,56 +1062,63 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0;
GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0);
gb->extra_penalty_for_sprite_at_0 = 0;
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
}
}
gb->cycles_for_line += 6;
GB_SLEEP(gb, display, 20, 6);
/* TODO: what does the PPU read if DMA is active? */
GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
/* TODO: Can this be deleted? { */
advance_fetcher_state_machine(gb);
gb->cycles_for_line++;
GB_SLEEP(gb, display, 41, 1);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
/* } */
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
advance_fetcher_state_machine(gb);
if (object->flags & 0x40) { /* Flip Y */
tile_y ^= height_16? 0xF : 7;
gb->cycles_for_line += 3;
GB_SLEEP(gb, display, 20, 3);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
/* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */
uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2;
gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]);
if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
line_address += 0x2000;
gb->cycles_for_line++;
GB_SLEEP(gb, display, 39, 1);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
gb->during_object_fetch = false;
gb->cycles_for_line++;
GB_SLEEP(gb, display, 40, 1);
const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
uint16_t line_address = get_object_line_address(gb, object);
uint8_t palette = (object->flags & 0x10) ? 1 : 0;
if (gb->cgb_mode) {
palette = object->flags & 0x7;
}
fifo_overlay_object_row(&gb->oam_fifo,
gb->vram[line_address],
gb->vram[line_address + 1],
gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address],
gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1],
palette,
object->flags & 0x80,
gb->cgb_mode? gb->visible_objs[gb->n_visible_objs - 1] : 0,
gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0,
object->flags & 0x20);
gb->n_visible_objs--;
}
/* Handle window */
/* Todo: Timing (Including penalty and access timings) not verified by test ROM */
if (!gb->in_window && window_enabled(gb) &&
gb->current_line >= gb->io_registers[GB_IO_WY] + gb->wy_diff &&
(uint8_t)(gb->position_in_line + 7) == gb->io_registers[GB_IO_WX]) {
gb->in_window = true;
fifo_clear(&gb->bg_fifo);
gb->bg_fifo_paused = true;
gb->oam_fifo_paused = true;
gb->fetcher_x = 0;
gb->fetcher_state = 0;
}
abort_fetching_object:
gb->object_fetch_aborted = false;
gb->during_object_fetch = false;
render_pixel_if_possible(gb);
advance_fetcher_state_machine(gb);
@ -842,6 +1128,29 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_SLEEP(gb, display, 21, 1);
}
while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) {
/* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */
uint32_t *dest = NULL;
if (gb->border_mode != GB_BORDER_ALWAYS) {
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
}
else {
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
}
*dest = gb->background_palettes_rgb[0];
gb->lcd_x++;
}
/* TODO: Verify timing */
if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) {
gb->wx166_glitch = true;
}
else {
gb->wx166_glitch = false;
}
gb->wx_triggered = false;
if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) {
gb->cycles_for_line++;
GB_SLEEP(gb, display, 30, 1);
@ -884,8 +1193,18 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
}
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
gb->mode_for_interrupt = 2;
// Todo: unverified timing
gb->current_lcd_line++;
if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
display_vblank(gb);
}
if (gb->icd_hreset_callback) {
gb->icd_hreset_callback(gb);
}
}
gb->wx166_glitch = false;
/* Lines 144 - 152 */
for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) {
gb->io_registers[GB_IO_LY] = gb->current_line;
@ -909,12 +1228,30 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_STAT_update(gb);
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
if (GB_is_cgb(gb)) {
GB_timing_sync(gb);
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
}
else {
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
gb->is_odd_frame ^= true;
display_vblank(gb);
}
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
}
else {
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
gb->is_odd_frame ^= true;
display_vblank(gb);
}
if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) {
gb->cgb_repeated_a_frame = true;
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
else {
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
display_vblank(gb);
gb->cgb_repeated_a_frame = false;
}
}
}
@ -947,11 +1284,21 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_SLEEP(gb, display, 17, LINE_LENGTH - 24);
/* Reset window rendering state */
gb->wy_diff = 0;
gb->window_disabled_while_active = false;
gb->current_line = 0;
gb->current_lcd_line = -1; // TODO: not the correct timing
/* Todo: verify timings */
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
(gb->io_registers[GB_IO_WY] == 0)) {
gb->wy_triggered = true;
}
else {
gb->wy_triggered = false;
}
// TODO: not the correct timing
gb->current_lcd_line = 0;
if (gb->icd_vreset_callback) {
gb->icd_vreset_callback(gb);
}
}
}
@ -1084,7 +1431,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h
GB_object_t *sprite = (GB_object_t *) &gb->oam;
uint8_t sprites_in_line = 0;
for (uint8_t i = 0; i < 40; i++, sprite++) {
int sprite_y = sprite->y - 16;
signed sprite_y = sprite->y - 16;
bool obscured = false;
// Is sprite not in this line?
if (sprite_y > y || sprite_y + *sprite_height <= y) continue;
@ -1117,7 +1464,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h
}
for (unsigned y = 0; y < *sprite_height; y++) {
#pragma unroll
UNROLL
for (unsigned x = 0; x < 8; x++) {
uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) |
((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 );
@ -1133,30 +1480,8 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h
return count;
}
/* Called when a write might enable or disable the window */
void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value)
{
bool before = window_enabled(gb);
gb->io_registers[addr] = value;
bool after = window_enabled(gb);
if (before != after && gb->current_line < LINES) {
/* Window was disabled or enabled outside of vblank */
if (gb->current_line >= gb->io_registers[GB_IO_WY]) {
if (after) {
if (!gb->window_disabled_while_active) {
/* Window was turned on for the first time this frame while LY > WY,
should start window in the next line */
gb->wy_diff = gb->current_line + 1 - gb->io_registers[GB_IO_WY];
}
else {
gb->wy_diff += gb->current_line;
}
}
else {
gb->wy_diff -= gb->current_line;
gb->window_disabled_while_active = true;
}
}
}
bool GB_is_odd_frame(GB_gameboy_t *gb)
{
return gb->is_odd_frame;
}

View File

@ -8,9 +8,15 @@
#ifdef GB_INTERNAL
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles);
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value);
void GB_STAT_update(GB_gameboy_t *gb);
void GB_lcd_off(GB_gameboy_t *gb);
enum {
GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility
GB_OBJECT_PRIORITY_X,
GB_OBJECT_PRIORITY_INDEX,
};
#endif
typedef enum {
@ -44,11 +50,13 @@ typedef enum {
GB_COLOR_CORRECTION_CORRECT_CURVES,
GB_COLOR_CORRECTION_EMULATE_HARDWARE,
GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS,
GB_COLOR_CORRECTION_REDUCE_CONTRAST,
} GB_color_correction_mode_t;
void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index);
void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type);
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height);
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color);
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border);
void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode);
bool GB_is_odd_frame(GB_gameboy_t *gb);
#endif /* display_h */

920
Core/gb.c

File diff suppressed because it is too large Load Diff

204
Core/gb.h
View File

@ -1,5 +1,6 @@
#ifndef GB_h
#define GB_h
#define typeof __typeof__
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
@ -20,15 +21,27 @@
#include "sm83_cpu.h"
#include "symbol_hash.h"
#include "sgb.h"
#include "cheats.h"
#include "rumble.h"
#define GB_STRUCT_VERSION 13
#ifdef GB_INTERNAL
#define GB_MODEL_FAMILY_MASK 0xF00
#define GB_MODEL_DMG_FAMILY 0x000
#define GB_MODEL_MGB_FAMILY 0x100
#define GB_MODEL_CGB_FAMILY 0x200
#define GB_MODEL_PAL_BIT 0x1000
#define GB_MODEL_NO_SFC_BIT 0x2000
#ifdef GB_INTERNAL
#if __clang__
#define UNROLL _Pragma("unroll")
#elif __GNUC__ >= 8
#define UNROLL _Pragma("GCC unroll 8")
#else
#define UNROLL
#endif
#endif
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
@ -39,6 +52,17 @@
#error Unable to detect endianess
#endif
typedef struct {
struct {
uint8_t r, g, b;
} colors[5];
} GB_palette_t;
extern const GB_palette_t GB_PALETTE_GREY;
extern const GB_palette_t GB_PALETTE_DMG;
extern const GB_palette_t GB_PALETTE_MGB;
extern const GB_palette_t GB_PALETTE_GBL;
typedef union {
struct {
uint8_t seconds;
@ -50,7 +74,6 @@ typedef union {
uint8_t data[5];
} GB_rtc_time_t;
typedef enum {
// GB_MODEL_DMG_0 = 0x000,
// GB_MODEL_DMG_A = 0x001,
@ -58,9 +81,13 @@ typedef enum {
// GB_MODEL_DMG_C = 0x003,
GB_MODEL_SGB = 0x004,
GB_MODEL_SGB_NTSC = GB_MODEL_SGB,
GB_MODEL_SGB_PAL = 0x1004,
GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT,
GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT,
GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC,
GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT,
// GB_MODEL_MGB = 0x100,
GB_MODEL_SGB2 = 0x101,
GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT,
// GB_MODEL_CGB_0 = 0x200,
// GB_MODEL_CGB_A = 0x201,
// GB_MODEL_CGB_B = 0x202,
@ -83,10 +110,16 @@ enum {
enum {
GB_CARRY_FLAG = 16,
GB_HALF_CARRY_FLAG = 32,
GB_SUBSTRACT_FLAG = 64,
GB_SUBTRACT_FLAG = 64,
GB_ZERO_FLAG = 128,
};
typedef enum {
GB_BORDER_SGB,
GB_BORDER_NEVER,
GB_BORDER_ALWAYS,
} GB_border_mode_t;
#define GB_MAX_IR_QUEUE 256
enum {
@ -154,7 +187,7 @@ enum {
// Unfortunately it is not readable or writable after boot has finished, so research of this
// register is quite limited. The value written to this register, however, can be controlled
// in some cases.
GB_IO_DMG_EMULATION = 0x4c,
GB_IO_KEY0 = 0x4c,
/* General CGB features */
GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
@ -162,7 +195,7 @@ enum {
/* Missing */
GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank
GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping
GB_IO_BANK = 0x50, // Write to disable the BIOS mapping
/* CGB DMA */
GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High
@ -181,9 +214,7 @@ enum {
GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index
GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data
// 1 is written for DMG ROMs on a CGB. Does not appear to have an effect.
GB_IO_DMG_EMULATION_INDICATION = 0x6c, // (FEh) Bit 0 (Read/Write)
GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based)
/* Missing */
@ -204,6 +235,17 @@ typedef enum {
GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE
} GB_log_attributes;
typedef enum {
GB_BOOT_ROM_DMG0,
GB_BOOT_ROM_DMG,
GB_BOOT_ROM_MGB,
GB_BOOT_ROM_SGB,
GB_BOOT_ROM_SGB2,
GB_BOOT_ROM_CGB0,
GB_BOOT_ROM_CGB,
GB_BOOT_ROM_AGB,
} GB_boot_rom_t;
#ifdef GB_INTERNAL
#define LCDC_PERIOD 70224
#define CPU_FREQUENCY 0x400000
@ -213,11 +255,11 @@ typedef enum {
#define INTERNAL_DIV_CYCLES (0x40000)
#if !defined(MIN)
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
#endif
#if !defined(MAX)
#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
#define MAX(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
#endif
#endif
@ -225,14 +267,20 @@ typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update);
typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on);
typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update);
typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude);
typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send);
typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value);
typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row);
typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type);
typedef struct {
bool state;
long delay;
uint64_t delay;
} GB_ir_queue_item_t;
struct GB_breakpoint_s;
@ -258,10 +306,6 @@ typedef struct {
This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64
bit platforms. */
/* We make sure bool is 1 for cross-platform save state compatibility. */
/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */
_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1");
#ifdef GB_INTERNAL
struct GB_gameboy_s {
#else
@ -323,6 +367,7 @@ struct GB_gameboy_internal_s {
bool infrared_input;
GB_printer_t printer;
uint8_t extra_oam[0xff00 - 0xfea0];
uint32_t ram_size; // Different between CGB and DMG
);
/* DMA and HDMA */
@ -360,9 +405,8 @@ struct GB_gameboy_internal_s {
} mbc2;
struct {
uint8_t rom_bank:7;
uint8_t padding:1;
uint8_t ram_bank:4;
uint8_t rom_bank:8;
uint8_t ram_bank:3;
} mbc3;
struct {
@ -374,18 +418,30 @@ struct GB_gameboy_internal_s {
struct {
uint8_t bank_low:6;
uint8_t bank_high:3;
uint8_t mode:1;
bool mode:1;
bool ir_mode:1;
} huc1;
struct {
uint8_t rom_bank;
uint8_t ram_bank;
uint8_t rom_bank:7;
uint8_t padding:1;
uint8_t ram_bank:4;
} huc3;
};
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
bool camera_registers_mapped;
uint8_t camera_registers[0x36];
bool rumble_state;
bool cart_ir;
// TODO: move to huc3 struct when breaking save compat
uint8_t huc3_mode;
uint8_t huc3_access_index;
uint16_t huc3_minutes, huc3_days;
uint16_t huc3_alarm_minutes, huc3_alarm_days;
bool huc3_alarm_enabled;
uint8_t huc3_read;
uint8_t huc3_access_flags;
);
@ -429,10 +485,11 @@ struct GB_gameboy_internal_s {
uint8_t position_in_line;
bool stat_interrupt_line;
uint8_t effective_scx;
uint8_t wy_diff;
uint8_t window_y;
/* The LCDC will skip the first frame it renders after turning it on.
On the CGB, a frame is not skipped if the previous frame was skipped as well.
See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */
/* TODO: Drop this and properly emulate the dropped vreset signal*/
enum {
GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state,
@ -445,7 +502,7 @@ struct GB_gameboy_internal_s {
bool vram_read_blocked;
bool oam_write_blocked;
bool vram_write_blocked;
bool window_disabled_while_active;
bool fifo_insertion_glitch;
uint8_t current_line;
uint16_t ly_for_comparison;
GB_fifo_t bg_fifo, oam_fifo;
@ -456,9 +513,9 @@ struct GB_gameboy_internal_s {
uint8_t current_tile_attributes;
uint8_t current_tile_data[2];
uint8_t fetcher_state;
bool bg_fifo_paused;
bool oam_fifo_paused;
bool in_window;
bool window_is_being_fetched;
bool wx166_glitch;
bool wx_triggered;
uint8_t visible_objs[10];
uint8_t obj_comparators[10];
uint8_t n_visible_objs;
@ -469,6 +526,21 @@ struct GB_gameboy_internal_s {
bool lyc_interrupt_line;
bool cgb_palettes_blocked;
uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases.
uint32_t cycles_in_stop_mode;
uint8_t object_priority;
bool oam_ppu_blocked;
bool vram_ppu_blocked;
bool cgb_palettes_ppu_blocked;
bool object_fetch_aborted;
bool during_object_fetch;
uint16_t object_low_line_address;
bool wy_triggered;
uint8_t window_tile_x;
uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases.
bool is_odd_frame;
uint16_t last_tile_data_address;
uint16_t last_tile_index_address;
bool cgb_repeated_a_frame;
);
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
@ -482,6 +554,7 @@ struct GB_gameboy_internal_s {
GB_STANDARD_MBC1_WIRING,
GB_MBC1M_WIRING,
} mbc1_wiring;
bool is_mbc30;
unsigned pending_cycles;
@ -494,8 +567,13 @@ struct GB_gameboy_internal_s {
uint32_t *screen;
uint32_t background_palettes_rgb[0x20];
uint32_t sprite_palettes_rgb[0x20];
const GB_palette_t *dmg_palette;
GB_color_correction_mode_t color_correction_mode;
bool keys[4][GB_KEY_MAX];
GB_border_mode_t border_mode;
GB_sgb_border_t borrowed_border;
bool tried_loading_sgb_border;
bool has_sgb_border;
/* Timing */
uint64_t last_sync;
@ -517,10 +595,17 @@ struct GB_gameboy_internal_s {
GB_rumble_callback_t rumble_callback;
GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback;
GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback;
GB_update_input_hint_callback_t update_input_hint_callback;
GB_joyp_write_callback_t joyp_write_callback;
GB_icd_pixel_callback_t icd_pixel_callback;
GB_icd_vreset_callback_t icd_hreset_callback;
GB_icd_vreset_callback_t icd_vreset_callback;
GB_read_memory_callback_t read_memory_callback;
GB_boot_rom_load_callback_t boot_rom_load_callback;
GB_print_image_callback_t printer_callback;
/* IR */
long cycles_since_ir_change; // In 8MHz units
long cycles_since_input_ir_change; // In 8MHz units
uint64_t cycles_since_ir_change; // In 8MHz units
uint64_t cycles_since_input_ir_change; // In 8MHz units
GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE];
size_t ir_queue_length;
@ -531,18 +616,18 @@ struct GB_gameboy_internal_s {
/* Breakpoints */
uint16_t n_breakpoints;
struct GB_breakpoint_s *breakpoints;
bool has_jump_to_breakpoints;
bool has_jump_to_breakpoints, has_software_breakpoints;
void *nontrivial_jump_state;
bool non_trivial_jump_breakpoint_occured;
/* SLD (Todo: merge with backtrace) */
bool stack_leak_detection;
int debug_call_depth;
signed debug_call_depth;
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
uint16_t addr_for_call_depth[0x200];
/* Backtrace */
unsigned int backtrace_size;
unsigned backtrace_size;
uint16_t backtrace_sps[0x200];
struct {
uint16_t bank;
@ -558,7 +643,7 @@ struct GB_gameboy_internal_s {
GB_reversed_symbol_map_t reversed_symbol_map;
/* Ticks command */
unsigned long debugger_ticks;
uint64_t debugger_ticks;
/* Rewind */
#define GB_REWIND_FRAMES_PER_KEY 255
@ -577,15 +662,26 @@ struct GB_gameboy_internal_s {
double sgb_intro_sweep_phase;
double sgb_intro_sweep_previous_sample;
/* Cheats */
bool cheat_enabled;
size_t cheat_count;
GB_cheat_t **cheats;
GB_cheat_hash_t *cheat_hash[256];
/* Misc */
bool turbo;
bool turbo_dont_skip;
bool disable_rendering;
uint32_t ram_size; // Different between CGB and DMG
uint8_t boot_rom[0x900];
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
double clock_multiplier;
GB_rumble_mode_t rumble_mode;
uint32_t rumble_on_cycles;
uint32_t rumble_off_cycles;
/* Temporary state */
bool wx_just_changed;
);
};
@ -605,13 +701,14 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
void GB_init(GB_gameboy_t *gb, GB_model_t model);
bool GB_is_inited(GB_gameboy_t *gb);
bool GB_is_cgb(GB_gameboy_t *gb);
bool GB_is_sgb(GB_gameboy_t *gb);
bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2
bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd
GB_model_t GB_get_model(GB_gameboy_t *gb);
void GB_free(GB_gameboy_t *gb);
void GB_reset(GB_gameboy_t *gb);
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model);
/* Returns the time passed, in 4MHz ticks. */
/* Returns the time passed, in 8MHz ticks. */
uint8_t GB_run(GB_gameboy_t *gb);
/* Returns the time passed since the last frame, in nanoseconds */
uint64_t GB_run_frame(GB_gameboy_t *gb);
@ -642,8 +739,14 @@ void GB_set_user_data(GB_gameboy_t *gb, void *data);
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path);
void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size);
int GB_load_rom(GB_gameboy_t *gb, const char *path);
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size);
int GB_load_isx(GB_gameboy_t *gb, const char *path);
int GB_save_battery_size(GB_gameboy_t *gb);
int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size);
int GB_save_battery(GB_gameboy_t *gb, const char *path);
void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size);
void GB_load_battery(GB_gameboy_t *gb, const char *path);
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip);
@ -653,9 +756,10 @@ void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);
void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4);
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode);
void GB_set_infrared_input(GB_gameboy_t *gb, bool state);
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); /* In 8MHz units*/
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change); /* In 8MHz units*/
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
@ -664,6 +768,11 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback);
void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback);
void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback);
void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback);
/* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */
void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback);
void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette);
/* These APIs are used when using internal clock */
void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback);
@ -675,14 +784,23 @@ void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data);
void GB_disconnect_serial(GB_gameboy_t *gb);
/* For cartridges with an alarm clock */
unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm
/* For integration with SFC/SNES emulators */
void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback);
void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback);
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback);
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback);
#ifdef GB_INTERNAL
uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier);
size_t GB_get_screen_width(GB_gameboy_t *gb);
size_t GB_get_screen_height(GB_gameboy_t *gb);
unsigned GB_get_screen_width(GB_gameboy_t *gb);
unsigned GB_get_screen_height(GB_gameboy_t *gb);
double GB_get_usual_frame_rate(GB_gameboy_t *gb);
unsigned GB_get_player_count(GB_gameboy_t *gb);
#endif /* GB_h */

View File

@ -0,0 +1,522 @@
static const uint16_t palette[] = {
0x410A, 0x0421, 0x35AD, 0x4A52, 0x7FFF, 0x2D49, 0x0C42, 0x1484,
0x18A5, 0x20C6, 0x6718, 0x5D6E, 0x0000, 0x0000, 0x0000, 0x0000,
};
static const uint16_t tilemap[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004,
0x0004, 0x0004, 0x0004, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0004, 0x0004, 0x0004, 0x0004,
0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000,
0x0000, 0x0006, 0x0007, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008,
0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008,
0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008,
0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x4007, 0x4006, 0x0000,
0x0000, 0x0009, 0x0008, 0x0008, 0x0008, 0x000A, 0x000B, 0x000B,
0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B,
0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B,
0x000B, 0x000B, 0x400A, 0x0008, 0x0008, 0x0008, 0xC009, 0x0000,
0x0000, 0x000C, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400C, 0x0000,
0x0000, 0x000E, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC00E, 0x0000,
0x0000, 0x000F, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400F, 0x0000,
0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000,
0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000,
0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000,
0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000,
0x0000, 0x0012, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4012, 0x0000,
0x0000, 0x0013, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC013, 0x0000,
0x0014, 0x0015, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4015, 0x4014,
0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016,
0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016,
0x0018, 0x0019, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4019, 0x4018,
0x001A, 0x001B, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC01B, 0xC01A,
0x001C, 0x001D, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x401D, 0x401C,
0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E,
0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E,
0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E,
0x001F, 0x801D, 0x0008, 0x0008, 0x0008, 0x0020, 0x0021, 0x0022,
0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A,
0x002B, 0x002C, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D,
0x002E, 0x0021, 0x4020, 0x0008, 0x0008, 0x0008, 0xC01D, 0x401F,
0x002F, 0x0030, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0031,
0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039,
0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041,
0x0042, 0x0043, 0x0008, 0x0008, 0x0008, 0x0008, 0x4030, 0x402F,
0x0044, 0x0045, 0x0046, 0x0047, 0x0008, 0x0008, 0x0048, 0x0049,
0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051,
0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
0x005A, 0x005B, 0x0008, 0x0008, 0x4047, 0x4046, 0x4045, 0x4044,
0x0000, 0x0000, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061,
0x0061, 0x0062, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063,
0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x4062, 0x0061,
0x0061, 0x4060, 0x405F, 0x405E, 0x405D, 0x405C, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
const uint8_t tiles[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1F, 0x01,
0x7F, 0x1F, 0xFF, 0x7E, 0xFF, 0xE1, 0xFF, 0x9F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x1E,
0x1F, 0x60, 0x7F, 0x80, 0xFF, 0x00, 0xBF, 0x40,
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x03, 0x01, 0x07, 0x03, 0x07, 0x03, 0x07, 0x06,
0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06,
0x01, 0x02, 0x03, 0x04, 0x03, 0x04, 0x06, 0x01,
0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01,
0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E,
0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E,
0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01,
0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00,
0x0F, 0x0E, 0x0F, 0x0E, 0x1F, 0x1B, 0x1F, 0x1B,
0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B,
0x0E, 0x01, 0x0E, 0x01, 0x1B, 0x04, 0x1B, 0x04,
0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00,
0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00,
0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B,
0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B,
0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04,
0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04,
0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B,
0x1F, 0x1B, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37,
0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04,
0x1B, 0x04, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08,
0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37,
0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37,
0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08,
0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08,
0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F,
0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F,
0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10,
0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10,
0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0xFF, 0xDF,
0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF,
0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0xDF, 0x20,
0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20,
0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF,
0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF,
0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20,
0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF,
0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0xBF,
0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20,
0xDF, 0x20, 0xDF, 0x20, 0xBF, 0x40, 0xBF, 0x40,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40,
0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40,
0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00,
0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00,
0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00,
0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00,
0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06,
0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00,
0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01,
0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06,
0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06,
0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01,
0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01,
0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01,
0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xF8, 0xFF, 0xE7, 0xF8, 0xDF, 0xE3,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x00, 0xE4, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x3F, 0xFF, 0xCE, 0x3F, 0xF5, 0x8E,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x3F, 0x00, 0x4E, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x1F, 0xFF, 0xEE, 0x1F, 0xB5, 0x4E,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x1F, 0x00, 0x0E, 0xA0,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x07, 0xFF, 0xFB, 0x07, 0x04, 0x73,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x03, 0x88,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x80, 0xFF, 0x7F, 0x80, 0x82, 0x39,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x80, 0x00, 0x01, 0x44,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0xFE,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x83, 0x7C,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x42, 0x01,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFE, 0xFF, 0xBB, 0x7C, 0x4F, 0xB0,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x00, 0xB1, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x07, 0xFF, 0xF9, 0x06, 0xE7, 0xF8,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x06, 0x00, 0x08, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x0E, 0xFF, 0xF5, 0x0E, 0x9B, 0x74,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x0E, 0x00, 0x94, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x1F, 0xFF, 0xF7, 0x0F, 0xBF, 0x47,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x0F, 0x00, 0xA7, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01,
0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
0x03, 0x04, 0x01, 0x02, 0x01, 0x02, 0x00, 0x01,
0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xDF,
0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0x7F, 0x80, 0xBF, 0x40, 0xBF, 0x40, 0xDF, 0x20,
0xB0, 0xD8, 0xA0, 0xD3, 0x67, 0x84, 0x47, 0xA4,
0x61, 0x81, 0xA0, 0xD0, 0xB4, 0xCA, 0x7E, 0x81,
0xD7, 0x08, 0xCC, 0x13, 0x98, 0x20, 0x98, 0x00,
0x9E, 0x20, 0xCF, 0x00, 0xCD, 0x02, 0x80, 0x01,
0x32, 0x2D, 0x13, 0x6D, 0x34, 0x48, 0xFC, 0x02,
0x7C, 0x00, 0x78, 0x05, 0x30, 0x49, 0x20, 0x50,
0xCD, 0x00, 0xAC, 0x40, 0x49, 0x82, 0x01, 0x02,
0x07, 0x80, 0xC2, 0x05, 0x86, 0x41, 0x9F, 0x40,
0x15, 0x2E, 0x09, 0x06, 0x09, 0x16, 0x0B, 0xD4,
0xC6, 0x49, 0x8E, 0x40, 0xCF, 0xC8, 0x06, 0x01,
0xCE, 0x20, 0xE6, 0x10, 0xE6, 0x00, 0x24, 0xD0,
0x39, 0x80, 0x38, 0x01, 0x31, 0x00, 0xF8, 0x00,
0x0C, 0x8B, 0x85, 0x8A, 0x03, 0x84, 0x27, 0x20,
0x22, 0x35, 0x12, 0x34, 0x20, 0x12, 0x10, 0x20,
0x73, 0x00, 0x72, 0x08, 0x7C, 0x80, 0xDC, 0x01,
0xC8, 0x11, 0xC9, 0x06, 0xCD, 0x22, 0xEF, 0x10,
0x83, 0x44, 0x86, 0x01, 0x03, 0x85, 0x26, 0x21,
0x46, 0x69, 0x46, 0x68, 0x8E, 0xCA, 0x86, 0x88,
0x39, 0x40, 0x78, 0x84, 0x7C, 0x80, 0xD8, 0x01,
0x90, 0x29, 0xD1, 0x28, 0x73, 0x00, 0xB3, 0x40,
0x00, 0x01, 0x01, 0x00, 0x3F, 0x00, 0x3F, 0x40,
0x03, 0x02, 0x01, 0x02, 0x41, 0x7C, 0x7F, 0x00,
0xFE, 0x00, 0xFF, 0x00, 0xC0, 0x00, 0x80, 0x40,
0xFC, 0x00, 0xFC, 0x00, 0x80, 0x02, 0xC0, 0x00,
0xC0, 0x00, 0x80, 0x4C, 0xCC, 0x43, 0x8E, 0x52,
0x80, 0x4C, 0x80, 0x00, 0x12, 0x1E, 0x9E, 0x00,
0x7F, 0x00, 0x33, 0x0C, 0x32, 0x01, 0x23, 0x50,
0x33, 0x4C, 0x7F, 0x00, 0x61, 0x80, 0xF1, 0x00,
0x7C, 0x02, 0x30, 0x48, 0x31, 0x40, 0x61, 0x50,
0x87, 0xE4, 0xE3, 0x84, 0x23, 0x44, 0x43, 0x44,
0x85, 0x42, 0x87, 0x40, 0x8F, 0x50, 0x8C, 0x12,
0x78, 0x00, 0x18, 0x20, 0xB8, 0x00, 0x98, 0x24,
0x03, 0x04, 0x03, 0xE0, 0xF1, 0x12, 0xF0, 0x09,
0xF9, 0x09, 0xF9, 0x08, 0xE1, 0x12, 0xF1, 0x12,
0xF8, 0x00, 0x1E, 0xE0, 0x0C, 0x02, 0x07, 0x08,
0x07, 0x00, 0x06, 0x00, 0x1C, 0x02, 0x0C, 0x00,
0x9F, 0x91, 0x86, 0x88, 0xC4, 0x4C, 0x80, 0x4C,
0xE1, 0x20, 0xC1, 0x22, 0x23, 0xD4, 0x22, 0xD5,
0x60, 0x00, 0xFB, 0x00, 0x37, 0x00, 0x73, 0x0C,
0x1F, 0x00, 0x3C, 0x00, 0xC8, 0x14, 0xC9, 0x14,
0x16, 0x2F, 0x76, 0x4F, 0x2D, 0xDE, 0xDD, 0xBE,
0xBA, 0x7D, 0x7A, 0xFD, 0x7A, 0xFD, 0xF4, 0xF8,
0xCF, 0x00, 0x8F, 0x00, 0x5E, 0x80, 0xBE, 0x00,
0x7D, 0x00, 0xFC, 0x00, 0xFC, 0x01, 0xF9, 0x02,
0xFF, 0x00, 0xBF, 0x78, 0x86, 0x09, 0x86, 0x89,
0x06, 0x25, 0x02, 0x25, 0x42, 0x45, 0x60, 0x11,
0x00, 0x00, 0x09, 0x00, 0x70, 0x81, 0x70, 0x09,
0xDC, 0x21, 0xD8, 0x01, 0x98, 0x25, 0xCC, 0x13,
0xFF, 0x00, 0xF3, 0xF8, 0x02, 0x03, 0x01, 0x30,
0x39, 0x09, 0x30, 0x09, 0x31, 0x09, 0x20, 0x19,
0x00, 0x00, 0x01, 0x04, 0xFC, 0x00, 0xCF, 0x30,
0xE6, 0x00, 0xE6, 0x01, 0xE6, 0x00, 0xF6, 0x08,
0xFF, 0x00, 0xFA, 0xC7, 0x18, 0x21, 0x09, 0x10,
0x88, 0x99, 0x93, 0x1A, 0x83, 0x11, 0xC2, 0x41,
0x00, 0x00, 0x00, 0x20, 0xC6, 0x21, 0xFF, 0x00,
0x67, 0x00, 0xE4, 0x08, 0x6F, 0x10, 0x3C, 0x00,
0xFD, 0x02, 0xB5, 0x3A, 0xC7, 0x44, 0x03, 0x84,
0x83, 0x24, 0x21, 0xB0, 0x21, 0x12, 0x21, 0x02,
0x02, 0x00, 0x02, 0x40, 0x3C, 0x00, 0xF8, 0x00,
0xD8, 0x24, 0x4C, 0x92, 0xEC, 0x00, 0xCC, 0x12,
0xFF, 0x00, 0xFF, 0xF3, 0x1C, 0x14, 0x0C, 0x04,
0x00, 0x0C, 0x04, 0x24, 0x00, 0x24, 0x10, 0x30,
0x00, 0x00, 0x10, 0x04, 0xE3, 0x00, 0xFB, 0x00,
0xF3, 0x08, 0xDB, 0x20, 0xDB, 0x04, 0xCF, 0x00,
0xFF, 0x00, 0xEC, 0x3E, 0xC1, 0x01, 0x01, 0x8E,
0x8F, 0x10, 0x0F, 0x90, 0x0F, 0x90, 0x0D, 0x09,
0x00, 0x00, 0x20, 0x01, 0x7E, 0x00, 0xF1, 0x0E,
0xE0, 0x10, 0x60, 0x10, 0x60, 0x10, 0x79, 0x82,
0xFF, 0x00, 0x7F, 0xFC, 0x03, 0x82, 0x01, 0x9E,
0x13, 0x80, 0x03, 0x80, 0x03, 0x9C, 0x0F, 0x90,
0x00, 0x00, 0x02, 0x00, 0x7C, 0x80, 0x60, 0x9C,
0x60, 0x9C, 0x7C, 0x80, 0x60, 0x9C, 0x70, 0x80,
0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xEF, 0xFF, 0xF7, 0x7F, 0x7B, 0x3F, 0x3C,
0x1F, 0x1F, 0x0F, 0x0F, 0x03, 0x03, 0x00, 0x00,
0xEF, 0x10, 0x77, 0x88, 0x3B, 0x44, 0x1C, 0x23,
0x0F, 0x10, 0x03, 0x0C, 0x00, 0x03, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x3F, 0xC0, 0xC3, 0x3C, 0xFC, 0x03, 0x3F, 0xC0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC1,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xC0, 0xC1, 0x3E,
0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xEA, 0x14, 0xC0, 0x00, 0x80, 0x21, 0x7F, 0x92,
0x9F, 0xE0, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x27, 0x18, 0x7F, 0x00, 0x1E, 0x61, 0x9A, 0x04,
0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x73, 0x53, 0x47, 0x44, 0x46, 0x25, 0xFD, 0x03,
0xF9, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x8C, 0x00, 0xD8, 0x20, 0x1D, 0xA0, 0x03, 0x00,
0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xC7, 0xE1, 0xE6, 0x05, 0x42, 0xA5, 0xBF, 0xC0,
0x9F, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x18, 0x24, 0x38, 0x01, 0xB8, 0x05, 0xC0, 0x00,
0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x21, 0x11, 0x31, 0x49, 0x33, 0x4A, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xDE, 0x00, 0x87, 0x48, 0x84, 0x48, 0x00, 0x00,
0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xCC, 0x02, 0x8E, 0x4A, 0xCC, 0x42, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x71, 0x08, 0x39, 0x00, 0x31, 0x02, 0x00, 0x00,
0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x3D, 0x40, 0x03, 0x02, 0x03, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xBC, 0x02, 0xFC, 0x00, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x12, 0x82, 0x80, 0x80, 0x01, 0x83, 0xFF, 0x00,
0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x61, 0x1C, 0x7F, 0x00, 0x7C, 0x82, 0x00, 0x00,
0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x22, 0x52, 0x30, 0xC0, 0x58, 0xA4, 0x8F, 0x72,
0x73, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x8C, 0x11, 0x4F, 0x90, 0xA3, 0x0C, 0x73, 0x00,
0xFC, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x23, 0xA4, 0x06, 0x0D, 0x05, 0x1B, 0xBB, 0x07,
0xE7, 0x1F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x98, 0x44, 0xF5, 0x08, 0xEB, 0x00, 0x87, 0x40,
0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x66, 0x85, 0xE2, 0xA5, 0x66, 0x81, 0xBF, 0xC1,
0x99, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x99, 0x00, 0xB9, 0x00, 0x9D, 0x20, 0xC1, 0x00,
0xE7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xF6, 0xFA, 0xFC, 0xF2, 0xF7, 0xF8, 0xFB, 0xFC,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF9, 0x00, 0xF1, 0x02, 0xF8, 0x00, 0xFC, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x52, 0x53, 0x30, 0x23, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x8C, 0x21, 0xCC, 0x13, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x03, 0x03, 0x06, 0xFE, 0x01, 0xF9, 0x07,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFD, 0x02, 0xFA, 0x04, 0x01, 0x00, 0x07, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x86, 0x05, 0x46, 0xA0, 0x5F, 0xB8, 0xBF, 0xC0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x38, 0x41, 0x99, 0x26, 0xB8, 0x00, 0xC0, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x30, 0x28, 0x09, 0x09, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xC6, 0x09, 0xE6, 0x10, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x20, 0x38, 0x38, 0x20, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xD7, 0x08, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x80, 0x41, 0xA1, 0x61, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x3E, 0x40, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x01, 0x82, 0x01, 0x82, 0xFF, 0x00, 0xFC, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x7C, 0x82, 0x7C, 0x82, 0x00, 0x00, 0x03, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x3F, 0x3F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x3C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFE, 0xFF, 0xFF, 0x3F, 0x3F, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFE, 0x01, 0x3F, 0xC0, 0x01, 0x3E, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, 0x3F, 0xC0,
0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF,
0xFF, 0xFF, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00,
0x3F, 0xC0, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFC,
0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0xFC, 0x03,
0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC,
0xFC, 0x03, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF,
};

View File

@ -0,0 +1,446 @@
static const uint16_t palette[] = {
0x7C1A, 0x0000, 0x0011, 0x3DEF, 0x6318, 0x7FFF, 0x1EBA, 0x19AF,
0x1EAF, 0x4648, 0x7FC0, 0x2507, 0x1484, 0x5129, 0x5010, 0x2095,
};
static const uint16_t tilemap[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003,
0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003,
0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003,
0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000,
0x0000, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0007, 0x0008, 0x0008,
0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008,
0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008,
0x0008, 0x0008, 0x4007, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x000A, 0x000B, 0x400A, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x800A, 0x000C, 0xC00A, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000,
0x0000, 0x000D, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x000E,
0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016,
0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E,
0x001F, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x400D, 0x0000,
0x0000, 0x0020, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0021,
0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029,
0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031,
0x0032, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4020, 0x0000,
0x0000, 0x0033, 0x0034, 0x0035, 0x0036, 0x0005, 0x0005, 0x0037,
0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
0x0040, 0x0005, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046,
0x0047, 0x0005, 0x0005, 0x4036, 0x4035, 0x4034, 0x4033, 0x0000,
0x0000, 0x0000, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D,
0x004E, 0x004E, 0x004F, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050,
0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x404F, 0x004E, 0x004E,
0x404D, 0x004C, 0x404B, 0x404A, 0x4049, 0x4048, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};
const uint8_t tiles[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08,
0x01, 0x11, 0x06, 0x26, 0x04, 0x24, 0x08, 0x48,
0x00, 0x00, 0x01, 0x01, 0x07, 0x07, 0x0F, 0x0F,
0x1E, 0x1F, 0x39, 0x3F, 0x3B, 0x3F, 0x77, 0x7F,
0x00, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x7F, 0x7F,
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF,
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x08, 0x48, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x77, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F,
0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F,
0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42,
0xBD, 0xBD, 0x7E, 0x66, 0x7E, 0xFF, 0x7E, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF,
0x7E, 0xFF, 0xFF, 0xE7, 0x7E, 0x7E, 0x7E, 0x7E,
0x7E, 0xFF, 0x3C, 0xFF, 0x81, 0xFF, 0xC3, 0xFF,
0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7E, 0x7E, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x81,
0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48,
0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F,
0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xDF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xC7,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xE3,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x9F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0xE1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xD8,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x27, 0x8F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x8F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x9F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC2,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x3F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xE0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x84,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x08,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF,
0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48,
0x08, 0x48, 0x04, 0x24, 0x04, 0x24, 0x02, 0x12,
0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F,
0x37, 0x7F, 0x1B, 0x3F, 0x1B, 0x3F, 0x0D, 0x1F,
0x0F, 0x08, 0x0E, 0x00, 0x1E, 0x12, 0x1E, 0x12,
0x1F, 0x10, 0x0F, 0x08, 0x02, 0x02, 0x00, 0x00,
0xF7, 0xF8, 0xFF, 0xF0, 0xED, 0xE3, 0xED, 0xE1,
0xEF, 0xE0, 0xF7, 0xF0, 0xFD, 0xFC, 0xFF, 0xFF,
0xF0, 0x10, 0x40, 0x00, 0x41, 0x41, 0x00, 0x00,
0x83, 0x82, 0xE3, 0x20, 0xC7, 0x04, 0xC7, 0x00,
0xEF, 0x1F, 0xFF, 0x1F, 0xBE, 0xFF, 0xFF, 0xFE,
0x7D, 0x7E, 0xDF, 0x3C, 0xFB, 0x18, 0xFF, 0x18,
0x60, 0x00, 0x70, 0x00, 0xF8, 0x08, 0xB0, 0x00,
0xD8, 0x40, 0x3C, 0x24, 0x5C, 0x44, 0xFC, 0x00,
0xFF, 0x8F, 0xFF, 0x0F, 0xF7, 0x07, 0xFF, 0x07,
0xBF, 0x47, 0xDB, 0x47, 0xBB, 0x03, 0xFF, 0x03,
0x3C, 0x04, 0x78, 0x00, 0x78, 0x00, 0xEC, 0x80,
0xFE, 0x92, 0xE7, 0x83, 0xE5, 0x80, 0x4F, 0x08,
0xFB, 0x83, 0xFF, 0x83, 0xFF, 0x83, 0x7F, 0x83,
0x6D, 0x93, 0x7C, 0x10, 0x7F, 0x10, 0xF7, 0x10,
0x3C, 0x00, 0x7C, 0x40, 0x78, 0x00, 0xC8, 0x80,
0xFC, 0x24, 0xBC, 0x24, 0xFD, 0x65, 0x3D, 0x25,
0xFF, 0xC3, 0xBF, 0x83, 0xFF, 0x83, 0x7F, 0x03,
0xDB, 0x23, 0xDB, 0x23, 0x9A, 0x67, 0xDA, 0x47,
0xFF, 0x80, 0xFF, 0x80, 0xE0, 0x80, 0x40, 0x00,
0xFF, 0x01, 0xFF, 0x01, 0xDF, 0x1F, 0xE0, 0x20,
0x7F, 0x80, 0x7F, 0x00, 0x7F, 0x1F, 0xFF, 0x1F,
0xFE, 0x00, 0xFE, 0x00, 0xE0, 0x01, 0xDF, 0x3F,
0xBF, 0xA0, 0xB9, 0xA0, 0x10, 0x00, 0x11, 0x01,
0x3B, 0x00, 0x3F, 0x00, 0x7E, 0x4E, 0x78, 0x48,
0x5F, 0x40, 0x5F, 0xC0, 0xFF, 0xC7, 0xFE, 0xC7,
0xFF, 0xC0, 0xFF, 0xC0, 0xB1, 0xC4, 0xB7, 0x8F,
0xE3, 0x22, 0xC7, 0x04, 0xCE, 0x08, 0xE6, 0x20,
0xCE, 0x42, 0xDE, 0x52, 0xFE, 0x32, 0xFC, 0x30,
0xDD, 0x3E, 0xFB, 0x18, 0xF7, 0x18, 0xDF, 0x31,
0xBD, 0x31, 0xAD, 0x23, 0xCD, 0x31, 0xCF, 0x11,
0xFE, 0x02, 0x9E, 0x00, 0x86, 0x80, 0x06, 0x00,
0x03, 0x00, 0x02, 0x00, 0x07, 0x01, 0x07, 0x01,
0xFD, 0x03, 0xFF, 0x01, 0x7F, 0xF0, 0xFF, 0xF8,
0xFF, 0xF8, 0xFF, 0xF8, 0xFE, 0xF8, 0xFE, 0xF1,
0x38, 0x08, 0x71, 0x41, 0x1F, 0x06, 0x39, 0x20,
0x0F, 0x00, 0x0F, 0x01, 0x04, 0x00, 0x0C, 0x00,
0xF7, 0x87, 0xBE, 0xC6, 0xF9, 0xC6, 0xDF, 0xE0,
0xFF, 0xE0, 0xFE, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1,
0x70, 0x10, 0xE0, 0x20, 0x80, 0x00, 0x80, 0x00,
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xEF, 0x1F, 0xDF, 0x1F, 0xFF, 0x3F, 0xFF, 0x7F,
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x3F, 0x3F, 0x7F, 0x7F, 0x78, 0x78, 0xF0, 0xF0,
0xF0, 0xF0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0xF0,
0xDF, 0xFF, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF,
0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF,
0xE7, 0xE0, 0xFF, 0xF0, 0xFE, 0xF0, 0x1C, 0x00,
0x3C, 0x20, 0x3C, 0x24, 0x3C, 0x24, 0x3C, 0x20,
0xFF, 0xFF, 0xEF, 0xFF, 0x6F, 0xFF, 0xFF, 0xFF,
0xDF, 0xFF, 0xDB, 0xFF, 0xDB, 0xFF, 0xDF, 0xFF,
0xF8, 0x00, 0xFC, 0xC0, 0x1F, 0x03, 0x1F, 0x13,
0x1F, 0x13, 0x1E, 0x02, 0x1E, 0x02, 0x3E, 0x02,
0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0xFF, 0xEC, 0xFF,
0xED, 0xFE, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF,
0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x21, 0x21,
0x20, 0x21, 0x00, 0x01, 0x00, 0x01, 0x40, 0x41,
0x8F, 0x7F, 0x1F, 0xFF, 0x1F, 0xFF, 0x3F, 0xDE,
0x1F, 0xFE, 0x3F, 0xFE, 0x3F, 0xFE, 0x7F, 0xBE,
0x40, 0x7F, 0x84, 0xFF, 0x11, 0xF1, 0x20, 0xE0,
0x20, 0xE0, 0x01, 0xC1, 0x01, 0xC1, 0x22, 0xE3,
0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x0E, 0xFF, 0x1F,
0xDF, 0x3F, 0xFE, 0x3F, 0xFF, 0x3E, 0xFD, 0x1E,
0x47, 0xC0, 0x27, 0xE0, 0x2F, 0xE8, 0x0F, 0xE9,
0x0F, 0xE1, 0x0F, 0xE0, 0x3F, 0xF0, 0x3F, 0xF1,
0xF8, 0x3F, 0xF8, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F,
0xF0, 0x1F, 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F,
0xFC, 0x00, 0xFE, 0xE2, 0x1E, 0x12, 0x1E, 0x12,
0x3E, 0x22, 0xFC, 0x00, 0xF8, 0x08, 0xF0, 0x10,
0x03, 0xFF, 0x01, 0xFF, 0xE1, 0xFF, 0xE1, 0xFF,
0xC1, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x0F, 0xFF,
0x01, 0x11, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0E, 0x1F, 0x07, 0x0F, 0x03, 0x07, 0x01, 0x03,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x40, 0x40, 0x30, 0x30,
0x0C, 0x0C, 0x03, 0xC3, 0x00, 0x30, 0x00, 0x0C,
0xFF, 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xCF, 0xFF,
0xF3, 0xFF, 0x3C, 0xFF, 0x0F, 0x3F, 0x03, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC0, 0xC0, 0x3C, 0x3C, 0x03, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF,
0x15, 0x15, 0x3F, 0x20, 0x2F, 0x20, 0x06, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xEA, 0xE6, 0xDF, 0xC0, 0xDF, 0xE0, 0xF9, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE6, 0x20, 0x9E, 0x12, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xDF, 0x30, 0xED, 0x31, 0xFF, 0x63, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x0E, 0x02, 0x1E, 0x12, 0x0D, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFD, 0x03, 0xED, 0xE1, 0xFE, 0xF1, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x4F, 0x08, 0xE6, 0x20, 0xE7, 0x21, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF7, 0x18, 0xDF, 0x18, 0xDE, 0x18, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xB9, 0xA1, 0x11, 0x01, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x5E, 0x46, 0xFE, 0xC6, 0xFF, 0xC6, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x3F, 0xFF, 0x01, 0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x01, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x7E, 0x4E, 0x3F, 0x00, 0x3E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB1, 0x8E, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xEE, 0x20, 0x8F, 0x08, 0x85, 0x84, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xDF, 0x30, 0xF7, 0x38, 0x7B, 0x7C, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xAE, 0xA2, 0xF8, 0x00, 0xE8, 0x08, 0xC0, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x5D, 0xE1, 0xFF, 0x03, 0xF7, 0x0F, 0x3F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x0E, 0x02, 0x1E, 0x12, 0x1E, 0x12, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFD, 0xF1, 0xED, 0xF1, 0xED, 0xE3, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFB, 0xFB, 0x7F, 0x7F, 0x3F, 0x3F, 0x0C, 0x0C,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xDF, 0xC1, 0xDF, 0xD0, 0x8F, 0x88, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFE, 0xFF, 0xEF, 0xFF, 0x77, 0xFF, 0xFE, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFA, 0x82, 0xF8, 0x08, 0xE0, 0x00, 0x81, 0x81,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7E, 0xFD, 0xF4, 0xFF, 0xFC, 0xFF, 0x7E, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x7D, 0x7D, 0x02, 0x02, 0x02, 0x02, 0xFC, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xFE, 0x01, 0xFF, 0x01, 0xFF, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x1C, 0xFF, 0x00, 0xFF, 0x41, 0x7F, 0x18, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF7, 0x08, 0xFF, 0x00, 0xFF, 0x80, 0xE7, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x5E, 0xC2, 0x9C, 0x80, 0x1C, 0x04, 0x08, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE1, 0x3F, 0xE3, 0x7F, 0xE3, 0xFF, 0xF7, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x80, 0x78, 0x08, 0x78, 0x48, 0x10, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0xFF, 0x87, 0xFF, 0x87, 0xFF, 0xEF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xC0, 0x00, 0x3C, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xFF, 0x03, 0x3F, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1C, 0x1C, 0x03, 0x03, 0x00, 0xE0, 0x00, 0x1C,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE3, 0xFF, 0xFC, 0xFF, 0x1F, 0xFF, 0x03, 0x1F,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFC, 0xFC, 0x03, 0x03, 0x00, 0x00,
0x00, 0xFC, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0x03, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF,
0x03, 0xFF, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x3F, 0x3F,
0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF,
0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0,
0x3F, 0x3F, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF,
0xC0, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF
};

View File

@ -0,0 +1,558 @@
static const uint16_t palette[] = {
0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6,
0x739C, 0x45A8, 0x4520, 0x18A5, 0x4A32, 0x2033, 0x20EC, 0x0000,
};
static const uint16_t tilemap[] = {
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0001, 0x0003, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005,
0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x4003, 0x0001,
0x0001, 0x0006, 0x0007, 0x0007, 0x0007, 0x0008, 0x0009, 0x000A,
0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012,
0x0013, 0x0014, 0x0015, 0x000E, 0x0016, 0x0017, 0x0018, 0x0019,
0x001A, 0x001B, 0x001C, 0x0007, 0x0007, 0x0007, 0x4006, 0x0001,
0x0001, 0x001D, 0x001E, 0x001E, 0x001E, 0x001F, 0x0020, 0x0021,
0x0022, 0x0023, 0x0024, 0x0025, 0x4024, 0x0026, 0x0025, 0x0025,
0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E,
0x002F, 0x0030, 0x0031, 0x001E, 0x001E, 0x001E, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0034, 0x0035, 0x4034, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x8034, 0x0036, 0xC034, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0037, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0038, 0x0001,
0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0039, 0x003A, 0x0001,
0x0001, 0x003B, 0x003C, 0x0032, 0x0032, 0xC03C, 0x003D, 0x003D,
0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D,
0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D,
0x003D, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0001, 0x0001,
0x0001, 0x0042, 0x0043, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044,
0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044,
0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044,
0x0044, 0x0044, 0x0045, 0x0046, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D,
0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B,
0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0001, 0x0001, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006A, 0x006B, 0x0001, 0x006C, 0x0001, 0x0001,
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
};
const uint8_t tiles[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7,
0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7,
0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00,
0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00,
0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF,
0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF,
0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9,
0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9,
0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22,
0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22,
0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF,
0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF,
0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20,
0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20,
0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF,
0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00,
0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF,
0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00,
0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41,
0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F,
0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7,
0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0,
0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10,
0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB,
0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9,
0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00,
0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF,
0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF,
0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00,
0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72,
0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78,
0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08,
0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02,
0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27,
0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F,
0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88,
0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20,
0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF,
0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00,
0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF,
0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00,
0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77,
0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77,
0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88,
0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88,
0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF,
0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00,
0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9,
0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9,
0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02,
0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02,
0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF,
0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF,
0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20,
0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20,
0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74,
0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74,
0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89,
0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89,
0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7,
0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4,
0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10,
0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12,
0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF,
0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF,
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00,
0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10,
0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF,
0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00,
0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F,
0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80,
0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80,
0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF,
0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF,
0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E,
0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81,
0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81,
0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7,
0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED,
0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00,
0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02,
0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB,
0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F,
0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04,
0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10,
0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7,
0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77,
0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00,
0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08,
0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7,
0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE,
0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40,
0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00,
0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF,
0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F,
0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00,
0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0,
0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01,
0x00, 0xFF, 0x00, 0xFF, 0x21, 0xDE, 0x00, 0x7F,
0x0C, 0xF3, 0x19, 0xE0, 0x10, 0xEE, 0x08, 0xF7,
0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x3F, 0x80, 0xFF,
0x00, 0xFF, 0x0E, 0xF7, 0x1F, 0xE1, 0x07, 0xF8,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xBF,
0x40, 0xBE, 0x80, 0x3F, 0x02, 0xFD, 0x00, 0xFB,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0,
0x7F, 0x81, 0xFE, 0x41, 0xFC, 0x03, 0xFC, 0x07,
0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFF,
0x00, 0xFB, 0x04, 0xFB, 0x24, 0xDB, 0x64, 0x9B,
0xFF, 0x00, 0xFF, 0x00, 0x87, 0x78, 0x07, 0xF8,
0x07, 0xFC, 0x07, 0xF8, 0x03, 0xFC, 0x43, 0xBC,
0x00, 0xFF, 0x00, 0xFF, 0x01, 0xDE, 0x20, 0xDF,
0x20, 0xDF, 0x00, 0xFF, 0x04, 0xFB, 0x04, 0xFB,
0xFF, 0x00, 0xFF, 0x00, 0xE0, 0x3F, 0xC0, 0x3F,
0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x00, 0xFF,
0x00, 0x77, 0x00, 0x7F, 0x80, 0x6F, 0x82, 0x7D,
0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF8, 0x07,
0xF8, 0x8F, 0xF0, 0x8F, 0x70, 0x9F, 0x60, 0x9F,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB,
0x24, 0xDB, 0x20, 0xDF, 0x20, 0xDF, 0x00, 0xDF,
0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7,
0x18, 0xE7, 0x18, 0xE7, 0x38, 0xC7, 0x38, 0xE7,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFC,
0x7E, 0x81, 0x80, 0x01, 0x80, 0x7F, 0xF8, 0x03,
0xFF, 0x00, 0xFF, 0x00, 0x01, 0xFE, 0x01, 0xFF,
0x01, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x07, 0xFC,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF,
0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0E, 0xF1,
0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F,
0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F,
0x00, 0xFF, 0x00, 0xFF, 0x40, 0x9F, 0x00, 0xFF,
0x10, 0xEF, 0x90, 0x6F, 0x10, 0xEB, 0x14, 0xEB,
0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x1F, 0xE0,
0x0E, 0xF1, 0x0C, 0xF3, 0x0C, 0xF7, 0x18, 0xE7,
0x00, 0xFF, 0x00, 0xFF, 0x20, 0x9F, 0x00, 0xFF,
0x0C, 0xF3, 0x31, 0xC0, 0x60, 0x9F, 0x40, 0xBF,
0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x00, 0xFF,
0x00, 0xFF, 0x1E, 0xEF, 0x3F, 0xC0, 0x7F, 0x80,
0x00, 0xFF, 0x00, 0xFF, 0x80, 0x77, 0x04, 0xDB,
0x00, 0xFB, 0x10, 0xEF, 0x00, 0xFD, 0x80, 0x77,
0xFF, 0x00, 0xFF, 0x00, 0x78, 0x8F, 0x38, 0xE7,
0x1C, 0xE7, 0x0C, 0xF3, 0x0E, 0xF3, 0x0E, 0xF9,
0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0x00, 0xFF,
0x40, 0xB7, 0x00, 0xEF, 0x01, 0xDE, 0x02, 0xFC,
0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x83, 0x78, 0x87,
0x38, 0xCF, 0x30, 0xDF, 0x21, 0xFE, 0x03, 0xFD,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x60, 0x9F,
0xC0, 0x3F, 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x3F, 0xC0,
0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x01, 0xFC,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFE, 0x01, 0xFC, 0x03, 0xFC, 0x07, 0xFE, 0x03,
0x00, 0xFF, 0x40, 0x3F, 0x30, 0x8F, 0x00, 0xF7,
0x80, 0x7F, 0x30, 0xCF, 0x01, 0xFE, 0x87, 0x78,
0x01, 0xFE, 0x80, 0xFF, 0xE0, 0x5F, 0xF8, 0x0F,
0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFC,
0x00, 0xFF, 0x08, 0xF7, 0x80, 0x6F, 0x80, 0x7F,
0x80, 0x5F, 0x87, 0x78, 0x04, 0x7B, 0x08, 0x73,
0xF8, 0x07, 0xF0, 0x0F, 0x70, 0x9F, 0x60, 0x9F,
0x60, 0xBF, 0xC3, 0x3C, 0x87, 0xF8, 0x87, 0xFC,
0xA0, 0x1F, 0x80, 0x7D, 0xE2, 0x1D, 0x02, 0xFD,
0x02, 0xFD, 0xF0, 0x0F, 0x10, 0xEE, 0x11, 0xEE,
0x43, 0xFC, 0xE3, 0x1E, 0x03, 0xFC, 0x01, 0xFE,
0x01, 0xFE, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1E,
0x44, 0xBB, 0x48, 0xB3, 0x48, 0xB7, 0x08, 0xF7,
0x0A, 0xF5, 0x02, 0xF5, 0x80, 0x77, 0x90, 0x67,
0x84, 0x7B, 0x84, 0x7F, 0x84, 0x7B, 0x84, 0x7B,
0x8C, 0x73, 0x8C, 0x7B, 0x0E, 0xF9, 0x0E, 0xF9,
0x86, 0x59, 0x06, 0xF9, 0x48, 0xB3, 0x08, 0xF7,
0x10, 0xE7, 0x14, 0xEB, 0x24, 0xCB, 0x20, 0xDF,
0x60, 0xBF, 0x44, 0xBB, 0x04, 0xFF, 0x0C, 0xF3,
0x0C, 0xFB, 0x18, 0xE7, 0x18, 0xF7, 0x38, 0xC7,
0x08, 0xD7, 0x48, 0x97, 0x48, 0xB7, 0x41, 0xBE,
0x41, 0xBE, 0x01, 0xBE, 0x10, 0xAF, 0x90, 0x2F,
0x30, 0xEF, 0x30, 0xEF, 0x30, 0xCF, 0x30, 0xCF,
0x70, 0x8F, 0x70, 0xCF, 0x60, 0xDF, 0x60, 0xDF,
0x04, 0xFB, 0x04, 0xFB, 0xFC, 0x03, 0x00, 0xFF,
0x00, 0xFF, 0xF8, 0x02, 0x05, 0xFA, 0x05, 0xFA,
0x03, 0xFC, 0x03, 0xFC, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0x07, 0xFD, 0x02, 0xFD, 0x06, 0xF9,
0x80, 0x7F, 0x80, 0x7F, 0x0F, 0xF0, 0x10, 0xE7,
0x10, 0xEE, 0x1E, 0xE1, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x0E, 0xF1, 0x0F, 0xF8,
0x0F, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x60, 0x8F, 0x00, 0xDF, 0x00, 0xFF, 0x00, 0xEF,
0x04, 0xEB, 0x20, 0xCF, 0x22, 0xDD, 0xC1, 0x1E,
0x38, 0xD7, 0x38, 0xE7, 0x18, 0xE7, 0x18, 0xF7,
0x18, 0xF7, 0x1C, 0xF3, 0x3E, 0xC1, 0x7F, 0xA0,
0x80, 0x3F, 0x80, 0x7F, 0x80, 0x7F, 0x01, 0xFE,
0x00, 0xBD, 0x18, 0xE7, 0x00, 0xFF, 0x83, 0x7C,
0x7F, 0xC0, 0x7F, 0x80, 0x7F, 0x80, 0x7E, 0x81,
0x7E, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x81, 0x76, 0x80, 0x77, 0x10, 0xE7, 0x10, 0xEF,
0x10, 0xEF, 0x21, 0xCE, 0x41, 0x9E, 0x81, 0x3E,
0x0E, 0xF9, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF0,
0x1F, 0xE0, 0x3E, 0xD1, 0x7E, 0xA1, 0xFE, 0x41,
0x04, 0xF9, 0x08, 0xF3, 0x18, 0xE7, 0x10, 0xEF,
0x10, 0xEF, 0x10, 0xEF, 0x00, 0xEF, 0x20, 0xCF,
0x07, 0xFA, 0x07, 0xFC, 0x0F, 0xF0, 0x0F, 0xF0,
0x0F, 0xF0, 0x1F, 0xE0, 0x1F, 0xF0, 0x1F, 0xF0,
0x7C, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x70, 0x8E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xC3, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x24, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x8F, 0x70, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xF8, 0x03, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x3E, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0xC1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xE0, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
};

View File

@ -3,6 +3,8 @@
void GB_update_joyp(GB_gameboy_t *gb)
{
if (gb->model & GB_MODEL_NO_SFC_BIT) return;
uint8_t key_selection = 0;
uint8_t previous_state = 0;
@ -10,7 +12,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0;
uint8_t current_player = gb->sgb? gb->sgb->current_player : 0;
uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0;
switch (key_selection) {
case 3:
if (gb->sgb && gb->sgb->player_count > 1) {
@ -53,19 +55,32 @@ void GB_update_joyp(GB_gameboy_t *gb)
break;
}
/* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */
/* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */
gb->io_registers[GB_IO_IF] |= 0x10;
gb->stopped = false;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
}
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
{
uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
gb->io_registers[GB_IO_JOYP] &= 0xF0;
gb->io_registers[GB_IO_JOYP] |= value & 0xF;
if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
gb->io_registers[GB_IO_IF] |= 0x10;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
}
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)
{
assert(index >= 0 && index < GB_KEY_MAX);
gb->keys[0][index] = pressed;
GB_update_joyp(gb);
}
void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed)
@ -73,4 +88,5 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play
assert(index >= 0 && index < GB_KEY_MAX);
assert(player < 4);
gb->keys[player][index] = pressed;
GB_update_joyp(gb);
}

View File

@ -17,6 +17,7 @@ typedef enum {
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed);
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value);
#ifdef GB_INTERNAL
void GB_update_joyp(GB_gameboy_t *gb);

View File

@ -37,8 +37,8 @@ const GB_cartridge_t GB_cart_defs[256] = {
[0xFC] =
{ GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
{ GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only)
{ GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings)
{ GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3
{ GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY
};
void GB_update_mbc_mappings(GB_gameboy_t *gb)
@ -86,6 +86,10 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
case GB_MBC3:
gb->mbc_rom_bank = gb->mbc3.rom_bank;
gb->mbc_ram_bank = gb->mbc3.ram_bank;
if (!gb->is_mbc30) {
gb->mbc_rom_bank &= 0x7F;
gb->mbc_ram_bank &= 0x3;
}
if (gb->mbc_rom_bank == 0) {
gb->mbc_rom_bank = 1;
}
@ -128,10 +132,13 @@ void GB_configure_cart(GB_gameboy_t *gb)
gb->mbc_ram_size = 0x200;
}
else {
static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
}
if (gb->mbc_ram_size) {
gb->mbc_ram = malloc(gb->mbc_ram_size);
}
/* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */
memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);
@ -147,6 +154,13 @@ void GB_configure_cart(GB_gameboy_t *gb)
}
}
/* Detect MBC30 */
if (gb->cartridge_type->mbc_type == GB_MBC3) {
if (gb->rom_size > 0x200000 || gb->mbc_ram_size > 0x8000) {
gb->is_mbc30 = true;
}
}
/* Set MBC5's bank to 1 correctly */
if (gb->cartridge_type->mbc_type == GB_MBC5) {
gb->mbc5.rom_bank_low = 1;

View File

@ -10,7 +10,7 @@ typedef struct {
GB_MBC2,
GB_MBC3,
GB_MBC5,
GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */
GB_HUC1,
GB_HUC3,
} mbc_type;
enum {

View File

@ -113,6 +113,11 @@ static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src);
}
static bool effective_ir_input(GB_gameboy_t *gb)
{
return gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir;
}
static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x100 && !gb->boot_rom_finished) {
@ -126,13 +131,13 @@ static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr)
if (!gb->rom_size) {
return 0xFF;
}
unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000;
unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000;
return gb->rom[effective_address & (gb->rom_size - 1)];
}
static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr)
{
unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000;
unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000;
return gb->rom[effective_address & (gb->rom_size - 1)];
}
@ -141,16 +146,56 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
if (gb->vram_read_blocked) {
return 0xFF;
}
if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) {
if (addr & 0x1000) {
addr = gb->last_tile_index_address;
}
else if (gb->last_tile_data_address & 0x1000) {
/* TODO: This is case is more complicated then the rest and differ between revisions
It's probably affected by how VRAM is layed out, might be easier after a decap is done*/
}
else {
addr = gb->last_tile_data_address;
}
}
return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000];
}
static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->cartridge_type->mbc_type == GB_HUC3) {
switch (gb->huc3_mode) {
case 0xC: // RTC read
if (gb->huc3_access_flags == 0x2) {
return 1;
}
return gb->huc3_read;
case 0xD: // RTC status
return 1;
case 0xE: // IR mode
return effective_ir_input(gb); // TODO: What are the other bits?
default:
GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr);
return 1; // TODO: What happens in this case?
case 0: // TODO: R/O RAM? (or is it disabled?)
case 0xA: // RAM
break;
}
}
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) &&
gb->cartridge_type->mbc_subtype != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF;
gb->cartridge_type->mbc_type != GB_HUC1 &&
gb->cartridge_type->mbc_type != GB_HUC3) {
return 0xFF;
}
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
return 0xc0 | effective_ir_input(gb);
}
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
/* RTC read */
gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */
return gb->rtc_latched.data[gb->mbc_ram_bank - 8];
@ -273,8 +318,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
;
case GB_MODEL_SGB2_NO_SFC:
break;
}
}
@ -292,21 +340,22 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
return gb->io_registers[GB_IO_TAC] | 0xF8;
case GB_IO_STAT:
return gb->io_registers[GB_IO_STAT] | 0x80;
case GB_IO_DMG_EMULATION_INDICATION:
if (!gb->cgb_mode) {
case GB_IO_OPRI:
if (!GB_is_cgb(gb)) {
return 0xFF;
}
return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE;
return gb->io_registers[GB_IO_OPRI] | 0xFE;
case GB_IO_PCM_12:
if (!GB_is_cgb(gb)) return 0xFF;
return (gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) |
(gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0);
return ((gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) |
(gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[0] : 0xFF);
case GB_IO_PCM_34:
if (!GB_is_cgb(gb)) return 0xFF;
return (gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) |
(gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0);
return ((gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) |
(gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[1] : 0xFF);
case GB_IO_JOYP:
GB_timing_sync(gb);
case GB_IO_TMA:
case GB_IO_LCDC:
case GB_IO_SCY:
@ -375,9 +424,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
case GB_IO_RP: {
if (!gb->cgb_mode) return 0xFF;
/* You will read your own IR LED if it's on. */
bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1);
uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C;
if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) {
if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && effective_ir_input(gb)) {
ret |= 2;
}
return ret;
@ -418,6 +466,11 @@ static GB_read_function_t * const read_map[] =
read_ram, read_high_memory, /* EXXX FXXX */
};
void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback)
{
gb->read_memory_callback = callback;
}
uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->n_watchpoints) {
@ -426,7 +479,12 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
if (is_addr_in_dma_use(gb, addr)) {
addr = gb->dma_current_src;
}
return read_map[addr >> 12](gb, addr);
uint8_t data = read_map[addr >> 12](gb, addr);
GB_apply_cheat(gb, addr, &data);
if (gb->read_memory_callback) {
data = gb->read_memory_callback(gb, addr, data);
}
return data;
}
static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
@ -442,9 +500,9 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
}
break;
case GB_MBC2:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break;
switch (addr & 0x4100) {
case 0x0000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x0100: gb->mbc2.rom_bank = value; break;
}
break;
case GB_MBC3:
@ -469,9 +527,6 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (gb->cartridge_type->has_rumble) {
if (!!(value & 8) != gb->rumble_state) {
gb->rumble_state = !gb->rumble_state;
if (gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
}
value &= 7;
}
@ -482,7 +537,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
break;
case GB_HUC1:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break;
case 0x2000: case 0x3000: gb->huc1.bank_low = value; break;
case 0x4000: case 0x5000: gb->huc1.bank_high = value; break;
case 0x6000: case 0x7000: gb->huc1.mode = value; break;
@ -490,7 +545,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
break;
case GB_HUC3:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x0000: case 0x1000:
gb->huc3_mode = value & 0xF;
gb->mbc_ram_enable = gb->huc3_mode == 0xA;
break;
case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break;
case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break;
}
@ -505,17 +563,135 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
//GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr);
return;
}
/* TODO: not verified */
if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) {
if (addr & 0x1000) {
addr = gb->last_tile_index_address;
}
else if (gb->last_tile_data_address & 0x1000) {
/* TODO: This is case is more complicated then the rest and differ between revisions
It's probably affected by how VRAM is layed out, might be easier after a decap is done */
}
else {
addr = gb->last_tile_data_address;
}
}
gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value;
}
static bool huc3_write(GB_gameboy_t *gb, uint8_t value)
{
switch (gb->huc3_mode) {
case 0xB: // RTC Write
switch (value >> 4) {
case 1:
if (gb->huc3_access_index < 3) {
gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF;
}
else if (gb->huc3_access_index < 7) {
gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF;
}
else {
// GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index);
}
gb->huc3_access_index++;
break;
case 2:
case 3:
if (gb->huc3_access_index < 3) {
gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4));
gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4));
}
else if (gb->huc3_access_index < 7) {
gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4));
gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4));
}
else if (gb->huc3_access_index >= 0x58 && gb->huc3_access_index <= 0x5a) {
gb->huc3_alarm_minutes &= ~(0xF << ((gb->huc3_access_index - 0x58) * 4));
gb->huc3_alarm_minutes |= ((value & 0xF) << ((gb->huc3_access_index - 0x58) * 4));
}
else if (gb->huc3_access_index >= 0x5b && gb->huc3_access_index <= 0x5e) {
gb->huc3_alarm_days &= ~(0xF << ((gb->huc3_access_index - 0x5b) * 4));
gb->huc3_alarm_days |= ((value & 0xF) << ((gb->huc3_access_index - 0x5b) * 4));
}
else if (gb->huc3_access_index == 0x5f) {
gb->huc3_alarm_enabled = value & 1;
}
else {
// GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3_access_index);
}
if ((value >> 4) == 3) {
gb->huc3_access_index++;
}
break;
case 4:
gb->huc3_access_index &= 0xF0;
gb->huc3_access_index |= value & 0xF;
break;
case 5:
gb->huc3_access_index &= 0x0F;
gb->huc3_access_index |= (value & 0xF) << 4;
break;
case 6:
gb->huc3_access_flags = (value & 0xF);
break;
default:
break;
}
return true;
case 0xD: // RTC status
// Not sure what writes here mean, they're always 0xFE
return true;
case 0xE: { // IR mode
bool old_input = effective_ir_input(gb);
gb->cart_ir = value & 1;
bool new_input = effective_ir_input(gb);
if (new_input != old_input) {
if (gb->infrared_callback) {
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
}
gb->cycles_since_ir_change = 0;
}
return true;
}
case 0xC:
return true;
default:
return false;
case 0: // Disabled
case 0xA: // RAM
return false;
}
}
static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (gb->cartridge_type->mbc_type == GB_HUC3) {
if (huc3_write(gb, value)) return;
}
if (gb->camera_registers_mapped) {
GB_camera_write_register(gb, addr, value);
return;
}
if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return;
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size)
&& gb->cartridge_type->mbc_type != GB_HUC1) return;
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
bool old_input = effective_ir_input(gb);
gb->cart_ir = value & 1;
bool new_input = effective_ir_input(gb);
if (new_input != old_input) {
if (gb->infrared_callback) {
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
}
gb->cycles_since_ir_change = 0;
}
return;
}
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value;
@ -583,7 +759,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
break;
@ -628,24 +807,34 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (addr < 0xFF80) {
/* Hardware registers */
switch (addr & 0xFF) {
case GB_IO_WY:
if (value == gb->current_line) {
gb->wy_triggered = true;
}
case GB_IO_WX:
GB_window_related_write(gb, addr & 0xFF, value);
break;
case GB_IO_IF:
case GB_IO_SCX:
case GB_IO_SCY:
case GB_IO_BGP:
case GB_IO_OBP0:
case GB_IO_OBP1:
case GB_IO_WY:
case GB_IO_SB:
case GB_IO_DMG_EMULATION_INDICATION:
case GB_IO_UNKNOWN2:
case GB_IO_UNKNOWN3:
case GB_IO_UNKNOWN4:
case GB_IO_UNKNOWN5:
gb->io_registers[addr & 0xFF] = value;
return;
case GB_IO_OPRI:
if ((!gb->boot_rom_finished || (gb->io_registers[GB_IO_KEY0] & 8)) && GB_is_cgb(gb)) {
gb->io_registers[addr & 0xFF] = value;
gb->object_priority = (value & 1) ? GB_OBJECT_PRIORITY_X : GB_OBJECT_PRIORITY_INDEX;
}
else if (gb->cgb_mode) {
gb->io_registers[addr & 0xFF] = value;
}
return;
case GB_IO_LYC:
/* TODO: Probably completely wrong in double speed mode */
@ -662,7 +851,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
/* These are the states when LY changes, let the display routine call GB_STAT_update for use
so it correctly handles T-cycle accurate LYC writes */
if (!GB_is_cgb(gb) || (
gb->display_state != 6 &&
gb->display_state != 35 &&
gb->display_state != 26 &&
gb->display_state != 15 &&
gb->display_state != 16)) {
@ -714,8 +903,19 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_timing_sync(gb);
GB_lcd_off(gb);
}
/* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */
GB_window_related_write(gb, addr & 0xFF, value);
/* Handle disabling objects while already fetching an object */
if ((gb->io_registers[GB_IO_LCDC] & 2) && !(value & 2)) {
if (gb->during_object_fetch) {
gb->cycles_for_line += gb->display_cycles;
gb->display_cycles = 0;
gb->object_fetch_aborted = true;
}
}
gb->io_registers[GB_IO_LCDC] = value;
if (!(value & 0x20)) {
gb->wx_triggered = false;
gb->wx166_glitch = false;
}
return;
case GB_IO_STAT:
@ -736,18 +936,25 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
case GB_IO_JOYP:
GB_sgb_write(gb, value);
gb->io_registers[GB_IO_JOYP] = value & 0xF0;
if (gb->joyp_write_callback) {
gb->joyp_write_callback(gb, value);
GB_update_joyp(gb);
}
else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) {
GB_sgb_write(gb, value);
gb->io_registers[GB_IO_JOYP] = (value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F);
GB_update_joyp(gb);
}
return;
case GB_IO_BIOS:
case GB_IO_BANK:
gb->boot_rom_finished = true;
return;
case GB_IO_DMG_EMULATION:
case GB_IO_KEY0:
if (GB_is_cgb(gb) && !gb->boot_rom_finished) {
gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */
gb->io_registers[GB_IO_KEY0] = value;
}
return;
@ -889,13 +1096,15 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (!GB_is_cgb(gb)) {
return;
}
if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) {
bool old_input = effective_ir_input(gb);
gb->io_registers[GB_IO_RP] = value;
bool new_input = effective_ir_input(gb);
if (new_input != old_input) {
if (gb->infrared_callback) {
gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change);
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
}
gb->cycles_since_ir_change = 0;
}
}
gb->io_registers[GB_IO_RP] = value;
return;
}

View File

@ -3,6 +3,9 @@
#include "gb_struct_def.h"
#include <stdint.h>
typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data);
void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback);
uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr);
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
#ifdef GB_INTERNAL

View File

@ -31,8 +31,8 @@ static void handle_command(GB_gameboy_t *gb)
image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
}
if (gb->printer.callback) {
gb->printer.callback(gb, image, gb->printer.image_offset / 160,
if (gb->printer_callback) {
gb->printer_callback(gb, image, gb->printer.image_offset / 160,
gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
gb->printer.command_data[3] & 0x7F);
}
@ -212,5 +212,5 @@ void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback)
memset(&gb->printer, 0, sizeof(gb->printer));
GB_set_serial_transfer_bit_start_callback(gb, serial_start);
GB_set_serial_transfer_bit_end_callback(gb, serial_end);
gb->printer.callback = callback;
gb->printer_callback = callback;
}

View File

@ -48,7 +48,8 @@ typedef struct
uint8_t image[160 * 200];
uint16_t image_offset;
GB_print_image_callback_t callback;
/* TODO: Delete me. */
uint64_t padding;
uint8_t compression_run_lenth;
bool compression_run_is_compressed;

38
Core/random.c Normal file
View File

@ -0,0 +1,38 @@
#include "random.h"
#include <time.h>
static uint64_t seed;
static bool enabled = true;
uint8_t GB_random(void)
{
if (!enabled) return 0;
seed *= 0x27BB2EE687B0B0FDL;
seed += 0xB504F32D;
return seed >> 56;
}
uint32_t GB_random32(void)
{
GB_random();
return seed >> 32;
}
void GB_random_seed(uint64_t new_seed)
{
seed = new_seed;
}
void GB_random_set_enabled(bool enable)
{
enabled = enable;
}
static void __attribute__((constructor)) init_seed(void)
{
seed = time(NULL);
for (unsigned i = 64; i--;) {
GB_random();
}
}

12
Core/random.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef random_h
#define random_h
#include <stdint.h>
#include <stdbool.h>
uint8_t GB_random(void);
uint32_t GB_random32(void);
void GB_random_seed(uint64_t seed);
void GB_random_set_enabled(bool enable);
#endif /* random_h */

53
Core/rumble.c Normal file
View File

@ -0,0 +1,53 @@
#include "rumble.h"
#include "gb.h"
void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode)
{
gb->rumble_mode = mode;
if (gb->rumble_callback) {
gb->rumble_callback(gb, 0);
}
}
void GB_handle_rumble(GB_gameboy_t *gb)
{
if (gb->rumble_callback) {
if (gb->rumble_mode == GB_RUMBLE_DISABLED) {
return;
}
if (gb->cartridge_type->has_rumble) {
if (gb->rumble_on_cycles + gb->rumble_off_cycles) {
gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles));
gb->rumble_on_cycles = gb->rumble_off_cycles = 0;
}
}
else if (gb->rumble_mode == GB_RUMBLE_ALL_GAMES) {
unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80));
unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10));
double ch4_rumble = (MIN(gb->apu.noise_channel.sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0;
ch4_rumble = MIN(ch4_rumble, 1.0);
ch4_rumble = MAX(ch4_rumble, 0.0);
double ch1_rumble = 0;
if (gb->apu.sweep_enabled && ((gb->io_registers[GB_IO_NR10] >> 4) & 7)) {
double sweep_speed = (gb->io_registers[GB_IO_NR10] & 7) / (double)((gb->io_registers[GB_IO_NR10] >> 4) & 7);
ch1_rumble = gb->apu.square_channels[GB_SQUARE_1].current_volume * ch1_volume / 32.0 * sweep_speed / 8.0 - 0.5;
ch1_rumble = MIN(ch1_rumble, 1.0);
ch1_rumble = MAX(ch1_rumble, 0.0);
}
if (!gb->apu.is_active[GB_NOISE]) {
ch4_rumble = 0;
}
if (!gb->apu.is_active[GB_SQUARE_1]) {
ch1_rumble = 0;
}
gb->rumble_callback(gb, MIN(MAX(ch1_rumble / 2 + ch4_rumble, 0.0), 1.0));
}
}
}

17
Core/rumble.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef rumble_h
#define rumble_h
#include "gb_struct_def.h"
typedef enum {
GB_RUMBLE_DISABLED,
GB_RUMBLE_CARTRIDGE_ONLY,
GB_RUMBLE_ALL_GAMES
} GB_rumble_mode_t;
#ifdef GB_INTERNAL
void GB_handle_rumble(GB_gameboy_t *gb);
#endif
void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode);
#endif /* rumble_h */

View File

@ -36,11 +36,10 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
if (!DUMP_SECTION(gb, f, rtc )) goto error;
if (!DUMP_SECTION(gb, f, video )) goto error;
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
}
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto error;
}
@ -73,7 +72,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
+ GB_SECTION_SIZE(apu ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(video ) + sizeof(uint32_t)
+ (GB_is_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
+ (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
+ gb->mbc_ram_size
+ gb->ram_size
+ gb->vram_size;
@ -105,7 +104,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
DUMP_SECTION(gb, buffer, rtc );
DUMP_SECTION(gb, buffer, video );
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb));
}
@ -116,13 +115,21 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
}
/* Best-effort read function for maximum future compatibility. */
static bool read_section(FILE *f, void *dest, uint32_t size)
static bool read_section(FILE *f, void *dest, uint32_t size, bool fix_broken_windows_saves)
{
uint32_t saved_size = 0;
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
return false;
}
if (fix_broken_windows_saves) {
if (saved_size < 4) {
return false;
}
saved_size -= 4;
fseek(f, 4, SEEK_CUR);
}
if (saved_size <= size) {
if (fread(dest, 1, saved_size, f) != saved_size) {
return false;
@ -139,11 +146,21 @@ static bool read_section(FILE *f, void *dest, uint32_t size)
}
#undef DUMP_SECTION
static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
{
if (gb->magic != save->magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) {
/* This is a save state with a bad printer struct from a 32-bit OS */
memcpy(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam);
}
if (save->ram_size == 0) {
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
incorrect RAM amount if it's a CGB instance */
if (GB_is_cgb(save)) {
save->ram_size = 0x2000 * 8; // Incorrect RAM size
}
else {
save->ram_size = gb->ram_size;
}
}
if (gb->version != save->version) {
@ -156,25 +173,53 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
return false;
}
if (gb->ram_size != save->ram_size) {
GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n");
return false;
}
if (gb->vram_size != save->vram_size) {
GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n");
return false;
}
if (GB_is_sgb(gb) != GB_is_sgb(save)) {
GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_sgb(save)? "" : "not ");
if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) {
GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not ");
return false;
}
if (gb->ram_size != save->ram_size) {
if (gb->ram_size == 0x1000 * 8 && save->ram_size == 0x2000 * 8) {
/* A bug in versions prior to 0.12 made CGB instances allocate twice the ammount of RAM.
Ignore this issue to retain compatibility with older, 0.11, save states. */
}
else {
GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n");
return false;
}
}
return true;
}
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
static void sanitize_state(GB_gameboy_t *gb)
{
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
gb->object_low_line_address &= gb->vram_size & ~1;
gb->fetcher_x &= 0x1f;
if (gb->lcd_x > gb->position_in_line) {
gb->lcd_x = gb->position_in_line;
}
if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) {
gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X;
}
}
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
int GB_load_state(GB_gameboy_t *gb, const char *path)
{
@ -182,6 +227,8 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
/* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save));
/* ...Except ram size, we use it to detect old saves with incorrect ram sizes */
save.ram_size = 0;
FILE *f = fopen(path, "rb");
if (!f) {
@ -189,7 +236,18 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
return errno;
}
bool fix_broken_windows_saves = false;
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
if (save.magic == 0) {
/* Potentially legacy, broken Windows save state */
fseek(f, 4, SEEK_SET);
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
fix_broken_windows_saves = true;
}
if (gb->magic != save.magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (!READ_SECTION(&save, f, core_state)) goto error;
if (!READ_SECTION(&save, f, dma )) goto error;
if (!READ_SECTION(&save, f, mbc )) goto error;
@ -199,13 +257,13 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
if (!READ_SECTION(&save, f, rtc )) goto error;
if (!READ_SECTION(&save, f, video )) goto error;
if (!verify_state_compatibility(gb, &save)) {
if (!verify_and_update_state_compatibility(gb, &save)) {
errno = -1;
goto error;
}
if (GB_is_sgb(gb)) {
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
if (GB_is_hle_sgb(gb)) {
if (!read_section(f, gb->sgb, sizeof(*gb->sgb), false)) goto error;
}
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
@ -219,27 +277,21 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
return EIO;
}
/* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */
fseek(f, save.ram_size - gb->ram_size, SEEK_CUR);
if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
fclose(f);
return EIO;
}
size_t orig_ram_size = gb->ram_size;
memcpy(gb, &save, sizeof(save));
gb->ram_size = orig_ram_size;
errno = 0;
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
sanitize_state(gb);
error:
fclose(f);
@ -262,13 +314,23 @@ static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, siz
return length;
}
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size)
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size, bool fix_broken_windows_saves)
{
uint32_t saved_size = 0;
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
return false;
}
if (saved_size > *buffer_length) return false;
if (fix_broken_windows_saves) {
if (saved_size < 4) {
return false;
}
saved_size -= 4;
*buffer += 4;
}
if (saved_size <= size) {
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
return false;
@ -285,15 +347,27 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
return true;
}
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
{
GB_gameboy_t save;
/* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save));
bool fix_broken_windows_saves = false;
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
if (save.magic == 0) {
/* Potentially legacy, broken Windows save state*/
buffer -= GB_SECTION_SIZE(header) - 4;
length += GB_SECTION_SIZE(header) - 4;
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
fix_broken_windows_saves = true;
}
if (gb->magic != save.magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
if (!READ_SECTION(&save, buffer, length, dma )) return -1;
if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
@ -303,12 +377,13 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
if (!READ_SECTION(&save, buffer, length, video )) return -1;
if (!verify_state_compatibility(gb, &save)) {
if (!verify_and_update_state_compatibility(gb, &save)) {
return -1;
}
if (GB_is_sgb(gb)) {
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1;
if (GB_is_hle_sgb(gb)) {
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb), false)) return -1;
}
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
@ -320,25 +395,17 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
return -1;
}
if (buffer_read(gb->vram,gb->vram_size, &buffer, &length) != gb->vram_size) {
if (buffer_read(gb->vram, gb->vram_size, &buffer, &length) != gb->vram_size) {
return -1;
}
/* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */
buffer += save.ram_size - gb->ram_size;
length -= save.ram_size - gb->ram_size;
memcpy(gb, &save, sizeof(save));
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
sanitize_state(gb);
return 0;
}

View File

@ -5,7 +5,7 @@
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))

View File

@ -1,5 +1,11 @@
#include "gb.h"
#include "random.h"
#include <math.h>
#include <assert.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define INTRO_ANIMATION_LENGTH 200
@ -11,6 +17,7 @@ enum {
ATTR_BLK = 0x04,
ATTR_LIN = 0x05,
ATTR_DIV = 0x06,
ATTR_CHR = 0x07,
PAL_SET = 0x0A,
PAL_TRN = 0x0B,
DATA_SND = 0x0F,
@ -66,6 +73,75 @@ static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index)
}
}
static const uint16_t built_in_palettes[] =
{
0x67BF, 0x265B, 0x10B5, 0x2866,
0x637B, 0x3AD9, 0x0956, 0x0000,
0x7F1F, 0x2A7D, 0x30F3, 0x4CE7,
0x57FF, 0x2618, 0x001F, 0x006A,
0x5B7F, 0x3F0F, 0x222D, 0x10EB,
0x7FBB, 0x2A3C, 0x0015, 0x0900,
0x2800, 0x7680, 0x01EF, 0x2FFF,
0x73BF, 0x46FF, 0x0110, 0x0066,
0x533E, 0x2638, 0x01E5, 0x0000,
0x7FFF, 0x2BBF, 0x00DF, 0x2C0A,
0x7F1F, 0x463D, 0x74CF, 0x4CA5,
0x53FF, 0x03E0, 0x00DF, 0x2800,
0x433F, 0x72D2, 0x3045, 0x0822,
0x7FFA, 0x2A5F, 0x0014, 0x0003,
0x1EED, 0x215C, 0x42FC, 0x0060,
0x7FFF, 0x5EF7, 0x39CE, 0x0000,
0x4F5F, 0x630E, 0x159F, 0x3126,
0x637B, 0x121C, 0x0140, 0x0840,
0x66BC, 0x3FFF, 0x7EE0, 0x2C84,
0x5FFE, 0x3EBC, 0x0321, 0x0000,
0x63FF, 0x36DC, 0x11F6, 0x392A,
0x65EF, 0x7DBF, 0x035F, 0x2108,
0x2B6C, 0x7FFF, 0x1CD9, 0x0007,
0x53FC, 0x1F2F, 0x0E29, 0x0061,
0x36BE, 0x7EAF, 0x681A, 0x3C00,
0x7BBE, 0x329D, 0x1DE8, 0x0423,
0x739F, 0x6A9B, 0x7293, 0x0001,
0x5FFF, 0x6732, 0x3DA9, 0x2481,
0x577F, 0x3EBC, 0x456F, 0x1880,
0x6B57, 0x6E1B, 0x5010, 0x0007,
0x0F96, 0x2C97, 0x0045, 0x3200,
0x67FF, 0x2F17, 0x2230, 0x1548,
};
static const struct {
char name[16];
unsigned palette_index;
} palette_assignments[] =
{
{"ZELDA", 5},
{"SUPER MARIOLAND", 6},
{"MARIOLAND2", 0x14},
{"SUPERMARIOLAND3", 2},
{"KIRBY DREAM LAND", 0xB},
{"HOSHINOKA-BI", 0xB},
{"KIRBY'S PINBALL", 3},
{"YOSSY NO TAMAGO", 0xC},
{"MARIO & YOSHI", 0xC},
{"YOSSY NO COOKIE", 4},
{"YOSHI'S COOKIE", 4},
{"DR.MARIO", 0x12},
{"TETRIS", 0x11},
{"YAKUMAN", 0x13},
{"METROID2", 0x1F},
{"KAERUNOTAMENI", 9},
{"GOLF", 0x18},
{"ALLEY WAY", 0x16},
{"BASEBALL", 0xF},
{"TENNIS", 0x17},
{"F1RACE", 0x1E},
{"KID ICARUS", 0xE},
{"QIX", 0x19},
{"SOLARSTRIKER", 7},
{"X", 0x1C},
{"GBWARS", 0x15},
};
static void command_ready(GB_gameboy_t *gb)
{
/* SGB header commands are used to send the contents of the header to the SNES CPU.
@ -75,6 +151,8 @@ static void command_ready(GB_gameboy_t *gb)
0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */
if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
if (gb->boot_rom_finished) return;
uint8_t checksum = 0;
for (unsigned i = 2; i < 0x10; i++) {
checksum += gb->sgb->command[i];
@ -84,19 +162,31 @@ static void command_ready(GB_gameboy_t *gb)
gb->sgb->disable_commands = true;
return;
}
if (gb->sgb->command[0] == 0xf9) {
if (gb->sgb->command[0xc] != 3) { // SGB Flag
unsigned index = (gb->sgb->command[0] >> 1) & 7;
if (index > 5) {
return;
}
memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14);
if (gb->sgb->command[0] == 0xfb) {
if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) {
gb->sgb->disable_commands = true;
for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) {
if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) {
gb->sgb->effective_palettes[0] = built_in_palettes[palette_assignments[i].palette_index * 4 - 4];
gb->sgb->effective_palettes[1] = built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4];
gb->sgb->effective_palettes[2] = built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4];
gb->sgb->effective_palettes[3] = built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4];
break;
}
}
else if (gb->sgb->command[0] == 0xfb) {
if (gb->sgb->command[0x3] != 0x33) { // Old licensee code
gb->sgb->disable_commands = true;
}
}
return;
}
/* Ignore malformed commands (0 length)*/
if ((gb->sgb->command[0] & 7) == 0) return;
switch (gb->sgb->command[0] >> 3) {
case PAL01:
pal_command(gb, 0, 1);
@ -157,7 +247,7 @@ static void command_ready(GB_gameboy_t *gb)
gb->sgb->attribute_map[x + 20 * y] = inside_palette;
}
}
else if(middle) {
else if (middle) {
gb->sgb->attribute_map[x + 20 * y] = middle_palette;
}
}
@ -165,6 +255,52 @@ static void command_ready(GB_gameboy_t *gb)
}
break;
}
case ATTR_CHR: {
struct __attribute__((packed)) {
uint8_t x, y;
uint16_t length;
uint8_t direction;
uint8_t data[];
} *command = (void *)(gb->sgb->command + 1);
uint16_t count = command->length;
#ifdef GB_BIG_ENDIAN
count = __builtin_bswap16(count);
#endif
uint8_t x = command->x;
uint8_t y = command->y;
if (x >= 20 || y >= 18 || (count + 3) / 4 > sizeof(gb->sgb->command) - sizeof(*command) - 1) {
/* TODO: Verify with the SFC BIOS */
break;
}
for (unsigned i = 0; i < count; i++) {
uint8_t palette = (command->data[i / 4] >> (((~i) & 3) << 1)) & 3;
gb->sgb->attribute_map[x + 20 * y] = palette;
if (command->direction) {
y++;
if (y == 18) {
x++;
y = 0;
if (x == 20) {
x = 0;
}
}
}
else {
x++;
if (x == 20) {
y++;
x = 0;
if (y == 18) {
y = 0;
}
}
}
}
break;
}
case ATTR_LIN: {
struct {
uint8_t count;
@ -248,8 +384,15 @@ static void command_ready(GB_gameboy_t *gb)
// Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this
break;
case MLT_REQ:
gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3];
gb->sgb->current_player = gb->sgb->player_count - 1;
if (gb->sgb->player_count == 1) {
gb->sgb->current_player = 0;
}
gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility,
fix this to be 0 based. */
if (gb->sgb->player_count == 3) {
gb->sgb->current_player++;
}
gb->sgb->mlt_lock = true;
break;
case CHR_TRN:
gb->sgb->vram_transfer_countdown = 2;
@ -291,21 +434,31 @@ static void command_ready(GB_gameboy_t *gb)
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
{
if (!GB_is_sgb(gb)) return;
if (!GB_is_hle_sgb(gb)) {
/* Notify via callback */
return;
}
if (gb->sgb->disable_commands) return;
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return;
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) {
return;
}
uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8;
if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
command_size = SGB_PACKET_SIZE * 8;
}
if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) {
gb->sgb->mlt_lock ^= true;
}
switch ((value >> 4) & 3) {
case 3:
gb->sgb->ready_for_pulse = true;
/* TODO: This is the logic used by BGB which *should* work for most/all games, but a proper test ROM is needed */
if (gb->sgb->player_count > 1 && (gb->io_registers[GB_IO_JOYP] & 0x30) == 0x10) {
if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) {
gb->sgb->current_player++;
gb->sgb->current_player &= gb->sgb->player_count - 1;
gb->sgb->current_player &= 3;
gb->sgb->mlt_lock = true;
}
break;
@ -366,22 +519,9 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
}
}
static inline uint8_t scale_channel(uint8_t x)
{
return (x << 3) | (x >> 2);
}
static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color)
{
uint8_t r = (color) & 0x1F;
uint8_t g = (color >> 5) & 0x1F;
uint8_t b = (color >> 10) & 0x1F;
r = scale_channel(r);
g = scale_channel(g);
b = scale_channel(b);
return gb->rgb_encode_callback(gb, r, g, b);
return GB_convert_rgb15(gb, color, false);
}
static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade)
@ -394,18 +534,19 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_
if (g >= 0x20) g = 0;
if (b >= 0x20) b = 0;
r = scale_channel(r);
g = scale_channel(g);
b = scale_channel(b);
color = r | (g << 5) | (b << 10);
return gb->rgb_encode_callback(gb, r, g, b);
return GB_convert_rgb15(gb, color, false);
}
#include <stdio.h>
static void render_boot_animation (GB_gameboy_t *gb)
{
#include "sgb_animation_logo.inc"
uint32_t *output = &gb->screen[48 + 40 * 256];
#include "graphics/sgb_animation_logo.inc"
uint32_t *output = gb->screen;
if (gb->border_mode != GB_BORDER_NEVER) {
output += 48 + 40 * 256;
}
uint8_t *input = animation_logo;
unsigned fade_blue = 0;
unsigned fade_red = 0;
@ -453,22 +594,21 @@ static void render_boot_animation (GB_gameboy_t *gb)
input++;
}
}
if (gb->border_mode != GB_BORDER_NEVER) {
output += 256 - 160;
}
}
}
static void render_jingle(GB_gameboy_t *gb, size_t count);
void GB_sgb_render(GB_gameboy_t *gb)
{
if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++;
if (!gb->screen || !gb->rgb_encode_callback) return;
if (gb->sgb->mask_mode != MASK_FREEZE) {
memcpy(gb->sgb->effective_screen_buffer,
gb->sgb->screen_buffer,
sizeof(gb->sgb->effective_screen_buffer));
if (gb->apu_output.sample_rate) {
render_jingle(gb, gb->apu_output.sample_rate / GB_get_usual_frame_rate(gb));
}
if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++;
if (gb->sgb->vram_transfer_countdown) {
if (--gb->sgb->vram_transfer_countdown == 0) {
if (gb->sgb->transfer_dest == TRANSFER_LOW_TILES || gb->sgb->transfer_dest == TRANSFER_HIGH_TILES) {
@ -530,16 +670,27 @@ void GB_sgb_render(GB_gameboy_t *gb)
}
}
if (!gb->screen || !gb->rgb_encode_callback || gb->disable_rendering) return;
uint32_t colors[4 * 4];
for (unsigned i = 0; i < 4 * 4; i++) {
colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]);
}
if (gb->sgb->mask_mode != MASK_FREEZE) {
memcpy(gb->sgb->effective_screen_buffer,
gb->sgb->screen_buffer,
sizeof(gb->sgb->effective_screen_buffer));
}
if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) {
render_boot_animation(gb);
}
else {
uint32_t *output = &gb->screen[48 + 40 * 256];
uint32_t *output = gb->screen;
if (gb->border_mode != GB_BORDER_NEVER) {
output += 48 + 40 * 256;
}
uint8_t *input = gb->sgb->effective_screen_buffer;
switch ((mask_mode_t) gb->sgb->mask_mode) {
case MASK_DISABLED:
@ -549,8 +700,10 @@ void GB_sgb_render(GB_gameboy_t *gb)
uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3;
*(output++) = colors[(*(input++) & 3) + palette * 4];
}
if (gb->border_mode != GB_BORDER_NEVER) {
output += 256 - 160;
}
}
break;
}
case MASK_BLACK:
@ -560,8 +713,10 @@ void GB_sgb_render(GB_gameboy_t *gb)
for (unsigned x = 0; x < 160; x++) {
*(output++) = black;
}
if (gb->border_mode != GB_BORDER_NEVER) {
output += 256 - 160;
}
}
break;
}
case MASK_COLOR_0:
@ -570,8 +725,10 @@ void GB_sgb_render(GB_gameboy_t *gb)
for (unsigned x = 0; x < 160; x++) {
*(output++) = colors[0];
}
if (gb->border_mode != GB_BORDER_NEVER) {
output += 256 - 160;
}
}
break;
}
}
@ -607,6 +764,9 @@ void GB_sgb_render(GB_gameboy_t *gb)
if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) {
gb_area = true;
}
else if (gb->border_mode == GB_BORDER_NEVER) {
continue;
}
uint16_t tile = gb->sgb->border.map[tile_x + tile_y * 32];
uint8_t flip_x = (tile & 0x4000)? 0x7 : 0;
uint8_t flip_y = (tile & 0x8000)? 0x7 : 0;
@ -614,12 +774,19 @@ void GB_sgb_render(GB_gameboy_t *gb)
for (unsigned y = 0; y < 8; y++) {
for (unsigned x = 0; x < 8; x++) {
uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF;
if (color == 0) {
if (gb_area) continue;
gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = colors[0];
uint32_t *output = gb->screen;
if (gb->border_mode == GB_BORDER_NEVER) {
output += (tile_x - 6) * 8 + x + ((tile_y - 5) * 8 + y) * 160;
}
else {
gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[color + palette * 16];
output += tile_x * 8 + x + (tile_y * 8 + y) * 256;
}
if (color == 0) {
if (gb_area) continue;
*output = colors[0];
}
else {
*output = border_colors[color + palette * 16];
}
}
}
@ -630,12 +797,12 @@ void GB_sgb_render(GB_gameboy_t *gb)
void GB_sgb_load_default_data(GB_gameboy_t *gb)
{
#include "sgb_border.inc"
#include "graphics/sgb_border.inc"
memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap));
memcpy(gb->sgb->border.palette, palette, sizeof(palette));
/* Expend tileset */
/* Expand tileset */
for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) {
for (unsigned y = 0; y < 8; y++) {
for (unsigned x = 0; x < 8; x++) {
@ -658,10 +825,10 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb)
/* Re-center */
memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0]));
}
gb->sgb->effective_palettes[0] = 0x639E;
gb->sgb->effective_palettes[1] = 0x263A;
gb->sgb->effective_palettes[2] = 0x10D4;
gb->sgb->effective_palettes[3] = 0x2866;
gb->sgb->effective_palettes[0] = built_in_palettes[0];
gb->sgb->effective_palettes[1] = built_in_palettes[1];
gb->sgb->effective_palettes[2] = built_in_palettes[2];
gb->sgb->effective_palettes[3] = built_in_palettes[3];
}
static double fm_synth(double phase)
@ -682,10 +849,10 @@ static double fm_sweep(double phase)
}
static double random_double(void)
{
return ((random() % 0x10001) - 0x8000) / (double) 0x8000;
return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000;
}
bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
static void render_jingle(GB_gameboy_t *gb, size_t count)
{
const double frequencies[7] = {
466.16, // Bb4
@ -697,15 +864,17 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
1567.98, // G6
};
assert(gb->apu_output.sample_callback);
if (gb->sgb->intro_animation < 0) {
GB_sample_t sample = {0, 0};
for (unsigned i = 0; i < count; i++) {
dest->left = dest->right = 0;
dest++;
gb->apu_output.sample_callback(gb, &sample);
}
return true;
return;
}
if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return false;
if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return;
signed jingle_stage = (gb->sgb->intro_animation - 64) / 3;
double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate;
@ -714,6 +883,7 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
sweep_cutoff_ratio = 1;
}
GB_sample_t stereo;
for (unsigned i = 0; i < count; i++) {
double sample = 0;
for (signed f = 0; f < 7 && f < jingle_stage; f++) {
@ -734,10 +904,10 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8;
}
dest->left = dest->right = sample * 0x7000;
dest++;
stereo.left = stereo.right = sample * 0x7000;
gb->apu_output.sample_callback(gb, &stereo);
}
return true;
return;
}

View File

@ -5,6 +5,16 @@
#include <stdbool.h>
typedef struct GB_sgb_s GB_sgb_t;
typedef struct {
uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/
union {
struct {
uint16_t map[32 * 32];
uint16_t palette[16 * 4];
};
uint16_t raw_data[0x440];
};
} GB_sgb_border_t;
#ifdef GB_INTERNAL
struct GB_sgb_s {
@ -29,16 +39,7 @@ struct GB_sgb_s {
uint8_t vram_transfer_countdown, transfer_dest;
/* Border */
struct {
uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/
union {
struct {
uint16_t map[32 * 32];
uint16_t palette[16 * 4];
};
uint16_t raw_data[0x440];
};
} border, pending_border;
GB_sgb_border_t border, pending_border;
uint8_t border_animation;
/* Colorization */
@ -49,12 +50,17 @@ struct GB_sgb_s {
/* Intro */
int16_t intro_animation;
/* GB Header */
uint8_t received_header[0x54];
/* Multiplayer (cont) */
bool mlt_lock;
};
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value);
void GB_sgb_render(GB_gameboy_t *gb);
void GB_sgb_load_default_data(GB_gameboy_t *gb);
bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count);
#endif

View File

@ -18,6 +18,9 @@ typedef enum {
GB_CONFLICT_STAT_DMG,
GB_CONFLICT_PALETTE_DMG,
GB_CONFLICT_PALETTE_CGB,
GB_CONFLICT_DMG_LCDC,
GB_CONFLICT_SGB_LCDC,
GB_CONFLICT_WX,
} GB_conflict_t;
/* Todo: How does double speed mode affect these? */
@ -29,25 +32,42 @@ static const GB_conflict_t cgb_conflict_map[0x80] = {
[GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB,
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB,
/* Todo: most values not verified, and probably differ between revisions */
};
/* Todo: verify on an MGB */
static const GB_conflict_t dmg_conflict_map[0x80] = {
[GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
[GB_IO_LYC] = GB_CONFLICT_READ_OLD,
[GB_IO_LCDC] = GB_CONFLICT_READ_NEW,
[GB_IO_LCDC] = GB_CONFLICT_DMG_LCDC,
[GB_IO_SCY] = GB_CONFLICT_READ_NEW,
[GB_IO_STAT] = GB_CONFLICT_STAT_DMG,
/* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */
[GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG,
[GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG,
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG,
[GB_IO_WY] = GB_CONFLICT_READ_OLD,
[GB_IO_WX] = GB_CONFLICT_WX,
/* Todo: these were not verified at all */
[GB_IO_SCX] = GB_CONFLICT_READ_NEW,
};
/* Todo: Verify on an SGB1 */
static const GB_conflict_t sgb_conflict_map[0x80] = {
[GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
[GB_IO_LYC] = GB_CONFLICT_READ_OLD,
[GB_IO_LCDC] = GB_CONFLICT_SGB_LCDC,
[GB_IO_SCY] = GB_CONFLICT_READ_NEW,
[GB_IO_STAT] = GB_CONFLICT_STAT_DMG,
[GB_IO_BGP] = GB_CONFLICT_READ_NEW,
[GB_IO_OBP0] = GB_CONFLICT_READ_NEW,
[GB_IO_OBP1] = GB_CONFLICT_READ_NEW,
[GB_IO_WY] = GB_CONFLICT_READ_OLD,
[GB_IO_WX] = GB_CONFLICT_WX,
/* Todo: these were not verified at all */
[GB_IO_WY] = GB_CONFLICT_READ_NEW,
[GB_IO_WX] = GB_CONFLICT_READ_NEW,
[GB_IO_SCX] = GB_CONFLICT_READ_NEW,
};
@ -92,7 +112,17 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
assert(gb->pending_cycles);
GB_conflict_t conflict = GB_CONFLICT_READ_OLD;
if ((addr & 0xFF80) == 0xFF00) {
conflict = (GB_is_cgb(gb)? cgb_conflict_map : dmg_conflict_map)[addr & 0x7F];
const GB_conflict_t *map = NULL;
if (GB_is_cgb(gb)) {
map = cgb_conflict_map;
}
else if (GB_is_sgb(gb)) {
map = sgb_conflict_map;
}
else {
map = dmg_conflict_map;
}
conflict = map[addr & 0x7F];
}
switch (conflict) {
case GB_CONFLICT_READ_OLD:
@ -164,6 +194,53 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->pending_cycles = 6;
return;
}
case GB_CONFLICT_DMG_LCDC: {
/* Similar to the palette registers, these interact directly with the LCD, so they appear to be affected by it. Both my DMG (B, blob) and Game Boy Light behave this way though.
Additionally, LCDC.1 is very nasty because on the it is read both by the FIFO when popping pixels,
and the sprite-fetching state machine, and both behave differently when it comes to access conflicts.
Hacks ahead.
*/
uint8_t old_value = GB_read_memory(gb, addr);
GB_advance_cycles(gb, gb->pending_cycles - 2);
if (/* gb->model != GB_MODEL_MGB && */ gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) {
old_value &= ~2;
}
GB_write_memory(gb, addr, old_value | (value & 1));
GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 5;
return;
}
case GB_CONFLICT_SGB_LCDC: {
/* Simplified version of the above */
uint8_t old_value = GB_read_memory(gb, addr);
GB_advance_cycles(gb, gb->pending_cycles - 2);
/* Hack to force aborting object fetch */
GB_write_memory(gb, addr, value);
GB_write_memory(gb, addr, old_value);
GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 5;
return;
}
case GB_CONFLICT_WX:
GB_advance_cycles(gb, gb->pending_cycles);
GB_write_memory(gb, addr, value);
gb->wx_just_changed = true;
GB_advance_cycles(gb, 1);
gb->wx_just_changed = false;
gb->pending_cycles = 3;
return;
}
}
@ -208,6 +285,26 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode)
{
}
static void enter_stop_mode(GB_gameboy_t *gb)
{
gb->stopped = true;
gb->oam_ppu_blocked = !gb->oam_read_blocked;
gb->vram_ppu_blocked = !gb->vram_read_blocked;
gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked;
}
static void leave_stop_mode(GB_gameboy_t *gb)
{
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x200; i--;) {
GB_advance_cycles(gb, 0x10);
}
gb->stopped = false;
gb->oam_ppu_blocked = false;
gb->vram_ppu_blocked = false;
gb->cgb_palettes_ppu_blocked = false;
}
static void stop(GB_gameboy_t *gb, uint8_t opcode)
{
if (gb->io_registers[GB_IO_KEY1] & 0x1) {
@ -224,9 +321,8 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
gb->cgb_double_speed ^= true;
gb->io_registers[GB_IO_KEY1] = 0;
for (unsigned i = 0x800; i--;) {
GB_advance_cycles(gb, 0x40);
}
enter_stop_mode(gb);
leave_stop_mode(gb);
if (!needs_alignment) {
GB_advance_cycles(gb, 0x4);
@ -234,7 +330,16 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
}
else {
gb->stopped = true;
GB_timing_sync(gb);
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
/* HW Bug? When STOP is executed while a button is down, the CPU halts forever
yet the other hardware keeps running. */
gb->interrupt_enable = 0;
gb->halted = true;
}
else {
enter_stop_mode(gb);
}
}
/* Todo: is PC being actually read? */
@ -281,7 +386,7 @@ static void inc_hr(GB_gameboy_t *gb, uint8_t opcode)
uint8_t register_id;
register_id = ((opcode >> 4) + 1) & 0x03;
gb->registers[register_id] += 0x100;
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
if ((gb->registers[register_id] & 0x0F00) == 0) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
@ -297,7 +402,7 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode)
register_id = ((opcode >> 4) + 1) & 0x03;
gb->registers[register_id] -= 0x100;
gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG;
if ((gb->registers[register_id] & 0x0F00) == 0xF00) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
@ -347,7 +452,7 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode)
addr = cycle_read_inc_oam_bug(gb, gb->pc++);
addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8;
cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF);
cycle_write(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8);
cycle_write(gb, addr + 1, gb->registers[GB_REGISTER_SP] >> 8);
}
static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode)
@ -359,14 +464,14 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode)
register_id = (opcode >> 4) + 1;
rr = gb->registers[register_id];
gb->registers[GB_REGISTER_HL] = hl + rr;
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG);
/* The meaning of the Half Carry flag is really hard to track -_- */
if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if ( ((unsigned long) hl + (unsigned long) rr) & 0x10000) {
if ( ((unsigned) hl + (unsigned) rr) & 0x10000) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -395,7 +500,7 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode)
value = (gb->registers[register_id] & 0xFF) + 1;
gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value;
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
if ((gb->registers[register_id] & 0x0F) == 0) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
@ -415,7 +520,7 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode)
gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value;
gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG;
if ((gb->registers[register_id] & 0x0F) == 0xF) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
@ -496,7 +601,7 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode)
gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG);
if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) {
if (gb->registers[GB_REGISTER_AF] & GB_SUBTRACT_FLAG) {
if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) {
result = (result - 0x06) & 0xFF;
}
@ -530,19 +635,19 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode)
static void cpl(GB_gameboy_t *gb, uint8_t opcode)
{
gb->registers[GB_REGISTER_AF] ^= 0xFF00;
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG;
}
static void scf(GB_gameboy_t *gb, uint8_t opcode)
{
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG);
gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG);
}
static void ccf(GB_gameboy_t *gb, uint8_t opcode)
{
gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG;
gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG);
gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG);
}
static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode)
@ -573,7 +678,7 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode)
value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1;
cycle_write(gb, gb->registers[GB_REGISTER_HL], value);
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
if ((value & 0x0F) == 0) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
@ -590,7 +695,7 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode)
cycle_write(gb, gb->registers[GB_REGISTER_HL], value);
gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG;
if ((value & 0x0F) == 0x0F) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
@ -606,7 +711,7 @@ static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode)
cycle_write(gb, gb->registers[GB_REGISTER_HL], data);
}
uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode)
static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode)
{
uint8_t src_register_id;
uint8_t src_low;
@ -684,6 +789,13 @@ LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL
LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a)
LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a)
// fire the debugger if software breakpoints are enabled
static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode)
{
if (gb->has_software_breakpoints) {
gb->debug_stopped = true;
}
}
static void add_a_r(GB_gameboy_t *gb, uint8_t opcode)
{
@ -697,7 +809,7 @@ static void add_a_r(GB_gameboy_t *gb, uint8_t opcode)
if ((a & 0xF) + (value & 0xF) > 0x0F) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if (((unsigned long) a) + ((unsigned long) value) > 0xFF) {
if (((unsigned) a) + ((unsigned) value) > 0xFF) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -716,7 +828,7 @@ static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode)
if ((a & 0xF) + (value & 0xF) + carry > 0x0F) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) {
if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -726,7 +838,7 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode)
uint8_t value, a;
value = get_src_value(gb, opcode);
a = gb->registers[GB_REGISTER_AF] >> 8;
gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG;
if (a == value) {
gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
}
@ -744,7 +856,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode)
value = get_src_value(gb, opcode);
a = gb->registers[GB_REGISTER_AF] >> 8;
carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG;
if ((uint8_t) (a - value - carry) == 0) {
gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
@ -752,7 +864,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode)
if ((a & 0xF) < (value & 0xF) + carry) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) {
if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -796,7 +908,7 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode)
value = get_src_value(gb, opcode);
a = gb->registers[GB_REGISTER_AF] >> 8;
gb->registers[GB_REGISTER_AF] &= 0xFF00;
gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG;
if (a == value) {
gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
}
@ -812,10 +924,7 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode)
{
assert(gb->pending_cycles == 4);
gb->pending_cycles = 0;
GB_advance_cycles(gb, 1);
GB_advance_cycles(gb, 1);
GB_advance_cycles(gb, 1);
GB_advance_cycles(gb, 1);
GB_advance_cycles(gb, 4);
gb->halted = true;
/* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */
@ -896,7 +1005,7 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode)
if ((a & 0xF) + (value & 0xF) > 0x0F) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if (((unsigned long) a) + ((unsigned long) value) > 0xFF) {
if (((unsigned) a) + ((unsigned) value) > 0xFF) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -915,7 +1024,7 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode)
if ((a & 0xF) + (value & 0xF) + carry > 0x0F) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) {
if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -925,7 +1034,7 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode)
uint8_t value, a;
value = cycle_read_inc_oam_bug(gb, gb->pc++);
a = gb->registers[GB_REGISTER_AF] >> 8;
gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG;
if (a == value) {
gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
}
@ -943,7 +1052,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode)
value = cycle_read_inc_oam_bug(gb, gb->pc++);
a = gb->registers[GB_REGISTER_AF] >> 8;
carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG;
if ((uint8_t) (a - value - carry) == 0) {
gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
@ -951,7 +1060,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode)
if ((a & 0xF) < (value & 0xF) + carry) {
gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
}
if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) {
if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) {
gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
}
}
@ -995,7 +1104,7 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode)
value = cycle_read_inc_oam_bug(gb, gb->pc++);
a = gb->registers[GB_REGISTER_AF] >> 8;
gb->registers[GB_REGISTER_AF] &= 0xFF00;
gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG;
if (a == value) {
gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
}
@ -1117,7 +1226,7 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode)
uint16_t addr;
gb->registers[GB_REGISTER_AF] &= 0xFF;
addr = cycle_read_inc_oam_bug(gb, gb->pc++);
addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8 ;
addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8;
gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8;
}
@ -1305,10 +1414,10 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode)
}
}
else if ((opcode & 0xC0) == 0x80) { /* res */
set_src_value(gb, opcode, value & ~bit) ;
set_src_value(gb, opcode, value & ~bit);
}
else if ((opcode & 0xC0) == 0xC0) { /* set */
set_src_value(gb, opcode, value | bit) ;
set_src_value(gb, opcode, value | bit);
}
}
@ -1357,7 +1466,7 @@ static GB_opcode_t *opcodes[256] = {
jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl,
jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */
jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf,
nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */
ld_b_b, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */
ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a,
ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */
ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a,
@ -1389,10 +1498,19 @@ void GB_cpu_run(GB_gameboy_t *gb)
return;
}
if (gb->stopped) {
GB_advance_cycles(gb, 64);
GB_timing_sync(gb);
GB_advance_cycles(gb, 4);
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
leave_stop_mode(gb);
GB_advance_cycles(gb, 8);
}
return;
}
if ((gb->interrupt_enable & 0x10) && (gb->ime || gb->halted)) {
GB_timing_sync(gb);
}
if (gb->halted && !GB_is_cgb(gb) && !gb->just_halted) {
GB_advance_cycles(gb, 2);
}
@ -1404,19 +1522,19 @@ void GB_cpu_run(GB_gameboy_t *gb)
}
gb->just_halted = false;
bool effecitve_ime = gb->ime;
bool effective_ime = gb->ime;
if (gb->ime_toggle) {
gb->ime = !gb->ime;
gb->ime_toggle = false;
}
/* Wake up from HALT mode without calling interrupt code. */
if (gb->halted && !effecitve_ime && interrupt_queue) {
if (gb->halted && !effective_ime && interrupt_queue) {
gb->halted = false;
}
/* Call interrupt */
else if (effecitve_ime && interrupt_queue) {
else if (effective_ime && interrupt_queue) {
gb->halted = false;
uint16_t call_addr = gb->pc;
@ -1453,7 +1571,7 @@ void GB_cpu_run(GB_gameboy_t *gb)
GB_debugger_call_hook(gb, call_addr);
}
/* Run mode */
else if(!gb->halted) {
else if (!gb->halted) {
gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++);
if (gb->halt_bug) {
gb->pc--;
@ -1462,10 +1580,11 @@ void GB_cpu_run(GB_gameboy_t *gb)
opcodes[gb->last_opcode_read](gb, gb->last_opcode_read);
}
flush_pending_cycles(gb);
if (gb->hdma_starting) {
gb->hdma_starting = false;
gb->hdma_on = true;
gb->hdma_cycles = -8;
}
flush_pending_cycles(gb);
}

View File

@ -97,7 +97,8 @@ static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
GB_log(gb, "RLA\n");
}
static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){
static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint16_t addr;
(*pc)++;
addr = GB_read_memory(gb, (*pc)++);

View File

@ -28,8 +28,6 @@ GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const c
{
size_t index = GB_map_find_symbol_index(map, addr);
if (index < map->n_symbols && map->symbols[index].addr == addr) return NULL;
map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0]));
memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0]));
map->symbols[index].addr = addr;
@ -71,9 +69,9 @@ void GB_map_free(GB_symbol_map_t *map)
free(map);
}
static int hash_name(const char *name)
static unsigned hash_name(const char *name)
{
int r = 0;
unsigned r = 0;
while (*name) {
r <<= 1;
if (r & 0x400) {
@ -87,7 +85,7 @@ static int hash_name(const char *name)
void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol)
{
int hash = hash_name(bank_symbol->name);
unsigned hash = hash_name(bank_symbol->name);
GB_symbol_t *symbol = malloc(sizeof(*symbol));
symbol->name = bank_symbol->name;
symbol->addr = bank_symbol->addr;
@ -98,7 +96,7 @@ void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB
const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name)
{
int hash = hash_name(name);
unsigned hash = hash_name(name);
GB_symbol_t *symbol = map->buckets[hash];
while (symbol) {

View File

@ -3,14 +3,14 @@
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif
#include <Windows.h>
#include <windows.h>
#else
#include <sys/time.h>
#endif
static const unsigned int GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128};
static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128};
#ifndef DISABLE_TIMEKEEPING
#ifndef GB_DISABLE_TIMEKEEPING
static int64_t get_nanoseconds(void)
{
#ifndef _WIN32
@ -59,7 +59,7 @@ void GB_timing_sync(GB_gameboy_t *gb)
return;
}
/* Prevent syncing if not enough time has passed.*/
if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return;
if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return;
uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
int64_t nanoseconds = get_nanoseconds();
@ -73,6 +73,9 @@ void GB_timing_sync(GB_gameboy_t *gb)
}
gb->cycles_since_last_sync = 0;
if (gb->update_input_hint_callback) {
gb->update_input_hint_callback(gb);
}
}
#else
@ -136,6 +139,11 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles)
{
if (gb->stopped) {
gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
return;
}
GB_STATE_MACHINE(gb, div, cycles, 1) {
GB_STATE(gb, div, 1);
GB_STATE(gb, div, 2);
@ -207,11 +215,14 @@ static void advance_serial(GB_gameboy_t *gb, uint8_t cycles)
void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
{
gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right
// Affected by speed boost
gb->dma_cycles += cycles;
GB_timers_run(gb, cycles);
advance_serial(gb, cycles);
if (!gb->stopped) {
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
}
gb->debugger_ticks += cycles;
@ -227,8 +238,18 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
gb->cycles_since_input_ir_change += cycles;
gb->cycles_since_last_sync += cycles;
gb->cycles_since_run += cycles;
if (gb->rumble_state) {
gb->rumble_on_cycles++;
}
else {
gb->rumble_off_cycles++;
}
if (!gb->stopped) { // TODO: Verify what happens in STOP mode
GB_dma_run(gb);
GB_hdma_run(gb);
}
GB_apu_run(gb);
GB_display_run(gb, cycles);
GB_ir_run(gb);
@ -244,8 +265,8 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac)
/* Glitch only happens when old_tac is enabled. */
if (!(old_tac & 4)) return;
unsigned int old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3];
unsigned int new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3];
unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3];
unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3];
/* The bit used for overflow testing must have been 1 */
if (gb->div_counter & old_clocks) {
@ -258,23 +279,30 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac)
void GB_rtc_run(GB_gameboy_t *gb)
{
if (gb->cartridge_type->mbc_type == GB_HUC3) {
time_t current_time = time(NULL);
while (gb->last_rtc_second / 60 < current_time / 60) {
gb->last_rtc_second += 60;
gb->huc3_minutes++;
if (gb->huc3_minutes == 60 * 24) {
gb->huc3_days++;
gb->huc3_minutes = 0;
}
}
return;
}
if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */
time_t current_time = time(NULL);
while (gb->last_rtc_second < current_time) {
gb->last_rtc_second++;
if (++gb->rtc_real.seconds == 60)
{
if (++gb->rtc_real.seconds == 60) {
gb->rtc_real.seconds = 0;
if (++gb->rtc_real.minutes == 60)
{
if (++gb->rtc_real.minutes == 60) {
gb->rtc_real.minutes = 0;
if (++gb->rtc_real.hours == 24)
{
if (++gb->rtc_real.hours == 24) {
gb->rtc_real.hours = 0;
if (++gb->rtc_real.days == 0)
{
if (gb->rtc_real.high & 1) /* Bit 8 of days*/
{
if (++gb->rtc_real.days == 0) {
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
gb->rtc_real.high |= 0x80; /* Overflow bit */
}
gb->rtc_real.high ^= 1;

View File

@ -15,7 +15,6 @@ enum {
GB_TIMA_RELOADED = 2
};
#define GB_HALT_VALUE (0xFFFF)
#define GB_SLEEP(gb, unit, state, cycles) do {\
(gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \
@ -26,12 +25,10 @@ enum {
}\
} while (0)
#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE
#define GB_STATE_MACHINE(gb, unit, cycles, divisor) \
static const int __state_machine_divisor = divisor;\
(gb)->unit##_cycles += cycles; \
if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\
if ((gb)->unit##_cycles <= 0) {\
return;\
}\
switch ((gb)->unit##_state)

View File

@ -4,7 +4,7 @@
#import <libkern/OSAtomic.h>
#define HFDEFAULT_FONT (@"Monaco")
#define HFDEFAULT_FONTSIZE ((CGFloat)10.)
#define HFDEFAULT_FONTSIZE ((CGFloat)11.)
#define HFZeroRange (HFRange){0, 0}

View File

@ -57,8 +57,8 @@ static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
interiorShadowEdge = NSMaxXEdge;
_borderedEdges = (1 << NSMaxXEdge);
_borderColor = [[NSColor darkGrayColor] retain];
_backgroundColor = [[NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
_borderColor = [[NSColor controlShadowColor] retain];
_backgroundColor = [[NSColor windowBackgroundColor] retain];
}
return self;
}
@ -82,8 +82,8 @@ static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
lineNumberFormat = (HFLineNumberFormat)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
_borderedEdges = [coder decodeObjectForKey:@"HFBorderedEdges"] ? (NSInteger)[coder decodeInt64ForKey:@"HFBorderedEdges"] : 0;
_borderColor = [[coder decodeObjectForKey:@"HFBorderColor"] ?: [NSColor darkGrayColor] retain];
_backgroundColor = [[coder decodeObjectForKey:@"HFBackgroundColor"] ?: [NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
_borderColor = [[NSColor controlShadowColor] retain];
_backgroundColor = [[NSColor windowBackgroundColor] retain];
return self;
}

View File

@ -119,20 +119,6 @@
[super viewWillMoveToWindow:newWindow];
}
- (void)drawGradientWithClip:(NSRect)clip {
[_representer.backgroundColor set];
NSRectFill(clip);
NSInteger shadowEdge = _representer.interiorShadowEdge;
if (shadowEdge >= 0) {
const CGFloat shadowWidth = 6;
NSWindow *window = self.window;
BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]);
HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip);
}
}
- (void)drawDividerWithClip:(NSRect)clipRect {
USE(clipRect);
@ -267,7 +253,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
NSUInteger glyphCount;
[textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
if (previousTextStorageCharacterCount == 0) {
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil];
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, nil];
[textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
[atts release];
}
@ -305,7 +291,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
@ -456,12 +442,12 @@ static inline int common_prefix_length(const char *a, const char *b) {
}
}
if (! textAttributes) {
if (!textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
[textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
}
@ -489,27 +475,28 @@ static inline int common_prefix_length(const char *a, const char *b) {
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1.5;
NSRect textRect = self.bounds;
textRect.size.width -= 5;
textRect.origin.y -= verticalOffset;
textRect.size.height += verticalOffset;
textRect.size.height += verticalOffset + _lineHeight;
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
[mutableStyle setMinimumLineHeight:_lineHeight];
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
NSColor *color = [[NSColor textColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
color = [NSColor colorWithRed:color.redComponent green:color.greenComponent blue:color.blueComponent alpha:0.75];
NSDictionary *_textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:_font.fontName size:9], NSFontAttributeName, color, NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
[string drawInRect:textRect withAttributes:textAttributes];
[string drawInRect:textRect withAttributes:_textAttributes];
[string release];
[_textAttributes release];
}
- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
@ -533,7 +520,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
@ -568,7 +555,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
#if TIME_LINE_NUMBERS
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif
NSInteger drawingMode = (useStringDrawingPath ? 1 : 3);
NSInteger drawingMode = 3; // (useStringDrawingPath ? 1 : 3);
switch (drawingMode) {
// Drawing can't be done right if fonts are wider than expected, but all
// of these have rather nasty behavior in that case. I've commented what
@ -606,7 +593,6 @@ static inline int common_prefix_length(const char *a, const char *b) {
}
- (void)drawRect:(NSRect)clipRect {
[self drawGradientWithClip:clipRect];
[self drawDividerWithClip:clipRect];
[self drawLineNumbersWithClip:clipRect];
}

View File

@ -441,7 +441,7 @@ enum LineCoverage_t {
- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect {
NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect);
if (! NSIsEmptyRect(caretRect)) {
[[NSColor blackColor] set];
[[NSColor controlTextColor] set];
NSRectFill(caretRect);
lastDrawnCaretRect = caretRect;
}
@ -456,12 +456,18 @@ enum LineCoverage_t {
/* This is the color when we are not in the key window */
- (NSColor *)inactiveTextSelectionColor {
if (@available(macOS 10.14, *)) {
return [NSColor unemphasizedSelectedTextBackgroundColor];
}
return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
}
/* This is the color when we are not the first responder, but we are in the key window */
- (NSColor *)secondaryTextSelectionColor {
return [[self primaryTextSelectionColor] blendedColorWithFraction:.66 ofColor:[NSColor colorWithCalibratedWhite:.8f alpha:1]];
if (@available(macOS 10.14, *)) {
return [NSColor unemphasizedSelectedTextBackgroundColor];
}
return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
}
- (NSColor *)textSelectionColor {
@ -826,7 +832,7 @@ enum LineCoverage_t {
- (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex {
HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init];
[run setRange:NSMakeRange(0, NSUIntegerMax)];
[run setForegroundColor:[NSColor blackColor]];
[run setForegroundColor:[NSColor textColor]];
return [run autorelease];
}
@ -902,8 +908,8 @@ static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count)
guideIndex++;
}
if (rectIndex > 0) {
[[NSColor colorWithCalibratedWhite:(CGFloat).8 alpha:1] set];
NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositePlusDarker);
[[NSColor gridColor] set];
NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositeSourceOver);
}
FREE_ARRAY(lineRects);
}
@ -1344,7 +1350,7 @@ static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count)
[self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)];
[self drawSelectionIfNecessaryWithClip:clip];
NSColor *textColor = [NSColor blackColor];
NSColor *textColor = [NSColor textColor];
[textColor set];
if (! antialias) {

View File

@ -432,7 +432,7 @@ static double distanceMod1(double a, double b) {
// Compute the vertical offset
CGFloat textYOffset = (glyphCount == 1 ? 4 : 5);
// LOL
if ([_label isEqualToString:@"6"] || [_label isEqualToString:@"7"] == 7) textYOffset -= 1;
if ([_label isEqualToString:@"6"]) textYOffset -= 1;
// Apply this text matrix

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