SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 60b89787622840b2f5cf1490d401a82fa0257a5d parent 88198e64f4398755d0892b63a4176dfabe6f690e Author: Lior Halphon <LIJI32@gmail.com> Date: Fri, 13 Nov 2020 23:07:35 +0200 Local link cable and infrared emulation in the Cocoa port Diffstat:
| M | Cocoa/AppDelegate.h | 3 | ++- |
| M | Cocoa/AppDelegate.m | 25 | ++++++++++++++++++++++++- |
| M | Cocoa/Document.h | 5 | ++++- |
| M | Cocoa/Document.m | 220 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
| M | Cocoa/Document.xib | 11 | +++++++---- |
| M | Cocoa/GBView.h | 2 | ++ |
| M | Cocoa/GBView.m | 118 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
| M | Cocoa/MainMenu.xib | 17 | ++++++++++++++++- |
| M | Core/gb.h | 2 | -- |
9 files changed, 351 insertions(+), 52 deletions(-)
diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h @@ -1,6 +1,6 @@ #import <Cocoa/Cocoa.h> -@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate> +@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate> @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 @@ -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 @@ -6,6 +6,7 @@ @class GBCheatWindowController; @interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate> +@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 @@ -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<NSValidatedUserInterfaceItem>)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 @@ -59,6 +59,9 @@ <view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView"> <rect key="frame" x="0.0" y="0.0" width="160" height="144"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <connections> + <outlet property="document" destination="-2" id="Fvh-rD-z4r"/> + </connections> </view> </subviews> </customView> @@ -115,7 +118,7 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S"> <rect key="frame" x="0.0" y="0.0" width="591" height="376"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <autoresizingMask key="autoresizingMask"/> <subviews> <textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl"> <rect key="frame" x="0.0" y="0.0" width="591" height="376"/> @@ -152,7 +155,7 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC"> <rect key="frame" x="0.0" y="0.0" width="329" height="47"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <autoresizingMask key="autoresizingMask"/> <subviews> <textView ambiguous="YES" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4"> <rect key="frame" x="0.0" y="0.0" width="329" height="47"/> @@ -186,7 +189,7 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg"> <rect key="frame" x="0.0" y="0.0" width="329" height="328"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <autoresizingMask key="autoresizingMask"/> <subviews> <textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp"> <rect key="frame" x="0.0" y="0.0" width="329" height="328"/> @@ -975,7 +978,7 @@ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/> <clipView key="contentView" id="mzf-yu-RID"> <rect key="frame" x="1" y="0.0" width="398" height="274"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <autoresizingMask key="autoresizingMask"/> <subviews> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" headerView="pvX-uJ-qK5" id="tA3-8T-bxb"> <rect key="frame" x="0.0" y="0.0" width="398" height="249"/> diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h @@ -1,6 +1,7 @@ #import <Cocoa/Cocoa.h> #include <Core/gb.h> #import <JoyKit/JoyKit.h> +@class Document; typedef enum { GB_FRAME_BLENDING_MODE_DISABLED, @@ -13,6 +14,7 @@ typedef enum { @interface GBView : NSView<JOYListener> - (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 @@ -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 @@ -12,7 +12,11 @@ </customObject> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/> - <customObject id="Voe-Tx-rLC" customClass="AppDelegate"/> + <customObject id="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"/> <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> <items> @@ -373,6 +377,17 @@ <action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/> </connections> </menuItem> + <menuItem title="Game Link Cable & Infrared" id="V4S-Fo-xJK"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Game Link Cable & 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"> <modifierMask key="keyEquivalentModifierMask"/> <connections> diff --git 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);
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.