diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 29fd33122..64ecf08c6 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -931,6 +931,7 @@ + @@ -1093,6 +1094,8 @@ + + @@ -1336,4 +1339,4 @@ - \ No newline at end of file + diff --git a/Core/GBA/Cart/GbaRtc.cpp b/Core/GBA/Cart/GbaRtc.cpp index 1b2d19ca0..8a87c47ea 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..50e4f81f1 --- /dev/null +++ b/Core/Shared/Utilities/S3511ARtc.cpp @@ -0,0 +1,358 @@ +#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; + } +} + +uint8_t S3511ARtc::GetCommandLength(uint8_t cmd) +{ + return GetCommandLength((Command)((cmd >> 1) & 0x07)); +} + +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..474d5837d --- /dev/null +++ b/Core/Shared/Utilities/S3511ARtc.h @@ -0,0 +1,78 @@ +#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: + 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(); + + 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); + void ResetBus(); + +public: + S3511ARtc(Emulator* emu); + + void LoadBattery(); + void SaveBattery(); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/WS/Carts/WsCart.cpp b/Core/WS/Carts/WsCart.cpp index 15aef58bc..1cd5678c8 100644 --- a/Core/WS/Carts/WsCart.cpp +++ b/Core/WS/Carts/WsCart.cpp @@ -1,7 +1,9 @@ #include "pch.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsRtc.h" #include "WS/WsMemoryManager.h" #include "WS/WsEeprom.h" +#include "Utilities/BitUtilities.h" #include "Utilities/Serializer.h" void WsCart::Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly) @@ -16,21 +18,40 @@ void WsCart::Unmap(uint32_t start, uint32_t end) WsCart::WsCart() { - _state.SelectedBanks[0] = 0xFF; - _state.SelectedBanks[1] = 0xFF; - _state.SelectedBanks[2] = 0xFF; - _state.SelectedBanks[3] = 0xFF; + _state.SelectedBanks[0] = 0x3F; + _state.SelectedBanks[1] = 0x3FF; + _state.SelectedBanks[2] = 0x3FF; + _state.SelectedBanks[3] = 0x3FF; } -void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom) +void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* cartRtc, uint8_t* prgRom, uint32_t prgRomSize, uint8_t* saveRam, uint32_t saveRamSize) { _memoryManager = memoryManager; _cartEeprom = cartEeprom; + _cartRtc = cartRtc; + _state.HasRtc = cartRtc != nullptr; + _prgRom = prgRom; + _prgRomSize = prgRomSize; + _saveRam = saveRam; + _saveRamSize = saveRamSize; +} + +uint32_t WsCart::GetPhysicalAddress(uint32_t addr) +{ + if(addr >= 0x40000) { + return (_state.SelectedBanks[0] << 20) | (addr & 0xFFFFF); + } else { + return (_state.SelectedBanks[addr >> 16] << 16) | (addr & 0xFFFF); + } } void WsCart::RefreshMappings() { - Map(0x10000, 0x1FFFF, MemoryType::WsCartRam, _state.SelectedBanks[1] * 0x10000, false); + if(_state.RomInRamBank) { + Map(0x10000, 0x1FFFF, MemoryType::WsPrgRom, _state.SelectedBanks[1] * 0x10000, true); + } else { + Map(0x10000, 0x1FFFF, MemoryType::WsCartRam, _state.SelectedBanks[1] * 0x10000, false); + } Map(0x20000, 0x2FFFF, MemoryType::WsPrgRom, _state.SelectedBanks[2] * 0x10000, true); Map(0x30000, 0x3FFFF, MemoryType::WsPrgRom, _state.SelectedBanks[3] * 0x10000, true); Map(0x40000, 0xFFFFF, MemoryType::WsPrgRom, _state.SelectedBanks[0] * 0x100000 + 0x40000, true); @@ -38,10 +59,18 @@ void WsCart::RefreshMappings() uint8_t WsCart::ReadPort(uint16_t port) { - if(port < 0xC4) { + if(port == 0xC0 || port == 0xCF) { + return _state.SelectedBanks[0]; + } else 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); + } else if(port == 0xCE) { + return _state.RomInRamBank ? 0x01 : 0; + } else if(port >= 0xD0 && port < 0xD6) { + return _state.SelectedBanks[1 + ((port - 0xD0) >> 1)] >> ((port & 1) ? 8 : 0); } return _memoryManager->GetUnmappedPort(); @@ -49,14 +78,39 @@ uint8_t WsCart::ReadPort(uint16_t port) void WsCart::WritePort(uint16_t port, uint8_t value) { - if(port < 0xC4) { - _state.SelectedBanks[port - 0xC0] = value; + if(port == 0xC0 || port == 0xCF) { + _state.SelectedBanks[0] = value & 0x3F; + _memoryManager->RefreshMappings(); + } else if(port < 0xC4) { + BitUtilities::SetBits<0>(_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); + } else if(port == 0xCE) { + _state.RomInRamBank = (value & 0x01) != 0; + _memoryManager->RefreshMappings(); + } else if(port >= 0xD0 && port < 0xD6) { + if(port & 1) { + BitUtilities::SetBits<8>(_state.SelectedBanks[1 + ((port - 0xD0) >> 1)], value & 0x03); + } else { + BitUtilities::SetBits<0>(_state.SelectedBanks[1 + ((port - 0xD0) >> 1)], value); + } + _memoryManager->RefreshMappings(); } } +uint8_t WsCart::ReadMemory(uint32_t addr) +{ + assert(false); +} + +void WsCart::WriteMemory(uint32_t addr, uint8_t value) +{ + assert(false); +} + void WsCart::Serialize(Serializer& s) { SVArray(_state.SelectedBanks, 4); diff --git a/Core/WS/Carts/WsCart.h b/Core/WS/Carts/WsCart.h index 87af797a9..9a269b01c 100644 --- a/Core/WS/Carts/WsCart.h +++ b/Core/WS/Carts/WsCart.h @@ -4,32 +4,43 @@ #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 +class WsCart : public ISerializable { protected: WsCartState _state = {}; WsMemoryManager* _memoryManager = nullptr; WsEeprom* _cartEeprom = nullptr; + WsRtc* _cartRtc = nullptr; + + uint8_t* _prgRom = nullptr; + uint32_t _prgRomSize = 0; + uint8_t* _saveRam = nullptr; + uint32_t _saveRamSize = 0; void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly); void Unmap(uint32_t start, uint32_t end); + uint32_t GetPhysicalAddress(uint32_t addr); + public: WsCart(); virtual ~WsCart() {} - void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom); - void RefreshMappings(); + virtual void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* cartRtc, uint8_t* prgRom, uint32_t prgRomSize, uint8_t* saveRam, uint32_t saveRamSize); + virtual void RefreshMappings(); WsCartState& GetState() { return _state; } WsEeprom* GetEeprom() { return _cartEeprom; } + WsRtc* GetRtc() { return _cartRtc; } + + virtual uint8_t ReadMemory(uint32_t addr); + virtual void WriteMemory(uint32_t addr, uint8_t value); virtual uint8_t ReadPort(uint16_t port); virtual void WritePort(uint16_t port, uint8_t value); diff --git a/Core/WS/Carts/WsCartFlash.cpp b/Core/WS/Carts/WsCartFlash.cpp new file mode 100644 index 000000000..364e6e8b9 --- /dev/null +++ b/Core/WS/Carts/WsCartFlash.cpp @@ -0,0 +1,139 @@ +#include "pch.h" +#include "WS/WsMemoryManager.h" +#include "WS/Carts/WsCartFlash.h" + +//TODOWS sector protection +//TODOWS timing, more accurate status reads during programming +//TODOWS more verification with datasheet and hardware + +WsCartFlash::WsCartFlash() : WsCart() +{ +} + +void WsCartFlash::RefreshMappings() +{ + WsCart::RefreshMappings(); + _memoryManager->SetCartFlash((_mode[0] != Mode::Read || _mode[1] != Mode::Read) ? WsRegisterAccess::ReadWrite : WsRegisterAccess::Write); +} + +uint8_t WsCartFlash::ReadMemory(uint32_t addr) +{ + bool sram = ((addr >> 16) == 1) && !_state.RomInRamBank; + uint32_t physAddr = GetPhysicalAddress(addr); + + if(sram) { + return _saveRam[physAddr & (_saveRamSize - 1)]; + } + + physAddr &= _prgRomSize - 1; + + uint8_t sector = physAddr >= 0x60000 ? 1 : 0; + uint8_t value = _prgRom[physAddr]; + if(_mode[sector] == Mode::Fast) { + return (value & 0x80) | 0x04; + } + if(_mode[sector] == Mode::Autoselect) { + //TODOWS support word bus reads correctly + if((physAddr & 0x87) == 0x00) { + return 0x04; + } + if((physAddr & 0x87) == 0x02) { + return 0x0C; + } + if((physAddr & 0x87) == 0x04) { + return 0x00; + } + //TODOWS what is read at other addresses? + } + return value; +} + +void WsCartFlash::WriteMemory(uint32_t addr, uint8_t value) +{ + bool sram = ((addr >> 16) == 1) && !_state.RomInRamBank; + uint32_t physAddr = GetPhysicalAddress(addr); + + if(sram) { + _saveRam[physAddr & (_saveRamSize - 1)] = value; + return; + } + + physAddr &= _prgRomSize - 1; + + uint8_t sector = physAddr >= 0x60000 ? 1 : 0; + if(_mode[sector] == Mode::Program) { + _prgRom[physAddr] &= value; + _mode[sector] = Mode::Read; + } else if(_mode[sector] == Mode::FastProgram) { + _prgRom[physAddr] &= value; + _mode[sector] = Mode::Fast; + } else if(_mode[sector] == Mode::Fast) { + if(value == 0xA0) { + _mode[sector] = Mode::FastProgram; + } else if(value == 0x90) { + _mode[sector] = Mode::Read; + } + } else { + //TODOWS how does this operate in erase mode? + if(value == 0xF0) { + _mode[sector] = Mode::Read; + } + + if(_unlock == 0 && value == 0xAA && (physAddr & 0xFFF) == 0xAAA) { + _unlock = 1; + } else if(_unlock == 1) { + _unlock = (value == 0x55 && (physAddr & 0xFFF) == 0x555) ? 2 : 0; + } else if(_unlock == 2) { + if(_mode[sector] == Mode::Erase) { + if(value == 0x10) { + memset(_prgRom, 0xFF, _prgRomSize); + } else if(value == 0x30) { + uint32_t offset, size; + if(physAddr < 0x60000) { + offset = physAddr & 0x70000; + size = 0x10000; + } else if(physAddr < 0x64000) { + offset = 0x60000; + size = 0x4000; + } else if(physAddr < 0x6c000) { + offset = 0x64000; + size = 0x8000; + } else if(physAddr < 0x74000) { + offset = physAddr & 0x7e000; + size = 0x2000; + } else if(physAddr < 0x7c000) { + offset = 0x74000; + size = 0x8000; + } else { + offset = 0x7c000; + size = 0x4000; + } + memset(_prgRom + offset, 0xFF, size); + } + _mode[sector] = Mode::Read; + } else { + switch(value) { + case 0x90: _mode[sector] = Mode::Autoselect; break; + case 0xA0: _mode[sector] = Mode::Program; break; + case 0x80: _mode[sector] = Mode::Erase; break; + case 0x20: _mode[sector] = Mode::Fast; break; + } + } + + _unlock = 0; + if(_mode[0] != Mode::Read && _mode[1] != Mode::Read) { + _mode[sector ^ 1] = Mode::Read; + } + } + } + + _memoryManager->SetCartFlash((_mode[0] != Mode::Read || _mode[1] != Mode::Read) ? WsRegisterAccess::ReadWrite : WsRegisterAccess::Write); +} + +void WsCartFlash::Serialize(Serializer& s) +{ + WsCart::Serialize(s); + + SV(_unlock); + SVArray(_mode, 2); +} diff --git a/Core/WS/Carts/WsCartFlash.h b/Core/WS/Carts/WsCartFlash.h new file mode 100644 index 000000000..4799ea2fb --- /dev/null +++ b/Core/WS/Carts/WsCartFlash.h @@ -0,0 +1,34 @@ +#pragma once +#include "pch.h" +#include "WS/WsTypes.h" +#include "WS/Carts/WsCart.h" +#include "Utilities/ISerializable.h" +#include "Shared/MemoryType.h" + +class WsCartFlash final : public WsCart +{ +private: + enum class Mode : uint8_t + { + Read = 0, + Autoselect, + Fast, + Program, + FastProgram, + Erase + }; + + Mode _mode[2]; + uint8_t _unlock; + +public: + WsCartFlash(); + virtual ~WsCartFlash() {} + + void RefreshMappings() override; + + uint8_t ReadMemory(uint32_t addr) override; + void WriteMemory(uint32_t addr, uint8_t value) override; + + void Serialize(Serializer& s) override; +}; diff --git a/Core/WS/Carts/WsRtc.cpp b/Core/WS/Carts/WsRtc.cpp new file mode 100644 index 000000000..e3e5c2b1b --- /dev/null +++ b/Core/WS/Carts/WsRtc.cpp @@ -0,0 +1,102 @@ +#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) +{ + _state.Ready = true; +} + +bool WsRtc::IsCommandRead() +{ + return (_state.Command & 0x01) != 0; +} + +void WsRtc::StartByteTransfer() +{ + if(_state.Busy) { + _state.Ready = false; + _transferClock = _console->GetCartridgeClock() + 8; + } +} + +void WsRtc::Update() +{ + if(!_state.Ready && _console->GetCartridgeClock() >= _transferClock) { + if(!_commandTransferred) { + WriteBits((_state.Command & 0x0F) | 0x60, 8); + _commandTransferred = true; + _state.Ready = true; + if(_bytesLeft) { + StartByteTransfer(); + } + } else if(_bytesLeft) { + if(IsCommandRead()) { + _state.Data = ReadBits(8); + } else { + WriteBits(_state.Data, 8); + } + _state.Ready = true; + _bytesLeft--; + } + + if(_commandTransferred && !_bytesLeft) { + ResetBus(); + _state.Busy = false; + _commandTransferred = false; + } + } +} + +uint8_t WsRtc::ReadPort(uint16_t port) +{ + Update(); + + if(port == 0xCA) { + return (_state.Command & 0x0F) | (_state.Busy ? 0x10 : 0) | (_state.Ready ? 0x80 : 0); + } else if(port == 0xCB) { + if(_bytesLeft && IsCommandRead()) { + StartByteTransfer(); + } + return _state.Data; + } + + return 0; +} + +void WsRtc::WritePort(uint16_t port, uint8_t value) +{ + Update(); + + if(port == 0xCA) { + _state.Busy = (value & 0x10) == 0x10; + _state.Command = (value & 0x0F); + _bytesLeft = GetCommandLength(_state.Command); + StartByteTransfer(); + } else if(port == 0xCB) { + _state.Data = value; + if(_bytesLeft && !IsCommandRead()) { + StartByteTransfer(); + } + } +} + +void WsRtc::Serialize(Serializer& s) +{ + S3511ARtc::Serialize(s); + + SV(_state.Command); + SV(_state.Data); + SV(_state.Ready); + SV(_state.Busy); + + SV(_bytesLeft); + SV(_commandTransferred); + SV(_transferClock); +} diff --git a/Core/WS/Carts/WsRtc.h b/Core/WS/Carts/WsRtc.h new file mode 100644 index 000000000..076a9cee6 --- /dev/null +++ b/Core/WS/Carts/WsRtc.h @@ -0,0 +1,32 @@ +#pragma once +#include "pch.h" +#include "WS/WsTypes.h" +#include "Utilities/ISerializable.h" +#include "Shared/Utilities/S3511ARtc.h" + +class Emulator; + +class WsRtc final : public S3511ARtc +{ +private: + WsConsole* _console; + WsRtcState _state = {}; + + uint8_t _bytesLeft = 0; + bool _commandTransferred = false; + uint32_t _transferClock = 0; + + bool IsCommandRead(); + void StartByteTransfer(); + void Update(); + +public: + WsRtc(Emulator* emu, WsConsole* console); + + WsRtcState& GetState() { return _state; } + + 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..396a7ff6b 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -1,9 +1,12 @@ +#include "WS/Carts/WsCartFlash.h" #include "pch.h" #include "WS/WsConsole.h" #include "WS/WsCpu.h" #include "WS/WsPpu.h" #include "WS/WsTimer.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsCartFlash.h" +#include "WS/Carts/WsRtc.h" #include "WS/WsControlManager.h" #include "WS/WsMemoryManager.h" #include "WS/WsDmaController.h" @@ -32,6 +35,16 @@ WsConsole::~WsConsole() delete[] _cartEepromData; } +bool WsConsole::IsWWCart() +{ + //TODOWS exclude static FreyaBIOS cartridges + return _prgRomSize == 0x80000 && _prgRom[0x70000] == 'E' // FreyaBIOS header + && _prgRom[0x70001] == 'L' && _prgRom[0x70002] == 'I' && _prgRom[0x70003] == 'S' && _prgRom[0x70004] == 'A' && _prgRom[0x7fff6] == 0x00 // Publisher ID + && _prgRom[0x7fff8] == 0x00 // Game ID + && _prgRom[0x7fffb] == 0x04 // Save format + && _prgRom[0x7fffd] == 0x01; // Mapper +} + LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) { vector romData; @@ -77,7 +90,7 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) MessageManager::Log(string("Color supported: ") + (hasColorSupport ? "Yes" : "No")); MessageManager::Log("Save RAM size: " + std::to_string(_saveRamSize / 1024) + " KB"); MessageManager::Log("Cart EEPROM size: " + std::to_string(_cartEepromSize) + " bytes"); - MessageManager::Log(string("Mapper: ") + (mapperType == 0 ? "Bandai 2001 / KARNAK" : (mapperType == 1 ? "Bandai 2003" : ("Unknown: " + std::to_string(mapperType))))); + MessageManager::Log(string("Mapper: ") + (IsWWCart() ? "Bandai 2003 + NOR flash" : (mapperType == 0 ? "Bandai 2001 / KARNAK" : (mapperType == 1 ? "Bandai 2003" : ("Unknown: " + std::to_string(mapperType)))))); MessageManager::Log("------------------------------"); @@ -94,6 +107,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; @@ -127,9 +144,9 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _dmaController.reset(new WsDmaController()); _ppu.reset(new WsPpu(_emu, this, _memoryManager.get(), _timer.get(), _workRam)); _apu.reset(new WsApu(_emu, this, _memoryManager.get(), _dmaController.get())); - _cart.reset(new WsCart()); + _cart.reset(IsWWCart() ? new WsCartFlash() : new WsCart()); - _cart->Init(_memoryManager.get(), _cartEeprom.get()); + _cart->Init(_memoryManager.get(), _cartEeprom.get(), _cartRtc.get(), _prgRom, _prgRomSize, _saveRam, _saveRamSize); _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,7 +328,13 @@ void WsConsole::LoadBattery() if(_cartEeprom) { _cartEeprom->LoadBattery(); } + if(_cartRtc) { + _cartRtc->LoadBattery(); + } + if(IsWWCart()) { + _emu->GetBatteryManager()->LoadBattery(".flash", _prgRom, _prgRomSize); + } if(_saveRam) { _emu->GetBatteryManager()->LoadBattery(".sav", _saveRam, _saveRamSize); } @@ -323,7 +346,13 @@ void WsConsole::SaveBattery() if(_cartEeprom) { _cartEeprom->SaveBattery(); } + if(_cartRtc) { + _cartRtc->SaveBattery(); + } + if(IsWWCart()) { + _emu->GetBatteryManager()->SaveBattery(".flash", _prgRom, _prgRomSize); + } if(_saveRam) { _emu->GetBatteryManager()->SaveBattery(".sav", _saveRam, _saveRamSize); } @@ -349,6 +378,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(); @@ -447,6 +486,9 @@ WsState WsConsole::GetState() if(_cartEeprom) { state.CartEeprom = _cartEeprom->GetState(); } + if(_cartRtc) { + state.CartRtc = _cartRtc->GetState(); + } return state; } @@ -496,4 +538,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..b6efa7a64 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; @@ -53,6 +55,7 @@ class WsConsole final : public IConsole WsModel _model = {}; bool _verticalMode = false; + bool IsWWCart(); void InitPostBootRomState(); public: @@ -81,6 +84,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; diff --git a/Core/WS/WsMemoryManager.cpp b/Core/WS/WsMemoryManager.cpp index db1a38643..221e322f4 100644 --- a/Core/WS/WsMemoryManager.cpp +++ b/Core/WS/WsMemoryManager.cpp @@ -54,6 +54,11 @@ void WsMemoryManager::RefreshMappings() } } +void WsMemoryManager::SetCartFlash(WsRegisterAccess cartFlash) +{ + _cartFlash = cartFlash; +} + void WsMemoryManager::Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly) { uint8_t* src = (uint8_t*)_emu->GetMemory(type).Memory; @@ -93,6 +98,9 @@ void WsMemoryManager::Unmap(uint32_t start, uint32_t end) uint8_t WsMemoryManager::DebugRead(uint32_t addr) { + if(((int)_cartFlash & (int)WsRegisterAccess::Read) && addr >= 0x10000) { + return _cart->ReadMemory(addr); + } uint8_t* handler = _reads[addr >> 12]; if(handler) { return handler[addr & 0xFFF]; @@ -102,6 +110,10 @@ uint8_t WsMemoryManager::DebugRead(uint32_t addr) void WsMemoryManager::DebugWrite(uint32_t addr, uint8_t value) { + if(((int)_cartFlash & (int)WsRegisterAccess::Write) && addr >= 0x10000) { + _cart->WriteMemory(addr, value); + return; + } uint8_t* handler = _writes[addr >> 12]; if(handler) { handler[addr & 0xFFF] = value; diff --git a/Core/WS/WsMemoryManager.h b/Core/WS/WsMemoryManager.h index 2ca926254..86e60591b 100644 --- a/Core/WS/WsMemoryManager.h +++ b/Core/WS/WsMemoryManager.h @@ -2,6 +2,7 @@ #include "pch.h" #include "WS/WsCpu.h" #include "WS/APU/WsApu.h" +#include "WS/Carts/WsCart.h" #include "WS/WsPpu.h" #include "WS/WsTypes.h" #include "Utilities/ISerializable.h" @@ -9,7 +10,6 @@ class WsConsole; class WsTimer; class WsControlManager; -class WsCart; class WsSerial; class WsDmaController; class WsEeprom; @@ -40,6 +40,7 @@ class WsMemoryManager final : public ISerializable WsMemoryManagerState _state = {}; + WsRegisterAccess _cartFlash; uint8_t* _reads[256] = {}; uint8_t* _writes[256] = {}; @@ -57,6 +58,7 @@ class WsMemoryManager final : public ISerializable uint8_t GetUnmappedPort(); + void SetCartFlash(WsRegisterAccess cartFlash); void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly); void Unmap(uint32_t start, uint32_t end); @@ -69,10 +71,14 @@ class WsMemoryManager final : public ISerializable __forceinline uint8_t InternalRead(uint32_t addr) { - uint8_t* handler = _reads[addr >> 12]; uint8_t value = 0x90; - if(handler) { - value = handler[addr & 0xFFF]; + if(((int)_cartFlash & (int)WsRegisterAccess::Read) && addr >= 0x10000) { + value = _cart->ReadMemory(addr); + } else { + uint8_t* handler = _reads[addr >> 12]; + if(handler) { + value = handler[addr & 0xFFF]; + } } //TODOWS open bus @@ -82,9 +88,13 @@ class WsMemoryManager final : public ISerializable __forceinline void InternalWrite(uint32_t addr, uint8_t value) { //TODOWS open bus - uint8_t* handler = _writes[addr >> 12]; - if(handler) { - handler[addr & 0xFFF] = value; + if(((int)_cartFlash & (int)WsRegisterAccess::Write) && addr >= 0x10000) { + _cart->WriteMemory(addr, value); + } else { + uint8_t* handler = _writes[addr >> 12]; + if(handler) { + handler[addr & 0xFFF] = value; + } } } diff --git a/Core/WS/WsTypes.h b/Core/WS/WsTypes.h index f7c80fa90..030f570ca 100644 --- a/Core/WS/WsTypes.h +++ b/Core/WS/WsTypes.h @@ -221,6 +221,14 @@ enum class WsIrqSource : uint8_t HorizontalBlankTimer = 0x80 }; +enum class WsRegisterAccess +{ + None = 0, + Read = 1, + Write = 2, + ReadWrite = 3 +}; + struct WsMemoryManagerState { uint8_t ActiveIrqs; @@ -445,7 +453,17 @@ struct WsEepromState struct WsCartState { - uint8_t SelectedBanks[4]; + bool HasRtc; + bool RomInRamBank; + uint16_t SelectedBanks[4]; +}; + +struct WsRtcState +{ + uint8_t Data; + uint8_t Command; + bool Ready; + bool Busy; }; struct WsState @@ -461,6 +479,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..c28b97d0a 100644 --- a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs +++ b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs @@ -366,9 +366,13 @@ private static RegisterViewerTab GetCartTab(ref WsState ws) entries.AddRange(new List() { new RegEntry("$C0", "ROM Linear Bank", cart.SelectedBanks[0], Format.X8), - new RegEntry("$C1", "RAM Bank", cart.SelectedBanks[1], Format.X8), - new RegEntry("$C2", "ROM0 Bank", cart.SelectedBanks[2], Format.X8), - new RegEntry("$C3", "ROM1 Bank", cart.SelectedBanks[3], Format.X8), + new RegEntry("$C1", "RAM Bank", cart.SelectedBanks[1] & 0xFF, Format.X8), + new RegEntry("$C2", "ROM0 Bank", cart.SelectedBanks[2] & 0xFF, Format.X8), + new RegEntry("$C3", "ROM1 Bank", cart.SelectedBanks[3] & 0xFF, Format.X8), + new RegEntry("$CE.0", "ROM in RAM Bank", cart.RomInRamBank), + new RegEntry("$D0", "Expanded RAM Bank", cart.SelectedBanks[1], Format.X16), + new RegEntry("$D2", "Expanded ROM0 Bank", cart.SelectedBanks[2], Format.X16), + new RegEntry("$D4", "Expanded ROM1 Bank", cart.SelectedBanks[3], Format.X16), }); if(ws.CartEeprom.Size != WsEepromSize.Size0) { @@ -383,6 +387,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..ead3ec765 100644 --- a/UI/Interop/ConsoleState/WsState.cs +++ b/UI/Interop/ConsoleState/WsState.cs @@ -442,8 +442,18 @@ public struct WsEepromState public struct WsCartState { + [MarshalAs(UnmanagedType.I1)] public bool HasRtc; + [MarshalAs(UnmanagedType.I1)] public bool RomInRamBank; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public byte[] SelectedBanks; + public UInt16[] 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 @@ -459,5 +469,6 @@ public struct WsState : BaseState public WsEepromState InternalEeprom; public WsCartState Cart; public WsEepromState CartEeprom; + public WsRtcState CartRtc; public WsModel Model; }