#include <stdio.h>
#include "driver.h"
#include "image.h"
#include "includes/sms.h"
#include "video/smsvdp.h"
#include "sound/2413intf.h"
#include "machine/eeprom.h"

#define CF_CODEMASTERS_MAPPER	0x01
#define CF_KOREAN_MAPPER	0x02
#define CF_93C46_EEPROM		0x04
#define CF_ONCART_RAM		0x08

#define MAX_CARTRIDGES		16

static UINT8 smsBiosPageCount;
static UINT8 smsFMDetect;
static UINT8 smsVersion;
static int smsPaused;

static UINT8 biosPort;

static UINT8 *BIOS;

static UINT8 *sms_mapper_ram;
static UINT8 sms_mapper[4];
static UINT8 *sms_banking_bios[5]; /* we are going to use 1-4, same as bank numbers */
static UINT8 *sms_banking_cart[5]; /* we are going to use 1-4, same as bank numbers */
static UINT8 *sms_banking_none[5]; /* we are going to use 1-4, same as bank numbers */
static UINT8 ggSIO[5] = { 0x7F, 0xFF, 0x00, 0xFF, 0x00 };
static UINT8 sms_store_control = 0;

static UINT8 sms_input_port0;
static UINT8 sms_input_port1;

/* Data needed for Rapid Fire Unit support */
static emu_timer	*rapid_fire_timer;
static UINT8 rapid_fire_state_1;
static UINT8 rapid_fire_state_2;

/* Data needed for Paddle Control controller */
static UINT32 last_paddle_read_time;
static UINT8 paddle_read_state;

/* Data needed for Sports Pad controller */
static UINT32 last_sports_pad_time_1;
static UINT32 last_sports_pad_time_2;
static UINT8 sports_pad_state_1;
static UINT8 sports_pad_state_2;
static UINT8 sports_pad_last_data_1;
static UINT8 sports_pad_last_data_2;
static UINT8 sports_pad_1_x;
static UINT8 sports_pad_1_y;
static UINT8 sports_pad_2_x;
static UINT8 sports_pad_2_y;

static struct {
	UINT8	*ROM;			/* Pointer to ROM image data */
	UINT32	size;			/* Size of the ROM image */
	UINT8	features;		/* on-cartridge special hardware */
	UINT8	*cartSRAM;		/* on-cartridge SRAM */
	UINT8	sram_save;		/* should be the contents of the on-cartridge SRAM be saved */
	UINT8	*cartRAM;		/* additional on-cartridge RAM (64KB for Ernie Els Golf) */
	UINT32	ram_size;		/* size of the on-cartridge RAM */
	UINT8	ram_page;		/* currently swapped in cartridge RAM */
} sms_cartridge[MAX_CARTRIDGES];
static UINT8	sms_current_cartridge;

static TIMER_CALLBACK( rapid_fire_callback ) {
	rapid_fire_state_1 ^= 0xFF;
	rapid_fire_state_2 ^= 0xFF;
}

static WRITE8_HANDLER( sms_input_write ) {
	switch( offset ) {
	case 0:
		switch( readinputport(11) & 0x0F ) {
		case 0x03:	/* Sports Pad */
			if ( data != sports_pad_last_data_1 ) {
				UINT32 cpu_cycles = activecpu_gettotalcycles();

				sports_pad_last_data_1 = data;
				if ( cpu_cycles - last_sports_pad_time_1 > 512 ) {
					sports_pad_state_1 = 3;
					sports_pad_1_x = readinputport(12);
					sports_pad_1_y = readinputport(13);
				}
				last_sports_pad_time_1 = cpu_cycles;
				sports_pad_state_1 = ( sports_pad_state_1 + 1 ) & 3;
			}
			break;
		}
		break;
	case 1:
		switch( readinputport(11) >> 4 ) {
		case 0x03:	/* Sports Pad */
			if ( data != sports_pad_last_data_2 ) {
				UINT32 cpu_cycles = activecpu_gettotalcycles();

				sports_pad_last_data_2 = data;
				if ( cpu_cycles - last_sports_pad_time_2 > 2048 ) {
					sports_pad_state_2 = 3;
					sports_pad_2_x = readinputport(14);
					sports_pad_2_y = readinputport(15);
				}
				last_sports_pad_time_2 = cpu_cycles;
				sports_pad_state_2 = ( sports_pad_state_2 + 1 ) & 3;
			}
			break;
		}
		break;
	}
}

