// 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, const XTAL &clock) : device_t(mconfig, VCS_CART_SLOT, tag, owner, clock), device_cartrom_image_interface(mconfig, *this), device_single_card_slot_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 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"); }