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

This commit is contained in:
Maximilian Mader 2020-09-21 16:12:49 +02:00
commit 405d85343f
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
50 changed files with 1307 additions and 385 deletions

View File

@ -329,101 +329,103 @@ FirstChecksumWithDuplicate:
ChecksumsEnd: ChecksumsEnd:
PalettePerChecksum: PalettePerChecksum:
; | $80 means game requires DMG boot tilemap palette_index: MACRO ; palette, flags
db 0 ; Default Palette db ((\1) * 3) | (\2) ; | $80 means game requires DMG boot tilemap
db 4 ; ALLEY WAY ENDM
db 5 ; YAKUMAN palette_index 0, 0 ; Default Palette
db 35 ; BASEBALL, (Game and Watch 2) palette_index 4, 0 ; ALLEY WAY
db 34 ; TENNIS palette_index 5, 0 ; YAKUMAN
db 3 ; TETRIS palette_index 35, 0 ; BASEBALL, (Game and Watch 2)
db 31 ; QIX palette_index 34, 0 ; TENNIS
db 15 ; DR.MARIO palette_index 3, 0 ; TETRIS
db 10 ; RADARMISSION palette_index 31, 0 ; QIX
db 5 ; F1RACE palette_index 15, 0 ; DR.MARIO
db 19 ; YOSSY NO TAMAGO palette_index 10, 0 ; RADARMISSION
db 36 ; palette_index 5, 0 ; F1RACE
db 7 | $80 ; X palette_index 19, 0 ; YOSSY NO TAMAGO
db 37 ; MARIOLAND2 palette_index 36, 0 ;
db 30 ; YOSSY NO COOKIE palette_index 7, $80 ; X
db 44 ; ZELDA palette_index 37, 0 ; MARIOLAND2
db 21 ; palette_index 30, 0 ; YOSSY NO COOKIE
db 32 ; palette_index 44, 0 ; ZELDA
db 31 ; TETRIS FLASH palette_index 21, 0 ;
db 20 ; DONKEY KONG palette_index 32, 0 ;
db 5 ; MARIO'S PICROSS palette_index 31, 0 ; TETRIS FLASH
db 33 ; palette_index 20, 0 ; DONKEY KONG
db 13 ; POKEMON RED, (GAMEBOYCAMERA G) palette_index 5, 0 ; MARIO'S PICROSS
db 14 ; POKEMON GREEN palette_index 33, 0 ;
db 5 ; PICROSS 2 palette_index 13, 0 ; POKEMON RED, (GAMEBOYCAMERA G)
db 29 ; YOSSY NO PANEPON palette_index 14, 0 ; POKEMON GREEN
db 5 ; KIRAKIRA KIDS palette_index 5, 0 ; PICROSS 2
db 18 ; GAMEBOY GALLERY palette_index 29, 0 ; YOSSY NO PANEPON
db 9 ; POCKETCAMERA palette_index 5, 0 ; KIRAKIRA KIDS
db 3 ; palette_index 18, 0 ; GAMEBOY GALLERY
db 2 ; BALLOON KID palette_index 9, 0 ; POCKETCAMERA
db 26 ; KINGOFTHEZOO palette_index 3, 0 ;
db 25 ; DMG FOOTBALL palette_index 2, 0 ; BALLOON KID
db 25 ; WORLD CUP palette_index 26, 0 ; KINGOFTHEZOO
db 41 ; OTHELLO palette_index 25, 0 ; DMG FOOTBALL
db 42 ; SUPER RC PRO-AM palette_index 25, 0 ; WORLD CUP
db 26 ; DYNABLASTER palette_index 41, 0 ; OTHELLO
db 45 ; BOY AND BLOB GB2 palette_index 42, 0 ; SUPER RC PRO-AM
db 42 ; MEGAMAN palette_index 26, 0 ; DYNABLASTER
db 45 ; STAR WARS-NOA palette_index 45, 0 ; BOY AND BLOB GB2
db 36 ; palette_index 42, 0 ; MEGAMAN
db 38 ; WAVERACE palette_index 45, 0 ; STAR WARS-NOA
db 26 ; palette_index 36, 0 ;
db 42 ; LOLO2 palette_index 38, 0 ; WAVERACE
db 30 ; YOSHI'S COOKIE palette_index 26, 0 ;
db 41 ; MYSTIC QUEST palette_index 42, 0 ; LOLO2
db 34 ; palette_index 30, 0 ; YOSHI'S COOKIE
db 34 ; TOPRANKINGTENNIS palette_index 41, 0 ; MYSTIC QUEST
db 5 ; MANSELL palette_index 34, 0 ;
db 42 ; MEGAMAN3 palette_index 34, 0 ; TOPRANKINGTENNIS
db 6 ; SPACE INVADERS palette_index 5, 0 ; MANSELL
db 5 ; GAME&WATCH palette_index 42, 0 ; MEGAMAN3
db 33 ; DONKEYKONGLAND95 palette_index 6, 0 ; SPACE INVADERS
db 25 ; ASTEROIDS/MISCMD palette_index 5, 0 ; GAME&WATCH
db 42 ; STREET FIGHTER 2 palette_index 33, 0 ; DONKEYKONGLAND95
db 42 ; DEFENDER/JOUST palette_index 25, 0 ; ASTEROIDS/MISCMD
db 40 ; KILLERINSTINCT95 palette_index 42, 0 ; STREET FIGHTER 2
db 2 ; TETRIS BLAST palette_index 42, 0 ; DEFENDER/JOUST
db 16 ; PINOCCHIO palette_index 40, 0 ; KILLERINSTINCT95
db 25 ; palette_index 2, 0 ; TETRIS BLAST
db 42 ; BA.TOSHINDEN palette_index 16, 0 ; PINOCCHIO
db 42 ; NETTOU KOF 95 palette_index 25, 0 ;
db 5 ; palette_index 42, 0 ; BA.TOSHINDEN
db 0 ; TETRIS PLUS palette_index 42, 0 ; NETTOU KOF 95
db 39 ; DONKEYKONGLAND 3 palette_index 5, 0 ;
db 36 ; palette_index 0, 0 ; TETRIS PLUS
db 22 ; SUPER MARIOLAND palette_index 39, 0 ; DONKEYKONGLAND 3
db 25 ; GOLF palette_index 36, 0 ;
db 6 ; SOLARSTRIKER palette_index 22, 0 ; SUPER MARIOLAND
db 32 ; GBWARS palette_index 25, 0 ; GOLF
db 12 ; KAERUNOTAMENI palette_index 6, 0 ; SOLARSTRIKER
db 36 ; palette_index 32, 0 ; GBWARS
db 11 ; POKEMON BLUE palette_index 12, 0 ; KAERUNOTAMENI
db 39 ; DONKEYKONGLAND palette_index 36, 0 ;
db 18 ; GAMEBOY GALLERY2 palette_index 11, 0 ; POKEMON BLUE
db 39 ; DONKEYKONGLAND 2 palette_index 39, 0 ; DONKEYKONGLAND
db 24 ; KID ICARUS palette_index 18, 0 ; GAMEBOY GALLERY2
db 31 ; TETRIS2 palette_index 39, 0 ; DONKEYKONGLAND 2
db 50 ; palette_index 24, 0 ; KID ICARUS
db 17 ; MOGURANYA palette_index 31, 0 ; TETRIS2
db 46 ; palette_index 50, 0 ;
db 6 ; GALAGA&GALAXIAN palette_index 17, 0 ; MOGURANYA
db 27 ; BT2RAGNAROKWORLD palette_index 46, 0 ;
db 0 ; KEN GRIFFEY JR palette_index 6, 0 ; GALAGA&GALAXIAN
db 47 ; palette_index 27, 0 ; BT2RAGNAROKWORLD
db 41 ; MAGNETIC SOCCER palette_index 0, 0 ; KEN GRIFFEY JR
db 41 ; VEGAS STAKES palette_index 47, 0 ;
db 0 ; palette_index 41, 0 ; MAGNETIC SOCCER
db 0 ; MILLI/CENTI/PEDE palette_index 41, 0 ; VEGAS STAKES
db 19 ; MARIO & YOSHI palette_index 0, 0 ;
db 34 ; SOCCER palette_index 0, 0 ; MILLI/CENTI/PEDE
db 23 ; POKEBOM palette_index 19, 0 ; MARIO & YOSHI
db 18 ; G&W GALLERY palette_index 34, 0 ; SOCCER
db 29 ; TETRIS ATTACK palette_index 23, 0 ; POKEBOM
palette_index 18, 0 ; G&W GALLERY
palette_index 29, 0 ; TETRIS ATTACK
Dups4thLetterArray: Dups4thLetterArray:
db "BEFAARBEKEK R-URAR INAILICE R" db "BEFAARBEKEK R-URAR INAILICE R"

View File

@ -4,6 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h> #include <assert.h>
#include <err.h>
void opts(uint8_t byte, uint8_t *options) void opts(uint8_t byte, uint8_t *options)
{ {
@ -13,6 +14,17 @@ void opts(uint8_t byte, uint8_t *options)
*(options++) = byte & (byte >> 1); *(options++) = byte & (byte >> 1);
} }
void write_all(int fd, const void *buf, size_t count) {
while (count) {
ssize_t written = write(fd, buf, count);
if (written < 0) {
err(1, "write");
}
count -= written;
buf += written;
}
}
int main() int main()
{ {
static uint8_t source[0x4000]; static uint8_t source[0x4000];
@ -76,15 +88,15 @@ int main()
if (bits >= 8) { if (bits >= 8) {
uint8_t outctl = control >> (bits - 8); uint8_t outctl = control >> (bits - 8);
assert(outctl != 1); assert(outctl != 1);
write(STDOUT_FILENO, &outctl, 1); write_all(STDOUT_FILENO, &outctl, 1);
write(STDOUT_FILENO, literals, literals_size); write_all(STDOUT_FILENO, literals, literals_size);
bits -= 8; bits -= 8;
control &= (1 << bits) - 1; control &= (1 << bits) - 1;
literals_size = 0; literals_size = 0;
} }
} }
uint8_t end_byte = 1; uint8_t end_byte = 1;
write(STDOUT_FILENO, &end_byte, 1); write_all(STDOUT_FILENO, &end_byte, 1);
return 0; return 0;
} }

View File

