// license:BSD-3-Clause
// copyright-holders:Fabio Priuli, Wilbert Pol
/***********************************************************************************************************

 Game Boy carts with MBC (Memory Bank Controller)


 TODO: RTC runs too fast while in-game, in MBC-3 games... find the problem!

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


#include "emu.h"
#include "mbc.h"


//-------------------------------------------------
//  gb_rom_mbc*_device - constructor
//-------------------------------------------------

DEFINE_DEVICE_TYPE(GB_ROM_MBC1,     gb_rom_mbc1_device,        "gb_rom_mbc1",     "GB MBC1 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_MBC2,     gb_rom_mbc2_device,        "gb_rom_mbc2",     "GB MBC2 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_MBC3,     gb_rom_mbc3_device,        "gb_rom_mbc3",     "GB MBC3 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_MBC5,     gb_rom_mbc5_device,        "gb_rom_mbc5",     "GB MBC5 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_MBC6,     gb_rom_mbc6_device,        "gb_rom_mbc6",     "GB MBC6 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_MBC7,     gb_rom_mbc7_device,        "gb_rom_mbc7",     "GB MBC7 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_M161,     gb_rom_m161_device,        "gb_rom_m161",     "GB M161 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_MMM01,    gb_rom_mmm01_device,       "gb_rom_mmm01",    "GB MMM01 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_SACHEN1,  gb_rom_sachen_mmc1_device, "gb_rom_sachen1",  "GB Sachen MMC1 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_SACHEN2,  gb_rom_sachen_mmc2_device, "gb_rom_sachen2",  "GB Sachen MMC2 Carts")
DEFINE_DEVICE_TYPE(GB_ROM_188IN1,   gb_rom_188in1_device,      "gb_rom_188in1",   "GB 188in1")
DEFINE_DEVICE_TYPE(GB_ROM_SINTAX,   gb_rom_sintax_device,      "gb_rom_sintax",   "GB MBC5 Sintax Carts")
DEFINE_DEVICE_TYPE(GB_ROM_CHONGWU,  gb_rom_chongwu_device,     "gb_rom_chongwu",  "GB Chong Wu Xiao Jing Ling")
DEFINE_DEVICE_TYPE(GB_ROM_LICHENG,  gb_rom_licheng_device,     "gb_rom_licheng",  "GB MBC5 Li Cheng Carts")
DEFINE_DEVICE_TYPE(GB_ROM_DIGIMON,  gb_rom_digimon_device,     "gb_rom_digimon",  "GB Digimon")
DEFINE_DEVICE_TYPE(GB_ROM_ROCKMAN8, gb_rom_rockman8_device,    "gb_rom_rockman8", "GB MBC1 Rockman 8")
DEFINE_DEVICE_TYPE(GB_ROM_SM3SP,    gb_rom_sm3sp_device,       "gb_sm3sp",        "GB MBC1 Super Mario 3 Special")


gb_rom_mbc_device::gb_rom_mbc_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_gb_cart_interface(mconfig, *this)
	, m_ram_enable(0)
{
}

gb_rom_mbc1_device::gb_rom_mbc1_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, type, tag, owner, clock)
	, m_mode(MODE_16M_64k)
	, m_mask(0x1f)
	, m_shift(0)
{
}

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

gb_rom_mbc2_device::gb_rom_mbc2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_MBC2, tag, owner, clock)
{
}

gb_rom_mbc3_device::gb_rom_mbc3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_MBC3, tag, owner, clock)
	, m_rtc_ready(0)
{
}

gb_rom_mbc5_device::gb_rom_mbc5_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, type, tag, owner, clock)
{
}

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

gb_rom_mbc6_device::gb_rom_mbc6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_MBC6, tag, owner, clock), m_latch1(0), m_latch2(0), m_bank_4000(0), m_bank_6000(0)
{
}

gb_rom_mbc7_device::gb_rom_mbc7_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_MBC7, tag, owner, clock)
{
}

gb_rom_m161_device::gb_rom_m161_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_M161, tag, owner, clock), m_base_bank(0), m_load_disable(0)
{
}

gb_rom_mmm01_device::gb_rom_mmm01_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_MMM01, tag, owner, clock), m_romb(0), m_romb_nwe(0), m_ramb(0), m_ramb_nwe(0), m_mode(0), m_mode_nwe(0), m_map(0), m_mux(0)
{
}

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

gb_rom_sachen_mmc1_device::gb_rom_sachen_mmc1_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, type, tag, owner, clock), m_base_bank(0), m_mask(0), m_mode(0), m_unlock_cnt(0)
{
}

gb_rom_sachen_mmc2_device::gb_rom_sachen_mmc2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_sachen_mmc1_device(mconfig, GB_ROM_SACHEN2, tag, owner, clock)
{
}

gb_rom_188in1_device::gb_rom_188in1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc1_device(mconfig, GB_ROM_188IN1, tag, owner, clock), m_game_base(0)
{
}

gb_rom_sintax_device::gb_rom_sintax_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_SINTAX, tag, owner, clock), m_bank_mask(0), m_bank(0), m_reg(0), m_currentxor(0), m_xor2(0), m_xor3(0), m_xor4(0), m_xor5(0), m_sintax_mode(0)
{
}

gb_rom_chongwu_device::gb_rom_chongwu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc5_device(mconfig, GB_ROM_CHONGWU, tag, owner, clock), m_protection_checked(0)
{
}

gb_rom_licheng_device::gb_rom_licheng_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc5_device(mconfig, GB_ROM_LICHENG, tag, owner, clock)
{
}

gb_rom_digimon_device::gb_rom_digimon_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc5_device(mconfig, GB_ROM_DIGIMON, tag, owner, clock)
{
}

gb_rom_rockman8_device::gb_rom_rockman8_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_ROCKMAN8, tag, owner, clock), m_bank_mask(0), m_bank(0), m_reg(0)
{
}

gb_rom_sm3sp_device::gb_rom_sm3sp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: gb_rom_mbc_device(mconfig, GB_ROM_SM3SP, tag, owner, clock), m_bank_mask(0), m_bank(0), m_reg(0), m_mode(0)
{
}


//-------------------------------------------------
//  shared_start
//-------------------------------------------------

void gb_rom_mbc_device::shared_start()
{
	save_item(NAME(m_latch_bank));
	save_item(NAME(m_latch_bank2));
	save_item(NAME(m_ram_bank));
	save_item(NAME(m_ram_enable));
}

//-------------------------------------------------
//  shared_reset
//-------------------------------------------------

void gb_rom_mbc_device::shared_reset()
{
	m_latch_bank = 0;
	m_latch_bank2 = 1;
	m_ram_bank = 0;
	m_ram_enable = 0;
}

//-------------------------------------------------
//  mapper specific start/reset
//-------------------------------------------------

void gb_rom_mbc3_device::device_start()
{
	shared_start();
	save_item(NAME(m_rtc_regs));
	save_item(NAME(m_rtc_ready));
}

void gb_rom_mbc3_device::device_reset()
{
	shared_reset();
	memset(m_rtc_regs, 0, sizeof(m_rtc_regs));
	m_rtc_ready = 0;
}

void gb_rom_mbc6_device::device_start()
{
	save_item(NAME(m_bank_4000));
	save_item(NAME(m_bank_6000));
	save_item(NAME(m_latch1));
	save_item(NAME(m_latch2));
	save_item(NAME(m_latch_bank));
	save_item(NAME(m_latch_bank2));
	save_item(NAME(m_ram_bank));
	save_item(NAME(m_ram_enable));
}

void gb_rom_mbc6_device::device_reset()
{
	m_bank_4000 = 2;    // correct default?
	m_bank_6000 = 3;    // correct default?
	m_latch1 = 0;   // correct default?
	m_latch2 = 0;   // correct default?

	m_latch_bank = 2;   // correct default?
	m_latch_bank2 = 3;  // correct default?
	m_ram_bank = 0;
	m_ram_enable = 0;
}

void gb_rom_m161_device::device_start()
{
	shared_start();
	save_item(NAME(m_base_bank));
	save_item(NAME(m_load_disable));
}

void gb_rom_m161_device::device_reset()
{
	shared_reset();
	m_base_bank = 0;
	m_load_disable = 0;
}

void gb_rom_mmm01_device::device_start()
{
	shared_start();
	save_item(NAME(m_romb));
	save_item(NAME(m_romb_nwe));
	save_item(NAME(m_ramb));
	save_item(NAME(m_ramb_nwe));
	save_item(NAME(m_mode));
	save_item(NAME(m_mode_nwe));
	save_item(NAME(m_map));
	save_item(NAME(m_mux));
}

void gb_rom_mmm01_device::device_reset()
{
	m_romb = 0x000;
	m_romb_nwe = 0x00;
	m_ramb = 0x00;
	m_ramb_nwe = 0x00;
	m_mode = 0x00;
	m_map = 0x00;
	m_mux = 0x00;
}

void gb_rom_sachen_mmc1_device::device_start()
{
	shared_start();
	save_item(NAME(m_base_bank));
	save_item(NAME(m_mask));
	save_item(NAME(m_mode));
	save_item(NAME(m_unlock_cnt));
}

void gb_rom_sachen_mmc1_device::device_reset()
{
	shared_reset();
	m_base_bank = 0x00;
	m_mask = 0x00;
	m_mode = MODE_LOCKED;
	m_unlock_cnt = 0x00;
}

void gb_rom_sachen_mmc2_device::device_start()
{
	shared_start();
	save_item(NAME(m_base_bank));
	save_item(NAME(m_mask));
	save_item(NAME(m_mode));
	save_item(NAME(m_unlock_cnt));
}

void gb_rom_sachen_mmc2_device::device_reset()
{
	shared_reset();
	m_base_bank = 0x00;
	m_mask = 0x00;
	m_mode = MODE_LOCKED_DMG;
	m_unlock_cnt = 0x00;
}

void gb_rom_sintax_device::device_start()
{
	shared_start();
	save_item(NAME(m_sintax_mode));
	save_item(NAME(m_currentxor));
	save_item(NAME(m_xor2));
	save_item(NAME(m_xor3));
	save_item(NAME(m_xor4));
	save_item(NAME(m_xor5));
}

void gb_rom_sintax_device::device_reset()
{
	shared_reset();
	m_sintax_mode = 0;
	m_currentxor = 0;
	m_xor2 = 0;
	m_xor3 = 0;
	m_xor4 = 0;
	m_xor5 = 0;
}

void gb_rom_chongwu_device::device_start()
{
	shared_start();
	save_item(NAME(m_protection_checked));
}

void gb_rom_chongwu_device::device_reset()
{
	shared_reset();
	m_protection_checked = 0;
}


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

READ8_MEMBER(gb_rom_mbc_device::read_rom)
{
	return m_rom[rom_bank_map[m_latch_bank] + offset];
}

READ8_MEMBER(gb_rom_mbc_device::read_ram)
{
	if (!m_ram.empty())
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + offset];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc_device::write_ram)
{
	if (!m_ram.empty())
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + offset] = data;
}


// MBC1

READ8_MEMBER(gb_rom_mbc1_device::read_rom)
{
	if (offset & 0x4000) /* RB1 */
		return m_rom[rom_bank_map[(m_ram_bank << (5 + m_shift)) | m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
	else
	{                    /* RB0 */
		int bank = (m_mode == MODE_4M_256k) ? (m_ram_bank << (5 + m_shift)) : 0;
		return m_rom[rom_bank_map[bank] * 0x4000 + (offset & 0x3fff)];
	}
}

WRITE8_MEMBER(gb_rom_mbc1_device::write_bank)
{
	// the mapper only uses inputs A15..A13
	switch (offset & 0xe000)
	{
		case 0x0000:    // RAM Enable Register
			m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0;
			break;
		case 0x2000:    // ROM Bank Register
			data &= 0x1f;
			m_latch_bank2 = data ? data : 1;
			m_latch_bank2 &= m_mask;
			break;
		case 0x4000:    // RAM Bank Register
			m_ram_bank = data & 0x3;
			break;
		case 0x6000:    // MBC1 Mode Register
			m_mode = (data & 0x1) ? MODE_4M_256k : MODE_16M_64k;
			break;
	}
}

READ8_MEMBER(gb_rom_mbc1_device::read_ram)
{
	if (!m_ram.empty() && m_ram_enable)
	{
		int bank = (m_mode == MODE_4M_256k) ? m_ram_bank : 0;
		return m_ram[ram_bank_map[bank] * 0x2000 + offset];
	}
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc1_device::write_ram)
{
	if (!m_ram.empty() && m_ram_enable)
	{
		int bank = (m_mode == MODE_4M_256k) ? m_ram_bank : 0;
		m_ram[ram_bank_map[bank] * 0x2000 + offset] = data;
	}
}


// MBC2

READ8_MEMBER(gb_rom_mbc2_device::read_rom)
{
	if (offset & 0x4000) /* RB1 */
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
	else                 /* RB0 */
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_mbc2_device::write_bank)
{
	// the mapper only has data lines D3..D0
	data &= 0x0f;

	// the mapper only uses inputs A15..A14, A8 for register accesses
	switch (offset & 0xc100)
	{
		case 0x0000:    // RAM Enable Register
			m_ram_enable = (data == 0x0a) ? 1 : 0;
			break;
		case 0x0100:    // ROM Bank Register
			m_latch_bank2 = (data == 0x00) ? 0x01 : data;
			break;
	}
}

READ8_MEMBER(gb_rom_mbc2_device::read_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x01ff)] | 0xf0;
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc2_device::write_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x01ff)] = data & 0x0f;
}


