From 719a8b3d7468b76444c6549c767b2ac3a1a72347 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 14:47:42 +0200 Subject: [PATCH 1/5] GBA: Separate S3511A RTC emulation code from GBA interface emulation code. --- Core/Core.vcxproj | 1 + Core/GBA/Cart/GbaRtc.cpp | 341 +-------------------------- Core/GBA/Cart/GbaRtc.h | 62 +---- Core/Shared/Utilities/S3511ARtc.cpp | 353 ++++++++++++++++++++++++++++ Core/Shared/Utilities/S3511ARtc.h | 77 ++++++ 5 files changed, 441 insertions(+), 393 deletions(-) create mode 100644 Core/Shared/Utilities/S3511ARtc.cpp create mode 100644 Core/Shared/Utilities/S3511ARtc.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 29fd33122..7c7147600 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -931,6 +931,7 @@ + diff --git a/Core/GBA/Cart/GbaRtc.cpp b/Core/GBA/Cart/GbaRtc.cpp index 1b2d19ca0..839abed59 100644 --- a/Core/GBA/Cart/GbaRtc.cpp +++ b/Core/GBA/Cart/GbaRtc.cpp @@ -1,292 +1,11 @@ +#include "Shared/Utilities/S3511ARtc.h" #include "pch.h" #include "GBA/Cart/GbaRtc.h" #include "Utilities/Serializer.h" -#include "Utilities/TimeUtilities.h" #include "Shared/Emulator.h" -#include "Shared/BatteryManager.h" -GbaRtc::GbaRtc(Emulator* emu) +GbaRtc::GbaRtc(Emulator* emu) : S3511ARtc(emu) { - _emu = emu; - - _state.Month = 1; - _state.Day = 1; - _state.Status = 0x02; - _state.IntHour = 0x80; - - _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); -} - -uint8_t GbaRtc::SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue) -{ - if(value > maxValue || (value & 0x0F) >= 0x0A || (value & 0xF0) >= 0xA0) { - //Invalid values - return fixedValue; - } - - return value; -} - -void GbaRtc::FromDateTime(uint64_t data, bool includeYmd) -{ - UpdateTime(); - - //TODOGBA process invalid days (e.g feb 30) - if(includeYmd) { - _state.Year = SanitizeData(data & 0xFF, 0x99, 0); - _state.Month = SanitizeData((data >> 8) & 0x1F, 0x12, 1); - _state.Day = SanitizeData((data >> 16) & 0x3F, 0x31, 1); - _state.DoW = SanitizeData((data >> 24) & 0x07, 0x06, 0); - } - _state.Minute = SanitizeData((data >> 40) & 0x7F, 0x59, 0); - _state.Second = SanitizeData((data >> 48) & 0x7F, 0x59, 0); - - bool is24hMode = _state.Status & 0x40; - uint8_t hourData = (data >> 32) & 0xBF; - if(is24hMode) { - _state.Hour = SanitizeData(hourData & 0x3F, 0x23, 0); - if(_state.Hour >= 0x12) { - _state.Hour |= 0x80; //Set PM flag - } - } else { - _state.Hour = SanitizeData(hourData & 0x3F, 0x11, 0) | (hourData & 0x80); - } -} - -uint64_t GbaRtc::ToDateTime() -{ - UpdateTime(); - return _state.Year | (_state.Month << 8) | (_state.Day << 16) | (_state.DoW << 24) | ((uint64_t)_state.Hour << 32) | ((uint64_t)_state.Minute << 40) | ((uint64_t)_state.Second << 48); -} - -uint8_t GbaRtc::GetCommandLength(Command cmd) -{ - switch(cmd) { - default: - case Command::Reset: return 0; - case Command::Status: return 8; - case Command::DateTime: return 8 * 7; - case Command::Time: return 8 * 3; - case Command::Alarm1: return 8 * 2; - case Command::Alarm2: return 8; - case Command::TestStart: return 0; - case Command::TestEnd: return 0; - } -} - -void GbaRtc::ProcessDataIn(uint8_t bit) -{ - Command cmd = (Command)((_command & 0x0F) >> 1); - - uint8_t length = GetCommandLength(cmd); - _dataIn |= ((uint64_t)bit << (length - _dataInSize)); - _dataInSize--; - - if(_dataInSize == 0) { - switch(cmd) { - case Command::Status: - _state.Status &= 0x80; - _state.Status |= (_dataIn & 0x6A); - break; - - case Command::DateTime: FromDateTime(_dataIn, true); break; - case Command::Time: FromDateTime(_dataIn << 32, false); break; - - default: - //TODOGBA not implemented - break; - } - - for(int i = 0; i < 8; i++) { - //Re-write over the last byte if written beyond the expected length - _dataInSize++; - _dataIn &= ~((uint64_t)1 << (length - _dataInSize)); - } - } -} - -void GbaRtc::ProcessDataOut() -{ - _bitOut = _dataOut & 0x01; - _dataOut >>= 1; - _dataOutSize--; - if(_dataOutSize == 0) { - //Repeat command output if read beyond expected length - ProcessCommand(); - } -} - -void GbaRtc::ProcessCommand() -{ - Command cmd = (Command)((_command & 0x0F) >> 1); - uint8_t length = GetCommandLength(cmd); - bool read = _command & 0x01; - if(read) { - _dataOutSize = length; - - if(_emu->IsDebugging()) { - _emu->DebugLog("[RTC] Read command: " + string(magic_enum::enum_name(cmd))); - } - - switch(cmd) { - case Command::Reset: Reset(); break; - case Command::Status: _dataOut = _state.Status; break; - case Command::DateTime: _dataOut = ToDateTime(); break; - case Command::Time: _dataOut = ToDateTime() >> 32; break; - default: - //TODOGBA not implemented - _dataOut = 0; - break; - } - } else { - if(_emu->IsDebugging()) { - _emu->DebugLog("[RTC] Write command: " + string(magic_enum::enum_name(cmd))); - } - - if(cmd == Command::Reset) { - Reset(); - } - - _dataInSize = length; - _dataIn = 0; - } -} - -void GbaRtc::Reset() -{ - _state = {}; - _state.Month = 1; - _state.Day = 1; -} - -void GbaRtc::UpdateTime() -{ - uint64_t currentTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - uint32_t elapsedSeconds = (uint32_t)(currentTime - _lastUpdateTime); - if(elapsedSeconds <= 0) { - return; - } - - std::tm tm = {}; - tm.tm_sec = (_state.Second & 0x0F) + ((_state.Second >> 4) * 10); - tm.tm_min = (_state.Minute & 0x0F) + ((_state.Minute >> 4) * 10); - int hour = _state.Hour & 0x3F; - tm.tm_hour = (hour & 0x0F) + ((hour >> 4) * 10); - if(tm.tm_hour < 12 && (_state.Hour & 0x80)) { - tm.tm_hour += 12; - } - tm.tm_mday = (_state.Day & 0x0F) + ((_state.Day >> 4) * 10); - - int month = _state.Month - 1; - tm.tm_mon = (month & 0x0F) + ((month >> 4) * 10); - tm.tm_year = 100 + (_state.Year & 0x0F) + ((_state.Year >> 4) * 10); - - std::time_t tt = TimeUtilities::TmToUtc(&tm); - if(tt == -1) { - //Invalid time - _lastUpdateTime = currentTime; - return; - } - - int8_t dowGap = 0; - if(tm.tm_wday != _state.DoW) { - //The DoW on the RTC can be set to any arbitrary value for a specific date - //Check the gap between the value set by the game & the real dow for that date - dowGap = (int8_t)tm.tm_wday - (int8_t)_state.DoW; - } - - std::chrono::system_clock::time_point timePoint = std::chrono::system_clock::from_time_t(tt); - timePoint += std::chrono::seconds((uint32_t)elapsedSeconds); - - std::time_t newTime = system_clock::to_time_t(timePoint); - std::tm newTm; -#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) - gmtime_s(&newTm, &newTime); -#else - gmtime_r(&newTime, &newTm); -#endif - - _state.Second = (newTm.tm_sec % 10) + ((newTm.tm_sec / 10) << 4); - _state.Minute = (newTm.tm_min % 10) + ((newTm.tm_min / 10) << 4); - - bool is24hMode = _state.Status & 0x40; - if(is24hMode) { - _state.Hour = (newTm.tm_hour % 10) + ((newTm.tm_hour / 10) << 4); - } else { - hour = newTm.tm_hour >= 12 ? newTm.tm_hour - 12 : newTm.tm_hour; - _state.Hour = (hour % 10) + ((hour / 10) << 4); - } - - if(newTm.tm_hour >= 12) { - //Set PM flag (in both 12h and 24h modes) - _state.Hour |= 0x80; - } - - _state.Day = (newTm.tm_mday % 10) + ((newTm.tm_mday / 10) << 4); - - month = newTm.tm_mon + 1; - _state.Month = (month % 10) + ((month / 10) << 4); - - int year = newTm.tm_year - 100; - _state.Year = (year % 10) + ((year / 10) << 4); - - int dow = newTm.tm_wday - dowGap; - _state.DoW = dow < 0 ? (dow + 7) : (dow % 7); - - _lastUpdateTime = currentTime; -} - -void GbaRtc::LoadBattery() -{ - vector rtcData = _emu->GetBatteryManager()->LoadBattery(".rtc"); - - if(rtcData.size() == sizeof(_state) + sizeof(uint64_t)) { - _state.Year = rtcData[0]; - _state.Month = rtcData[1]; - _state.Day = rtcData[2]; - _state.DoW = rtcData[3]; - _state.Hour = rtcData[4]; - _state.Minute = rtcData[5]; - _state.Second = rtcData[6]; - _state.Status = rtcData[7]; - _state.IntHour = rtcData[8]; - _state.IntMinute = rtcData[9]; - - uint64_t time = 0; - for(uint32_t i = 0; i < sizeof(uint64_t); i++) { - time <<= 8; - time |= rtcData[sizeof(_state) + i]; - } - _lastUpdateTime = time; - } else { - _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - } -} - -void GbaRtc::SaveBattery() -{ - vector rtcData; - - rtcData.push_back(_state.Year); - rtcData.push_back(_state.Month); - rtcData.push_back(_state.Day); - rtcData.push_back(_state.DoW); - rtcData.push_back(_state.Hour); - rtcData.push_back(_state.Minute); - rtcData.push_back(_state.Second); - rtcData.push_back(_state.Status); - rtcData.push_back(_state.IntHour); - rtcData.push_back(_state.IntMinute); - - rtcData.resize(sizeof(_state) + sizeof(uint64_t), 0); - - uint64_t time = _lastUpdateTime; - for(uint32_t i = 0; i < sizeof(uint64_t); i++) { - rtcData[sizeof(_state) + i] = (time >> 56) & 0xFF; - time <<= 8; - } - - _emu->GetBatteryManager()->SaveBattery(".rtc", rtcData.data(), (uint32_t)rtcData.size()); } uint8_t GbaRtc::Read() @@ -309,72 +28,28 @@ void GbaRtc::Write(uint8_t value) bool chipSelect = value & 0x04; if(!chipSelect || !_chipSelect) { - _command = 0; - _bitCounter = 0; + ResetBus(); _clk = 0; - _dataOut = 0; - _dataOutSize = 0; - _dataIn = 0; - _dataInSize = 0; _bitOut = 1; _chipSelect = chipSelect; return; } if(clk && !_clk) { - if(_dataOutSize) { - //Data output is updated on falling edge of clock - } else if(_dataInSize) { - ProcessDataIn(data); - } else { - _command <<= 1; - _command |= data; - _bitCounter++; - - if(_bitCounter == 8) { - if((_command & 0xF0) == 0x60) { - ProcessCommand(); - } else { - //invalid command - _command = 0; - } - - _bitCounter = 0; - } - } + WriteBits(data, 1); } else if(!clk && _clk) { - if(_dataOutSize) { - ProcessDataOut(); - } else { - _bitOut = 1; - } + _bitOut = ReadBits(1); } + _clk = clk; } void GbaRtc::Serialize(Serializer& s) { - SV(_bitCounter); - SV(_command); + S3511ARtc::Serialize(s); + SV(_clk); - SV(_dataOut); - SV(_dataOutSize); - SV(_dataIn); - SV(_dataInSize); SV(_bitOut); SV(_chipSelect); - SV(_lastUpdateTime); SV(_prevValue); - - SV(_state.Year); - SV(_state.Month); - SV(_state.Day); - SV(_state.DoW); - SV(_state.Hour); - SV(_state.Minute); - SV(_state.Second); - SV(_state.Status); - SV(_state.IntHour); - SV(_state.IntMinute); - SV(_state.TestMode); } diff --git a/Core/GBA/Cart/GbaRtc.h b/Core/GBA/Cart/GbaRtc.h index 0ec736768..0cdb9be57 100644 --- a/Core/GBA/Cart/GbaRtc.h +++ b/Core/GBA/Cart/GbaRtc.h @@ -1,79 +1,21 @@ #pragma once #include "pch.h" #include "Utilities/ISerializable.h" +#include "Shared/Utilities/S3511ARtc.h" class Emulator; -struct GbaRtcState -{ - uint8_t Year; - uint8_t Month; - uint8_t Day; - uint8_t DoW; - uint8_t Hour; - uint8_t Minute; - uint8_t Second; - - uint8_t Status; - uint8_t IntHour; //unimplemented - uint8_t IntMinute; //unimplemented - - bool TestMode; //unimplemented -}; - -class GbaRtc final : public ISerializable +class GbaRtc final : public S3511ARtc { private: - enum class Command : uint8_t - { - Reset, - Status, - DateTime, - Time, - Alarm1, - Alarm2, - TestStart, - TestEnd - }; - - Emulator* _emu = nullptr; - - GbaRtcState _state = {}; - uint64_t _lastUpdateTime = 0; - - uint8_t _bitCounter = 0; - uint8_t _command = 0; uint8_t _clk = 0; uint8_t _prevValue = 0; - - uint64_t _dataOut = 0; - uint8_t _dataOutSize = 0; - - uint64_t _dataIn = 0; - uint8_t _dataInSize = 0; - uint8_t _bitOut = 0; bool _chipSelect = false; - uint8_t SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue); - void FromDateTime(uint64_t data, bool includeYmd); - uint64_t ToDateTime(); - - uint8_t GetCommandLength(Command cmd); - - void ProcessDataIn(uint8_t value); - void ProcessDataOut(); - void ProcessCommand(); - - void Reset(); - void UpdateTime(); - public: GbaRtc(Emulator* emu); - void LoadBattery(); - void SaveBattery(); - uint8_t Read(); void Write(uint8_t value); diff --git a/Core/Shared/Utilities/S3511ARtc.cpp b/Core/Shared/Utilities/S3511ARtc.cpp new file mode 100644 index 000000000..38fd321c1 --- /dev/null +++ b/Core/Shared/Utilities/S3511ARtc.cpp @@ -0,0 +1,353 @@ +#include "pch.h" +#include "S3511ARtc.h" +#include "Utilities/Serializer.h" +#include "Utilities/TimeUtilities.h" +#include "Shared/Emulator.h" +#include "Shared/BatteryManager.h" + +S3511ARtc::S3511ARtc(Emulator* emu) +{ + _emu = emu; + + _state.Month = 1; + _state.Day = 1; + _state.Status = 0x02; + _state.IntHour = 0x80; + + _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +uint8_t S3511ARtc::SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue) +{ + if(value > maxValue || (value & 0x0F) >= 0x0A || (value & 0xF0) >= 0xA0) { + //Invalid values + return fixedValue; + } + + return value; +} + +void S3511ARtc::FromDateTime(uint64_t data, bool includeYmd) +{ + UpdateTime(); + + //TODO process invalid days (e.g feb 30) + if(includeYmd) { + _state.Year = SanitizeData(data & 0xFF, 0x99, 0); + _state.Month = SanitizeData((data >> 8) & 0x1F, 0x12, 1); + _state.Day = SanitizeData((data >> 16) & 0x3F, 0x31, 1); + _state.DoW = SanitizeData((data >> 24) & 0x07, 0x06, 0); + } + _state.Minute = SanitizeData((data >> 40) & 0x7F, 0x59, 0); + _state.Second = SanitizeData((data >> 48) & 0x7F, 0x59, 0); + + bool is24hMode = _state.Status & 0x40; + uint8_t hourData = (data >> 32) & 0xBF; + if(is24hMode) { + _state.Hour = SanitizeData(hourData & 0x3F, 0x23, 0); + if(_state.Hour >= 0x12) { + _state.Hour |= 0x80; //Set PM flag + } + } else { + _state.Hour = SanitizeData(hourData & 0x3F, 0x11, 0) | (hourData & 0x80); + } +} + +uint64_t S3511ARtc::ToDateTime() +{ + UpdateTime(); + return _state.Year | (_state.Month << 8) | (_state.Day << 16) | (_state.DoW << 24) | ((uint64_t)_state.Hour << 32) | ((uint64_t)_state.Minute << 40) | ((uint64_t)_state.Second << 48); +} + +uint8_t S3511ARtc::GetCommandLength(Command cmd) +{ + switch(cmd) { + default: + case Command::Reset: return 0; + case Command::Status: return 1; + case Command::DateTime: return 7; + case Command::Time: return 3; + case Command::Alarm1: return 2; + case Command::Alarm2: return 1; + case Command::TestStart: return 0; + case Command::TestEnd: return 0; + } +} + +void S3511ARtc::ResetBus() +{ + _command = 0; + _bitCounter = 0; + _dataOut = 0; + _dataOutSize = 0; + _dataIn = 0; + _dataInSize = 0; +} + +void S3511ARtc::WriteBits(uint32_t value, int count) +{ + if(_dataOutSize) { + return; + } + if(_dataInSize == 0) { + _command <<= count; + _command |= value; + _bitCounter += count; + + assert(_bitCounter <= 8); + if(_bitCounter == 8) { + if((_command & 0xF0) == 0x60) { + ProcessCommand(); + } else { + //invalid command + _command = 0; + } + + _bitCounter = 0; + } + + return; + } + assert(_dataInSize >= count); + + Command cmd = (Command)((_command & 0x0F) >> 1); + + uint8_t length = GetCommandLength(cmd) * 8; + _dataIn |= ((uint64_t)value << (length - _dataInSize)); + _dataInSize -= count; + + if(_dataInSize == 0) { + switch(cmd) { + case Command::Status: + _state.Status &= 0x80; + _state.Status |= (_dataIn & 0x6A); + break; + + case Command::DateTime: FromDateTime(_dataIn, true); break; + case Command::Time: FromDateTime(_dataIn << 32, false); break; + + default: + //TODO not implemented + break; + } + + for(int i = 0; i < 8; i++) { + //Re-write over the last byte if written beyond the expected length + _dataInSize++; + _dataIn &= ~((uint64_t)1 << (length - _dataInSize)); + } + } +} + +uint32_t S3511ARtc::ReadBits(int count) +{ + if(_dataOutSize == 0) { + return (1 << count) - 1; + } + assert(_dataOutSize >= count); + + uint32_t output = _dataOut & ((1 << count) - 1); + _dataOut >>= count; + _dataOutSize -= count; + if(_dataOutSize == 0) { + //Repeat command output if read beyond expected length + ProcessCommand(); + } + return output; +} + +void S3511ARtc::ProcessCommand() +{ + Command cmd = (Command)((_command & 0x0F) >> 1); + uint8_t length = GetCommandLength(cmd) * 8; + bool read = _command & 0x01; + if(read) { + _dataOutSize = length; + + if(_emu->IsDebugging()) { + _emu->DebugLog("[RTC] Read command: " + string(magic_enum::enum_name(cmd))); + } + + switch(cmd) { + case Command::Reset: Reset(); break; + case Command::Status: _dataOut = _state.Status; break; + case Command::DateTime: _dataOut = ToDateTime(); break; + case Command::Time: _dataOut = ToDateTime() >> 32; break; + default: + //TODO not implemented + _dataOut = 0; + break; + } + } else { + if(_emu->IsDebugging()) { + _emu->DebugLog("[RTC] Write command: " + string(magic_enum::enum_name(cmd))); + } + + if(cmd == Command::Reset) { + Reset(); + } + + _dataInSize = length; + _dataIn = 0; + } +} + +void S3511ARtc::Reset() +{ + _state = {}; + _state.Month = 1; + _state.Day = 1; +} + +void S3511ARtc::UpdateTime() +{ + uint64_t currentTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint32_t elapsedSeconds = (uint32_t)(currentTime - _lastUpdateTime); + if(elapsedSeconds <= 0) { + return; + } + + std::tm tm = {}; + tm.tm_sec = (_state.Second & 0x0F) + ((_state.Second >> 4) * 10); + tm.tm_min = (_state.Minute & 0x0F) + ((_state.Minute >> 4) * 10); + int hour = _state.Hour & 0x3F; + tm.tm_hour = (hour & 0x0F) + ((hour >> 4) * 10); + if(tm.tm_hour < 12 && (_state.Hour & 0x80)) { + tm.tm_hour += 12; + } + tm.tm_mday = (_state.Day & 0x0F) + ((_state.Day >> 4) * 10); + + int month = _state.Month - 1; + tm.tm_mon = (month & 0x0F) + ((month >> 4) * 10); + tm.tm_year = 100 + (_state.Year & 0x0F) + ((_state.Year >> 4) * 10); + + std::time_t tt = TimeUtilities::TmToUtc(&tm); + if(tt == -1) { + //Invalid time + _lastUpdateTime = currentTime; + return; + } + + int8_t dowGap = 0; + if(tm.tm_wday != _state.DoW) { + //The DoW on the RTC can be set to any arbitrary value for a specific date + //Check the gap between the value set by the game & the real dow for that date + dowGap = (int8_t)tm.tm_wday - (int8_t)_state.DoW; + } + + std::chrono::system_clock::time_point timePoint = std::chrono::system_clock::from_time_t(tt); + timePoint += std::chrono::seconds((uint32_t)elapsedSeconds); + + std::time_t newTime = system_clock::to_time_t(timePoint); + std::tm newTm; +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) + gmtime_s(&newTm, &newTime); +#else + gmtime_r(&newTime, &newTm); +#endif + + _state.Second = (newTm.tm_sec % 10) + ((newTm.tm_sec / 10) << 4); + _state.Minute = (newTm.tm_min % 10) + ((newTm.tm_min / 10) << 4); + + bool is24hMode = _state.Status & 0x40; + if(is24hMode) { + _state.Hour = (newTm.tm_hour % 10) + ((newTm.tm_hour / 10) << 4); + } else { + hour = newTm.tm_hour >= 12 ? newTm.tm_hour - 12 : newTm.tm_hour; + _state.Hour = (hour % 10) + ((hour / 10) << 4); + } + + if(newTm.tm_hour >= 12) { + //Set PM flag (in both 12h and 24h modes) + _state.Hour |= 0x80; + } + + _state.Day = (newTm.tm_mday % 10) + ((newTm.tm_mday / 10) << 4); + + month = newTm.tm_mon + 1; + _state.Month = (month % 10) + ((month / 10) << 4); + + int year = newTm.tm_year - 100; + _state.Year = (year % 10) + ((year / 10) << 4); + + int dow = newTm.tm_wday - dowGap; + _state.DoW = dow < 0 ? (dow + 7) : (dow % 7); + + _lastUpdateTime = currentTime; +} + +void S3511ARtc::LoadBattery() +{ + vector rtcData = _emu->GetBatteryManager()->LoadBattery(".rtc"); + + if(rtcData.size() == sizeof(_state) + sizeof(uint64_t)) { + _state.Year = rtcData[0]; + _state.Month = rtcData[1]; + _state.Day = rtcData[2]; + _state.DoW = rtcData[3]; + _state.Hour = rtcData[4]; + _state.Minute = rtcData[5]; + _state.Second = rtcData[6]; + _state.Status = rtcData[7]; + _state.IntHour = rtcData[8]; + _state.IntMinute = rtcData[9]; + + uint64_t time = 0; + for(uint32_t i = 0; i < sizeof(uint64_t); i++) { + time <<= 8; + time |= rtcData[sizeof(_state) + i]; + } + _lastUpdateTime = time; + } else { + _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } +} + +void S3511ARtc::SaveBattery() +{ + vector rtcData; + + rtcData.push_back(_state.Year); + rtcData.push_back(_state.Month); + rtcData.push_back(_state.Day); + rtcData.push_back(_state.DoW); + rtcData.push_back(_state.Hour); + rtcData.push_back(_state.Minute); + rtcData.push_back(_state.Second); + rtcData.push_back(_state.Status); + rtcData.push_back(_state.IntHour); + rtcData.push_back(_state.IntMinute); + + rtcData.resize(sizeof(_state) + sizeof(uint64_t), 0); + + uint64_t time = _lastUpdateTime; + for(uint32_t i = 0; i < sizeof(uint64_t); i++) { + rtcData[sizeof(_state) + i] = (time >> 56) & 0xFF; + time <<= 8; + } + + _emu->GetBatteryManager()->SaveBattery(".rtc", rtcData.data(), (uint32_t)rtcData.size()); +} + +void S3511ARtc::Serialize(Serializer& s) +{ + SV(_bitCounter); + SV(_command); + SV(_dataOut); + SV(_dataOutSize); + SV(_dataIn); + SV(_dataInSize); + SV(_lastUpdateTime); + + SV(_state.Year); + SV(_state.Month); + SV(_state.Day); + SV(_state.DoW); + SV(_state.Hour); + SV(_state.Minute); + SV(_state.Second); + SV(_state.Status); + SV(_state.IntHour); + SV(_state.IntMinute); + SV(_state.TestMode); +} diff --git a/Core/Shared/Utilities/S3511ARtc.h b/Core/Shared/Utilities/S3511ARtc.h new file mode 100644 index 000000000..5354b8c1c --- /dev/null +++ b/Core/Shared/Utilities/S3511ARtc.h @@ -0,0 +1,77 @@ +#pragma once +#include "pch.h" +#include "Utilities/ISerializable.h" + +class Emulator; + +struct S3511ARtcState +{ + uint8_t Year; + uint8_t Month; + uint8_t Day; + uint8_t DoW; + uint8_t Hour; + uint8_t Minute; + uint8_t Second; + + uint8_t Status; + uint8_t IntHour; //unimplemented + uint8_t IntMinute; //unimplemented + + bool TestMode; //unimplemented +}; + +class S3511ARtc : public ISerializable +{ +private: + enum class Command : uint8_t + { + Reset, + Status, + DateTime, + Time, + Alarm1, + Alarm2, + TestStart, + TestEnd + }; + + S3511ARtcState _state = {}; + uint64_t _lastUpdateTime = 0; + + uint8_t _bitCounter = 0; + uint8_t _command = 0; + + uint64_t _dataOut = 0; + uint8_t _dataOutSize = 0; + + uint64_t _dataIn = 0; + uint8_t _dataInSize = 0; + + uint8_t SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue); + void FromDateTime(uint64_t data, bool includeYmd); + uint64_t ToDateTime(); + + uint8_t GetCommandLength(Command cmd); + + void ProcessCommand(); + + void Reset(); + void UpdateTime(); + +protected: + Emulator* _emu = nullptr; + + //GBA only uses count values of 1, WS only uses 8 + void WriteBits(uint32_t value, int count); + uint32_t ReadBits(int count); + void ResetBus(); + +public: + S3511ARtc(Emulator* emu); + + void LoadBattery(); + void SaveBattery(); + + void Serialize(Serializer& s) override; +}; From e89a123dcfc5f84f6f9343903c6804efae95cf47 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 16:25:17 +0200 Subject: [PATCH 2/5] WS: Initial cartridge RTC implementation. --- Core/Shared/Utilities/S3511ARtc.cpp | 5 ++ Core/Shared/Utilities/S3511ARtc.h | 29 ++++---- Core/WS/Carts/WsCart.cpp | 12 +++- Core/WS/Carts/WsCart.h | 7 +- Core/WS/Carts/WsRtc.cpp | 101 ++++++++++++++++++++++++++++ Core/WS/Carts/WsRtc.h | 32 +++++++++ Core/WS/WsConsole.cpp | 26 ++++++- Core/WS/WsConsole.h | 4 ++ 8 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 Core/WS/Carts/WsRtc.cpp create mode 100644 Core/WS/Carts/WsRtc.h diff --git a/Core/Shared/Utilities/S3511ARtc.cpp b/Core/Shared/Utilities/S3511ARtc.cpp index 38fd321c1..d817d472b 100644 --- a/Core/Shared/Utilities/S3511ARtc.cpp +++ b/Core/Shared/Utilities/S3511ARtc.cpp @@ -74,6 +74,11 @@ uint8_t S3511ARtc::GetCommandLength(Command cmd) } } +uint8_t S3511ARtc::GetCommandLength(uint8_t cmd) +{ + return GetCommandLength((Command) ((cmd >> 1) & 0x07)); +} + void S3511ARtc::ResetBus() { _command = 0; diff --git a/Core/Shared/Utilities/S3511ARtc.h b/Core/Shared/Utilities/S3511ARtc.h index 5354b8c1c..b3412713e 100644 --- a/Core/Shared/Utilities/S3511ARtc.h +++ b/Core/Shared/Utilities/S3511ARtc.h @@ -24,18 +24,6 @@ struct S3511ARtcState class S3511ARtc : public ISerializable { private: - enum class Command : uint8_t - { - Reset, - Status, - DateTime, - Time, - Alarm1, - Alarm2, - TestStart, - TestEnd - }; - S3511ARtcState _state = {}; uint64_t _lastUpdateTime = 0; @@ -52,16 +40,29 @@ class S3511ARtc : public ISerializable void FromDateTime(uint64_t data, bool includeYmd); uint64_t ToDateTime(); - uint8_t GetCommandLength(Command cmd); - void ProcessCommand(); void Reset(); void UpdateTime(); protected: + enum class Command : uint8_t + { + Reset, + Status, + DateTime, + Time, + Alarm1, + Alarm2, + TestStart, + TestEnd + }; + Emulator* _emu = nullptr; + uint8_t GetCommandLength(Command cmd); + uint8_t GetCommandLength(uint8_t cmd); + //GBA only uses count values of 1, WS only uses 8 void WriteBits(uint32_t value, int count); uint32_t ReadBits(int count); diff --git a/Core/WS/Carts/WsCart.cpp b/Core/WS/Carts/WsCart.cpp index 15aef58bc..e09ca57f7 100644 --- a/Core/WS/Carts/WsCart.cpp +++ b/Core/WS/Carts/WsCart.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsRtc.h" #include "WS/WsMemoryManager.h" #include "WS/WsEeprom.h" #include "Utilities/Serializer.h" @@ -22,10 +23,11 @@ WsCart::WsCart() _state.SelectedBanks[3] = 0xFF; } -void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom) +void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* cartRtc) { _memoryManager = memoryManager; _cartEeprom = cartEeprom; + _cartRtc = cartRtc; } void WsCart::RefreshMappings() @@ -40,8 +42,10 @@ uint8_t WsCart::ReadPort(uint16_t port) { if(port < 0xC4) { return _state.SelectedBanks[port - 0xC0]; - } else if(port < 0xC9 && _cartEeprom) { + } else if(port >= 0xC4 && port < 0xC9 && _cartEeprom) { return _cartEeprom->ReadPort(port - 0xC4); + } else if(port >= 0xCA && port < 0xCC && _cartRtc) { + return _cartRtc->ReadPort(port); } return _memoryManager->GetUnmappedPort(); @@ -52,8 +56,10 @@ void WsCart::WritePort(uint16_t port, uint8_t value) if(port < 0xC4) { _state.SelectedBanks[port - 0xC0] = value; _memoryManager->RefreshMappings(); - } else if(port < 0xC9 && _cartEeprom) { + } else if(port >= 0xC4 && port < 0xC9 && _cartEeprom) { _cartEeprom->WritePort(port - 0xC4, value); + } else if(port >= 0xCA && port < 0xCC && _cartRtc) { + _cartRtc->WritePort(port, value); } } diff --git a/Core/WS/Carts/WsCart.h b/Core/WS/Carts/WsCart.h index 87af797a9..1779c395d 100644 --- a/Core/WS/Carts/WsCart.h +++ b/Core/WS/Carts/WsCart.h @@ -4,10 +4,11 @@ #include "Utilities/ISerializable.h" #include "Shared/MemoryType.h" +class WsConsole; class WsMemoryManager; class WsEeprom; +class WsRtc; -//TODOWS RTC //TODOWS Flash class WsCart final : public ISerializable @@ -17,6 +18,7 @@ class WsCart final : public ISerializable WsMemoryManager* _memoryManager = nullptr; WsEeprom* _cartEeprom = nullptr; + WsRtc* _cartRtc = nullptr; void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly); void Unmap(uint32_t start, uint32_t end); @@ -25,11 +27,12 @@ class WsCart final : public ISerializable WsCart(); virtual ~WsCart() {} - void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom); + void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* cartRtc); void RefreshMappings(); WsCartState& GetState() { return _state; } WsEeprom* GetEeprom() { return _cartEeprom; } + WsRtc* GetRtc() { return _cartRtc; } virtual uint8_t ReadPort(uint16_t port); virtual void WritePort(uint16_t port, uint8_t value); diff --git a/Core/WS/Carts/WsRtc.cpp b/Core/WS/Carts/WsRtc.cpp new file mode 100644 index 000000000..0d47f64f1 --- /dev/null +++ b/Core/WS/Carts/WsRtc.cpp @@ -0,0 +1,101 @@ +#include "Shared/Utilities/S3511ARtc.h" +#include "WS/WsConsole.h" +#include "pch.h" +#include "WS/Carts/WsRtc.h" +#include "Utilities/Serializer.h" +#include "Shared/Emulator.h" + +// TODOWS clock-accurate behavior, needs tests +// TODOWS edge case: changing commands mid-transfer, needs tests + +WsRtc::WsRtc(Emulator* emu, WsConsole* console) : S3511ARtc(emu), _console(console) +{ + _ready = true; +} + +bool WsRtc::IsCommandRead() +{ + return (_command & 0x01) != 0; +} + +void WsRtc::StartByteTransfer() +{ + if(_busy) { + _ready = false; + _transferClock = _console->GetCartridgeClock() + 8; + } +} + +void WsRtc::Update() +{ + if(!_ready && _console->GetCartridgeClock() >= _transferClock) { + if(!_commandTransferred) { + WriteBits((_command & 0x0F) | 0x60, 8); + _commandTransferred = true; + _ready = true; + if(_bytesLeft) { + StartByteTransfer(); + } + } else if(_bytesLeft) { + if(IsCommandRead()) { + _data = ReadBits(8); + } else { + WriteBits(_data, 8); + } + _ready = true; + _bytesLeft--; + } + + if(_commandTransferred && !_bytesLeft) { + ResetBus(); + _busy = false; + _commandTransferred = false; + } + } +} + +uint8_t WsRtc::ReadPort(uint16_t port) +{ + Update(); + + if(port == 0xCA) { + return (_command & 0x0F) | (_busy ? 0x10 : 0) | (_ready ? 0x80 : 0); + } else if(port == 0xCB) { + if(_bytesLeft && IsCommandRead()) { + StartByteTransfer(); + } + return _data; + } + + return 0; +} + +void WsRtc::WritePort(uint16_t port, uint8_t value) +{ + Update(); + + if(port == 0xCA) { + _busy = (value & 0x10) == 0x10; + _command = (value & 0x0F); + _bytesLeft = GetCommandLength(_command); + StartByteTransfer(); + } else if(port == 0xCB) { + _data = value; + if(_bytesLeft && !IsCommandRead()) { + StartByteTransfer(); + } + } +} + +void WsRtc::Serialize(Serializer& s) +{ + S3511ARtc::Serialize(s); + + SV(_command); + SV(_data); + SV(_bytesLeft); + SV(_commandTransferred); + SV(_transferClock); + SV(_ready); + SV(_busy); +} diff --git a/Core/WS/Carts/WsRtc.h b/Core/WS/Carts/WsRtc.h new file mode 100644 index 000000000..f0561a6a1 --- /dev/null +++ b/Core/WS/Carts/WsRtc.h @@ -0,0 +1,32 @@ +#pragma once +#include "pch.h" +#include "Utilities/ISerializable.h" +#include "Shared/Utilities/S3511ARtc.h" + +class Emulator; + +class WsRtc final : public S3511ARtc +{ +private: + WsConsole* _console; + + uint8_t _command = 0; + uint8_t _data = 0; + uint8_t _bytesLeft = 0; + bool _commandTransferred = false; + uint32_t _transferClock = 0; + bool _ready = true; + bool _busy = false; + + bool IsCommandRead(); + void StartByteTransfer(); + void Update(); + +public: + WsRtc(Emulator* emu, WsConsole* console); + + uint8_t ReadPort(uint16_t port); + void WritePort(uint16_t port, uint8_t value); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/WS/WsConsole.cpp b/Core/WS/WsConsole.cpp index b6445985b..cdd5079c3 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -4,6 +4,7 @@ #include "WS/WsPpu.h" #include "WS/WsTimer.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsRtc.h" #include "WS/WsControlManager.h" #include "WS/WsMemoryManager.h" #include "WS/WsDmaController.h" @@ -94,6 +95,10 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _cartEeprom.reset(new WsEeprom(_emu, this, (WsEepromSize)_cartEepromSize, _cartEepromData, false)); } + if(mapperType >= 0x01) { + _cartRtc.reset(new WsRtc(_emu, this)); + } + _model = _emu->GetSettings()->GetWsConfig().Model; if(_model == WsModel::Auto) { _model = hasColorSupport ? WsModel::Color : WsModel::Monochrome; @@ -129,7 +134,7 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _apu.reset(new WsApu(_emu, this, _memoryManager.get(), _dmaController.get())); _cart.reset(new WsCart()); - _cart->Init(_memoryManager.get(), _cartEeprom.get()); + _cart->Init(_memoryManager.get(), _cartEeprom.get(), _cartRtc.get()); _memoryManager->Init(_emu, this, _cpu.get(), _ppu.get(), _controlManager.get(), _cart.get(), _timer.get(), _dmaController.get(), _internalEeprom.get(), _apu.get(), _serial.get()); _timer->Init(_memoryManager.get()); _dmaController->Init(_memoryManager.get(), _apu.get()); @@ -311,6 +316,9 @@ void WsConsole::LoadBattery() if(_cartEeprom) { _cartEeprom->LoadBattery(); } + if(_cartRtc) { + _cartRtc->LoadBattery(); + } if(_saveRam) { _emu->GetBatteryManager()->LoadBattery(".sav", _saveRam, _saveRamSize); @@ -323,6 +331,9 @@ void WsConsole::SaveBattery() if(_cartEeprom) { _cartEeprom->SaveBattery(); } + if(_cartRtc) { + _cartRtc->SaveBattery(); + } if(_saveRam) { _emu->GetBatteryManager()->SaveBattery(".sav", _saveRam, _saveRamSize); @@ -349,6 +360,16 @@ vector WsConsole::GetCpuTypes() return { CpuType::Ws }; } +uint64_t WsConsole::GetCartridgeClock() +{ + return _cpu->GetCycleCount() / 8; +} + +uint32_t WsConsole::GetCartridgeClockRate() +{ + return 12288000 / 32; +} + uint64_t WsConsole::GetMasterClock() { return _cpu->GetCycleCount(); @@ -496,4 +517,7 @@ void WsConsole::Serialize(Serializer& s) if(_cartEeprom) { SV(_cartEeprom); } + if(_cartRtc) { + SV(_cartRtc); + } } diff --git a/Core/WS/WsConsole.h b/Core/WS/WsConsole.h index 628016f73..b48814a52 100644 --- a/Core/WS/WsConsole.h +++ b/Core/WS/WsConsole.h @@ -14,6 +14,7 @@ class WsMemoryManager; class WsControlManager; class WsDmaController; class WsEeprom; +class WsRtc; enum class WsModel : uint8_t; class WsConsole final : public IConsole @@ -31,6 +32,7 @@ class WsConsole final : public IConsole unique_ptr _dmaController; unique_ptr _internalEeprom; unique_ptr _cartEeprom; + unique_ptr _cartRtc; uint8_t* _workRam = nullptr; uint32_t _workRamSize = 0; @@ -81,6 +83,8 @@ class WsConsole final : public IConsole ConsoleRegion GetRegion() override; ConsoleType GetConsoleType() override; vector GetCpuTypes() override; + uint64_t GetCartridgeClock(); + uint32_t GetCartridgeClockRate(); uint64_t GetMasterClock() override; uint32_t GetMasterClockRate() override; double GetFps() override; From 55f507ad80ee8f96187c778bf19cb9e0957aa5ff Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 16:44:53 +0200 Subject: [PATCH 3/5] WS: Expose cartridge RTC state to register viewer. --- Core/WS/Carts/WsCart.cpp | 1 + Core/WS/Carts/WsRtc.cpp | 43 ++++++++++--------- Core/WS/Carts/WsRtc.h | 8 ++-- Core/WS/WsConsole.cpp | 3 ++ Core/WS/WsTypes.h | 10 +++++ .../RegisterViewer/WsRegisterViewer.cs | 10 +++++ UI/Interop/ConsoleState/WsState.cs | 10 +++++ 7 files changed, 60 insertions(+), 25 deletions(-) diff --git a/Core/WS/Carts/WsCart.cpp b/Core/WS/Carts/WsCart.cpp index e09ca57f7..6895417a7 100644 --- a/Core/WS/Carts/WsCart.cpp +++ b/Core/WS/Carts/WsCart.cpp @@ -28,6 +28,7 @@ void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* c _memoryManager = memoryManager; _cartEeprom = cartEeprom; _cartRtc = cartRtc; + _state.HasRtc = cartRtc != nullptr; } void WsCart::RefreshMappings() diff --git a/Core/WS/Carts/WsRtc.cpp b/Core/WS/Carts/WsRtc.cpp index 0d47f64f1..9212cbbe3 100644 --- a/Core/WS/Carts/WsRtc.cpp +++ b/Core/WS/Carts/WsRtc.cpp @@ -10,45 +10,45 @@ WsRtc::WsRtc(Emulator* emu, WsConsole* console) : S3511ARtc(emu), _console(console) { - _ready = true; + _state.Ready = true; } bool WsRtc::IsCommandRead() { - return (_command & 0x01) != 0; + return (_state.Command & 0x01) != 0; } void WsRtc::StartByteTransfer() { - if(_busy) { - _ready = false; + if(_state.Busy) { + _state.Ready = false; _transferClock = _console->GetCartridgeClock() + 8; } } void WsRtc::Update() { - if(!_ready && _console->GetCartridgeClock() >= _transferClock) { + if(!_state.Ready && _console->GetCartridgeClock() >= _transferClock) { if(!_commandTransferred) { - WriteBits((_command & 0x0F) | 0x60, 8); + WriteBits((_state.Command & 0x0F) | 0x60, 8); _commandTransferred = true; - _ready = true; + _state.Ready = true; if(_bytesLeft) { StartByteTransfer(); } } else if(_bytesLeft) { if(IsCommandRead()) { - _data = ReadBits(8); + _state.Data = ReadBits(8); } else { - WriteBits(_data, 8); + WriteBits(_state.Data, 8); } - _ready = true; + _state.Ready = true; _bytesLeft--; } if(_commandTransferred && !_bytesLeft) { ResetBus(); - _busy = false; + _state.Busy = false; _commandTransferred = false; } } @@ -59,12 +59,12 @@ uint8_t WsRtc::ReadPort(uint16_t port) Update(); if(port == 0xCA) { - return (_command & 0x0F) | (_busy ? 0x10 : 0) | (_ready ? 0x80 : 0); + return (_state.Command & 0x0F) | (_state.Busy ? 0x10 : 0) | (_state.Ready ? 0x80 : 0); } else if(port == 0xCB) { if(_bytesLeft && IsCommandRead()) { StartByteTransfer(); } - return _data; + return _state.Data; } return 0; @@ -75,12 +75,12 @@ void WsRtc::WritePort(uint16_t port, uint8_t value) Update(); if(port == 0xCA) { - _busy = (value & 0x10) == 0x10; - _command = (value & 0x0F); - _bytesLeft = GetCommandLength(_command); + _state.Busy = (value & 0x10) == 0x10; + _state.Command = (value & 0x0F); + _bytesLeft = GetCommandLength(_state.Command); StartByteTransfer(); } else if(port == 0xCB) { - _data = value; + _state.Data = value; if(_bytesLeft && !IsCommandRead()) { StartByteTransfer(); } @@ -91,11 +91,12 @@ void WsRtc::Serialize(Serializer& s) { S3511ARtc::Serialize(s); - SV(_command); - SV(_data); + SV(_state.Command); + SV(_state.Data); + SV(_state.Ready); + SV(_state.Busy); + SV(_bytesLeft); SV(_commandTransferred); SV(_transferClock); - SV(_ready); - SV(_busy); } diff --git a/Core/WS/Carts/WsRtc.h b/Core/WS/Carts/WsRtc.h index f0561a6a1..076a9cee6 100644 --- a/Core/WS/Carts/WsRtc.h +++ b/Core/WS/Carts/WsRtc.h @@ -1,5 +1,6 @@ #pragma once #include "pch.h" +#include "WS/WsTypes.h" #include "Utilities/ISerializable.h" #include "Shared/Utilities/S3511ARtc.h" @@ -9,14 +10,11 @@ class WsRtc final : public S3511ARtc { private: WsConsole* _console; + WsRtcState _state = {}; - uint8_t _command = 0; - uint8_t _data = 0; uint8_t _bytesLeft = 0; bool _commandTransferred = false; uint32_t _transferClock = 0; - bool _ready = true; - bool _busy = false; bool IsCommandRead(); void StartByteTransfer(); @@ -25,6 +23,8 @@ class WsRtc final : public S3511ARtc public: WsRtc(Emulator* emu, WsConsole* console); + WsRtcState& GetState() { return _state; } + uint8_t ReadPort(uint16_t port); void WritePort(uint16_t port, uint8_t value); diff --git a/Core/WS/WsConsole.cpp b/Core/WS/WsConsole.cpp index cdd5079c3..ed2f42693 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -468,6 +468,9 @@ WsState WsConsole::GetState() if(_cartEeprom) { state.CartEeprom = _cartEeprom->GetState(); } + if(_cartRtc) { + state.CartRtc = _cartRtc->GetState(); + } return state; } diff --git a/Core/WS/WsTypes.h b/Core/WS/WsTypes.h index f7c80fa90..367d22327 100644 --- a/Core/WS/WsTypes.h +++ b/Core/WS/WsTypes.h @@ -445,9 +445,18 @@ struct WsEepromState struct WsCartState { + bool HasRtc; uint8_t SelectedBanks[4]; }; +struct WsRtcState +{ + uint8_t Data; + uint8_t Command; + bool Ready; + bool Busy; +}; + struct WsState { WsCpuState Cpu; @@ -461,6 +470,7 @@ struct WsState WsEepromState InternalEeprom; WsCartState Cart; WsEepromState CartEeprom; + WsRtcState CartRtc; WsModel Model; }; diff --git a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs index 40ac369a9..2e209c904 100644 --- a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs +++ b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs @@ -383,6 +383,16 @@ private static RegisterViewerTab GetCartTab(ref WsState ws) }); } + if(cart.HasRtc) { + entries.AddRange(new List() { + new RegEntry("", "Cart RTC"), + new RegEntry("$CA.0-3", "Command", ws.CartRtc.Command, Format.X8), + new RegEntry("$CA.4", "Busy", ws.CartRtc.Busy), + new RegEntry("$CA.7", "Ready", ws.CartRtc.Ready), + new RegEntry("$CB", "Data", ws.CartRtc.Data) + }); + } + return new RegisterViewerTab("Cart", entries, CpuType.Ws, MemoryType.WsPort); } diff --git a/UI/Interop/ConsoleState/WsState.cs b/UI/Interop/ConsoleState/WsState.cs index af8d317cc..7df108e42 100644 --- a/UI/Interop/ConsoleState/WsState.cs +++ b/UI/Interop/ConsoleState/WsState.cs @@ -442,10 +442,19 @@ public struct WsEepromState public struct WsCartState { + [MarshalAs(UnmanagedType.I1)] public bool HasRtc; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] SelectedBanks; } +public struct WsRtcState +{ + public byte Data; + public byte Command; + [MarshalAs(UnmanagedType.I1)] public bool Ready; + [MarshalAs(UnmanagedType.I1)] public bool Busy; +} + public struct WsState : BaseState { public WsCpuState Cpu; @@ -459,5 +468,6 @@ public struct WsState : BaseState public WsEepromState InternalEeprom; public WsCartState Cart; public WsEepromState CartEeprom; + public WsRtcState CartRtc; public WsModel Model; } From 61948e83d501f6bed1128bc9389b92c6448ceba1 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 16:49:25 +0200 Subject: [PATCH 4/5] Code: Fix formatting. --- Core/GBA/Cart/GbaRtc.cpp | 2 +- Core/Shared/Utilities/S3511ARtc.cpp | 2 +- Core/Shared/Utilities/S3511ARtc.h | 2 +- Core/WS/Carts/WsRtc.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/GBA/Cart/GbaRtc.cpp b/Core/GBA/Cart/GbaRtc.cpp index 839abed59..8a87c47ea 100644 --- a/Core/GBA/Cart/GbaRtc.cpp +++ b/Core/GBA/Cart/GbaRtc.cpp @@ -40,7 +40,7 @@ void GbaRtc::Write(uint8_t value) } else if(!clk && _clk) { _bitOut = ReadBits(1); } - + _clk = clk; } diff --git a/Core/Shared/Utilities/S3511ARtc.cpp b/Core/Shared/Utilities/S3511ARtc.cpp index d817d472b..50e4f81f1 100644 --- a/Core/Shared/Utilities/S3511ARtc.cpp +++ b/Core/Shared/Utilities/S3511ARtc.cpp @@ -76,7 +76,7 @@ uint8_t S3511ARtc::GetCommandLength(Command cmd) uint8_t S3511ARtc::GetCommandLength(uint8_t cmd) { - return GetCommandLength((Command) ((cmd >> 1) & 0x07)); + return GetCommandLength((Command)((cmd >> 1) & 0x07)); } void S3511ARtc::ResetBus() diff --git a/Core/Shared/Utilities/S3511ARtc.h b/Core/Shared/Utilities/S3511ARtc.h index b3412713e..474d5837d 100644 --- a/Core/Shared/Utilities/S3511ARtc.h +++ b/Core/Shared/Utilities/S3511ARtc.h @@ -63,7 +63,7 @@ class S3511ARtc : public ISerializable uint8_t GetCommandLength(Command cmd); uint8_t GetCommandLength(uint8_t cmd); - //GBA only uses count values of 1, WS only uses 8 + //GBA only uses count values of 1, WS only uses 8 void WriteBits(uint32_t value, int count); uint32_t ReadBits(int count); void ResetBus(); diff --git a/Core/WS/Carts/WsRtc.cpp b/Core/WS/Carts/WsRtc.cpp index 9212cbbe3..e3e5c2b1b 100644 --- a/Core/WS/Carts/WsRtc.cpp +++ b/Core/WS/Carts/WsRtc.cpp @@ -95,7 +95,7 @@ void WsRtc::Serialize(Serializer& s) SV(_state.Data); SV(_state.Ready); SV(_state.Busy); - + SV(_bytesLeft); SV(_commandTransferred); SV(_transferClock); From 0e93f2a706303ce05ca8903474bdaf438891bb27 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 17:35:59 +0200 Subject: [PATCH 5/5] WS: Update Core.vcxproj. --- Core/Core.vcxproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 7c7147600..eab83ef2e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -931,7 +931,7 @@ - + @@ -1094,6 +1094,7 @@ + @@ -1337,4 +1338,4 @@ - \ No newline at end of file +