/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 1998 BERO 
 *  Copyright (C) 2002 Ben Parnell
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "types.h"
#include "x6502.h"
#include "fce.h"
#define INESPRIV
#include "ines.h"
#include "version.h"
#include "svga.h"
#include "general.h"
#include "state.h"
#include "file.h"
#include "memory.h"
#include "cart.h"
#include "crc32.h"
#include "cheat.h"

static uint8 *ROM=NULL;
uint8 *VROM=NULL;

static uint32 ROM_size;
uint32 VROM_size;

static int MMC_init(int type);
void (*MapClose)(void);
void (*MapperReset)(void);

static int MapperNo;
static int SaveGame=0;

static iNES_HEADER head;

/*  MapperReset() is called when the NES is reset(with the reset button).  
    Mapperxxx_init is called when the NES has been powered on.
*/

static void iNESGI(int h)
{
 switch(h)
 {
  case GI_RESETM2:
		if(MapperReset)
		 MapperReset();
		break;
  case GI_POWER:
		SetReadHandler(0x8000,0xFFFF,CartBR);
                MMC_init(MapperNo);
		break;
  case GI_CLOSE:
		{
	         FILE *sp;
	        
	 	 if(ROM) {free(ROM);ROM=0;}
		 if(VROM) {free(VROM);VROM=0;}

        	 if(SaveGame)
        	 {
	 	  char *soot;
	          SaveGame=0;
	          soot=FCEU_MakeFName(FCEUMKF_SAV,0,"sav");
	          sp=fopen(soot,"wb");
	          if (sp==NULL)
 	           FCEU_PrintError("WRAM file \"%s\" cannot be written to.\n",soot);
	          else
	          {
	           fwrite(WRAM,8192,1,sp);
	           if(MapperNo==5)
	           {
	            extern uint8 MMC5WRAMsize;
	            if(MMC5WRAMsize==4)
	             fwrite(MapperExRAM+8192,32768-8192,1,sp);
	           }
	           fclose(sp);
	          }
        	 }
	         if(MapClose) MapClose();
	        }
        	break;
     }
}

uint32 iNESGameCRC32;

struct CRCMATCH	{
	uint32 crc;
	char *name;
};

static void CheckBad(void)
{
 int x;
 #define CRCNT	6
 struct CRCMATCH tab[CRCNT]={
	{0x7095ac65,"Akumajo Densetsu"},
	{0x1bd7ed5a,"Gradius 2"},
	{0x81c3aa66,"Crisis Force"},
	{0xfe3088df,"Fire Emblem Gaiden"},
	{0xfa8339a5,"Bucky O'Hare"},
	{0x3c539d78,"Ganbare Goemon 2"}
	};
  for(x=0;x<CRCNT;x++)
   if(tab[x].crc == iNESGameCRC32)
   {
    FCEU_PrintError("The copy of the game you have loaded, %s, is a bad dump, has been hacked, or both.  It will not work correctly on FCE Ultra.  Use a good copy of the ROM image instead.",tab[x].name);
    break;
   }
}

struct INPSEL {
	uint32 crc32;
	int input1;
	int input2;
	int inputfc;
};

/* This is mostly for my personal use.  So HA. */
static void SetInput(void)
{
 #define SETINCNT	16
 static struct INPSEL moo[SETINCNT]=
	{
	 {0xad9c63e2,SI_GAMEPAD,-1,SIFC_SHADOW},	/* Space Shadow */
	 {0x24598791,-1,SI_ZAPPER,0},	/* Duck Hunt */
	 {0xff24d794,-1,SI_ZAPPER,0},   /* Hogan's Alley */
	 {0xbeb8ab01,-1,SI_ZAPPER,0},	/* Gumshoe */
	 {0xde8fd935,-1,SI_ZAPPER,0},	/* To the Earth */
	 {0x23d17f5e,SI_GAMEPAD,SI_ZAPPER,0},	/* The Lone Ranger */
	 {0x5112dc21,-1,SI_ZAPPER,0},  /* Wild Gunman */
	 {0x4318a2f8,-1,SI_ZAPPER,0},  /* Barker Bill's Trick Shooting */
	 {0x5ee6008e,-1,SI_ZAPPER,0},  /* Mechanized Attack */
	 {0x3e58a87e,-1,SI_ZAPPER,0},  /* Freedom Force */
	 {0x851eb9be,SI_GAMEPAD,SI_ZAPPER,0},	/* Shooting Range */
	 {0x32fb0583,-1,SI_ARKANOID,0}, /* Arkanoid(NES) */
	 {0x0f141525,-1,-1,SIFC_ARKANOID}, /* Arkanoid 2(J) */

	 {0xf7606810,-1,-1,SIFC_FKB},	/* Family BASIC 2.0A */
	 {0x895037bc,-1,-1,SIFC_FKB},	/* Family BASIC 2.1a */
	 {0xb2530afc,-1,-1,SIFC_FKB},	/* Family BASIC 3.0 */
	};
 int x;

 for(x=0;x<SETINCNT;x++)
  if(moo[x].crc32==iNESGameCRC32)
  {
   FCEUGameInfo.input[0]=moo[x].input1;
   FCEUGameInfo.input[1]=moo[x].input2;
   FCEUGameInfo.inputfc=moo[x].inputfc;
   break;
  }
 #undef SETINCNT
}

