#include <errno.h>
#include <string.h>

#include "pcfx.h"
#include "vdc.h"
#include "soundbox.h"
#include "pad.h"
#include "king.h"
#include "timer.h"
#include "interrupt.h"
#include "debug.h"
#include "rainbow.h"
#include "scsicd.h"
#include "../mempatcher.h"
#include "../netplay.h"
#include "../endian.h"
#include "../cdromif.h"
#include "../md5.h"

static uint8 *BIOSROM; // 1MB
static uint8 *RAM; // 2MB

static uint16 lastchoo[2];

bool PCFXGA = 0;
static uint32 PCFXGACodeSegmentAddress, PCFXGADataSegmentAddress, PCFXGAGlobalDataSegmentAddress;

//static 
fx_vdc_t *fx_vdc_chips[2];

static uint16 BackupControl;
static uint8 BackupRAM[0x8000];

uint8 mem_rbyte(uint32 A)
{
 if(A >= 0xFFF00000 && A <= 0xFFFFFFFF)
 {
  return(BIOSROM[A & 0xFFFFF]);
 }

 if(A <= 0x1FFFFF)
 {
  return(RAM[A & 0x1FFFFF]);
 }
 else if(A >= 0xE0000000 && A <= 0xE7FFFFFF)
 {
  return(BackupRAM[(A & 0xFFFF) >> 1]);
 }
 else if(A >= 0xE8000000 && A <= 0xE9FFFFFF)
 {
  // External backup RAM
  return(0x00);
 }

 //printf("RBYTE Unknown: %08x\n", A);
 return(0x00);
}

uint16 mem_rhword(uint32 A)
{
 if(A >= 0xFFF00000 && A <= 0xFFFFFFFF)
 {
  return(le16toh(*(uint16 *)&BIOSROM[A & 0xFFFFF]));
 }

 if(A <= 0x1FFFFF)
 {
  return(le16toh(*(uint16*)&RAM[A & 0x1FFFFF]));
 }
 else if(A >= 0xE0000000 && A <= 0xE7FFFFFF)
 {
  return(BackupRAM[(A & 0xFFFF) >> 1]);
 }
 else if(A >= 0xE8000000 && A <= 0xEFFFFFFF)
 {
  // External backup RAM
  return(0x00);
 }
 else if(A >= 0xF8000000 && A <= 0xFFEFFFFF) // PIO
 {
  //puts("Err");
  return(0x00);
 }
 return(0);
}

uint32 mem_rword(uint32 A)
{
 if(A >= 0xFFF00000 && A <= 0xFFFFFFFF)
 {
  return(le32toh(*(uint32 *)&BIOSROM[A & 0xFFFFF]));
 }
 else if(A <= 0x1FFFFF)
 {
  return(le32toh(*(uint32*)&RAM[A & 0x1FFFFF]));
 }
 else if(A >= 0xE0000000 && A <= 0xE7FFFFFF)
 {
  uint32 ret = BackupRAM[(A & 0xFFFF) >> 1];
  ret |= (BackupRAM[((A | 2) & 0xFFFF) >> 1] << 16);
  printf("RWORD: %08x\n", (A & 0xFFFF) >> 1);
  //exit(1);
  return(ret);
 }
 else if(A >= 0xE8000000 && A <= 0xE9FFFFFF)
 {
  // External backup RAM
  return(0x00);
 }
 else
 {
  //printf("Unknown word read: %08x\n", A);
  //return(rand());
 }

 return(0);
}

void mem_wbyte(uint32 A, uint8 V)
{
 if(A <= 0x1FFFFF)
 {
  RAM[A & 0x1FFFFF] = V;
 }
 else if(A >= 0xE0000000 && A <= 0xE7FFFFFF)
 {
  if(BackupControl & 0x1)
  {
   BackupRAM[(A & 0xFFFF) >> 1] = V;
  }
 }
 else if(A >= 0xE8000000 && A <= 0xEFFFFFFF)
 {
  // External backup RAM
 }
 else if(A >= 0xF8000000 && A <= 0xFFEFFFFF)
 {
  // PIO?
 }
 else if(A >= 0x80000100 && A <= 0x800001FF)
  SoundBox_Write(A & 0xFFF, V);
 else
  printf("Unknown byte write: %08x %02x\n", A, V);
}

void mem_whword(uint32 A, uint16 V)
{
 if(A <= 0x1FFFFF)
 {
  *(uint16*)&RAM[A & 0x1FFFFF] = htole16(V);
 }
 else if(A >= 0xE0000000 && A <= 0xE7FFFFFF)
 {
  if(BackupControl & 0x1)
  {
   BackupRAM[(A & 0xFFFF) >> 1] = (uint8)V;
  }
 }
 else if(A >= 0xE8000000 && A <= 0xEFFFFFFF)
 {
  // External backup RAM
 }
 else if(A >= 0xF8000000 && A <= 0xFFEFFFFF)
 {
  // PIO?
 }
 else
 {
  //printf("Unknown hword write: %08x %04x\n", A, V);
 }
}

