Local link cable and infrared emulation in the Cocoa port

This commit is contained in:
Lior Halphon 2020-11-13 23:07:35 +02:00
parent 88198e64f4
commit 60b8978762
9 changed files with 351 additions and 52 deletions

View File

@ -1,6 +1,6 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate> @interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate>
@property IBOutlet NSWindow *preferencesWindow; @property IBOutlet NSWindow *preferencesWindow;
@property (strong) IBOutlet NSView *graphicsTab; @property (strong) IBOutlet NSView *graphicsTab;
@ -10,6 +10,7 @@
- (IBAction)showPreferences: (id) sender; - (IBAction)showPreferences: (id) sender;
- (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)toggleDeveloperMode:(id)sender;
- (IBAction)switchPreferencesTab:(id)sender; - (IBAction)switchPreferencesTab:(id)sender;
@property (weak) IBOutlet NSMenuItem *linkCableMenuItem;
@end @end

View File

@ -82,9 +82,28 @@
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; [(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
} }
if (anItem == self.linkCableMenuItem) {
return [[NSDocumentController sharedDocumentController] documents].count > 1;
}
return true; return true;
} }
- (void)menuNeedsUpdate:(NSMenu *)menu
{
NSMutableArray *items = [NSMutableArray array];
NSDocument *currentDocument = [[NSDocumentController sharedDocumentController] currentDocument];
for (NSDocument *document in [[NSDocumentController sharedDocumentController] documents]) {
if (document == currentDocument) continue;
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:document.displayName action:@selector(connectLinkCable:) keyEquivalent:@""];
item.representedObject = document;
item.image = [[NSWorkspace sharedWorkspace] iconForFile:document.fileURL.path];
[item.image setSize:NSMakeSize(16, 16)];
[items addObject:item];
}
menu.itemArray = items;
}
- (IBAction) showPreferences: (id) sender - (IBAction) showPreferences: (id) sender
{ {
NSArray *objects; NSArray *objects;
@ -109,4 +128,8 @@
{ {
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
} }
- (IBAction)nop:(id)sender
{
}
@end @end

View File

@ -6,6 +6,7 @@
@class GBCheatWindowController; @class GBCheatWindowController;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate> @interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@property (readonly) GB_gameboy_t *gb;
@property (strong) IBOutlet GBView *view; @property (strong) IBOutlet GBView *view;
@property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSTextView *consoleOutput;
@property (strong) IBOutlet NSPanel *consoleWindow; @property (strong) IBOutlet NSPanel *consoleWindow;
@ -36,10 +37,12 @@
@property (strong) IBOutlet NSBox *debuggerVerticalLine; @property (strong) IBOutlet NSBox *debuggerVerticalLine;
@property (strong) IBOutlet NSPanel *cheatsWindow; @property (strong) IBOutlet NSPanel *cheatsWindow;
@property (strong) IBOutlet GBCheatWindowController *cheatWindowController; @property (strong) IBOutlet GBCheatWindowController *cheatWindowController;
@property (readonly) Document *partner;
@property (readonly) bool isSlave;
-(uint8_t) readMemory:(uint16_t) addr; -(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block; -(void) performAtomicBlock: (void (^)())block;
-(void) connectLinkCable:(NSMenuItem *)sender;
@end @end

View File

@ -28,6 +28,7 @@ enum model {
NSMutableAttributedString *pending_console_output; NSMutableAttributedString *pending_console_output;
NSRecursiveLock *console_output_lock; NSRecursiveLock *console_output_lock;
NSTimer *console_output_timer; NSTimer *console_output_timer;
NSTimer *hex_timer;
bool fullScreen; bool fullScreen;
bool in_sync_input; bool in_sync_input;
@ -47,7 +48,7 @@ enum model {
bool oamUpdating; bool oamUpdating;
NSMutableData *currentPrinterImageData; NSMutableData *currentPrinterImageData;
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy} accessory; enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
bool rom_warning_issued; bool rom_warning_issued;
@ -66,6 +67,12 @@ enum model {
size_t audioBufferNeeded; size_t audioBufferNeeded;
bool borderModeChanged; bool borderModeChanged;
/* Link cable*/
Document *master;
Document *slave;
signed linkOffset;
bool linkCableBit;
} }
@property GBAudioClient *audioClient; @property GBAudioClient *audioClient;
@ -81,6 +88,10 @@ enum model {
- (void) gotNewSample:(GB_sample_t *)sample; - (void) gotNewSample:(GB_sample_t *)sample;
- (void) rumbleChanged:(double)amp; - (void) rumbleChanged:(double)amp;
- (void) loadBootROM:(GB_boot_rom_t)type; - (void) loadBootROM:(GB_boot_rom_t)type;
- (void)linkCableBitStart:(bool)bit;
- (bool)linkCableBitEnd;
- (void)infraredStateChanged:(bool)state;
@end @end
static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
@ -160,6 +171,26 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self rumbleChanged:amp]; [self rumbleChanged:amp];
} }
static void linkCableBitStart(GB_gameboy_t *gb, bool bit_to_send)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self linkCableBitStart:bit_to_send];
}
static bool linkCableBitEnd(GB_gameboy_t *gb)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
return [self linkCableBitEnd];
}
static void infraredStateChanged(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self infraredStateChanged:on];
}
@implementation Document @implementation Document
{ {
GB_gameboy_t gb; GB_gameboy_t gb;
@ -264,6 +295,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
GB_apu_set_sample_callback(&gb, audioCallback); GB_apu_set_sample_callback(&gb, audioCallback);
GB_set_rumble_callback(&gb, rumbleCallback); GB_set_rumble_callback(&gb, rumbleCallback);
GB_set_infrared_callback(&gb, infraredStateChanged);
[self updateRumbleMode]; [self updateRumbleMode];
} }
@ -335,9 +367,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[_view setRumble:amp]; [_view setRumble:amp];
} }
- (void) run - (void) preRun
{ {
running = true;
GB_set_pixels_output(&gb, self.view.pixels); GB_set_pixels_output(&gb, self.view.pixels);
GB_set_sample_rate(&gb, 96000); GB_set_sample_rate(&gb, 96000);
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
@ -368,7 +399,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
[self.audioClient start]; [self.audioClient start];
} }
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
/* Clear pending alarms, don't play alarms while playing */ /* Clear pending alarms, don't play alarms while playing */
@ -388,19 +419,58 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
} }
} }
} }
}
while (running) { static unsigned *multiplication_table_for_frequency(unsigned frequency)
if (rewind) { {
rewind = false; unsigned *ret = malloc(sizeof(*ret) * 0x100);
GB_rewind_pop(&gb); for (unsigned i = 0; i < 0x100; i++) {
if (!GB_rewind_pop(&gb)) { ret[i] = i * frequency;
rewind = self.view.isRewinding; }
return ret;
}
- (void) run
{
assert(!master);
running = true;
[self preRun];
if (slave) {
[slave preRun];
unsigned *masterTable = multiplication_table_for_frequency(GB_get_clock_rate(&gb));
unsigned *slaveTable = multiplication_table_for_frequency(GB_get_clock_rate(&slave->gb));
while (running) {
if (linkOffset <= 0) {
linkOffset += slaveTable[GB_run(&gb)];
}
else {
linkOffset -= masterTable[GB_run(&slave->gb)];
} }
} }
else { free(masterTable);
GB_run(&gb); free(slaveTable);
[slave postRun];
}
else {
while (running) {
if (rewind) {
rewind = false;
GB_rewind_pop(&gb);
if (!GB_rewind_pop(&gb)) {
rewind = self.view.isRewinding;
}
}
else {
GB_run(&gb);
}
} }
} }
[self postRun];
stopping = false;
}
- (void)postRun
{
[hex_timer invalidate]; [hex_timer invalidate];
[audioLock lock]; [audioLock lock];
memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer)); memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer));
@ -431,18 +501,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"]; [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"];
} }
[_view setRumble:0]; [_view setRumble:0];
stopping = false;
} }
- (void) start - (void) start
{ {
if (running) return;
self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0;
if (master) {
[master start];
return;
}
if (running) return;
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
} }
- (void) stop - (void) stop
{ {
if (master) {
if (!master->running) return;
GB_debugger_set_disabled(&gb, true);
if (GB_debugger_is_stopped(&gb)) {
[self interruptDebugInputRead];
}
[master stop];
GB_debugger_set_disabled(&gb, false);
return;
}
if (!running) return; if (!running) return;
GB_debugger_set_disabled(&gb, true); GB_debugger_set_disabled(&gb, true);
if (GB_debugger_is_stopped(&gb)) { if (GB_debugger_is_stopped(&gb)) {
@ -519,6 +602,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)togglePause:(id)sender - (IBAction)togglePause:(id)sender
{ {
if (master) {
[master togglePause:sender];
return;
}
if (running) { if (running) {
[self stop]; [self stop];
} }
@ -749,6 +836,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)close - (void)close
{ {
[self disconnectLinkCable];
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"]; [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"];
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"];
[self stop]; [self stop];
@ -760,9 +848,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{ {
[self log:"^C\n"]; [self log:"^C\n"];
GB_debugger_break(&gb); GB_debugger_break(&gb);
if (!running) { [self start];
[self start];
}
[self.consoleWindow makeKeyAndOrderFront:nil]; [self.consoleWindow makeKeyAndOrderFront:nil];
[self.consoleInput becomeFirstResponder]; [self.consoleInput becomeFirstResponder];
} }
@ -781,10 +867,13 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{ {
if ([anItem action] == @selector(mute:)) { if ([anItem action] == @selector(mute:)) {
[(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; [(NSMenuItem *)anItem setState:!self.audioClient.isPlaying];
} }
else if ([anItem action] == @selector(togglePause:)) { else if ([anItem action] == @selector(togglePause:)) {
[(NSMenuItem*)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))]; if (master) {
[(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))];
}
[(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))];
return !GB_debugger_is_stopped(&gb); return !GB_debugger_is_stopped(&gb);
} }
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) { else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
@ -804,6 +893,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
else if ([anItem action] == @selector(connectWorkboy:)) { else if ([anItem action] == @selector(connectWorkboy:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy]; [(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy];
} }
else if ([anItem action] == @selector(connectLinkCable:)) {
[(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
[(NSMenuItem *)anItem representedObject] == slave];
}
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)];
} }
@ -1090,6 +1183,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{ {
while (!GB_is_inited(&gb)); while (!GB_is_inited(&gb));
bool was_running = running && !GB_debugger_is_stopped(&gb); bool was_running = running && !GB_debugger_is_stopped(&gb);
if (master) {
was_running |= master->running;
}
if (was_running) { if (was_running) {
[self stop]; [self stop];
} }
@ -1700,6 +1796,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)disconnectAllAccessories:(id)sender - (IBAction)disconnectAllAccessories:(id)sender
{ {
[self disconnectLinkCable];
[self performAtomicBlock:^{ [self performAtomicBlock:^{
accessory = GBAccessoryNone; accessory = GBAccessoryNone;
GB_disconnect_serial(&gb); GB_disconnect_serial(&gb);
@ -1708,6 +1805,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)connectPrinter:(id)sender - (IBAction)connectPrinter:(id)sender
{ {
[self disconnectLinkCable];
[self performAtomicBlock:^{ [self performAtomicBlock:^{
accessory = GBAccessoryPrinter; accessory = GBAccessoryPrinter;
GB_connect_printer(&gb, printImage); GB_connect_printer(&gb, printImage);
@ -1716,6 +1814,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)connectWorkboy:(id)sender - (IBAction)connectWorkboy:(id)sender
{ {
[self disconnectLinkCable];
[self performAtomicBlock:^{ [self performAtomicBlock:^{
accessory = GBAccessoryWorkboy; accessory = GBAccessoryWorkboy;
GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime); GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime);
@ -1832,4 +1931,83 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{ {
GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb)); GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb));
} }
- (void)disconnectLinkCable
{
bool wasRunning = self->running;
Document *partner = master ?: slave;
if (partner) {
[self stop];
partner->master = nil;
partner->slave = nil;
master = nil;
slave = nil;
if (wasRunning) {
[partner start];
[self start];
}
GB_set_turbo_mode(&gb, false, false);
GB_set_turbo_mode(&partner->gb, false, false);
partner->accessory = GBAccessoryNone;
accessory = GBAccessoryNone;
}
}
- (void)connectLinkCable:(NSMenuItem *)sender
{
[self disconnectAllAccessories:sender];
Document *partner = [sender representedObject];
[partner disconnectAllAccessories:sender];
bool wasRunning = self->running;
[self stop];
[partner stop];
GB_set_turbo_mode(&partner->gb, true, true);
slave = partner;
partner->master = self;
linkOffset = 0;
partner->accessory = GBAccessoryLinkCable;
accessory = GBAccessoryLinkCable;
GB_set_serial_transfer_bit_start_callback(&gb, linkCableBitStart);
GB_set_serial_transfer_bit_start_callback(&partner->gb, linkCableBitStart);
GB_set_serial_transfer_bit_end_callback(&gb, linkCableBitEnd);
GB_set_serial_transfer_bit_end_callback(&partner->gb, linkCableBitEnd);
if (wasRunning) {
[self start];
}
}
- (void)linkCableBitStart:(bool)bit
{
linkCableBit = bit;
}
-(bool)linkCableBitEnd
{
bool ret = GB_serial_get_data_bit(&self.partner->gb);
GB_serial_set_data_bit(&self.partner->gb, linkCableBit);
return ret;
}
- (void)infraredStateChanged:(bool)state
{
if (self.partner) {
GB_set_infrared_input(&self.partner->gb, state);
}
}
-(Document *)partner
{
return slave ?: master;
}
- (bool)isSlave
{
return master;
}
- (GB_gameboy_t *)gb
{
return &gb;
}
@end @end

View File

@ -59,6 +59,9 @@
<view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView"> <view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/> <rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<connections>
<outlet property="document" destination="-2" id="Fvh-rD-z4r"/>
</connections>
</view> </view>
</subviews> </subviews>
</customView> </customView>
@ -115,7 +118,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" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<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"/>
@ -152,7 +155,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" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<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"/>
@ -186,7 +189,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" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<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"/>
@ -975,7 +978,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" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<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"/>

View File

@ -1,6 +1,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include <Core/gb.h> #include <Core/gb.h>
#import <JoyKit/JoyKit.h> #import <JoyKit/JoyKit.h>
@class Document;
typedef enum { typedef enum {
GB_FRAME_BLENDING_MODE_DISABLED, GB_FRAME_BLENDING_MODE_DISABLED,
@ -13,6 +14,7 @@ typedef enum {
@interface GBView : NSView<JOYListener> @interface GBView : NSView<JOYListener>
- (void) flip; - (void) flip;
- (uint32_t *) pixels; - (uint32_t *) pixels;
@property (weak) IBOutlet Document *document;
@property GB_gameboy_t *gb; @property GB_gameboy_t *gb;
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;

View File

@ -5,6 +5,7 @@
#import "GBViewMetal.h" #import "GBViewMetal.h"
#import "GBButtons.h" #import "GBButtons.h"
#import "NSString+StringForKey.h" #import "NSString+StringForKey.h"
#import "Document.h"
#define JOYSTICK_HIGH 0x4000 #define JOYSTICK_HIGH 0x4000
#define JOYSTICK_LOW 0x3800 #define JOYSTICK_LOW 0x3800
@ -257,6 +258,9 @@ static const uint8_t workboy_vk_to_key[] = {
{ {
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) { if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
GB_set_clock_multiplier(_gb, analogClockMultiplier); GB_set_clock_multiplier(_gb, analogClockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier);
}
if (analogClockMultiplier == 1.0) { if (analogClockMultiplier == 1.0) {
analogClockMultiplierValid = false; analogClockMultiplierValid = false;
} }
@ -265,10 +269,16 @@ static const uint8_t workboy_vk_to_key[] = {
if (underclockKeyDown && clockMultiplier > 0.5) { if (underclockKeyDown && clockMultiplier > 0.5) {
clockMultiplier -= 1.0/16; clockMultiplier -= 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier); GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
} }
if (!underclockKeyDown && clockMultiplier < 1.0) { if (!underclockKeyDown && clockMultiplier < 1.0) {
clockMultiplier += 1.0/16; clockMultiplier += 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier); GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
} }
} }
current_buffer = (current_buffer + 1) % self.numberOfBuffers; current_buffer = (current_buffer + 1) % self.numberOfBuffers;
@ -299,6 +309,9 @@ static const uint8_t workboy_vk_to_key[] = {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
unsigned player_count = GB_get_player_count(_gb); unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) { for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) { for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
@ -308,13 +321,20 @@ static const uint8_t workboy_vk_to_key[] = {
handled = true; handled = true;
switch (button) { switch (button) {
case GBTurbo: case GBTurbo:
GB_set_turbo_mode(_gb, true, self.isRewinding); if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, true, false);
}
else {
GB_set_turbo_mode(_gb, true, self.isRewinding);
}
analogClockMultiplierValid = false; analogClockMultiplierValid = false;
break; break;
case GBRewind: case GBRewind:
self.isRewinding = true; if (!self.document.partner) {
GB_set_turbo_mode(_gb, false, false); self.isRewinding = true;
GB_set_turbo_mode(_gb, false, false);
}
break; break;
case GBUnderclock: case GBUnderclock:
@ -323,7 +343,17 @@ static const uint8_t workboy_vk_to_key[] = {
break; break;
default: default:
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true); if (self.document.partner) {
if (player == 0) {
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true);
}
else {
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true);
}
}
else {
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
}
break; break;
} }
} }
@ -351,6 +381,9 @@ static const uint8_t workboy_vk_to_key[] = {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
unsigned player_count = GB_get_player_count(_gb); unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) { for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) { for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
@ -360,7 +393,12 @@ static const uint8_t workboy_vk_to_key[] = {
handled = true; handled = true;
switch (button) { switch (button) {
case GBTurbo: case GBTurbo:
GB_set_turbo_mode(_gb, false, false); if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false);
}
analogClockMultiplierValid = false; analogClockMultiplierValid = false;
break; break;
@ -374,7 +412,17 @@ static const uint8_t workboy_vk_to_key[] = {
break; break;
default: default:
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false); if (self.document.partner) {
if (player == 0) {
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false);
}
else {
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false);
}
}
else {
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
}
break; break;
} }
} }
@ -415,13 +463,11 @@ static const uint8_t workboy_vk_to_key[] = {
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{ {
if (![self.window isMainWindow]) return; if (![self.window isMainWindow]) return;
if (controller != lastController) {
[self setRumble:0];
lastController = controller;
}
unsigned player_count = GB_get_player_count(_gb); unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
IOPMAssertionID assertionID; IOPMAssertionID assertionID;
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID); IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
@ -447,33 +493,63 @@ static const uint8_t workboy_vk_to_key[] = {
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3]; usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
} }
GB_gameboy_t *effectiveGB = _gb;
unsigned effectivePlayer = player;
if (player && self.document.partner) {
effectiveGB = self.document.partner.gb;
effectivePlayer = 0;
if (controller != self.document.partner.view->lastController) {
[self setRumble:0];
self.document.partner.view->lastController = controller;
}
}
else {
if (controller != lastController) {
[self setRumble:0];
lastController = controller;
}
}
switch (usage) { switch (usage) {
case JOYButtonUsageNone: break; case JOYButtonUsageNone: break;
case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break; case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break;
case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break; case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break;
case JOYButtonUsageC: break; case JOYButtonUsageC: break;
case JOYButtonUsageStart: case JOYButtonUsageStart:
case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break; case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break;
case JOYButtonUsageSelect: case JOYButtonUsageSelect:
case JOYButtonUsageY: GB_set_key_state_for_player(_gb, GB_KEY_SELECT, player, button.isPressed); break; case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageR2: case JOYButtonUsageR2:
case JOYButtonUsageL2: case JOYButtonUsageL2:
case JOYButtonUsageZ: { case JOYButtonUsageZ: {
self.isRewinding = button.isPressed; self.isRewinding = button.isPressed;
if (button.isPressed) { if (button.isPressed) {
GB_set_turbo_mode(_gb, false, false); if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false);
}
} }
break; break;
} }
case JOYButtonUsageL1: GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break; case JOYButtonUsageL1: {
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false); break;
}
else {
GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break;
}
}
case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break; case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break;
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break; case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break; case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break; case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break; case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break;
default: default:
break; break;

View File

@ -12,7 +12,11 @@
</customObject> </customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/> <customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
<outlet property="linkCableMenuItem" destination="V4S-Fo-xJK" id="KL9-3K-64i"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items> <items>
@ -373,6 +377,17 @@
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/> <action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Game Link Cable &amp; Infrared" id="V4S-Fo-xJK">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Game Link Cable &amp; Infrared" id="6sJ-Wz-QLj">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PMY-5j-25T"/>
</connections>
</menu>
<connections>
<action selector="nop:" target="Voe-Tx-rLC" id="Bpa-0C-lkN"/>
</connections>
</menuItem>
<menuItem title="Game Boy Printer" id="zHR-Ha-pOR"> <menuItem title="Game Boy Printer" id="zHR-Ha-pOR">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>

View File

@ -803,9 +803,7 @@ void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callbac
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback); void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback);
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback);
#ifdef GB_INTERNAL
uint32_t GB_get_clock_rate(GB_gameboy_t *gb); uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier);
unsigned GB_get_screen_width(GB_gameboy_t *gb); unsigned GB_get_screen_width(GB_gameboy_t *gb);