// MBC3

void gb_rom_mbc3_device::update_rtc()
{
	system_time curtime;
	machine().current_datetime(curtime);

	m_rtc_regs[0] = curtime.local_time.second;
	m_rtc_regs[1] = curtime.local_time.minute;
	m_rtc_regs[2] = curtime.local_time.hour;
	m_rtc_regs[3] = curtime.local_time.day & 0xff;
	m_rtc_regs[4] = (m_rtc_regs[4] & 0xf0) | (curtime.local_time.day >> 8);
}

READ8_MEMBER(gb_rom_mbc3_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_mbc3_device::write_bank)
{
	if (offset < 0x2000)
		m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0;
	else if (offset < 0x4000)
	{
		// 7bits
		data &= 0x7f;
		/* Selecting bank 0 == selecting bank 1 */
		if (data == 0)
			data = 1;

		m_latch_bank2 = data;
	}
	else if (offset < 0x6000)
	{
		m_ram_bank = data;
	}
	else if (has_timer)
	{
		if (m_rtc_ready == 1 && data == 0)
			m_rtc_ready = 0;
		if (m_rtc_ready == 0 && data == 1)
		{
			m_rtc_ready = 1;
			update_rtc();
		}
	}
}

READ8_MEMBER(gb_rom_mbc3_device::read_ram)
{
	if (m_ram_bank < 4 && m_ram_enable)
	{
		// RAM
		if (!m_ram.empty())
			return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)];
	}
	if (m_ram_bank >= 0x8 && m_ram_bank <= 0xc)
	{
		// RTC registers
		if (has_timer)
			return m_rtc_regs[m_ram_bank - 8];
	}
	return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc3_device::write_ram)
{
	if (m_ram_bank < 4 && m_ram_enable)
	{
		// RAM
		if (!m_ram.empty())
			m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data;
	}
	if (m_ram_bank >= 0x8 && m_ram_bank <= 0xc && m_ram_enable)
	{
		// RTC registers are writeable too
		if (has_timer)
			m_rtc_regs[m_ram_bank - 8] = data;
	}
}

