/*********************************************************************************************************** Game Boy cart emulation (through slot devices) The driver exposes address ranges 0x0000-0x7fff to read_rom/write_bank 0xa000-0xbfff to read_ram/write_ram (typically RAM/NVRAM accesses, but megaduck uses the write for bankswitch) currently available slot devices: gb_rom: standard carts + TAMA5 mapper + pirate carts with protection & bankswitch gb_mbc: MBC1-MBC7 carts (more complex bankswitch + RAM + possibly RTC/Rumble/etc.) ***********************************************************************************************************/ #include "emu.h" #include "machine/gb_slot.h" //************************************************************************** // GLOBAL VARIABLES //************************************************************************** const device_type GB_CART_SLOT = &device_creator; const device_type MEGADUCK_CART_SLOT = &device_creator; //************************************************************************** // GB cartridges Interface //************************************************************************** //------------------------------------------------- // device_gb_cart_interface - constructor //------------------------------------------------- device_gb_cart_interface::device_gb_cart_interface(const machine_config &mconfig, device_t &device) : device_slot_card_interface(mconfig, device), m_rom(NULL), m_ram(NULL), m_rom_size(0), m_ram_size(0) { } //------------------------------------------------- // ~device_gb_cart_interface - destructor //------------------------------------------------- device_gb_cart_interface::~device_gb_cart_interface() { } //------------------------------------------------- // rom_alloc - alloc the space for the cart //------------------------------------------------- void device_gb_cart_interface::rom_alloc(running_machine &machine, UINT32 size) { if (m_rom == NULL) { m_rom = auto_alloc_array_clear(machine, UINT8, size); m_rom_size = size; } } //------------------------------------------------- // ram_alloc - alloc the space for the ram //------------------------------------------------- void device_gb_cart_interface::ram_alloc(running_machine &machine, UINT32 size) { if (m_ram == NULL) { m_ram = auto_alloc_array_clear(machine, UINT8, size); m_ram_size = size; state_save_register_item_pointer(machine, "GB_CART", this->device().tag(), 0, m_ram, m_ram_size); } } //------------------------------------------------- // rom_map_setup - setup map of rom banks in 16K // blocks, so to simplify ROM access //------------------------------------------------- void device_gb_cart_interface::rom_map_setup(UINT32 size) { int i; // setup the rom_bank_map array to faster ROM read for (i = 0; i < size / 0x4000; i++) rom_bank_map[i] = i; // fill up remaining blocks with mirrors while (i % 512) { int j = 0, repeat_banks; while ((i % (512 >> j)) && j < 9) j++; repeat_banks = i % (512 >> (j - 1)); for (int k = 0; k < repeat_banks; k++) rom_bank_map[i + k] = rom_bank_map[i + k - repeat_banks]; i += repeat_banks; } // check bank map! // for (i = 0; i < 256; i++) // { // printf("bank %3d = %3d\t", i, rom_bank_map[i]); // if ((i%8) == 7) // printf("\n"); // } } //------------------------------------------------- // ram_map_setup - setup map of ram banks in 16K // blocks, so to simplify ROM access //------------------------------------------------- void device_gb_cart_interface::ram_map_setup(UINT8 banks) { int mask = banks - 1; for (int i = 0; i < banks; i++) ram_bank_map[i] = i; // Set up rest of the (mirrored) RAM pages for (int i = banks; i < 256; i++) ram_bank_map[i] = i & mask; } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // base_gb_cart_slot_device - constructor //------------------------------------------------- base_gb_cart_slot_device::base_gb_cart_slot_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) : device_t(mconfig, type, name, tag, owner, clock, shortname, source), device_image_interface(mconfig, *this), device_slot_interface(mconfig, *this), m_sgb_hack(0), m_type(GB_MBC_UNKNOWN) { } gb_cart_slot_device::gb_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : base_gb_cart_slot_device(mconfig, GB_CART_SLOT, "Game Boy Cartridge Slot", tag, owner, clock, "gba_cart_slot", __FILE__) { } megaduck_cart_slot_device::megaduck_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : base_gb_cart_slot_device(mconfig, MEGADUCK_CART_SLOT, "Megaduck Cartridge Slot", tag, owner, clock, "megaduck_cart_slot", __FILE__) { } //------------------------------------------------- // base_gb_cart_slot_device - destructor //------------------------------------------------- base_gb_cart_slot_device::~base_gb_cart_slot_device() { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void base_gb_cart_slot_device::device_start() { m_cart = dynamic_cast(get_card_device()); } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void base_gb_cart_slot_device::device_config_complete() { // set brief and instance name update_names(); } //------------------------------------------------- // GB PCB //------------------------------------------------- struct gb_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 gb_slot slot_list[] = { { GB_MBC_MBC1, "rom_mbc1" }, { GB_MBC_MBC1_COL, "rom_mbc1col" }, { GB_MBC_MBC2, "rom_mbc2" }, { GB_MBC_MBC3, "rom_mbc3" }, { GB_MBC_MBC5, "rom_mbc5" }, { GB_MBC_MBC6, "rom_mbc6" }, { GB_MBC_MBC7, "rom_mbc7" }, { GB_MBC_TAMA5, "rom_tama5" }, { GB_MBC_MMM01, "rom_mmm01" }, { GB_MBC_MBC3, "rom_huc1" }, // for now treat this as alias for MBC3 { GB_MBC_MBC3, "rom_huc3" }, // for now treat this as alias for MBC3 { GB_MBC_WISDOM, "rom_wisdom" }, { GB_MBC_YONGYONG, "rom_yong" }, { GB_MBC_LASAMA, "rom_lasama" }, { GB_MBC_ATVRACIN, "rom_atvrac" }, { GB_MBC_SINTAX, "rom_sintax" }, { GB_MBC_CHONGWU, "rom_chong" }, { GB_MBC_DIGIMON, "rom_digimon" }, { GB_MBC_ROCKMAN8, "rom_rock8" }, { GB_MBC_SM3SP, "rom_sm3sp" }, { GB_MBC_UNK01, "rom_unk01" }, { GB_MBC_DKONG5, "rom_dkong5" }, { GB_MBC_CAMERA, "rom_camera" } }; static int gb_get_pcb_id(const char *slot) { for (int i = 0; i < ARRAY_LENGTH(slot_list); i++) { if (!mame_stricmp(slot_list[i].slot_option, slot)) return slot_list[i].pcb_id; } return 0; } static const char *gb_get_slot(int type) { for (int i = 0; i < ARRAY_LENGTH(slot_list); i++) { if (slot_list[i].pcb_id == type) return slot_list[i].slot_option; } return "rom"; } /*------------------------------------------------- call load -------------------------------------------------*/ bool base_gb_cart_slot_device::call_load() { if (m_cart) { UINT32 offset = 0; UINT32 len = (software_entry() == NULL) ? length() : get_software_region_length("rom"); UINT8 *ROM; int rambanks = 0; // From fullpath, check for presence of a header and skip it + check filesize is valid if (software_entry() == NULL) { if ((len % 0x4000) == 512) { logerror("Rom-header found, skipping\n"); offset = 512; len -= offset; fseek(offset, SEEK_SET); } /* Verify that the file contains 16kb blocks */ if ((len == 0) || ((len % 0x4000) != 0)) { seterror(IMAGE_ERROR_UNSPECIFIED, "Invalid rom file size\n"); return IMAGE_INIT_FAIL; } } m_cart->rom_alloc(machine(), len); ROM = m_cart->get_rom_base(); if (software_entry() == NULL) fread(ROM, len); else memcpy(ROM, get_software_region("rom"), len); // determine cart type offset = 0; if (get_mmm01_candidate(ROM, len)) offset = len - 0x8000; int type; if (software_entry() != NULL) type = gb_get_pcb_id(get_feature("slot") ? get_feature("slot") : "rom"); else type = get_cart_type(ROM + offset, len - offset); // setup RAM/NVRAM/RTC/RUMBLE if (software_entry() != NULL) { // from softlist we only rely on xml if (get_software_region("ram")) rambanks = get_software_region_length("ram") / 0x2000; if (get_software_region("nvram")) { m_cart->set_has_battery(TRUE); rambanks = get_software_region_length("nvram") / 0x2000; } if (get_feature("rumble")) { if (!mame_stricmp(get_feature("rumble"), "yes")) m_cart->set_has_rumble(TRUE); } if (get_feature("rtc")) { if (!mame_stricmp(get_feature("rtc"), "yes")) m_cart->set_has_timer(TRUE); } } else { // from fullpath we rely on header switch (ROM[0x0147 + offset]) { case 0x03: case 0x06: case 0x09: case 0x0d: case 0x13: case 0x17: case 0x1b: case 0x22: m_cart->set_has_battery(TRUE); break; case 0x0f: case 0x10: m_cart->set_has_battery(TRUE); m_cart->set_has_timer(TRUE); break; case 0x1c: case 0x1d: m_cart->set_has_rumble(TRUE); break; case 0x1e: m_cart->set_has_battery(TRUE); m_cart->set_has_rumble(TRUE); break; } switch (ROM[0x0149 + offset] & 0x07) { case 0x00: case 0x06: case 0x07: rambanks = 0; break; case 0x01: case 0x02: rambanks = 1; break; case 0x03: rambanks = 4; break; case 0x04: rambanks = 16; break; case 0x05: default: rambanks = 8; break; } if (type == GB_MBC_MBC2 || type == GB_MBC_MBC7) rambanks = 1; } // setup rom bank map based on real length, not header value m_cart->rom_map_setup(len); if (rambanks) setup_ram(rambanks); if (m_cart->get_ram_size() && m_cart->get_has_battery()) battery_load(m_cart->get_ram_base(), m_cart->get_ram_size(), 0xff); //printf("Type: %s\n", gb_get_slot(type)); internal_header_logging(ROM + offset, len); // Hack to support Donkey Kong Land 2 + 3 in SGB // For some reason, these store the tile data differently. Hacks will go once it's been figured out if (strncmp((const char*)(ROM + 0x134), "DONKEYKONGLAND 2", 16) == 0 || strncmp((const char*)(ROM + 0x134), "DONKEYKONGLAND 3", 16) == 0) m_sgb_hack = 1; return IMAGE_INIT_PASS; } return IMAGE_INIT_PASS; } bool megaduck_cart_slot_device::call_load() { if (m_cart) { UINT32 len = (software_entry() == NULL) ? length() : get_software_region_length("rom"); UINT8 *ROM; m_cart->rom_alloc(machine(), len); ROM = m_cart->get_rom_base(); if (software_entry() == NULL) fread(ROM, len); else memcpy(ROM, get_software_region("rom"), len); // setup rom bank map based on real length, not header value m_cart->rom_map_setup(len); return IMAGE_INIT_PASS; } return IMAGE_INIT_PASS; } /*------------------------------------------------- call_unload -------------------------------------------------*/ void base_gb_cart_slot_device::call_unload() { if (m_cart && m_cart->get_ram_size() && m_cart->get_has_battery()) battery_save(m_cart->get_ram_base(), m_cart->get_ram_size()); } void base_gb_cart_slot_device::setup_ram(UINT8 banks) { m_cart->ram_alloc(machine(), banks * 0x2000); memset(m_cart->get_ram_base(), 0xff, m_cart->get_ram_size()); m_cart->ram_map_setup(banks); } /*------------------------------------------------- call softlist load -------------------------------------------------*/ bool base_gb_cart_slot_device::call_softlist_load(char *swlist, char *swname, rom_entry *start_entry) { load_software_part_region(this, swlist, swname, start_entry ); return TRUE; } // This fails to catch Mani 4-in-1 carts... even when they match this, then they have MBC1/3 in the internal header instead of MMM01... bool base_gb_cart_slot_device::get_mmm01_candidate(UINT8 *ROM, UINT32 len) { if (len < 0x8147) return FALSE; static const UINT8 nintendo_logo[0x18] = { 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E }; int bytes_matched = 0; for (int i = 0; i < 0x18; i++) { if (ROM[(len - 0x8000) + 0x104 + i] == nintendo_logo[i]) bytes_matched++; } if (bytes_matched == 0x18 && ROM[(len - 0x8000) + 0x147] >= 0x0b && ROM[(len - 0x8000) + 0x147] <= 0x0d) return TRUE; else return FALSE; } int base_gb_cart_slot_device::get_cart_type(UINT8 *ROM, UINT32 len) { int type = GB_MBC_NONE; if (len < 0x014c) fatalerror("Checking header of a corrupted image!\n"); switch(ROM[0x0147]) { case 0x00: type = GB_MBC_NONE; break; case 0x01: type = GB_MBC_MBC1; break; case 0x02: type = GB_MBC_MBC1; break; case 0x03: type = GB_MBC_MBC1; break; case 0x05: type = GB_MBC_MBC2; break; case 0x06: type = GB_MBC_MBC2; break; case 0x08: type = GB_MBC_NONE; break; case 0x09: type = GB_MBC_NONE; break; case 0x0b: type = GB_MBC_MMM01; break; case 0x0c: type = GB_MBC_MMM01; break; case 0x0d: type = GB_MBC_MMM01; break; case 0x0f: type = GB_MBC_MBC3; break; case 0x10: type = GB_MBC_MBC3; break; case 0x11: type = GB_MBC_MBC3; break; case 0x12: type = GB_MBC_MBC3; break; case 0x13: type = GB_MBC_MBC3; break; case 0x15: type = GB_MBC_MBC4; break; case 0x16: type = GB_MBC_MBC4; break; case 0x17: type = GB_MBC_MBC4; break; case 0x19: type = GB_MBC_MBC5; break; case 0x1a: type = GB_MBC_MBC5; break; case 0x1b: type = GB_MBC_MBC5; break; case 0x1c: type = GB_MBC_MBC5; break; case 0x1d: type = GB_MBC_MBC5; break; case 0x1e: type = GB_MBC_MBC5; break; case 0x20: type = GB_MBC_MBC6; break; case 0x22: type = GB_MBC_MBC7; break; case 0xbe: type = GB_MBC_NONE; break; /* used in Flash2Advance GB Bridge boot program */ case 0xea: type = GB_MBC_YONGYONG; break; /* Found in Sonic 3D Blast 5 pirate */ case 0xfc: type = GB_MBC_CAMERA; break; case 0xfd: type = GB_MBC_TAMA5; break; case 0xfe: type = GB_MBC_HUC3; break; case 0xff: type = GB_MBC_HUC1; break; } // Check for special mappers if (type == GB_MBC_NONE) { int count = 0; for (int i = 0x0134; i <= 0x014c; i++) { count += ROM[i]; } if (count == 0) { type = GB_MBC_WISDOM; } } // Check for some unlicensed games if (type == GB_MBC_MBC5) { int count = 0; for (int i = 0x0184; i < 0x0184 + 0x30; i++) { count += ROM[i]; } if (count == 4876) { // printf("Niutoude!\n"); // type = GB_MBC_NIUTOUDE; } if (count == 4138 || count == 4125) { // Zhi Huan Wang uses 4138 // most sintax use 4125 printf("Sintax %d!\n", count); type = GB_MBC_SINTAX; } } /* Check if we're dealing with the multigame variant of the MBC1 mapper */ if (type == GB_MBC_MBC1) { if (ROM[0x13f] == 0x42 && ROM[0x140] == 0x32 && ROM[0x141] == 0x43 && ROM[0x142] == 0x4B) type = GB_MBC_MBC1_COL; } return type; } /*------------------------------------------------- get default card software -------------------------------------------------*/ const char * base_gb_cart_slot_device::get_default_card_software(const machine_config &config, emu_options &options) { if (open_image_file(options)) { const char *slot_string = "rom"; UINT32 len = core_fsize(m_file), offset = 0; UINT8 *ROM = global_alloc_array(UINT8, len); int type; core_fread(m_file, ROM, len); if ((len % 0x4000) == 512) offset = 512; if (get_mmm01_candidate(ROM + offset, len - offset)) offset += (len - 0x8000); type = get_cart_type(ROM + offset, len - offset); slot_string = gb_get_slot(type); //printf("type: %s\n", slot_string); global_free(ROM); clear(); return slot_string; } return software_get_default_slot(config, options, this, "rom"); } const char * megaduck_cart_slot_device::get_default_card_software(const machine_config &config, emu_options &options) { if (open_image_file(options)) return "rom"; return software_get_default_slot(config, options, this, "rom"); } /*------------------------------------------------- read -------------------------------------------------*/ READ8_MEMBER(base_gb_cart_slot_device::read_rom) { if (m_cart) return m_cart->read_rom(space, offset); else return 0xff; } READ8_MEMBER(base_gb_cart_slot_device::read_ram) { if (m_cart) return m_cart->read_ram(space, offset); else return 0xff; } /*------------------------------------------------- write -------------------------------------------------*/ WRITE8_MEMBER(base_gb_cart_slot_device::write_bank) { if (m_cart) m_cart->write_bank(space, offset, data); } WRITE8_MEMBER(base_gb_cart_slot_device::write_ram) { if (m_cart) m_cart->write_ram(space, offset, data); } /*------------------------------------------------- Internal header logging -------------------------------------------------*/ void base_gb_cart_slot_device::internal_header_logging(UINT8 *ROM, UINT32 len) { static const char *const cart_types[] = { "ROM ONLY", "ROM+MBC1", "ROM+MBC1+RAM", "ROM+MBC1+RAM+BATTERY", "UNKNOWN", "ROM+MBC2", "ROM+MBC2+BATTERY", "UNKNOWN", "ROM+RAM", "ROM+RAM+BATTERY", "UNKNOWN", "ROM+MMM01", "ROM+MMM01+SRAM", "ROM+MMM01+SRAM+BATTERY", "UNKNOWN", "ROM+MBC3+TIMER+BATTERY", "ROM+MBC3+TIMER+RAM+BATTERY", "ROM+MBC3", "ROM+MBC3+RAM", "ROM+MBC3+RAM+BATTERY", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "ROM+MBC5", "ROM+MBC5+RAM", "ROM+MBC5+RAM+BATTERY", "ROM+MBC5+RUMBLE", "ROM+MBC5+RUMBLE+SRAM", "ROM+MBC5+RUMBLE+SRAM+BATTERY", "Pocket Camera", "Bandai TAMA5", /* Need heaps of unknowns here */ "Hudson HuC-3", "Hudson HuC-1" }; // some company codes static const struct { UINT16 code; const char *name; } companies[] = { {0x3301, "Nintendo"}, {0x7901, "Accolade"}, {0xA400, "Konami"}, {0x6701, "Ocean"}, {0x5601, "LJN"}, {0x9900, "ARC?"}, {0x0101, "Nintendo"}, {0x0801, "Capcom"}, {0x0100, "Nintendo"}, {0xBB01, "SunSoft"}, {0xA401, "Konami"}, {0xAF01, "Namcot?"}, {0x4901, "Irem"}, {0x9C01, "Imagineer"}, {0xA600, "Kawada?"}, {0xB101, "Nexoft"}, {0x5101, "Acclaim"}, {0x6001, "Titus"}, {0xB601, "HAL"}, {0x3300, "Nintendo"}, {0x0B00, "Coconuts?"}, {0x5401, "Gametek"}, {0x7F01, "Kemco?"}, {0xC001, "Taito"}, {0xEB01, "Atlus"}, {0xE800, "Asmik?"}, {0xDA00, "Tomy?"}, {0xB100, "ASCII?"}, {0xEB00, "Atlus"}, {0xC000, "Taito"}, {0x9C00, "Imagineer"}, {0xC201, "Kemco?"}, {0xD101, "Sofel?"}, {0x6101, "Virgin"}, {0xBB00, "SunSoft"}, {0xCE01, "FCI?"}, {0xB400, "Enix?"}, {0xBD01, "Imagesoft"}, {0x0A01, "Jaleco?"}, {0xDF00, "Altron?"}, {0xA700, "Takara?"}, {0xEE00, "IGS?"}, {0x8300, "Lozc?"}, {0x5001, "Absolute?"}, {0xDD00, "NCS?"}, {0xE500, "Epoch?"}, {0xCB00, "VAP?"}, {0x8C00, "Vic Tokai"}, {0xC200, "Kemco?"}, {0xBF00, "Sammy?"}, {0x1800, "Hudson Soft"}, {0xCA01, "Palcom/Ultra"}, {0xCA00, "Palcom/Ultra"}, {0xC500, "Data East?"}, {0xA900, "Technos Japan?"}, {0xD900, "Banpresto?"}, {0x7201, "Broderbund?"}, {0x7A01, "Triffix Entertainment?"}, {0xE100, "Towachiki?"}, {0x9300, "Tsuburava?"}, {0xC600, "Tonkin House?"}, {0xCE00, "Pony Canyon"}, {0x7001, "Infogrames?"}, {0x8B01, "Bullet-Proof Software?"}, {0x5501, "Park Place?"}, {0xEA00, "King Records?"}, {0x5D01, "Tradewest?"}, {0x6F01, "ElectroBrain?"}, {0xAA01, "Broderbund?"}, {0xC301, "SquareSoft"}, {0x5201, "Activision?"}, {0x5A01, "Bitmap Brothers/Mindscape"}, {0x5301, "American Sammy"}, {0x4701, "Spectrum Holobyte"}, {0x1801, "Hudson Soft"}, {0x0000, NULL} }; static const int ramsize[8] = { 0, 2, 8, 32, 128, 64, 0, 0 }; char soft[17]; UINT32 tmp = 0; int csum = 0, i = 0; int rom_banks; switch (ROM[0x0148]) { case 0x52: rom_banks = 72; break; case 0x53: rom_banks = 80; break; case 0x54: rom_banks = 96; break; case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: rom_banks = 2 << ROM[0x0148]; break; default: rom_banks = 256; break; } strncpy(soft, (char *)&ROM[0x0134], 16); soft[16] = '\0'; logerror("Cart Information\n"); logerror("\tName: %s\n", soft); logerror("\tType: %s [0x%2X]\n", (ROM[0x0147] <= 32) ? cart_types[ROM[0x0147]] : "", ROM[0x0147] ); logerror("\tGame Boy: %s\n", (ROM[0x0143] == 0xc0) ? "No" : "Yes" ); logerror("\tSuper GB: %s [0x%2X]\n", (ROM[0x0146] == 0x03) ? "Yes" : "No", ROM[0x0146] ); logerror("\tColor GB: %s [0x%2X]\n", (ROM[0x0143] == 0x80 || ROM[0x0143] == 0xc0) ? "Yes" : "No", ROM[0x0143] ); logerror("\tROM Size: %d 16kB Banks [0x%2X]\n", rom_banks, ROM[0x0148]); logerror("\tRAM Size: %d kB [0x%2X]\n", ramsize[ROM[0x0149] & 0x07], ROM[0x0149]); logerror("\tLicense code: 0x%2X%2X\n", ROM[0x0145], ROM[0x0144] ); tmp = (ROM[0x014b] << 8) + ROM[0x014a]; for (i = 0; i < ARRAY_LENGTH(companies); i++) if (tmp == companies[i].code) break; logerror("\tManufacturer ID: 0x%2X", tmp); logerror(" [%s]\n", (i < ARRAY_LENGTH(companies)) ? companies[i].name : "?"); logerror("\tVersion Number: 0x%2X\n", ROM[0x014c]); logerror("\tComplement Check: 0x%2X\n", ROM[0x014d]); logerror("\tChecksum: 0x%2X\n", ((ROM[0x014e] << 8) + ROM[0x014f])); tmp = (ROM[0x0103] << 8) + ROM[0x0102]; logerror("\tStart Address: 0x%2X\n", tmp); // Additional checks if (rom_banks == 256) logerror("\nWarning loading cartridge: Unknown ROM size in header [0x%x].\n", ROM[0x0148]); if ((len / 0x4000) != rom_banks) logerror("\nWarning loading cartridge: Filesize (0x%x) and reported ROM banks (0x%x) don't match.\n", len, rom_banks * 0x4000); /* Calculate and check checksum */ tmp = (ROM[0x014e] << 8) + ROM[0x014f]; for (int i = 0; i < len; i++) csum += ROM[i]; csum -= (ROM[0x014e] + ROM[0x014f]); csum &= 0xffff; if (csum != tmp) logerror("\nWarning loading cartridge: Checksum is wrong (Actual %X vs Internal %X)\n", csum, tmp); }