@ -51,8 +51,10 @@
JOYHatsEmulateButtonsKey: @YES, JOYHatsEmulateButtonsKey: @YES,
}]; }];
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
} }
}
- (IBAction)toggleDeveloperMode:(id)sender - (IBAction)toggleDeveloperMode:(id)sender
{ {

30
Cocoa/BigSurToolbar.h Normal file
View File

@ -0,0 +1,30 @@
#import <Cocoa/Cocoa.h>
#ifndef BigSurToolbar_h
#define BigSurToolbar_h
/* Backport the toolbarStyle property to allow compilation with older SDKs*/
#ifndef __MAC_10_16
typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) {
// The default value. The style will be determined by the window's given configuration
NSWindowToolbarStyleAutomatic,
// The toolbar will appear below the window title
NSWindowToolbarStyleExpanded,
// The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible
NSWindowToolbarStylePreference,
// The window title will appear inline with the toolbar when visible
NSWindowToolbarStyleUnified,
// Same as NSWindowToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window
NSWindowToolbarStyleUnifiedCompact
} API_AVAILABLE(macos(11.0));
@interface NSWindow (toolbarStyle)
@property NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0));
@end
@interface NSImage (SFSymbols)
+ (instancetype)imageWithSystemSymbolName:(NSString *)symbolName accessibilityDescription:(NSString *)description API_AVAILABLE(macos(11.0));
@end
#endif
#endif

View File

@ -30,7 +30,6 @@
@property (strong) IBOutlet NSTableView *spritesTableView; @property (strong) IBOutlet NSTableView *spritesTableView;
@property (strong) IBOutlet NSPanel *printerFeedWindow; @property (strong) IBOutlet NSPanel *printerFeedWindow;
@property (strong) IBOutlet NSImageView *feedImageView; @property (strong) IBOutlet NSImageView *feedImageView;
@property (strong) IBOutlet NSButton *feedSaveButton;
@property (strong) IBOutlet NSTextView *debuggerSideViewInput; @property (strong) IBOutlet NSTextView *debuggerSideViewInput;
@property (strong) IBOutlet NSTextView *debuggerSideView; @property (strong) IBOutlet NSTextView *debuggerSideView;
@property (strong) IBOutlet GBSplitView *debuggerSplitView; @property (strong) IBOutlet GBSplitView *debuggerSplitView;

View File

