/*
 * 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 "snes9x.h"
#include "memmap.h"
#include "ppu.h"
#include "cpuexec.h"
#include "missing.h"
#include "apu.h"
#include "dma.h"
#include "gfx.h"
#include "display.h"
#include "sa1.h"
#include "sdd1.h"
#include "srtc.h"

#include "port.h"

#include "fxemu.h"
#include "fxinst.h"
extern FxInit_s SuperFX;
extern FxRegs_s GSU;
extern uint8* HDMAMemPointers [8];

void S9xUpdateHTimer()
{
   if (PPU.HTimerEnabled)
   {
#ifdef DEBUGGER
      missing.hirq_pos = PPU.IRQHBeamPos;
#endif
      PPU.HTimerPosition = PPU.IRQHBeamPos * Settings.H_Max / SNES_HCOUNTER_MAX;
      if (PPU.HTimerPosition == Settings.H_Max ||
            PPU.HTimerPosition == Settings.HBlankStart)
         PPU.HTimerPosition--;

      if (!PPU.VTimerEnabled || CPU.V_Counter == PPU.IRQVBeamPos)
      {
         if (PPU.HTimerPosition < CPU.Cycles)
         {
            // Missed the IRQ on this line already
            if (CPU.WhichEvent == HBLANK_END_EVENT ||
                  CPU.WhichEvent == HTIMER_AFTER_EVENT)
            {
               CPU.WhichEvent = HBLANK_END_EVENT;
               CPU.NextEvent = Settings.H_Max;
            }
            else
            {
               CPU.WhichEvent = HBLANK_START_EVENT;
               CPU.NextEvent = Settings.HBlankStart;
            }
         }
         else
         {
            if (CPU.WhichEvent == HTIMER_BEFORE_EVENT ||
                  CPU.WhichEvent == HBLANK_START_EVENT)
            {
               if (PPU.HTimerPosition > Settings.HBlankStart)
               {
                  // HTimer was to trigger before h-blank start,
                  // now triggers after start of h-blank
                  CPU.NextEvent = Settings.HBlankStart;
                  CPU.WhichEvent = HBLANK_START_EVENT;
               }
               else
               {
                  CPU.NextEvent = PPU.HTimerPosition;
                  CPU.WhichEvent = HTIMER_BEFORE_EVENT;
               }
            }
            else
            {
               CPU.WhichEvent = HTIMER_AFTER_EVENT;
               CPU.NextEvent = PPU.HTimerPosition;
            }
         }
      }
   }
}

void S9xFixColourBrightness()
{
   IPPU.XB = mul_brightness [PPU.Brightness];
   if (Settings.SixteenBit)
   {
      unsigned int i;
      for (i = 0; i < 256; i++)
      {
         //IPPU.Red [i] = IPPU.XB [PPU.CGDATA [i] & 0x1f];
         //IPPU.Green [i] = IPPU.XB [(PPU.CGDATA [i] >> 5) & 0x1f];
         //IPPU.Blue [i] = IPPU.XB [(PPU.CGDATA [i] >> 10) & 0x1f];
         IPPU.ScreenColors [i] = BUILD_PIXEL(IPPU.XB[IPPU.Red [i]], IPPU.XB[IPPU.Green [i]], IPPU.XB[IPPU.Blue [i]]);
      }
   }
}

/**********************************************************************************************/
/* S9xSetPPU()                                                                                   */
/* This function sets a PPU Register to a specific byte                                       */
/**********************************************************************************************/
#include "ppu_setppu.h"

/**********************************************************************************************/
/* S9xGetPPU()                                                                                   */
/* This function retrieves a PPU Register                                                     */
/**********************************************************************************************/
#include "ppu_getppu.h"