static void sms_get_inputs(void) {
	UINT8 data = 0x00;
	UINT32 cpu_cycles = activecpu_gettotalcycles();

	sms_input_port0 = 0xFF;
	sms_input_port1 = 0xFF;

	if ( cpu_cycles - last_paddle_read_time > 256 ) {
		paddle_read_state ^= 0xFF;
		last_paddle_read_time = cpu_cycles;
	}

	/* Player 1 */
	switch( readinputport(11) & 0x0F ) {
	case 0x00:  /* Joystick */
		data = readinputport(0);
		/* Rapid Fire setting for Button A */
		if ( readinputport(7) & 0x01 ) {
			data = ( data & 0xEF ) | ( rapid_fire_state_1 & 0x10 );
		}
		/* Check Rapid Fire setting for Button B */
		if ( readinputport(7) & 0x02 ) {
			data = ( data & 0xDF ) | ( rapid_fire_state_1 & 0x20 );
		}
		sms_input_port0 = ( sms_input_port0 & 0xC0 ) | ( data & 0x3F );
		break;
	case 0x01:  /* Light Phaser */
		break;
	case 0x02:  /* Paddle Control */
		/* Get button A state */
		data = readinputport(8);
		if ( paddle_read_state ) {
			data = data >> 4;
		}
		sms_input_port0 = ( sms_input_port0 & 0xC0 ) | ( data & 0x0F ) | ( paddle_read_state & 0x20 )
		                | ( ( readinputport(10) & 0x02 ) << 3 );
		break;
	case 0x03:	/* Sega Sports Pad */
		switch( sports_pad_state_1 ) {
		case 0:
			data = ( sports_pad_1_x >> 4 ) & 0x0F;
			break;
		case 1:
			data = sports_pad_1_x & 0x0F;
			break;
		case 2:
			data = ( sports_pad_1_y >> 4 ) & 0x0F;
			break;
		case 3:
			data = sports_pad_1_y & 0x0F;
			break;
		}
		sms_input_port0 = ( sms_input_port0 & 0xC0 ) | data | ( ( readinputport(10) & 0x0C ) << 2 );
		break;
	}

	/* Player 2 */
	switch( readinputport(11) >> 4 ) {
	case 0x00:	/* Joystick */
		data = readinputport(0);
		sms_input_port0 = ( sms_input_port0 & 0x3F ) | ( data & 0xC0 );
		data = readinputport(1);
		if ( readinputport(7) & 0x04 ) {
			data = ( data & 0xFB ) | ( rapid_fire_state_2 & 0x04 );
		}
		if ( readinputport(7) & 0x08 ) {
			data = ( data & 0xF7 ) | ( rapid_fire_state_2 & 0x08 );
		}
		sms_input_port1 = ( sms_input_port1 & 0xF0 ) | ( data & 0x0F );
		break;
	case 0x01:	/* Light Phaser */
		break;
	case 0x02:	/* Paddle Control */
		/* Get button A state */
		data = readinputport(9);
		if ( paddle_read_state ) {
			data = data >> 4;
		}
		sms_input_port0 = ( sms_input_port0 & 0x3F ) | ( ( data & 0x03 ) << 6 );
		sms_input_port1 = ( sms_input_port1 & 0xF0 ) | ( ( data & 0x0C ) >> 2 ) | ( paddle_read_state & 0x08 )
		                | ( ( readinputport(10) & 0x20 ) >> 3 );
		break;
	case 0x03:	/* Sega Sports Pad */
		switch( sports_pad_state_2 ) {
		case 0:
			data = sports_pad_2_x & 0x0F;
			break;
		case 1:
			data = ( sports_pad_2_x >> 4 ) & 0x0F;
		case 2:
			data = sports_pad_2_y & 0x0F;
			break;
		case 3:
			data = ( sports_pad_2_y >> 4 ) & 0x0F;
			break;
		}
		sms_input_port0 = ( sms_input_port0 & 0x3F ) | ( ( data & 0x03 ) << 6 );
		sms_input_port1 = ( sms_input_port1 & 0xF0 ) | ( data >> 2 ) | ( ( readinputport(10) & 0xC0 ) >> 4 );
		break;
	}
}

WRITE8_HANDLER(sms_fm_detect_w) {
	if ( HAS_FM ) {
		smsFMDetect = (data & 0x01);
	}
}

READ8_HANDLER(sms_fm_detect_r) {
	if ( HAS_FM ) {
		return smsFMDetect;
	} else {
		if ( biosPort & IO_CHIP ) {
			return 0xFF;
		} else {
			sms_get_inputs();
			return sms_input_port0;
		}
	}
}

WRITE8_HANDLER(sms_version_w) {
	if ((data & 0x01) && (data & 0x04)) {
		smsVersion = (data & 0xA0);
	}
	if ( data & 0x08 ) {
		sms_input_write( 0, ( data & 0x20 ) >> 5 );
	}
	if ( data & 0x02 ) {
		sms_input_write( 1, ( data & 0x80 ) >> 7 );
	}
}

 READ8_HANDLER(sms_version_r) {
	UINT8 temp;

	if (biosPort & IO_CHIP) {
		return (0xFF);
	}

	/* Move bits 7,5 of port 3F into bits 7, 6 */
	temp = (smsVersion & 0x80) | (smsVersion & 0x20) << 1;

	/* Inverse version detect value for Japanese machines */
	if ( IS_REGION_JAPAN ) {
		temp ^= 0xC0;
	}

	/* Merge version data with input port #2 data */
	sms_get_inputs();
	temp = (temp & 0xC0) | (sms_input_port1 & 0x3F);

	return (temp);
}

READ8_HANDLER(sms_count_r) {
	if ( offset & 0x01 ) {
		return sms_vdp_hcount_r(offset);
	} else {
		/* VCount read */
		return sms_vdp_vcount_r(offset);
	}
}