void mem_wword(uint32 A, uint32 V)
{
 if(A <= 0x1FFFFF)
 {
  *(uint32*)&RAM[A & 0x1FFFFF] = htole32(V);
 }
 else if(A >= 0xE0000000 && A <= 0xE7FFFFFF)
 {
  if(BackupControl & 0x1)
  {
   BackupRAM[(A & 0xFFFF) >> 1] = (uint8)V;
   BackupRAM[((A | 2) & 0xFFFF) >> 1] = (uint8)(V >> 16);
  }
 }
 else if(A >= 0xE8000000 && A <= 0xEFFFFFFF)
 {
  // External backup RAM
 }
 else if(A >= 0xF8000000 && A <= 0xFFEFFFFF)
 {
  // PIO?
 }
 else
 {
  //printf("Unknown word write: %08x %08x\n", A, V);
 }
}

uint8 port_rbyte(uint32 A)
{
 if(A >= 0x000 && A <= 0x0FF)
  return(FXPAD_Read8(A));
 else if(A >= 0x300 && A <= 0x3FF) // FXVCE
 {
  return(FXVCE_Read8(A));
 }
 else if(A >= 0x400 && A <= 0x4FF) // VDC-A
 {
  printf("VDC A Byte Read: %04x\n", A);
  return(FXVDC_Read(fx_vdc_chips[0], ((A & 4) >> 1) | (A & 1)));
 }
 else if(A >= 0x500 && A <= 0x5FF) // VDC-B
 {
  printf("VDC B Byte Read: %04x\n", A);
  return(FXVDC_Read(fx_vdc_chips[1], ((A & 4) >> 1) | (A & 1)));
 }
 else if(A >= 0x600 && A <= 0x6FF)
 {
  return(KING_Read8(A));
 }
 else if(A >= 0xc00 && A <= 0xCFF) // Backup memory control
 {
  switch(A & 0xC0)
  {
   case 0x80: return(BackupControl);
   case 0x00: return(lastchoo[0]);
   case 0x40: return(lastchoo[1]);
  }
 }
 else if(A >= 0xe00 && A <= 0xeff)
 {
  return(PCFXIRQ_Read8(A));
 }
 else if(A >= 0xf00 && A <= 0xfff)
 {
  return(FXTIMER_Read8(A));
 }

 printf("Unknown byte pread %08x\n", A);
 return(0x00);
}

uint16 port_rhword(uint32 A)
{
 if(A >= 0x000 && A <= 0x0FF)
  return(FXPAD_Read16(A));
 else if(A >= 0x300 && A <= 0x3FF)
 {
  uint16 ret = FXVCE_Read8(A);
  ret |= FXVCE_Read8(A | 1) << 8;
  return(ret);
 }
 else if(A >= 0x400 && A <= 0x4FF) // VDC-A
 {
  return(FXVDC_Read(fx_vdc_chips[0], ((A & 4) >> 1) | 0) | (FXVDC_Read(fx_vdc_chips[0], ((A & 4) >> 1) | 1) << 8) );
 }
 else if(A >= 0x500 && A <= 0x5FF) // VDC-B
 {
  return(FXVDC_Read(fx_vdc_chips[1], ((A & 4) >> 1) | 0) | (FXVDC_Read(fx_vdc_chips[1], ((A & 4) >> 1) | 1) << 8) );
 }
 else if(A >= 0x600 && A <= 0x6FF)
 {
  return(KING_Read16(A));
 }
 else if(A >= 0xc00 && A <= 0xCFF) // Backup memory control
 {
  switch(A & 0xC0)
  {
   case 0x80: return(BackupControl);
   case 0x00: return(lastchoo[0]);
   case 0x40: return(lastchoo[1]);
  }
 }
 else if(A >= 0xe00 && A <= 0xeff)
 {
  return(PCFXIRQ_Read16(A));
 }
 else if(A >= 0xf00 && A <= 0xfff)
 {
  return(FXTIMER_Read16(A));
 }
 if(A != 0x007ffff0)
  printf("Unknown hword pread: %08x\n", A);
 return(0x00);
}

uint32 port_rword(uint32 A)
{
 //printf("PreadW: %08x\n", A);

 if(A >= 0x000 && A <= 0x0FF)
  return(FXPAD_Read32(A));
 else if(A >= 0x300 && A <= 0x5FF)
 {
  uint32 ret = port_rhword(A);
  ret |= port_rhword(A | 2) << 16;
 }
 else if(A >= 0x600 && A <= 0x6FF)
  return(KING_Read32(A));

 if(A != 0x007ffff0) printf("Unknown word pread: %08x\n", A);

 return(0x00);
}

void port_wbyte(uint32 A, uint8 V)
{
  //printf("PwriteB: %08x %02x\n", A, V);
  if(A >= 0x000 && A <= 0x0FF)
   FXPAD_Write8(A, V);
  else if(A >= 0x100 && A <= 0x1FF)
  {
   SoundBox_Write(A, V);
  }
  else if(A >= 0x200 && A <= 0x2FF)
  {
   RAINBOW_Write8(A, V);
  }
  else if(A >= 0x300 && A <= 0x3FF) // FXVCE
  {
   //printf("VCEZort: %04x %04x\n", A, V);
   FXVCE_Write8(A, V);
  }
  else if(A >= 0x400 && A <= 0x4FF) // VDC-A
  {
   printf("VDC A Byte Write: %04x %02x\n", A, V);
   if(!(A & 3)) lastchoo[0] = V;
   FXVDC_Write(fx_vdc_chips[0], ((A & 4) >> 1) | (A & 1), V);
  }
  else if(A >= 0x500 && A <= 0x5FF) // VDC-B
  {
   printf("VDC B Byte Write: %04x %02x\n", A, V);
   if(!(A & 3)) lastchoo[1] = V;
   FXVDC_Write(fx_vdc_chips[1], ((A & 4) >> 1) | (A & 1), V);
  }
  else if(A >= 0x600 && A <= 0x6FF)
  {
   KING_Write8(A, V);
  }
  else if(A >= 0xc00 && A <= 0xCFF)
  {
   printf("BMC8Write: %04x %02x\n", A, V);
  }
  else
   printf("PortB: %08x %02x\n", A, V);
}