/**********************************************************************************************/
/* S9xSetCPU()                                                                                   */
/* This function sets a CPU/DMA Register to a specific byte                                   */
/**********************************************************************************************/
void S9xSetCPU(uint8 byte, uint16 Address)
{
   int d;

   if (Address < 0x4200)
   {
#ifdef VAR_CYCLES
      CPU.Cycles += ONE_CYCLE;
#endif
      switch (Address)
      {
      case 0x4016 :
         // S9xReset reading of old-style joypads
         if ((byte & 1) && !(Memory.FillRAM[Address] & 1))
         {
            PPU.Joypad1ButtonReadPos = 0;
            PPU.Joypad2ButtonReadPos = 0;
            PPU.Joypad3ButtonReadPos = 0;
         }
         break;
      case 0x4017 :
         break;
      default :
#ifdef DEBUGGER
         missing.unknowncpu_write = Address;
         if (Settings.TraceUnknownRegisters)
         {
            sprintf(String, "Unknown register register write: $%02X->$%04X\n", byte, Address);
            S9xMessage(S9X_TRACE, S9X_PPU_TRACE, String);
         }
#endif
         break;
      }
   }
   else
      switch (Address)
      {
      case 0x4200 :
         // NMI, V & H IRQ and joypad reading enable flags
         if (byte & 0x20)
         {
            if (!PPU.VTimerEnabled)
            {
#ifdef DEBUGGER
               missing.virq = 1;
               missing.virq_pos = PPU.IRQVBeamPos;
#endif
               PPU.VTimerEnabled = TRUE;
               if (PPU.HTimerEnabled)
                  S9xUpdateHTimer();
               else if (PPU.IRQVBeamPos == CPU.V_Counter)
                  S9xSetIRQ(PPU_V_BEAM_IRQ_SOURCE);
            }
         }
         else
            PPU.VTimerEnabled = FALSE;

         if (byte & 0x10)
         {
            if (!PPU.HTimerEnabled)
            {
#ifdef DEBUGGER
               missing.hirq = 1;
               missing.hirq_pos = PPU.IRQHBeamPos;
#endif
               PPU.HTimerEnabled = TRUE;
               S9xUpdateHTimer();
            }
         }
         else
         {
            // No need to check for HTimer being disabled as the scanline
            // event trigger code won't trigger an H-IRQ unless its enabled.
            PPU.HTimerEnabled = FALSE;
            PPU.HTimerPosition = Settings.H_Max + 1;
         }

#ifndef RC_OPTIMIZED
         if (!Settings.DaffyDuck)
            CLEAR_IRQ_SOURCE(PPU_V_BEAM_IRQ_SOURCE | PPU_H_BEAM_IRQ_SOURCE);

         if ((byte & 0x80)
               && !(Memory.FillRAM[0x4200] & 0x80)
               && CPU.V_Counter >= PPU.ScreenHeight + FIRST_VISIBLE_LINE
               && CPU.V_Counter <= PPU.ScreenHeight + (SNESGameFixes.alienVSpredetorFix ? 25 : 15)
               && //jyam 15->25 alien vs predetor
               // Panic Bomberman clears the NMI pending flag @ scanline 230 before enabling
               // NMIs again. The NMI routine crashes the CPU if it is called without the NMI
               // pending flag being set...
               (Memory.FillRAM[0x4210] & 0x80) && !CPU.NMIActive)
         {
            CPU.Flags |= NMI_FLAG;
            CPU.NMIActive = TRUE;
            CPU.NMICycleCount = CPU.NMITriggerPoint;
         }
#endif
         break;
      case 0x4201 :
      // I/O port output
      case 0x4202 :
         // Multiplier (for multply)
         break;
      case 0x4203 :
      {
         // Multiplicand
         uint32 res = Memory.FillRAM[0x4202] * byte;

         Memory.FillRAM[0x4216] = (uint8) res;
         Memory.FillRAM[0x4217] = (uint8)(res >> 8);
         break;
      }
      case 0x4204 :
      case 0x4205 :
         // Low and high muliplier (for divide)
         break;
      case 0x4206 :
      {
         // Divisor
         uint16 a =
            Memory.FillRAM[0x4204] + (Memory.FillRAM[0x4205] << 8);
         uint16 div = byte ? a / byte : 0xffff;
         uint16 rem = byte ? a % byte : a;

         Memory.FillRAM[0x4214] = (uint8) div;
         Memory.FillRAM[0x4215] = div >> 8;
         Memory.FillRAM[0x4216] = (uint8) rem;
         Memory.FillRAM[0x4217] = rem >> 8;
         break;
      }
      case 0x4207 :
         d = PPU.IRQHBeamPos;
         PPU.IRQHBeamPos = (PPU.IRQHBeamPos & 0xFF00) | byte;

         if (PPU.HTimerEnabled && PPU.IRQHBeamPos != d)
            S9xUpdateHTimer();
         break;

      case 0x4208 :
         d = PPU.IRQHBeamPos;
         PPU.IRQHBeamPos = (PPU.IRQHBeamPos & 0xFF) | ((byte & 1) << 8);

         if (PPU.HTimerEnabled && PPU.IRQHBeamPos != d)
            S9xUpdateHTimer();

         break;

      case 0x4209 :
         d = PPU.IRQVBeamPos;
         PPU.IRQVBeamPos = (PPU.IRQVBeamPos & 0xFF00) | byte;
#ifdef DEBUGGER
         missing.virq_pos = PPU.IRQVBeamPos;
#endif
         if (PPU.VTimerEnabled && PPU.IRQVBeamPos != d)
         {
            if (PPU.HTimerEnabled)
               S9xUpdateHTimer();
            else
            {
               if (PPU.IRQVBeamPos == CPU.V_Counter)
                  S9xSetIRQ(PPU_V_BEAM_IRQ_SOURCE);
            }
         }
         break;

      case 0x420A :
         d = PPU.IRQVBeamPos;
         PPU.IRQVBeamPos = (PPU.IRQVBeamPos & 0xFF) | ((byte & 1) << 8);
#ifdef DEBUGGER
         missing.virq_pos = PPU.IRQVBeamPos;
#endif
         if (PPU.VTimerEnabled && PPU.IRQVBeamPos != d)
         {
            if (PPU.HTimerEnabled)
               S9xUpdateHTimer();
            else
            {
               if (PPU.IRQVBeamPos == CPU.V_Counter)
                  S9xSetIRQ(PPU_V_BEAM_IRQ_SOURCE);
            }
         }
         break;

      case 0x420B :
#ifdef DEBUGGER
         missing.dma_this_frame = byte;
         missing.dma_channels = byte;
#endif
         if ((byte & 0x01) != 0)
            S9xDoDMA(0);
         if ((byte & 0x02) != 0)
            S9xDoDMA(1);
         if ((byte & 0x04) != 0)
            S9xDoDMA(2);
         if ((byte & 0x08) != 0)
            S9xDoDMA(3);
         if ((byte & 0x10) != 0)
            S9xDoDMA(4);
         if ((byte & 0x20) != 0)
            S9xDoDMA(5);
         if ((byte & 0x40) != 0)
            S9xDoDMA(6);
         if ((byte & 0x80) != 0)
            S9xDoDMA(7);
         break;
      case 0x420C :
#ifdef DEBUGGER
         missing.hdma_this_frame |= byte;
         missing.hdma_channels |= byte;
#endif
         //if (Settings.DisableHDMA)
         // byte = 0;
         Memory.FillRAM[0x420c] = byte;
         IPPU.HDMA = byte;
         break;

      case 0x420d :
         // Cycle speed 0 - 2.68Mhz, 1 - 3.58Mhz (banks 0x80 +)
         if ((byte & 1) != (Memory.FillRAM[0x420d] & 1))
         {
            if (byte & 1)
            {
               CPU.FastROMSpeed = ONE_CYCLE;
#ifdef DEBUGGER
               missing.fast_rom = 1;
#endif
            }
            else
               CPU.FastROMSpeed = SLOW_ONE_CYCLE;

            FixROMSpeed();
         }
      /* FALL */
      case 0x420e :
      case 0x420f :
         // --->>> Unknown
         break;
      case 0x4210 :
         // NMI ocurred flag (reset on read or write)
         Memory.FillRAM[0x4210] = 0;
         return;
      case 0x4211 :
         // IRQ ocurred flag (reset on read or write)
         CLEAR_IRQ_SOURCE(PPU_V_BEAM_IRQ_SOURCE | PPU_H_BEAM_IRQ_SOURCE);
         break;
      case 0x4212 :
      // v-blank, h-blank and joypad being scanned flags (read-only)
      case 0x4213 :
      // I/O Port (read-only)
      case 0x4214 :
      case 0x4215 :
      // Quotent of divide (read-only)
      case 0x4216 :
      case 0x4217 :
         // Multiply product (read-only)
         return;
      case 0x4218 :
      case 0x4219 :
      case 0x421a :
      case 0x421b :
      case 0x421c :
      case 0x421d :
      case 0x421e :
      case 0x421f :
         // Joypad values (read-only)
         return;

      case 0x4300 :
      case 0x4310 :
      case 0x4320 :
      case 0x4330 :
      case 0x4340 :
      case 0x4350 :
      case 0x4360 :
      case 0x4370 :
         d = (Address >> 4) & 0x7;
         DMA[d].TransferDirection = (byte & 128) != 0 ? 1 : 0;
         DMA[d].HDMAIndirectAddressing = (byte & 64) != 0 ? 1 : 0;
         DMA[d].AAddressDecrement = (byte & 16) != 0 ? 1 : 0;
         DMA[d].AAddressFixed = (byte & 8) != 0 ? 1 : 0;
         DMA[d].TransferMode = (byte & 7);
         break;

      case 0x4301 :
      case 0x4311 :
      case 0x4321 :
      case 0x4331 :
      case 0x4341 :
      case 0x4351 :
      case 0x4361 :
      case 0x4371 :
         DMA[((Address >> 4) & 0x7)].BAddress = byte;
         break;

      case 0x4302 :
      case 0x4312 :
      case 0x4322 :
      case 0x4332 :
      case 0x4342 :
      case 0x4352 :
      case 0x4362 :
      case 0x4372 :
         d = (Address >> 4) & 0x7;
         DMA[d].AAddress &= 0xFF00;
         DMA[d].AAddress |= byte;
         break;

      case 0x4303 :
      case 0x4313 :
      case 0x4323 :
      case 0x4333 :
      case 0x4343 :
      case 0x4353 :
      case 0x4363 :
      case 0x4373 :
         d = (Address >> 4) & 0x7;
         DMA[d].AAddress &= 0xFF;
         DMA[d].AAddress |= byte << 8;
         break;

      case 0x4304 :
      case 0x4314 :
      case 0x4324 :
      case 0x4334 :
      case 0x4344 :
      case 0x4354 :
      case 0x4364 :
      case 0x4374 :
         DMA[((Address >> 4) & 0x7)].ABank = byte;
         HDMAMemPointers[((Address >> 4) & 0x7)] = NULL;
         break;

      case 0x4305 :
      case 0x4315 :
      case 0x4325 :
      case 0x4335 :
      case 0x4345 :
      case 0x4355 :
      case 0x4365 :
      case 0x4375 :
         d = (Address >> 4) & 0x7;
         DMA[d].TransferBytes &= 0xFF00;
         DMA[d].TransferBytes |= byte;
         DMA[d].IndirectAddress &= 0xff00;
         DMA[d].IndirectAddress |= byte;
         HDMAMemPointers[d] = NULL;
         break;

      case 0x4306 :
      case 0x4316 :
      case 0x4326 :
      case 0x4336 :
      case 0x4346 :
      case 0x4356 :
      case 0x4366 :
      case 0x4376 :
         d = (Address >> 4) & 0x7;
         DMA[d].TransferBytes &= 0xFF;
         DMA[d].TransferBytes |= byte << 8;
         DMA[d].IndirectAddress &= 0xff;
         DMA[d].IndirectAddress |= byte << 8;
         HDMAMemPointers[d] = NULL;
         break;

      case 0x4307 :
      case 0x4317 :
      case 0x4327 :
      case 0x4337 :
      case 0x4347 :
      case 0x4357 :
      case 0x4367 :
      case 0x4377 :
         DMA[d = ((Address >> 4) & 0x7)].IndirectBank = byte;
         HDMAMemPointers[d] = NULL;
         break;

      case 0x4308 :
      case 0x4318 :
      case 0x4328 :
      case 0x4338 :
      case 0x4348 :
      case 0x4358 :
      case 0x4368 :
      case 0x4378 :
         d = (Address >> 4) & 7;
         DMA[d].Address &= 0xff00;
         DMA[d].Address |= byte;
         HDMAMemPointers[d] = NULL;
         break;

      case 0x4309 :
      case 0x4319 :
      case 0x4329 :
      case 0x4339 :
      case 0x4349 :
      case 0x4359 :
      case 0x4369 :
      case 0x4379 :
         d = (Address >> 4) & 0x7;
         DMA[d].Address &= 0xff;
         DMA[d].Address |= byte << 8;
         HDMAMemPointers[d] = NULL;
         break;

      case 0x430A :
      case 0x431A :
      case 0x432A :
      case 0x433A :
      case 0x434A :
      case 0x435A :
      case 0x436A :
      case 0x437A :
         d = (Address >> 4) & 0x7;
         DMA[d].LineCount = byte & 0x7f;
         DMA[d].Repeat = !(byte & 0x80);
         break;

      case 0x430F:
      case 0x431F:
      case 0x432F:
      case 0x433F:
      case 0x434F:
      case 0x435F:
      case 0x436F:
      case 0x437F:
         Address &= ~4; // Convert 43xF to 43xB
      /* fall through */
      case 0x430B:
      case 0x431B:
      case 0x432B:
      case 0x433B:
      case 0x434B:
      case 0x435B:
      case 0x436B:
      case 0x437B:

         // Unknown, but they seem to be RAM-ish
         break;
      case 0x4800 :
      case 0x4801 :
      case 0x4802 :
      case 0x4803 :
         //printf ("%02x->%04x\n", byte, Address);
         break;

      case 0x4804 :
      case 0x4805 :
      case 0x4806 :
      case 0x4807 :
         //printf ("%02x->%04x\n", byte, Address);

         S9xSetSDD1MemoryMap(Address - 0x4804, byte & 7);
         break;
      default :
#ifdef DEBUGGER
         missing.unknowncpu_write = Address;
         if (Settings.TraceUnknownRegisters)
         {
            sprintf(
               String,
               "Unknown register write: $%02X->$%04X\n",
               byte,
               Address);
            S9xMessage(S9X_TRACE, S9X_PPU_TRACE, String);
         }
#endif
         break;
      }
   Memory.FillRAM[Address] = byte;
}