void sms_check_pause_button( void ) {
	if ( ! IS_GAMEGEAR ) {
		if ( ! (readinputport(2) & 0x80) ) {
			if ( ! smsPaused ) {
				cpunum_set_input_line( 0, INPUT_LINE_NMI, ASSERT_LINE );
				cpunum_set_input_line( 0, INPUT_LINE_NMI, CLEAR_LINE );
			}
			smsPaused = 1;
		} else {
			smsPaused = 0;
		}
	}
}

 READ8_HANDLER(sms_input_port_0_r) {
	if (biosPort & IO_CHIP) {
		return (0xFF);
	} else {
		sms_get_inputs();
		return sms_input_port0;
	}
}

WRITE8_HANDLER(sms_YM2413_register_port_0_w) {
	if ( HAS_FM ) {
		YM2413_register_port_0_w(offset, (data & 0x3F));
	}
}

WRITE8_HANDLER(sms_YM2413_data_port_0_w) {
	if ( HAS_FM ) {
		logerror("data_port_0_w %x %x\n", offset, data);
		YM2413_data_port_0_w(offset, data);
	}
}

 READ8_HANDLER(gg_input_port_2_r) {
	//logerror("joy 2 read, val: %02x, pc: %04x\n", (( IS_REGION_JAPAN ? 0x00 : 0x40) | (readinputport(2) & 0x80)), activecpu_get_pc());
	return (( IS_REGION_JAPAN ? 0x00 : 0x40 ) | (readinputport(2) & 0x80));
}

 READ8_HANDLER(sms_mapper_r)
{
	return sms_mapper[offset];
}

WRITE8_HANDLER(sms_mapper_w)
{
	int page;
	UINT8 *SOURCE_BIOS;
	UINT8 *SOURCE_CART;
	UINT8 *SOURCE;
	UINT8	rom_page_count = sms_cartridge[sms_current_cartridge].size / 0x4000;

	offset &= 3;

	sms_mapper[offset] = data;
	sms_mapper_ram[offset] = data;

	if ( sms_cartridge[sms_current_cartridge].ROM ) {
		SOURCE_CART = sms_cartridge[sms_current_cartridge].ROM + ( ( rom_page_count > 0) ? data % rom_page_count : 0 ) * 0x4000;
	} else {
		SOURCE_CART = sms_banking_none[1];
	}
	if ( BIOS ) {
		SOURCE_BIOS = BIOS + ( (smsBiosPageCount > 0) ? data % smsBiosPageCount : 0 ) * 0x4000;
	} else {
		SOURCE_BIOS = sms_banking_none[1];
	}

	if (biosPort & IO_BIOS_ROM || ( IS_GAMEGEAR && BIOS == NULL ) )
	{
		if (!(biosPort & IO_CARTRIDGE) || ( IS_GAMEGEAR && BIOS == NULL ) )
		{
			page = ( rom_page_count > 0) ? data % rom_page_count : 0;
			if ( ! sms_cartridge[sms_current_cartridge].ROM )
				return;
			SOURCE = SOURCE_CART;
		}
		else
		{
			/* nothing to page in */
			return;
		}
	}
	else
	{
		page = (smsBiosPageCount > 0) ? data % smsBiosPageCount : 0;
		if ( ! BIOS )
			return;
		SOURCE = SOURCE_BIOS;
	}

	switch(offset) {
		case 0: /* Control */
			/* Is it ram or rom? */
			if (data & 0x08) { /* it's ram */
				sms_cartridge[sms_current_cartridge].sram_save = 1;			/* SRAM should be saved on exit. */
				if (data & 0x04) {
#ifdef LOG_PAGING
					logerror("ram 1 paged.\n");
#endif
					SOURCE = sms_cartridge[sms_current_cartridge].cartSRAM + 0x4000;
				} else {
#ifdef LOG_PAGING
					logerror("ram 0 paged.\n");
#endif
					SOURCE = sms_cartridge[sms_current_cartridge].cartSRAM;
				}
				memory_set_bankptr( 4, SOURCE );
				memory_set_bankptr( 5, SOURCE + 0x2000 );
			} else { /* it's rom */
				if ( biosPort & IO_BIOS_ROM || ! HAS_BIOS ) {
					page = ( rom_page_count > 0) ? sms_mapper[3] % rom_page_count : 0;
					SOURCE = sms_banking_cart[4];
				} else {
					page = (smsBiosPageCount > 0) ? sms_mapper[3] % smsBiosPageCount : 0;
					SOURCE = sms_banking_bios[4];
				}
#ifdef LOG_PAGING
				logerror("rom 2 paged in %x.\n", page);
#endif
				memory_set_bankptr( 4, SOURCE );
				memory_set_bankptr( 5, SOURCE + 0x2000 );
			}
			break;
		case 1: /* Select 16k ROM bank for 0400-3FFF */
#ifdef LOG_PAGING
			logerror("rom 0 paged in %x.\n", page);
#endif
			sms_banking_bios[2] = SOURCE_BIOS + 0x0400;
			sms_banking_cart[2] = SOURCE_CART + 0x0400;
			if ( IS_GAMEGEAR ) {
				SOURCE = SOURCE_CART;
			}
			memory_set_bankptr( 2, SOURCE + 0x0400 );
			break;
		case 2: /* Select 16k ROM bank for 4000-7FFF */
#ifdef LOG_PAGING
			logerror("rom 1 paged in %x.\n", page);
#endif
			sms_banking_bios[3] = SOURCE_BIOS;
			sms_banking_cart[3] = SOURCE_CART;
			if ( IS_GAMEGEAR ) {
				SOURCE = SOURCE_CART;
			}
			memory_set_bankptr( 3, SOURCE );
			break;
		case 3: /* Select 16k ROM bank for 8000-BFFF */
			sms_banking_bios[4] = SOURCE_BIOS;
			if ( IS_GAMEGEAR ) {
				SOURCE = SOURCE_CART;
			}
			if ( sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER ) {
				if ( SOURCE == SOURCE_CART ) {
					SOURCE = sms_banking_cart[4];
				}
			} else {
				sms_banking_cart[4] = SOURCE_CART;
			}
			if ( ! ( sms_mapper[0] & 0x08 ) ) { /* is RAM disabled? */
#ifdef LOG_PAGING
				logerror("rom 2 paged in %x.\n", page);
#endif
				memory_set_bankptr( 4, SOURCE );
				memory_set_bankptr( 5, SOURCE + 0x2000 );
			}
			break;
	}
}