@ -8,6 +8,8 @@
#include "GBMemoryByteArray.h" #include "GBMemoryByteArray.h"
#include "GBWarningPopover.h" #include "GBWarningPopover.h"
#include "GBCheatWindowController.h" #include "GBCheatWindowController.h"
#include "GBTerminalTextFieldCell.h"
#include "BigSurToolbar.h"
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* 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!!! */ /* Todo: Split into category files! This is so messy!!! */
@ -45,7 +47,7 @@ enum model {
bool oamUpdating; bool oamUpdating;
NSMutableData *currentPrinterImageData; NSMutableData *currentPrinterImageData;
enum {GBAccessoryNone, GBAccessoryPrinter} accessory; enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy} accessory;
bool rom_warning_issued; bool rom_warning_issued;
@ -136,6 +138,16 @@ 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]; [self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure];
} }
static void setWorkboyTime(GB_gameboy_t *gb, time_t t)
{
[[NSUserDefaults standardUserDefaults] setInteger:time(NULL) - t forKey:@"GBWorkboyTimeOffset"];
}
static time_t getWorkboyTime(GB_gameboy_t *gb)
{
return time(NULL) - [[NSUserDefaults standardUserDefaults] integerForKey:@"GBWorkboyTimeOffset"];
}
static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
{ {
Document *self = (__bridge Document *)GB_get_user_data(gb); Document *self = (__bridge Document *)GB_get_user_data(gb);
@ -403,6 +415,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
unsigned time_to_alarm = GB_time_to_alarm(&gb); unsigned time_to_alarm = GB_time_to_alarm(&gb);
if (time_to_alarm) { if (time_to_alarm) {
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = (id)[NSApp delegate];
NSUserNotification *notification = [[NSUserNotification alloc] init]; NSUserNotification *notification = [[NSUserNotification alloc] init];
NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension]; NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil]; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil];
@ -546,6 +559,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
self.debuggerSideViewInput.textColor = [NSColor whiteColor]; self.debuggerSideViewInput.textColor = [NSColor whiteColor];
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style; self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"]; [self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateSideView) selector:@selector(updateSideView)
name:NSTextDidChangeNotification name:NSTextDidChangeNotification
@ -563,15 +577,20 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
[self.feedSaveButton removeFromSuperview];
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [[self.fileURL path] lastPathComponent]]; self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [[self.fileURL path] lastPathComponent]];
self.debuggerSplitView.dividerColor = [NSColor clearColor]; self.debuggerSplitView.dividerColor = [NSColor clearColor];
if (@available(macOS 11.0, *)) {
/* contentView.superview.subviews.lastObject is the titlebar view */ self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded;
NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject; self.printerFeedWindow.toolbarStyle = NSWindowToolbarStyleUnifiedCompact;
[titleView addSubview: self.feedSaveButton]; [self.printerFeedWindow.toolbar removeItemAtIndex:1];
self.feedSaveButton.frame = (NSRect){{268, 2}, {48, 17}}; self.printerFeedWindow.toolbar.items.firstObject.image =
[NSImage imageWithSystemSymbolName:@"square.and.arrow.down"
accessibilityDescription:@"Save"];
self.printerFeedWindow.toolbar.items.lastObject.image =
[NSImage imageWithSystemSymbolName:@"printer"
accessibilityDescription:@"Print"];
}
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateHighpassFilter) selector:@selector(updateHighpassFilter)
@ -645,7 +664,6 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{ {
hex_controller = [[HFController alloc] init]; hex_controller = [[HFController alloc] init];
[hex_controller setBytesPerColumn:1]; [hex_controller setBytesPerColumn:1];
[hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]];
[hex_controller setEditMode:HFOverwriteMode]; [hex_controller setEditMode:HFOverwriteMode];
[hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]]; [hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]];
@ -783,6 +801,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
else if ([anItem action] == @selector(connectPrinter:)) { else if ([anItem action] == @selector(connectPrinter:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter]; [(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter];
} }
else if ([anItem action] == @selector(connectWorkboy:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy];
}
else if ([anItem action] == @selector(toggleCheats:)) { else if ([anItem action] == @selector(toggleCheats:)) {
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)]; [(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
} }
@ -1008,6 +1029,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[debugger_input_queue removeObjectAtIndex:0]; [debugger_input_queue removeObjectAtIndex:0];
} }
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
if ((id)input == [NSNull null]) {
return NULL;
}
return input? strdup([input UTF8String]): NULL; return input? strdup([input UTF8String]): NULL;
} }
@ -1632,13 +1656,24 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
scale:2.0]; scale:2.0];
NSRect frame = self.printerFeedWindow.frame; NSRect frame = self.printerFeedWindow.frame;
frame.size = self.feedImageView.image.size; 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; 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 setFrame:frame display:NO animate: self.printerFeedWindow.isVisible];
[self.printerFeedWindow orderFront:NULL]; [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 - (IBAction)savePrinterFeed:(id)sender
{ {
bool shouldResume = running; bool shouldResume = running;
@ -1679,6 +1714,14 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
}]; }];
} }
- (IBAction)connectWorkboy:(id)sender
{
[self performAtomicBlock:^{
accessory = GBAccessoryWorkboy;
GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime);
}];
}
- (void) updateHighpassFilter - (void) updateHighpassFilter
{ {
if (GB_is_inited(&gb)) { if (GB_is_inited(&gb)) {

View File

@ -19,7 +19,6 @@
<outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/> <outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/>
<outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/> <outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/>
<outlet property="feedImageView" destination="Ar0-nN-eop" id="wHa-St-o4G"/> <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="gridButton" destination="fL6-2S-Rgd" id="jtV-jh-GHC"/>
<outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/> <outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/>
<outlet property="memoryBankInput" destination="rdV-q6-hc6" id="KBx-9T-2mX"/> <outlet property="memoryBankInput" destination="rdV-q6-hc6" id="KBx-9T-2mX"/>
@ -116,7 +115,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S"> <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"/> <rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl"> <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"/> <rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
@ -153,7 +152,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC"> <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"/> <rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
@ -187,7 +186,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg"> <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"/> <rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
@ -244,9 +243,9 @@
<toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H"> <toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H">
<nil key="toolTip"/> <nil key="toolTip"/>
<size key="minSize" width="100" height="25"/> <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"> <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"/> <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"> <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"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -507,7 +506,7 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/> <rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <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"/> <rect key="frame" x="0.0" y="0.0" width="512" height="391"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
@ -786,9 +785,10 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/> <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"/> <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"> <view key="contentView" id="RRS-aa-bPT">
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/> <rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" 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"/> <rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
@ -797,20 +797,25 @@
</imageView> </imageView>
</subviews> </subviews>
</view> </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">
<connections>
<action selector="savePrinterFeed:" target="-2" id="Dm3-h0-ch4"/>
</connections>
</toolbarItem>
<toolbarItem implicitItemIdentifier="NSToolbarPrintItem" explicitItemIdentifier="Print" 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"/> <point key="canvasLocation" x="-159" y="356"/>
</window> </window>
<button verticalHuggingPriority="750" id="RLc-0I-sYZ">
<rect key="frame" x="0.5" y="0.0" width="48" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" title="Save" bezelStyle="texturedRounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="shw-MJ-B3T">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="miniSystem"/>
</buttonCell>
<connections>
<action selector="savePrinterFeed:" target="-2" id="Y3g-fU-2te"/>
</connections>
<point key="canvasLocation" x="-507" y="397"/>
</button>
<window title="Cheats" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="4Yb-Np-JrF" customClass="NSPanel"> <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"/> <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/> <rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/>
@ -896,7 +901,7 @@
</connections> </connections>
</textField> </textField>
<button verticalHuggingPriority="750" id="C3V-Ep-bMj"> <button verticalHuggingPriority="750" id="C3V-Ep-bMj">
<rect key="frame" x="202.5" y="12" width="82" height="23"/> <rect key="frame" x="202.5" y="12" width="83" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <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"> <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"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -970,7 +975,7 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/>
<clipView key="contentView" id="mzf-yu-RID"> <clipView key="contentView" id="mzf-yu-RID">
<rect key="frame" x="1" y="0.0" width="398" height="274"/> <rect key="frame" x="1" y="0.0" width="398" height="274"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="398" height="249"/>
@ -1069,6 +1074,7 @@
</customObject> </customObject>
</objects> </objects>
<resources> <resources>
<image name="NSFolder" width="32" height="32"/>
<image name="NSStopProgressFreestandingTemplate" width="14" height="14"/> <image name="NSStopProgressFreestandingTemplate" width="14" height="14"/>
</resources> </resources>
</document> </document>

View File

@ -13,8 +13,14 @@ static inline double scale_channel(uint8_t x)
- (void)setObjectValue:(id)objectValue - (void)setObjectValue:(id)objectValue
{ {
_integerValue = [objectValue integerValue]; _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 - (NSInteger)integerValue

View File

@ -1,6 +1,7 @@
#import "GBPreferencesWindow.h" #import "GBPreferencesWindow.h"
#import "NSString+StringForKey.h" #import "NSString+StringForKey.h"
#import "GBButtons.h" #import "GBButtons.h"
#import "BigSurToolbar.h"
#import <Carbon/Carbon.h> #import <Carbon/Carbon.h>
@implementation GBPreferencesWindow @implementation GBPreferencesWindow
@ -52,6 +53,11 @@
return filters; return filters;
} }
- (NSWindowToolbarStyle)toolbarStyle
{
return NSWindowToolbarStylePreference;
}
- (void)close - (void)close
{ {
joystick_configuration_state = -1; joystick_configuration_state = -1;
@ -164,6 +170,20 @@
return GBGameBoyButtonCount; 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 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{ {
if ([tableColumn.identifier isEqualToString:@"keyName"]) { if ([tableColumn.identifier isEqualToString:@"keyName"]) {
@ -176,6 +196,12 @@
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)]; NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
if (key) { if (key) {
if ([self usesForKey:[key unsignedIntValue]] > 1) {
return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]]
attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0],
NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
}];
}
return [NSString displayStringForKeyCode: [key unsignedIntegerValue]]; return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
} }

View File

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

View File

@ -2,6 +2,7 @@
#import "GBTerminalTextFieldCell.h" #import "GBTerminalTextFieldCell.h"
@interface GBTerminalTextView : NSTextView @interface GBTerminalTextView : NSTextView
@property GB_gameboy_t *gb;
@end @end
@implementation GBTerminalTextFieldCell @implementation GBTerminalTextFieldCell
@ -12,10 +13,12 @@
- (NSTextView *)fieldEditorForView:(NSView *)controlView - (NSTextView *)fieldEditorForView:(NSView *)controlView
{ {
if (field_editor) { if (field_editor) {
field_editor.gb = self.gb;
return field_editor; return field_editor;
} }
field_editor = [[GBTerminalTextView alloc] init]; field_editor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:YES]; [field_editor setFieldEditor:YES];
field_editor.gb = self.gb;
return field_editor; return field_editor;
} }
@ -26,6 +29,8 @@
NSMutableOrderedSet *lines; NSMutableOrderedSet *lines;
NSUInteger current_line; NSUInteger current_line;
bool reverse_search_mode; bool reverse_search_mode;
NSRange auto_complete_range;
uintptr_t auto_complete_context;
} }
- (instancetype)init - (instancetype)init
@ -170,6 +175,7 @@
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag -(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
{ {
reverse_search_mode = false; reverse_search_mode = false;
auto_complete_context = 0;
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag]; [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
} }
@ -188,6 +194,38 @@
[attributes setObject:color forKey:NSForegroundColorAttributeName]; [attributes setObject:color forKey:NSForegroundColorAttributeName];
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; [[[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 @end

View File

@ -1,4 +1,5 @@
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
#import <Carbon/Carbon.h>
#import "GBView.h" #import "GBView.h"
#import "GBViewGL.h" #import "GBViewGL.h"
#import "GBViewMetal.h" #import "GBViewMetal.h"
@ -8,6 +9,98 @@
#define JOYSTICK_HIGH 0x4000 #define JOYSTICK_HIGH 0x4000
#define JOYSTICK_LOW 0x3800 #define JOYSTICK_LOW 0x3800
static const uint8_t workboy_ascii_to_key[] = {
['0'] = GB_WORKBOY_0,
['`'] = GB_WORKBOY_UMLAUT,
['1'] = GB_WORKBOY_1,
['2'] = GB_WORKBOY_2,
['3'] = GB_WORKBOY_3,
['4'] = GB_WORKBOY_4,
['5'] = GB_WORKBOY_5,
['6'] = GB_WORKBOY_6,
['7'] = GB_WORKBOY_7,
['8'] = GB_WORKBOY_8,
['9'] = GB_WORKBOY_9,
['\r'] = GB_WORKBOY_ENTER,
[3] = GB_WORKBOY_ENTER,
['!'] = GB_WORKBOY_EXCLAMATION_MARK,
['$'] = GB_WORKBOY_DOLLAR,
['#'] = GB_WORKBOY_HASH,
['~'] = GB_WORKBOY_TILDE,
['*'] = GB_WORKBOY_ASTERISK,
['+'] = GB_WORKBOY_PLUS,
['-'] = GB_WORKBOY_MINUS,
['('] = GB_WORKBOY_LEFT_PARENTHESIS,
[')'] = GB_WORKBOY_RIGHT_PARENTHESIS,
[';'] = GB_WORKBOY_SEMICOLON,
[':'] = GB_WORKBOY_COLON,
['%'] = GB_WORKBOY_PERCENT,
['='] = GB_WORKBOY_EQUAL,
[','] = GB_WORKBOY_COMMA,
['<'] = GB_WORKBOY_LT,
['.'] = GB_WORKBOY_DOT,
['>'] = GB_WORKBOY_GT,
['/'] = GB_WORKBOY_SLASH,
['?'] = GB_WORKBOY_QUESTION_MARK,
[' '] = GB_WORKBOY_SPACE,
['\''] = GB_WORKBOY_QUOTE,
['@'] = GB_WORKBOY_AT,
['q'] = GB_WORKBOY_Q,
['w'] = GB_WORKBOY_W,
['e'] = GB_WORKBOY_E,
['r'] = GB_WORKBOY_R,
['t'] = GB_WORKBOY_T,
['y'] = GB_WORKBOY_Y,
['u'] = GB_WORKBOY_U,
['i'] = GB_WORKBOY_I,
['o'] = GB_WORKBOY_O,
['p'] = GB_WORKBOY_P,
['a'] = GB_WORKBOY_A,
['s'] = GB_WORKBOY_S,
['d'] = GB_WORKBOY_D,
['f'] = GB_WORKBOY_F,
['g'] = GB_WORKBOY_G,
['h'] = GB_WORKBOY_H,
['j'] = GB_WORKBOY_J,
['k'] = GB_WORKBOY_K,
['l'] = GB_WORKBOY_L,
['z'] = GB_WORKBOY_Z,
['x'] = GB_WORKBOY_X,
['c'] = GB_WORKBOY_C,
['v'] = GB_WORKBOY_V,
['b'] = GB_WORKBOY_B,
['n'] = GB_WORKBOY_N,
['m'] = GB_WORKBOY_M,
};
static const uint8_t workboy_vk_to_key[] = {
[kVK_F1] = GB_WORKBOY_CLOCK,
[kVK_F2] = GB_WORKBOY_TEMPERATURE,
[kVK_F3] = GB_WORKBOY_MONEY,
[kVK_F4] = GB_WORKBOY_CALCULATOR,
[kVK_F5] = GB_WORKBOY_DATE,
[kVK_F6] = GB_WORKBOY_CONVERSION,
[kVK_F7] = GB_WORKBOY_RECORD,
[kVK_F8] = GB_WORKBOY_WORLD,
[kVK_F9] = GB_WORKBOY_PHONE,
[kVK_F10] = GB_WORKBOY_UNKNOWN,
[kVK_Delete] = GB_WORKBOY_BACKSPACE,
[kVK_Shift] = GB_WORKBOY_SHIFT_DOWN,
[kVK_RightShift] = GB_WORKBOY_SHIFT_DOWN,
[kVK_UpArrow] = GB_WORKBOY_UP,
[kVK_DownArrow] = GB_WORKBOY_DOWN,
[kVK_LeftArrow] = GB_WORKBOY_LEFT,
[kVK_RightArrow] = GB_WORKBOY_RIGHT,
[kVK_Escape] = GB_WORKBOY_ESCAPE,
[kVK_ANSI_KeypadDecimal] = GB_WORKBOY_DECIMAL_POINT,
[kVK_ANSI_KeypadClear] = GB_WORKBOY_M,
[kVK_ANSI_KeypadMultiply] = GB_WORKBOY_H,
[kVK_ANSI_KeypadDivide] = GB_WORKBOY_J,
};
@implementation GBView @implementation GBView
{ {
uint32_t *image_buffers[3]; uint32_t *image_buffers[3];
@ -188,7 +281,20 @@
-(void)keyDown:(NSEvent *)theEvent -(void)keyDown:(NSEvent *)theEvent
{ {
if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return;
unsigned short keyCode = theEvent.keyCode; unsigned short keyCode = theEvent.keyCode;
if (GB_workboy_is_enabled(_gb)) {
if (theEvent.keyCode < sizeof(workboy_vk_to_key) && workboy_vk_to_key[theEvent.keyCode]) {
GB_workboy_set_key(_gb, workboy_vk_to_key[theEvent.keyCode]);
return;
}
unichar c = [theEvent type] != NSEventTypeFlagsChanged? [theEvent.charactersIgnoringModifiers.lowercaseString characterAtIndex:0] : 0;
if (c < sizeof(workboy_ascii_to_key) && workboy_ascii_to_key[c]) {
GB_workboy_set_key(_gb, workboy_ascii_to_key[c]);
return;
}
}
bool handled = false; bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
@ -232,6 +338,15 @@
-(void)keyUp:(NSEvent *)theEvent -(void)keyUp:(NSEvent *)theEvent
{ {
unsigned short keyCode = theEvent.keyCode; unsigned short keyCode = theEvent.keyCode;
if (GB_workboy_is_enabled(_gb)) {
if (keyCode == kVK_Shift || keyCode == kVK_RightShift) {
GB_workboy_set_key(_gb, GB_WORKBOY_SHIFT_UP);
}
else {
GB_workboy_set_key(_gb, GB_WORKBOY_NONE);
}
}
bool handled = false; bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

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

@ -379,6 +379,12 @@
<action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/> <action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Workboy" id="lo9-CX-BJj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/>
</connections>
</menuItem>
</items> </items>
</menu> </menu>
</menuItem> </menuItem>

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

@ -492,7 +492,7 @@
<rect key="frame" x="1" y="1" width="238" height="209"/> <rect key="frame" x="1" y="1" width="238" height="209"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <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"/> <rect key="frame" x="0.0" y="0.0" width="238" height="209"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
@ -507,8 +507,8 @@
</tableHeaderCell> </tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mqT-jD-eXS"> <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mqT-jD-eXS">
<font key="font" metaFont="system"/> <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="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>
@ -520,8 +520,8 @@
</tableHeaderCell> </tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" identifier="keyValue" title="Text Cell" id="tn8-0i-1q1"> <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" identifier="keyValue" title="Text Cell" id="tn8-0i-1q1">
<font key="font" metaFont="system"/> <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="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>

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

@ -132,7 +132,7 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer
} }
/* Avoid overflow */ /* Avoid overflow */
if (symbol && strlen(symbol->name) > 240) { if (symbol && strlen(symbol->name) >= 240) {
symbol = NULL; symbol = NULL;
} }
@ -172,7 +172,7 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo
} }
/* Avoid overflow */ /* Avoid overflow */
if (symbol && strlen(symbol->name) > 240) { if (symbol && strlen(symbol->name) >= 240) {
symbol = NULL; symbol = NULL;
} }
@ -689,6 +689,7 @@ exit:
struct debugger_command_s; 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 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 { typedef struct debugger_command_s {
const char *command; const char *command;
@ -697,6 +698,8 @@ typedef struct debugger_command_s {
const char *help_string; // Null if should not appear in help const char *help_string; // Null if should not appear in help
const char *arguments_format; // For usage message const char *arguments_format; // For usage message
const char *modifiers_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; } debugger_command_t;
static const char *lstrip(const char *str) static const char *lstrip(const char *str)
@ -832,6 +835,19 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
return true; 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 */ /* Enable or disable software breakpoints */
static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
@ -873,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min; 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) static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
bool is_jump_to = true; bool is_jump_to = true;
@ -1040,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min; 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) static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
if (strlen(lstrip(arguments)) == 0) { if (strlen(lstrip(arguments)) == 0) {
@ -1277,6 +1365,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
return _should_break(gb, full_addr, jump_to); 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) static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
if (strlen(lstrip(arguments)) == 0) { if (strlen(lstrip(arguments)) == 0) {
@ -1432,7 +1533,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
const GB_cartridge_t *cartridge = gb->cartridge_type; const GB_cartridge_t *cartridge = gb->cartridge_type;
if (cartridge->has_ram) { if (cartridge->has_ram) {
GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
} }
else { else {
GB_log(gb, "No cartridge RAM\n"); GB_log(gb, "No cartridge RAM\n");
@ -1740,6 +1841,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
return true; 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) 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]))) { if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
@ -1787,7 +1901,7 @@ static const debugger_command_t commands[] = {
{"finish", 1, finish, "Run until the current function returns"}, {"finish", 1, finish, "Run until the current function returns"},
{"backtrace", 2, backtrace, "Displays the current call stack"}, {"backtrace", 2, backtrace, "Displays the current call stack"},
{"bt", 2, }, /* Alias */ {"bt", 2, }, /* Alias */
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
"used"}, "used"},
{"registers", 1, registers, "Print values of processor registers and other important registers"}, {"registers", 1, registers, "Print values of processor registers and other important registers"},
@ -1796,30 +1910,33 @@ static const debugger_command_t commands[] = {
{"apu", 3, apu, "Displays information about the current state of the audio chip"}, {"apu", 3, apu, "Displays information about the current state of the audio chip"},
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"}, "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Displays the current CGB palettes"}, {"palettes", 3, palettes, "Displays the current CGB palettes"},
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"}, {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
"Can also modify the condition of existing breakpoints." 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 "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
"jumping to the target.", "jumping to the target.",
"<expression>[ if <condition expression>]", "j"}, "<expression>[ if <condition expression>]", "j",
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"}, .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 {"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 "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
"Default watchpoint type is write-only.", "Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)"}, "<expression>[ if <condition expression>]", "(r|w|rw)",
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"}, .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"}, {"list", 1, list, "List all set breakpoints and watchpoints"},
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE {"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 "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).", "decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format"}, "<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
{"eval", 2, }, /* Alias */ {"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 */ {"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>]"}, {"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
@ -2075,6 +2192,63 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
} }
} }
/* Returns true if debugger waits for more commands */
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context)
{
char *command_string = input;
char *arguments = strchr(input, ' ');
if (arguments) {
/* Actually "split" the string. */
arguments[0] = 0;
arguments++;
}
char *modifiers = strchr(command_string, '/');
if (modifiers) {
/* Actually "split" the string. */
modifiers[0] = 0;
modifiers++;
}
const debugger_command_t *command = find_command(command_string);
if (command && command->implementation == help && arguments) {
command_string = arguments;
arguments = NULL;
}
/* No commands and no modifiers, complete the command */
if (!arguments && !modifiers) {
size_t length = strlen(command_string);
if (*context >= sizeof(commands) / sizeof(commands[0])) {
return NULL;
}
for (const debugger_command_t *command = &commands[*context]; command->command; command++) {
(*context)++;
if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */
return strdup(command->command + length);
}
}
return NULL;
}
if (command) {
if (arguments) {
if (command->argument_completer) {
return command->argument_completer(gb, arguments, context);
}
return NULL;
}
if (modifiers) {
if (command->modifiers_completer) {
return command->modifiers_completer(gb, modifiers, context);
}
return NULL;
}
}
return NULL;
}
typedef enum { typedef enum {
JUMP_TO_NONE, JUMP_TO_NONE,
JUMP_TO_BREAK, JUMP_TO_BREAK,

View File

@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
void void
#endif #endif
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */ 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); 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); const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);