/**********************************************************************************************/
/* S9xGetCPU()                                                                                   */
/* This function retrieves a CPU/DMA Register                                                 */
/**********************************************************************************************/
uint8 S9xGetCPU(uint16 Address)
{
   uint8 byte;
   if (Address >= 0x4800 && Address <= 0x4807 && Settings.SDD1)
      return Memory.FillRAM[Address];

   if (Address < 0x4200)
   {
#ifdef VAR_CYCLES
      CPU.Cycles += ONE_CYCLE;
#endif
      switch (Address)
      {
      // Secret of the Evermore
      case 0x4000 :
      case 0x4001 :
         return (0x40);

      case 0x4016 :
      {
         if (Memory.FillRAM[0x4016] & 1)
            return (0);

         byte = IPPU.Joypads[0] >> (PPU.Joypad1ButtonReadPos ^ 15);
         PPU.Joypad1ButtonReadPos++;
         return (byte & 1);
      }
      case 0x4017 :
      {
         if (Memory.FillRAM[0x4016] & 1)
         {
            // MultiPlayer5 adaptor is only allowed to be plugged into port 2
            switch (IPPU.Controller)
            {
            case SNES_MULTIPLAYER5 :
               return (2);

            case SNES_MOUSE :
               if (++PPU.MouseSpeed[0] > 2)
                  PPU.MouseSpeed[0] = 0;
               break;
            }
            return (0x00);
         }

         if (IPPU.Controller == SNES_MULTIPLAYER5)
         {
            if (Memory.FillRAM[0x4201] & 0x80)
            {
               byte =
                  ((IPPU.Joypads[0]
                    >> (PPU.Joypad2ButtonReadPos ^ 15))
                   & 1)
                  | (((IPPU.Joypads[2]
                       >> (PPU.Joypad2ButtonReadPos ^ 15))
                      & 1)
                     << 1);
               PPU.Joypad2ButtonReadPos++;
               return (byte);
            }
            else
            {
               byte =
                  ((IPPU.Joypads[3]
                    >> (PPU.Joypad3ButtonReadPos ^ 15))
                   & 1)
                  | (((IPPU.Joypads[4]
                       >> (PPU.Joypad3ButtonReadPos ^ 15))
                      & 1)
                     << 1);
               PPU.Joypad3ButtonReadPos++;
               return (byte);
            }
         }
         return (
                   (IPPU.Joypads[1]
                    >> (PPU.Joypad2ButtonReadPos++ ^ 15))
                   & 1);
      }
      default :
#ifdef DEBUGGER
         missing.unknowncpu_read = Address;
         if (Settings.TraceUnknownRegisters)
         {
            sprintf(String, "Unknown register read: $%04X\n", Address);
            S9xMessage(S9X_TRACE, S9X_PPU_TRACE, String);
         }
#endif
         break;
      }
      return (Memory.FillRAM[Address]);
   }
   else
      switch (Address)
      {
      // BS Dynami Tracer! needs to be able to check if NMIs are enabled
      // already, otherwise the game locks up.
      case 0x4200 :
         // NMI, h & v timers and joypad reading enable
         if (SNESGameFixes.Old_Read0x4200)
         {
#ifdef CPU_SHUTDOWN
            CPU.WaitAddress = CPU.PCAtOpcodeStart;
#endif
            return (REGISTER_4212());
         }
      case 0x4201 :
      // I/O port (output - write only?)
      case 0x4202 :
      case 0x4203 :
      // Multiplier and multiplicand (write)
      case 0x4204 :
      case 0x4205 :
      case 0x4206 :
         // Divisor and dividend (write)
         return (Memory.FillRAM[Address]);
      case 0x4207 :
         return (uint8)(PPU.IRQHBeamPos);
      case 0x4208 :
         return (PPU.IRQHBeamPos >> 8);
      case 0x4209 :
         return (uint8)(PPU.IRQVBeamPos);
      case 0x420a :
         return (PPU.IRQVBeamPos >> 8);
      case 0x420b :
         // General purpose DMA enable
         // Super Formation Soccer 95 della Serie A UCC Xaqua requires this
         // register should not always return zero.
         // .. But Aero 2 waits until this register goes zero..
         // Just keep toggling the value for now in the hope that it breaks
         // the game out of its wait loop...
         Memory.FillRAM[0x420b] = !Memory.FillRAM[0x420b];
         return (Memory.FillRAM[0x420b]);
      case 0x420c :
         // H-DMA enable
         return (IPPU.HDMA);
      case 0x420d :
         // Cycle speed 0 - 2.68Mhz, 1 - 3.58Mhz (banks 0x80 +)
         return (Memory.FillRAM[Address]);
      case 0x420e :
      case 0x420f :
         // --->>> Unknown
         return (Memory.FillRAM[Address]);
      case 0x4210 :
#ifdef CPU_SHUTDOWN
         CPU.WaitAddress = CPU.PCAtOpcodeStart;
#endif
         byte = Memory.FillRAM[0x4210];
         Memory.FillRAM[0x4210] = 0;
         return (byte);
      case 0x4211 :
         byte =
            (CPU.IRQActive
             & (PPU_V_BEAM_IRQ_SOURCE | PPU_H_BEAM_IRQ_SOURCE))
            ? 0x80
            : 0;
         // Super Robot Wars Ex ROM bug requires this.
         byte |= CPU.Cycles >= Settings.HBlankStart ? 0x40 : 0;
         CLEAR_IRQ_SOURCE(PPU_V_BEAM_IRQ_SOURCE | PPU_H_BEAM_IRQ_SOURCE);
         return (byte);
      case 0x4212 :
         // V-blank, h-blank and joypads being read flags (read-only)
#ifdef CPU_SHUTDOWN
         CPU.WaitAddress = CPU.PCAtOpcodeStart;
#endif
         return (REGISTER_4212());
      case 0x4213 :
      // I/O port input
      case 0x4214 :
      case 0x4215 :
      // Quotient of divide result
      case 0x4216 :
      case 0x4217 :
         // Multiplcation result (for multiply) or remainder of
         // divison.
         return (Memory.FillRAM[Address]);
      case 0x4218 :
      case 0x4219 :
      case 0x421a :
      case 0x421b :
      case 0x421c :
      case 0x421d :
      case 0x421e :
      case 0x421f :
         // Joypads 1-4 button and direction state.
         return (Memory.FillRAM[Address]);

      case 0x4300 :
      case 0x4310 :
      case 0x4320 :
      case 0x4330 :
      case 0x4340 :
      case 0x4350 :
      case 0x4360 :
      case 0x4370 :
         // DMA direction, address type, fixed flag,
         return (Memory.FillRAM[Address]);

      case 0x4301 :
      case 0x4311 :
      case 0x4321 :
      case 0x4331 :
      case 0x4341 :
      case 0x4351 :
      case 0x4361 :
      case 0x4371 :
         return (Memory.FillRAM[Address]);

      case 0x4302 :
      case 0x4312 :
      case 0x4322 :
      case 0x4332 :
      case 0x4342 :
      case 0x4352 :
      case 0x4362 :
      case 0x4372 :
         return (Memory.FillRAM[Address]);

      case 0x4303 :
      case 0x4313 :
      case 0x4323 :
      case 0x4333 :
      case 0x4343 :
      case 0x4353 :
      case 0x4363 :
      case 0x4373 :
         return (Memory.FillRAM[Address]);

      case 0x4304 :
      case 0x4314 :
      case 0x4324 :
      case 0x4334 :
      case 0x4344 :
      case 0x4354 :
      case 0x4364 :
      case 0x4374 :
         return (Memory.FillRAM[Address]);

      case 0x4305 :
      case 0x4315 :
      case 0x4325 :
      case 0x4335 :
      case 0x4345 :
      case 0x4355 :
      case 0x4365 :
      case 0x4375 :
         return (Memory.FillRAM[Address]);

      case 0x4306 :
      case 0x4316 :
      case 0x4326 :
      case 0x4336 :
      case 0x4346 :
      case 0x4356 :
      case 0x4366 :
      case 0x4376 :
         return (Memory.FillRAM[Address]);

      case 0x4307 :
      case 0x4317 :
      case 0x4327 :
      case 0x4337 :
      case 0x4347 :
      case 0x4357 :
      case 0x4367 :
      case 0x4377 :
         return (DMA[(Address >> 4) & 7].IndirectBank);

      case 0x4308 :
      case 0x4318 :
      case 0x4328 :
      case 0x4338 :
      case 0x4348 :
      case 0x4358 :
      case 0x4368 :
      case 0x4378 :
         return (Memory.FillRAM[Address]);

      case 0x4309 :
      case 0x4319 :
      case 0x4329 :
      case 0x4339 :
      case 0x4349 :
      case 0x4359 :
      case 0x4369 :
      case 0x4379 :
         return (Memory.FillRAM[Address]);

      case 0x430A :
      case 0x431A :
      case 0x432A :
      case 0x433A :
      case 0x434A :
      case 0x435A :
      case 0x436A :
      case 0x437A :
      {
         int d = (Address & 0x70) >> 4;
         if (IPPU.HDMA & (1 << d))
            return (DMA[d].LineCount);
         return (Memory.FillRAM[Address]);
      }
      case 0x430F:
      case 0x431F:
      case 0x432F:
      case 0x433F:
      case 0x434F:
      case 0x435F:
      case 0x436F:
      case 0x437F:
         Address &= ~4; // Convert 43xF to 43xB
      /* fall through */
      case 0x430B:
      case 0x431B:
      case 0x432B:
      case 0x433B:
      case 0x434B:
      case 0x435B:
      case 0x436B:
      case 0x437B:

         // Unknown, but they seem to be RAM-ish
         return (Memory.FillRAM[Address]);
      default :
#ifdef DEBUGGER
         missing.unknowncpu_read = Address;
         if (Settings.TraceUnknownRegisters)
         {
            sprintf(String, "Unknown register read: $%04X\n", Address);
            S9xMessage(S9X_TRACE, S9X_PPU_TRACE, String);
         }

#endif
         break;
      }
   return (Memory.FillRAM[Address]);
}