static WRITE8_HANDLER(sms_codemasters_page0_w) {
	if ( sms_cartridge[sms_current_cartridge].ROM && sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER ) {
		UINT8 rom_page_count = sms_cartridge[sms_current_cartridge].size / 0x4000;
		sms_banking_cart[1] = sms_cartridge[sms_current_cartridge].ROM + ( ( rom_page_count > 0) ? data % rom_page_count : 0 ) * 0x4000;
		sms_banking_cart[2] = sms_banking_cart[1] + 0x0400;
		memory_set_bankptr( 1, sms_banking_cart[1] );
		memory_set_bankptr( 2, sms_banking_cart[2] );
	}
}

static WRITE8_HANDLER(sms_codemasters_page1_w) {
	if ( sms_cartridge[sms_current_cartridge].ROM && sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER ) {
		/* Check if we need to switch in some RAM */
		if ( data & 0x80 ) {
			sms_cartridge[sms_current_cartridge].ram_page = data & 0x07;
			memory_set_bankptr( 5, sms_cartridge[sms_current_cartridge].cartRAM + sms_cartridge[sms_current_cartridge].ram_page * 0x2000 );
		} else {
			UINT8 rom_page_count = sms_cartridge[sms_current_cartridge].size / 0x4000;
			sms_banking_cart[3] = sms_cartridge[sms_current_cartridge].ROM + ( ( rom_page_count > 0) ? data % rom_page_count : 0 ) * 0x4000;
			memory_set_bankptr( 3, sms_banking_cart[3] );
			memory_set_bankptr( 5, sms_banking_cart[4] + 0x2000 );
		}
	}
}

WRITE8_HANDLER(sms_bios_w) {
	biosPort = data;

	logerror("bios write %02x, pc: %04x\n", data, activecpu_get_pc());

	setup_rom();
}

WRITE8_HANDLER(sms_cartram2_w) {
	if (sms_mapper[0] & 0x08) {
		logerror("write %02X to cartram at offset #%04X\n", data, offset + 0x2000);
		if (sms_mapper[0] & 0x04) {
			sms_cartridge[sms_current_cartridge].cartSRAM[offset + 0x6000] = data;
		} else {
			sms_cartridge[sms_current_cartridge].cartSRAM[offset + 0x2000] = data;
		}
	}
	if ( sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER ) {
		sms_cartridge[sms_current_cartridge].cartRAM[sms_cartridge[sms_current_cartridge].ram_page * 0x2000 + offset] = data;
	}
	if ( sms_cartridge[sms_current_cartridge].features & CF_KOREAN_MAPPER && offset == 0 ) { /* Dodgeball King mapper */
		UINT8	rom_page_count = sms_cartridge[sms_current_cartridge].size / 0x4000;
		int		page = (rom_page_count > 0) ? data % rom_page_count : 0;
		if ( ! sms_cartridge[sms_current_cartridge].ROM )
			return;
		sms_banking_cart[4] = sms_cartridge[sms_current_cartridge].ROM + page * 0x4000;
		memory_set_bankptr( 4, sms_banking_cart[4] );
		memory_set_bankptr( 5, sms_banking_cart[4] + 0x2000 );
#ifdef LOG_PAGING
		logerror("rom 2 paged in %x dodgeball king.\n", page);
#endif
	}
}

