/****************************************************************************
 *  Cartridge Hardware support
 *
 *  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
 ***************************************************************************/
/*
	cart extra hardware emulation  
*/

#include "shared.h"

#define CART_CNT 22

/* Function prototypes */
void default_time_w(unsigned int address, unsigned int value);
void special_mapper_w(unsigned int address, unsigned int value);
void realtec_mapper_w(unsigned int address, unsigned int value);
void seganet_mapper_w(unsigned int address, unsigned int value);
unsigned int radica_mapper_r(unsigned int address);
void default_regs_w(unsigned int address, unsigned int value);
unsigned int default_regs_r(unsigned int address);
void special_regs_w(unsigned int address, unsigned int value);

/* Cart database entry */
typedef struct
{
	uint16 chk_1;		/* header checksum */
	uint16 chk_2;		/* real checksum */
	uint8 bank_start;	/* first mapped bank in $400000-$7fffff region */
	uint8 bank_end;		/* last mapped bank in $400000-$7fffff region */
	T_CART_HW cart_hw;	/* hardware description */
} T_CART_ENTRY;

/* Games that need extra hardware emulation:
	- copy protection device
	- custom ROM banking device
*/
T_CART_ENTRY rom_database[CART_CNT] =
{
/* Game no Kanzume Otokuyou */
	{0x0000,0xf9d1,0,0,{{0,0,0,0},{0,0,0,0},{0,0,0,0},0,0,seganet_mapper_w,0,0}},
/* RADICA (Volume 1) (not byteswapped) */
	{0x0000,0x2326,0,0,{{0,0,0,0},{0,0,0,0},{0,0,0,0},0,radica_mapper_r,0,0,0}},
/* RADICA (Volume 2) */
	{0x4f10,0x0836,0,0,{{0,0,0,0},{0,0,0,0},{0,0,0,0},0,radica_mapper_r,0,0,0}},
/* RADICA (Volume 1) */
	{0xf424,0x9f82,0,0,{{0,0,0,0},{0,0,0,0},{0,0,0,0},0,radica_mapper_r,0,0,0}},
/* Funny World & Balloon Boy */
	{0x0000,0x06ab,8,8,{{0,0,0,0},{0,0,0,0},{0,0,0,0},1,0,0,0,realtec_mapper_w}},
/* Whac-a-Critter */
	{0xffff,0xf863,8,8,{{0,0,0,0},{0,0,0,0},{0,0,0,0},1,0,0,0,realtec_mapper_w}},
/* Earth Defense */
	{0xffff,0x44fb,8,8,{{0,0,0,0},{0,0,0,0},{0,0,0,0},1,0,0,0,realtec_mapper_w}},
/* Super Mario 2 1998 */
	{0xffff,0x0474,0,0,{{0x0a,0,0,0},{0xffffff,0,0,0},{0xa13000,0,0,0},0,default_regs_r,0,0,0}},
/* Super Mario 2 1998 */
	{0x2020,0xb4eb,0,0,{{0x1c,0,0,0},{0xffffff,0,0,0},{0xa13000,0,0,0},0,default_regs_r,0,0,0}},
/* Supper Bubble Bobble */
	{0x0000,0x16cd,8,8,{{0x55,0x0f,0,0},{0xffffff,0xffffff,0,0},{0x400000,0x400002,0,0},0,0,0,default_regs_r,0}},
/* Mahjong Lover */
	{0x0000,0x7037,8,8,{{0x90,0xd3,0,0},{0xffffff,0xffffff,0,0},{0x400000,0x401000,0,0},0,0,0,default_regs_r,0}},
/* Lion King 2 */
	{0xffff,0x1d9b,8,8,{{0,0,0,0},{0xfffffd,0xfffffd,0,0},{0x400000,0x400004,0,0},0,0,0,default_regs_r,default_regs_w}},
/* Squirell King */
	{0x0000,0x8ec8,8,8,{{0,0,0,0},{0xfffffd,0xfffffd,0,0},{0x400000,0x400004,0,0},0,0,0,default_regs_r,default_regs_w}},
/* Rockman X3 */
	{0x0000,0x9d0e,8,8,{{0x0c,0x88,0,0},{0xffffff,0xffffff,0,0},{0xa13000,0x400004,0,0},0,default_regs_r,0,default_regs_r,0}},
/* A Bug's Life */
	{0x7f7f,0x2aad,0,0,{{0x28,0x1f,0x01,0},{0xffffff,0xffffff,0xffffff,0},{0xa13000,0xa13002,0xa1303e,0},0,default_regs_r,0,0,0}},
/* Lion King 3 */
	{0x0000,0x507c,12,15,{{0,0,0,0},{0xf0000e,0xf0000e,0xf0000e,0},{0x600000,0x600002,0x600004,0},0,0,0,default_regs_r,special_regs_w}},
/* Super King Kong 99 */
	{0x0000,0x7d6e,12,15,{{0,0,0,0},{0xf0000e,0xf0000e,0xf0000e,0},{0x600000,0x600002,0x600004,0},0,0,0,default_regs_r,special_regs_w}},
/* Elf Wor */
	{0x0080,0x3dba,8,8,{{0x55,0x0f,0xc9,0x18},{0xffffff,0xffffff,0xffffff,0xffffff},{0x400000,0x400002,0x400004,0x400006},0,0,0,default_regs_r,0}},
/* Huan Le Tao Qi Shu - Smart Mouse */
	{0x0000,0x1a28,8,8,{{0x55,0x0f,0xaa,0xf0},{0xffffff,0xffffff,0xffffff,0xffffff},{0x400000,0x400002,0x400004,0x400006},0,0,0,default_regs_r,0}},
/* Ya-Se Chuanshuo */
	{0xffff,0xd472,8,8,{{0x63,0x98,0xc9,0x18},{0xffffff,0xffffff,0xffffff,0xffffff},{0x400000,0x400002,0x400004,0x400006},0,0,0,default_regs_r,0}},
/* King of Fighter 98 */
	{0x0000,0xd0a0,9,9,{{0xaa,0xa0,0xf0,0xa0},{0xfc0000,0xffffff,0xffffff,0xffffff},{0x480000,0x4c82c0,0x4cdda0,0x4f8820},0,0,0,default_regs_r,0}},
/* Lian Huan Pao - Barver Battle Saga */
	{0x30b9,0x1c2a,8,8,{{0,0,0,0},{0,0,0,0},{0,0,0,0},0,0,0,default_regs_r,0}}
};