void S9xResetPPU()
{
   PPU.BGMode = 0;
   PPU.BG3Priority = 0;
   PPU.Brightness = 0;
   PPU.VMA.High = 0;
   PPU.VMA.Increment = 1;
   PPU.VMA.Address = 0;
   PPU.VMA.FullGraphicCount = 0;
   PPU.VMA.Shift = 0;

   uint8 B;
   for (B = 0; B != 4; B++)
   {
      PPU.BG[B].SCBase = 0;
      PPU.BG[B].VOffset = 0;
      PPU.BG[B].HOffset = 0;
      PPU.BG[B].BGSize = 0;
      PPU.BG[B].NameBase = 0;
      PPU.BG[B].SCSize = 0;

      PPU.ClipCounts[B] = 0;
      PPU.ClipWindowOverlapLogic[B] = CLIP_OR;
      PPU.ClipWindow1Enable[B] = FALSE;
      PPU.ClipWindow2Enable[B] = FALSE;
      PPU.ClipWindow1Inside[B] = TRUE;
      PPU.ClipWindow2Inside[B] = TRUE;
   }

   PPU.ClipCounts[4] = 0;
   PPU.ClipCounts[5] = 0;
   PPU.ClipWindowOverlapLogic[4] = PPU.ClipWindowOverlapLogic[5] = CLIP_OR;
   PPU.ClipWindow1Enable[4] = PPU.ClipWindow1Enable[5] = FALSE;
   PPU.ClipWindow2Enable[4] = PPU.ClipWindow2Enable[5] = FALSE;
   PPU.ClipWindow1Inside[4] = PPU.ClipWindow1Inside[5] = TRUE;
   PPU.ClipWindow2Inside[4] = PPU.ClipWindow2Inside[5] = TRUE;

   PPU.CGFLIP = 0;
   unsigned int c;
   for (c = 0; c < 256; c++)
   {
      IPPU.Red[c] = (c & 7) << 2;
      IPPU.Green[c] = ((c >> 3) & 7) << 2;
      IPPU.Blue[c] = ((c >> 6) & 2) << 3;
      PPU.CGDATA[c] =
         IPPU.Red[c] | (IPPU.Green[c] << 5) | (IPPU.Blue[c] << 10);
   }

   PPU.FirstSprite = 0;
   PPU.LastSprite = 127;
   int Sprite;
   for (Sprite = 0; Sprite < 128; Sprite++)
   {
      PPU.OBJ[Sprite].HPos = 0;
      PPU.OBJ[Sprite].VPos = 0;
      PPU.OBJ[Sprite].VFlip = 0;
      PPU.OBJ[Sprite].HFlip = 0;
      PPU.OBJ[Sprite].Priority = 0;
      PPU.OBJ[Sprite].Palette = 0;
      PPU.OBJ[Sprite].Name = 0;
      PPU.OBJ[Sprite].Size = 0;
   }
   PPU.OAMPriorityRotation = 0;
   PPU.OAMWriteRegister = 0;

   PPU.OAMFlip = 0;
   PPU.OAMTileAddress = 0;
   PPU.OAMAddr = 0;
   PPU.IRQVBeamPos = 0;
   PPU.IRQHBeamPos = 0;
   PPU.VBeamPosLatched = 0;
   PPU.HBeamPosLatched = 0;

   PPU.HBeamFlip = 0;
   PPU.VBeamFlip = 0;
   PPU.HVBeamCounterLatched = 0;

   PPU.MatrixA = PPU.MatrixB = PPU.MatrixC = PPU.MatrixD = 0;
   PPU.CentreX = PPU.CentreY = 0;
   PPU.Joypad1ButtonReadPos = 0;
   PPU.Joypad2ButtonReadPos = 0;
   PPU.Joypad3ButtonReadPos = 0;

   PPU.CGADD = 0;
   PPU.FixedColourRed = PPU.FixedColourGreen = PPU.FixedColourBlue = 0;
   PPU.SavedOAMAddr = 0;
   PPU.SavedOAMAddr2 = 0;
   PPU.ScreenHeight = SNES_HEIGHT;
   PPU.WRAM = 0;
   PPU.BG_Forced = 0;
   PPU.ForcedBlanking = TRUE;
   PPU.OBJThroughMain = FALSE;
   PPU.OBJThroughSub = FALSE;
   PPU.OBJSizeSelect = 0;
   PPU.OBJNameSelect = 0;
   PPU.OBJNameBase = 0;
   PPU.OBJAddition = FALSE;
   PPU.OAMReadFlip = 0;
   PPU.BGnxOFSbyte = 0;
   memset(PPU.OAMData, 0, 512 + 32);

   PPU.VTimerEnabled = FALSE;
   PPU.HTimerEnabled = FALSE;
   PPU.HTimerPosition = Settings.H_Max + 1;
   PPU.Mosaic = 0;
   PPU.BGMosaic[0] = PPU.BGMosaic[1] = FALSE;
   PPU.BGMosaic[2] = PPU.BGMosaic[3] = FALSE;
   PPU.Mode7HFlip = FALSE;
   PPU.Mode7VFlip = FALSE;
   PPU.Mode7Repeat = 0;
   PPU.Window1Left = 1;
   PPU.Window1Right = 0;
   PPU.Window2Left = 1;
   PPU.Window2Right = 0;
   PPU.RecomputeClipWindows = TRUE;
   PPU.CGFLIPRead = 0;
   PPU.Need16x8Mulitply = FALSE;
   PPU.MouseSpeed[0] = PPU.MouseSpeed[1] = 0;

   IPPU.ColorsChanged = TRUE;
   IPPU.HDMA = 0;
   IPPU.HDMAStarted = FALSE;
   IPPU.MaxBrightness = 0;
   IPPU.LatchedBlanking = 0;
   IPPU.OBJChanged = TRUE;
   IPPU.RenderThisFrame = TRUE;
   IPPU.DirectColourMapsNeedRebuild = TRUE;
   IPPU.FrameCount = 0;
   IPPU.RenderedFramesCount = 0;
   IPPU.DisplayedRenderedFrameCount = 0;
   IPPU.SkippedFrames = 0;
   IPPU.FrameSkip = 0;
   memset(IPPU.TileCached[TILE_2BIT], 0, MAX_2BIT_TILES);
   memset(IPPU.TileCached[TILE_4BIT], 0, MAX_4BIT_TILES);
   memset(IPPU.TileCached[TILE_8BIT], 0, MAX_8BIT_TILES);
   IPPU.FirstVRAMRead = FALSE;
   IPPU.LatchedInterlace = FALSE;
   IPPU.DoubleWidthPixels = FALSE;
   IPPU.RenderedScreenWidth = SNES_WIDTH;
   IPPU.RenderedScreenHeight = SNES_HEIGHT;
   IPPU.XB = NULL;
   for (c = 0; c < 256; c++)
      IPPU.ScreenColors[c] = c;
   S9xFixColourBrightness();
   IPPU.PreviousLine = IPPU.CurrentLine = 0;
   IPPU.Joypads[0] = IPPU.Joypads[1] = IPPU.Joypads[2] = 0;
   IPPU.Joypads[3] = IPPU.Joypads[4] = 0;
   IPPU.SuperScope = 0;
   IPPU.Mouse[0] = IPPU.Mouse[1] = 0;
   IPPU.PrevMouseX[0] = IPPU.PrevMouseX[1] = 256 / 2;
   IPPU.PrevMouseY[0] = IPPU.PrevMouseY[1] = 224 / 2;

   if (Settings.ControllerOption == 0)
      IPPU.Controller = SNES_MAX_CONTROLLER_OPTIONS - 1;
   else
      IPPU.Controller = Settings.ControllerOption - 1;
   S9xNextController();

   for (c = 0; c < 2; c++)
      memset(& IPPU.Clip[c], 0, sizeof(ClipData));

   if (Settings.MouseMaster)
   {
      S9xProcessMouse(0);
      S9xProcessMouse(1);
   }
   for (c = 0; c < 0x8000; c += 0x100)
      memset(& Memory.FillRAM[c], c >> 8, 0x100);

   memset(& Memory.FillRAM[0x2100], 0, 0x100);
   memset(& Memory.FillRAM[0x4200], 0, 0x100);
   memset(& Memory.FillRAM[0x4000], 0, 0x100);
   // For BS Suttehakkun 2...
   memset(& Memory.FillRAM[0x1000], 0, 0x1000);
   Memory.FillRAM[0x4201] = Memory.FillRAM[0x4213] = 0xFF;

   PPU.BG[0].OffsetsChanged = 0;
   PPU.BG[1].OffsetsChanged = 0;
   PPU.BG[2].OffsetsChanged = 0;
   PPU.BG[3].OffsetsChanged = 0;
   ROpCount = 0;
   memset(&rops, 0, MAX_ROPS);
   GFX.r212c_s = 0;
   GFX.r212d_s = 0;
   GFX.r212e_s = 0;
   GFX.r212f_s = 0;
   GFX.r2130_s = 0;
   GFX.r2131_s = 0;
}