struct CHINF {
	uint32 crc32;
	int32 mapper;
	int32 mirror;
};

#define CHCNT 8
static void CheckHInfo(void)
{
 static struct CHINF moo[CHCNT]=
	{
	 {0x026c5fca,152,-1},	/* Saint Seiya Ougon Densetsu */
	 {0x0f141525,152,-1},	/* Arkanoid 2 (Japanese) */
	 {0xbba58be5,152,-1},	/* Family Trainer - Manhattan Police */
	 {0xb9b4d9e0,118,-1},	/* NES Play Action Football */
	 {0x78b657ac,118,-1},	/* Armadillo */
	 {0x37b62d04,118,-1},	/* Ys 3 */
	 {0x2705eaeb,234,-1},	/* Maxi 15 */
	 {0x404b2e8b,4,2},	/* Rad Racer 2 */
	};
 int tofix=0;
 int x;

 for(x=0;x<CHCNT;x++)
  if(moo[x].crc32==iNESGameCRC32)
  {
   if(MapperNo!=moo[x].mapper)
   {
    tofix|=1;
    MapperNo=moo[x].mapper;
   }
   if(moo[x].mirror>=0)
   {
    if(Mirroring!=moo[x].mirror)
    {
     tofix|=2;
     Mirroring=moo[x].mirror;
    }
   }
   break;
  }

 /* Games that use these iNES mappers tend to have the four-screen bit set
    when it should not be.
 */
 if((MapperNo==118 || MapperNo==24 || MapperNo==26) && (Mirroring==2))
 {
  Mirroring=0;
  tofix|=2;
 }
 if(tofix)
 {
  char gigastr[512];
  strcpy(gigastr,"The iNES header contains incorrect information.  For now, the information will be corrected in RAM.");
  if(tofix&1)
   sprintf(gigastr+strlen(gigastr),"  The mapper number should be set to %d.",MapperNo);
  if(tofix&2)
  {
   char *mstr[3]={"Horizontal","Vertical","Four-screen"};
   sprintf(gigastr+strlen(gigastr),"  Mirroring should be set to \"%s\".",mstr[Mirroring]);
  }
  
  FCEUD_PrintError(gigastr);
 }
}
#undef CHCNT