/* current cart hardware */
T_CART_HW cart_hw;

/************************************************************
					Cart Hardware initialization 
*************************************************************/

/* hardware that need to be reseted on power on */
void cart_hw_reset()
{
	/* Realtec mapper */
	if (cart_hw.realtec & 1)
	{
		int i;

		/* enable BOOTROM */
		for (i=0; i<8; i++) m68k_readmap_16[i]	= REALTEC_ROM;
		cart_hw.realtec |= 2;
	}
	
	/* SVP chip */
	if (svp) svp_reset();
}

/* cart hardware detection */
void cart_hw_init()
{
	int i;
	
	/**********************************************
					DEFAULT CART MAPPING 
	***********************************************/
	for (i=0; i<8; i++)
	{
		m68k_readmap_8[i]	= ROM;
		m68k_readmap_16[i]	= ROM;
		m68k_writemap_8[i]	= UNUSED;
		m68k_writemap_16[i]	= UNUSED;
	}

	for (i=8; i<16; i++)
	{
		m68k_readmap_8[i]	= UNUSED;
		m68k_readmap_16[i]	= UNUSED;
		m68k_writemap_8[i]	= UNUSED;
		m68k_writemap_16[i]	= UNUSED;
	}
	

	/**********************************************
					EXTERNAL RAM 
	***********************************************/
	sram_init();
	eeprom_init();
	if (sram.on)
	{
		int offset = sram.start >> 19;
		if (sram.custom)
		{
			m68k_readmap_8[offset]		= EEPROM;
			m68k_readmap_16[offset]		= EEPROM;
			m68k_writemap_8[offset]		= EEPROM;
			m68k_writemap_16[offset]	= EEPROM;
		}
		else
		{
			m68k_readmap_8[offset]		= SRAM;
			m68k_readmap_16[offset]		= SRAM;
			m68k_writemap_8[offset]		= SRAM;
			m68k_writemap_16[offset]	= SRAM;
		}
	}

	/**********************************************
					SVP CHIP 
	***********************************************/
	svp = NULL;
	if ((strstr(rominfo.product,"MK-1229") != NULL) ||
		(strstr(rominfo.product,"G-7001") != NULL))
	{
		svp_init();
		m68k_readmap_16[6]	= SVP_DRAM;
		m68k_writemap_16[6]	= SVP_DRAM;
		m68k_readmap_16[7]	= SVP_CELL;
		m68k_writemap_16[7]	= SVP_CELL;
	}

	/**********************************************
					SEGA MENACER 
	***********************************************/
	if (strstr(rominfo.product,"MK-1658") != NULL)
	{
		/* PORT B by default */
		input.system[0] = NO_SYSTEM;
		input.system[1] = SYSTEM_MENACER;
	}
	else
	{
		/* PORT A default */
		if (sys_type[0] == 0)	   input.system[0] = SYSTEM_GAMEPAD;
		else if (sys_type[0] == 1) input.system[0] = SYSTEM_TEAMPLAYER;
		else if (sys_type[0] == 2) input.system[0] = NO_SYSTEM;

		/* PORT B default */
		if (sys_type[1] == 0)	   input.system[1] = SYSTEM_GAMEPAD;
		else if (sys_type[1] == 1) input.system[1] = SYSTEM_TEAMPLAYER;
		else if (sys_type[1] == 2) input.system[1] = NO_SYSTEM;
	}

	/**********************************************
					J-CART 
	***********************************************/
	j_cart = 0;
	if (((strstr(rominfo.product,"00000000") != NULL) && (rominfo.checksum == 0x168b)) ||	/* Super Skidmarks, Micro Machines Military*/
		((strstr(rominfo.product,"00000000") != NULL) && (rominfo.checksum == 0x165e)) ||	/* Pete Sampras Tennis (1991), Micro Machines 96 */
		((strstr(rominfo.product,"00000000") != NULL) && (rominfo.checksum == 0xcee0)) ||	/* Micro Machines Military (bad) */
		((strstr(rominfo.product,"00000000") != NULL) && (rominfo.checksum == 0x2c41)) ||	/* Micro Machines 96 (bad) */
		((strstr(rominfo.product,"XXXXXXXX") != NULL) && (rominfo.checksum == 0xdf39)) ||	/* Sampras Tennis 96 */
		((strstr(rominfo.product,"T-123456") != NULL) && (rominfo.checksum == 0x1eae)) ||	/* Sampras Tennis 96 */
		((strstr(rominfo.product,"T-120066") != NULL) && (rominfo.checksum == 0x16a4)) ||	/* Pete Sampras Tennis (1994)*/
		 (strstr(rominfo.product,"T-120096") != NULL))										/*  Micro Machines 2 */
	{
		if (genromsize <= 0x380000)	/* just to be sure ! */
		{
			j_cart = 1;
			m68k_readmap_16[7]	= J_CART;
			m68k_writemap_16[7]	= J_CART;
		}
	}

	/**********************************************
				Mappers & HW registers 
	***********************************************/
	memset(&cart_hw, 0, sizeof(cart_hw));

	/* default write handler for !TIME signal */
	cart_hw.time_w = default_time_w;

	/* search for game into database */
	for (i=0; i < CART_CNT + 1; i++)
	{
		/* known cart found ! */
		if ((rominfo.checksum == rom_database[i].chk_1) &&
			(realchecksum == rom_database[i].chk_2))
		{
			/* retrieve hardware information */
			memcpy(&cart_hw, &(rom_database[i].cart_hw), sizeof(cart_hw));

			/* initialize memory handlers for $400000-$7fffff region */
			int j = rom_database[i].bank_start;
			while (j <= rom_database[i].bank_end)
			{
				if (cart_hw.regs_r) m68k_readmap_8[j]  = CART_HW;
				if (cart_hw.regs_w) m68k_writemap_8[j] = CART_HW;
				j++;
			}
			i = CART_CNT + 1;
		}
	}
}