// MBC5

READ8_MEMBER(gb_rom_mbc5_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_mbc5_device::write_bank)
{
	if (offset < 0x2000)
		m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0;
	else if (offset < 0x3000)
	{
		// MBC5 has a 9 bit bank select
		// Writing into 2000-2fff sets the lower 8 bits
		m_latch_bank2 = (m_latch_bank2 & 0x100) | data;
	}
	else if (offset < 0x4000)
	{
		// MBC5 has a 9 bit bank select
		// Writing into 3000-3fff sets the 9th bit
		m_latch_bank2 = (m_latch_bank2 & 0xff) | ((data & 0x01) << 8);
	}
	else if (offset < 0x6000)
	{
		data &= 0x0f;
		if (has_rumble)
		{
			machine().output().set_value("Rumble", BIT(data, 3));
			data &= 0x7;
		}
		m_ram_bank = data;
	}
}

READ8_MEMBER(gb_rom_mbc5_device::read_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc5_device::write_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data;
}

// MBC6

READ8_MEMBER(gb_rom_mbc6_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else if (offset < 0x6000)
		return m_rom[rom_bank_map[m_bank_4000 >> 1] * 0x4000 + (m_bank_4000 & 0x01) * 0x2000 + (offset & 0x1fff)];
	else
		return m_rom[rom_bank_map[m_bank_6000 >> 1] * 0x4000 + (m_bank_6000 & 0x01) * 0x2000 + (offset & 0x1fff)];
}