void S9xProcessMouse(int which1)
{
   int x, y;
   uint32 buttons;

   if ((IPPU.Controller == SNES_MOUSE
         || IPPU.Controller == SNES_MOUSE_SWAPPED)
         && S9xReadMousePosition(which1, &x, &y, &buttons))
   {
      int delta_x, delta_y;
#define MOUSE_SIGNATURE 0x1
      IPPU.Mouse[which1] =
         MOUSE_SIGNATURE
         | (PPU.MouseSpeed[which1] << 4)
         | ((buttons & 1) << 6)
         | ((buttons & 2) << 6);

      delta_x = x - IPPU.PrevMouseX[which1];
      delta_y = y - IPPU.PrevMouseY[which1];

      if (delta_x > 63)
      {
         delta_x = 63;
         IPPU.PrevMouseX[which1] += 63;
      }
      else if (delta_x < -63)
      {
         delta_x = -63;
         IPPU.PrevMouseX[which1] -= 63;
      }
      else
         IPPU.PrevMouseX[which1] = x;

      if (delta_y > 63)
      {
         delta_y = 63;
         IPPU.PrevMouseY[which1] += 63;
      }
      else if (delta_y < -63)
      {
         delta_y = -63;
         IPPU.PrevMouseY[which1] -= 63;
      }
      else
         IPPU.PrevMouseY[which1] = y;

      if (delta_x < 0)
      {
         delta_x = -delta_x;
         IPPU.Mouse[which1] |= (delta_x | 0x80) << 16;
      }
      else
         IPPU.Mouse[which1] |= delta_x << 16;

      if (delta_y < 0)
      {
         delta_y = -delta_y;
         IPPU.Mouse[which1] |= (delta_y | 0x80) << 24;
      }
      else
         IPPU.Mouse[which1] |= delta_y << 24;

      if (IPPU.Controller == SNES_MOUSE_SWAPPED)
         IPPU.Joypads[0] = IPPU.Mouse[which1];
      else
         IPPU.Joypads[1] = IPPU.Mouse[which1];
   }
}