void port_whword(uint32 A, uint16 V)
{
  if(A >= 0x000 && A <= 0x0FF)
   FXPAD_Write16(A, V);
  else if(A >= 0x100 && A <= 0x1FF)
  {
   SoundBox_Write(A, V & 0xFF);
   SoundBox_Write(A | 1, V >> 8);
  }
  else if(A >= 0x200 && A <= 0x2FF)
  {
   RAINBOW_Write16(A, V);
  }
  else if(A >= 0x300 && A <= 0x3FF)
  {
   FXVCE_Write8(A, V);
   FXVCE_Write8(A | 1, V >> 8);
  }
  else if(A >= 0x400 && A <= 0x4FF) // VDC-A
  {
   if(!(A & 4)) lastchoo[0] = V;
   FXVDC_Write(fx_vdc_chips[0], ((A & 4) >> 1) | 0, V);
   FXVDC_Write(fx_vdc_chips[0], ((A & 4) >> 1) | 1, V >> 8);
  }
  else if(A >= 0x500 && A <= 0x5FF) // VDC-B
  {
   if(!(A & 4)) lastchoo[1] = V;
   FXVDC_Write(fx_vdc_chips[1], ((A & 4) >> 1) | 0, V);
   FXVDC_Write(fx_vdc_chips[1], ((A & 4) >> 1) | 1, V >> 8);
  }
  else if(A >= 0x600 && A <= 0x6FF)
  {
   KING_Write16(A, V);
  }
  else if(A >= 0x800 && A <= 0x8FF) // ?? LIP writes here
  {

  }
  else if(A >= 0xc00 && A <= 0xCFF)
  {
   switch(A & 0xC0)
   {
    case 0x80: BackupControl = V & 0x3; break;
    default: printf("PortH: %08x %04x\n", A, V); break;
   }
  }
  else if(A >= 0xe00 && A <= 0xeff)
  { 
   PCFXIRQ_Write16(A, V);
  }
  else if(A >= 0xF00 && A <= 0xFFF)
  {
   FXTIMER_Write16(A, V);
  }
  else
  {
   //printf("PortH: %08x %04x\n", A, V);
  }
}

void port_wword(uint32 A, uint32 V)
{
  //printf("PwriteW: %08x %08x\n", A, V);

  if(A >= 0x000 && A <= 0x0FF)
   FXPAD_Write32(A, V);
  else if(A >= 0x100 && A <= 0x1FF)
  {
   SoundBox_Write(A, V & 0xFF);
   SoundBox_Write(A | 1, (V >> 8) & 0xFF);
   SoundBox_Write(A | 2, (V >> 16) & 0xFF);
   SoundBox_Write(A | 3, (V >> 24) & 0xFF);
  }
  else if(A >= 0x200 && A <= 0x2FF)
  {
   RAINBOW_Write32(A, V);
  }
  else if(A >= 0x300 && A <= 0x5FF)
  {
   port_whword(A, V);
   port_whword(A | 2, V >> 16);
  }
  else if(A >= 0x600 && A <= 0x6FF)
  {
   KING_Write32(A, V);
  }
  else if(A >= 0xc00 && A <= 0xCFF)
  {
   printf("BMC32Write: %04x %08x\n", A, V);
  }
  else if(A >= 0xe00 && A <= 0xeff)
  {
   PCFXIRQ_Write16(A, V);
   PCFXIRQ_Write16(A | 2, V >> 16);
  }
  else
   printf("PortW: %08x %02x\n", A, V);

}


static int needrew = 0;

static void DoRewind(void)
{
 needrew = 1;
}

static void Emulate(uint32 *pXBuf, MDFN_Rect *LineWidths, uint32 *pHLOverlayBuf, MDFN_Rect *HLOverlayLineWidths, int16 **SoundBuf, int32 *SoundBufSize, int skip)
{
 int didrew;

 MDFNGameInfo->fb = pXBuf;
 FXPAD_Frame();

 didrew = MDFN_StateEvil(needrew);

 MDFNMP_ApplyPeriodicCheats();


 KING_RunFrame(fx_vdc_chips, pXBuf, LineWidths, pHLOverlayBuf, HLOverlayLineWidths, skip);
 *SoundBuf = SoundBox_Flush(SoundBufSize, didrew);

 needrew = 0;
}

static void PCFX_Reset(void)
{
 if(!PCFXGA)
  memset(RAM, 0, 2048 * 1024);

 FXVDC_Reset(fx_vdc_chips[0]);
 FXVDC_Reset(fx_vdc_chips[1]);

 v810_reset();
 if(PCFXGA)
 {
  v810_setPC(PCFXGACodeSegmentAddress);

  P_REG[0x3] = 0x180000;		 // Stack pointer
  P_REG[0x4] = PCFXGAGlobalDataSegmentAddress; // Global pointer
  P_REG[0x5] = PCFXGACodeSegmentAddress; // Text pointer
  P_REG[31] = 0xFFFFE134;
  S_REG[PSW] =  0x00000000;
 }
}