int iNESLoad(char *name, int fp)
{
        FILE *sp;

	if(FCEU_fread(&head,1,16,fp)!=16)
 	 return 0;

        if(memcmp(&head,"NES\x1a",4))
         return 0;

        if(!memcmp((char *)(&head)+0x7,"DiskDude",8))
        {
         memset((char *)(&head)+0x7,0,0x9);
        }

        if(!memcmp((char *)(&head)+0x7,"demiforce",9))
        {
         memset((char *)(&head)+0x7,0,0x9);
        }

        if(!memcmp((char *)(&head)+0xA,"Ni03",4))
        {
         if(!memcmp((char *)(&head)+0x7,"Dis",3))
          memset((char *)(&head)+0x7,0,0x9);
         else
          memset((char *)(&head)+0xA,0,0x6);
        }

        if(!head.ROM_size)
         head.ROM_size++;

        ROM_size = head.ROM_size;
        VROM_size = head.VROM_size;
	ROM_size=uppow2(ROM_size);

        if(VROM_size)
	 VROM_size=uppow2(VROM_size);

        MapperNo = (head.ROM_type>>4);
        MapperNo|=(head.ROM_type2&0xF0);
        Mirroring = (head.ROM_type&1);
	if(head.ROM_type&8) Mirroring=2;

        if(!(ROM=(uint8 *)FCEU_malloc(ROM_size<<14)))
	 return 0;
	
        if (VROM_size) 
         if(!(VROM=(uint8 *)FCEU_malloc(VROM_size<<13)))
	 {
	  free(ROM);
	  return 0;
	 }

        memset(ROM,0xFF,ROM_size<<14);
        if(VROM_size) memset(VROM,0xFF,VROM_size<<13);
        if(head.ROM_type&4) 	// Trainer
 	 FCEU_fread(WRAM+0x1000,512,1,fp);

	ResetCartMapping();
	SetupCartPRGMapping(0,ROM,ROM_size*0x4000,0);
        SetupCartPRGMapping(1,WRAM,8192,1);

        FCEU_fread(ROM,0x4000,head.ROM_size,fp);

	if(VROM_size)
 	{
	 FCEU_fread(VROM,0x2000,head.VROM_size,fp);
	 SetupCartCHRMapping(0,VROM,VROM_size*0x2000,0);
	}

        if(head.ROM_type2&1)
        {
	 FCEUGameInfo.type=GIT_VSUNI;
         pale=FindVSUniPalette();
        }

        printf("File %s loaded.\n",name);

	iNESGameCRC32=CalcCRC32(0,ROM,ROM_size<<14);
	if(VROM_size)
	 iNESGameCRC32=CalcCRC32(iNESGameCRC32,VROM,VROM_size<<13);
	CheckBad();
	SetInput();
	CheckHInfo();

        if(Mirroring==2)
         SetupCartMirroring(4,1,ExtraNTARAM);
        else
         SetupCartMirroring(Mirroring,0,0);

        if(MapperNo==5) DetectMMC5WRAMSize();

        if(head.ROM_type&2)
        {         
         char *soot;

         SaveGame=1;
         soot=FCEU_MakeFName(FCEUMKF_SAV,0,"sav");
         sp=fopen(soot,"rb");
         if(sp!=NULL)
         {
          if(fread(WRAM,1,8192,sp)==8192)
          {
           if(MapperNo==5)
           {
            extern uint8 MMC5WRAMsize;
            if(MMC5WRAMsize==4)
            {
             if(fread(MapperExRAM+8192,1,32768-8192,sp)==32768-8192)
             goto loaded;
            }
            else
             goto loaded;
           }
          else
           goto loaded;
          }
         goto notloaded;
         loaded:
         printf("  WRAM Save File \"%s\" loaded...\n",soot);
         notloaded:
         fclose(sp);
        }

   }
        printf("\n PRG ROM:  %3d x 16k\n CHR ROM:  %3d x  8k\n ROM CRC32:  %08lx\n Mapper:  %d\n Mirroring: %s\n",head.ROM_size,head.VROM_size,iNESGameCRC32,MapperNo,Mirroring==2?"None(Four-screen)":Mirroring?"Vertical":"Horizontal");
        if(head.ROM_type&2) puts(" Battery-backed.");
        if(head.ROM_type&4) puts(" Trained.");
	GameInterface=iNESGI;
        return 1;
}

#include	"banksw.h"


void FASTAPASS(1) onemir(uint8 V)
{
	if(Mirroring==2) return;
        if(V>1)
         V=1;
	Mirroring=0x10|V;
	setmirror(MI_0+V);
}

void FASTAPASS(1) MIRROR_SET2(uint8 V)
{
	if(Mirroring==2) return;
	Mirroring=V;
	setmirror(V);
}

void FASTAPASS(1) MIRROR_SET(uint8 V)
{
	if(Mirroring==2) return;
	V^=1;
	Mirroring=V;
	setmirror(V);
}

static void NONE_init(void)
{
        ROM_BANK16(0x8000,0);
        ROM_BANK16(0xC000,~0);

        if(VROM_size) 
	 VROM_BANK8(0);
        else
	 setvram8(CHRRAM);
}

