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


    Atari VCS 2600 cart emulation
    (through slot devices)

    Emulation of the cartslot for Atari 2600
    Several banking schemes have been used for larger roms,
    and some carts contained RAM (so called "Special Chip")

    Mapper identification routines based on Cowering's code.

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

#include "emu.h"
#include "vcs_slot.h"

//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

DEFINE_DEVICE_TYPE(VCS_CART_SLOT, vcs_cart_slot_device, "vcs_cart_slot", "Atari VCS 2600 Cartridge Slot")

/* PCB */
enum
{
	A26_2K_4K = 0,
	A26_F4,
	A26_F6,
	A26_F8,
	A26_F8SW,
	A26_FA,
	A26_FE,
	A26_3E,     // to test
	A26_3F,
	A26_E0,
	A26_E7,
	A26_UA,
	A26_DC,
	A26_CV,
	A26_FV,
	A26_JVP,    // to test
	A26_32IN1,
	A26_8IN1,
	A26_4IN1,
	A26_DPC,
	A26_SS,
	A26_CM,
	A26_X07,
	A26_HARMONY,
};

//-------------------------------------------------
//  device_vcs_cart_interface - constructor
//-------------------------------------------------

device_vcs_cart_interface::device_vcs_cart_interface(const machine_config &mconfig, device_t &device) :
	device_interface(device, "vcscart"),
	m_rom(nullptr),
	m_rom_size(0)
{
}


//-------------------------------------------------
//  ~device_vcs_cart_interface - destructor
//-------------------------------------------------

device_vcs_cart_interface::~device_vcs_cart_interface()
{
}

//-------------------------------------------------
//  rom_alloc - alloc the space for the cart
//-------------------------------------------------

void device_vcs_cart_interface::rom_alloc(uint32_t size, const char *tag)
{
	if (m_rom == nullptr)
	{
		m_rom = device().machine().memory().region_alloc(std::string(tag).append(":cart:rom"), size, 1, ENDIANNESS_LITTLE)->base();
		m_rom_size = size;
	}
}

//-------------------------------------------------
//  ram_alloc - alloc the space for the on-cart RAM
//-------------------------------------------------

void device_vcs_cart_interface::ram_alloc(uint32_t size)
{
	m_ram.resize(size);
	device().save_item(NAME(m_ram));
}



//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  vcs_cart_slot_device - constructor
//-------------------------------------------------
vcs_cart_slot_device::vcs_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, VCS_CART_SLOT, tag, owner, clock),
	device_cartrom_image_interface(mconfig, *this),
	device_single_card_slot_interface<device_vcs_cart_interface>(mconfig, *this),
	m_cart(nullptr),
	m_type(0),
	m_address_space(*this, finder_base::DUMMY_TAG, -1, 8)
{
}


//-------------------------------------------------
//  vcs_cart_slot_device - destructor
//-------------------------------------------------

vcs_cart_slot_device::~vcs_cart_slot_device()
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void vcs_cart_slot_device::device_start()
{
	m_cart = get_card_device();
}



/*-------------------------------------------------
 call load
 -------------------------------------------------*/

//-------------------------------------------------
//  VCS PCBs
//-------------------------------------------------

struct vcs_slot
{
	int                     pcb_id;
	const char              *slot_option;
};

// Here, we take the feature attribute from .xml (i.e. the PCB name) and we assign a unique ID to it
static const vcs_slot slot_list[] =
{
	{ A26_2K_4K, "a26_2k_4k" },
	{ A26_F4, "a26_f4" },
	{ A26_F6, "a26_f6" },
	{ A26_F8, "a26_f8" },
	{ A26_F8SW, "a26_f8sw" },
	{ A26_FA, "a26_fa" },
	{ A26_FE, "a26_fe" },
	{ A26_E0, "a26_e0" },
	{ A26_E7, "a26_e7" },
	{ A26_3E, "a26_3e" },
	{ A26_3F, "a26_3f" },
	{ A26_UA, "a26_ua" },
	{ A26_CV, "a26_cv" },
	{ A26_DC, "a26_dc" },
	{ A26_FV, "a26_fv" },
	{ A26_JVP, "a26_jvp" },
	{ A26_CM, "a26_cm" },
	{ A26_SS, "a26_ss" },
	{ A26_DPC, "a26_dpc" },
	{ A26_4IN1, "a26_4in1" },
	{ A26_8IN1, "a26_8in1" },
	{ A26_32IN1, "a26_32in1" },
	{ A26_X07, "a26_x07" },
	{ A26_HARMONY, "a26_harmony" },
};