WRITE8_MEMBER(gb_rom_mbc6_device::write_bank)
{
	if (offset < 0x2000)
	{
		logerror( "%s write to mbc6 ram enable area: %04X <- 0x%02X\n", machine().describe_context(), offset, data );
	}
	else if (offset < 0x3000)
	{
		if (!(offset & 0x0800))
			m_latch1 = data;
		else if (data == 0x00)
			m_bank_4000 = m_latch1;
	}
	else if (offset < 0x4000)
	{
		if (!(offset & 0x0800))
			m_latch2 = data;
		else if (data == 0x00)
			m_bank_6000 = m_latch2;
	}
}

READ8_MEMBER(gb_rom_mbc6_device::read_ram)
{
	if (!m_ram.empty())
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc6_device::write_ram)
{
	if (!m_ram.empty())
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data;
}

// MBC7

READ8_MEMBER(gb_rom_mbc7_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_mbc7_device::write_bank)
{
	if (offset < 0x2000)
	{
		// FIXME: Add RAM enable support
		logerror("%s Write to ram enable register 0x%04X <- 0x%02X\n", machine().describe_context(), offset, data);
	}
	else if (offset < 0x3000)
	{
		logerror( "%s write to mbc7 rom select register: 0x%04X <- 0x%02X\n", machine().describe_context(), 0x2000 + offset, data );
		/* Bit 12 must be set for writing to the mbc register */
		if (offset & 0x0100)
			m_latch_bank2 = data;
	}
	else
	{
		logerror( "%s write to mbc7 rom area: 0x%04X <- 0x%02X\n", machine().describe_context(), 0x3000 + offset, data );
		/* Bit 12 must be set for writing to the mbc register */
		if (offset & 0x0100)
		{
			switch (offset & 0x7000)
			{
				case 0x3000:    /* 0x3000-0x3fff */
				case 0x4000:    /* 0x4000-0x4fff */
				case 0x5000:    /* 0x5000-0x5fff */
				case 0x6000:    /* 0x6000-0x6fff */
				case 0x7000:    /* 0x7000-0x7fff */
					break;
			}
		}
	}
}

READ8_MEMBER(gb_rom_mbc7_device::read_ram)
{
	if (!m_ram.empty())
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mbc7_device::write_ram)
{
	if (!m_ram.empty())
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data;
}


// M161

READ8_MEMBER(gb_rom_m161_device::read_rom)
{
	return m_rom[rom_bank_map[m_base_bank] * 0x4000 + (offset & 0x7fff)];
}

