diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs index 3681c532d3..70a2aef2fe 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs @@ -116,6 +116,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DefaultValue(false)] public bool RealTimeRTC { get; set; } + [DisplayName("RTC Divisor Offset")] + [Description("CPU clock frequency relative to real time clock. Base value is 2^22 Hz. Used in cycle-based RTC to sync on real hardware to account for RTC imperfections.")] + [DefaultValue(0)] + public int RTCDivisorOffset { get; set; } + [DisplayName("Equal Length Frames")] [Description("When false, emulation frames sync to vblank. Only useful for high level TASing.")] [DefaultValue(false)] diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 3b837f5b78..0de16d0723 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -130,6 +130,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { LibGambatte.gambatte_settimemode(GambatteState, false); } + LibGambatte.gambatte_setrtcdivisoroffset(GambatteState, _syncSettings.RTCDivisorOffset); _cdCallback = new LibGambatte.CDCallback(CDCallbackProc); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs index 038ff62021..d156afdb63 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs @@ -280,6 +280,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void gambatte_settimemode(IntPtr core, bool useCycles); + /// + /// Adjusts the CPU clock frequency relative to real time. Base value is 2^22 Hz. + /// This is used to account for drift in the RTC when syncing cycle-based RTC to real hardware. + /// RTCs in carts are not perfectly accurate, and the value will differ from cart to cart. + /// + /// opaque state pointer + /// CPU frequency adjustment + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void gambatte_setrtcdivisoroffset(IntPtr core, int rtcDivisorOffset); + /// /// Returns true if the currently loaded ROM image is treated as having CGB support. /// diff --git a/libgambatte/include/gambatte.h b/libgambatte/include/gambatte.h index 1a841deccd..368b2a4727 100644 --- a/libgambatte/include/gambatte.h +++ b/libgambatte/include/gambatte.h @@ -130,6 +130,9 @@ public: /** Use cycle-based RTC instead of real-time. */ void setTimeMode(bool useCycles); + /** adjust the assumed clock speed of the CPU compared to the RTC */ + void setRtcDivisorOffset(long const rtcDivisorOffset); + /** Returns true if the currently loaded ROM image is treated as having CGB support. */ bool isCgb() const; diff --git a/libgambatte/src/cinterface.cpp b/libgambatte/src/cinterface.cpp index 9d6e862e36..f9b4ced4fb 100644 --- a/libgambatte/src/cinterface.cpp +++ b/libgambatte/src/cinterface.cpp @@ -72,6 +72,10 @@ GBEXPORT void gambatte_settimemode(GB *g, bool useCycles) { g->setTimeMode(useCycles); } +GBEXPORT void gambatte_setrtcdivisoroffset(GB *g, int rtcDivisorOffset) { + g->setRtcDivisorOffset(rtcDivisorOffset); +} + GBEXPORT void gambatte_reset(GB *g) { g->reset(); } diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index 6059b0fad4..96c1a25b4f 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -91,6 +91,7 @@ public: mem_.setCgbPalette(lut); } void setTimeMode(bool useCycles) { mem_.setTimeMode(useCycles, cycleCounter_); } + void setRtcDivisorOffset(long const rtcDivisorOffset) { mem_.setRtcDivisorOffset(rtcDivisorOffset); } void setBios(char const *buffer, std::size_t size) { mem_.setBios(buffer, size); } bool gbIsCgb() { return mem_.gbIsCgb(); } diff --git a/libgambatte/src/gambatte.cpp b/libgambatte/src/gambatte.cpp index d222874ac6..0bc0639a76 100644 --- a/libgambatte/src/gambatte.cpp +++ b/libgambatte/src/gambatte.cpp @@ -134,6 +134,10 @@ void GB::setTimeMode(bool useCycles) { p_->cpu.setTimeMode(useCycles); } +void GB::setRtcDivisorOffset(long const rtcDivisorOffset) { + p_->cpu.setRtcDivisorOffset(rtcDivisorOffset); +} + LoadRes GB::load(char const *romfiledata, unsigned romfilelength, unsigned const flags) { LoadRes const loadres = p_->cpu.load(romfiledata, romfilelength, flags & FORCE_DMG, flags & MULTICART_COMPAT); diff --git a/libgambatte/src/mem/cartridge.h b/libgambatte/src/mem/cartridge.h index db46afb262..e07baa4aa8 100644 --- a/libgambatte/src/mem/cartridge.h +++ b/libgambatte/src/mem/cartridge.h @@ -71,6 +71,7 @@ public: void resetCc(unsigned long const oldCc, unsigned long const newCc) { time_.resetCc(oldCc, newCc); } void speedChange(unsigned long const cc) { time_.speedChange(cc); } void setTimeMode(bool useCycles, unsigned long const cc) { time_.setTimeMode(useCycles, cc); } + void setRtcDivisorOffset(long const rtcDivisorOffset) { time_.setRtcDivisorOffset(rtcDivisorOffset); } void rtcWrite(unsigned data, unsigned long const cc) { rtc_.write(data, cc); } unsigned char rtcRead() const { return *rtc_.activeData(); } void loadSavedata(char const *data, unsigned long cycleCounter); diff --git a/libgambatte/src/mem/time.cpp b/libgambatte/src/mem/time.cpp index 71e92d1356..87ffb7e10c 100644 --- a/libgambatte/src/mem/time.cpp +++ b/libgambatte/src/mem/time.cpp @@ -34,6 +34,7 @@ static timeval operator-(timeval l, timeval r) { Time::Time() : useCycles_(true) +, rtcDivisor_(0x400000) { } @@ -108,9 +109,9 @@ void Time::setTimeMode(bool useCycles, unsigned long const cc) { void Time::update(unsigned long const cc) { if (useCycles_) { - std::uint32_t diff = (cc - lastCycles_) / (0x400000 << ds_); + std::uint32_t diff = (cc - lastCycles_) / (rtcDivisor_ << ds_); seconds_ += diff; - lastCycles_ += diff * (0x400000 << ds_); + lastCycles_ += diff * (rtcDivisor_ << ds_); } else { std::uint32_t diff = (now() - lastTime_).tv_sec; seconds_ += diff; @@ -121,13 +122,13 @@ void Time::update(unsigned long const cc) { void Time::cyclesFromTime(unsigned long const cc) { update(cc); timeval diff = now() - lastTime_; - lastCycles_ = cc - diff.tv_usec * ((0x400000 << ds_) / 1000000.0f); + lastCycles_ = cc - diff.tv_usec * ((rtcDivisor_ << ds_) / 1000000.0f); } void Time::timeFromCycles(unsigned long const cc) { update(cc); unsigned long diff = cc - lastCycles_; - timeval usec = { 0, (long)(diff / ((0x400000 << ds_) / 1000000.0f)) }; + timeval usec = { 0, (long)(diff / ((rtcDivisor_ << ds_) / 1000000.0f)) }; lastTime_ = now() - usec; } diff --git a/libgambatte/src/mem/time.h b/libgambatte/src/mem/time.h index aa8e49b5e3..3b720bea78 100644 --- a/libgambatte/src/mem/time.h +++ b/libgambatte/src/mem/time.h @@ -57,12 +57,14 @@ public: timeval baseTime(unsigned long cycleCounter); void setBaseTime(timeval baseTime, unsigned long cycleCounter); void setTimeMode(bool useCycles, unsigned long cycleCounter); + void setRtcDivisorOffset(long const rtcDivisorOffset) { rtcDivisor_ = 0x400000L + rtcDivisorOffset; } private: std::uint32_t seconds_; timeval lastTime_; unsigned long lastCycles_; bool useCycles_; + unsigned long rtcDivisor_; bool ds_; void update(unsigned long cycleCounter); diff --git a/libgambatte/src/memory.h b/libgambatte/src/memory.h index f00e28ac24..7d7dcc1d73 100644 --- a/libgambatte/src/memory.h +++ b/libgambatte/src/memory.h @@ -266,6 +266,7 @@ public: void setTimeMode(bool useCycles, unsigned long const cc) { cart_.setTimeMode(useCycles, cc); } + void setRtcDivisorOffset(long const rtcDivisorOffset) { cart_.setRtcDivisorOffset(rtcDivisorOffset); } int linkStatus(int which); diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index de86acd89f..e482dab5d7 100644 Binary files a/output/dll/libgambatte.dll and b/output/dll/libgambatte.dll differ