snes9x2005/source/srtc.c

480 lines
13 KiB
C

#include "../copyright"
#include <string.h>
#include "snes9x.h"
#include "srtc.h"
#include "memmap.h"
/*** The format of the rtc_data structure is:
Index Description Range (nibble)
----- -------------- ---------------------------------------
0 Seconds low 0-9
1 Seconds high 0-5
2 Minutes low 0-9
3 Minutes high 0-5
4 Hour low 0-9
5 Hour high 0-2
6 Day low 0-9
7 Day high 0-3
8 Month 1-C (0xC is December, 12th month)
9 Year ones 0-9
A Year tens 0-9
B Year High 9-B (9=19xx, A=20xx, B=21xx)
C Day of week 0-6 (0=Sunday, 1=Monday,...,6=Saturday)
***/
SRTC_DATA rtc;
static int32_t month_keys[12] = { 1, 4, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6 };
/*********************************************************************************************
*
* Note, if you are doing a save state for this game:
*
* On save:
*
* Call S9xUpdateSrtcTime and save the rtc data structure.
*
* On load:
*
* restore the rtc data structure
* rtc.system_timestamp = time (NULL);
*
*
*********************************************************************************************/
void S9xResetSRTC()
{
rtc.index = -1;
rtc.mode = MODE_READ;
}
void S9xHardResetSRTC()
{
memset(&rtc, 0, sizeof(rtc));
rtc.index = -1;
rtc.mode = MODE_READ;
rtc.count_enable = false;
rtc.needs_init = true;
// Get system timestamp
rtc.system_timestamp = time(NULL);
}
/**********************************************************************************************/
/* S9xSRTCComputeDayOfWeek() */
/* Return 0-6 for Sunday-Saturday */
/**********************************************************************************************/
uint32_t S9xSRTCComputeDayOfWeek()
{
uint32_t year = rtc.data[10] * 10 + rtc.data[9];
uint32_t month = rtc.data[8];
uint32_t day = rtc.data[7] * 10 + rtc.data[6];
uint32_t day_of_week;
year += (rtc.data[11] - 9) * 100;
// Range check the month for valid array indicies
if (month > 12)
month = 1;
day_of_week = year + (year / 4) + month_keys[month - 1] + day - 1;
if ((year % 4 == 0) && (month <= 2))
day_of_week--;
day_of_week %= 7;
return day_of_week;
}
/**********************************************************************************************/
/* S9xSRTCDaysInMonth() */
/* Return the number of days in a specific month for a certain year */
/**********************************************************************************************/
int32_t S9xSRTCDaysInMmonth(int32_t month, int32_t year)
{
int32_t mdays;
switch (month)
{
case 2:
if ((year % 4 == 0)) // DKJM2 only uses 199x - 22xx
mdays = 29;
else
mdays = 28;
break;
case 4:
case 6:
case 9:
case 11:
mdays = 30;
break;
default: // months 1,3,5,7,8,10,12
mdays = 31;
break;
}
return mdays;
}
#define DAYTICKS (60*60*24)
#define HOURTICKS (60*60)
#define MINUTETICKS 60
/**********************************************************************************************/
/* S9xUpdateSrtcTime() */
/* Advance the S-RTC time if counting is enabled */
/**********************************************************************************************/
void S9xUpdateSrtcTime()
{
time_t cur_systime;
int32_t time_diff;
// Keep track of game time by computing the number of seconds that pass on the system
// clock and adding the same number of seconds to the S-RTC clock structure.
// I originally tried using mktime and localtime library functions to keep track
// of time but some of the GNU time functions fail when the year goes to 2099
// (and maybe less) and this would have caused a bug with DKJM2 so I'm doing
// it this way to get around that problem.
// Note: Dai Kaijyu Monogatari II only allows dates in the range 1996-21xx.
if (rtc.count_enable && !rtc.needs_init)
{
cur_systime = time(NULL);
// This method assumes one time_t clock tick is one second
// which should work on PCs and GNU systems.
// If your tick interval is different adjust the
// DAYTICK, HOURTICK, and MINUTETICK defines
time_diff = (int32_t)(cur_systime - rtc.system_timestamp);
rtc.system_timestamp = cur_systime;
if (time_diff > 0)
{
int32_t seconds;
int32_t minutes;
int32_t hours;
int32_t days;
int32_t month;
int32_t year;
int32_t temp_days;
int32_t year_hundreds;
int32_t year_tens;
int32_t year_ones;
if (time_diff > DAYTICKS)
{
days = time_diff / DAYTICKS;
time_diff = time_diff - days * DAYTICKS;
}
else
days = 0;
if (time_diff > HOURTICKS)
{
hours = time_diff / HOURTICKS;
time_diff = time_diff - hours * HOURTICKS;
}
else
hours = 0;
if (time_diff > MINUTETICKS)
{
minutes = time_diff / MINUTETICKS;
time_diff = time_diff - minutes * MINUTETICKS;
}
else
minutes = 0;
if (time_diff > 0)
seconds = time_diff;
else
seconds = 0;
seconds += (rtc.data[1] * 10 + rtc.data[0]);
if (seconds >= 60)
{
seconds -= 60;
minutes += 1;
}
minutes += (rtc.data[3] * 10 + rtc.data[2]);
if (minutes >= 60)
{
minutes -= 60;
hours += 1;
}
hours += (rtc.data[5] * 10 + rtc.data[4]);
if (hours >= 24)
{
hours -= 24;
days += 1;
}
if (days > 0)
{
year = rtc.data[10] * 10 + rtc.data[9];
year += (1000 + rtc.data[11] * 100);
month = rtc.data[8];
days += (rtc.data[7] * 10 + rtc.data[6]);
while (days > (temp_days = S9xSRTCDaysInMmonth(month, year)))
{
days -= temp_days;
month += 1;
if (month > 12)
{
year += 1;
month = 1;
}
}
year_tens = year % 100;
year_ones = year_tens % 10;
year_tens /= 10;
year_hundreds = (year - 1000) / 100;
rtc.data[6] = days % 10;
rtc.data[7] = days / 10;
rtc.data[8] = month;
rtc.data[9] = year_ones;
rtc.data[10] = year_tens;
rtc.data[11] = year_hundreds;
rtc.data[12] = S9xSRTCComputeDayOfWeek();
}
rtc.data[0] = seconds % 10;
rtc.data[1] = seconds / 10;
rtc.data[2] = minutes % 10;
rtc.data[3] = minutes / 10;
rtc.data[4] = hours % 10;
rtc.data[5] = hours / 10;
return;
}
}
}
/**********************************************************************************************/
/* S9xSetSRTC() */
/* This function sends data to the S-RTC used in Dai Kaijyu Monogatari II */
/**********************************************************************************************/
void S9xSetSRTC(uint8_t data, uint16_t Address)
{
data &= 0x0F; // Data is only 4-bits, mask out unused bits.
if (data >= 0xD)
{
// It's an RTC command
switch (data)
{
case 0xD:
rtc.mode = MODE_READ;
rtc.index = -1;
break;
case 0xE:
rtc.mode = MODE_COMMAND;
break;
default:
// Ignore the write if it's an 0xF ???
// Probably should switch back to read mode -- but this
// sequence never occurs in DKJM2
break;
}
return;
}
if (rtc.mode == MODE_LOAD_RTC)
{
if ((rtc.index >= 0) || (rtc.index < MAX_RTC_INDEX))
{
rtc.data[rtc.index++] = data;
if (rtc.index == MAX_RTC_INDEX)
{
// We have all the data for the RTC load
rtc.system_timestamp = time(NULL); // Get local system time
// Get the day of the week
rtc.data[rtc.index++] = S9xSRTCComputeDayOfWeek();
// Start RTC counting again
rtc.count_enable = true;
rtc.needs_init = false;
}
return;
}
else
{
// Attempting to write too much data
// error(); // ignore??
}
}
else if (rtc.mode == MODE_COMMAND)
{
switch (data)
{
case COMMAND_CLEAR_RTC:
// Disable RTC counter
rtc.count_enable = false;
memset(rtc.data, 0, MAX_RTC_INDEX + 1);
rtc.index = -1;
rtc.mode = MODE_COMMAND_DONE;
break;
case COMMAND_LOAD_RTC:
// Disable RTC counter
rtc.count_enable = false;
rtc.index = 0; // Setup for writing
rtc.mode = MODE_LOAD_RTC;
break;
default:
rtc.mode = MODE_COMMAND_DONE;
// unrecognized command - need to implement.
}
return;
}
else
{
if (rtc.mode == MODE_READ)
{
// Attempting to write while in read mode. Ignore.
}
if (rtc.mode == MODE_COMMAND_DONE)
{
// Maybe this isn't an error. Maybe we should kick off
// a new E command. But is this valid?
}
}
}
/**********************************************************************************************/
/* S9xGetSRTC() */
/* This function retrieves data from the S-RTC */
/**********************************************************************************************/
uint8_t S9xGetSRTC(uint16_t Address)
{
if (rtc.mode == MODE_READ)
{
if (rtc.index < 0)
{
S9xUpdateSrtcTime(); // Only update it if the game reads it
rtc.index++;
return (0x0f); // Send start marker.
}
else if (rtc.index > MAX_RTC_INDEX)
{
rtc.index = -1; // Setup for next set of reads
return (0x0f); // Data done marker.
}
else
{
// Feed out the data
return rtc.data[rtc.index++];
}
}
else
return 0x0;
}
void S9xSRTCPreSaveState()
{
if (Settings.SRTC)
{
S9xUpdateSrtcTime();
int32_t s = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0;
if (s > 0x20000)
s = 0x20000;
Memory.SRAM [s + 0] = rtc.needs_init;
Memory.SRAM [s + 1] = rtc.count_enable;
// memmove converted: Different mallocs [Neb]
memcpy(&Memory.SRAM [s + 2], rtc.data, MAX_RTC_INDEX + 1);
Memory.SRAM [s + 3 + MAX_RTC_INDEX] = rtc.index;
Memory.SRAM [s + 4 + MAX_RTC_INDEX] = rtc.mode;
#ifdef MSB_FIRST
Memory.SRAM [s + 5 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 0);
Memory.SRAM [s + 6 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 8);
Memory.SRAM [s + 7 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 16);
Memory.SRAM [s + 8 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 24);
Memory.SRAM [s + 9 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 32);
Memory.SRAM [s + 10 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 40);
Memory.SRAM [s + 11 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 48);
Memory.SRAM [s + 12 + MAX_RTC_INDEX] = (uint8_t)(rtc.system_timestamp >> 56);
#else
// memmove converted: Different mallocs [Neb]
memcpy(&Memory.SRAM [s + 5 + MAX_RTC_INDEX], &rtc.system_timestamp, 8);
#endif
}
}
void S9xSRTCPostLoadState()
{
if (Settings.SRTC)
{
int32_t s = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0;
if (s > 0x20000)
s = 0x20000;
rtc.needs_init = Memory.SRAM [s + 0];
rtc.count_enable = Memory.SRAM [s + 1];
// memmove converted: Different mallocs [Neb]
memcpy(rtc.data, &Memory.SRAM [s + 2], MAX_RTC_INDEX + 1);
rtc.index = Memory.SRAM [s + 3 + MAX_RTC_INDEX];
rtc.mode = Memory.SRAM [s + 4 + MAX_RTC_INDEX];
#ifdef MSB_FIRST
rtc.system_timestamp |= (Memory.SRAM [s + 5 + MAX_RTC_INDEX] << 0);
rtc.system_timestamp |= (Memory.SRAM [s + 6 + MAX_RTC_INDEX] << 8);
rtc.system_timestamp |= (Memory.SRAM [s + 7 + MAX_RTC_INDEX] << 16);
rtc.system_timestamp |= (Memory.SRAM [s + 8 + MAX_RTC_INDEX] << 24);
rtc.system_timestamp |= (Memory.SRAM [s + 9 + MAX_RTC_INDEX] << 32);
rtc.system_timestamp |= (Memory.SRAM [s + 10 + MAX_RTC_INDEX] << 40);
rtc.system_timestamp |= (Memory.SRAM [s + 11 + MAX_RTC_INDEX] << 48);
rtc.system_timestamp |= (Memory.SRAM [s + 12 + MAX_RTC_INDEX] << 56);
#else
// memmove converted: Different mallocs [Neb]
memcpy(&rtc.system_timestamp, &Memory.SRAM [s + 5 + MAX_RTC_INDEX], 8);
#endif
S9xUpdateSrtcTime();
}
}