static void PCFX_Power(void)
{
 PCFX_Reset();
}


static bool LoadCommon(void)
{
 FILE *fp;
 std::string biospath = MDFN_GetSettingS("pcfx.bios");

 if(!(fp = fopen(biospath.c_str(), "rb")))
 {
  MDFN_PrintError(_("Could not open BIOS file \"%s\": %s\n"), biospath.c_str(), strerror(errno));
  return(0);
 }

 BIOSROM = (uint8 *)malloc(1024 * 1024); // 1MB
 RAM = (uint8 *)malloc(2048 * 1024); // 2MB

 if(fread(BIOSROM, 1024 * 1024, 1, fp) < 1)
 {
  MDFN_PrintError(_("Error reading BIOS file \"%s\": %s\n"), biospath.c_str(), strerror(errno));
  free(BIOSROM);
  free(RAM);
  return(0);
 }
 fclose(fp);

 fx_vdc_chips[0] = FXVDC_Init(12);
 fx_vdc_chips[1] = FXVDC_Init(14);

 KING_Init();
 SoundBox_Init();

 MDFNGameInfo->fps = (uint32)((double)7159090.90909090 / 455 / 263 * 65536 * 256);
 MDFNGameInfo->soundchan = 2;


 MDFNMP_Init(1024 * 1024, ((uint64)1 << 32) / (1024 * 1024));
 MDFNMP_AddRAM(2048 * 1024, 0x00000000, RAM);

 // Initialize backup RAM
 memset(BackupRAM, 0, sizeof(BackupRAM));
 static const uint8 BRInit00[] = { 0x24, 0x8A, 0xDF, 0x50, 0x43, 0x46, 0x58, 0x53, 0x72, 0x61, 0x6D, 0x80,
                                   0x00, 0x01, 0x01, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0xF9, 0x03, 0x00,
                                   0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
                                  };
 static const uint8 BRInit80[] = { 0xF9, 0xFF, 0xFF };
 memcpy(BackupRAM + 0x00, BRInit00, sizeof(BRInit00));
 memcpy(BackupRAM + 0x80, BRInit80, sizeof(BRInit80));

 gzFile savefp;
 if((savefp = gzopen(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav").c_str(), "rb")))
 {
  gzread(savefp, BackupRAM, 0x8000);
  gzclose(savefp);
 }

 return(1);
}

typedef struct
{
 const char *name;   // The game name.  DUUUHHHHHHAAAAAAAHAHAHAHA
 const char *MD5Sexy[2]; // MD5 hash string of first two sectors of the first data track
} CDGameEntry;

// Don't change the case of the MD5 strings in this list, as they are directly MD5'd together
// to create the special game set md5 string
static CDGameEntry GameList[] = 
{
 { "Aa! Megami Sama", { "a93ae00cc1c5084ea39c101622d2a06f", "a93ae00cc1c5084ea39c101622d2a06f" } },
 { "All Japan Female Pro Wrestle - Queen of Queens", { "40c3804754a188ae98a34d7a6d6601dc", NULL } }, // todo
 { "Anime Freak FX Vol 5", { "048c0781e7aac561edff13b3f4906852", "cf40cc69e529f8b7adf98fb497aea3e8" } },
 { "Anime Freak FX Vol 6", { "9e15355ad93018f2107a9786887681f8", "277faf579939a40787853775a3818193" } },
 { "Last Imperial Prince", { "ac6748228447390c8b250ca843645d9e", "f6b6c9a54ed9aaab387c2c6e5592b240" } },
 { "Zoku Hatukoi Monogatari", { "279dab0f1c23f8171ebec938e406b6f6", "279dab0f1c23f8171ebec938e406b6f6" } },


 // Now list single-CD games
 { "Angelique Special", { "f2a1f345f5b41871606acd8b5ed9435b", NULL } },
 { "Angelique Special 2", { "0c96497b3387f84b3d2934c7dc99466a", NULL } },
 { "Anime Freak FX Vol 1", { "e99c34a7432f116bbbda6c48a06d5fe4", NULL } },
 { "Anime Freak FX Vol 2", { "ab910f4d822bcb1caf67e82e3e95ef08", NULL } },
 { "Anime Freak FX Vol 3", { "3c0e14ee0ca1caf95f17c486e9bbcf58", NULL } },
 { "Anime Freak FX Vol 4", { "88c8a4094b804b6e0cc87e4cf6042118", NULL } },
 { "Blue Breaker", { "89d71784f3cdf5cb38e401d5b26e038c", NULL } },

 { "Chip Chan Kick", { "3634ffcc02ce993b5a99d80f2fdfb3d3", NULL } },
 { "Cutey Honey FX", { "072194f9776c0d98196b6734fc08fdf4", NULL } },
 { "Farland Story FX", { "b4d5aa2c0a27c6ab5e55b7b6ca4c9604", NULL } },
 { "Kishin Douji Zenki Vajura Fight FX", { "ce3447d2a87d21b166a3c4284107b313", NULL } },
 { "Makeruna! Makendou Z", { "f6c34a22759b03b8ac4c1d9d29a778c3", NULL } },
 { "Miraculum", { "16d135106cb0bc4d6a5fd4d6ef5cc133", NULL } },
 { "Return to Zork", { "c2b1c9a6ae3fac1304d671561619ba88", NULL } },
 { "Sotsugyou II - Neo Generation FX", { "9a2e05a8a0a812fafd746ff20cf43534", NULL } },
 { "Sparkling Feather", { "c55568d1ef66a511c4c9fa5db347a086", NULL } },
 { "Tekipaki Working Love", { "25ae8aefb89f3294e084af8cbd00e5ac", NULL } },
 { "Tyoushin Heiki Zeroigar", { "5e77cfe28246effcc747a57b54b6fe0f", NULL } },
 { "Wakusei-Koukitai Little Cats", { "b0a1cce48ba06c0ef6add495dc6d5be8", NULL } },
};

static void DoMD5CDVoodoo(void)
{
 bool DTFound = FALSE;
 uint32 track;
 uint8 CDIDMD5[16];

 // Find the first data track
 for(track = CDIF_GetFirstTrack(); track <= CDIF_GetLastTrack(); track++)
 {
  CDIF_Track_Format format;

  if(CDIF_GetTrackFormat(track, format) && format == CDIF_FORMAT_MODE1)
  {
   DTFound = 1;
   break;
  }
 }

 md5_context md5, md5_gameset;

 md5.starts();
 md5_gameset.starts();

 if(DTFound)
 {
  uint32 start_sector = CDIF_GetTrackStartPositionLBA(track);
  uint8 sector_buffer[2048];

  for(int x = 0; x < 128; x++)
  {
   memset(sector_buffer, 0, 2048);
   CDIF_ReadSector(sector_buffer, start_sector + x, 1);
   md5.update(sector_buffer, 2048);
   if(x < 2)
    md5_gameset.update(sector_buffer, 2048);
  }
 }

 md5.finish(MDFNGameInfo->MD5);
 md5_gameset.finish(CDIDMD5);

 std::string CDIDMD5_String = md5_context::asciistr(CDIDMD5, 0);
 bool GameFound = FALSE;
 
 for(int x = 0; x < sizeof(GameList) / sizeof(CDGameEntry); x++)
 {
  if(!strcasecmp(GameList[x].MD5Sexy[0], CDIDMD5_String.c_str()) || (GameList[x].MD5Sexy[1] && !strcasecmp(GameList[x].MD5Sexy[1], CDIDMD5_String.c_str())   ) )
  {
   MDFNGameInfo->name = (UTF8*)strdup(GameList[x].name);
   if(GameList[x].MD5Sexy[1]) // Only do the GameSet kludge if it's a multi-disc game
   {
    md5_context gid;

    gid.starts();
    gid.update((uint8*)GameList[x].MD5Sexy[0], strlen(GameList[x].MD5Sexy[0]));
    gid.update((uint8*)GameList[x].MD5Sexy[1], strlen(GameList[x].MD5Sexy[1]));

    gid.finish(MDFNGameInfo->GameSetMD5);
    MDFNGameInfo->GameSetMD5Valid = TRUE;
   }
   else
    MDFNGameInfo->GameSetMD5Valid = FALSE;

   GameFound = TRUE;
   break;
  }
 }


 MDFN_printf(_("CD ID MD5(first 4KiB):  0x%s\n"), md5_context::asciistr(CDIDMD5, 0).c_str());

 if(MDFNGameInfo->GameSetMD5Valid)
  MDFN_printf(_("GameSet MD5:	          0x%s\n"), md5_context::asciistr(MDFNGameInfo->GameSetMD5, 0).c_str());

 MDFN_printf(_("CD MD5(first 256KiB):   0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());
}


static int LoadCD(void)
{
 DoMD5CDVoodoo();

 if(!LoadCommon())
  return(0);

 MDFN_LoadGameCheats(0);

 PCFXGA = FALSE;
 PCFX_Power();

 return(1);
}

static int Load(const char *name, MDFNFILE *fp)
{
 uint8 header[0x40];

 MDFN_fseek(fp, 0, SEEK_SET);
 MDFN_fread(header, 1, 0x40, fp);

 if(memcmp(header, "HuEXE", 5))
 {
  return(-1);
 }

 MDFNGameInfo->GameSetMD5Valid = FALSE;
 if(!LoadCommon())
  return(0);

 uint32 NumSegments;
 uint32 AddressTableOffset;
 uint32 AddressTableSize;
 uint32 SymbolTableSize;
 uint32 SymbolTableOffset;

 NumSegments = MDFN_de32msb(header + 0xC);
 AddressTableOffset = MDFN_de32msb(header + 0x14);
 AddressTableSize = MDFN_de32msb(header + 0x18);
 SymbolTableSize = MDFN_de32msb(header + 0x1C);
 SymbolTableOffset = AddressTableOffset + AddressTableSize;

 printf("%d\n", NumSegments);
 for(int n = 0; n < NumSegments; n++)
 {
  uint8 subheader[0x30];
  uint32 SegmentOffset;
  uint32 SegmentSize;
  uint32 SegmentAddr;
  uint32 SegmentFlag;

  MDFN_fseek(fp, 0x40 + n * 0x30, SEEK_SET);
  MDFN_fread(subheader, 1, 0x30, fp);

  SegmentOffset = MDFN_de32msb(subheader + 0x10);
  SegmentSize = MDFN_de32msb(subheader + 0x14);
  SegmentFlag = MDFN_de32msb(subheader + 0x20);
  SegmentAddr = MDFN_de32msb(subheader + 0x24);

  if(!(SegmentFlag & 0x00000004))
  {
   memcpy(RAM + SegmentAddr, fp->data + SegmentOffset, SegmentSize);
   printf("%.16s %08x %08x %08x %02x %02x\n", subheader, SegmentAddr, SegmentSize, SegmentOffset, RAM[0x8000], *(uint8 *)(fp->data + SegmentOffset));
   if(!strncmp((char*)subheader, "cseg", 16))
    PCFXGACodeSegmentAddress = SegmentAddr;
   if(!strncmp((char *)subheader, "dseg", 16))
    PCFXGADataSegmentAddress = SegmentAddr;
   if(!strncmp((char *)subheader, "gdseg", 16))
    PCFXGAGlobalDataSegmentAddress = SegmentAddr;
  }
  else // MRAM fill?
  {
   printf("Fill: %08x %08x\n", SegmentAddr, SegmentSize);
//   memset(RAM + SegmentAddr, 0, SegmentSize);
  }
 }

 PCFXGA = TRUE;
 PCFX_Power();
 return(1);
}

static void CloseGame(void)
{
 gzFile fp;

 if((fp = gzopen(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav").c_str(), "wb6")))
 {
  gzwrite(fp, BackupRAM, 0x8000);
  gzclose(fp);
 }

 MDFN_FlushGameCheats(0);
}

static void DoSimpleCommand(int cmd)
{
 switch(cmd)
 {
  case MDFNNPCMD_RESET: PCFX_Reset(); break;
  case MDFNNPCMD_POWER: PCFX_Power(); break;
 }
}

static int StateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFARRAY(RAM, 0x200000),
  SFARRAY16(lastchoo, 2),
  SFVAR(BackupControl),
  SFARRAY(BackupRAM, 0x8000),
  SFEND
 };

 int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN");

 ret &= FXVDC_StateAction(sm, load, data_only, fx_vdc_chips[0], "VDC0");
 ret &= FXVDC_StateAction(sm, load, data_only, fx_vdc_chips[1], "VDC1");
 ret &= FXPAD_StateAction(sm, load, data_only);
 ret &= PCFXIRQ_StateAction(sm, load, data_only);
 ret &= KING_StateAction(sm, load, data_only);
 ret &= V810_StateAction(sm, load, data_only);
 ret &= FXTIMER_StateAction(sm, load, data_only);
 ret &= SoundBox_StateAction(sm, load, data_only);
 ret &= SCSICD_StateAction(sm, load, data_only);
 ret &= RAINBOW_StateAction(sm, load, data_only);

 if(load)
 {

 }

 return(ret);
}

static MDFNSetting PCFXSettings[] =
{
  { "pcfx.input.port1", gettext_noop("Select input device for input port 1."), MDFNST_STRING, "gamepad", NULL, NULL },
  { "pcfx.input.port2", gettext_noop("Select input device for input port 2."), MDFNST_STRING, "gamepad", NULL, NULL },
  { "pcfx.bios", gettext_noop("Path to the ROM BIOS"), MDFNST_STRING, "pcfx.bios PATH NOT SET" },
  { NULL }
};

#ifdef WANT_DEBUGGER
static RegType PCFXRegs0[] =
{
        { "PC", "Program Counter", 4 },
	{ "PR1", "Program Register 1", 4 },
	{ "HSP", "Program Register 2(Handler Stack Pointer)", 4 },
	{ "SP", "Program Register 3(Stack Pointer)", 4 },
	{ "GP", "Program Register 4(Global Pointer)", 4 },
	{ "TP", "Program Register 5(Text Pointer)", 4 },
	{ "PR6", "Program Register 6", 4 },
	{ "PR7", "Program Register 7", 4 },
	{ "PR8", "Program Register 8", 4 },
	{ "PR9", "Program Register 9", 4 },
	{ "PR10", "Program Register 10", 4 },
	{ "PR11", "Program Register 11", 4 },
	{ "PR12", "Program Register 12", 4 },
	{ "PR13", "Program Register 13", 4 },
	{ "PR14", "Program Register 14", 4 },
	{ "PR15", "Program Register 15", 4 },
        { "PR16", "Program Register 16", 4 },
        { "PR17", "Program Register 17", 4 },
        { "PR18", "Program Register 18", 4 },
        { "PR19", "Program Register 19", 4 },
        { "PR20", "Program Register 20", 4 },
        { "PR21", "Program Register 21", 4 },
        { "PR22", "Program Register 22", 4 },
        { "PR23", "Program Register 23", 4 },
        { "PR24", "Program Register 24", 4 },
        { "PR25", "Program Register 25", 4 },
        { "PR26", "Program Register 26(String Dest Bit Offset)", 4 },
        { "PR27", "Program Register 27(String Source Bit Offset)", 4 },
        { "", "", 0 },
};

static RegType PCFXRegs1[] =
{
        { "PR28", "Program Register 28(String Length)", 4 },
        { "PR29", "Program Register 29(String Dest)", 4 },
        { "PR30", "Program Register 30(String Source)", 4 },
        { "PR31", "Program Register 31(Link Pointer)", 4 },
        { "SR0", "Exception/Interrupt PC", 4 },
        { "SR1", "Exception/Interrupt PSW", 4 },
        { "SR2", "Fatal Error PC", 4 },
        { "SR3", "Fatal Error PSW", 4 },
        { "SR4", "Exception Cause Register", 4 },
        { "SR5", "Program Status Word", 4 },
        { "SR6", "Processor ID Register", 4 },
        { "SR7", "Task Control Word", 4 },
        { "SR24", "Cache Control Word", 4 },
        { "SR25", "Address Trap Register", 4 },
	{ "IMASK", "Interrupt Mask", 2 },
	{ "IPRIO0", "Interrupt Priority Register 0", 2 },
	{ "IPRIO1", "Interrupt Priority Register 1", 2 },
	{ "TIMCTRL", "Timer Control", 2 },
	{ "TIMPRD", "Timer Period", 2 },
	{ "TIMCNTR", "Timer Counter", 3 },
        { "MPROG", "Micro-program Control", 2 },
        { "BGMODE", "Background Mode", 2 },
        { "BGPRIO", "Background Priority", 2 },
        { "BGSCRM", "Background Scroll Mode", 2 },
        { "BGSIZ0", "Background 0 Size", 2 },
        { "BGSIZ1", "Background 1 Size", 2 },
        { "BGSIZ2", "Background 2 Size", 2 },
        { "BGSIZ3", "Background 3 Size", 2 },
        { "", "", 0 },
};

static RegType PCFXRegs2[] =
{
	{ "BGXSC0", "Background 0 X Scroll", 2 },
	{ "BGXSC1", "Background 1 X Scroll", 2 },
	{ "BGXSC2", "Background 2 X Scroll", 2 },
	{ "BGXSC3", "Background 3 X Scroll", 2 },
        { "BGYSC0", "Background 0 Y Scroll", 2 },
        { "BGYSC1", "Background 1 Y Scroll", 2 },
        { "BGYSC2", "Background 2 Y Scroll", 2 },
        { "BGYSC3", "Background 3 Y Scroll", 2 },
	{ "BGBAT0", "Background 0 BAT Address", 2 },
	{ "BGBATS", "Background SUB BAT Address", 2 },
	{ "BGBAT1", "Background 1 BAT Address", 2 },
	{ "BGBAT2", "Background 2 BAT Address", 2 },
	{ "BGBAT3", "Background 3 BAT Address", 2 },
        { "BGCG0", "Background 0 CG Address", 2 },
        { "BGCGS", "Background SUB CG Address", 2 },
        { "BGCG1", "Background 1 CG Address", 2 },
        { "BGCG2", "Background 2 CG Address", 2 },
        { "BGCG3", "Background 3 CG Address", 2 },
        { "RTCTRL", "Rainbow Transfer Control", 2 },
        { "RKRAMA", "Rainbow Transfer K-RAM Address", 3},
        { "RSTART", "Rainbow Transfer Start Position", 2},
        { "RCOUNT", "Rainbow Transfer Block Count", 2 },
        { "DMATAL", "DMA Transfer Address Latch", 3 },
        { "DMATA", "Current DMA Transfer Address", 3 },
        { "DMATS", "DMA Transfer Size", 4 },
        { "DMASTT", "DMA Status", 2 },
        { "VCEPRIO0", "VCE Priority 0", 2 },
        { "VCEPRIO1", "VCE Priority 1", 2 },
        { "", "", 0 },
};

static RegType PCFXRegs3[] =
{
	{ "VCEPICMODE", "VCE Picture Mode", 2},
	{ "Frame Cntr", "VCE Frame Counter", 2},
	{ "VCEPALRWOF", "VCE Palette R/W Offset", 2 },
	{ "VCEPALRWLA", "VCE Palette R/W Latch", 2 },
	{ "VCEPALOFS0", "VCE Palette Offset 0", 2 } ,
	{ "VCEPALOFS1", "VCE Palette Offset 1", 2 },
	{ "VCEPALOFS2", "VCE Palette Offset 2", 2 },
	{ "VCEPALOFS3", "VCE Palette Offset 3", 2 },
	{ "VCECCR", "VCE Fixed Color Register", 2 },
	{ "VCEBLE", "VCE Cellophane Setting Register", 2 },
	{ "VCESPBL", "VCE Sprite Cellophane Setting Register", 2 },
	{ "VCECOEFF0", "VCE Cellophane Coefficient 0(1A)", 2 },
        { "VCECOEFF1", "VCE Cellophane Coefficient 1(1B)", 2 },
        { "VCECOEFF2", "VCE Cellophane Coefficient 2(2A)", 2 },
        { "VCECOEFF3", "VCE Cellophane Coefficient 3(2B)", 2 },
        { "VCECOEFF4", "VCE Cellophane Coefficient 4(3A)", 2 },
        { "VCECOEFF5", "VCE Cellophane Coefficient 5(3B)", 2 },
	{ "ChromaKeyY", "VCE Chroma Key Y", 2 },
        { "ChromaKeyU", "VCE Chroma Key U", 2 },
        { "ChromaKeyV", "VCE Chroma Key V", 2 },

        { "MAWR-A", "Memory Write Address, VDC-A", 2 },
        { "MARR-A", "Memory Read Address, VDC-A", 2 },
        { "CR-A", "Control, VDC-A", 2 },
        { "RCR-A", "Raster Counter, VDC-A", 2 },
        { "BXR-A", "X Scroll, VDC-A", 2 },
        { "BYR-A", "Y Scroll, VDC-A", 2 },
        { "MWR-A", "Memory Width, VDC-A", 2 },


	{ "", "", 0 },
};

static RegType PCFXRegs4[] =
{
        { "HSR-A", "HSR, VDC-A", 2 },
        { "HDR-A", "HDR, VDC-A", 2 },
        { "VSR-A", "VSR, VDC-A", 2 },
        { "VDR-A", "VDR, VDC-A", 2 },

        { "VCR-A", "VCR, VDC-A", 2 },
        { "DCR-A", "DMA Control, VDC-A", 2 },
        { "SOUR-A", "VRAM DMA Source Address, VDC-A", 2 },
        { "DESR-A", "VRAM DMA Dest Address, VDC-A", 2 },
        { "LENR-A", "VRAM DMA Length, VDC-A", 2 },
        { "SATB-A", "SATB Update Address, VDC-A", 2 },

        { "MAWR-B", "Memory Write Address, VDC-B", 2 },
        { "MARR-B", "Memory Read Address, VDC-B", 2 },
        { "CR-B", "Control, VDC-B", 2 },
        { "RCR-B", "Raster Counter, VDC-B", 2 },
        { "BXR-B", "X Scroll, VDC-B", 2 },
        { "BYR-B", "Y Scroll, VDC-B", 2 },
        { "MWR-B", "Memory Width, VDC-B", 2 },

        { "HSR-B", "HSR, VDC-B", 2 },
        { "HDR-B", "HDR, VDC-B", 2 },
        { "VSR-B", "VSR, VDC-B", 2 },
        { "VDR-B", "VDR, VDC-B", 2 },

        { "VCR-B", "VCR, VDC-B", 2 },
        { "DCR-B", "DMA Control, VDC-B", 2 },
        { "SOUR-B", "VRAM DMA Source Address, VDC-B", 2 },
        { "DESR-B", "VRAM DMA Dest Address, VDC-B", 2 },
        { "LENR-B", "VRAM DMA Length, VDC-B", 2 },
        { "SATB-B", "SATB Update Address, VDC-B", 2 },
        { "", "", 0 },
};



static uint32 GfxMemPeek(uint32 A, unsigned int bsize, bool hl)
{
 uint32 ret = 0;

 for(int n = 0; n < bsize; n++)
 {
  uint8 zebyte = 0;

  if(A <= 0xFFFFF) // KRAM?
  {
   zebyte = KING_MemPeek(A);
  }
  else if(A >= 0x100000 && A <= (0x100000 + 262144))
  {
   zebyte = (fx_vdc_chips[(A & 0x100000) ? 1 : 0]->VRAM[(A >> 1) & 0xFFFF] >> ((A & 1) * 8)) & 0xFF;
  }

  ret |= zebyte << (n * 8);
  A++;
 }
 return(ret);
}

 // If hl is true, write to the underlying ROM or RAM directly, if present
static void GfxMemPoke(uint32 A, uint32 V, unsigned int bsize, bool hl)
{

}

static void ForceIRQ(int level)
{
 v810_int(level);
}

static DebuggerInfoStruct DBGInfo =
{
 { PCFXRegs0, PCFXRegs1, PCFXRegs2, PCFXRegs3, PCFXRegs4 },
 4,
 32,
 32,
 0x00000000,
 PCFXDBG_MemPeek,
 PCFXDBG_MemPoke,
 GfxMemPeek,
 GfxMemPoke,
 PCFXDBG_Disassemble,
 ForceIRQ,
 NULL, //NESDBG_GetVector,
 PCFXDBG_GetRegister,
 PCFXDBG_SetRegister,
 PCFXDBG_FlushBreakPoints,
 PCFXDBG_AddBreakPoint,
 PCFXDBG_SetCPUCallback,
 PCFXDBG_SetBPCallback,
 PCFXDBG_GetBranchTrace,
 KING_SetGraphicsDecode,
 KING_GetGraphicsDecodeBuffer,

};
#endif

MDFNGI EmulatedPCFX =
{
 "pcfx",
 #ifdef WANT_DEBUGGER
 &DBGInfo,
 #endif
 &PCFXInputInfo,
 Load,
 LoadCD,
 CloseGame,
 KING_ToggleLayer,
 "BG0\0BG1\0BG2\0BG3\0VDC-A BG\0VDC-A SPR\0VDC-B BG\0VDC-B SPR\0RAINBOW\0",
 NULL,
 NULL,
 NULL,
 StateAction,
 DoRewind,
 Emulate,
 KING_SetPixelFormat,
 FXPAD_SetInput,
 NULL,
 NULL,
 NULL,
 SoundBox_SetSoundMultiplier,
 SoundBox_SetSoundVolume,
 SoundBox_Sound,
 DoSimpleCommand,
 PCFXSettings,
 0,
 NULL,
 288,
 240,
 256, // Save state preview width
 1024 * sizeof(uint32),
 { 0, 8, 320, 224 },
 TRUE
};