/************************************************************
					MAPPER handlers 
*************************************************************/
/* 
	"official" ROM/RAM switch
*/
static inline void sega_mapper_w(unsigned int address, unsigned int value)
{
	uint8 bank = (address >> 1) & 7;
	
	switch (bank)
	{
		case 0:
		{
			/* ROM/RAM switch (Phantasy Star IV, Story of Thor/Beyond Oasis, Sonic 3 & Knuckles) */
			if (value & 1)
			{
				/* SRAM is mapped above 200000h */
				m68k_readmap_8[4]	= SRAM;
				m68k_readmap_16[4]	= SRAM;

				if (value & 2)
				{
					/* SRAM write protected */
					m68k_writemap_8[4]	= UNUSED;
					m68k_writemap_16[4]	= UNUSED;
				}
				else
				{
					/* SRAM write enabled */
					m68k_writemap_8[4]	= SRAM;
					m68k_writemap_16[4]	= SRAM;
				}
			}
			else
			{
				/* ROM is mapped above 200000h */
				m68k_readmap_8[4]	= ROM;
				m68k_readmap_16[4]	= ROM;
				m68k_writemap_8[4]	= UNUSED;
				m68k_writemap_16[4]	= UNUSED;
			}
			break;
		}
		
		default:
			/* ROM Bankswitch (Super Street Fighter 2)
			   documented by Bart Trzynadlowski (http://www.trzy.org/files/ssf2.txt) 
			*/
			rom_readmap[bank] = &cart_rom[value << 19];
			break;
	}
}