static int vcs_get_pcb_id(const char *slot)
{
	for (auto & elem : slot_list)
	{
		if (!strcmp(elem.slot_option, slot))
			return elem.pcb_id;
	}

	return 0;
}

static const char *vcs_get_slot(int type)
{
	for (auto & elem : slot_list)
	{
		if (elem.pcb_id == type)
			return elem.slot_option;
	}

	return "a26_2k_4k";
}

image_init_result vcs_cart_slot_device::call_load()
{
	if (m_cart)
	{
		const uint32_t len = loaded_through_softlist() ? get_software_region_length("rom") : length();

		// check that filesize is among the supported ones
		switch (len)
		{
			case 0x00800:
			case 0x01000:
			case 0x02000:
			case 0x028ff:
			case 0x02900:
			case 0x03000:
			case 0x04000:
			case 0x08000:
			case 0x10000:
			case 0x80000:
				break;

			default:
				seterror(image_error::INVALIDIMAGE, "Invalid ROM file size" );
				return image_init_result::FAIL;
		}

		m_cart->rom_alloc(len, tag());
		uint8_t *ROM = m_cart->get_rom_base();

		if (loaded_through_softlist())
		{
			memcpy(ROM, get_software_region("rom"), len);
			const char *pcb_name = get_feature("slot");

			if (pcb_name != nullptr)
				m_type = vcs_get_pcb_id(pcb_name);
			else
			{
				// identify type based on size
				switch (len)
				{
					case 0x800:
					case 0x1000:
						m_type = A26_2K_4K;
						break;
					case 0x2000:
						m_type = A26_F8;
						break;
					case 0x28ff:
					case 0x2900:
						m_type = A26_DPC;
						break;
					case 0x3000:
						m_type = A26_FA;
						break;
					case 0x4000:
						m_type = A26_F6;
						break;
					case 0x8000:
						m_type = A26_F4;
						break;
					case 0x10000:
						m_type = A26_32IN1;
						break;
					case 0x80000:
						m_type = A26_3F;
						break;
					default:
						m_type = A26_2K_4K;
						printf("Unrecognized cart type!\n");
						break;
				}
			}

			if (get_software_region("ram"))
				m_cart->ram_alloc(get_software_region_length("ram"));
		}
		else
		{
			fread(ROM, len);
			m_type = identify_cart_type(ROM, len);

			// check for Super Chip (128bytes of RAM)
			if (len == 0x2000 || len == 0x4000 || len == 0x8000)
				if (detect_super_chip(ROM, len))
				{
					m_cart->ram_alloc(0x80);
				}

			// add CBS RAM+ (256bytes of RAM)
			if (m_type == A26_FA)
				m_cart->ram_alloc(0x100);
			// add M Network RAM
			else if (m_type == A26_E7)
				m_cart->ram_alloc(0x800);
			// add Commavid RAM
			else if (m_type == A26_CV)
				m_cart->ram_alloc(0x400);
			// add Starpath Superchager RAM
			else if (m_type == A26_SS)
				m_cart->ram_alloc(0x1800);
			// add Boulder Dash RAM
			else if (m_type == A26_3E)
				m_cart->ram_alloc(0x8000);
		}

		// pass a pointer to the now allocated ROM for the DPC chip
		if (m_type == A26_DPC)
			m_cart->setup_addon_ptr((uint8_t *)m_cart->get_rom_base() + 0x2000);

		m_cart->install_memory_handlers(m_address_space.target());

		return image_init_result::PASS;
	}

	return image_init_result::PASS;
}


/*-------------------------------------------------
 call_unload
 -------------------------------------------------*/

void vcs_cart_slot_device::call_unload()
{
}



/*-------------------------------------------------
  detection helper routines
 -------------------------------------------------*/

