// license:GPL-2.0+
// copyright-holders:Fabio Priuli, byuu
/***********************************************************************************************************

 Super NES/Famicom (LoROM) cartridge emulation (for SNES/SFC)

 ***********************************************************************************************************/


#include "emu.h"
#include "rom.h"


//-------------------------------------------------
//  sns_rom_device - constructor
//-------------------------------------------------

DEFINE_DEVICE_TYPE(SNS_LOROM,          sns_rom_device,           "sns_rom",           "SNES Cart (LoROM)")
DEFINE_DEVICE_TYPE(SNS_LOROM_OBC1,     sns_rom_obc1_device,      "sns_rom_obc1",      "SNES Cart (LoROM) + OBC-1")
// LoROM pirate carts with protection
DEFINE_DEVICE_TYPE(SNS_LOROM_POKEMON,  sns_rom_pokemon_device,   "sns_rom_pokemon",   "SNES Pirate Carts with Protection")
DEFINE_DEVICE_TYPE(SNS_LOROM_TEKKEN2,  sns_rom_tekken2_device,   "sns_rom_tekken2",   "SNES Tekken 2")
DEFINE_DEVICE_TYPE(SNS_LOROM_SOULBLAD, sns_rom_soulblad_device,  "sns_rom_soulblad",  "SNES Sound Blade")
DEFINE_DEVICE_TYPE(SNS_LOROM_BANANA,   sns_rom_banana_device,    "sns_rom_banana",    "SNES Banana de Pijamas")
DEFINE_DEVICE_TYPE(SNS_LOROM_BUGSLIFE, sns_rom_bugs_device,      "sns_rom_bugslife",  "SNES A Bug's Life")
// LoROM pirate multicarts
DEFINE_DEVICE_TYPE(SNS_LOROM_MCPIR1,   sns_rom_mcpirate1_device, "sns_rom_mcpirate1", "SNES Pirate Multigame Carts Type 1")
DEFINE_DEVICE_TYPE(SNS_LOROM_MCPIR2,   sns_rom_mcpirate2_device, "sns_rom_mcpirate2", "SNES Pirate Multigame Carts Type 2")
DEFINE_DEVICE_TYPE(SNS_LOROM_20COL,    sns_rom_20col_device,     "sns_rom_20col",     "SNES Super 20 Collection")


sns_rom_device::sns_rom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, type, tag, owner, clock), device_sns_cart_interface(mconfig, *this)
{
}

sns_rom_device::sns_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM, tag, owner, clock)
{
}

sns_rom_obc1_device::sns_rom_obc1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_OBC1, tag, owner, clock), m_address(0), m_offset(0), m_shift(0)
{
}



// Pirate LoROM 'mappers'
sns_rom_pokemon_device::sns_rom_pokemon_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_POKEMON, tag, owner, clock), m_latch(0)
{
}

sns_rom_tekken2_device::sns_rom_tekken2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_TEKKEN2, tag, owner, clock), m_prot(0)
{
}

sns_rom_soulblad_device::sns_rom_soulblad_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_SOULBLAD, tag, owner, clock)
{
}

sns_rom_banana_device::sns_rom_banana_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_BANANA, tag, owner, clock)
{
}

sns_rom_bugs_device::sns_rom_bugs_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_BUGSLIFE, tag, owner, clock)
{
}

// Multigame LoROM 'mappers'
sns_rom_mcpirate1_device::sns_rom_mcpirate1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_MCPIR1, tag, owner, clock), m_base_bank(0)
{
}

sns_rom_mcpirate2_device::sns_rom_mcpirate2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_MCPIR2, tag, owner, clock), m_base_bank(0)
{
}

sns_rom_20col_device::sns_rom_20col_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: sns_rom_device(mconfig, SNS_LOROM_20COL, tag, owner, clock), m_base_bank(0)
{
}


void sns_rom_device::device_start()
{
}