WRITE8_MEMBER(gb_rom_m161_device::write_bank)
{
	// the mapper (74HC161A) only has data lines D2..D0
	data &= 0x07;

	// A15 is connected to #LOAD and overwritten by QD (m_load_disable)
	switch (offset & 0x8000)
	{
		case 0x0000:    // Base Bank Register
			if (!m_load_disable)
				m_base_bank = data << 1;
			m_load_disable = 0x01;
			break;
		default:
			break;
	}
}


// MMM01

READ8_MEMBER(gb_rom_mmm01_device::read_rom)
{
	uint16_t romb = m_romb & ~(0x1e0 | m_romb_nwe);
	uint16_t romb_base = m_romb & (0x1e0 | m_romb_nwe);
	uint8_t ramb_masked = ((offset & 0x4000) | m_mode ? m_ramb : m_ramb & ~0x03);

	// zero-adjust RA18..RA14
	romb = (romb ? romb : 0x01);
	// if unmapped, force
	romb = (m_map ? romb : 0x01);

	// RB 0 logic
	if (!(offset & 0x4000))
		romb = 0x00;

	// combine with base
	romb |= romb_base;

	// multiplex with AA14..AA13
	if (m_mux)
		romb = (romb & ~0x60) | ((ramb_masked & 0x03) << 5);

	// if unmapped, force
	if (!m_map)
		romb |= 0x1fe;

	return m_rom[rom_bank_map[romb] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_mmm01_device::write_bank)
{
	// the mapper only has data lines D6..D0
	data &= 0x7f;

	// the mapper only uses inputs A15..A13
	switch (offset & 0xe000)
	{
		case 0x0000: // Map Latch, AA #WE, RAM Enable
			if (!m_map) {
				m_ramb_nwe = (data & (0x30)) >> 4;
				m_map = (data & 0x40);
			}
			m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0;
			break;
		case 0x2000: // RA20..RA19 RA18..RA14
			if (!m_map)
				m_romb = (m_romb & ~0x60) | (data & 0x60);

			m_romb = (m_romb & (~0x1f | m_romb_nwe)) | (data & (0x1f & ~m_romb_nwe));
			break;
		case 0x4000: // Mode #WE, RA22..RA21, AA16..AA15, AA14..AA13
			if (!m_map) {
				m_mode_nwe = data & 0x40;
				m_romb = (m_romb & ~0x180) | ((data & 0x30) << 3);
				m_ramb = (m_ramb & ~0x0c) | (data & 0x0c);
			}

			m_ramb = (m_ramb & (~0x03 | m_ramb_nwe)) | (data & (0x03 & ~m_ramb_nwe));
			break;
		case 0x6000: // Mux, RA18..RA15 #WE/Mask, ???, MBC1 Mode
			if (!m_map) {
				m_mux = data & 0x40;
				// m_romb_nwe is aligned to RA14, hence >> 1 instead of >> 2
				m_romb_nwe = (data & 0x3c) >> 1;
			}

			if (!m_mode_nwe)
				m_mode = data & 0x01;
			break;
		default:
			break;
	}
}

READ8_MEMBER(gb_rom_mmm01_device::read_ram)
{
	uint8_t ramb_masked = ((offset & 0x4000) | m_mode ? m_ramb : m_ramb & ~0x03);
	uint8_t ramb = ramb_masked;

	// multiplex with RA20..RA19
	if (m_mux)
		ramb = (ramb & ~0x03) | ((m_romb & 0x60) >> 5);

	if (!m_ram.empty() && m_ram_enable)
	{
		return m_ram[ram_bank_map[ramb] * 0x2000 + (offset & 0x1fff)];
	}
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_mmm01_device::write_ram)
{
	uint8_t ramb_masked = ((offset & 0x4000) | m_mode ? m_ramb : m_ramb & ~0x03);
	uint8_t ramb = ramb_masked;

	// multiplex with RA20..RA19
	if (m_mux)
		ramb = (ramb & ~0x03) | ((m_romb & 0x60) >> 5);

	if (!m_ram.empty() && m_ram_enable)
	{
		m_ram[ram_bank_map[ramb] * 0x2000 + (offset & 0x1fff)] = data;
	}
}

// Sachen MMC1

READ8_MEMBER(gb_rom_sachen_mmc1_device::read_rom)
{
	uint16_t off_edit = offset;

	/* Wait for 0x31 transitions of A15 (hi -> lo), i.e. ROM accesses; A15 = HI while in bootstrap */
	/* This is 0x31 transitions, because we increment counter _after_ checking it */
	if (m_unlock_cnt == 0x30)
		m_mode = MODE_UNLOCKED;
	else
		m_unlock_cnt++;

	/* Logo Switch */
	if (m_mode == MODE_LOCKED)
		off_edit |= 0x80;

	/* Header Un-Scramble */
	if ((off_edit & 0xff00) == 0x0100) {
		off_edit &= 0xffac;
		off_edit |= ((offset >> 6) & 0x01) << 0;
		off_edit |= ((offset >> 4) & 0x01) << 1;
		off_edit |= ((offset >> 1) & 0x01) << 4;
		off_edit |= ((offset >> 0) & 0x01) << 6;
	}
	//logerror("read from %04X (%04X)\n", offset, off_edit);

	if (offset & 0x4000) /* RB1 */
		return m_rom[rom_bank_map[(m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask)] * 0x4000 + (offset & 0x3fff)];
	else                 /* RB0 */
		return m_rom[rom_bank_map[(m_base_bank & m_mask) | (m_latch_bank & ~m_mask)] * 0x4000 + (off_edit & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_sachen_mmc1_device::write_bank)
{
	/* Only A15..A6, A4, A1..A0 are connected */
	/* We only decode upper three bits */
	switch ((offset & 0xffd3) & 0xe000)
	{
		case 0x0000: /* Base ROM Bank Register */

			if ((m_latch_bank2 & 0x30) == 0x30)
				m_base_bank = data;
			//logerror("write to base bank %X - %X\n", data, (m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask));
			break;

		case 0x2000: /* ROM Bank Register */

			m_latch_bank2 = data ? data : 0x01;
			//logerror("write to latch %X - %X\n", data, (m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask));
			break;

		case 0x4000: /* ROM Bank Mask Register */

			if ((m_latch_bank2 & 0x30) == 0x30)
				m_mask = data;
			//logerror("write to mask %X - %X\n", data, (m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask));
			break;

		case 0x6000:

			/* nothing happens when writing to 0x6000-0x7fff, as verified by Tauwasser */
			break;

		default:

			//logerror("write to unknown/unmapped area %04X <= %02X\n", offset, data);
			/* did not extensively test other unlikely ranges */
			break;
	}
}

// Sachen MMC2

READ8_MEMBER(gb_rom_sachen_mmc2_device::read_rom)
{
	uint16_t off_edit = offset;

	/* Wait for 0x30 transitions of A15 (lo -> hi), i.e. ROM accesses; A15 = HI while in bootstrap */
	/* This is 0x30 transitions, because we increment counter _after_ checking it, but A15 lo -> hi*/
	/* transition means first read (hi -> lo transition) must not count */

	if (m_unlock_cnt == 0x30 && m_mode == MODE_LOCKED_DMG) {
		m_mode = MODE_LOCKED_CGB;
		m_unlock_cnt = 0x00;
	} else if (m_unlock_cnt == 0x30 && m_mode == MODE_LOCKED_CGB) {
		m_mode = MODE_UNLOCKED;
	}

	if (m_unlock_cnt != 0x30)
		m_unlock_cnt++;

	/* Logo Switch */
	if (m_mode == MODE_LOCKED_CGB)
		off_edit |= 0x80;

	/* Header Un-Scramble */
	if ((off_edit & 0xff00) == 0x0100) {
		off_edit &= 0xffac;
		off_edit |= ((offset >> 6) & 0x01) << 0;
		off_edit |= ((offset >> 4) & 0x01) << 1;
		off_edit |= ((offset >> 1) & 0x01) << 4;
		off_edit |= ((offset >> 0) & 0x01) << 6;
	}
	//logerror("read from %04X (%04X) cnt: %02X\n", offset, off_edit, m_unlock_cnt);

	if (offset & 0x4000) /* RB1 */
		return m_rom[rom_bank_map[(m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask)] * 0x4000 + (offset & 0x3fff)];
	else                 /* RB0 */
		return m_rom[rom_bank_map[(m_base_bank & m_mask) | (m_latch_bank & ~m_mask)] * 0x4000 + (off_edit & 0x3fff)];
}

READ8_MEMBER(gb_rom_sachen_mmc2_device::read_ram)
{
	if (m_mode == MODE_LOCKED_DMG) {
		m_unlock_cnt = 0x00;
		m_mode = MODE_LOCKED_CGB;
	}
	return 0xff;

}

WRITE8_MEMBER(gb_rom_sachen_mmc2_device::write_ram)
{
	if (m_mode == MODE_LOCKED_DMG) {
		m_unlock_cnt = 0x00;
		m_mode = MODE_LOCKED_CGB;
	}

}


// 188 in 1 pirate (only preliminary)

READ8_MEMBER(gb_rom_188in1_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[m_game_base + rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[m_game_base + rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_188in1_device::write_bank)
{
	if (offset == 0x7b00)
	{
		if (data < 0x80)
			logerror("write to 0x%X data 0x%X\n", offset, data);
		else
		{
			data -= 0x80;
			m_game_base = 0x400000 + (data * 0x8000);
			//logerror("offset 0x%X\n", m_game_base);
		}
	}
	else if (offset == 0x7b01 || offset == 0x7b02)
	{
		// what do these writes do?
		printf("write to 0x%X data 0x%X\n", offset, data);
	}
	else
		gb_rom_mbc1_device::write_bank(space, offset, data);
}


// MBC5 variant used by Li Cheng / Niutoude games

WRITE8_MEMBER(gb_rom_licheng_device::write_bank)
{
	if (offset > 0x2100 && offset < 0x3000)
		return;

	gb_rom_mbc5_device::write_bank(space, offset, data);
}

// MBC5 variant used by Chong Wu Xiao Jing Ling (this appears to be a re-release of a Li Cheng / Niutoude game,
// given that it contains the Niutoude logo, with most protection checks patched out)

READ8_MEMBER(gb_rom_chongwu_device::read_rom)
{
	// protection check at the first read here...
	if (offset == 0x41c3 && !m_protection_checked)
	{
		m_protection_checked = 1;
		return 0x5d;
	}

	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
}

// MBC5 variant used by Sintax games

void gb_rom_sintax_device::set_xor_for_bank(uint8_t bank)
{
	switch (bank & 0x0f)
	{
		case 0x00: case 0x04: case 0x08: case 0x0c:
			m_currentxor = m_xor2;
			break;
		case 0x01: case 0x05: case 0x09: case 0x0d:
			m_currentxor = m_xor3;
			break;
		case 0x02: case 0x06: case 0x0a: case 0x0e:
			m_currentxor = m_xor4;
			break;
		case 0x03: case 0x07: case 0x0b: case 0x0f:
			m_currentxor = m_xor5;
			break;
	}
}

READ8_MEMBER(gb_rom_sintax_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)] ^ m_currentxor;
}

WRITE8_MEMBER(gb_rom_sintax_device::write_bank)
{
	if (offset < 0x2000)
		m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0;
	else if (offset < 0x3000)
	{
		set_xor_for_bank(data);

		switch (m_sintax_mode & 0x0f)
		{
			case 0x0d:
				data = bitswap<8>(data, 1,0,7,6,5,4,3,2);
				break;
			case 0x09:
				//data = bitswap<8>(data, 3,2,5,4,0,1,6,7); // Monkey..no
				data = bitswap<8>(data, 4,5,2,3,0,1,6,7);
				break;
			case 0x00: // 0x10=lion 0x00 hmmmmm // 1 and 0 unconfirmed
				data = bitswap<8>(data, 7,0,5,6,3,4,1,2);
				break;
			case 0x01:
				data = bitswap<8>(data, 0,1,6,7,4,5,2,3);
				break;
			case 0x05:
				data = bitswap<8>(data, 7,6,1,0,3,2,5,4); // Not 100% on this one
				break;
			case 0x07:
				data = bitswap<8>(data, 2,0,3,1,5,4,7,6); // 5 and 7 unconfirmed
				break;
			case 0x0b:
				data = bitswap<8>(data, 2,3,0,1,6,7,4,5); // 5 and 6 unconfirmed
				break;
		}
		m_latch_bank2 = (m_latch_bank2 & 0x100) | data;
	}
	else if (offset < 0x4000)
	{
		m_latch_bank2 = (m_latch_bank2 & 0xff) | ((data & 0x01) << 8);
	}
	else if (offset < 0x5000)
	{
		data &= 0x0f;
		m_ram_bank = data;
	}
	else if (offset < 0x6000)
	{
		if (!m_sintax_mode)
		{
			m_sintax_mode = data;
			write_bank(space, 0x2000, 1);   //force a fake bank switch
		}
//      printf("sintax mode %x\n", m_sintax_mode & 0xf);
	}
	else if (offset >= 0x7000)
	{
		switch ((offset & 0x00f0) >> 4)
		{
			case 2:
				m_xor2 = data;
				break;
			case 3:
				m_xor3 = data;
				break;
			case 4:
				m_xor4 = data;
				break;
			case 5:
				m_xor5 = data;
				break;
		}

		if (m_currentxor == 0)
			set_xor_for_bank(4);
	}

}

READ8_MEMBER(gb_rom_sintax_device::read_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_sintax_device::write_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data;
}

/*

 Further MBC5 variants to emulate:

 Digimon 2 & Digimon 4 (Yong Yong)

 Digimon 2 writes at $2000 to select latch2 (data must be divided by 2, and 0 becomes 1),
 then writes to $2400 a series of values that the patched version does not write...
 Digimon 4 seems to share part of the $2000 behavior, but does not write to $2400...

 */

// MBC5 variant used by Digimon 2 (and maybe 4?)

READ8_MEMBER(gb_rom_digimon_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_digimon_device::write_bank)
{
	if (offset < 0x2000)
		m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0;
	else if (offset == 0x2000)
	{
//      printf("written $02 %X at %X\n", data, offset);
		if (!data)
			data++;
		m_latch_bank2 = data/2;
	}
	else if (offset < 0x3000)
	{
//      printf("written $03 %X at %X\n", data, offset);
	}
	else if (offset < 0x4000)
	{
//      printf("written $04 %X at %X\n", data, offset);
	}
	else if (offset < 0x6000)
	{
//      printf("written $05-$06 %X at %X\n", data, offset);
		data &= 0x0f;
		m_ram_bank = data;
	}
//  else
//      printf("written $07 %X at %X\n", data, offset);
}

READ8_MEMBER(gb_rom_digimon_device::read_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_digimon_device::write_ram)
{
	if (!m_ram.empty() && m_ram_enable)
		m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data;
}


// MBC1 variant used by Yong Yong for Rockman 8

READ8_MEMBER(gb_rom_rockman8_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[m_latch_bank * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[m_latch_bank2 * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_rockman8_device::write_bank)
{
	if (offset < 0x2000)
		return;
	else if (offset < 0x4000)
	{
		// 5bits only
		data &= 0x1f;
		if (data == 0)
			data = 1;
		if (data > 0xf)
			data -= 8;

		m_latch_bank2 = data;
	}
}

READ8_MEMBER(gb_rom_rockman8_device::read_ram)
{
	if (!m_ram.empty())
		return m_ram[offset];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_rockman8_device::write_ram)
{
	if (!m_ram.empty())
		m_ram[offset] = data;
}

// MBC1 variant used by Yong Yong for Super Mario 3 Special

// Mario special seems to be 512k image (mirrored up to 1m or 2m [redump needed to establish this])
// it consists of 13 unique 16k chunks layed out as follows
// unique chunk --> bank in bin
// 1st to 7th   --> 0x00 to 0x06
// 8th          --> 0x08
// 9th          --> 0x0b
// 10th         --> 0x0c
// 11th         --> 0x0d
// 12th         --> 0x0f
// 13th         --> 0x13

// writing data to 0x2000-0x2fff switches bank according to the table below
// (the value values corresponding to table[0x0f] is not confirmed, choices
// 0,1,2,3,8,c,f freeze the game, while 4,5,6,7,b,d,0x13 work with glitches)
static uint8_t smb3_table1[0x20] =
{
	0x00,0x04,0x01,0x05, 0x02,0x06,0x03,0x05, 0x08,0x0c,0x03,0x0d, 0x03,0x0b,0x0b,0x08 /* original doc here put 0x0f (i.e. 11th unique bank) */,
	0x05,0x06,0x0b,0x0d, 0x08,0x06,0x13,0x0b, 0x08,0x05,0x05,0x08, 0x0b,0x0d,0x06,0x05
};

// according to old doc from Brian Provinciano, writing bit5 in 0x5000-0x5fff should
// change the bank layout, in the sense that writing to bankswitch acts like if
// the original rom has a different layout (as if unique chunks were under permutations
// (24), (365) and (8a9) with 0,1,7,b,c fixed) and the same table above is used
// however, no such a write ever happen (only bit4 is written, but changing mode with
// bit4 breaks the gfx...)

READ8_MEMBER(gb_rom_sm3sp_device::read_rom)
{
	if (offset < 0x4000)
		return m_rom[rom_bank_map[0] * 0x4000 + (offset & 0x3fff)];
	else
		return m_rom[m_latch_bank2 * 0x4000 + (offset & 0x3fff)];
}

WRITE8_MEMBER(gb_rom_sm3sp_device::write_bank)
{
//  printf("write 0x%x at %x\n", data, offset);
	if (offset < 0x2000)
		return;
	else if (offset < 0x3000)
	{
		// Table 1 confirmed...
		// 0->0, 4->2, 6->3
		// 1e -> 6 (level 1 bg gfx)
		// 19 -> 5 (level 2 bg gfx)
		// 1b -> 8 (level 3 bg gfx)
		// 1d -> D (level 4 bg gfx)
		// 1c -> B (bonus house bg gfx)
		// 1 (9 maybe, or 3)? f (5 maybe)? 2->1?
		// 16 -> 4-8? b?

		// 5bits only
		data &= 0x1f;

		m_latch_bank2 = smb3_table1[data];
		if (m_mode)
		{
			switch (m_latch_bank2)
			{
				case 0x02:  m_latch_bank2 = 4;  break;
				case 0x03:  m_latch_bank2 = 6;  break;
				case 0x04:  m_latch_bank2 = 2;  break;
				case 0x05:  m_latch_bank2 = 3;  break;
				case 0x06:  m_latch_bank2 = 5;  break;
				case 0x0b:  m_latch_bank2 = 0xd;    break;
				case 0x0c:  m_latch_bank2 = 0xb;    break;
				case 0x0d:  m_latch_bank2 = 0xc;    break;

				case 0x00:
				case 0x01:
				case 0x08:
				case 0x0f:
				case 0x13:
				default:
					break;
			}
		}
	}
	else if (offset < 0x5000)
	{
//      printf("write $5 %X at %X\n", data, offset);
		//maybe rumble??
	}
	else if (offset < 0x6000)
	{
//      printf("write mode %x\n", data);
		m_mode = BIT(data, 5);
//      write_bank(space, 0x2000, 1);
	}
}

READ8_MEMBER(gb_rom_sm3sp_device::read_ram)
{
	if (!m_ram.empty())
		return m_ram[offset];
	else
		return 0xff;
}

WRITE8_MEMBER(gb_rom_sm3sp_device::write_ram)
{
	if (!m_ram.empty())
		m_ram[offset] = data;
}