bool vcs_cart_slot_device::detect_modeDC(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	// signature is also in 'video reflex'.. maybe figure out that controller port someday...
	static const unsigned char signature[3] = { 0x8d, 0xf0, 0xff };

	if (len == 0x10000)
	{
		for (int i = 0; i < len - sizeof signature; i++)
		{
			if (!memcmp(&cart[i], signature, sizeof signature))
			{
				numfound = 1;
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeF6(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signature[3] = { 0x8d, 0xf6, 0xff };

	if (len == 0x4000)
	{
		for (int i = 0; i < len - sizeof signature; i++)
		{
			if (!memcmp(&cart[i], signature, sizeof signature))
			{
				numfound = 1;
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_snowhite(const uint8_t *cart, uint32_t len)
{
	static const unsigned char snowwhite[] = { 0x10, 0xd0, 0xff, 0xff }; // Snow White Proto

	if (len == 0x2000 && !memcmp(&cart[0x1ffc], snowwhite, sizeof(snowwhite)))
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_mode3E(const uint8_t *cart, uint32_t len)
{
	// this one is a little hacky... looks for STY $3e, which is unique to
	// 'not boulderdash', but is the only example I have (cow)
	// Would have used STA $3e, but 'Alien' and 'Star Raiders' do that for unknown reasons
	int numfound = 0;
	static const unsigned char signature[3] = { 0x84, 0x3e, 0x9d };

	if (len == 0x0800 || len == 0x1000)
	{
		for (int i = 0; i < len - sizeof signature; i++)
		{
			if (!memcmp(&cart[i], signature, sizeof signature))
			{
				numfound = 1;
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeSS(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signature[5] = { 0xbd, 0xe5, 0xff, 0x95, 0x81 };

	if (len == 0x0800 || len == 0x1000)
	{
		for (int i = 0; i < len - sizeof signature; i++)
		{
			if (!memcmp(&cart[i], signature, sizeof signature))
			{
				numfound = 1;
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeFE(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signatures[][5] =  {
									{ 0x20, 0x00, 0xd0, 0xc6, 0xc5 },
									{ 0x20, 0xc3, 0xf8, 0xa5, 0x82 },
									{ 0xd0, 0xfb, 0x20, 0x73, 0xfe },
									{ 0x20, 0x00, 0xf0, 0x84, 0xd6 }
	};

	if (len == 0x2000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (int j = 0; j < (sizeof signatures/sizeof signatures[0]) && !numfound; j++)
			{
				if (!memcmp(&cart[i], &signatures[j], sizeof signatures[0]))
				{
					numfound = 1;
				}
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeE0(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signatures[][3] =  {
									{ 0x8d, 0xe0, 0x1f },
									{ 0x8d, 0xe0, 0x5f },
									{ 0x8d, 0xe9, 0xff },
									{ 0xad, 0xe9, 0xff },
									{ 0xad, 0xed, 0xff },
									{ 0xad, 0xf3, 0xbf }
	};

	if (len == 0x2000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (int j = 0; j < (sizeof signatures/sizeof signatures[0]) && !numfound; j++)
			{
				if (!memcmp(&cart[i], &signatures[j], sizeof signatures[0]))
				{
					numfound = 1;
				}
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeCV(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signatures[][3] = {
									{ 0x9d, 0xff, 0xf3 },
									{ 0x99, 0x00, 0xf4 }
	};

	if (len == 0x0800 || len == 0x1000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (int j = 0; j < (sizeof signatures/sizeof signatures[0]) && !numfound; j++)
			{
				if (!memcmp(&cart[i], &signatures[j], sizeof signatures[0]))
				{
					numfound = 1;
				}
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeFV(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signatures[][3] = { { 0x2c, 0xd0, 0xff } };

	if (len == 0x2000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (int j = 0; j < (sizeof signatures/sizeof signatures[0]) && !numfound; j++)
			{
				if (!memcmp(&cart[i], &signatures[j], sizeof signatures[0]))
				{
					numfound = 1;
				}
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeJVP(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signatures[][4] = {
									{ 0x2c, 0xc0, 0xef, 0x60 },
									{ 0x8d, 0xa0, 0x0f, 0xf0 }
	};

	if (len == 0x4000 || len == 0x2000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (int j = 0; j < (sizeof signatures/sizeof signatures[0]) && !numfound; j++)
			{
				if (!memcmp(&cart[i], &signatures[j], sizeof signatures[0]))
				{
					numfound = 1;
				}
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeE7(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signatures[][3] = {
									{ 0xad, 0xe5, 0xff },
									{ 0x8d, 0xe7, 0xff }
	};

	if (len == 0x2000 || len == 0x4000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (int j = 0; j < (sizeof signatures/sizeof signatures[0]) && !numfound; j++)
			{
				if (!memcmp(&cart[i], &signatures[j], sizeof signatures[0]))
				{
					numfound = 1;
				}
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_modeUA(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signature[3] = { 0x8d, 0x40, 0x02 };

	if (len == 0x2000)
	{
		for (int i = 0; i < len - sizeof signature; i++)
		{
			if (!memcmp(&cart[i], signature, sizeof signature))
			{
				numfound = 1;
			}
		}
	}
	if (numfound)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_8K_mode3F(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signature1[4] = { 0xa9, 0x01, 0x85, 0x3f };
	static const unsigned char signature2[4] = { 0xa9, 0x02, 0x85, 0x3f };
	// have to look for two signatures because 'not boulderdash' gives false positive otherwise

	if (len == 0x2000)
	{
		for (int i = 0; i < len - sizeof signature1; i++)
		{
			if (!memcmp(&cart[i], signature1, sizeof signature1))
			{
				numfound |= 0x01;
			}
			if (!memcmp(&cart[i], signature2, sizeof signature2))
			{
				numfound |= 0x02;
			}
		}
	}
	if (numfound == 0x03)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_32K_mode3F(const uint8_t *cart, uint32_t len)
{
	int numfound = 0;
	static const unsigned char signature[4] = { 0xa9, 0x0e, 0x85, 0x3f };

	if (len >= 0x8000)
	{
		for (int i = 0; i < len - sizeof signature; i++)
		{
			if (!memcmp(&cart[i], signature, sizeof signature))
			{
				numfound++;
			}
		}
	}
	if (numfound > 1)
		return 1;
	return 0;
}

bool vcs_cart_slot_device::detect_super_chip(const uint8_t *cart, uint32_t len)
{
	static const unsigned char signatures[][5] = {
									{ 0xa2, 0x7f, 0x9d, 0x00, 0xf0 }, // dig dug
									{ 0xae, 0xf6, 0xff, 0x4c, 0x00 } // off the wall
	};

	if (len == 0x4000)
	{
		for (int i = 0; i < len - sizeof signatures[0]; i++)
		{
			for (auto & signature : signatures)
			{
				if (!memcmp(&cart[i], &signature, sizeof signatures[0]))
				{
					return 1;
				}
			}
		}
	}
	for (int i = 0x1000; i < len; i += 0x1000)
	{
		if (memcmp(cart, cart + i, 0x100))
		{
			return 0;
		}
	}
	/* Check the reset vector does not point into the super chip RAM area */
	if ((((cart[0x0ffd] << 8) | cart[0x0ffc]) & 0x0fff) < 0x0100)
	{
		return 0;
	}
	return 1;
}


/*-------------------------------------------------
 identify_cart_type - code to detect cart type from
 fullpath
 -------------------------------------------------*/

// 4in1 & 8in1 are not currently detected from fullpath...
int vcs_cart_slot_device::identify_cart_type(const uint8_t *ROM, uint32_t len)
{
	int type = 0xff;

	// auto-detect bank mode
	if (detect_modeDC(ROM, len))
		type = A26_DC;
	else if (detect_mode3E(ROM, len))
		type = A26_3E;
	else if (detect_modeFE(ROM, len))
		type = A26_FE;
	else if (detect_modeSS(ROM, len))
		type = A26_SS;
	else if (detect_modeE0(ROM, len))
		type = A26_E0;
	else if (detect_modeCV(ROM, len))
		type = A26_CV;
	else if (detect_modeFV(ROM, len))
		type = A26_FV;
	else if (detect_modeJVP(ROM, len))
		type = A26_JVP;
	else if (detect_modeUA(ROM, len))
		type = A26_UA;
	else if (detect_8K_mode3F(ROM, len))
		type = A26_3F;
	else if (detect_32K_mode3F(ROM, len))
		type = A26_3F;
	else if (detect_modeE7(ROM, len))
		type = A26_E7;
	else if (detect_snowhite(ROM, len))
		type = A26_F8SW;

	// otherwise, choose based on size
	if (type == 0xff)
	{
		switch (len)
		{
			case 0x800:
			case 0x1000:
				type = A26_2K_4K;
				break;
			case 0x2000:
				type = A26_F8;
				break;
			case 0x28ff:
			case 0x2900:
				type = A26_DPC;
				break;
			case 0x3000:
				type = A26_FA;
				break;
			case 0x4000:
				type = A26_F6;
				break;
			case 0x8000:
				type = A26_F4;
				break;
			case 0x10000:
				type = A26_32IN1;
				break;
			case 0x80000:
				type = A26_3F;
				break;
			default:
				type = A26_2K_4K;
				printf("Unrecognized cart type!\n");
				break;
		}
	}

	return type;
}

/*-------------------------------------------------
 get default card software
 -------------------------------------------------*/

std::string vcs_cart_slot_device::get_default_card_software(get_default_card_software_hook &hook) const
{
	if (hook.image_file())
	{
		uint64_t len;
		hook.image_file()->length(len); // FIXME: check error return, guard against excessively large files
		std::vector<uint8_t> rom(len);

		size_t actual;
		hook.image_file()->read(&rom[0], len, actual); // FIXME: check error return or read returning short

		int const type = identify_cart_type(&rom[0], len);
		char const *const slot_string = vcs_get_slot(type);

		return std::string(slot_string);
	}
	else
		return software_get_default_slot("a26_2k_4k");
}