View File

@ -1131,8 +1131,9 @@ void GB_disconnect_serial(GB_gameboy_t *gb)
gb->serial_transfer_bit_start_callback = NULL; gb->serial_transfer_bit_start_callback = NULL;
gb->serial_transfer_bit_end_callback = NULL; gb->serial_transfer_bit_end_callback = NULL;
/* Reset any internally-emulated device. Currently, only the printer. */ /* Reset any internally-emulated device. */
memset(&gb->printer, 0, sizeof(gb->printer)); memset(&gb->printer, 0, sizeof(gb->printer));
memset(&gb->workboy, 0, sizeof(gb->workboy));
} }
bool GB_is_inited(GB_gameboy_t *gb) bool GB_is_inited(GB_gameboy_t *gb)

View File

@ -23,6 +23,7 @@
#include "sgb.h" #include "sgb.h"
#include "cheats.h" #include "cheats.h"
#include "rumble.h" #include "rumble.h"
#include "workboy.h"
#define GB_STRUCT_VERSION 13 #define GB_STRUCT_VERSION 13
@ -52,6 +53,10 @@
#error Unable to detect endianess #error Unable to detect endianess
#endif #endif
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; })
#endif
typedef struct { typedef struct {
struct { struct {
uint8_t r, g, b; uint8_t r, g, b;
@ -368,6 +373,7 @@ struct GB_gameboy_internal_s {
GB_printer_t printer; GB_printer_t printer;
uint8_t extra_oam[0xff00 - 0xfea0]; uint8_t extra_oam[0xff00 - 0xfea0];
uint32_t ram_size; // Different between CGB and DMG uint32_t ram_size; // Different between CGB and DMG
GB_workboy_t workboy;
); );
/* DMA and HDMA */ /* DMA and HDMA */
@ -434,7 +440,7 @@ struct GB_gameboy_internal_s {
bool rumble_state; bool rumble_state;
bool cart_ir; bool cart_ir;
// TODO: move to huc3 struct when breaking save compat // TODO: move to huc3/mbc3 struct when breaking save compat
uint8_t huc3_mode; uint8_t huc3_mode;
uint8_t huc3_access_index; uint8_t huc3_access_index;
uint16_t huc3_minutes, huc3_days; uint16_t huc3_minutes, huc3_days;
@ -442,6 +448,7 @@ struct GB_gameboy_internal_s {
bool huc3_alarm_enabled; bool huc3_alarm_enabled;
uint8_t huc3_read; uint8_t huc3_read;
uint8_t huc3_access_flags; uint8_t huc3_access_flags;
bool mbc3_rtc_mapped;
); );
@ -602,6 +609,9 @@ struct GB_gameboy_internal_s {
GB_icd_vreset_callback_t icd_vreset_callback; GB_icd_vreset_callback_t icd_vreset_callback;
GB_read_memory_callback_t read_memory_callback; GB_read_memory_callback_t read_memory_callback;
GB_boot_rom_load_callback_t boot_rom_load_callback; GB_boot_rom_load_callback_t boot_rom_load_callback;
GB_print_image_callback_t printer_callback;
GB_workboy_set_time_callback workboy_set_time_callback;
GB_workboy_get_time_callback workboy_get_time_callback;
/* IR */ /* IR */
uint64_t cycles_since_ir_change; // In 8MHz units uint64_t cycles_since_ir_change; // In 8MHz units

View File

@ -88,7 +88,6 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_ram_bank = gb->mbc3.ram_bank; gb->mbc_ram_bank = gb->mbc3.ram_bank;
if (!gb->is_mbc30) { if (!gb->is_mbc30) {
gb->mbc_rom_bank &= 0x7F; gb->mbc_rom_bank &= 0x7F;
gb->mbc_ram_bank &= 0x3;
} }
if (gb->mbc_rom_bank == 0) { if (gb->mbc_rom_bank == 0) {
gb->mbc_rom_bank = 1; gb->mbc_rom_bank = 1;
@ -135,7 +134,10 @@ void GB_configure_cart(GB_gameboy_t *gb)
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
} }
if (gb->mbc_ram_size) {
gb->mbc_ram = malloc(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? */ /* 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); memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);

View File

@ -183,7 +183,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
} }
} }
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && if ((!gb->mbc_ram_enable) &&
gb->cartridge_type->mbc_subtype != GB_CAMERA && gb->cartridge_type->mbc_subtype != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC1 &&
gb->cartridge_type->mbc_type != GB_HUC3) { gb->cartridge_type->mbc_type != GB_HUC3) {
@ -195,17 +195,17 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
} }
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) {
/* RTC read */ /* RTC read */
gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */
return gb->rtc_latched.data[gb->mbc_ram_bank - 8]; return gb->rtc_latched.data[gb->mbc_ram_bank];
} }
if (gb->camera_registers_mapped) { if (gb->camera_registers_mapped) {
return GB_camera_read_register(gb, addr); return GB_camera_read_register(gb, addr);
} }
if (!gb->mbc_ram) { if (!gb->mbc_ram || !gb->mbc_ram_size) {
return 0xFF; return 0xFF;
} }
@ -213,7 +213,11 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
return GB_camera_read_image(gb, addr - 0xa100); return GB_camera_read_image(gb, addr - 0xa100);
} }
uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; uint8_t effective_bank = gb->mbc_ram_bank;
if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) {
effective_bank &= 0x3;
}
uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)];
if (gb->cartridge_type->mbc_type == GB_MBC2) { if (gb->cartridge_type->mbc_type == GB_MBC2) {
ret |= 0xF0; ret |= 0xF0;
} }
@ -509,7 +513,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
switch (addr & 0xF000) { switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break;
case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break; case 0x4000: case 0x5000:
gb->mbc3.ram_bank = value;
gb->mbc3_rtc_mapped = value & 8;
break;
case 0x6000: case 0x7000: case 0x6000: case 0x7000:
if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */ if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */
memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real));
@ -677,7 +684,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return; return;
} }
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) if ((!gb->mbc_ram_enable)
&& gb->cartridge_type->mbc_type != GB_HUC1) return; && gb->cartridge_type->mbc_type != GB_HUC1) return;
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
@ -693,16 +700,21 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return; return;
} }
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) {
gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value;
return; return;
} }
if (!gb->mbc_ram) { if (!gb->mbc_ram || !gb->mbc_ram_size) {
return; return;
} }
gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; uint8_t effective_bank = gb->mbc_ram_bank;
if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) {
effective_bank &= 0x3;
}
gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value;
} }
static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)

