#include "../copyright"

#include "spc7110.h"
#include "spc7110dec.h"
#include "memmap.h"
#include <time.h>

const char* S9xGetFilename(const char*);

SPC7110Regs s7r;         /* SPC7110 registers, about 33KB */
S7RTC rtc_f9;            /* FEOEZ (and Shounen Jump no SHou) RTC */
void S9xUpdateRTC(void); /* S-RTC function hacked to work with the RTC */

void S9xSpc7110Init(void) /* Emulate power on state */
{
   spc7110dec_init();
   s7r.DataRomOffset = 0x00100000; /* handy constant! */
   s7r.DataRomSize = Memory.CalculatedSize - s7r.DataRomOffset;
   s7r.reg4800 = 0;
   s7r.reg4801 = 0;
   s7r.reg4802 = 0;
   s7r.reg4803 = 0;
   s7r.reg4804 = 0;
   s7r.reg4805 = 0;
   s7r.reg4806 = 0;
   s7r.reg4807 = 0;
   s7r.reg4808 = 0;
   s7r.reg4809 = 0;
   s7r.reg480A = 0;
   s7r.reg480B = 0;
   s7r.reg480C = 0;
   s7r.reg4811 = 0;
   s7r.reg4812 = 0;
   s7r.reg4813 = 0;
   s7r.reg4814 = 0;
   s7r.reg4815 = 0;
   s7r.reg4816 = 0;
   s7r.reg4817 = 0;
   s7r.reg4818 = 0;
   s7r.reg4820 = 0;
   s7r.reg4821 = 0;
   s7r.reg4822 = 0;
   s7r.reg4823 = 0;
   s7r.reg4824 = 0;
   s7r.reg4825 = 0;
   s7r.reg4826 = 0;
   s7r.reg4827 = 0;
   s7r.reg4828 = 0;
   s7r.reg4829 = 0;
   s7r.reg482A = 0;
   s7r.reg482B = 0;
   s7r.reg482C = 0;
   s7r.reg482D = 0;
   s7r.reg482E = 0;
   s7r.reg482F = 0;
   s7r.reg4830 = 0;
   s7r.reg4831 = 0;
   s7r.reg4832 = 1;
   s7r.reg4833 = 2;
   s7r.reg4834 = 0;
   s7r.reg4840 = 0;
   s7r.reg4841 = 0;
   s7r.reg4842 = 0;
   s7r.written = 0;
   s7r.offset_add = 0;
   s7r.AlignBy = 1;
   s7r.bank50Internal = 0;
   memset(s7r.bank50, 0x00, DECOMP_BUFFER_SIZE);
}

