From 08ca56eec7f53f6f8849f667c2137666162ca585 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 May 2020 00:05:43 +0300 Subject: [PATCH 001/107] Cleanup --- Core/gb.h | 2 +- Core/memory.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index f5f7df5..426a3ef 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -418,7 +418,7 @@ struct GB_gameboy_internal_s { struct { uint8_t bank_low:6; uint8_t bank_high:3; - uint8_t mode:1; + bool mode:1; } huc1; struct { diff --git a/Core/memory.c b/Core/memory.c index 474eda6..61a397a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -174,13 +174,16 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_subtype != GB_CAMERA && gb->cartridge_type->mbc_type != GB_HUC1 && - gb->cartridge_type->mbc_type != GB_HUC3) return 0xFF; + gb->cartridge_type->mbc_type != GB_HUC3) { + return 0xFF; + } if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { return 0xc0 | effective_ir_input(gb); } - if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && + gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ return gb->rtc_latched.data[gb->mbc_ram_bank - 8]; From 369410f3705b155117273548898389d649d52a20 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 May 2020 00:09:30 +0300 Subject: [PATCH 002/107] Fix HuC-1 regression --- Core/gb.h | 1 + Core/memory.c | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 426a3ef..4e6cb3e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -419,6 +419,7 @@ struct GB_gameboy_internal_s { uint8_t bank_low:6; uint8_t bank_high:3; bool mode:1; + bool ir_mode:1; } huc1; struct { diff --git a/Core/memory.c b/Core/memory.c index 61a397a..85ee099 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -178,7 +178,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) return 0xFF; } - if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { return 0xc0 | effective_ir_input(gb); } @@ -525,9 +525,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_HUC1: switch (addr & 0xF000) { - case 0x0000: case 0x1000: gb->huc1.mode = (value & 0xF) == 0xE; break; + case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break; case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->huc1.mode = value; break; } break; case GB_HUC3: @@ -634,7 +635,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_type != GB_HUC1) return; - if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { bool old_input = effective_ir_input(gb); gb->cart_ir = value & 1; bool new_input = effective_ir_input(gb); From 7af66387def986861992d977aab82502e1fe6b6b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 May 2020 14:50:54 +0300 Subject: [PATCH 003/107] HuC-3 alarm clock emulation --- Cocoa/AppDelegate.h | 2 +- Cocoa/AppDelegate.m | 6 ++ Cocoa/Document.m | 33 ++++++++++ Core/gb.c | 39 +++++++++++ Core/gb.h | 5 ++ Core/memory.c | 156 +++++++++++++++++++++++++------------------- 6 files changed, 172 insertions(+), 69 deletions(-) diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index 608a50c..22e0c36 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; diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 3404620..e54012f 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -50,6 +50,8 @@ JOYAxes2DEmulateButtonsKey: @YES, JOYHatsEmulateButtonsKey: @YES, }]; + + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; } - (IBAction)toggleDeveloperMode:(id)sender @@ -101,4 +103,8 @@ return YES; } +- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification +{ + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; +} @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ff47cd9..ff77f8e 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -358,6 +358,23 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) } NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; + + /* Clear pending alarms, don't play alarms while playing*/ + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + for (NSUserNotification *notification in [center scheduledNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeScheduledNotification:notification]; + break; + } + } + + for (NSUserNotification *notification in [center deliveredNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeDeliveredNotification:notification]; + break; + } + } + while (running) { if (rewind) { rewind = false; @@ -381,6 +398,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) self.view.mouseHidingEnabled = NO; GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); + unsigned time_to_alarm = GB_time_to_alarm(&gb); + + if (time_to_alarm) { + NSUserNotification *notification = [[NSUserNotification alloc] init]; + NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension]; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil]; + friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""]; + friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName]; + notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName]; + notification.identifier = self.fileName; + notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm]; + notification.soundName = NSUserNotificationDefaultSoundName; + [center scheduleNotification:notification]; + } [_view setRumble:0]; stopping = false; } diff --git a/Core/gb.c b/Core/gb.c index d0632dd..ce9b9af 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -564,6 +564,8 @@ typedef struct __attribute__((packed)) { uint64_t last_rtc_second; uint16_t minutes; uint16_t days; + uint16_t alarm_minutes, alarm_days; + uint8_t alarm_enabled; } GB_huc3_rtc_time_t; typedef union { @@ -612,12 +614,18 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) __builtin_bswap64(gb->last_rtc_second), __builtin_bswap16(gb->huc3_minutes), __builtin_bswap16(gb->huc3_days), + __builtin_bswap16(gb->huc3_alarm_minutes), + __builtin_bswap16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, }; #else GB_huc3_rtc_time_t rtc_save = { gb->last_rtc_second, gb->huc3_minutes, gb->huc3_days, + gb->huc3_alarm_minutes, + gb->huc3_alarm_days, + gb->huc3_alarm_enabled, }; #endif memcpy(buffer, &rtc_save, sizeof(rtc_save)); @@ -666,12 +674,18 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) __builtin_bswap64(gb->last_rtc_second), __builtin_bswap16(gb->huc3_minutes), __builtin_bswap16(gb->huc3_days), + __builtin_bswap16(gb->huc3_alarm_minutes), + __builtin_bswap16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, }; #else GB_huc3_rtc_time_t rtc_save = { gb->last_rtc_second, gb->huc3_minutes, gb->huc3_days, + gb->huc3_alarm_minutes, + gb->huc3_alarm_days, + gb->huc3_alarm_enabled, }; #endif @@ -726,10 +740,16 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); gb->huc3_days = __builtin_bswap16(rtc_save.days); + gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #else gb->last_rtc_second = rtc_save.last_rtc_second; gb->huc3_minutes = rtc_save.minutes; gb->huc3_days = rtc_save.days; + gb->huc3_alarm_minutes = rtc_save.alarm_minutes; + gb->huc3_alarm_days = rtc_save.alarm_days; + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -802,6 +822,7 @@ reset_rtc: gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ gb->huc3_days = 0xFFFF; gb->huc3_minutes = 0xFFF; + gb->huc3_alarm_enabled = false; exit: return; } @@ -827,10 +848,16 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); gb->huc3_days = __builtin_bswap16(rtc_save.days); + gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #else gb->last_rtc_second = rtc_save.last_rtc_second; gb->huc3_minutes = rtc_save.minutes; gb->huc3_days = rtc_save.days; + gb->huc3_alarm_minutes = rtc_save.alarm_minutes; + gb->huc3_alarm_days = rtc_save.alarm_days; + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; #endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -902,6 +929,7 @@ reset_rtc: gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ gb->huc3_days = 0xFFFF; gb->huc3_minutes = 0xFFF; + gb->huc3_alarm_enabled = false; exit: fclose(f); return; @@ -1568,3 +1596,14 @@ void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t gb->boot_rom_load_callback = callback; request_boot_rom(gb); } + +unsigned GB_time_to_alarm(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3) return 0; + if (!gb->huc3_alarm_enabled) return 0; + if (!(gb->huc3_alarm_days & 0x2000)) return 0; + unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60); + unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60; + if (current_time > alarm_time) return 0; + return alarm_time - current_time; +} diff --git a/Core/gb.h b/Core/gb.h index 4e6cb3e..27b95b3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -438,6 +438,8 @@ struct GB_gameboy_internal_s { uint8_t huc3_mode; uint8_t huc3_access_index; uint16_t huc3_minutes, huc3_days; + uint16_t huc3_alarm_minutes, huc3_alarm_days; + bool huc3_alarm_enabled; uint8_t huc3_read; uint8_t huc3_access_flags; ); @@ -779,6 +781,9 @@ void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data); void GB_disconnect_serial(GB_gameboy_t *gb); +/* For cartridges with an alarm clock */ +unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm + /* For integration with SFC/SNES emulators */ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); diff --git a/Core/memory.c b/Core/memory.c index 85ee099..7f7686a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -554,77 +554,97 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; } +static bool huc3_write(GB_gameboy_t *gb, uint8_t value) +{ + switch (gb->huc3_mode) { + case 0xB: // RTC Write + switch (value >> 4) { + case 1: + if (gb->huc3_access_index < 3) { + gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; + } + else if (gb->huc3_access_index < 7) { + gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; + } + else { + // GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); + } + gb->huc3_access_index++; + break; + case 2: + case 3: + if (gb->huc3_access_index < 3) { + gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); + gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); + } + else if (gb->huc3_access_index < 7) { + gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); + gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); + } + else if (gb->huc3_access_index >= 0x58 && gb->huc3_access_index <= 0x5a) { + gb->huc3_alarm_minutes &= ~(0xF << ((gb->huc3_access_index - 0x58) * 4)); + gb->huc3_alarm_minutes |= ((value & 0xF) << ((gb->huc3_access_index - 0x58) * 4)); + } + else if (gb->huc3_access_index >= 0x5b && gb->huc3_access_index <= 0x5e) { + gb->huc3_alarm_days &= ~(0xF << ((gb->huc3_access_index - 0x5b) * 4)); + gb->huc3_alarm_days |= ((value & 0xF) << ((gb->huc3_access_index - 0x5b) * 4)); + } + else if (gb->huc3_access_index == 0x5f) { + gb->huc3_alarm_enabled = value & 1; + } + else { + // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3_access_index); + } + if ((value >> 4) == 3) { + gb->huc3_access_index++; + } + break; + case 4: + gb->huc3_access_index &= 0xF0; + gb->huc3_access_index |= value & 0xF; + break; + case 5: + gb->huc3_access_index &= 0x0F; + gb->huc3_access_index |= (value & 0xF) << 4; + break; + case 6: + gb->huc3_access_flags = (value & 0xF); + break; + + default: + break; + } + + return true; + case 0xD: // RTC status + // Not sure what writes here mean, they're always 0xFE + return true; + case 0xE: { // IR mode + bool old_input = effective_ir_input(gb); + gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } + gb->cycles_since_ir_change = 0; + } + return true; + } + case 0xC: + return true; + default: + return false; + case 0: // Disabled + case 0xA: // RAM + return false; + } +} + static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (gb->cartridge_type->mbc_type == GB_HUC3) { - switch (gb->huc3_mode) { - case 0xB: // RTC Write - switch (value >> 4) { - case 1: - if (gb->huc3_access_index < 3) { - gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; - } - else if (gb->huc3_access_index < 7) { - gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; - } - else { - GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); - } - gb->huc3_access_index++; - return; - case 2: - case 3: - if (gb->huc3_access_index < 3) { - gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); - gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); - } - else if (gb->huc3_access_index < 7) { - gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); - gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); - } - if ((value >> 4) == 3) { - gb->huc3_access_index++; - } - return; - case 4: - gb->huc3_access_index &= 0xF0; - gb->huc3_access_index |= value & 0xF; - return; - case 5: - gb->huc3_access_index &= 0x0F; - gb->huc3_access_index |= (value & 0xF) << 4; - return; - case 6: - gb->huc3_access_flags = (value & 0xF); - return; - - default: - break; - } - - return; - case 0xD: // RTC status - // Not sure what writes here mean, they're always 0xFE - return; - case 0xE: { // IR mode - bool old_input = effective_ir_input(gb); - gb->cart_ir = value & 1; - bool new_input = effective_ir_input(gb); - if (new_input != old_input) { - if (gb->infrared_callback) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); - } - gb->cycles_since_ir_change = 0; - } - return; - } - default: - GB_log(gb, "Unsupported HuC-3 mode %x write: [%04x] = %02x\n", gb->huc3_mode, addr, value); - return; - case 0: // Disabled - case 0xA: // RAM - break; - } + if (huc3_write(gb, value)) return; } if (gb->camera_registers_mapped) { From f1442b0ea6b86712a1b93c8e1375b08ddb985fcf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 24 May 2020 23:04:36 +0300 Subject: [PATCH 004/107] Attempt to add rumble support to SDL. Who knows it might work. --- Cocoa/Preferences.xib | 6 ++--- SDL/gui.c | 56 +++++++++++++++++++++++++++++++++++++++---- SDL/gui.h | 2 ++ SDL/main.c | 7 ++++++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 149f71e..aa4a87d 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -603,13 +603,13 @@ - + - - + + diff --git a/SDL/gui.c b/SDL/gui.c index 5650d9b..81a9e42 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -108,10 +108,11 @@ configuration_t configuration = .rewind_length = 60 * 2, .model = MODEL_CGB, .volume = 100, + .rumble_mode = GB_RUMBLE_ALL_GAMES, }; -static const char *help[] ={ +static const char *help[] = { "Drop a ROM to play.\n" "\n" "Keyboard Shortcuts:\n" @@ -763,6 +764,7 @@ static void enter_controls_menu(unsigned index) static unsigned joypad_index = 0; static SDL_Joystick *joystick = NULL; static SDL_GameController *controller = NULL; +SDL_Haptic *haptic = NULL; const char *current_joypad_name(unsigned index) { @@ -792,6 +794,12 @@ static void cycle_joypads(unsigned index) if (joypad_index >= SDL_NumJoysticks()) { joypad_index = 0; } + + if (haptic) { + SDL_HapticClose(haptic); + haptic = NULL; + } + if (controller) { SDL_GameControllerClose(controller); controller = NULL; @@ -806,14 +814,22 @@ static void cycle_joypads(unsigned index) else { joystick = SDL_JoystickOpen(joypad_index); } -} + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + }} static void cycle_joypads_backwards(unsigned index) { - joypad_index++; + joypad_index--; if (joypad_index >= SDL_NumJoysticks()) { joypad_index = SDL_NumJoysticks() - 1; } + + if (haptic) { + SDL_HapticClose(haptic); + haptic = NULL; + } + if (controller) { SDL_GameControllerClose(controller); controller = NULL; @@ -828,7 +844,9 @@ static void cycle_joypads_backwards(unsigned index) else { joystick = SDL_JoystickOpen(joypad_index); } -} + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + }} static void detect_joypad_layout(unsigned index) { @@ -837,9 +855,36 @@ static void detect_joypad_layout(unsigned index) joypad_axis_temp = -1; } +static void cycle_rumble_mode(unsigned index) +{ + if (configuration.rumble_mode == GB_RUMBLE_ALL_GAMES) { + configuration.rumble_mode = GB_RUMBLE_DISABLED; + } + else { + configuration.rumble_mode++; + } +} + +static void cycle_rumble_mode_backwards(unsigned index) +{ + if (configuration.rumble_mode == GB_RUMBLE_DISABLED) { + configuration.rumble_mode = GB_RUMBLE_ALL_GAMES; + } + else { + configuration.rumble_mode--; + } +} + +const char *current_rumble_mode(unsigned index) +{ + return (const char *[]){"Disabled", "Rumble Game Paks Only", "All Games"} + [configuration.rumble_mode]; +} + static const struct menu_item joypad_menu[] = { {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, {"Configure layout", detect_joypad_layout}, + {"Rumble Mode:", cycle_rumble_mode, current_rumble_mode, cycle_rumble_mode_backwards}, {"Back", return_to_root_menu}, {NULL,} }; @@ -893,6 +938,9 @@ void connect_joypad(void) joystick = SDL_JoystickOpen(0); } } + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + } } void run_gui(bool is_running) diff --git a/SDL/gui.h b/SDL/gui.h index 4a3b55f..af7543b 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -21,6 +21,7 @@ extern SDL_Window *window; extern SDL_Renderer *renderer; extern SDL_Texture *texture; extern SDL_PixelFormat *pixel_format; +extern SDL_Haptic *haptic; extern shader_t shader; enum scaling_mode { @@ -105,6 +106,7 @@ typedef struct { uint8_t dmg_palette; GB_border_mode_t border_mode; uint8_t volume; + GB_rumble_mode_t rumble_mode; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index c4a4d0f..f75e3aa 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -367,6 +367,11 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return SDL_MapRGB(pixel_format, r, g, b); } +static void rumble(GB_gameboy_t *gb, double amp) +{ + SDL_HapticRumblePlay(haptic, amp, 250); +} + static void debugger_interrupt(int ignore) { if (!GB_is_inited(&gb)) return; @@ -488,6 +493,8 @@ restart: GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); GB_set_pixels_output(&gb, active_pixel_buffer); GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_rumble_callback(&gb, rumble); + GB_set_rumble_mode(&gb, configuration.rumble_mode); GB_set_sample_rate(&gb, GB_audio_get_frequency()); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); update_palette(); From 17dfe0dd6a4e9ec2c6433059a5c94be56ffa1f4c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 May 2020 16:30:40 +0300 Subject: [PATCH 005/107] Fix minor CGB-C regression --- Core/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index be2e108..4bfae7a 100644 --- a/Core/display.c +++ b/Core/display.c @@ -851,8 +851,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 37, 2); gb->cgb_palettes_blocked = true; - gb->cycles_for_line += 3; - GB_SLEEP(gb, display, 38, 3); + gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3; + GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3); gb->vram_read_blocked = true; gb->vram_write_blocked = true; From 29b64d7545a4464a294d9096f6bbf7318913a6d4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 May 2020 16:51:20 +0300 Subject: [PATCH 006/107] Slightly reduce the scanline-ish LCD effect --- Shaders/MasterShader.metal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index ee8dec9..a0b6393 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -42,7 +42,7 @@ static inline float4 texture(texture2d texture, float2 pos) #line 1 {filter} -#define BLEND_BIAS (1.0/3.0) +#define BLEND_BIAS (2.0/5.0) enum frame_blending_mode { DISABLED, From ffa569deeb1656c2a473e95a782132608152bf8a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 29 May 2020 23:10:23 +0300 Subject: [PATCH 007/107] Partial emulation of reading VRAM right after mode 3 --- Core/display.c | 10 ++++++---- Core/gb.h | 2 ++ Core/memory.c | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Core/display.c b/Core/display.c index 4bfae7a..94d7f77 100644 --- a/Core/display.c +++ b/Core/display.c @@ -603,14 +603,15 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; } - gb->current_tile = gb->vram[map + x + y / 8 * 32]; + gb->last_tile_index_address = map + x + y / 8 * 32; + gb->current_tile = gb->vram[gb->last_tile_index_address]; if (gb->vram_ppu_blocked) { gb->current_tile = 0xFF; } if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. - This probably means the CGB has a 16-bit data bus for the VRAM. */ - gb->current_tile_attributes = gb->vram[map + x + y / 8 * 32 + 0x2000]; + This probably means the CGB has a 16-bit data bus for the VRAM. */ + gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000]; if (gb->vram_ppu_blocked) { gb->current_tile_attributes = 0xFF; } @@ -667,8 +668,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } + gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; gb->current_tile_data[1] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; + gb->vram[gb->last_tile_data_address]; if (gb->vram_ppu_blocked) { gb->current_tile_data[1] = 0xFF; } diff --git a/Core/gb.h b/Core/gb.h index 27b95b3..68c4ea9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -538,6 +538,8 @@ struct GB_gameboy_internal_s { uint8_t window_tile_x; uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. bool is_odd_frame; + uint16_t last_tile_data_address; + uint16_t last_tile_index_address; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index 7f7686a..3f924bc 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -146,6 +146,18 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) if (gb->vram_read_blocked) { return 0xFF; } + if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done*/ + } + else { + addr = gb->last_tile_data_address; + } + } return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; } @@ -551,6 +563,19 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); return; } + /* TODO: not verified */ + if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done */ + } + else { + addr = gb->last_tile_data_address; + } + } gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; } From fa7232944f277e4abdadd6edcd3f2bc487aca41f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 01:25:21 +0300 Subject: [PATCH 008/107] =?UTF-8?q?Better=20emulation=20of=20CGB=E2=80=99s?= =?UTF-8?q?=20first=20frame=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/display.c | 11 +++++++++-- Core/gb.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 94d7f77..2eb8c42 100644 --- a/Core/display.c +++ b/Core/display.c @@ -128,7 +128,7 @@ static void display_vblank(GB_gameboy_t *gb) bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; - if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (!GB_is_sgb(gb)) { uint32_t color = 0; @@ -796,6 +796,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) while (true) { GB_SLEEP(gb, display, 1, LCDC_PERIOD); display_vblank(gb); + gb->cgb_repeated_a_frame = true; } return; } @@ -1240,11 +1241,17 @@ abort_fetching_object: } } else { - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { gb->is_odd_frame ^= true; display_vblank(gb); } + if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) { + gb->cgb_repeated_a_frame = true; + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else { + gb->cgb_repeated_a_frame = false; + } } } diff --git a/Core/gb.h b/Core/gb.h index 68c4ea9..1376a12 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -540,6 +540,7 @@ struct GB_gameboy_internal_s { bool is_odd_frame; uint16_t last_tile_data_address; uint16_t last_tile_index_address; + bool cgb_repeated_a_frame; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ From 4d91081046f75efc701e0fda7787daebe982c72c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 17:58:02 +0300 Subject: [PATCH 009/107] Do not send LED updates if nothing changed --- JoyKit/JOYController.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 015737e..12f0d39 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -164,6 +164,8 @@ typedef union { // Used when creating inputs JOYElement *_previousAxisElement; + + uint8_t _playerLEDs; } @@ -342,6 +344,7 @@ typedef union { _logicallyConnected = true; _device = (IOHIDDeviceRef)CFRetain(device); _serialSuffix = suffix; + _playerLEDs = -1; IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self); IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); @@ -709,6 +712,10 @@ typedef union { - (void)setPlayerLEDs:(uint8_t)mask { mask &= 0xF; + if (mask == _playerLEDs) { + return; + } + _playerLEDs = mask; if (_isSwitch) { _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs _lastVendorSpecificOutput.switchPacket.sequence++; From 59b94b92ca0469f8b207081dd609fd2846fd7937 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 18:43:09 +0300 Subject: [PATCH 010/107] Make sure reports are only sent from one thread --- Cocoa/GBView.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index e733731..726259d 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -115,7 +115,7 @@ [NSCursor unhide]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; - [lastController setRumbleAmplitude:0]; + [self setRumble:0]; [JOYController unregisterListener:self]; } - (instancetype)initWithCoder:(NSCoder *)coder @@ -272,7 +272,9 @@ - (void)setRumble:(double)amp { - [lastController setRumbleAmplitude:amp]; + dispatch_async(dispatch_get_main_queue(), ^{ + [lastController setRumbleAmplitude:amp]; + }); } - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis @@ -301,7 +303,7 @@ { if (![self.window isMainWindow]) return; if (controller != lastController) { - [lastController setRumbleAmplitude:0]; + [self setRumble:0]; lastController = controller; } @@ -319,7 +321,9 @@ ![preferred_joypad isEqualToString:controller.uniqueID]) { continue; } - [controller setPlayerLEDs:1 << player]; + dispatch_async(dispatch_get_main_queue(), ^{ + [controller setPlayerLEDs:1 << player]; + }); NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; if (!mapping) { mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; From e678b50101271ede54d2f0371af2cbc3775ed550 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 20:15:13 +0300 Subject: [PATCH 011/107] Force all controllers to use a rumble thread --- JoyKit/JOYController.m | 96 +++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 12f0d39..968e2e9 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -39,7 +39,7 @@ static bool axesEmulateButtons = false; static bool axes2DEmulateButtons = false; static bool hatsEmulateButtons = false; -static NSLock *globalPWMThreadLock; +static NSLock *globalRumbleThreadLock; @interface JOYController () + (void)controllerAdded:(IOHIDDeviceRef) device; @@ -152,12 +152,12 @@ typedef union { bool _isSwitch; // Does this controller use the Switch protocol? bool _isDualShock3; // Does this controller use DS3 outputs? JOYVendorSpecificOutput _lastVendorSpecificOutput; - NSLock *_rumblePWMThreadLock; + NSLock *_rumbleThreadLock; volatile double _rumblePWMRatio; bool _physicallyConnected; bool _logicallyConnected; - bool _rumblePWMThreadRunning; - volatile bool _forceStopPWMThread; + bool _rumbleThreadRunning; + volatile bool _forceStopRumbleThread; NSDictionary *_hacks; NSMutableData *_lastReport; @@ -358,7 +358,7 @@ typedef union { _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; _hatEmulatedButtons = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; - _rumblePWMThreadLock = [[NSLock alloc] init]; + _rumbleThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; @@ -697,7 +697,7 @@ typedef union { } } _physicallyConnected = false; - [self _forceStopPWMThread]; // Stop the rumble thread. + [self _forceStopRumbleThread]; // Stop the rumble thread. [exposedControllers removeObject:self]; _device = nil; } @@ -731,23 +731,30 @@ typedef union { } } -- (void)pwmThread +- (void)rumbleThread { unsigned rumbleCounter = 0; - while (self.connected && !_forceStopPWMThread) { - if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { - break; - } - rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); - if (rumbleCounter >= PWM_RESOLUTION) { - rumbleCounter -= PWM_RESOLUTION; + if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { + while (self.connected && !_forceStopRumbleThread) { + if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { + break; + } + rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); + if (rumbleCounter >= PWM_RESOLUTION) { + rumbleCounter -= PWM_RESOLUTION; + } } } - [_rumblePWMThreadLock lock]; + else { + while (self.connected && !_forceStopRumbleThread) { + [_rumbleElement setValue:_rumblePWMRatio * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; + } + } + [_rumbleThreadLock lock]; [_rumbleElement setValue:0]; - _rumblePWMThreadRunning = false; - _forceStopPWMThread = false; - [_rumblePWMThreadLock unlock]; + _rumbleThreadRunning = false; + _forceStopRumbleThread = false; + [_rumbleThreadLock unlock]; } - (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ @@ -799,32 +806,27 @@ typedef union { [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; } else { - if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { - [_rumblePWMThreadLock lock]; - _rumblePWMRatio = amp; - if (!_rumblePWMThreadRunning) { // PWM thread not running, start it. - if (amp != 0) { - /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more - than one controller uses rumble. At least make sure any sibling controllers don't have their - PWM thread running. */ - - [globalPWMThreadLock lock]; - for (JOYController *controller in [JOYController allControllers]) { - if (controller != self && controller->_device == _device) { - [controller _forceStopPWMThread]; - } + [_rumbleThreadLock lock]; + _rumblePWMRatio = amp; + if (!_rumbleThreadRunning) { // PWM thread not running, start it. + if (amp != 0) { + /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more + than one controller uses rumble. At least make sure any sibling controllers don't have their + PWM thread running. */ + + [globalRumbleThreadLock lock]; + for (JOYController *controller in [JOYController allControllers]) { + if (controller != self && controller->_device == _device) { + [controller _forceStopRumbleThread]; } - _rumblePWMRatio = amp; - _rumblePWMThreadRunning = true; - [self performSelectorInBackground:@selector(pwmThread) withObject:nil]; - [globalPWMThreadLock unlock]; } + _rumblePWMRatio = amp; + _rumbleThreadRunning = true; + [self performSelectorInBackground:@selector(rumbleThread) withObject:nil]; + [globalRumbleThreadLock unlock]; } - [_rumblePWMThreadLock unlock]; - } - else { - [_rumbleElement setValue:amp * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; } + [_rumbleThreadLock unlock]; } } @@ -833,14 +835,14 @@ typedef union { return _logicallyConnected && _physicallyConnected; } -- (void)_forceStopPWMThread +- (void)_forceStopRumbleThread { - [_rumblePWMThreadLock lock]; - if (_rumblePWMThreadRunning) { - _forceStopPWMThread = true; + [_rumbleThreadLock lock]; + if (_rumbleThreadRunning) { + _forceStopRumbleThread = true; } - [_rumblePWMThreadLock unlock]; - while (_rumblePWMThreadRunning); + [_rumbleThreadLock unlock]; + while (_rumbleThreadRunning); } + (void)controllerAdded:(IOHIDDeviceRef) device @@ -900,7 +902,7 @@ typedef union { controllers = [NSMutableDictionary dictionary]; exposedControllers = [NSMutableArray array]; - globalPWMThreadLock = [[NSLock alloc] init]; + globalRumbleThreadLock = [[NSLock alloc] init]; NSArray *array = @[ CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), From af5cb72edc7b819969a3079ca11f83ae5f6c64fa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 21:31:00 +0300 Subject: [PATCH 012/107] Restore Switch LED support --- JoyKit/JOYController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 968e2e9..886dea4 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -722,7 +722,7 @@ typedef union { _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED _lastVendorSpecificOutput.switchPacket.commandData[0] = mask; - //[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; } else if (_isDualShock3) { _lastVendorSpecificOutput.ds3Output.reportID = 1; From c9b401135fc35119cda01bebdbd5f3e4e83a7288 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 22:18:32 +0300 Subject: [PATCH 013/107] =?UTF-8?q?Actually,=20don=E2=80=99t=20use=20rumbl?= =?UTF-8?q?e=20threads=20at=20all,=20because=20IOHIDDeviceSetReport=20seem?= =?UTF-8?q?s=20to=20queue=20stuff=20despite=20being=20blocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cocoa/GBView.m | 4 +- JoyKit/JOYController.m | 206 +++++++++++++++++------------------------ 2 files changed, 85 insertions(+), 125 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 726259d..e5cb7c8 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -272,9 +272,7 @@ - (void)setRumble:(double)amp { - dispatch_async(dispatch_get_main_queue(), ^{ - [lastController setRumbleAmplitude:amp]; - }); + [lastController setRumbleAmplitude:amp]; } - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 886dea4..96de291 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -39,8 +39,6 @@ static bool axesEmulateButtons = false; static bool axes2DEmulateButtons = false; static bool hatsEmulateButtons = false; -static NSLock *globalRumbleThreadLock; - @interface JOYController () + (void)controllerAdded:(IOHIDDeviceRef) device; + (void)controllerRemoved:(IOHIDDeviceRef) device; @@ -95,7 +93,9 @@ static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type, uint32_t reportID, uint8_t *report, CFIndex reportLength) { - [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; + if (reportLength) { + [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; + } } typedef struct __attribute__((packed)) { @@ -152,12 +152,9 @@ typedef union { bool _isSwitch; // Does this controller use the Switch protocol? bool _isDualShock3; // Does this controller use DS3 outputs? JOYVendorSpecificOutput _lastVendorSpecificOutput; - NSLock *_rumbleThreadLock; - volatile double _rumblePWMRatio; + volatile double _rumbleAmplitude; bool _physicallyConnected; bool _logicallyConnected; - bool _rumbleThreadRunning; - volatile bool _forceStopRumbleThread; NSDictionary *_hacks; NSMutableData *_lastReport; @@ -166,7 +163,8 @@ typedef union { JOYElement *_previousAxisElement; uint8_t _playerLEDs; - + double _sentRumbleAmp; + unsigned _rumbleCounter; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -358,7 +356,6 @@ typedef union { _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; _hatEmulatedButtons = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; - _rumbleThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; @@ -368,18 +365,19 @@ typedef union { _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue]; NSDictionary *customReports = hacks[JOYCustomReports]; - + _lastReport = [NSMutableData dataWithLength:MAX( + MAX( + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] + ), + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] + )]; + IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + if (hacks[JOYCustomReports]) { _multiElements = [NSMutableDictionary dictionary]; _fullReportElements = [NSMutableDictionary dictionary]; - _lastReport = [NSMutableData dataWithLength:MAX( - MAX( - [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], - [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] - ), - [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] - )]; - IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + for (NSNumber *_reportID in customReports) { signed reportID = [_reportID intValue]; @@ -555,16 +553,17 @@ typedef union { - (void)gotReport:(NSData *)report { JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)]; - if (!element) return; - [element updateValue:report]; - - NSArray *subElements = _multiElements[element]; - if (subElements) { - for (JOYElement *subElement in subElements) { - [self _elementChanged:subElement]; + if (element) { + [element updateValue:report]; + + NSArray *subElements = _multiElements[element]; + if (subElements) { + for (JOYElement *subElement in subElements) { + [self _elementChanged:subElement]; + } } - return; } + [self updateRumble]; } - (void)elementChanged:(IOHIDElementRef)element @@ -697,7 +696,6 @@ typedef union { } } _physicallyConnected = false; - [self _forceStopRumbleThread]; // Stop the rumble thread. [exposedControllers removeObject:self]; _device = nil; } @@ -731,103 +729,78 @@ typedef union { } } -- (void)rumbleThread +- (void)updateRumble { - unsigned rumbleCounter = 0; + if (!self.connected) { + return; + } if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { - while (self.connected && !_forceStopRumbleThread) { - if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) { - break; - } - rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION); - if (rumbleCounter >= PWM_RESOLUTION) { - rumbleCounter -= PWM_RESOLUTION; - } + double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION); + if (ampToSend != _sentRumbleAmp) { + [_rumbleElement setValue:ampToSend]; + _sentRumbleAmp = ampToSend; + } + _rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION); + if (_rumbleCounter >= PWM_RESOLUTION) { + _rumbleCounter -= PWM_RESOLUTION; } } else { - while (self.connected && !_forceStopRumbleThread) { - [_rumbleElement setValue:_rumblePWMRatio * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; + if (_rumbleAmplitude == _sentRumbleAmp) { + return; + } + _sentRumbleAmp = _rumbleAmplitude; + if (_isSwitch) { + double frequency = 144; + double amp = _rumbleAmplitude; + + uint8_t highAmp = amp * 0x64; + uint8_t lowAmp = amp * 0x32 + 0x40; + if (frequency < 0) frequency = 0; + if (frequency > 1252) frequency = 1252; + uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); + + uint16_t highFreq = (encodedFrequency - 0x60) * 4; + uint8_t lowFreq = encodedFrequency - 0x40; + + //if (frequency < 82 || frequency > 312) { + if (amp) { + highAmp = 0; + } + + if (frequency < 40 || frequency > 626) { + lowAmp = 0; + } + + _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; + _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); + _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; + _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; + + + _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0; // LED + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + } + else if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output.reportID = 1; + _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xff : 0; + _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xff); + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; + } + else { + [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; } } - [_rumbleThreadLock lock]; - [_rumbleElement setValue:0]; - _rumbleThreadRunning = false; - _forceStopRumbleThread = false; - [_rumbleThreadLock unlock]; } - (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ { - double frequency = 144; // I have no idea what I'm doing. - if (amp < 0) amp = 0; if (amp > 1) amp = 1; - if (_isSwitch) { - if (amp == 0) { - amp = 1; - frequency = 0; - } - - uint8_t highAmp = amp * 0x64; - uint8_t lowAmp = amp * 0x32 + 0x40; - if (frequency < 0) frequency = 0; - if (frequency > 1252) frequency = 1252; - uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); - - uint16_t highFreq = (encodedFrequency - 0x60) * 4; - uint8_t lowFreq = encodedFrequency - 0x40; - - //if (frequency < 82 || frequency > 312) { - if (amp) { - highAmp = 0; - } - - if (frequency < 40 || frequency > 626) { - lowAmp = 0; - } - - _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; - _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); - _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; - _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; - - - _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only - _lastVendorSpecificOutput.switchPacket.sequence++; - _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; - _lastVendorSpecificOutput.switchPacket.command = 0; // LED - [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; - } - else if (_isDualShock3) { - _lastVendorSpecificOutput.ds3Output.reportID = 1; - _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = amp? 0xff : 0; - _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = amp * 0xff; - [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; - } - else { - [_rumbleThreadLock lock]; - _rumblePWMRatio = amp; - if (!_rumbleThreadRunning) { // PWM thread not running, start it. - if (amp != 0) { - /* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more - than one controller uses rumble. At least make sure any sibling controllers don't have their - PWM thread running. */ - - [globalRumbleThreadLock lock]; - for (JOYController *controller in [JOYController allControllers]) { - if (controller != self && controller->_device == _device) { - [controller _forceStopRumbleThread]; - } - } - _rumblePWMRatio = amp; - _rumbleThreadRunning = true; - [self performSelectorInBackground:@selector(rumbleThread) withObject:nil]; - [globalRumbleThreadLock unlock]; - } - } - [_rumbleThreadLock unlock]; - } + _rumbleAmplitude = amp; } - (bool)isConnected @@ -835,16 +808,6 @@ typedef union { return _logicallyConnected && _physicallyConnected; } -- (void)_forceStopRumbleThread -{ - [_rumbleThreadLock lock]; - if (_rumbleThreadRunning) { - _forceStopRumbleThread = true; - } - [_rumbleThreadLock unlock]; - while (_rumbleThreadRunning); -} - + (void)controllerAdded:(IOHIDDeviceRef) device { NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); @@ -902,7 +865,6 @@ typedef union { controllers = [NSMutableDictionary dictionary]; exposedControllers = [NSMutableArray array]; - globalRumbleThreadLock = [[NSLock alloc] init]; NSArray *array = @[ CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), From c665fcb2ed2705d017bd6dcaef7f405a836c9ce5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 22:20:45 +0300 Subject: [PATCH 014/107] Minor fixes --- JoyKit/JOYController.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 96de291..b815ddb 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -697,6 +697,8 @@ typedef union { } _physicallyConnected = false; [exposedControllers removeObject:self]; + [self setRumbleAmplitude:0]; + [self updateRumble]; _device = nil; } @@ -734,6 +736,9 @@ typedef union { if (!self.connected) { return; } + if (!_rumbleElement && !_isSwitch && !_isDualShock3) { + return; + } if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION); if (ampToSend != _sentRumbleAmp) { From 83b959c12625ad8840fdb3634a0a7b9ea8be6f8c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 May 2020 22:46:06 +0300 Subject: [PATCH 015/107] Delay requests to show notifications --- Cocoa/Document.m | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ff77f8e..035c0e2 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -359,19 +359,21 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; - /* Clear pending alarms, don't play alarms while playing*/ - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - for (NSUserNotification *notification in [center scheduledNotifications]) { - if ([notification.identifier isEqualToString:self.fileName]) { - [center removeScheduledNotification:notification]; - break; + /* Clear pending alarms, don't play alarms while playing */ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + for (NSUserNotification *notification in [center scheduledNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeScheduledNotification:notification]; + break; + } } - } - - for (NSUserNotification *notification in [center deliveredNotifications]) { - if ([notification.identifier isEqualToString:self.fileName]) { - [center removeDeliveredNotification:notification]; - break; + + for (NSUserNotification *notification in [center deliveredNotifications]) { + if ([notification.identifier isEqualToString:self.fileName]) { + [center removeDeliveredNotification:notification]; + break; + } } } @@ -412,7 +414,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) notification.identifier = self.fileName; notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm]; notification.soundName = NSUserNotificationDefaultSoundName; - [center scheduleNotification:notification]; + [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"]; } [_view setRumble:0]; stopping = false; From f105f2801791f9cafe309adaa407e3c22b2ac3c7 Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Sat, 30 May 2020 15:54:51 -0400 Subject: [PATCH 016/107] Add ld b,b breakpoint Signed-off-by: James Larrowe --- Core/sm83_cpu.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 13f05df..9dbc90f 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1,6 +1,7 @@ #include #include #include +#include "debugger.h" #include "gb.h" @@ -789,6 +790,11 @@ LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) +// simply fire the debugger +static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_debugger_break(gb); +} static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { @@ -1462,7 +1468,7 @@ static GB_opcode_t *opcodes[256] = { jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, - nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_b_b, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, From abdece7737c272dc3f1e771fd18256b4d6da8bb9 Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Sat, 30 May 2020 16:35:07 -0400 Subject: [PATCH 017/107] add debugger command to enable and disable --- Core/debugger.c | 18 ++++++++++++++++++ Core/gb.h | 2 +- Core/sm83_cpu.c | 6 ++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index a46de86..ed498c9 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -832,6 +832,23 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } +/* Enable or disable software breakpoints */ +static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strcmp(lstrip(arguments), "on") == 0) { + gb->has_software_breakpoints = true; + } + else if(strcmp(lstrip(arguments), "off") == 0) { + gb->has_software_breakpoints = false; + } + else { + print_usage(gb, command); + } + + return true; +} + /* Find the index of the closest breakpoint equal or greater to addr */ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) { @@ -1780,6 +1797,7 @@ static const debugger_command_t commands[] = { "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, + {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE "Can also modify the condition of existing breakpoints." HELP_NEWLINE "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE diff --git a/Core/gb.h b/Core/gb.h index 97a8069..1445a68 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -601,7 +601,7 @@ struct GB_gameboy_internal_s { /* Breakpoints */ uint16_t n_breakpoints; struct GB_breakpoint_s *breakpoints; - bool has_jump_to_breakpoints; + bool has_jump_to_breakpoints, has_software_breakpoints; void *nontrivial_jump_state; bool non_trivial_jump_breakpoint_occured; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 9dbc90f..b337b36 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -790,10 +790,12 @@ LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) -// simply fire the debugger +// fire the debugger if software breakpoints are enabled static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) { - GB_debugger_break(gb); + if(gb->has_software_breakpoints) { + GB_debugger_break(gb); + } } static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) From 6fcf77c7f6e83b3f6e0a207f6a80ea2a2ba3e4af Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Sat, 30 May 2020 16:46:17 -0400 Subject: [PATCH 018/107] Make no argument for softbreak be equivalent to "on" --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index ed498c9..09f78c8 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -836,7 +836,7 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS - if (strcmp(lstrip(arguments), "on") == 0) { + if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) { gb->has_software_breakpoints = true; } else if(strcmp(lstrip(arguments), "off") == 0) { From fd97e1191914df4d1ea78024e1f7255dfe7f4fb9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 00:54:13 +0300 Subject: [PATCH 019/107] Spacing --- Core/sm83_cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index b337b36..f6a69fb 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -793,7 +793,7 @@ LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL // fire the debugger if software breakpoints are enabled static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) { - if(gb->has_software_breakpoints) { + if (gb->has_software_breakpoints) { GB_debugger_break(gb); } } From f1ea39f1c660ef2e377473a3e3264bed8ed05332 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 00:54:49 +0300 Subject: [PATCH 020/107] Spacing --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index 09f78c8..87b0914 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -839,7 +839,7 @@ static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) { gb->has_software_breakpoints = true; } - else if(strcmp(lstrip(arguments), "off") == 0) { + else if (strcmp(lstrip(arguments), "off") == 0) { gb->has_software_breakpoints = false; } else { From 97e844a0b743529d17309026e43072fc290e32e5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 01:01:06 +0300 Subject: [PATCH 021/107] GB_debugger_break is for external APIs, not available on libretro builds --- Core/sm83_cpu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 84105ba..3b3eceb 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1,7 +1,6 @@ #include #include #include -#include "debugger.h" #include "gb.h" @@ -794,7 +793,7 @@ LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) { if (gb->has_software_breakpoints) { - GB_debugger_break(gb); + gb->debug_stopped = true; } } From 0c0ca8e862ebf5aa049b68d067d5db8a7a82b83b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 01:41:27 +0300 Subject: [PATCH 022/107] =?UTF-8?q?Last=20resort=20for=20Macs=20that=20can?= =?UTF-8?q?=E2=80=99t=20send=20reports=20to=20certain=20devices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JoyKit/JOYController.m | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index b815ddb..ca2d1b1 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -120,7 +120,7 @@ typedef struct __attribute__((packed)) { uint8_t dutyLength; uint8_t enabled; uint8_t dutyOff; - uint8_t dutyOnn; + uint8_t dutyOn; } __attribute__((packed)) led[5]; uint8_t padding3[13]; } JOYDualShock3Output; @@ -165,6 +165,7 @@ typedef union { uint8_t _playerLEDs; double _sentRumbleAmp; unsigned _rumbleCounter; + bool _deviceCantSendReports; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -482,11 +483,11 @@ typedef union { _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){ .reportID = 1, .led = { - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32}, - {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOnn = 0}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0}, } }; @@ -706,7 +707,13 @@ typedef union { { if (!report.length) return; if (!_device) return; - IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length); + if (_deviceCantSendReports) return; + /* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after + freezing for 5 seconds. Stop sending reports if that's the case. */ + if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) { + _deviceCantSendReports = true; + NSLog(@"This Mac appears to be incapable of sending output reports to %@", self); + } } - (void)setPlayerLEDs:(uint8_t)mask From 08efb46d41bc06c193cba85edfe00522bb81626d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 20:32:00 +0300 Subject: [PATCH 023/107] =?UTF-8?q?Made=20the=20command=20line=20debugger?= =?UTF-8?q?=20output=20=E2=80=9C>=E2=80=9D=20before=20inputs,=20added=20sp?= =?UTF-8?q?ecial=20magic=20sequence=20to=20break=20the=20debugger=20from?= =?UTF-8?q?=20stdin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/gb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index ce9b9af..1bc2235 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -62,6 +62,9 @@ static char *default_input_callback(GB_gameboy_t *gb) { char *expression = NULL; size_t size = 0; + if (gb->debug_stopped) { + printf(">"); + } if (getline(&expression, &size, stdin) == -1) { /* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */ @@ -77,6 +80,12 @@ static char *default_input_callback(GB_gameboy_t *gb) if (expression[length - 1] == '\n') { expression[length - 1] = 0; } + + if (expression[0] == '\x03') { + gb->debug_stopped = true; + free(expression); + return strdup(""); + } return expression; } From 9521729e4e7ae90133d8cdc23b5e82fe837f865d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 21:54:54 +0300 Subject: [PATCH 024/107] Fixed Windows build --- Makefile | 4 ++-- Windows/unistd.h | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Windows/unistd.h diff --git a/Makefile b/Makefile index 78978e4..8f96bb6 100644 --- a/Makefile +++ b/Makefile @@ -387,10 +387,10 @@ $(OBJ)/%.2bpp: %.png rgbgfx -h -u -o $@ $< $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS) - $(PB12_COMPRESS) < $< > $@ + $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(CC) -Wall -Werror $< -o $@ + $(CC) $(LDFLAGS) $(CFLAGS) -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm diff --git a/Windows/unistd.h b/Windows/unistd.h new file mode 100644 index 0000000..b7aabf2 --- /dev/null +++ b/Windows/unistd.h @@ -0,0 +1,7 @@ +#include +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define read(...) _read(__VA_ARGS__) +#define write(...) _write(__VA_ARGS__) From 9e8b4345c03aac473dfc1471b3e22fc540ed961d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 May 2020 21:55:04 +0300 Subject: [PATCH 025/107] Update version to 0.13 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8f96bb6..1c6b01a 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.12.3 +VERSION := 0.13 export VERSION CONF ?= debug SDL_AUDIO_DRIVER ?= sdl From 6a3cd371d01e5d253b0ab999da53d87ac84ab7cc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Jun 2020 20:54:06 +0300 Subject: [PATCH 026/107] Fix potential memory corruption when execution malformed ROMs --- Core/mbc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/mbc.c b/Core/mbc.c index 72073f6..ba5055f 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -135,7 +135,10 @@ void GB_configure_cart(GB_gameboy_t *gb) static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; } - gb->mbc_ram = malloc(gb->mbc_ram_size); + + if (gb->mbc_ram_size) { + gb->mbc_ram = malloc(gb->mbc_ram_size); + } /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */ memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); From b7a9039e50426d80f136403c95e85f23aad11ea3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Jun 2020 21:06:47 +0300 Subject: [PATCH 027/107] Sanitize SDL preferences for cross-version stability --- SDL/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SDL/main.c b/SDL/main.c index f75e3aa..3df369f 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -671,7 +671,19 @@ int main(int argc, char **argv) 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.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; } From ef203cf0e5b6dd1e9ee5da47b0827b0ef132fa02 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 3 Jun 2020 21:18:09 +0300 Subject: [PATCH 028/107] Update version to 0.13.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1c6b01a..4b9c291 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.13 +VERSION := 0.13.1 export VERSION CONF ?= debug SDL_AUDIO_DRIVER ?= sdl From c07588e3bd7db8f2bfe9e483046ba6856d23fd1e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Jun 2020 02:10:05 +0300 Subject: [PATCH 029/107] Console auto complete --- Cocoa/Document.m | 5 + Cocoa/GBTerminalTextFieldCell.h | 3 +- Cocoa/GBTerminalTextFieldCell.m | 40 ++++++- Core/debugger.c | 194 ++++++++++++++++++++++++++++++-- Core/debugger.h | 2 +- 5 files changed, 231 insertions(+), 13 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 035c0e2..095cfe3 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -8,6 +8,7 @@ #include "GBMemoryByteArray.h" #include "GBWarningPopover.h" #include "GBCheatWindowController.h" +#include "GBTerminalTextFieldCell.h" /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: Split into category files! This is so messy!!! */ @@ -546,6 +547,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) self.debuggerSideViewInput.textColor = [NSColor whiteColor]; self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style; [self.debuggerSideViewInput setString:@"registers\nbacktrace\n"]; + ((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSideView) name:NSTextDidChangeNotification @@ -1008,6 +1010,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [debugger_input_queue removeObjectAtIndex:0]; } [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; + if ((id)input == [NSNull null]) { + return NULL; + } return input? strdup([input UTF8String]): NULL; } diff --git a/Cocoa/GBTerminalTextFieldCell.h b/Cocoa/GBTerminalTextFieldCell.h index eae02e5..484e0c3 100644 --- a/Cocoa/GBTerminalTextFieldCell.h +++ b/Cocoa/GBTerminalTextFieldCell.h @@ -1,5 +1,6 @@ #import +#include @interface GBTerminalTextFieldCell : NSTextFieldCell - +@property GB_gameboy_t *gb; @end diff --git a/Cocoa/GBTerminalTextFieldCell.m b/Cocoa/GBTerminalTextFieldCell.m index e95e785..c1ed203 100644 --- a/Cocoa/GBTerminalTextFieldCell.m +++ b/Cocoa/GBTerminalTextFieldCell.m @@ -2,6 +2,7 @@ #import "GBTerminalTextFieldCell.h" @interface GBTerminalTextView : NSTextView +@property GB_gameboy_t *gb; @end @implementation GBTerminalTextFieldCell @@ -12,10 +13,12 @@ - (NSTextView *)fieldEditorForView:(NSView *)controlView { if (field_editor) { + field_editor.gb = self.gb; return field_editor; } field_editor = [[GBTerminalTextView alloc] init]; [field_editor setFieldEditor:YES]; + field_editor.gb = self.gb; return field_editor; } @@ -26,6 +29,8 @@ NSMutableOrderedSet *lines; NSUInteger current_line; bool reverse_search_mode; + NSRange auto_complete_range; + uintptr_t auto_complete_context; } - (instancetype)init @@ -170,6 +175,7 @@ -(void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag { reverse_search_mode = false; + auto_complete_context = 0; [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag]; } @@ -188,6 +194,38 @@ [attributes setObject:color forKey:NSForegroundColorAttributeName]; [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; } - } + +/* Todo: lazy design, make it use a delegate instead of having a gb reference*/ + +- (void)insertTab:(id)sender +{ + if (auto_complete_context == 0) { + NSRange selection = self.selectedRange; + if (selection.length) { + [self delete:nil]; + } + auto_complete_range = NSMakeRange(selection.location, 0); + } + char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String); + uintptr_t context = auto_complete_context; + char *completion = GB_debugger_complete_substring(self.gb, substring, &context); + free(substring); + if (completion) { + NSString *ns_completion = @(completion); + free(completion); + if (!ns_completion) { + goto error; + } + self.selectedRange = auto_complete_range; + auto_complete_range.length = ns_completion.length; + [self replaceCharactersInRange:self.selectedRange withString:ns_completion]; + auto_complete_context = context; + return; + } +error: + auto_complete_context = context; + NSBeep(); +} + @end diff --git a/Core/debugger.c b/Core/debugger.c index 34144df..038f76f 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -689,6 +689,7 @@ exit: struct debugger_command_s; typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); +typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context); typedef struct debugger_command_s { const char *command; @@ -697,6 +698,8 @@ typedef struct debugger_command_s { const char *help_string; // Null if should not appear in help const char *arguments_format; // For usage message const char *modifiers_format; // For usage message + debugger_completer_imp_t *argument_completer; + debugger_completer_imp_t *modifiers_completer; } debugger_command_t; static const char *lstrip(const char *str) @@ -832,6 +835,19 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } +static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"on", "off"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + /* Enable or disable software breakpoints */ static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { @@ -873,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } +static inline bool is_legal_symbol_char(char c) +{ + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'Z') return true; + if (c >= 'a' && c <= 'z') return true; + if (c == '_') return true; + if (c == '.') return true; + return false; +} + +static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context) +{ + const char *symbol_prefix = string; + while (*string) { + if (!is_legal_symbol_char(*string)) { + symbol_prefix = string + 1; + } + string++; + } + + if (*symbol_prefix == '$') { + return NULL; + } + + struct { + uint16_t bank; + uint32_t symbol; + } *context = (void *)_context; + + + size_t length = strlen(symbol_prefix); + while (context->bank < 0x200) { + if (gb->bank_symbols[context->bank] == NULL || + context->symbol >= gb->bank_symbols[context->bank]->n_symbols) { + context->bank++; + context->symbol = 0; + continue; + } + const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name; + if (memcmp(symbol_prefix, candidate, length) == 0) { + return strdup(candidate + length); + } + } + return NULL; +} + +static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"j"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { bool is_jump_to = true; @@ -1040,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) return (uint16_t) min; } +static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"r", "rw", "w"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { @@ -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); } +static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"a", "b", "d", "o", "x"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) == 0) { @@ -1740,6 +1841,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg return true; } +static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"c", "f", "l"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) { @@ -1787,7 +1901,7 @@ static const debugger_command_t commands[] = { {"finish", 1, finish, "Run until the current function returns"}, {"backtrace", 2, backtrace, "Displays the current call stack"}, {"bt", 2, }, /* Alias */ - {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, + {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"}, {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE "used"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, @@ -1796,30 +1910,33 @@ static const debugger_command_t commands[] = { {"apu", 3, apu, "Displays information about the current state of the audio chip"}, {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE - "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"}, + "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, - {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"}, + {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE "Can also modify the condition of existing breakpoints." HELP_NEWLINE "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE "jumping to the target.", - "[ if ]", "j"}, - {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, + "[ if ]", "j", + .argument_completer = symbol_completer, .modifiers_completer = j_completer}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]", .argument_completer = symbol_completer}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE "Default watchpoint type is write-only.", - "[ if ]", "(r|w|rw)"}, - {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"}, + "[ if ]", "(r|w|rw)", + .argument_completer = symbol_completer, .modifiers_completer = rw_completer + }, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]", .argument_completer = symbol_completer}, {"list", 1, list, "List all set breakpoints and watchpoints"}, {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE "decimal (d), hexadecimal (x), octal (o) or binary (b).", - "", "format"}, + "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, {"eval", 2, }, /* Alias */ - {"examine", 2, examine, "Examine values at address", "", "count"}, + {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, {"x", 1, }, /* Alias */ - {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count"}, + {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, {"help", 1, help, "List available commands or show help for the specified 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 { JUMP_TO_NONE, JUMP_TO_BREAK, diff --git a/Core/debugger.h b/Core/debugger.h index b6a12d9..0678b30 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G void #endif GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */ - +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); From 4a51f5c95698ad3df8bae22acd01e4e60a74de80 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 9 Jun 2020 20:09:50 +0300 Subject: [PATCH 030/107] Cherry-picking libretro memory map bugfix (Closes #227, #205). Fixing libretro build with modern macOS SDKs. --- Makefile | 2 +- libretro/libretro.c | 81 ++++++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 4b9c291..587f2f8 100644 --- a/Makefile +++ b/Makefile @@ -390,7 +390,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(CC) $(LDFLAGS) $(CFLAGS) -Wall -Werror $< -o $@ + cc -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm diff --git a/libretro/libretro.c b/libretro/libretro.c index a7d6432..24514d4 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -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]; size_t size; uint16_t bank; + unsigned i; /* 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.num_descriptors = sizeof(descs) / sizeof(descs[0]); 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 */ 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(); + + retro_set_memory_maps(); + return true; } From edf77624087f09decd3656adf71bc0400a17af06 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 10 Jun 2020 01:10:11 +0300 Subject: [PATCH 031/107] Improved Dark Mode support, improved Hex Fiend's general system-native appearance --- Cocoa/Document.m | 1 - Cocoa/Document.xib | 22 +++++------ Cocoa/GBColorCell.m | 8 +++- Cocoa/GBPreferencesWindow.m | 20 ++++++++++ Cocoa/Joypad~dark.png | Bin 0 -> 6244 bytes Cocoa/Joypad~dark@2x.png | Bin 0 -> 7175 bytes Cocoa/NSImageNamedDarkSupport.m | 42 ++++++++++++++++++++ Cocoa/Preferences.xib | 10 ++--- Cocoa/Speaker~dark.png | Bin 0 -> 4562 bytes Cocoa/Speaker~dark@2x.png | Bin 0 -> 5992 bytes HexFiend/HFFunctions.h | 2 +- HexFiend/HFLineCountingRepresenter.m | 10 ++--- HexFiend/HFLineCountingView.m | 56 ++++++++++----------------- HexFiend/HFRepresenterTextView.m | 18 ++++++--- HexFiend/HFStatusBarRepresenter.m | 44 +++++---------------- HexFiend/HFTextRepresenter.m | 12 ++++-- 16 files changed, 141 insertions(+), 104 deletions(-) create mode 100644 Cocoa/Joypad~dark.png create mode 100644 Cocoa/Joypad~dark@2x.png create mode 100644 Cocoa/NSImageNamedDarkSupport.m create mode 100644 Cocoa/Speaker~dark.png create mode 100644 Cocoa/Speaker~dark@2x.png diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 095cfe3..fdb7d97 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -647,7 +647,6 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { hex_controller = [[HFController alloc] init]; [hex_controller setBytesPerColumn:1]; - [hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]]; [hex_controller setEditMode:HFOverwriteMode]; [hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]]; diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 81ce018..e2b0ca6 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -116,7 +116,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -187,7 +187,7 @@ - + @@ -339,7 +339,7 @@ - + @@ -505,9 +505,9 @@ - + - + @@ -640,7 +640,7 @@ - + @@ -649,7 +649,7 @@ - + @@ -800,7 +800,7 @@ @@ -970,7 +977,7 @@ - + @@ -1069,6 +1076,7 @@ + From 4f42f4f718ec17c38c729b1fa9d9906effa88e36 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 11 Jun 2020 00:38:53 +0300 Subject: [PATCH 035/107] Minor layout fixes --- Cocoa/Document.xib | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index c13c9dd..1197c0f 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -243,9 +243,9 @@ - + - + @@ -903,7 +903,7 @@