View File

@ -31,8 +31,8 @@ static void handle_command(GB_gameboy_t *gb)
image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3]; image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
} }
if (gb->printer.callback) { if (gb->printer_callback) {
gb->printer.callback(gb, image, gb->printer.image_offset / 160, 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[1] >> 4, gb->printer.command_data[1] & 7,
gb->printer.command_data[3] & 0x7F); gb->printer.command_data[3] & 0x7F);
} }
@ -189,13 +189,13 @@ static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received)
static void serial_start(GB_gameboy_t *gb, bool bit_received) static void serial_start(GB_gameboy_t *gb, bool bit_received)
{ {
gb->printer.byte_being_recieved <<= 1; gb->printer.byte_being_received <<= 1;
gb->printer.byte_being_recieved |= bit_received; gb->printer.byte_being_received |= bit_received;
gb->printer.bits_recieved++; gb->printer.bits_received++;
if (gb->printer.bits_recieved == 8) { if (gb->printer.bits_received == 8) {
byte_reieve_completed(gb, gb->printer.byte_being_recieved); byte_reieve_completed(gb, gb->printer.byte_being_received);
gb->printer.bits_recieved = 0; gb->printer.bits_received = 0;
gb->printer.byte_being_recieved = 0; gb->printer.byte_being_received = 0;
} }
} }
@ -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)); memset(&gb->printer, 0, sizeof(gb->printer));
GB_set_serial_transfer_bit_start_callback(gb, serial_start); GB_set_serial_transfer_bit_start_callback(gb, serial_start);
GB_set_serial_transfer_bit_end_callback(gb, serial_end); GB_set_serial_transfer_bit_end_callback(gb, serial_end);
gb->printer.callback = callback; gb->printer_callback = callback;
} }

View File

@ -48,13 +48,14 @@ typedef struct
uint8_t image[160 * 200]; uint8_t image[160 * 200];
uint16_t image_offset; uint16_t image_offset;
GB_print_image_callback_t callback; /* TODO: Delete me. */
uint64_t padding;
uint8_t compression_run_lenth; uint8_t compression_run_lenth;
bool compression_run_is_compressed; bool compression_run_is_compressed;
uint8_t bits_recieved; uint8_t bits_received;
uint8_t byte_being_recieved; uint8_t byte_being_received;
bool bit_to_send; bool bit_to_send;
} GB_printer_t; } GB_printer_t;

View File

@ -40,7 +40,6 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; 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) { if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto error; goto error;
} }
@ -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. */ /* 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; uint32_t saved_size = 0;
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) { if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
return false; 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 (saved_size <= size) {
if (fread(dest, 1, saved_size, f) != saved_size) { if (fread(dest, 1, saved_size, f) != saved_size) {
return false; return false;
@ -139,11 +146,21 @@ static bool read_section(FILE *f, void *dest, uint32_t size)
} }
#undef DUMP_SECTION #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) { if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); /* This is a save state with a bad printer struct from a 32-bit OS */
return false; 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) { if (gb->version != save->version) {
@ -202,7 +219,7 @@ static void sanitize_state(GB_gameboy_t *gb)
} }
} }
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) #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) int GB_load_state(GB_gameboy_t *gb, const char *path)
{ {
@ -219,7 +236,18 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
return errno; 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 (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, core_state)) goto error;
if (!READ_SECTION(&save, f, dma )) goto error; if (!READ_SECTION(&save, f, dma )) goto error;
if (!READ_SECTION(&save, f, mbc )) goto error; if (!READ_SECTION(&save, f, mbc )) goto error;
@ -229,24 +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, rtc )) goto error;
if (!READ_SECTION(&save, f, video )) goto error; if (!READ_SECTION(&save, f, video )) goto error;
if (save.ram_size == 0) { if (!verify_and_update_state_compatibility(gb, &save)) {
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
incorrect RAM amount if it's a CGB instance */
if (GB_is_cgb(&save)) {
save.ram_size = 0x2000 * 8; // Incorrect RAM size
}
else {
save.ram_size = gb->ram_size;
}
}
if (!verify_state_compatibility(gb, &save)) {
errno = -1; errno = -1;
goto error; goto error;
} }
if (GB_is_hle_sgb(gb)) { if (GB_is_hle_sgb(gb)) {
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; 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); memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
@ -297,7 +314,7 @@ static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, siz
return length; 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; uint32_t saved_size = 0;
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) { if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
@ -306,6 +323,14 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
if (saved_size > *buffer_length) 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 (saved_size <= size) {
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) { if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
return false; return false;
@ -322,15 +347,27 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
return true; 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) int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
{ {
GB_gameboy_t save; GB_gameboy_t save;
/* Every unread value should be kept the same. */ /* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save)); 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 (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, core_state)) return -1;
if (!READ_SECTION(&save, buffer, length, dma )) return -1; if (!READ_SECTION(&save, buffer, length, dma )) return -1;
if (!READ_SECTION(&save, buffer, length, mbc )) return -1; if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
@ -340,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, rtc )) return -1;
if (!READ_SECTION(&save, buffer, length, video )) 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; return -1;
} }
if (GB_is_hle_sgb(gb)) { if (GB_is_hle_sgb(gb)) {
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1; 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); memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);

View File

@ -5,7 +5,7 @@
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use #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_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_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) #define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))

View File

@ -291,8 +291,20 @@ void GB_rtc_run(GB_gameboy_t *gb)
} }
return; return;
} }
if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */
time_t current_time = time(NULL); time_t current_time = time(NULL);
while (gb->last_rtc_second + 60 * 60 * 24 < current_time) {
gb->last_rtc_second += 60 * 60 * 24;
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;
}
}
while (gb->last_rtc_second < current_time) { while (gb->last_rtc_second < current_time) {
gb->last_rtc_second++; gb->last_rtc_second++;
if (++gb->rtc_real.seconds == 60) { if (++gb->rtc_real.seconds == 60) {

169
Core/workboy.c Normal file
View File

@ -0,0 +1,169 @@
#include "gb.h"
#include <time.h>
static inline uint8_t int_to_bcd(uint8_t i)
{
return (i % 10) + ((i / 10) << 4);
}
static inline uint8_t bcd_to_int(uint8_t i)
{
return (i & 0xF) + (i >> 4) * 10;
}
/*
Note: This peripheral was never released. This is a hacky software reimplementation of it that allows
reaccessing all of the features present in Workboy's ROM. Some of the implementation details are
obviously wrong, but without access to the actual hardware, this is the best I can do.
*/
static void serial_start(GB_gameboy_t *gb, bool bit_received)
{
gb->workboy.byte_being_received <<= 1;
gb->workboy.byte_being_received |= bit_received;
gb->workboy.bits_received++;
if (gb->workboy.bits_received == 8) {
gb->workboy.byte_to_send = 0;
if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'R') {
gb->workboy.byte_to_send = 'D';
gb->workboy.key = GB_WORKBOY_NONE;
gb->workboy.mode = gb->workboy.byte_being_received;
gb->workboy.buffer_index = 1;
time_t time = gb->workboy_get_time_callback(gb);
struct tm tm;
tm = *localtime(&time);
memset(gb->workboy.buffer, 0, sizeof(gb->workboy.buffer));
gb->workboy.buffer[0] = 4; // Unknown, unused, but appears to be expected to be 4
gb->workboy.buffer[2] = int_to_bcd(tm.tm_sec); // Seconds, BCD
gb->workboy.buffer[3] = int_to_bcd(tm.tm_min); // Minutes, BCD
gb->workboy.buffer[4] = int_to_bcd(tm.tm_hour); // Hours, BCD
gb->workboy.buffer[5] = int_to_bcd(tm.tm_mday); // Days, BCD. Upper most 2 bits are added to Year for some reason
gb->workboy.buffer[6] = int_to_bcd(tm.tm_mon + 1); // Months, BCD
gb->workboy.buffer[0xF] = tm.tm_year; // Years, plain number, since 1900
}
else if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'W') {
gb->workboy.byte_to_send = 'D'; // It is actually unknown what this value should be
gb->workboy.key = GB_WORKBOY_NONE;
gb->workboy.mode = gb->workboy.byte_being_received;
gb->workboy.buffer_index = 0;
}
else if (gb->workboy.mode != 'W' && (gb->workboy.byte_being_received == 'O' || gb->workboy.mode == 'O')) {
gb->workboy.mode = 'O';
gb->workboy.byte_to_send = gb->workboy.key;
if (gb->workboy.key != GB_WORKBOY_NONE) {
if (gb->workboy.key & GB_WORKBOY_REQUIRE_SHIFT) {
gb->workboy.key &= ~GB_WORKBOY_REQUIRE_SHIFT;
if (gb->workboy.shift_down) {
gb->workboy.byte_to_send = gb->workboy.key;
gb->workboy.key = GB_WORKBOY_NONE;
}
else {
gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_DOWN;
gb->workboy.shift_down = true;
}
}
else if (gb->workboy.key & GB_WORKBOY_FORBID_SHIFT) {
gb->workboy.key &= ~GB_WORKBOY_FORBID_SHIFT;
if (!gb->workboy.shift_down) {
gb->workboy.byte_to_send = gb->workboy.key;
gb->workboy.key = GB_WORKBOY_NONE;
}
else {
gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_UP;
gb->workboy.shift_down = false;
}
}
else {
if (gb->workboy.key == GB_WORKBOY_SHIFT_DOWN) {
gb->workboy.shift_down = true;
gb->workboy.user_shift_down = true;
}
else if (gb->workboy.key == GB_WORKBOY_SHIFT_UP) {
gb->workboy.shift_down = false;
gb->workboy.user_shift_down = false;
}
gb->workboy.byte_to_send = gb->workboy.key;
gb->workboy.key = GB_WORKBOY_NONE;
}
}
}
else if (gb->workboy.mode == 'R') {
if (gb->workboy.buffer_index / 2 >= sizeof(gb->workboy.buffer)) {
gb->workboy.byte_to_send = 0;
}
else {
if (gb->workboy.buffer_index & 1) {
gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] & 0xF];
}
else {
gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] >> 4];
}
gb->workboy.buffer_index++;
}
}
else if (gb->workboy.mode == 'W') {
gb->workboy.byte_to_send = 'D';
if (gb->workboy.buffer_index < 2) {
gb->workboy.buffer_index++;
}
else if ((gb->workboy.buffer_index - 2) < sizeof(gb->workboy.buffer)) {
gb->workboy.buffer[gb->workboy.buffer_index - 2] = gb->workboy.byte_being_received;
gb->workboy.buffer_index++;
if (gb->workboy.buffer_index - 2 == sizeof(gb->workboy.buffer)) {
struct tm tm = {0,};
tm.tm_sec = bcd_to_int(gb->workboy.buffer[7]);
tm.tm_min = bcd_to_int(gb->workboy.buffer[8]);
tm.tm_hour = bcd_to_int(gb->workboy.buffer[9]);
tm.tm_mday = bcd_to_int(gb->workboy.buffer[0xA]);
tm.tm_mon = bcd_to_int(gb->workboy.buffer[0xB] & 0x3F) - 1;
tm.tm_year = (uint8_t)(gb->workboy.buffer[0x14] + (gb->workboy.buffer[0xA] >> 6)); // What were they thinking?
gb->workboy_set_time_callback(gb, mktime(&tm));
gb->workboy.mode = 'O';
}
}
}
gb->workboy.bits_received = 0;
gb->workboy.byte_being_received = 0;
}
}
static bool serial_end(GB_gameboy_t *gb)
{
bool ret = gb->workboy.bit_to_send;
gb->workboy.bit_to_send = gb->workboy.byte_to_send & 0x80;
gb->workboy.byte_to_send <<= 1;
return ret;
}
void GB_connect_workboy(GB_gameboy_t *gb,
GB_workboy_set_time_callback set_time_callback,
GB_workboy_get_time_callback get_time_callback)
{
memset(&gb->workboy, 0, sizeof(gb->workboy));
GB_set_serial_transfer_bit_start_callback(gb, serial_start);
GB_set_serial_transfer_bit_end_callback(gb, serial_end);
gb->workboy_set_time_callback = set_time_callback;
gb->workboy_get_time_callback = get_time_callback;
}
bool GB_workboy_is_enabled(GB_gameboy_t *gb)
{
return gb->workboy.mode;
}
void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key)
{
if (gb->workboy.user_shift_down != gb->workboy.shift_down &&
(key & (GB_WORKBOY_REQUIRE_SHIFT | GB_WORKBOY_FORBID_SHIFT)) == 0) {
if (gb->workboy.user_shift_down) {
key |= GB_WORKBOY_REQUIRE_SHIFT;
}
else {
key |= GB_WORKBOY_FORBID_SHIFT;
}
}
gb->workboy.key = key;
}