void sns_rom_obc1_device::device_start()
{
	save_item(NAME(m_ram));
	save_item(NAME(m_address));
	save_item(NAME(m_offset));
	save_item(NAME(m_shift));
}

void sns_rom_obc1_device::device_reset()
{
	memset(m_ram, 0xff, sizeof(m_ram));
	// or from rom?
	m_offset  = (m_ram[0x1ff5] & 0x01) ? 0x1800 : 0x1c00;
	m_address = (m_ram[0x1ff6] & 0x7f);
	m_shift   = (m_ram[0x1ff6] & 0x03) << 1;
}


void sns_rom_pokemon_device::device_start()
{
	save_item(NAME(m_latch));
}

void sns_rom_pokemon_device::device_reset()
{
	m_latch = 0;
}

void sns_rom_tekken2_device::device_start()
{
	save_item(NAME(m_prot));
}

void sns_rom_tekken2_device::device_reset()
{
	m_prot = 0;
}

void sns_rom_bugs_device::device_start()
{
	save_item(NAME(m_latch));
}

void sns_rom_bugs_device::device_reset()
{
	memset(m_latch, 0, sizeof(m_latch));
}



void sns_rom_mcpirate1_device::device_start()
{
	m_base_bank = 0;
	save_item(NAME(m_base_bank));
}

void sns_rom_mcpirate1_device::device_reset()
{
}

void sns_rom_mcpirate2_device::device_start()
{
	m_base_bank = 0;
	save_item(NAME(m_base_bank));
}

void sns_rom_mcpirate2_device::device_reset()
{
}

void sns_rom_20col_device::device_start()
{
	m_base_bank = 4;
	save_item(NAME(m_base_bank));
}


/*-------------------------------------------------
 mapper specific handlers
 -------------------------------------------------*/

READ8_MEMBER(sns_rom_device::read_l)
{
	return read_h(space, offset);
}

READ8_MEMBER(sns_rom_device::read_h)
{
	int bank = offset / 0x10000;
	return m_rom[rom_bank_map[bank] * 0x8000 + (offset & 0x7fff)];
}



// Lo-ROM + OBC-1 (used by Metal Combat - Falcon's Revenge)
// same as above but additional read/write handling for the add-on chip

/***************************************************************************

 Based on C++ implementation by Byuu in BSNES.

 Byuu's code is released under GNU General Public License
 version 2 as published by the Free Software Foundation.

 ***********************************************************************************************************/


READ8_MEMBER( sns_rom_obc1_device::chip_read )
{
	uint16_t address = offset & 0x1fff;
	uint8_t value;

	switch (address)
	{
		case 0x1ff0:
			value = m_ram[m_offset + (m_address << 2) + 0];
			break;

		case 0x1ff1:
			value = m_ram[m_offset + (m_address << 2) + 1];
			break;

		case 0x1ff2:
			value = m_ram[m_offset + (m_address << 2) + 2];
			break;

		case 0x1ff3:
			value = m_ram[m_offset + (m_address << 2) + 3];
			break;

		case 0x1ff4:
			value = m_ram[m_offset + (m_address >> 2) + 0x200];
			break;

		default:
			value = m_ram[address];
			break;
	}

	return value;
}


WRITE8_MEMBER( sns_rom_obc1_device::chip_write )
{
	uint16_t address = offset & 0x1fff;
	uint8_t temp;

	switch(address)
	{
		case 0x1ff0:
			m_ram[m_offset + (m_address << 2) + 0] = data;
			break;

		case 0x1ff1:
			m_ram[m_offset + (m_address << 2) + 1] = data;
			break;

		case 0x1ff2:
			m_ram[m_offset + (m_address << 2) + 2] = data;
			break;

		case 0x1ff3:
			m_ram[m_offset + (m_address << 2) + 3] = data;
			break;

		case 0x1ff4:
			temp = m_ram[m_offset + (m_address >> 2) + 0x200];
			temp = (temp & ~(3 << m_shift)) | ((data & 0x03) << m_shift);
			m_ram[m_offset + (m_address >> 2) + 0x200] = temp;
			break;

		case 0x1ff5:
			m_offset = (data & 0x01) ? 0x1800 : 0x1c00;
			m_ram[address & 0x1fff] = data;
			break;

		case 0x1ff6:
			m_address = data & 0x7f;
			m_shift = (data & 0x03) << 1;
			m_ram[address & 0x1fff] = data;
			break;

		default:
			m_ram[address & 0x1fff] = data;
			break;
	}
}