WRITE8_HANDLER(sms_cartram_w) {
	int page;

	if (sms_mapper[0] & 0x08) {
		logerror("write %02X to cartram at offset #%04X\n", data, offset);
		if (sms_mapper[0] & 0x04) {
			sms_cartridge[sms_current_cartridge].cartSRAM[offset + 0x4000] = data;
		} else {
			sms_cartridge[sms_current_cartridge].cartSRAM[offset] = data;
		}
	} else {
		if ( sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER && offset == 0 ) { /* Codemasters mapper */
			UINT8	rom_page_count = sms_cartridge[sms_current_cartridge].size / 0x4000;
			page = (rom_page_count > 0) ? data % rom_page_count : 0;
			if ( ! sms_cartridge[sms_current_cartridge].ROM )
				return;
			sms_banking_cart[4] = sms_cartridge[sms_current_cartridge].ROM + page * 0x4000;
			memory_set_bankptr( 4, sms_banking_cart[4] );
			memory_set_bankptr( 5, sms_banking_cart[4] + 0x2000 );
#ifdef LOG_PAGING
			logerror("rom 2 paged in %x codemasters.\n", page);
#endif
		} else if ( sms_cartridge[sms_current_cartridge].features & CF_ONCART_RAM ) {
			sms_cartridge[sms_current_cartridge].cartRAM[offset & ( sms_cartridge[sms_current_cartridge].ram_size - 1 ) ] = data;
		} else {
			logerror("INVALID write %02X to cartram at offset #%04X\n", data, offset);
		}
	}
}

WRITE8_HANDLER(gg_sio_w) {
	logerror("*** write %02X to SIO register #%d\n", data, offset);

	ggSIO[offset & 0x07] = data;
	switch(offset & 7) {
		case 0x00: /* Parallel Data */
			break;
		case 0x01: /* Data Direction/ NMI Enable */
			break;
		case 0x02: /* Serial Output */
			break;
		case 0x03: /* Serial Input */
			break;
		case 0x04: /* Serial Control / Status */
			break;
	}
}

 READ8_HANDLER(gg_sio_r) {
	logerror("*** read SIO register #%d\n", offset);

	switch(offset & 7) {
		case 0x00: /* Parallel Data */
			break;
		case 0x01: /* Data Direction/ NMI Enable */
			break;
		case 0x02: /* Serial Output */
			break;
		case 0x03: /* Serial Input */
			break;
		case 0x04: /* Serial Control / Status */
			break;
	}

	return ggSIO[offset];
}

 READ8_HANDLER(gg_psg_r) {
	return 0xFF;
}

WRITE8_HANDLER(gg_psg_w) {
	logerror("write %02X to psg at offset #%d.\n",data , offset);

	/* D7 = Noise Left */
	/* D6 = Tone3 Left */
	/* D5 = Tone2 Left */
	/* D4 = Tone1 Left */

	/* D3 = Noise Right */
	/* D2 = Tone3 Right */
	/* D1 = Tone2 Right */
	/* D0 = Tone1 Right */
}

static void sms_machine_stop(running_machine *machine) {
	/* Does the cartridge have SRAM that should be saved? */
	if ( sms_cartridge[sms_current_cartridge].sram_save ) {
		image_battery_save( image_from_devtype_and_index(IO_CARTSLOT, 0), sms_cartridge[sms_current_cartridge].cartSRAM, sizeof(UINT8) * NVRAM_SIZE );
	}
}

void setup_rom(void)
{
	/* 1. set up bank pointers to point to nothing */
	memory_set_bankptr( 1, sms_banking_none[1] );
	memory_set_bankptr( 2, sms_banking_none[2] );
	memory_set_bankptr( 3, sms_banking_none[3] );
	memory_set_bankptr( 4, sms_banking_none[4] );
	memory_set_bankptr( 5, sms_banking_none[4] + 0x2000 );

	/* 2. check and set up expansion port */
	if (!(biosPort & IO_EXPANSION) && (biosPort & IO_CARTRIDGE) && (biosPort & IO_CARD)) {
		/* TODO: Implement me */
		logerror( "Switching to unsupported expansion port.\n" );
	}

	/* 3. check and set up card rom */
	if (!(biosPort & IO_CARD) && (biosPort & IO_CARTRIDGE) && (biosPort & IO_EXPANSION)) {
		/* TODO: Implement me */
		logerror( "Switching to unsupported card rom port.\n" );
	}

	/* 4. check and set up cartridge rom */
	/* if ( ( !(biosPort & IO_CARTRIDGE) && (biosPort & IO_EXPANSION) && (biosPort & IO_CARD) ) || IS_GAMEGEAR ) { */
	/* Out Run Europa initially writes a value to port 3E where IO_CARTRIDGE, IO_EXPANSION and IO_CARD are reset */
	if ( ( ! ( biosPort & IO_CARTRIDGE ) ) || IS_GAMEGEAR ) {
		memory_set_bankptr( 1, sms_banking_cart[1] );
		memory_set_bankptr( 2, sms_banking_cart[2] );
		memory_set_bankptr( 3, sms_banking_cart[3] );
		memory_set_bankptr( 4, sms_banking_cart[4] );
		memory_set_bankptr( 5, sms_banking_cart[4] + 0x2000 );
		logerror( "Switched in cartridge rom.\n" );
	}

	/* 5. check and set up bios rom */
	if ( !(biosPort & IO_BIOS_ROM) ) {
		/* 0x0400 bioses */
		if ( HAS_BIOS_0400 ) {
			memory_set_bankptr( 1, sms_banking_bios[1] );
			logerror( "Switched in 0x0400 bios.\n" );
		}
		/* 0x2000 bioses */
		if ( HAS_BIOS_2000 ) {
			memory_set_bankptr( 1, sms_banking_bios[1] );
			memory_set_bankptr( 2, sms_banking_bios[2] );
			logerror( "Switched in 0x2000 bios.\n" );
		}
		if ( HAS_BIOS_FULL ) {
			memory_set_bankptr( 1, sms_banking_bios[1] );
			memory_set_bankptr( 2, sms_banking_bios[2] );
			memory_set_bankptr( 3, sms_banking_bios[3] );
			memory_set_bankptr( 4, sms_banking_bios[4] );
			memory_set_bankptr( 5, sms_banking_bios[4] + 0x2000 );
			logerror( "Switched in full bios.\n" );
		}
	}

	if ( sms_cartridge[sms_current_cartridge].features & CF_ONCART_RAM ) {
		memory_set_bankptr( 4, sms_cartridge[sms_current_cartridge].cartRAM );
		memory_set_bankptr( 5, sms_cartridge[sms_current_cartridge].cartRAM );
	}
}