118
Core/workboy.h Normal file
View File

@ -0,0 +1,118 @@
#ifndef workboy_h
#define workboy_h
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include "gb_struct_def.h"
typedef struct {
uint8_t byte_to_send;
bool bit_to_send;
uint8_t byte_being_received;
uint8_t bits_received;
uint8_t mode;
uint8_t key;
bool shift_down;
bool user_shift_down;
uint8_t buffer[0x15];
uint8_t buffer_index; // In nibbles during read, in bytes during write
} GB_workboy_t;
typedef void (*GB_workboy_set_time_callback)(GB_gameboy_t *gb, time_t time);
typedef time_t (*GB_workboy_get_time_callback)(GB_gameboy_t *gb);
enum {
GB_WORKBOY_NONE = 0xFF,
GB_WORKBOY_REQUIRE_SHIFT = 0x40,
GB_WORKBOY_FORBID_SHIFT = 0x80,
GB_WORKBOY_CLOCK = 1,
GB_WORKBOY_TEMPERATURE = 2,
GB_WORKBOY_MONEY = 3,
GB_WORKBOY_CALCULATOR = 4,
GB_WORKBOY_DATE = 5,
GB_WORKBOY_CONVERSION = 6,
GB_WORKBOY_RECORD = 7,
GB_WORKBOY_WORLD = 8,
GB_WORKBOY_PHONE = 9,
GB_WORKBOY_ESCAPE = 10,
GB_WORKBOY_BACKSPACE = 11,
GB_WORKBOY_UNKNOWN = 12,
GB_WORKBOY_LEFT = 13,
GB_WORKBOY_Q = 17,
GB_WORKBOY_1 = 17 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_W = 18,
GB_WORKBOY_2 = 18 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_E = 19,
GB_WORKBOY_3 = 19 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_R = 20,
GB_WORKBOY_T = 21,
GB_WORKBOY_Y = 22 ,
GB_WORKBOY_U = 23 ,
GB_WORKBOY_I = 24,
GB_WORKBOY_EXCLAMATION_MARK = 24 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_O = 25,
GB_WORKBOY_TILDE = 25 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_P = 26,
GB_WORKBOY_ASTERISK = 26 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_DOLLAR = 27 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_HASH = 27 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_A = 28,
GB_WORKBOY_4 = 28 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_S = 29,
GB_WORKBOY_5 = 29 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_D = 30,
GB_WORKBOY_6 = 30 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_F = 31,
GB_WORKBOY_PLUS = 31 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_G = 32,
GB_WORKBOY_MINUS = 32 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_H = 33,
GB_WORKBOY_J = 34,
GB_WORKBOY_K = 35,
GB_WORKBOY_LEFT_PARENTHESIS = 35 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_L = 36,
GB_WORKBOY_RIGHT_PARENTHESIS = 36 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_SEMICOLON = 37 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_COLON = 37,
GB_WORKBOY_ENTER = 38,
GB_WORKBOY_SHIFT_DOWN = 39,
GB_WORKBOY_Z = 40,
GB_WORKBOY_7 = 40 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_X = 41,
GB_WORKBOY_8 = 41 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_C = 42,
GB_WORKBOY_9 = 42 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_V = 43,
GB_WORKBOY_DECIMAL_POINT = 43 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_B = 44,
GB_WORKBOY_PERCENT = 44 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_N = 45,
GB_WORKBOY_EQUAL = 45 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_M = 46,
GB_WORKBOY_COMMA = 47 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_LT = 47 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_DOT = 48 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_GT = 48 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_SLASH = 49 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_QUESTION_MARK = 49 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_SHIFT_UP = 50,
GB_WORKBOY_0 = 51 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_UMLAUT = 51,
GB_WORKBOY_SPACE = 52,
GB_WORKBOY_QUOTE = 53 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_AT = 53 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_UP = 54,
GB_WORKBOY_DOWN = 55,
GB_WORKBOY_RIGHT = 56,
};
void GB_connect_workboy(GB_gameboy_t *gb,
GB_workboy_set_time_callback set_time_callback,
GB_workboy_get_time_callback get_time_callback);
bool GB_workboy_is_enabled(GB_gameboy_t *gb);
void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key);
#endif

View File

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

View File

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

View File

@ -119,20 +119,6 @@
[super viewWillMoveToWindow:newWindow]; [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 { - (void)drawDividerWithClip:(NSRect)clipRect {
USE(clipRect); USE(clipRect);
@ -267,7 +253,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
NSUInteger glyphCount; NSUInteger glyphCount;
[textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters]; [textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
if (previousTextStorageCharacterCount == 0) { 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)]; [textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
[atts release]; [atts release];
} }
@ -305,7 +291,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setAlignment:NSRightTextAlignment]; [mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy]; NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release]; [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]; [paragraphStyle release];
} }
@ -461,7 +447,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setAlignment:NSRightTextAlignment]; [mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy]; NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release]; [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]; [paragraphStyle release];
[textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])]; [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))); NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
CGFloat linesToVerticallyOffset = ld2f(_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; NSRect textRect = self.bounds;
textRect.size.width -= 5; textRect.size.width -= 5;
textRect.origin.y -= verticalOffset; textRect.origin.y -= verticalOffset;
textRect.size.height += verticalOffset; textRect.size.height += verticalOffset + _lineHeight;
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment]; [mutableStyle setAlignment:NSRightTextAlignment];
[mutableStyle setMinimumLineHeight:_lineHeight]; [mutableStyle setMinimumLineHeight:_lineHeight];
[mutableStyle setMaximumLineHeight:_lineHeight]; [mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy]; NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release]; [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]; [paragraphStyle release];
}
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)]; NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
[string drawInRect:textRect withAttributes:textAttributes]; [string drawInRect:textRect withAttributes:_textAttributes];
[string release]; [string release];
[_textAttributes release];
} }
- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect { - (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
@ -533,7 +520,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setMaximumLineHeight:_lineHeight]; [mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy]; NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release]; [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]; [paragraphStyle release];
} }
@ -568,7 +555,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
#if TIME_LINE_NUMBERS #if TIME_LINE_NUMBERS
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif #endif
NSInteger drawingMode = (useStringDrawingPath ? 1 : 3); NSInteger drawingMode = 3; // (useStringDrawingPath ? 1 : 3);
switch (drawingMode) { switch (drawingMode) {
// Drawing can't be done right if fonts are wider than expected, but all // 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 // 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 { - (void)drawRect:(NSRect)clipRect {
[self drawGradientWithClip:clipRect];
[self drawDividerWithClip:clipRect]; [self drawDividerWithClip:clipRect];
[self drawLineNumbersWithClip:clipRect]; [self drawLineNumbersWithClip:clipRect];
} }

View File

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

View File