void ProcessSuperScope()
{
   int x, y;
   uint32 buttons;

   if (IPPU.Controller == SNES_SUPERSCOPE
         && S9xReadSuperScopePosition(&x, &y, &buttons))
   {
#define SUPERSCOPE_SIGNATURE 0x00ff
      uint32 scope;

      scope =
         SUPERSCOPE_SIGNATURE
         | ((buttons & 1) << (7 + 8))
         | ((buttons & 2) << (5 + 8))
         | ((buttons & 4) << (3 + 8))
         | ((buttons & 8) << (1 + 8));
      if (x > 255)
         x = 255;
      if (x < 0)
         x = 0;
      if (y > PPU.ScreenHeight - 1)
         y = PPU.ScreenHeight - 1;
      if (y < 0)
         y = 0;

      PPU.VBeamPosLatched = (uint16)(y + 1);
      PPU.HBeamPosLatched = (uint16) x;
      PPU.HVBeamCounterLatched = TRUE;
      Memory.FillRAM[0x213F] |= 0x40;
      IPPU.Joypads[1] = scope;
   }
}

void S9xNextController()
{
   switch (IPPU.Controller)
   {
   case SNES_MULTIPLAYER5 :
      IPPU.Controller = SNES_JOYPAD;
      break;
   case SNES_JOYPAD :
      if (Settings.MouseMaster)
      {
         IPPU.Controller = SNES_MOUSE_SWAPPED;
         break;
      }
   case SNES_MOUSE_SWAPPED :
      if (Settings.MouseMaster)
      {
         IPPU.Controller = SNES_MOUSE;
         break;
      }
   case SNES_MOUSE :
      if (Settings.SuperScopeMaster)
      {
         IPPU.Controller = SNES_SUPERSCOPE;
         break;
      }
   case SNES_SUPERSCOPE :
      if (Settings.MultiPlayer5Master)
      {
         IPPU.Controller = SNES_MULTIPLAYER5;
         break;
      }
   default :
      IPPU.Controller = SNES_JOYPAD;
      break;
   }
}

