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