lsnes/snes/chip/srtc/srtc.cpp
Ilari Liusvaara 7379b4570e Don't use time() in emulating chips
Instead of using time() in chip emulation, create new interface method
currentTime(), defaulting to time(0). This way frontend can cleanly
override the current time bsnes is using.
2013-05-03 17:48:31 +03:00

231 lines
5.4 KiB
C++
Executable file

#include <snes/snes.hpp>
#define SRTC_CPP
namespace SNES {
SRTC srtc;
#include "serialization.cpp"
const unsigned SRTC::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
void SRTC::init() {
}
void SRTC::load() {
for(unsigned n = 0; n < 20; n++) rtc[n] = 0xff;
cartridge.nvram.append({ ".rtc", rtc, 20 });
}
void SRTC::unload() {
}
void SRTC::power() {
}
void SRTC::reset() {
rtc_mode = RtcRead;
rtc_index = -1;
update_time();
}
void SRTC::update_time() {
time_t rtc_time = (rtc[16] << 0) | (rtc[17] << 8) | (rtc[18] << 16) | (rtc[19] << 24);
time_t current_time = SNES::interface->currentTime();
//sizeof(time_t) is platform-dependent; though rtc[] needs to be platform-agnostic.
//yet platforms with 32-bit signed time_t will overflow every ~68 years. handle this by
//accounting for overflow at the cost of 1-bit precision (to catch underflow). this will allow
//rtc[] timestamp to remain valid for up to ~34 years from the last update, even if
//time_t overflows. calculation should be valid regardless of number representation, time_t size,
//or whether time_t is signed or unsigned.
time_t diff
= (current_time >= rtc_time)
? (current_time - rtc_time)
: (std::numeric_limits<time_t>::max() - rtc_time + current_time + 1); //compensate for overflow
if(diff > std::numeric_limits<time_t>::max() / 2) diff = 0; //compensate for underflow
if(diff > 0) {
unsigned second = rtc[ 0] + rtc[ 1] * 10;
unsigned minute = rtc[ 2] + rtc[ 3] * 10;
unsigned hour = rtc[ 4] + rtc[ 5] * 10;
unsigned day = rtc[ 6] + rtc[ 7] * 10;
unsigned month = rtc[ 8];
unsigned year = rtc[ 9] + rtc[10] * 10 + rtc[11] * 100;
unsigned weekday = rtc[12];
day--;
month--;
year += 1000;
second += diff;
while(second >= 60) {
second -= 60;
minute++;
if(minute < 60) continue;
minute = 0;
hour++;
if(hour < 24) continue;
hour = 0;
day++;
weekday = (weekday + 1) % 7;
unsigned days = months[month % 12];
if(days == 28) {
bool leapyear = false;
if((year % 4) == 0) {
leapyear = true;
if((year % 100) == 0 && (year % 400) != 0) leapyear = false;
}
if(leapyear) days++;
}
if(day < days) continue;
day = 0;
month++;
if(month < 12) continue;
month = 0;
year++;
}
day++;
month++;
year -= 1000;
rtc[ 0] = second % 10;
rtc[ 1] = second / 10;
rtc[ 2] = minute % 10;
rtc[ 3] = minute / 10;
rtc[ 4] = hour % 10;
rtc[ 5] = hour / 10;
rtc[ 6] = day % 10;
rtc[ 7] = day / 10;
rtc[ 8] = month;
rtc[ 9] = year % 10;
rtc[10] = (year / 10) % 10;
rtc[11] = year / 100;
rtc[12] = weekday % 7;
}
rtc[16] = current_time >> 0;
rtc[17] = current_time >> 8;
rtc[18] = current_time >> 16;
rtc[19] = current_time >> 24;
}
//returns day of week for specified date
//eg 0 = Sunday, 1 = Monday, ... 6 = Saturday
//usage: weekday(2008, 1, 1) returns weekday of January 1st, 2008
unsigned SRTC::weekday(unsigned year, unsigned month, unsigned day) {
unsigned y = 1900, m = 1; //epoch is 1900-01-01
unsigned sum = 0; //number of days passed since epoch
year = max(1900, year);
month = max(1, min(12, month));
day = max(1, min(31, day));
while(y < year) {
bool leapyear = false;
if((y % 4) == 0) {
leapyear = true;
if((y % 100) == 0 && (y % 400) != 0) leapyear = false;
}
sum += leapyear ? 366 : 365;
y++;
}
while(m < month) {
unsigned days = months[m - 1];
if(days == 28) {
bool leapyear = false;
if((y % 4) == 0) {
leapyear = true;
if((y % 100) == 0 && (y % 400) != 0) leapyear = false;
}
if(leapyear) days++;
}
sum += days;
m++;
}
sum += day - 1;
return (sum + 1) % 7; //1900-01-01 was a Monday
}
uint8 SRTC::read(unsigned addr) {
addr &= 0xffff;
if(addr == 0x2800) {
if(rtc_mode != RtcRead) return 0x00;
if(rtc_index < 0) {
update_time();
rtc_index++;
return 0x0f;
} else if(rtc_index > 12) {
rtc_index = -1;
return 0x0f;
} else {
return rtc[rtc_index++];
}
}
return cpu.regs.mdr;
}
void SRTC::write(unsigned addr, uint8 data) {
addr &= 0xffff;
if(addr == 0x2801) {
data &= 0x0f; //only the low four bits are used
if(data == 0x0d) {
rtc_mode = RtcRead;
rtc_index = -1;
return;
}
if(data == 0x0e) {
rtc_mode = RtcCommand;
return;
}
if(data == 0x0f) return; //unknown behavior
if(rtc_mode == RtcWrite) {
if(rtc_index >= 0 && rtc_index < 12) {
rtc[rtc_index++] = data;
if(rtc_index == 12) {
//day of week is automatically calculated and written
unsigned day = rtc[ 6] + rtc[ 7] * 10;
unsigned month = rtc[ 8];
unsigned year = rtc[ 9] + rtc[10] * 10 + rtc[11] * 100;
year += 1000;
rtc[rtc_index++] = weekday(year, month, day);
}
}
} else if(rtc_mode == RtcCommand) {
if(data == 0) {
rtc_mode = RtcWrite;
rtc_index = 0;
} else if(data == 4) {
rtc_mode = RtcReady;
rtc_index = -1;
for(unsigned i = 0; i < 13; i++) rtc[i] = 0;
} else {
//unknown behavior
rtc_mode = RtcReady;
}
}
}
}
SRTC::SRTC() {
}
}