/* reads SPC7110 and RTC registers. */
uint8_t S9xGetSPC7110(uint16_t Address)
{
   switch (Address)
   {
   /* decompressed data read port. decrements 4809-A (with wrap) */
   /* 4805-6 is the offset into the bank */
   /* AlignBy is set (afaik) at decompression time, and is the offset multiplier */
   /* bank50internal is an internal pointer to the actual byte to read. */
   /* so you read from offset*multiplier + bank50internal */
   /* the offset registers cannot be incremented due to the offset multiplier. */
   case 0x4800:
   {
      uint16_t count = s7r.reg4809 | (s7r.reg480A << 8);
      if (count > 0)
         count--;
      else
         count = 0xFFFF;
      s7r.reg4809 = 0x00ff & count;
      s7r.reg480A = (0xff00 & count) >> 8;
      s7r.reg4800 = spc7110dec_read();
      return s7r.reg4800;
   }
   case 0x4801: /* table register low */
      return s7r.reg4801;
   case 0x4802: /* table register middle */
      return s7r.reg4802;
   case 0x4803: /* table register high */
      return s7r.reg4803;
   case 0x4804: /* index of pointer in table (each entry is 4 bytes) */
      return s7r.reg4804;
   case 0x4805: /* offset register low */
      return s7r.reg4805;
   case 0x4806: /* offset register high */
      return s7r.reg4806;
   /* DMA channel (not that I see this usually set, */
   /* regardless of what channel DMA is on) */
   case 0x4807:
      return s7r.reg4807;
   /* C r/w option, unknown, defval:00 is what Dark Force says */
   /* afaict, Snes9x doesn't use this at all. */
   case 0x4808:
      return s7r.reg4808;
   /* C-Length low */
   /* counts down the number of bytes left to read from the decompression buffer. */
   /* this is set by the ROM, and wraps on bounds. */
   case 0x4809:
      return s7r.reg4809;
   case 0x480A: /* C Length high */
      return s7r.reg480A;
   /* Offset enable. */
   /* if this is zero, 4805-6 are useless. Emulated by setting AlignBy to 0 */
   case 0x480B:
      return s7r.reg480B;
   case 0x480C:  /* decompression finished: just emulated by switching each read. */
      s7r.reg480C ^= 0x80;
      return s7r.reg480C ^ 0x80;
   /* Data access port */
   /* reads from the data ROM (anywhere over the first 8 mbits */
   /* behavior is complex, will document later, */
   /* possibly missing cases, because of the number of switches in play */
   case 0x4810:
      if (s7r.written == 0)
         return 0;
      if ((s7r.written & 0x07) == 0x07)
      {
         uint8_t tmp;
         uint32_t i = (s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811;
         i %= s7r.DataRomSize;
         if (s7r.reg4818 & 0x02)
         {
            if (s7r.reg4818 & 0x08)
            {
               int16_t r4814;
               r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
               i += r4814;
               r4814++;
               s7r.reg4815 = (uint8_t)(r4814 >> 8);
               s7r.reg4814 = (uint8_t)(r4814 & 0x00FF);
            }
            else
            {
               uint16_t r4814;
               r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
               i += r4814;
               if (r4814 != 0xFFFF)
                  r4814++;
               else
                  r4814 = 0;
               s7r.reg4815 = (uint8_t)(r4814 >> 8);
               s7r.reg4814 = (uint8_t)(r4814 & 0x00FF);

            }
         }
         i += s7r.DataRomOffset;
         tmp = Memory.ROM[i];
         i   = (s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811;

         if (s7r.reg4818 & 0x02)
         {
         }
         else if (s7r.reg4818 & 0x01)
         {
            if (s7r.reg4818 & 0x04)
            {
               int16_t inc = (s7r.reg4817 << 8) | s7r.reg4816;

               if (!(s7r.reg4818 & 0x10))
                  i += inc;
               else
               {
                  if (s7r.reg4818 & 0x08)
                  {
                     int16_t r4814;
                     r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
                     r4814 += inc;
                     s7r.reg4815 = (r4814 & 0xFF00) >> 8;
                     s7r.reg4814 = r4814 & 0xFF;
                  }
                  else
                  {
                     uint16_t r4814;
                     r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
                     r4814 += inc;
                     s7r.reg4815 = (r4814 & 0xFF00) >> 8;
                     s7r.reg4814 = r4814 & 0xFF;

                  }
               }
               /* is signed */
            }
            else
            {
               uint16_t inc;
               inc = (s7r.reg4817 << 8) | s7r.reg4816;
               if (!(s7r.reg4818 & 0x10))
                  i += inc;
               else
               {
                  if (s7r.reg4818 & 0x08)
                  {
                     int16_t r4814;
                     r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
                     r4814 += inc;
                     s7r.reg4815 = (r4814 & 0xFF00) >> 8;
                     s7r.reg4814 = r4814 & 0xFF;
                  }
                  else
                  {
                     uint16_t r4814;
                     r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
                     r4814 += inc;
                     s7r.reg4815 = (r4814 & 0xFF00) >> 8;
                     s7r.reg4814 = r4814 & 0xFF;

                  }
               }
            }
         }
         else
         {
            if (!(s7r.reg4818 & 0x10))
               i += 1;
            else
            {
               if (s7r.reg4818 & 0x08)
               {
                  int16_t r4814;
                  r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
                  r4814 += 1;
                  s7r.reg4815 = (r4814 & 0xFF00) >> 8;
                  s7r.reg4814 = r4814 & 0xFF;
               }
               else
               {
                  uint16_t r4814;
                  r4814 = (s7r.reg4815 << 8) | s7r.reg4814;
                  r4814 += 1;
                  s7r.reg4815 = (r4814 & 0xFF00) >> 8;
                  s7r.reg4814 = r4814 & 0xFF;

               }
            }
         }

         i %= s7r.DataRomSize;
         s7r.reg4811 = i & 0x00FF;
         s7r.reg4812 = (i & 0x00FF00) >> 8;
         s7r.reg4813 = ((i & 0xFF0000) >> 16);
         return tmp;
      }
      else
         return 0;
   case 0x4811: /* direct read address low */
      return s7r.reg4811;
   case 0x4812: /* direct read address middle */
      return s7r.reg4812;
   case 0x4813: /* direct read access high */
      return s7r.reg4813;
   case 0x4814: /* read adjust low */
      return s7r.reg4814;
   case 0x4815: /* read adjust high */
      return s7r.reg4815;
   case 0x4816: /* read increment low */
      return s7r.reg4816;
   case 0x4817: /* read increment high */
      return s7r.reg4817;
   case 0x4818: /* Data ROM command mode; essentially, this controls the insane code of $4810 and $481A */
      return s7r.reg4818;
   /* read after adjust port */
   /* what this does, besides more nasty stuff like 4810, */
   /* I don't know. Just assume it is a different implementation of $4810, */
   /* if it helps your sanity */
   case 0x481A:
      if (s7r.written == 0x1F)
      {
         uint8_t tmp;
         uint32_t i = ((s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811);
         if (s7r.reg4818 & 0x08)
            i += ((int16_t)(s7r.reg4815 << 8)) | s7r.reg4814;
         else
            i += (s7r.reg4815 << 8) | s7r.reg4814;

         i %= s7r.DataRomSize;
         i += s7r.DataRomOffset;
         tmp = Memory.ROM[i];
         if ((s7r.reg4818 & 0x60) == 0x60)
         {
            i = ((s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811);

            if (!(s7r.reg4818 & 0x10))
            {
               if (s7r.reg4818 & 0x08)
               {
                  int16_t adj = ((int16_t)(s7r.reg4815 << 8)) | s7r.reg4814;
                  i += adj;
               }
               else
               {
                  uint16_t adj;
                  adj = (s7r.reg4815 << 8) | s7r.reg4814;
                  i += adj;
               }
               i %= s7r.DataRomSize;
               s7r.reg4811 = i & 0x00FF;
               s7r.reg4812 = (i & 0x00FF00) >> 8;
               s7r.reg4813 = ((i & 0xFF0000) >> 16);
            }
            else
            {
               if (s7r.reg4818 & 0x08)
               {
                  int16_t adj = ((int16_t)(s7r.reg4815 << 8)) | s7r.reg4814;
                  adj += adj;
                  s7r.reg4815 = (adj & 0xFF00) >> 8;
                  s7r.reg4814 = adj & 0xFF;
               }
               else
               {
                  uint16_t adj;
                  adj = (s7r.reg4815 << 8) | s7r.reg4814;
                  adj += adj;
                  s7r.reg4815 = (adj & 0xFF00) >> 8;
                  s7r.reg4814 = adj & 0xFF;
               }
            }
         }
         return tmp;
      }
      else
         return 0;
   case 0x4820: /* multiplicand low or dividend lowest */
      return s7r.reg4820;
   case 0x4821: /* multiplicand high or divdend lower */
      return s7r.reg4821;
   case 0x4822: /* dividend higher */
      return s7r.reg4822;
   case 0x4823: /* dividend highest */
      return s7r.reg4823;
   case 0x4824: /* multiplier low */
      return s7r.reg4824;
   case 0x4825: /* multiplier high */
      return s7r.reg4825;
   case 0x4826: /* divisor low */
      return s7r.reg4826;
   case 0x4827: /* divisor high */
      return s7r.reg4827;
   case 0x4828: /* result lowest */
      return s7r.reg4828;
   case 0x4829: /* result lower */
      return s7r.reg4829;
   case 0x482A: /* result higher */
      return s7r.reg482A;
   case 0x482B: /* result highest */
      return s7r.reg482B;
   case 0x482C: /* remainder (division) low */
      return s7r.reg482C;
   case 0x482D: /* remainder (division) high */
      return s7r.reg482D;
   case 0x482E: /* signed/unsigned */
      return s7r.reg482E;
   case 0x482F: /* finished flag, emulated as an on-read toggle. */
      if (s7r.reg482F)
      {
         s7r.reg482F = 0;
         return 0x80;
      }
      return 0;
   case 0x4830: /* SRAM toggle */
      return s7r.reg4830;
   case 0x4831: /* DX bank mapping */
      return s7r.reg4831;
   case 0x4832: /* EX bank mapping */
      return s7r.reg4832;
   case 0x4833: /* FX bank mapping */
      return s7r.reg4833;
   case 0x4834: /* SRAM mapping? We have no clue! */
      return s7r.reg4834;
   case 0x4840: /* RTC enable */
      if (!Settings.SPC7110RTC)
         return Address >> 8;
      return s7r.reg4840;
   case 0x4841: /* command/index/value of RTC (essentially, zero unless we're in read mode */
      if (!Settings.SPC7110RTC)
         return Address >> 8;
      if (rtc_f9.init)
      {
         uint8_t tmp;

         S9xUpdateRTC();
         tmp = rtc_f9.reg[rtc_f9.index];
         rtc_f9.index++;
         rtc_f9.index %= 0x10;
         return tmp;
      }
      else
         return 0;
   case 0x4842: /* RTC done flag */
      if (!Settings.SPC7110RTC)
         return Address >> 8;
      s7r.reg4842 ^= 0x80;
      return s7r.reg4842 ^ 0x80;
   default:
      return 0x00;
   }
}

static uint32_t datarom_addr(uint32_t addr)
{
   uint32_t size = Memory.CalculatedSize - 0x100000;

   while(addr >= size)
      addr -= size;
   return addr + 0x100000;
}

void S9xSetSPC7110(uint8_t data, uint16_t Address)
{
   switch (Address)
   {
   /* Writes to $4800 are undefined. */
   case 0x4801: /* table low, middle, and high. */
      s7r.reg4801 = data;
      break;
   case 0x4802:
      s7r.reg4802 = data;
      break;
   case 0x4803:
      s7r.reg4803 = data;
      break;
   case 0x4804: /* table index (4 byte entries, bigendian with a multiplier byte) */
      s7r.reg4804 = data;
      break;
   case 0x4805: /* offset low */
      s7r.reg4805 = data;
      break;
   case 0x4806: /* offset high, starts decompression */
   {
      uint32_t table = (s7r.reg4801 + (s7r.reg4802 << 8) + (s7r.reg4803 << 16));
      uint32_t index = (s7r.reg4804 << 2);
      uint32_t addr = datarom_addr(table + index);
      uint32_t mode = (Memory.ROM[addr + 0]);
      uint32_t offset = (Memory.ROM[addr + 1] << 16) + (Memory.ROM[addr + 2] << 8) + (Memory.ROM[addr + 3]);
      s7r.reg4806 = data;
      spc7110dec_clear(mode, offset, (s7r.reg4805 + (s7r.reg4806 << 8)) << mode);
      s7r.bank50Internal = 0;
      s7r.reg480C &= 0x7F;
      break;
   }
   case 0x4807: /* DMA channel register (Is it used??) */
      s7r.reg4807 = data;
      break;
   /* C r/w? I have no idea. If you get weird values written here before a bug, */
   /* The Dumper should probably be contacted about running a test. */
   case 0x4808:
      s7r.reg4808 = data;
      break;
   case 0x4809: /* C-Length low */
      s7r.reg4809 = data;
      break;
   case 0x480A: /* C-Length high */
      s7r.reg480A = data;
      break;
   case 0x480B: /* Offset enable */
   {
      int32_t table;
      int32_t j;

      s7r.reg480B = data;
      table = (s7r.reg4803 << 16) | (s7r.reg4802 << 8) | s7r.reg4801;
      j = 4 * s7r.reg4804 + s7r.DataRomOffset + table;

      if (s7r.reg480B == 0)
         s7r.AlignBy = 0;
      else
      {
         switch (Memory.ROM[j])
         {
         case 0x03:
            s7r.AlignBy = 8;
            break;
         case 0x01:
            s7r.AlignBy = 2;
            break;
         case 0x02:
            s7r.AlignBy = 4;
            break;
         case 0x00:
         default:
            s7r.AlignBy = 1;
            break;
         }
      }
      break;
   }
   case 0x4811: /* Data port address low */
      s7r.reg4811 = data;
      s7r.written |= 0x01;
      break;
   case 0x4812: /* data port address middle */
      s7r.reg4812 = data;
      s7r.written |= 0x02;
      break;
   case 0x4813: /* data port address high */
      s7r.reg4813 = data;
      s7r.written |= 0x04;
      break;
   case 0x4814: /* data port adjust low (has a funky immediate increment mode) */
      s7r.reg4814 = data;
      if (s7r.reg4818 & 0x02)
      {
         if ((s7r.reg4818 & 0x20) && !(s7r.reg4818 & 0x40))
         {
            s7r.offset_add |= 0x01;
            if (s7r.offset_add == 3)
            {
               if(!(s7r.reg4818 & 0x10))
               {
                  uint32_t i = (s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811;
                  if (s7r.reg4818 & 0x08)
                     i += (int8_t)s7r.reg4814;
                  else
                     i += s7r.reg4814;
                  i %= s7r.DataRomSize;
                  s7r.reg4811 = i & 0x00FF;
                  s7r.reg4812 = (i & 0x00FF00) >> 8;
                  s7r.reg4813 = ((i & 0xFF0000) >> 16);
               }
            }
         }
         else if ((s7r.reg4818 & 0x40) && !(s7r.reg4818 & 0x20))
         {
            s7r.offset_add |= 0x01;
            if (s7r.offset_add == 3)
            {
               if(!(s7r.reg4818 & 0x10))
               {
                  uint32_t i = (s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811;
                  if (s7r.reg4818 & 0x08)
                     i += ((int16_t)(s7r.reg4815 << 8)) | s7r.reg4814;
                  else
                     i += (s7r.reg4815 << 8) | s7r.reg4814;
                  i %= s7r.DataRomSize;
                  s7r.reg4811 = i & 0x00FF;
                  s7r.reg4812 = (i & 0x00FF00) >> 8;
                  s7r.reg4813 = ((i & 0xFF0000) >> 16);
               }
            }
         }
      }

      s7r.written |= 0x08;
      break;
   case 0x4815: /* data port adjust high (has a funky immediate increment mode) */
      s7r.reg4815 = data;
      if (s7r.reg4818 & 0x02)
      {
         if (s7r.reg4818 & 0x20 && !(s7r.reg4818 & 0x40))
         {
            s7r.offset_add |= 0x02;
            if (s7r.offset_add == 3)
            {
               if(!(s7r.reg4818 & 0x10))
               {
                  uint32_t i = (s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811;

                  if (s7r.reg4818 & 0x08)
                     i += (int8_t)s7r.reg4814;
                  else
                     i += s7r.reg4814;
                  i %= s7r.DataRomSize;
                  s7r.reg4811 = i & 0x00FF;
                  s7r.reg4812 = (i & 0x00FF00) >> 8;
                  s7r.reg4813 = ((i & 0xFF0000) >> 16);
               }
            }
         }
         else if (s7r.reg4818 & 0x40 && !(s7r.reg4818 & 0x20))
         {
            s7r.offset_add |= 0x02;
            if (s7r.offset_add == 3)
            {
               if(!(s7r.reg4818 & 0x10))
               {
                  uint32_t i = (s7r.reg4813 << 16) | (s7r.reg4812 << 8) | s7r.reg4811;
                  if (s7r.reg4818 & 0x08)
                     i += ((int16_t)(s7r.reg4815 << 8)) | s7r.reg4814;
                  else
                     i += (s7r.reg4815 << 8) | s7r.reg4814;
                  i %= s7r.DataRomSize;
                  s7r.reg4811 = i & 0x00FF;
                  s7r.reg4812 = (i & 0x00FF00) >> 8;
                  s7r.reg4813 = ((i & 0xFF0000) >> 16);
               }
            }
         }
      }
      s7r.written |= 0x10;
      break;
   case 0x4816: /* data port increment low */
      s7r.reg4816 = data;
      break;
   case 0x4817: /* data port increment high */
      s7r.reg4817 = data;
      break;
   /* data port mode switches */
   /* note that it starts inactive. */
   case 0x4818:
      if ((s7r.written & 0x18) != 0x18)
         break;
      s7r.offset_add = 0;
      s7r.reg4818 = data;
      break;
   case 0x4820: /* multiplicand low or dividend lowest */
      s7r.reg4820 = data;
      break;
   case 0x4821: /* multiplicand high or dividend lower */
      s7r.reg4821 = data;
      break;
   case 0x4822: /* dividend higher */
      s7r.reg4822 = data;
      break;
   case 0x4823: /* dividend highest */
      s7r.reg4823 = data;
      break;
   case 0x4824: /* multiplier low */
      s7r.reg4824 = data;
      break;
   case 0x4825: /* multiplier high (triggers operation) */
      s7r.reg4825 = data;
      if (s7r.reg482E & 0x01)
      {
         int16_t m1 = (int16_t)((s7r.reg4824) | (s7r.reg4825 << 8));
         int16_t m2 = (int16_t)((s7r.reg4820) | (s7r.reg4821 << 8));
         int32_t mul = m1 * m2;
         s7r.reg4828 = (uint8_t)(mul & 0x000000FF);
         s7r.reg4829 = (uint8_t)((mul & 0x0000FF00) >> 8);
         s7r.reg482A = (uint8_t)((mul & 0x00FF0000) >> 16);
         s7r.reg482B = (uint8_t)((mul & 0xFF000000) >> 24);
      }
      else
      {
         uint16_t m1 = (uint16_t)((s7r.reg4824) | (s7r.reg4825 << 8));
         uint16_t m2 = (uint16_t)((s7r.reg4820) | (s7r.reg4821 << 8));
         uint32_t mul = m1 * m2;
         s7r.reg4828 = (uint8_t)(mul & 0x000000FF);
         s7r.reg4829 = (uint8_t)((mul & 0x0000FF00) >> 8);
         s7r.reg482A = (uint8_t)((mul & 0x00FF0000) >> 16);
         s7r.reg482B = (uint8_t)((mul & 0xFF000000) >> 24);
      }
      s7r.reg482F = 0x80;
      break;
   case 0x4826: /* divisor low */
      s7r.reg4826 = data;
      break;
   case 0x4827: /* divisor high (triggers operation) */
      s7r.reg4827 = data;
      if (s7r.reg482E & 0x01)
      {
         int32_t quotient;
         int16_t remainder;
         int32_t dividend = (int32_t)(s7r.reg4820 | (s7r.reg4821 << 8) | (s7r.reg4822 << 16) | (s7r.reg4823 << 24));
         int16_t divisor = (int16_t)(s7r.reg4826 | (s7r.reg4827 << 8));
         if (divisor != 0)
         {
            quotient = (int32_t)(dividend / divisor);
            remainder = (int16_t)(dividend % divisor);
         }
         else
         {
            quotient = 0;
            remainder = dividend & 0x0000FFFF;
         }
         s7r.reg4828 = (uint8_t)(quotient & 0x000000FF);
         s7r.reg4829 = (uint8_t)((quotient & 0x0000FF00) >> 8);
         s7r.reg482A = (uint8_t)((quotient & 0x00FF0000) >> 16);
         s7r.reg482B = (uint8_t)((quotient & 0xFF000000) >> 24);
         s7r.reg482C = (uint8_t)remainder & 0x00FF;
         s7r.reg482D = (uint8_t)((remainder & 0xFF00) >> 8);
      }
      else
      {
         uint32_t quotient;
         uint16_t remainder;
         uint32_t dividend = (uint32_t)(s7r.reg4820 | (s7r.reg4821 << 8) | (s7r.reg4822 << 16) | (s7r.reg4823 << 24));
         uint16_t divisor = (uint16_t)(s7r.reg4826 | (s7r.reg4827 << 8));
         if (divisor != 0)
         {
            quotient = (uint32_t)(dividend / divisor);
            remainder = (uint16_t)(dividend % divisor);
         }
         else
         {
            quotient = 0;
            remainder = dividend & 0x0000FFFF;
         }
         s7r.reg4828 = (uint8_t)(quotient & 0x000000FF);
         s7r.reg4829 = (uint8_t)((quotient & 0x0000FF00) >> 8);
         s7r.reg482A = (uint8_t)((quotient & 0x00FF0000) >> 16);
         s7r.reg482B = (uint8_t)((quotient & 0xFF000000) >> 24);
         s7r.reg482C = (uint8_t)remainder & 0x00FF;
         s7r.reg482D = (uint8_t)((remainder & 0xFF00) >> 8);
      }
      s7r.reg482F = 0x80;
      break;
   /* result registers are possibly read-only */

   /* reset: writes here nuke the whole math unit */
   /* Zero indicates unsigned math, resets with non-zero values turn on signed math */
   case 0x482E:
      s7r.reg4820 = s7r.reg4821 = s7r.reg4822 = s7r.reg4823 = s7r.reg4824 = s7r.reg4825 = s7r.reg4826 = s7r.reg4827 = s7r.reg4828 = s7r.reg4829 = s7r.reg482A = s7r.reg482B = s7r.reg482C = s7r.reg482D = 0;
      s7r.reg482E = data;
      break;
      /* math status register possibly read only */
   case 0x4830: /* SRAM toggle */
      SPC7110Sram(data);
      s7r.reg4830 = data;
      break;
   case 0x4831: /* Bank DX mapping */
      s7r.reg4831 = data;
      break;
   case 0x4832: /* Bank EX mapping */
      s7r.reg4832 = data;
      break;
   case 0x4833: /* Bank FX mapping */
      s7r.reg4833 = data;
      break;
   case 0x4834: /* S-RAM mapping? who knows? */
      s7r.reg4834 = data;
      break;
   case 0x4840: /* RTC Toggle */
      if(!data)
         S9xUpdateRTC();
      else if(data & 0x01)
      {
         s7r.reg4842 = 0x80;
         rtc_f9.init = false;
         rtc_f9.index = -1;
      }
      s7r.reg4840 = data;
      break;
   case 0x4841: /* RTC init/command/index register */
      if (rtc_f9.init)
      {
         if (rtc_f9.index == -1)
         {
            rtc_f9.index = data & 0x0F;
            break;
         }
         if (rtc_f9.control == 0x0C)
         {
            rtc_f9.index = data & 0x0F;
            s7r.reg4842 = 0x80;
            rtc_f9.last_used = time(NULL);
         }
         else
         {
            if (rtc_f9.index == 0x0D)
            {
               if (data & 0x08)
               {
                  if (rtc_f9.reg[1] < 3)
                  {
                     S9xUpdateRTC();
                     rtc_f9.reg[0] = 0;
                     rtc_f9.reg[1] = 0;
                     rtc_f9.last_used = time(NULL);
                  }
                  else
                  {
                     S9xUpdateRTC();
                     rtc_f9.reg[0] = 0;
                     rtc_f9.reg[1] = 0;
                     rtc_f9.last_used = time(NULL) - 60;
                     S9xUpdateRTC();
                     rtc_f9.last_used = time(NULL);
                  }
                  data &= 0x07;
               }
               if (rtc_f9.reg[0x0D] & 0x01)
               {
                  if (!(data % 2))
                  {
                     rtc_f9.reg[rtc_f9.index & 0x0F] = data;
                     rtc_f9.last_used = time(NULL) - 1;
                     S9xUpdateRTC();
                     rtc_f9.last_used = time(NULL);
                  }
               }
            }
            if (rtc_f9.index == 0x0F)
            {
               if (data & 0x01 && !(rtc_f9.reg[0x0F] & 0x01))
               {
                  S9xUpdateRTC();
                  rtc_f9.reg[0] = 0;
                  rtc_f9.reg[1] = 0;
                  rtc_f9.last_used = time(NULL);
               }
               if (data & 0x02 && !(rtc_f9.reg[0x0F] & 0x02))
               {
                  S9xUpdateRTC();
                  rtc_f9.last_used = time(NULL);
               }
            }
            rtc_f9.reg[rtc_f9.index & 0x0F] = data;
            s7r.reg4842 = 0x80;
            rtc_f9.index = (rtc_f9.index + 1) % 0x10;
         }
      }
      else
      {
         if (data == 0x03 || data == 0x0C)
         {
            rtc_f9.init = true;
            rtc_f9.control = data;
            rtc_f9.index = -1;
         }
      }
      break;
   }
}

/* emulate the SPC7110's ability to remap banks Dx, Ex, and Fx. */
uint8_t S9xGetSPC7110Byte(uint32_t Address)
{
   uint32_t i;
   switch ((Address & 0x00F00000) >> 16)
   {
   case 0xD0:
      i = s7r.reg4831 * 0x00100000;
      break;
   case 0xE0:
      i = s7r.reg4832 * 0x00100000;
      break;
   case 0xF0:
      i = s7r.reg4833 * 0x00100000;
      break;
   default:
      i = 0;
   }
   i += Address & 0x000FFFFF;
   i += s7r.DataRomOffset;
   return Memory.ROM[i];
}

/**********************************************************************************************/
/* S9xSRTCDaysInMonth()                                                                       */
/* Return the number of days in a specific month for a certain year                           */
/* copied directly for RTC functionality, separated in case of incompatibilities         */
/**********************************************************************************************/
int32_t S9xRTCDaysInMonth(int32_t month, int32_t year)
{
   switch(month)
   {
   case 2:
      if(year % 4 == 0) /* DKJM2 only uses 199x - 22xx */
         return 29;
      return 28;
   case 4:
   case 6:
   case 9:
   case 11:
      return 30;
   default: /* months 1,3,5,7,8,10,12 */
      return 31;
   }
}

#define MINUTETICKS  60
#define HOURTICKS   (60 * MINUTETICKS)
#define DAYTICKS    (24 * HOURTICKS)

/**********************************************************************************************/
/* S9xUpdateRTC()                                                                   */
/* Advance the RTC time                                                                 */
/**********************************************************************************************/

void  S9xUpdateRTC(void)
{
   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 RTC clock structure. */

   if (rtc_f9.init && 0 == (rtc_f9.reg[0x0D] & 0x01) && 0 == (rtc_f9.reg[0x0F] & 0x03))
   {
      cur_systime = time(NULL);

      /* This method assumes one time_t clock tick is one second */
      time_diff = (int32_t)(cur_systime - rtc_f9.last_used);
      rtc_f9.last_used = 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_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_f9.reg[1] * 10 + rtc_f9.reg[0]);
         if (seconds >= 60)
         {
            seconds -= 60;
            minutes += 1;
         }

         minutes += (rtc_f9.reg[3] * 10 + rtc_f9.reg[2]);
         if (minutes >= 60)
         {
            minutes -= 60;
            hours += 1;
         }

         hours += (rtc_f9.reg[5] * 10 + rtc_f9.reg[4]);
         if (hours >= 24)
         {
            hours -= 24;
            days += 1;
         }

         year =  rtc_f9.reg[11] * 10 + rtc_f9.reg[10];
         year += (1900);
         month = rtc_f9.reg[8] + 10 * rtc_f9.reg[9];
         rtc_f9.reg[12] += days;
         days += (rtc_f9.reg[7] * 10 + rtc_f9.reg[6]);
         if (days > 0)
         {
            while (days > (temp_days = S9xRTCDaysInMonth(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;

         rtc_f9.reg[0] = seconds % 10;
         rtc_f9.reg[1] = seconds / 10;
         rtc_f9.reg[2] = minutes % 10;
         rtc_f9.reg[3] = minutes / 10;
         rtc_f9.reg[4] = hours % 10;
         rtc_f9.reg[5] = hours / 10;
         rtc_f9.reg[6] = days % 10;
         rtc_f9.reg[7] = days / 10;
         rtc_f9.reg[8] = month % 10;
         rtc_f9.reg[9] = month / 10;
         rtc_f9.reg[10] = year_ones;
         rtc_f9.reg[11] = year_tens;
         rtc_f9.reg[12] %= 7;
         return;
      }
   }
}

/* allows DMA from the ROM (is this even possible on the SPC7110? */
uint8_t* Get7110BasePtr(uint32_t Address)
{
   uint32_t i;
   switch ((Address & 0x00F00000) >> 16)
   {
   case 0xD0:
      i = s7r.reg4831 * 0x100000;
      break;
   case 0xE0:
      i = s7r.reg4832 * 0x100000;
      break;
   case 0xF0:
      i = s7r.reg4833 * 0x100000;
      break;
   default:
      i = 0;
   }
   i += Address & 0x000F0000;
   return &Memory.ROM[i];
}

/* Cache 1 clean up function */
void Del7110Gfx(void)
{
   spc7110dec_deinit();
   Settings.SPC7110 = false;
   Settings.SPC7110RTC = false;
}

/* emulate a reset. */
void S9xSpc7110Reset(void)
{
   s7r.reg4800 = 0;
   s7r.reg4801 = 0;
   s7r.reg4802 = 0;
   s7r.reg4803 = 0;
   s7r.reg4804 = 0;
   s7r.reg4805 = 0;
   s7r.reg4806 = 0;
   s7r.reg4807 = 0;
   s7r.reg4808 = 0;
   s7r.reg4809 = 0;
   s7r.reg480A = 0;
   s7r.reg480B = 0;
   s7r.reg480C = 0;
   s7r.reg4811 = 0;
   s7r.reg4812 = 0;
   s7r.reg4813 = 0;
   s7r.reg4814 = 0;
   s7r.reg4815 = 0;
   s7r.reg4816 = 0;
   s7r.reg4817 = 0;
   s7r.reg4818 = 0;
   s7r.reg4820 = 0;
   s7r.reg4821 = 0;
   s7r.reg4822 = 0;
   s7r.reg4823 = 0;
   s7r.reg4824 = 0;
   s7r.reg4825 = 0;
   s7r.reg4826 = 0;
   s7r.reg4827 = 0;
   s7r.reg4828 = 0;
   s7r.reg4829 = 0;
   s7r.reg482A = 0;
   s7r.reg482B = 0;
   s7r.reg482C = 0;
   s7r.reg482D = 0;
   s7r.reg482E = 0;
   s7r.reg482F = 0;
   s7r.reg4830 = 0;
   s7r.reg4831 = 0;
   s7r.reg4832 = 1;
   s7r.reg4833 = 2;
   s7r.reg4834 = 0;
   s7r.reg4840 = 0;
   s7r.reg4841 = 0;
   s7r.reg4842 = 0;
   s7r.written = 0;
   s7r.offset_add = 0;
   s7r.AlignBy = 1;
   s7r.bank50Internal = 0;
   memset(s7r.bank50, 0x00, DECOMP_BUFFER_SIZE);
   spc7110dec_reset();
}