static int sms_verify_cart(UINT8 *magic, int size) {
	int retval;

	retval = IMAGE_VERIFY_FAIL;

	/* Verify the file is a valid image - check $7ff0 for "TMR SEGA" */
	if (size >= 0x8000) {
		if (!strncmp((char*)&magic[0x7FF0], "TMR SEGA", 8)) {
			/* Technically, it should be this, but remove for now until verified:
			if (!strcmp(sysname, "gamegear")) {
				if ((unsigned char)magic[0x7FFD] < 0x50)
					retval = IMAGE_VERIFY_PASS;
			}
			if (!strcmp(sysname, "sms")) {
				if ((unsigned char)magic[0x7FFD] >= 0x50)
					retval = IMAGE_VERIFY_PASS;
			}
			*/
			retval = IMAGE_VERIFY_PASS;
		}
	}

		/* Check at $81f0 also */
		//if (!retval) {
	 //	 if (!strncmp(&magic[0x81f0], "TMR SEGA", 8)) {
				/* Technically, it should be this, but remove for now until verified:
				if (!strcmp(sysname, "gamegear")) {
					if ((unsigned char)magic[0x81fd] < 0x50)
						retval = IMAGE_VERIFY_PASS;
				}
				if (!strcmp(sysname, "sms")) {
					if ((unsigned char)magic[0x81fd] >= 0x50)
						retval = IMAGE_VERIFY_PASS;
				}
				*/
		 //	 retval = IMAGE_VERIFY_PASS;
		//	}
		//}

	return retval;
}

/* Check for Codemasters mapper
  0x7FE3 - 93 - sms Cosmis Spacehead
              - sms Dinobasher
              - sms The Excellent Dizzy Collection
              - sms Fantastic Dizzy
              - sms Micro Machines
              - gamegear Cosmic Spacehead
              - gamegear Micro Machines
         - 94 - gamegear Dropzone
              - gamegear Ernie Els Golf (also has 64KB additional RAM on the cartridge)
              - gamegear Pete Sampras Tennis
              - gamegear S.S. Lucifer
         - 95 - gamegear Micro Machines 2 - Turbo Tournament

The Korean game Jang Pung II also seems to use a codemasters style mapper.
 */
static int detect_codemasters_mapper( UINT8 *rom ) {
	UINT8	jang_pung2[16] = { 0x00, 0xBA, 0x38, 0x0D, 0x00, 0xB8, 0x38, 0x0C, 0x00, 0xB6, 0x38, 0x0B, 0x00, 0xB4, 0x38, 0x0A };

	if ( ( ( rom[0x7fe0] & 0x0F ) <= 9 ) &&
	     ( rom[0x7fe3] == 0x93 || rom[0x7fe3] == 0x94 || rom[0x7fe3] == 0x95 ) &&
	     rom[0x7fef] == 0x00 ) {
		return 1;
	}

	if ( ! memcmp( &rom[0x7ff0], jang_pung2, 16 ) ) {
		return 1;
	}

	return 0;
}

static int detect_korean_mapper( UINT8 *rom ) {
	UINT8	signatures[2][16] = {
		{ 0x3E, 0x11, 0x32, 0x00, 0xA0, 0x78, 0xCD, 0x84, 0x85, 0x3E, 0x02, 0x32, 0x00, 0xA0, 0xC9, 0xFF }, /* Dodgeball King */
		{ 0x41, 0x48, 0x37, 0x37, 0x44, 0x37, 0x4E, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20 },	/* Sangokushi 3 */
	};
	int i;

	for( i = 0; i < 2; i++ ) {
		if ( !memcmp( &rom[0x7ff0], signatures[i], 16 ) ) {
			return 1;
		}
	}
	return 0;
}

DEVICE_INIT( sms_cart ) {
	int i;

	for ( i = 0; i < MAX_CARTRIDGES; i++ ) {
		sms_cartridge[i].ROM = NULL;
		sms_cartridge[i].size = 0;
		sms_cartridge[i].features = 0;
		sms_cartridge[i].cartSRAM = NULL;
		sms_cartridge[i].sram_save = 0;
		sms_cartridge[i].cartRAM = NULL;
		sms_cartridge[i].ram_size = 0;
		sms_cartridge[i].ram_page = 0;
	}
	sms_current_cartridge = 0;

	biosPort = (IO_EXPANSION | IO_CARTRIDGE | IO_CARD);
	if ( ! IS_GAMEGEAR && ! HAS_BIOS ) {
		biosPort &= ~(IO_CARTRIDGE);
		biosPort |= IO_BIOS_ROM;
	}

	/* Initially set the vdp to not use sms compatibility mode for GG */
	sms_set_ggsmsmode( 0 );

	return INIT_PASS;
}