void S9xUpdateJoypads()
{
#if defined (__WIZ__)
   IPPU.Joypads[0] = S9xReadJoypad(0);

   if (IPPU.Joypads[0] & SNES_LEFT_MASK)
      IPPU.Joypads[0] &= ~SNES_RIGHT_MASK;
   if (IPPU.Joypads[0] & SNES_UP_MASK)
      IPPU.Joypads[0] &= ~SNES_DOWN_MASK;

   if (Memory.FillRAM [0x4200] & 1)
   {
      PPU.Joypad1ButtonReadPos = 16;

      Memory.FillRAM [0x4218] = (uint8) IPPU.Joypads[0];
      Memory.FillRAM [0x4219] = (uint8)(IPPU.Joypads[0] >> 8);
      if (Memory.FillRAM [0x4201] & 0x80)
      {
         Memory.FillRAM [0x421c] = (uint8) IPPU.Joypads[0];
         Memory.FillRAM [0x421d] = (uint8)(IPPU.Joypads[0] >> 8);
      }
   }
#else
   int i;

   for (i = 0; i < 5; i++)
   {
      IPPU.Joypads[i] = S9xReadJoypad(i);
      if (IPPU.Joypads[i] & SNES_LEFT_MASK)
         IPPU.Joypads[i] &= ~SNES_RIGHT_MASK;
      if (IPPU.Joypads[i] & SNES_UP_MASK)
         IPPU.Joypads[i] &= ~SNES_DOWN_MASK;
   }

   if (IPPU.Controller == SNES_JOYPAD
         || IPPU.Controller == SNES_MULTIPLAYER5)
   {
      for (i = 0; i < 5; i++)
      {
         if (IPPU.Joypads[i])
            IPPU.Joypads[i] |= 0xffff0000;
      }
   }

   // Read mouse position if enabled
   if (Settings.MouseMaster)
   {
      for (i = 0; i < 2; i++)
         S9xProcessMouse(i);
   }

   // Read SuperScope if enabled
   if (Settings.SuperScopeMaster)
      ProcessSuperScope();

   if (Memory.FillRAM[0x4200] & 1)
   {
      PPU.Joypad1ButtonReadPos = 16;
      if (Memory.FillRAM[0x4201] & 0x80)
      {
         PPU.Joypad2ButtonReadPos = 16;
         PPU.Joypad3ButtonReadPos = 0;
      }
      else
      {
         PPU.Joypad2ButtonReadPos = 0;
         PPU.Joypad3ButtonReadPos = 16;
      }

      Memory.FillRAM[0x4218] = (uint8) IPPU.Joypads[0];
      Memory.FillRAM[0x4219] = (uint8)(IPPU.Joypads[0] >> 8);
      Memory.FillRAM[0x421a] = (uint8) IPPU.Joypads[1];
      Memory.FillRAM[0x421b] = (uint8)(IPPU.Joypads[1] >> 8);
      if (Memory.FillRAM[0x4201] & 0x80)
      {
         Memory.FillRAM[0x421c] = (uint8) IPPU.Joypads[0];
         Memory.FillRAM[0x421d] = (uint8)(IPPU.Joypads[0] >> 8);
         Memory.FillRAM[0x421e] = (uint8) IPPU.Joypads[2];
         Memory.FillRAM[0x421f] = (uint8)(IPPU.Joypads[2] >> 8);
      }
      else
      {
         Memory.FillRAM[0x421c] = (uint8) IPPU.Joypads[3];
         Memory.FillRAM[0x421d] = (uint8)(IPPU.Joypads[3] >> 8);
         Memory.FillRAM[0x421e] = (uint8) IPPU.Joypads[4];
         Memory.FillRAM[0x421f] = (uint8)(IPPU.Joypads[4] >> 8);
      }
   }
#endif
}

void S9xSuperFXExec()
{
   if ((Memory.FillRAM[0x3000 + GSU_SFR] & FLG_G)
         && (Memory.FillRAM[0x3000 + GSU_SCMR] & 0x18) == 0x18)
   {
      if (!Settings.WinterGold || Settings.StarfoxHack)
         FxEmulate(~0);
      else FxEmulate((Memory.FillRAM[0x3000 + GSU_CLSR] & 1) ? 700 : 350);
      int GSUStatus = Memory.FillRAM[0x3000
                                     + GSU_SFR] | (Memory.FillRAM[0x3000 + GSU_SFR + 1] << 8);
      if ((GSUStatus & (FLG_G | FLG_IRQ)) == FLG_IRQ)
      {
         // Trigger a GSU IRQ.
         S9xSetIRQ(GSU_IRQ_SOURCE);
      }
   }
}