/* 
	custom ROM Bankswitch used by pirate "Multi-in-1" carts
	(documented by Haze)
*/
static inline void multi_mapper_w(unsigned int address, unsigned int value)
{
	int i;
	uint32 bank_addr = (address & 0x3F) << 16;

	/* those games are generally not bigger than 1MB but it's safer to update all 512K banks */
	for (i=0; i<8; i++)
	{
		if (bank_addr >= genromsize) return;
		rom_readmap[i] = &cart_rom[bank_addr];
		bank_addr += 0x80000;
	}
}

/* 
	Special ROM Bankswitch used for copy protection
	Used by unlicensed cartridges (Lion King III, Super King Kong 99)
	(documented by Haze)
*/
void special_mapper_w(unsigned int address, unsigned int value)
{
	rom_readmap[0] = &cart_rom[value << 15];
}

/* 
	Realtec ROM Bankswitch (Earth Defend, Balloon Boy & Funny World, Whac-A-Critter)
	(documented by TascoDeluxe)
*/
void realtec_mapper_w(unsigned int address, unsigned int value)
{
	uint32 base_addr;

	if (cart_hw.realtec & 2)
	{
		int i;
		cart_hw.realtec &= ~2;
		
		/* disable Realtec BOOTROM	*/
		for (i=0; i<8; i++)
		{
			m68k_readmap_8[i]	= ROM;
			m68k_readmap_16[i]	= ROM;
		}
	}

	switch (address)
	{
		case 0x404000:	/* three lower bits of ROM base address */
			cart_hw.regs[0] = value;
			base_addr = (value | ((cart_hw.regs[1] & 6) << 2)) << 17;
			rom_readmap[0] = &cart_rom[base_addr];
			return;

		case 0x400000:	/* two higher bits of ROM base address */
			cart_hw.regs[1] = value;
			base_addr = ((cart_hw.regs[0] & 7) | ((value & 6) << 2)) << 17;
			rom_readmap[0] = &cart_rom[base_addr];
			return;

		case 0x402000:	/* size of ROM range to map */
			cart_hw.regs[2] = value;
			return;

		default:
			//m68k_unused_8_w(address, value);
			return;
	}
}


