diff --git a/Cocoa/Document.m b/Cocoa/Document.m
index fa589a68..a9b31017 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 85ad6b37..b25e4767 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 bd3a4a83..54d190fb 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 248cfce9..58b5ddbd 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 985ed4d3..3a0864d4 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 2c0f11a7..40a20a25 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 2522982b..a0be0e38 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 d4fa07f9..07e04734 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);