DEVICE_LOAD( sms_cart )
{
	int size = image_length(image);
	int index = image_index_in_device( image );
	const char *fname = image_filename( image );
	int fname_len = fname ? strlen( fname ) : 0;
	const char *extrainfo = image_extrainfo( image );

	/* Check for 512-byte header */
	if ((size / 512) & 1)
	{
		image_fseek(image, 512, SEEK_SET);
		size -= 512;
	}

	if ( ! size ) {
		image_seterror( image, IMAGE_ERROR_UNSPECIFIED, "Invalid ROM image: ROM image is too small" );
		return INIT_FAIL;
	}

	/* Create a new memory region to hold the ROM. */
	/* Make sure the region holds only complete (0x4000) rom banks */
	sms_cartridge[index].size = (size&0x3FFF) ? (((size>>14)+1)<<14) : size;
	sms_cartridge[index].ROM = auto_malloc( sms_cartridge[index].size );

	sms_cartridge[index].cartSRAM = auto_malloc( NVRAM_SIZE );

	/* Load ROM banks */
	size = image_fread(image, sms_cartridge[index].ROM, size);

	/* check the image */
	if ( ! HAS_BIOS ) {
		if (sms_verify_cart(sms_cartridge[index].ROM, size) == IMAGE_VERIFY_FAIL) {
			logerror("Warning loading image: sms_verify_cart failed\n");
		}
	}

	sms_cartridge[index].features = 0;

	/* Detect special features from the extrainfo field */
	if ( extrainfo ) {
		/* Check for codemasters mapper */
		if ( strstr( extrainfo, "CODEMASTERS" ) ) {
			sms_cartridge[index].features |= CF_CODEMASTERS_MAPPER;
		}

		/* Check for korean mapper */
		if ( strstr( extrainfo, "KOREAN" ) ) {
			sms_cartridge[index].features |= CF_KOREAN_MAPPER;
		}

		/* Check for special SMS Compatibility mode gamegear cartridges */
		if ( IS_GAMEGEAR && strstr( extrainfo, "GGSMS" ) ) {
			sms_set_ggsmsmode( 1 );
		}

		/* Check for 93C46 eeprom */
		if ( strstr( extrainfo, "93C46" ) ) {
			sms_cartridge[index].features |= CF_93C46_EEPROM;
		}

		/* Check for 8KB on-cart RAM */
		if ( strstr( extrainfo, "8KB_CART_RAM" ) ) {
			sms_cartridge[index].features |= CF_ONCART_RAM;
			sms_cartridge[index].ram_size = 0x2000;
			sms_cartridge[index].cartRAM = auto_malloc( sms_cartridge[index].ram_size );
		}
	} else {
		/* If no extrainfo information is available try to find special information out on our own */
		/* Check for special cartridge features */
		if ( size >= 0x8000 ) {
			/* Check for special mappers */
			if ( detect_codemasters_mapper( sms_cartridge[index].ROM ) ) {
				sms_cartridge[index].features |= CF_CODEMASTERS_MAPPER;
			}
			if ( detect_korean_mapper( sms_cartridge[index].ROM ) ) {
				sms_cartridge[index].features |= CF_KOREAN_MAPPER;
			}
		}

		/* Check for special SMS Compatibility mode gamegear cartridges */
		if ( IS_GAMEGEAR ) {
			/* Just in case someone passes us an sms file */
			if ( fname_len > 3 && ! mame_stricmp( fname + fname_len - 3, "sms" ) ) {
				sms_set_ggsmsmode( 1 );
			}
		}
	}

	if ( sms_cartridge[index].features & CF_CODEMASTERS_MAPPER ) {
		sms_cartridge[index].ram_size = 0x10000;
		sms_cartridge[index].cartRAM = auto_malloc( sms_cartridge[index].ram_size );
		sms_cartridge[index].ram_page = 0;
	}

	/* Load battery backed RAM, if available */
	image_battery_load( image, sms_cartridge[index].cartSRAM, sizeof(UINT8) * NVRAM_SIZE );

	return INIT_PASS;
}

static void setup_cart_banks( void ) {
	if ( sms_cartridge[sms_current_cartridge].ROM ) {
		UINT8   rom_page_count = sms_cartridge[sms_current_cartridge].size / 0x4000;
		sms_banking_cart[1] = sms_cartridge[sms_current_cartridge].ROM;
		sms_banking_cart[2] = sms_cartridge[sms_current_cartridge].ROM + 0x0400;
		sms_banking_cart[3] = sms_cartridge[sms_current_cartridge].ROM + ( ( 1 < rom_page_count ) ? 0x4000 : 0 );
		sms_banking_cart[4] = sms_cartridge[sms_current_cartridge].ROM + ( ( 2 < rom_page_count ) ? 0x8000 : 0 );
		/* Codemasters mapper points to bank 0 for page 2 */
		if ( sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER ) {
			sms_banking_cart[4] = sms_cartridge[sms_current_cartridge].ROM;
		}
	} else {
		sms_banking_cart[1] = sms_banking_none[1];
		sms_banking_cart[2] = sms_banking_none[2];
		sms_banking_cart[3] = sms_banking_none[3];
		sms_banking_cart[4] = sms_banking_none[4];
	}
}

