diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index 22e0c36..8f91565 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -1,6 +1,6 @@ #import -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject @property IBOutlet NSWindow *preferencesWindow; @property (strong) IBOutlet NSView *graphicsTab; @@ -10,6 +10,7 @@ - (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)switchPreferencesTab:(id)sender; +@property (weak) IBOutlet NSMenuItem *linkCableMenuItem; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 133fab7..282105e 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -81,10 +81,29 @@ if ([anItem action] == @selector(toggleDeveloperMode:)) { [(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; } - + + if (anItem == self.linkCableMenuItem) { + return [[NSDocumentController sharedDocumentController] documents].count > 1; + } 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 { NSArray *objects; @@ -109,4 +128,8 @@ { [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; } + +- (IBAction)nop:(id)sender +{ +} @end diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 660d7bc..bf5d9c0 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -6,6 +6,7 @@ @class GBCheatWindowController; @interface Document : NSDocument +@property (readonly) GB_gameboy_t *gb; @property (strong) IBOutlet GBView *view; @property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSPanel *consoleWindow; @@ -36,10 +37,12 @@ @property (strong) IBOutlet NSBox *debuggerVerticalLine; @property (strong) IBOutlet NSPanel *cheatsWindow; @property (strong) IBOutlet GBCheatWindowController *cheatWindowController; +@property (readonly) Document *partner; +@property (readonly) bool isSlave; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; - +-(void) connectLinkCable:(NSMenuItem *)sender; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 653179d..84e181f 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -28,6 +28,7 @@ enum model { NSMutableAttributedString *pending_console_output; NSRecursiveLock *console_output_lock; NSTimer *console_output_timer; + NSTimer *hex_timer; bool fullScreen; bool in_sync_input; @@ -47,7 +48,7 @@ enum model { bool oamUpdating; NSMutableData *currentPrinterImageData; - enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy} accessory; + enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory; bool rom_warning_issued; @@ -66,6 +67,12 @@ enum model { size_t audioBufferNeeded; bool borderModeChanged; + + /* Link cable*/ + Document *master; + Document *slave; + signed linkOffset; + bool linkCableBit; } @property GBAudioClient *audioClient; @@ -81,6 +88,10 @@ enum model { - (void) gotNewSample:(GB_sample_t *)sample; - (void) rumbleChanged:(double)amp; - (void) loadBootROM:(GB_boot_rom_t)type; +- (void)linkCableBitStart:(bool)bit; +- (bool)linkCableBitEnd; +- (void)infraredStateChanged:(bool)state; + @end 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]; } + +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 { 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_apu_set_sample_callback(&gb, audioCallback); GB_set_rumble_callback(&gb, rumbleCallback); + GB_set_infrared_callback(&gb, infraredStateChanged); [self updateRumbleMode]; } @@ -335,9 +367,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [_view setRumble:amp]; } -- (void) run +- (void) preRun { - running = true; GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { @@ -368,7 +399,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { [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]; /* Clear pending alarms, don't play alarms while playing */ @@ -388,19 +419,58 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) } } } - - while (running) { - if (rewind) { - rewind = false; - GB_rewind_pop(&gb); - if (!GB_rewind_pop(&gb)) { - rewind = self.view.isRewinding; +} + +static unsigned *multiplication_table_for_frequency(unsigned frequency) +{ + unsigned *ret = malloc(sizeof(*ret) * 0x100); + for (unsigned i = 0; i < 0x100; i++) { + ret[i] = i * frequency; + } + 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 { - GB_run(&gb); + free(masterTable); + 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]; [audioLock lock]; memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer)); @@ -421,7 +491,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil]; friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""]; friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - + notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName]; notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName]; notification.identifier = self.fileName; @@ -431,18 +501,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"]; } [_view setRumble:0]; - stopping = false; } - (void) start { - if (running) return; 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]; } - (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; GB_debugger_set_disabled(&gb, true); if (GB_debugger_is_stopped(&gb)) { @@ -519,6 +602,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)togglePause:(id)sender { + if (master) { + [master togglePause:sender]; + return; + } if (running) { [self stop]; } @@ -749,6 +836,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (void)close { + [self disconnectLinkCable]; [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"]; [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; [self stop]; @@ -760,9 +848,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { [self log:"^C\n"]; GB_debugger_break(&gb); - if (!running) { - [self start]; - } + [self start]; [self.consoleWindow makeKeyAndOrderFront:nil]; [self.consoleInput becomeFirstResponder]; } @@ -781,10 +867,13 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (BOOL)validateUserInterfaceItem:(id)anItem { if ([anItem action] == @selector(mute:)) { - [(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; + [(NSMenuItem *)anItem setState:!self.audioClient.isPlaying]; } 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); } 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:)) { [(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:)) { [(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)); bool was_running = running && !GB_debugger_is_stopped(&gb); + if (master) { + was_running |= master->running; + } if (was_running) { [self stop]; } @@ -1700,6 +1796,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)disconnectAllAccessories:(id)sender { + [self disconnectLinkCable]; [self performAtomicBlock:^{ accessory = GBAccessoryNone; GB_disconnect_serial(&gb); @@ -1708,6 +1805,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)connectPrinter:(id)sender { + [self disconnectLinkCable]; [self performAtomicBlock:^{ accessory = GBAccessoryPrinter; GB_connect_printer(&gb, printImage); @@ -1716,6 +1814,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)connectWorkboy:(id)sender { + [self disconnectLinkCable]; [self performAtomicBlock:^{ accessory = GBAccessoryWorkboy; 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)); } + +- (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 diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index d02f5bd..a2cf5ee 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -59,6 +59,9 @@ + + + @@ -115,7 +118,7 @@ - + @@ -152,7 +155,7 @@ - + @@ -186,7 +189,7 @@ - + @@ -975,7 +978,7 @@ - + diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index 80721cd..26fce14 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,6 +1,7 @@ #import #include #import +@class Document; typedef enum { GB_FRAME_BLENDING_MODE_DISABLED, @@ -13,6 +14,7 @@ typedef enum { @interface GBView : NSView - (void) flip; - (uint32_t *) pixels; +@property (weak) IBOutlet Document *document; @property GB_gameboy_t *gb; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 6854ba7..0d834c0 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -5,6 +5,7 @@ #import "GBViewMetal.h" #import "GBButtons.h" #import "NSString+StringForKey.h" +#import "Document.h" #define JOYSTICK_HIGH 0x4000 #define JOYSTICK_LOW 0x3800 @@ -257,6 +258,9 @@ static const uint8_t workboy_vk_to_key[] = { { if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) { GB_set_clock_multiplier(_gb, analogClockMultiplier); + if (self.document.partner) { + GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier); + } if (analogClockMultiplier == 1.0) { analogClockMultiplierValid = false; } @@ -265,10 +269,16 @@ static const uint8_t workboy_vk_to_key[] = { if (underclockKeyDown && clockMultiplier > 0.5) { clockMultiplier -= 1.0/16; GB_set_clock_multiplier(_gb, clockMultiplier); + if (self.document.partner) { + GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier); + } } if (!underclockKeyDown && clockMultiplier < 1.0) { clockMultiplier += 1.0/16; 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; @@ -299,6 +309,9 @@ static const uint8_t workboy_vk_to_key[] = { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } for (unsigned player = 0; player < player_count; player++) { for (GBButton button = 0; button < GBButtonCount; button++) { NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; @@ -308,13 +321,20 @@ static const uint8_t workboy_vk_to_key[] = { handled = true; switch (button) { 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; break; case GBRewind: - self.isRewinding = true; - GB_set_turbo_mode(_gb, false, false); + if (!self.document.partner) { + self.isRewinding = true; + GB_set_turbo_mode(_gb, false, false); + } break; case GBUnderclock: @@ -323,7 +343,17 @@ static const uint8_t workboy_vk_to_key[] = { break; 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; } } @@ -351,6 +381,9 @@ static const uint8_t workboy_vk_to_key[] = { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } for (unsigned player = 0; player < player_count; player++) { for (GBButton button = 0; button < GBButtonCount; button++) { NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; @@ -360,7 +393,12 @@ static const uint8_t workboy_vk_to_key[] = { handled = true; switch (button) { 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; break; @@ -374,7 +412,17 @@ static const uint8_t workboy_vk_to_key[] = { break; 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; } } @@ -415,13 +463,11 @@ static const uint8_t workboy_vk_to_key[] = { - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { if (![self.window isMainWindow]) return; - if (controller != lastController) { - [self setRumble:0]; - lastController = controller; - } - unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } IOPMAssertionID 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]; } + 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) { case JOYButtonUsageNone: break; - case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break; - case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break; + case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break; + case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break; case JOYButtonUsageC: break; case JOYButtonUsageStart: - case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break; + case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break; 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 JOYButtonUsageL2: case JOYButtonUsageZ: { self.isRewinding = 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; } - 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 JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break; - case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break; - case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break; - case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break; + case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, 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(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break; default: break; diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 586d872..04bcf8f 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -12,7 +12,11 @@ - + + + + + @@ -373,6 +377,17 @@ + + + + + + + + + + + diff --git a/Core/gb.h b/Core/gb.h index 9043936..c42af4b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -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_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); -#ifdef GB_INTERNAL uint32_t GB_get_clock_rate(GB_gameboy_t *gb); -#endif void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); unsigned GB_get_screen_width(GB_gameboy_t *gb);