int FindVSUniPalette(void)
{

        if (!(memcmp(ROM+(131044&(ROM_size*0x4000-1)),"CASTLE VANIA",12)))
                return 1;
        if (!(memcmp(ROM+(9983&(ROM_size*0x4000-1)),"EIIIEGG",7))) /* VS SMB*/
                return 2;
        if (!(memcmp(ROM+(28483&(ROM_size*0x4000-1)),"iopnHHJqSLn",11))) /* VS ICE */
                return 2;
        if (!(memcmp(ROM+(29664&(ROM_size*0x4000-1)),"<8887776644444444333333332222222222222222",41))) /* VS DUCK*/
        {
	 FCEUGameInfo.input[0]=SI_ZAPPER;
	 return 0;
	}
        if (!(memcmp(ROM+(4003&(ROM_size*0x4000-1)),"#*L",3))) /* VS Hogan */
        {
	 FCEUGameInfo.input[0]=SI_ZAPPER;
	 return 7;
	}
        if (!(memcmp(ROM+(32497&(ROM_size*0x4000-1)),"DDUDDDD3",8))) /* VS Dr Mario */
                return 3;
        if (!(memcmp(ROM+(32673&(ROM_size*0x4000-1)),"bBjBpBbjBq",10))) /* VS EB*/
                return 6;
        if (!(memcmp(ROM+(30379&(ROM_size*0x4000-1)),"SRRQQ",5))) /* VS Golf*/
                return 1;
        if (!(memcmp(ROM+(28848&(ROM_size*0x4000-1)),"YKKqqq",6))) /* VS Pinball*/
                return 7;
        if (!(memcmp(ROM+(42193&(ROM_size*0x4000-1)),"tsruvw",6))) /* VS Gradius*/
                return 7;
        if (!(memcmp(ROM+(2142&(ROM_size*0x4000-1)),"|||hi",5))) /* VS Platoon*/
                return 7;
        if (!(memcmp(ROM+(10&(ROM_size*0x4000-1)),"861128H",7))) /* VS Goonies*/
                return 4;
        if (!(memcmp(ROM+(0xA29&(ROM_size*0x4000-1)),"SLALOM",6))) /* VS Slalom */
                return 5;
        if (!(memcmp(ROM+(0x9E22&(ROM_size*0x4000-1)),"1986",4))) /* VS RBI Baseball */
                vsdip=0x20;
        if (!(memcmp(ROM+(0x140e&(ROM_size*0x4000-1)),"oAmAoAm",7))) /* VS Sky Kid */
                vsdip=0x20;

 return 0;
}
static int VSindex;

DECLFR(VSRead)
{
 switch(A)
 {
  case 0x5e00: VSindex=0;return 0xFF;
  case 0x5e01: switch(VSindex++)
 	       {
	       case 9: return 0x6F;
	       default: return 0xB4;
	       }
 }
 return 0xFF;
}



void (*MapInitTab[256])(void)=
{
	0,
	Mapper1_init,Mapper2_init,Mapper3_init,Mapper4_init,
	Mapper5_init,Mapper6_init,Mapper7_init,Mapper8_init,
	Mapper9_init,Mapper10_init,Mapper11_init,0,
	Mapper13_init,0,Mapper15_init,Mapper16_init,
	Mapper17_init,Mapper18_init,Mapper19_init,0,
	Mapper21_init,Mapper22_init,Mapper23_init,Mapper24_init,
	Mapper25_init,Mapper26_init,0,0,
	0,0,0,Mapper32_init,
	Mapper33_init,Mapper34_init,0,0,
	0,0,0,Mapper40_init,
	Mapper41_init,Mapper42_init,Mapper43_init,Mapper44_init,
	Mapper45_init,Mapper46_init,Mapper47_init,Mapper33_init,Mapper49_init,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,Mapper64_init,
	Mapper65_init,Mapper66_init,Mapper67_init,Mapper68_init,
	Mapper69_init,Mapper70_init,Mapper71_init,Mapper72_init,
	Mapper73_init,0,Mapper75_init,Mapper76_init,
	Mapper77_init,Mapper78_init,Mapper79_init,Mapper80_init,
	0,Mapper82_init,Mapper83_init,0,
	Mapper85_init,Mapper86_init,Mapper87_init,Mapper88_init,
	Mapper89_init,Mapper90_init,0,Mapper92_init,
	Mapper93_init,Mapper94_init,Mapper95_init,Mapper96_init,
	Mapper97_init,0,Mapper99_init,0,
	0,0,0,0,Mapper105_init,0,0,0,
	0,0,0,Mapper112_init,Mapper113_init,0,0,0,
	Mapper117_init,Mapper118_init,Mapper119_init,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,Mapper140_init,
	0,0,0,0,0,0,0,0,
	0,0,Mapper151_init,Mapper152_init,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,Mapper180_init,
	0,Mapper182_init,0,Mapper184_init,0,0,0,0,
	Mapper189_init,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,Mapper225_init,Mapper226_init,Mapper227_init,Mapper228_init,
	Mapper229_init,0,0,Mapper232_init,0,Mapper234_init,Mapper43_init,0,
	0,0,0,Mapper240_init,0,Mapper242_init,0,0,
	0,Mapper246_init,0,Mapper248_init,0,Mapper250_init,0,0,0,0,0
};