static void setup_banks( void ) {
	sms_banking_bios[1] = sms_banking_cart[1] = sms_banking_none[1] = memory_region(REGION_CPU1);
	sms_banking_bios[2] = sms_banking_cart[2] = sms_banking_none[2] = memory_region(REGION_CPU1);
	sms_banking_bios[3] = sms_banking_cart[3] = sms_banking_none[3] = memory_region(REGION_CPU1);
	sms_banking_bios[4] = sms_banking_cart[4] = sms_banking_none[4] = memory_region(REGION_CPU1);

	BIOS = memory_region(REGION_USER1);

	smsBiosPageCount = ( BIOS ? memory_region_length(REGION_USER1) / 0x4000 : 0 );

	setup_cart_banks();

	if ( BIOS == NULL || BIOS[0] == 0x00 ) {
		BIOS = NULL;
                biosPort |= IO_BIOS_ROM;
	}

	if ( BIOS ) {
		sms_banking_bios[1] = BIOS;
		sms_banking_bios[2] = BIOS + 0x0400;
		sms_banking_bios[3] = BIOS + ( ( 1 < smsBiosPageCount) ? 0x4000 : 0 );
		sms_banking_bios[4] = BIOS + ( ( 2 < smsBiosPageCount) ? 0x8000 : 0 );
	}
}

MACHINE_START(sms) {
	add_exit_callback(machine, sms_machine_stop);
}

MACHINE_RESET(sms)
{
	smsVersion = 0x00;
	if ( HAS_FM ) {
		smsFMDetect = 0x01;
	}

	sms_mapper_ram = memory_get_write_ptr( 0, ADDRESS_SPACE_PROGRAM, 0xDFFC );

	if ( sms_cartridge[sms_current_cartridge].features & CF_CODEMASTERS_MAPPER ) {
		/* Install special memory handlers */
		memory_install_write8_handler( 0, ADDRESS_SPACE_PROGRAM, 0x0000, 0x0000, 0, 0, sms_codemasters_page0_w );
		memory_install_write8_handler( 0, ADDRESS_SPACE_PROGRAM, 0x4000, 0x4000, 0, 0, sms_codemasters_page1_w );
	}

	/* Initialize SIO stuff for GG */
	ggSIO[0] = 0x7F;
	ggSIO[1] = 0xFF;
	ggSIO[2] = 0x00;
	ggSIO[3] = 0xFF;
	ggSIO[4] = 0x00;

	sms_store_control = 0;

	setup_banks();

	setup_rom();

	rapid_fire_state_1 = 0;
	rapid_fire_state_2 = 0;
	rapid_fire_timer = timer_alloc( rapid_fire_callback , NULL);
	timer_adjust( rapid_fire_timer, ATTOTIME_IN_HZ(10), 0, ATTOTIME_IN_HZ(10) );

	last_paddle_read_time = 0;
	paddle_read_state = 0;

	last_sports_pad_time_1 = 0;
	last_sports_pad_time_2 = 0;
	sports_pad_state_1 = 0;
	sports_pad_state_2 = 0;
	sports_pad_last_data_1 = 0;
	sports_pad_last_data_2 = 0;
	sports_pad_1_x = 0;
	sports_pad_1_y = 0;
	sports_pad_2_x = 0;
	sports_pad_2_y = 0;
}

READ8_HANDLER(sms_store_cart_select_r) {
	return 0xFF;
}

WRITE8_HANDLER(sms_store_cart_select_w) {
	UINT8 slot = data >> 4;
	UINT8 slottype = data & 0x08;

	logerror("switching in part of %s slot #%d\n", slottype ? "card" : "cartridge", slot );
	/* cartridge? slot #0 */
	if ( slottype == 0 ) {
		sms_current_cartridge = slot;
	}
	setup_cart_banks();
	memory_set_bankptr( 10, sms_banking_cart[3] + 0x2000 );
	setup_rom();
}

READ8_HANDLER(sms_store_select1) {
	return 0xFF;
}

READ8_HANDLER(sms_store_select2) {
	return 0xFF;
}

READ8_HANDLER(sms_store_control_r) {
	return sms_store_control;
}

WRITE8_HANDLER(sms_store_control_w) {
	logerror( "0x%04X: sms_store_control write 0x%02X\n", activecpu_get_pc(), data );
	if ( data & 0x02 ) {
		cpunum_resume( 0, SUSPEND_REASON_HALT );
	} else {
		/* Pull reset line of CPU #0 low */
		cpunum_suspend( 0, SUSPEND_REASON_HALT, 1 );
		cpunum_reset( 0 );
	}
	sms_store_control = data;
}

void sms_int_callback( int state ) {
	cpunum_set_input_line( 0, 0, state );
}

void sms_store_int_callback( int state ) {
	cpunum_set_input_line( sms_store_control & 0x01 ? 1 : 0, 0, state );
}