// Lo-ROM + Protection devices used in pirate carts


// Pokemon (and many others): a byte is written and a permutation of its bits must be returned.
// Address range for read/write depends on the game (check snes.xml)
READ8_MEMBER( sns_rom_pokemon_device::chip_read )
{
	return bitswap<8>(m_latch,0,6,7,1,2,3,4,5);
}

WRITE8_MEMBER( sns_rom_pokemon_device::chip_write )
{
	m_latch = data;
}


// Tekken 2: It accesses the protection in a very strange way, always reading/writing the same data $f0 times,
// because each access must be repeated a couple of times to be registered (typically around 7-30 times)
// They probably used a microcontroller here.
// The protection itself is accessed in banks $80-$bf. Accessing (read/write, doesn't matter) address lines
// A8,A9,A10 in these banks in a certain sequence makes the mc return a 4bit value. [d4s]
// Details on a possible algorythm behind the sequence of accesses were provided by nocash. Thanks!
void sns_rom_tekken2_device::update_prot(uint32_t offset)
{
	// accesses to [80-bf][8000-87ff] ranges update the protection value
	offset &= 0x7ff;

	switch (offset & 0x700)
	{
		case 0x000:
			m_prot = 0;
			break;
		case 0x100:
			// used for read access
			break;
		case 0x200: // BIT 0
			m_prot |= 1;
			break;
		case 0x300: // BIT 1
			m_prot |= 2;
			break;
		case 0x400: // BIT 2
			m_prot |= 4;
			break;
		case 0x500: // BIT 3
			m_prot |= 8;
			break;
		case 0x600: // DIRECTION
			m_prot |= 0x10;
			break;
		case 0x700: // FUNCTION
			m_prot |= 0x20;
			break;
	}
}

READ8_MEMBER( sns_rom_tekken2_device::chip_read )
{
	update_prot(offset);

	if ((offset & 0x700) == 0x100)
	{
		if (BIT(m_prot, 5))     // FUNCTION = 1 means Shift
		{
			if (BIT(m_prot, 4))     // DIRECTION = 1 means Right
				return (m_prot & 0x0f) >> 1;
			else                    // DIRECTION = 0 means Left
				return (m_prot & 0x0f) << 1;
		}
		else                        // FUNCTION = 0 means Add/Sub
		{
			if (BIT(m_prot, 4))     // DIRECTION = 1 means Minus
				return (m_prot & 0x0f) - 1;
			else                    // DIRECTION = 0 means Plus
				return (m_prot & 0x0f) + 1;
		}
	}

	return 0xff; // should be open_bus
}

WRITE8_MEMBER( sns_rom_tekken2_device::chip_write )
{
	update_prot(offset);
}


// Soul Blade: Adresses $xxx0-$xxx3 in banks $80-$bf always read $55, $0f, $aa, $f0.
// Banks $c0-$ff return open bus.
READ8_MEMBER( sns_rom_soulblad_device::chip_read )
{
	uint8_t value;
	offset &= 3;
	switch (offset)
	{
		case 0:
		default:
			value = 0x55;
			break;
		case 1:
			value = 0x0f;
			break;
		case 2:
			value = 0xaa;
			break;
		case 3:
			value = 0xf0;
			break;
	}
	return value;
}

// Multicart pirate banking emulation
// LoROM games, writes to [ff][ff00-ffff] control bankswitch
// The actual banks depends on the last 8bits of the address accessed.