static DECLFW(BWRAM)
{
                WRAM[A-0x6000]=V;
}

static DECLFR(AWRAM)
{
                return WRAM[A-0x6000];
}

void (*MapStateRestore)(int version);
void iNESStateRestore(int version)
{
 int x;
 
 if(!MapperNo) return;

 for(x=0;x<4;x++)
  setprg8(0x8000+x*8192,PRGBankList[x]);

 if(VROM_size)
  for(x=0;x<8;x++) 
    setchr1(0x400*x,CHRBankList[x]);

 switch(Mirroring)
 {
   case 0:setmirror(MI_H);break;
   case 1:setmirror(MI_V);break;
   case 0x12:
   case 0x10:setmirror(MI_0);break;
   case 0x13:
   case 0x11:setmirror(MI_1);break;
 }
 if(MapStateRestore) MapStateRestore(version);
}

static int MMC_init(int type)
{
        int x;

        IRQa=IRQLatch=IRQLatch2=IRQCount=0;
        GameStateRestore=iNESStateRestore;
        MapClose=0;
	MapperReset=0;
        MapStateRestore=0;
        setprg8r(1,0x6000,0);
        SetReadHandler(0x6000,0x7FFF,AWRAM);
        SetWriteHandler(0x6000,0x7FFF,BWRAM);
        FCEU_CheatAddRAM(8,0x6000,WRAM);

        memset(mapbyte1,0,8);
        memset(mapbyte2,0,8);
        memset(mapbyte3,0,8);
        memset(mapbyte4,0,8);
 
        if(head.ROM_type&4)
        {
         memset(WRAM,0x00,4096);
         memset(WRAM+4096+512,0x00,4096-512);
        }
        else if(!(head.ROM_type&2)) memset(WRAM,0x00,8192);
	memset(CHRRAM,0,8192);
	memset(ExtraNTARAM,0,2048);

        if(type==5)
         memset(MapperExRAM,0,8192);
        else
         memset(MapperExRAM,0,0x4000);

        NONE_init();

	if(MapInitTab[type]) MapInitTab[type]();
        else if(type)
	{
 	 FCEU_PrintError("iNES mapper #%d is not supported at all.",type);
	}

	if(FCEUGameInfo.type==GIT_VSUNI)
         SetReadHandler(0x5e00,0x5e01,VSRead);
        if(type==5)
        {
          MMC5HackVROMMask=CHRmask4[0];
          MMC5HackExNTARAMPtr=MapperExRAM+0x6000;
          MMC5Hack=1;
          MMC5HackVROMPTR=VROM;
          MMC5HackCHRMode=0;
        }
        ResetExState();
        AddExState(WRAM, 8192, 0, "WRAM");
        if(type==13 || type==19 || type==5 || type==6 || type==69 || type==85)
         AddExState(MapperExRAM, 32768, 0, "MEXR");
        if(!VROM_size || type==6 || type==19 || type==119)
         AddExState(CHRRAM, 8192, 0, "CHRR");
        if(head.ROM_type&8)
         AddExState(ExtraNTARAM, 2048, 0, "EXNR");
	if(type)
	{
         AddExState(mapbyte1, 32, 0, "MPBY");
         AddExState(&Mirroring, 1, 0, "MIRR");
         AddExState(&IRQCount, 4, 1, "IRQC");
         AddExState(&IRQLatch, 4, 1, "IQL1");
         AddExState(&IRQLatch2, 4, 1, "IQL2");
         AddExState(&IRQa, 1, 0, "IRQA");
         AddExState(PRGBankList, 4, 0, "PBL");
         for(x=0;x<8;x++)
         {
          char tak[8];
          sprintf(tak,"CBL%d",x);         
          AddExState(&CHRBankList[x], 2, 1,tak);
         }
	}
        return 1;
}
