text: /*
   * 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).
   *
   * 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 <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 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 ()
  {
      ZeroMemory (&rtc, 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;
  
                  ZeroMemory (rtc.data, 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)
      {
  	S9xUpdateSrtcTime ();
  
  	int 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 LSB_FIRST
  	memmove (&SRAM [s + 5 + MAX_RTC_INDEX], &rtc.system_timestamp, 8);
  #else
  	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);
  #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 LSB_FIRST
  	memmove (&rtc.system_timestamp, &SRAM [s + 5 + MAX_RTC_INDEX], 8);
  #else
  	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);
  #endif
  	S9xUpdateSrtcTime ();
      }
  }
  