diff --git a/Cocoa/Document.m b/Cocoa/Document.m index fa589a6..a9b3101 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -295,6 +295,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); + GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); GB_apu_set_sample_callback(&gb, audioCallback); GB_set_rumble_callback(&gb, rumbleCallback); GB_set_infrared_callback(&gb, infraredStateChanged); @@ -726,6 +727,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) name:@"GBRewindLengthChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRTCMode) + name:@"GBRTCModeChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dmgModelChanged) name:@"GBDMGModelChanged" @@ -1878,6 +1885,13 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) }]; } +- (void) updateRTCMode +{ + if (GB_is_inited(&gb)) { + GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); + } +} + - (void)dmgModelChanged { modelsChanging = true; diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 85ad6b3..b25e476 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -12,6 +12,7 @@ @property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *rtcPopupButton; @property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton; @property (nonatomic, strong) IBOutlet NSButton *skipButton; @property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index bd3a4a8..54d190f 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -19,6 +19,7 @@ NSPopUpButton *_colorPalettePopupButton; NSPopUpButton *_displayBorderPopupButton; NSPopUpButton *_rewindPopupButton; + NSPopUpButton *_rtcPopupButton; NSButton *_aspectRatioCheckbox; NSButton *_analogControlsCheckbox; NSEventModifierFlags previousModifiers; @@ -181,6 +182,18 @@ return _rewindPopupButton; } +- (NSPopUpButton *)rtcPopupButton +{ + return _rtcPopupButton; +} + +- (void)setRtcPopupButton:(NSPopUpButton *)rtcPopupButton +{ + _rtcPopupButton = rtcPopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]; + [_rtcPopupButton selectItemAtIndex:mode]; +} + - (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton { _highpassFilterPopupButton = highpassFilterPopupButton; @@ -360,6 +373,14 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil]; } +- (IBAction)rtcModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBRTCMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil]; + +} + - (IBAction) configureJoypad:(id)sender { [self.configureJoypadButton setEnabled:NO]; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 248cfce..58b5ddb 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -77,6 +77,7 @@ + @@ -85,11 +86,11 @@ - + - + @@ -98,7 +99,7 @@ - + @@ -135,7 +136,7 @@ - + @@ -144,7 +145,7 @@ - + @@ -166,7 +167,7 @@ - + @@ -175,7 +176,7 @@ - + @@ -195,7 +196,7 @@ - + @@ -204,7 +205,7 @@ - + @@ -225,7 +226,7 @@ - + @@ -234,7 +235,7 @@ - + @@ -253,6 +254,23 @@ + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -321,8 +298,17 @@ + + + + + + + + + - + @@ -330,8 +316,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -354,104 +448,39 @@ - - + + - - - - - - - - - - + - + - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + @@ -471,7 +500,7 @@ - + @@ -489,7 +518,7 @@ - + @@ -497,14 +526,14 @@ - + - + @@ -522,14 +551,14 @@ - + - - + + - + @@ -548,7 +577,7 @@ - + @@ -589,7 +618,7 @@ - + @@ -609,7 +638,7 @@ - + @@ -618,7 +647,7 @@ - + @@ -637,7 +666,7 @@ - + @@ -665,19 +694,8 @@ - + diff --git a/Core/gb.c b/Core/gb.c index 985ed4d..3a0864d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -664,9 +664,9 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; #ifdef GB_BIG_ENDIAN - rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); + rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL)); #else - rtc_save.vba64.last_rtc_second = gb->last_rtc_second; + rtc_save.vba64.last_rtc_second = time(NULL); #endif memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); } @@ -728,9 +728,9 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; #ifdef GB_BIG_ENDIAN - rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); + rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL)); #else - rtc_save.vba64.last_rtc_second = gb->last_rtc_second; + rtc_save.vba64.last_rtc_second = time(NULL); #endif if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) { fclose(f); @@ -976,7 +976,6 @@ uint8_t GB_run(GB_gameboy_t *gb) gb->cycles_since_run = 0; GB_cpu_run(gb); if (gb->vblank_just_occured) { - GB_rtc_run(gb); GB_debugger_handle_async_commands(gb); GB_rewind_push(gb); } @@ -1530,15 +1529,20 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) uint32_t GB_get_clock_rate(GB_gameboy_t *gb) { - if (gb->model & GB_MODEL_PAL_BIT) { - return SGB_PAL_FREQUENCY * gb->clock_multiplier; - } - if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { - return SGB_NTSC_FREQUENCY * gb->clock_multiplier; - } - return CPU_FREQUENCY * gb->clock_multiplier; + return GB_get_unmultiplied_clock_rate(gb) * gb->clock_multiplier; } + +uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb) +{ + if (gb->model & GB_MODEL_PAL_BIT) { + return SGB_PAL_FREQUENCY; + } + if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { + return SGB_NTSC_FREQUENCY; + } + return CPU_FREQUENCY; +} void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode) { if (gb->border_mode > GB_BORDER_ALWAYS) return; @@ -1623,3 +1627,12 @@ unsigned GB_time_to_alarm(GB_gameboy_t *gb) if (current_time > alarm_time) return 0; return alarm_time - current_time; } + +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode) +{ + if (gb->rtc_mode != mode) { + gb->rtc_mode = mode; + gb->rtc_cycles = 0; + gb->last_rtc_second = time(NULL); + } +} diff --git a/Core/gb.h b/Core/gb.h index 2c0f11a..40a20a2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -254,6 +254,11 @@ typedef enum { GB_BOOT_ROM_AGB, } GB_boot_rom_t; +typedef enum { + GB_RTC_MODE_SYNC_TO_HOST, + GB_RTC_MODE_ACCURATE, +} GB_rtc_mode_t; + #ifdef GB_INTERNAL #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 @@ -481,6 +486,7 @@ struct GB_gameboy_internal_s { GB_rtc_time_t rtc_real, rtc_latched; uint64_t last_rtc_second; bool rtc_latch; + uint32_t rtc_cycles; ); /* Video Display */ @@ -588,6 +594,7 @@ struct GB_gameboy_internal_s { /* Timing */ uint64_t last_sync; uint64_t cycles_since_last_sync; // In 8MHz units + GB_rtc_mode_t rtc_mode; /* Audio */ GB_apu_output_t apu_output; @@ -798,6 +805,9 @@ 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 +/* RTC emulation mode */ +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode); + /* 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); @@ -805,6 +815,7 @@ void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callb void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); uint32_t GB_get_clock_rate(GB_gameboy_t *gb); +uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb); void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); unsigned GB_get_screen_width(GB_gameboy_t *gb); diff --git a/Core/timing.c b/Core/timing.c index 2522982..a0be0e3 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -240,6 +240,70 @@ static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) } +static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return; + gb->rtc_cycles += cycles; + time_t current_time = 0; + + switch (gb->rtc_mode) { + case GB_RTC_MODE_SYNC_TO_HOST: + // Sync in a 1/32s resolution + if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) / 16) return; + gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) / 16; + current_time = time(NULL); + break; + case GB_RTC_MODE_ACCURATE: + if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) * 2) return; + gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) * 2; + current_time = gb->last_rtc_second + 1; + break; + } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + while (gb->last_rtc_second / 60 < current_time / 60) { + gb->last_rtc_second += 60; + gb->huc3_minutes++; + if (gb->huc3_minutes == 60 * 24) { + gb->huc3_days++; + gb->huc3_minutes = 0; + } + } + return; + } + + if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ + while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { + gb->last_rtc_second += 60 * 60 * 24; + if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + gb->rtc_real.high ^= 1; + } + } + + while (gb->last_rtc_second < current_time) { + gb->last_rtc_second++; + if (++gb->rtc_real.seconds != 60) continue; + gb->rtc_real.seconds = 0; + + if (++gb->rtc_real.minutes != 60) continue; + gb->rtc_real.minutes = 0; + + if (++gb->rtc_real.hours != 24) continue; + gb->rtc_real.hours = 0; + + if (++gb->rtc_real.days != 0) continue; + + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + } + } +} + + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right @@ -280,6 +344,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) GB_apu_run(gb); GB_display_run(gb, cycles); GB_ir_run(gb, cycles); + GB_rtc_run(gb, cycles); } /* @@ -303,52 +368,3 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) } } } - -void GB_rtc_run(GB_gameboy_t *gb) -{ - if (gb->cartridge_type->mbc_type == GB_HUC3) { - time_t current_time = time(NULL); - while (gb->last_rtc_second / 60 < current_time / 60) { - gb->last_rtc_second += 60; - gb->huc3_minutes++; - if (gb->huc3_minutes == 60 * 24) { - gb->huc3_days++; - gb->huc3_minutes = 0; - } - } - return; - } - - if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ - time_t current_time = time(NULL); - - while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { - gb->last_rtc_second += 60 * 60 * 24; - if (++gb->rtc_real.days == 0) { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - gb->rtc_real.high ^= 1; - } - } - - while (gb->last_rtc_second < current_time) { - gb->last_rtc_second++; - if (++gb->rtc_real.seconds == 60) { - gb->rtc_real.seconds = 0; - if (++gb->rtc_real.minutes == 60) { - gb->rtc_real.minutes = 0; - if (++gb->rtc_real.hours == 24) { - gb->rtc_real.hours = 0; - if (++gb->rtc_real.days == 0) { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - gb->rtc_real.high ^= 1; - } - } - } - } - } - } -} diff --git a/Core/timing.h b/Core/timing.h index d4fa07f..07e0473 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -4,7 +4,6 @@ #ifdef GB_INTERNAL void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void GB_rtc_run(GB_gameboy_t *gb); void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ void GB_timing_sync(GB_gameboy_t *gb);