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;
}