/* Game no Kanzume Otokuyou ROM Mapper */
void seganet_mapper_w(unsigned int address, unsigned int value)
{
	int i;
	switch (address & 0xff)
	{
		case 0x01:
			if (value & 1)
			{
				/* ROM disabled */
				for (i=0; i<8; i++)
				{
					m68k_readmap_8[i]	= UNUSED;
					m68k_readmap_16[i]	= UNUSED;
				}
			}
			else
			{
				/* ROM enabled */
				for (i=0; i<8; i++)
				{
					m68k_readmap_8[i]	= ROM;
					m68k_readmap_16[i]	= ROM;
				}
			}
			break;;

		case 0xf1:
			if (value & 1)
			{
				/* ROM Write protected*/
				for (i=0; i<8; i++)
				{
					m68k_writemap_8[i]	= UNUSED;
					m68k_writemap_16[i]	= UNUSED;
				}
			}
			else
			{
				/* ROM Write enabled */
				for (i=0; i<8; i++)
				{
					m68k_writemap_8[i]	= ROM;
					m68k_writemap_16[i]	= ROM;
				}
			}
			break;

		default:
			break;
	}
}

/*
	RADICA ROM Bankswitch
	(documented by Haze)
*/
unsigned int radica_mapper_r(unsigned int address)
{
	/* 64KB ROM banks */
	uint8 bank = (address >> 1) & 0x3F;
	rom_readmap[0] = &cart_rom[bank * 0x10000];
	rom_readmap[1] = &cart_rom[bank * 0x10000 + 0x80000]; /* ROM is max. 1MB */
	return 0xff;
}


/************************************************************
					default !TIME signal handler 
*************************************************************/

/* default ROM bankswitch */
void default_time_w(unsigned int address, unsigned int value)
{
	if ((address & 0xf1) == 0xf1) sega_mapper_w(address, value);
	else if (address < 0xa13040) multi_mapper_w(address, value);
}

/************************************************************
					Internal register handlers
*************************************************************/

unsigned int default_regs_r(unsigned int address)
{
	uint8 i;
	for (i=0; i<4; i++)
	{
		if ((address & cart_hw.mask[i]) == cart_hw.addr[i])
			return cart_hw.regs[i];
	}

	/* unused */
	return -1;
}

void default_regs_w(unsigned int address, unsigned int value)
{
	uint8 i;
	for (i=0; i<4; i++)
	{
		if ((address & cart_hw.mask[i]) == cart_hw.addr[i])
		{
			cart_hw.regs[i] = value;
		}
	}
}

/* special register behaviour (Lion King III, Super Donkey Kong  99) */
void special_regs_w(unsigned int address, unsigned int value)
{
	/* ROM bankswitch */
	if ((address >> 16) > 0x6f)
	{
		special_mapper_w(address, value);
		return;
	}

	/* write regs */
	default_regs_w(address, value);

	/* bitswapping (documented by Haze) */
	uint8 temp = cart_hw.regs[0];
	switch (cart_hw.regs[1])
	{					
		case 1:
			cart_hw.regs[2] = (temp >> 1);
			return;

		case 2:
			cart_hw.regs[2] = ((temp >> 4) | ((temp & 0x0F) << 4));
			return;

		default:
			cart_hw.regs[2] = (((temp >> 7) & 0x01) | ((temp >> 5) & 0x02) |
				  			   ((temp >> 3) & 0x04) | ((temp >> 1) & 0x08) |
							   ((temp << 1) & 0x10) | ((temp << 3) & 0x20) |
								((temp << 5) & 0x40) | ((temp << 7) & 0x80));
			return;
	}
}