// Type 1: bits0-4 of the address are used as base bank (256KB chunks)
READ8_MEMBER(sns_rom_mcpirate1_device::read_l)
{
	return read_h(space, offset);
}

READ8_MEMBER(sns_rom_mcpirate1_device::read_h)
{
	int bank = (offset / 0x10000) + (m_base_bank * 8);
	return m_rom[rom_bank_map[bank] * 0x8000 + (offset & 0x7fff)];
}

WRITE8_MEMBER( sns_rom_mcpirate1_device::chip_write )
{
	m_base_bank = offset & 0x1f;
//  printf("offset %X data %X bank %X\n", offset, data, m_base_bank);
}

// Type 2: bits0-3 & bit5 of the address are used as base bank (256KB chunks)
READ8_MEMBER(sns_rom_mcpirate2_device::read_l)
{
	return read_h(space, offset);
}

READ8_MEMBER(sns_rom_mcpirate2_device::read_h)
{
	int bank = (offset / 0x10000) + (m_base_bank * 8);
	return m_rom[rom_bank_map[bank] * 0x8000 + (offset & 0x7fff)];
}

WRITE8_MEMBER( sns_rom_mcpirate2_device::chip_write )
{
	m_base_bank = (offset & 0x0f) | ((offset & 0x20) >> 1);
//  printf("offset %X data %X bank %X\n", offset, data, m_base_bank);
}

// Korean 20 in 1 collection with NES games
// - base bank is selected (in 32KB chunks) by bits 0-4 of data written at 0x808000
// - bits 6-7 seem related to prg size: 0x00 means 4*32KB, 0xc0 means 2*32KB, 0x80 means 1*32KB
//   (they are used to setup how large is the ROM to be accessed, games 15-20 don't work well if
//   accesses in [01-3f] don't go to the only 32KB bank)
// - bit 5 is always 0
// it's worth to notice that for FC games size of bank is twice the size of original FC PRG
READ8_MEMBER(sns_rom_20col_device::read_l)
{
	return read_h(space, offset);
}

READ8_MEMBER(sns_rom_20col_device::read_h)
{
	int prg32k = (!BIT(m_base_bank, 6) && BIT(m_base_bank, 7));
	int bank = prg32k ? 0 : (offset / 0x10000);
	return m_rom[((m_base_bank & 0x1f) + bank) * 0x8000 + (offset & 0x7fff)];
}

WRITE8_MEMBER( sns_rom_20col_device::chip_write )
{
	// [#]  game - written bank value
	// [01] spartan x - c6
	// [02] smb - c8
	// [03] antarcitc adv - 8e
	// [04] twinbee - ca
	// [05] battle city - 8f
	// [06] circus charlie - 90
	// [07] galaga - 91
	// [08] yie ar kungfu - 92
	// [09] star force - 93
	// [10] road fighter - 94
	// [11] pinball - 95
	// [12] bomberman - 96
	// [13] new tetris?? - 0
	// [14] arkanoid - cc
	// [15] balloon fight - 97
	// [16] donkey kong - 98
	// [17] donkey kong 3 - 99
	// [18] donkey kong jr - 9a
	// [19] mario bros - 9b
	// [20] popeye - 9c
	m_base_bank = data & 0xdf;
//  printf("offset %X data %X bank %X\n", offset, data, m_base_bank);
}



// Work in progress (probably very wrong)

READ8_MEMBER( sns_rom_banana_device::chip_read )
{
	return bitswap<8>(m_latch[0xf],0,6,7,1,2,3,4,5);
}

WRITE8_MEMBER( sns_rom_banana_device::chip_write )
{
//  printf("write addr %X data %X\n", offset, data);
	m_latch[0xf] = data;
}

READ8_MEMBER( sns_rom_bugs_device::chip_read )
{
	return bitswap<8>(m_latch[offset & 0xff],0,6,7,1,2,3,4,5);
}

WRITE8_MEMBER( sns_rom_bugs_device::chip_write )
{
	m_latch[offset & 0xff] = data;
}