@ -29,7 +29,7 @@
- (void)_sharedInitStatusBarView { - (void)_sharedInitStatusBarView {
NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[style setAlignment:NSCenterTextAlignment]; [style setAlignment:NSCenterTextAlignment];
cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor colorWithCalibratedWhite:(CGFloat).15 alpha:1], NSForegroundColorAttributeName, [NSFont labelFontOfSize:10], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil]; cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor windowFrameTextColor], NSForegroundColorAttributeName, [NSFont labelFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil];
cell = [[NSCell alloc] initTextCell:@""]; cell = [[NSCell alloc] initTextCell:@""];
[cell setAlignment:NSCenterTextAlignment]; [cell setAlignment:NSCenterTextAlignment];
[cell setBackgroundStyle:NSBackgroundStyleRaised]; [cell setBackgroundStyle:NSBackgroundStyleRaised];
@ -62,51 +62,24 @@
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
} }
- (void)drawDividerWithClip:(NSRect)clipRect {
[[NSColor lightGrayColor] set];
NSRect bounds = [self bounds];
NSRect lineRect = bounds;
lineRect.size.height = 1;
NSRectFill(NSIntersectionRect(lineRect, clipRect));
}
- (NSGradient *)getGradient:(BOOL)active {
static NSGradient *sActiveGradient;
static NSGradient *sInactiveGradient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sActiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithCalibratedWhite:.89 alpha:1.], 0.00,
[NSColor colorWithCalibratedWhite:.77 alpha:1.], 0.9,
[NSColor colorWithCalibratedWhite:.82 alpha:1.], 1.0,
nil];
sInactiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithCalibratedWhite:.93 alpha:1.], 0.00,
[NSColor colorWithCalibratedWhite:.87 alpha:1.], 0.9,
[NSColor colorWithCalibratedWhite:.90 alpha:1.], 1.0,
nil];
});
return active ? sActiveGradient : sInactiveGradient;
}
- (void)drawRect:(NSRect)clip { - (void)drawRect:(NSRect)clip {
USE(clip); USE(clip);
NSRect bounds = [self bounds]; NSRect bounds = [self bounds];
// [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set]; // [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set];
// NSRectFill(clip); // NSRectFill(clip);
NSWindow *window = [self window];
BOOL drawActive = (window == nil || [window isMainWindow] || [window isKeyWindow]);
[[self getGradient:drawActive] drawInRect:bounds angle:90.];
[self drawDividerWithClip:clip];
NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height); NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height);
[cell drawWithFrame:cellRect inView:self]; [cell drawWithFrame:cellRect inView:self];
} }
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
[self.window setContentBorderThickness:frame.origin.y + frame.size.height forEdge:NSMinYEdge];
}
- (void)mouseDown:(NSEvent *)event { - (void)mouseDown:(NSEvent *)event {
USE(event); USE(event);
HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT; HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT;
@ -122,6 +95,7 @@
- (void)viewDidMoveToWindow { - (void)viewDidMoveToWindow {
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications); HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
registeredForAppNotifications = YES; registeredForAppNotifications = YES;
[self.window setContentBorderThickness:self.frame.origin.y + self.frame.size.height forEdge:NSMinYEdge];
[super viewDidMoveToWindow]; [super viewDidMoveToWindow];
} }

View File

@ -20,9 +20,13 @@
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
NSColor *color1 = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; if (@available(macOS 10.14, *)) {
NSColor *color2 = [NSColor colorWithCalibratedRed:.87 green:.89 blue:1. alpha:1.]; _rowBackgroundColors = [[NSColor alternatingContentBackgroundColors] retain];
} else {
NSColor *color1 = [NSColor windowBackgroundColor];
NSColor *color2 = [NSColor colorWithDeviceWhite:0.96 alpha:1];
_rowBackgroundColors = [@[color1, color2] retain]; _rowBackgroundColors = [@[color1, color2] retain];
}
return self; return self;
} }
@ -237,7 +241,7 @@
FOREACH(HFRangeWrapper *, wrapper, selectedRanges) { FOREACH(HFRangeWrapper *, wrapper, selectedRanges) {
HFRange selectedRange = [wrapper HFRange]; HFRange selectedRange = [wrapper HFRange];
BOOL clippedRangeIsVisible; BOOL clippedRangeIsVisible;
NSRange clippedSelectedRange; NSRange clippedSelectedRange = {0,};
/* Necessary because zero length ranges do not intersect anything */ /* Necessary because zero length ranges do not intersect anything */
if (selectedRange.length == 0) { if (selectedRange.length == 0) {
/* Remember that {6, 0} is considered a subrange of {3, 3} */ /* Remember that {6, 0} is considered a subrange of {3, 3} */

View File

@ -16,8 +16,10 @@ endif
ifeq ($(PLATFORM),windows32) ifeq ($(PLATFORM),windows32)
_ := $(shell chcp 65001) _ := $(shell chcp 65001)
EXESUFFIX:=.exe EXESUFFIX:=.exe
NATIVE_CC = clang -IWindows -Wno-deprecated-declarations
else else
EXESUFFIX:= EXESUFFIX:=
NATIVE_CC := cc
endif endif
PB12_COMPRESS := build/pb12$(EXESUFFIX) PB12_COMPRESS := build/pb12$(EXESUFFIX)
@ -34,7 +36,7 @@ ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS := $(DEFAULT) MAKECMDGOALS := $(DEFAULT)
endif endif
VERSION := 0.13 VERSION := 0.13.6
export VERSION export VERSION
CONF ?= debug CONF ?= debug
SDL_AUDIO_DRIVER ?= sdl SDL_AUDIO_DRIVER ?= sdl
@ -74,6 +76,13 @@ LDFLAGS += -march=native -mtune=native
CFLAGS += -march=native -mtune=native CFLAGS += -march=native -mtune=native
endif endif
ifeq ($(CONF),fat_release)
override CONF := release
FAT_FLAGS += -arch x86_64 -arch arm64
endif
# Set compilation and linkage flags based on target, platform and configuration # Set compilation and linkage flags based on target, platform and configuration
OPEN_DIALOG = OpenDialog/gtk.c OPEN_DIALOG = OpenDialog/gtk.c
@ -97,6 +106,11 @@ ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2>
WARNINGS += -Wpartial-availability WARNINGS += -Wpartial-availability
endif endif
# GCC's implementation of this warning has false positives, so we skip it
ifneq ($(shell $(CC) --version 2>&1 | grep "gcc"), )
WARNINGS += -Wno-maybe-uninitialized
endif
CFLAGS += $(WARNINGS) CFLAGS += $(WARNINGS)
CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES
@ -132,9 +146,9 @@ ifeq ($(SYSROOT),/Library/Developer/CommandLineTools/SDKs/)
$(error Could not find a macOS SDK) $(error Could not find a macOS SDK)
endif endif
CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 -isysroot $(SYSROOT)
OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT)
LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -isysroot $(SYSROOT)
GL_LDFLAGS := -framework OpenGL GL_LDFLAGS := -framework OpenGL
endif endif
CFLAGS += -Wno-deprecated-declarations CFLAGS += -Wno-deprecated-declarations
@ -227,24 +241,24 @@ $(OBJ)/%.dep: %
$(OBJ)/Core/%.c.o: Core/%.c $(OBJ)/Core/%.c.o: Core/%.c
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -DGB_INTERNAL -c $< -o $@ $(CC) $(CFLAGS) $(FAT_FLAGS) -DGB_INTERNAL -c $< -o $@
$(OBJ)/SDL/%.c.o: SDL/%.c $(OBJ)/SDL/%.c.o: SDL/%.c
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@
$(OBJ)/%.c.o: %.c $(OBJ)/%.c.o: %.c
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) $(FAT_FLAGS) -c $< -o $@
# HexFiend requires more flags # HexFiend requires more flags
$(OBJ)/HexFiend/%.m.o: HexFiend/%.m $(OBJ)/HexFiend/%.m.o: HexFiend/%.m
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch $(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch
$(OBJ)/%.m.o: %.m $(OBJ)/%.m.o: %.m
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ $(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@
# Cocoa Port # Cocoa Port
@ -272,13 +286,13 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
$(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS) $(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit
ifeq ($(CONF), release) ifeq ($(CONF), release)
$(STRIP) $@ $(STRIP) $@
endif endif
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib
ibtool --compile $@ $^ ibtool --compile $@ $^ 2>&1 | cat -
# Quick Look generator # Quick Look generator
@ -294,7 +308,7 @@ $(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL
# once in the QL Generator. It should probably become a dylib instead. # once in the QL Generator. It should probably become a dylib instead.
$(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS) $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS)
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook
# cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided # cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided
# boot ROM directory. # boot ROM directory.
@ -307,7 +321,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs
# Unix versions build only one binary # Unix versions build only one binary
$(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) $(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS)
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS)
ifeq ($(CONF), release) ifeq ($(CONF), release)
$(STRIP) $@ $(STRIP) $@
endif endif
@ -390,7 +404,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE
$(realpath $(PB12_COMPRESS)) < $< > $@ $(realpath $(PB12_COMPRESS)) < $< > $@
$(PB12_COMPRESS): BootROMs/pb12.c $(PB12_COMPRESS): BootROMs/pb12.c
$(CC) $(LDFLAGS) $(CFLAGS) -Wall -Werror $< -o $@ $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@
$(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm
$(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm

View File

@ -61,9 +61,9 @@ static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder)
} }
if (showBorder) { if (showBorder) {
/* Use the CGB flag to determine the cartrdige "look": /* Use the CGB flag to determine the cartridge "look":
- DMG cartridges are grey - DMG cartridges are grey
- CGB cartrdiges are transparent - CGB cartridges are transparent
- CGB cartridges that support DMG systems are black - CGB cartridges that support DMG systems are black
*/ */
NSImage *effectiveTemplate = nil; NSImage *effectiveTemplate = nil;
@ -105,8 +105,9 @@ OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview,
OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize) OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize)
{ {
extern NSString *kQLThumbnailPropertyIconFlavorKey;
@autoreleasepool { @autoreleasepool {
CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, ((NSSize){1024, 1024}), true, (__bridge CFDictionaryRef)(@{@"IconFlavor" : @(0)})); CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, ((NSSize){1024, 1024}), true, (__bridge CFDictionaryRef)(@{kQLThumbnailPropertyIconFlavorKey : @(0)}));
if (render(cgContext, url, true) == noErr) { if (render(cgContext, url, true) == noErr) {
QLThumbnailRequestFlushContext(thumbnail, cgContext); QLThumbnailRequestFlushContext(thumbnail, cgContext);
CGContextRelease(cgContext); CGContextRelease(cgContext);

View File

@ -46,8 +46,8 @@ On Windows, SameBoy also requires:
* [GnuWin](http://gnuwin32.sourceforge.net/) * [GnuWin](http://gnuwin32.sourceforge.net/)
* Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively.
To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release` or `CONF=native_release` to control optimization and symbols. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. You may set `BOOTROMS_DIR=...` to a directory containing precompiled `dmg_boot.bin` and `cgb_boot.bin` files, otherwise the build system will compile and use SameBoy's own boot ROMs. To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs.
By default, the SDL port will look for resource files with a path relative to executable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character. By default, the SDL port will look for resource files with a path relative to executable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character.
SameBoy was compiled and tested on macOS, Ubuntu and 32-bit Windows 7. SameBoy was compiled and tested on macOS, Ubuntu and 64-bit Windows 7.

View File

@ -109,6 +109,7 @@ configuration_t configuration =
.model = MODEL_CGB, .model = MODEL_CGB,
.volume = 100, .volume = 100,
.rumble_mode = GB_RUMBLE_ALL_GAMES, .rumble_mode = GB_RUMBLE_ALL_GAMES,
.default_scale = 2,
}; };
@ -170,6 +171,11 @@ void update_viewport(void)
} }
} }
static void rescale_window(void)
{
SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale);
}
/* Does NOT check for bounds! */ /* Does NOT check for bounds! */
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color) static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color)
{ {
@ -435,6 +441,12 @@ const char *current_scaling_mode(unsigned index)
[configuration.scaling_mode]; [configuration.scaling_mode];
} }
const char *current_default_scale(unsigned index)
{
return (const char *[]){"1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x"}
[configuration.default_scale - 1];
}
const char *current_color_correction_mode(unsigned index) const char *current_color_correction_mode(unsigned index)
{ {
return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast"} return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast"}
@ -475,6 +487,32 @@ void cycle_scaling_backwards(unsigned index)
render_texture(NULL, NULL); render_texture(NULL, NULL);
} }
void cycle_default_scale(unsigned index)
{
if (configuration.default_scale == GB_SDL_DEFAULT_SCALE_MAX) {
configuration.default_scale = 1;
}
else {
configuration.default_scale++;
}
rescale_window();
update_viewport();
}
void cycle_default_scale_backwards(unsigned index)
{
if (configuration.default_scale == 1) {
configuration.default_scale = GB_SDL_DEFAULT_SCALE_MAX;
}
else {
configuration.default_scale--;
}
rescale_window();
update_viewport();
}
static void cycle_color_correction(unsigned index) static void cycle_color_correction(unsigned index)
{ {
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { if (configuration.color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) {
@ -643,6 +681,7 @@ const char *blending_mode_string(unsigned index)
static const struct menu_item graphics_menu[] = { static const struct menu_item graphics_menu[] = {
{"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards},
{"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards},
{"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards},
{"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards},
{"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards}, {"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards},

View File

@ -41,6 +41,7 @@ enum pending_command {
GB_SDL_QUIT_COMMAND, GB_SDL_QUIT_COMMAND,
}; };
#define GB_SDL_DEFAULT_SCALE_MAX 8
extern enum pending_command pending_command; extern enum pending_command pending_command;
extern unsigned command_parameter; extern unsigned command_parameter;
@ -107,6 +108,8 @@ typedef struct {
GB_border_mode_t border_mode; GB_border_mode_t border_mode;
uint8_t volume; uint8_t volume;
GB_rumble_mode_t rumble_mode; GB_rumble_mode_t rumble_mode;
uint8_t default_scale;
} configuration_t; } configuration_t;
extern configuration_t configuration; extern configuration_t configuration;

View File

@ -624,12 +624,47 @@ int main(int argc, char **argv)
SDL_Init(SDL_INIT_EVERYTHING); SDL_Init(SDL_INIT_EVERYTHING);
strcpy(prefs_path, resource_path("prefs.bin"));
if (access(prefs_path, R_OK | W_OK) != 0) {
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
SDL_free(prefs_dir);
}
FILE *prefs_file = fopen(prefs_path, "rb");
if (prefs_file) {
fread(&configuration, 1, sizeof(configuration), prefs_file);
fclose(prefs_file);
/* Sanitize for stability */
configuration.color_correction_mode %= GB_COLOR_CORRECTION_REDUCE_CONTRAST +1;
configuration.scaling_mode %= GB_SDL_SCALING_MAX;
configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1;
configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1;
configuration.highpass_mode %= GB_HIGHPASS_MAX;
configuration.model %= MODEL_MAX;
configuration.sgb_revision %= SGB_MAX;
configuration.dmg_palette %= 3;
configuration.border_mode %= GB_BORDER_ALWAYS + 1;
configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1;
}
if (configuration.model >= MODEL_MAX) {
configuration.model = MODEL_CGB;
}
if (configuration.default_scale == 0) {
configuration.default_scale = 2;
}
atexit(save_configuration);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); 160 * configuration.default_scale, 144 * configuration.default_scale, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SetWindowMinimumSize(window, 160, 144); SDL_SetWindowMinimumSize(window, 160, 144);
if (fullscreen) { if (fullscreen) {
@ -660,24 +695,6 @@ int main(int argc, char **argv)
SDL_EventState(SDL_DROPFILE, SDL_ENABLE); SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
strcpy(prefs_path, resource_path("prefs.bin"));
if (access(prefs_path, R_OK | W_OK) != 0) {
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
SDL_free(prefs_dir);
}
FILE *prefs_file = fopen(prefs_path, "rb");
if (prefs_file) {
fread(&configuration, 1, sizeof(configuration), prefs_file);
fclose(prefs_file);
}
if (configuration.model >= MODEL_MAX) {
configuration.model = MODEL_CGB;
}
atexit(save_configuration);
if (!init_shader_with_name(&shader, configuration.filter)) { if (!init_shader_with_name(&shader, configuration.filter)) {
init_shader_with_name(&shader, "NearestNeighbor"); init_shader_with_name(&shader, "NearestNeighbor");
} }

View File

@ -158,8 +158,5 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
ret *= output_resolution.y - pixel_position.y; ret *= output_resolution.y - pixel_position.y;
} }
// Gamma correction
ret = pow(ret, vec4(0.72));
return ret; return ret;
} }

View File

@ -12,7 +12,7 @@ STATIC vec3 rgb_to_hq_colorspace(vec4 rgb)
STATIC bool is_different(vec4 a, vec4 b) STATIC bool is_different(vec4 a, vec4 b)
{ {
vec3 diff = abs(rgb_to_hq_colorspace(a) - rgb_to_hq_colorspace(b)); vec3 diff = abs(rgb_to_hq_colorspace(a) - rgb_to_hq_colorspace(b));
return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
} }
#define P(m, r) ((pattern & (m)) == (r)) #define P(m, r) ((pattern & (m)) == (r))
@ -84,7 +84,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0);
} }
if (P(0x2f,0x2f)) { if (P(0x2f,0x2f)) {
return interp_3px(w4, 1.04, w3, 1.0, w1, 1.0); return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0);
} }
if (P(0xbf,0x37) || P(0xdb,0x13)) { if (P(0xbf,0x37) || P(0xdb,0x13)) {
return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0);

View File

@ -9,14 +9,22 @@ uniform vec2 origin;
#define equal(x, y) ((x) == (y)) #define equal(x, y) ((x) == (y))
#define inequal(x, y) ((x) != (y)) #define inequal(x, y) ((x) != (y))
#define STATIC #define STATIC
#define GAMMA (2.2)
out vec4 frag_color; out vec4 frag_color;
vec4 _texture(sampler2D t, vec2 pos)
{
return pow(texture(t, pos), vec4(GAMMA));
}
#define texture _texture
#line 1 #line 1
{filter} {filter}
#define BLEND_BIAS (1.0/3.0) #define BLEND_BIAS (2.0/5.0)
#define DISABLED 0 #define DISABLED 0
#define SIMPLE 1 #define SIMPLE 1
@ -35,7 +43,7 @@ void main()
switch (frame_blending_mode) { switch (frame_blending_mode) {
default: default:
case DISABLED: case DISABLED:
frag_color = scale(image, position, input_resolution, output_resolution); frag_color = pow(scale(image, position, input_resolution, output_resolution), vec4(1.0 / GAMMA));
return; return;
case SIMPLE: case SIMPLE:
ratio = 0.5; ratio = 0.5;
@ -58,7 +66,7 @@ void main()
break; break;
} }
frag_color = mix(scale(image, position, input_resolution, output_resolution), frag_color = pow(mix(scale(image, position, input_resolution, output_resolution),
scale(previous_image, position, input_resolution, output_resolution), ratio); scale(previous_image, position, input_resolution, output_resolution), ratio), vec4(1.0 / GAMMA));
} }

View File

@ -12,6 +12,7 @@ typedef texture2d<half> sampler2D;
#define equal(x, y) all((x) == (y)) #define equal(x, y) all((x) == (y))
#define inequal(x, y) any((x) != (y)) #define inequal(x, y) any((x) != (y))
#define STATIC static #define STATIC static
#define GAMMA (2.2)
typedef struct { typedef struct {
float4 position [[position]]; float4 position [[position]];
@ -36,7 +37,7 @@ vertex rasterizer_data vertex_shader(uint index [[ vertex_id ]],
static inline float4 texture(texture2d<half> texture, float2 pos) static inline float4 texture(texture2d<half> texture, float2 pos)
{ {
constexpr sampler texture_sampler; constexpr sampler texture_sampler;
return float4(texture.sample(texture_sampler, pos)); return pow(float4(texture.sample(texture_sampler, pos)), GAMMA);
} }
#line 1 #line 1
@ -87,7 +88,7 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]],
break; break;
} }
return mix(scale(image, in.texcoords, input_resolution, *output_resolution), return pow(mix(scale(image, in.texcoords, input_resolution, *output_resolution),
scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio); scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio), 1 / GAMMA);
} }

View File

@ -19,7 +19,7 @@ STATIC vec3 rgb_to_hq_colospace(vec4 rgb)
STATIC bool is_different(vec4 a, vec4 b) STATIC bool is_different(vec4 a, vec4 b)
{ {
vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b));
return diff.x > 0.125 || diff.y > 0.027 || diff.z > 0.031; return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
} }
#define P(m, r) ((pattern & (m)) == (r)) #define P(m, r) ((pattern & (m)) == (r))

View File

@ -389,47 +389,12 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
} }
} }
static void init_for_current_model(unsigned id) static void retro_set_memory_maps(void)
{ {
unsigned i = id;
enum model effective_model;
effective_model = model[i];
if (effective_model == MODEL_AUTO) {
effective_model = auto_model;
}
if (GB_is_inited(&gameboy[i])) {
GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]);
}
else {
GB_init(&gameboy[i], libretro_to_internal_model[effective_model]);
}
GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load);
/* When running multiple devices they are assumed to use the same resolution */
GB_set_pixels_output(&gameboy[i],
(uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i));
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
GB_set_rumble_callback(&gameboy[i], rumble_callback);
/* todo: attempt to make these more generic */
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
if (emulated_devices == 2) {
GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2);
if (link_cable_emulation) {
set_link_cable_state(true);
}
}
struct retro_memory_descriptor descs[11]; struct retro_memory_descriptor descs[11];
size_t size; size_t size;
uint16_t bank; uint16_t bank;
unsigned i;
/* todo: add netplay awareness for this so achievements can be granted on the respective client */ /* todo: add netplay awareness for this so achievements can be granted on the respective client */
@ -489,6 +454,45 @@ static void init_for_current_model(unsigned id)
mmaps.descriptors = descs; mmaps.descriptors = descs;
mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]);
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
}
static void init_for_current_model(unsigned id)
{
unsigned i = id;
enum model effective_model;
effective_model = model[i];
if (effective_model == MODEL_AUTO) {
effective_model = auto_model;
}
if (GB_is_inited(&gameboy[i])) {
GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]);
}
else {
GB_init(&gameboy[i], libretro_to_internal_model[effective_model]);
}
GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load);
/* When running multiple devices they are assumed to use the same resolution */
GB_set_pixels_output(&gameboy[i],
(uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i));
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
GB_set_rumble_callback(&gameboy[i], rumble_callback);
/* todo: attempt to make these more generic */
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
if (emulated_devices == 2) {
GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2);
if (link_cable_emulation) {
set_link_cable_state(true);
}
}
/* Let's be extremely nitpicky about how devices and descriptors are set */ /* Let's be extremely nitpicky about how devices and descriptors are set */
if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) {
@ -1070,6 +1074,9 @@ bool retro_load_game(const struct retro_game_info *info)
} }
check_variables(); check_variables();
retro_set_memory_maps();
return true; return true;
} }