/* * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. * * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and * Jerremy Koot (jkoot@snes9x.com) * * Super FX C emulator code * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and * Gary Henderson. * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_. * * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson. * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_. * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com). * * (c) Copyright 2014 - 2016 Daniel De Matteis. (UNDER NO CIRCUMSTANCE * WILL COMMERCIAL RIGHTS EVER BE APPROPRIATED TO ANY PARTY) * * DOS port code contains the works of other authors. See headers in * individual files. * * Snes9x homepage: http://www.snes9x.com * * Permission to use, copy, modify and distribute Snes9x in both binary and * source form, for non-commercial purposes, is hereby granted without fee, * providing that this license information and copyright notice appear with * all copies and any derived work. * * This software is provided 'as-is', without any express or implied * warranty. In no event shall the authors be held liable for any damages * arising from the use of this software. * * Snes9x is freeware for PERSONAL USE only. Commercial users should * seek permission of the copyright holders first. Commercial use includes * charging money for Snes9x or software derived from Snes9x. * * The copyright holders request that bug fixes and improvements to the code * should be forwarded to them so everyone can benefit from the modifications * in future versions. * * Super NES and Super Nintendo Entertainment System are trademarks of * Nintendo Co., Limited and its subsidiary companies. */ #include #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 int 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 */ /**********************************************************************************************/ unsigned int S9xSRTCComputeDayOfWeek() { unsigned year = rtc.data[10] * 10 + rtc.data[9]; unsigned month = rtc.data[8]; unsigned day = rtc.data[7] * 10 + rtc.data[6]; unsigned 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 */ /**********************************************************************************************/ int S9xSRTCDaysInMmonth(int month, int year) { int 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; long 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 = (long)(cur_systime - rtc.system_timestamp); rtc.system_timestamp = cur_systime; if (time_diff > 0) { int seconds; int minutes; int hours; int days; int month; int year; int temp_days; int year_hundreds; int year_tens; int 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 data, uint16 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 S9xGetSRTC(uint16 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) { int s; S9xUpdateSrtcTime(); s = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0; if (s > 0x20000) s = 0x20000; SRAM [s + 0] = rtc.needs_init; SRAM [s + 1] = rtc.count_enable; memmove(&SRAM [s + 2], rtc.data, MAX_RTC_INDEX + 1); SRAM [s + 3 + MAX_RTC_INDEX] = rtc.index; SRAM [s + 4 + MAX_RTC_INDEX] = rtc.mode; #ifdef MSB_FIRST SRAM [s + 5 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 0); SRAM [s + 6 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 8); SRAM [s + 7 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 16); SRAM [s + 8 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 24); SRAM [s + 9 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 32); SRAM [s + 10 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 40); SRAM [s + 11 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 48); SRAM [s + 12 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 56); #else memmove(&SRAM [s + 5 + MAX_RTC_INDEX], &rtc.system_timestamp, 8); #endif } } void S9xSRTCPostLoadState() { if (Settings.SRTC) { int s = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0; if (s > 0x20000) s = 0x20000; rtc.needs_init = SRAM [s + 0]; rtc.count_enable = SRAM [s + 1]; memmove(rtc.data, &SRAM [s + 2], MAX_RTC_INDEX + 1); rtc.index = SRAM [s + 3 + MAX_RTC_INDEX]; rtc.mode = SRAM [s + 4 + MAX_RTC_INDEX]; #ifdef MSB_FIRST rtc.system_timestamp |= (SRAM [s + 5 + MAX_RTC_INDEX] << 0); rtc.system_timestamp |= (SRAM [s + 6 + MAX_RTC_INDEX] << 8); rtc.system_timestamp |= (SRAM [s + 7 + MAX_RTC_INDEX] << 16); rtc.system_timestamp |= (SRAM [s + 8 + MAX_RTC_INDEX] << 24); rtc.system_timestamp |= (SRAM [s + 9 + MAX_RTC_INDEX] << 32); rtc.system_timestamp |= (SRAM [s + 10 + MAX_RTC_INDEX] << 40); rtc.system_timestamp |= (SRAM [s + 11 + MAX_RTC_INDEX] << 48); rtc.system_timestamp |= (SRAM [s + 12 + MAX_RTC_INDEX] << 56); #else memmove(&rtc.system_timestamp, &SRAM [s + 5 + MAX_RTC_INDEX], 8); #endif S9xUpdateSrtcTime(); } }