diff options
Diffstat (limited to 'src/devices/bus/gameboy/mbc.cpp')
-rw-r--r-- | src/devices/bus/gameboy/mbc.cpp | 2488 |
1 files changed, 1307 insertions, 1181 deletions
diff --git a/src/devices/bus/gameboy/mbc.cpp b/src/devices/bus/gameboy/mbc.cpp index bd57a4da23b..2db965839ad 100644 --- a/src/devices/bus/gameboy/mbc.cpp +++ b/src/devices/bus/gameboy/mbc.cpp @@ -1,11 +1,55 @@ // license:BSD-3-Clause -// copyright-holders:Fabio Priuli, Wilbert Pol +// copyright-holders:Vas Crabb, 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! + MBC1 Mapper + =========== + + The MBC1 mapper has two modes: 2MB ROM/8KB RAM or 512KB ROM/32KB RAM. + Initially, the mapper operates in 2MB ROM/8KB RAM mode. + + 0000-1FFF - Writing to this area enables (value 0x0A) or disables (not 0x0A) the + SRAM. + 2000-3FFF - Writing a value 0bXXXBBBBB into the 2000-3FFF memory area selects the + lower 5 bits of the ROM bank to select for the 4000-7FFF memory area. + If a value of 0bXXX00000 is written then this will automatically be + changed to 0bXXX00001 by the mbc chip. Initial value 00. + 4000-5FFF - Writing a value 0bXXXXXXBB into the 4000-5FFF memory area either selects + the RAM bank to use or bits 6 and 7 for the ROM bank to use for the 4000-7FFF + memory area. This behaviour depends on the memory mode chosen. + These address lines are fixed in mode 1 and switch depending on A14 in mode 0. + In mode 0 these will drive 0 when RB 00 is accessed (A14 low) or the value set + in 4000-5FFF when RB <> 00 is accessed (A14 high). + Switching between modes does not clear this register. Initial value 00. + 6000-7FFF - Writing a value 0bXXXXXXXB into the 6000-7FFF memory area switches the mode. + B=0 - 2MB ROM/8KB RAM mode + B=1 - 512KB ROM/32KB RAM mode + + Regular ROM aliasing rules apply. + + + MBC5 Mapper + =========== + + 0000-1FFF - Writing to this area enables (0x0A) or disables (not 0x0A) the SRAM area. + 2000-2FFF - Writing to this area updates bits 7-0 of the ROM bank number to + appear at 4000-7FFF. + 3000-3FFF - Writing to this area updates bit 8 of the ROM bank number to appear + at 4000-7FFF. + 4000-5FFF - Writing to this area select the RAM bank number to use. If the + cartridge includes a Rumble Pack then bit 3 is used to control + rumble motor (0 - disable motor, 1 - enable motor). + + + TODO: + * MBC5 logo spoofing class implements several strategies. It's likely not all carts using it use all + the strategies. Strategies implemented by each cartridge should be identified. + * HK0701 and HK0819 seem to differ in that HK0819 fully decodes ROM addresses while HK0701 mirrors - we + should probably emulated the difference at some point. + * BC-R1616T3P is a variant of DSHGGB-81 with the same ROM scrambling but without logo spoofing. ***********************************************************************************************************/ @@ -13,1428 +57,1510 @@ #include "emu.h" #include "mbc.h" +#include "cartbase.ipp" +#include "cartheader.h" +#include "gbxfile.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") -DEFINE_DEVICE_TYPE(GB_ROM_CAMERA, gb_rom_camera_device, "gb_rom_camera", "GB Camera") - - -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) -{ -} +#include "bus/generic/slot.h" -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) -{ -} +#include "corestr.h" -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) -{ -} +#include <algorithm> +#include <iterator> +#include <limits> +#include <locale> +#include <sstream> +#include <type_traits> -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) -{ -} +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" -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) -{ -} +namespace bus::gameboy { -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) -{ -} +namespace { -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) -{ -} +//************************************************************************** +// MBC1/MBC3/MBC5 banked RAM support helper class +//************************************************************************** -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) +class rom_mbc_device_base : public mbc_ram_device_base<mbc_dual_device_base> { -} - -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) -{ -} +protected: + rom_mbc_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + mbc_ram_device_base<mbc_dual_device_base>(mconfig, type, tag, owner, clock), + m_view_ram(*this, "ram"), + m_bank_lines{ 0U, 0U } + { + } -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) -{ -} + std::error_condition install_memory(std::string &message, unsigned highbits, unsigned lowbits) ATTR_COLD + { + // see if the cartridge shouldn't use all the low bank bits + char const *const lowbitsfeature(get_feature("banklowbits")); + if (lowbitsfeature) + { + std::istringstream s; + s.imbue(std::locale::classic()); + s.str(lowbitsfeature); + int b(-1); + if (!(s >> b) || (0 > b) || (lowbits < b)) + { + message = util::string_format( + "Invalid 'banklowbits' value (must be a number from 0 to %u)\n", + lowbits); + return image_error::BADSOFTWARE; + } + lowbits = b; + } -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) -{ -} + // check for valid ROM/RAM regions + set_bank_bits_rom(highbits, lowbits); + set_bank_bits_ram(highbits); + if (!check_rom(message) || !check_ram(message)) + return image_error::BADSOFTWARE; + + // everything checked out - install memory and return success + m_bank_lines[0] = util::make_bitmask<u16>(lowbits); + m_bank_lines[1] = util::make_bitmask<u16>(highbits); + cart_space()->install_view(0xa000, 0xbfff, m_view_ram); + install_rom(); + install_ram(m_view_ram[0]); + return std::error_condition(); + } -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) -{ -} + void set_bank_rom_fine(u16 entry) + { + mbc_ram_device_base<mbc_dual_device_base>::set_bank_rom_fine(entry & m_bank_lines[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) -{ -} + void set_ram_enable(bool enable) + { + LOG( + "%s: Cartridge RAM %s\n", + machine().describe_context(), + enable ? "enabled" : "disabled"); + if (enable) + m_view_ram.select(0); + else + m_view_ram.disable(); + } -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) -{ -} + void bank_switch_coarse(u8 data) + { + set_bank_rom_coarse(data & m_bank_lines[1]); + set_bank_ram(data & m_bank_lines[1]); + } -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) -{ -} +private: + memory_view m_view_ram; -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) -{ -} + u16 m_bank_lines[2]; +}; -gb_rom_camera_device::gb_rom_camera_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_CAMERA, tag, owner, clock) -{ -} -//------------------------------------------------- -// shared_start -//------------------------------------------------- +//************************************************************************** +// MBC5 and clones (max 128 MiB ROM, max 128 KiB SRAM) +//************************************************************************** -void gb_rom_mbc_device::shared_start() +class mbc5_device_base : public rom_mbc_device_base { - save_item(NAME(m_latch_bank)); - save_item(NAME(m_latch_bank2)); - save_item(NAME(m_ram_bank)); - save_item(NAME(m_ram_enable)); -} +public: + virtual std::error_condition load(std::string &message) override ATTR_COLD + { + // set up ROM and RAM + std::error_condition err = install_memory(message); + if (err) + return err; + + // install handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + emu::rw_delegate(*this, FUNC(mbc5_device_base::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x2fff, + emu::rw_delegate(*this, FUNC(mbc5_device_base::bank_switch_fine_low))); + cart_space()->install_write_handler( + 0x3000, 0x3fff, + emu::rw_delegate(*this, FUNC(mbc5_device_base::bank_switch_fine_high))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + emu::rw_delegate(*this, FUNC(mbc5_device_base::bank_switch_coarse))); + + // all good + return std::error_condition(); + } -//------------------------------------------------- -// shared_reset -//------------------------------------------------- +protected: + mbc5_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + rom_mbc_device_base(mconfig, type, tag, owner, clock) + { + } -void gb_rom_mbc_device::shared_reset() -{ - m_latch_bank = 0; - m_latch_bank2 = 1; - m_ram_bank = 0; - m_ram_enable = 0; -} + virtual void device_reset() override ATTR_COLD + { + rom_mbc_device_base::device_reset(); -//------------------------------------------------- -// mapper specific start/reset -//------------------------------------------------- + set_bank_rom_coarse(0); + set_bank_rom_fine(0); + set_ram_enable(false); + set_bank_ram(0); + } -void gb_rom_mbc3_device::device_start() -{ - shared_start(); - save_item(NAME(m_rtc_regs)); - save_item(NAME(m_rtc_ready)); -} + std::error_condition install_memory(std::string &message) ATTR_COLD + { + return rom_mbc_device_base::install_memory(message, 4, 9); + } -void gb_rom_mbc3_device::device_reset() -{ - shared_reset(); - memset(m_rtc_regs, 0, sizeof(m_rtc_regs)); - m_rtc_ready = 0; -} + void enable_ram(u8 data) + { + set_ram_enable(0x0a == data); + } -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 bank_switch_fine_low(u8 data) + { + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(data)); + } -void gb_rom_m161_device::device_reset() -{ - shared_reset(); - m_base_bank = 0; - m_load_disable = 0; -} + void bank_switch_fine_high(u8 data) + { + set_bank_rom_fine((bank_rom_fine() & 0x00ff) | (u16(data & 0x01) << 8)); + } +}; -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; -} -void gb_rom_camera_device::device_start() -{ - shared_start(); - save_item(NAME(m_camera_regs)); -} +//************************************************************************** +// MBC5-like pirate cartridges with logo spoofing +//************************************************************************** -void gb_rom_camera_device::device_reset() +class mbc5_logo_spoof_device_base : public mbc5_device_base { - shared_reset(); - memset(m_camera_regs, 0, sizeof(m_camera_regs)); -} - - -/*------------------------------------------------- - mapper specific handlers - -------------------------------------------------*/ +public: + virtual std::error_condition load(std::string &message) override ATTR_COLD + { + // install regular MBC5 handlers + std::error_condition const result(mbc5_device_base::load(message)); + if (result) + return result; + + // intercept ROM reads for logo spoofing + cart_space()->install_read_handler( + 0x0000, 0x7fff, + emu::rw_delegate(*this, FUNC(mbc5_logo_spoof_device_base::read_rom))); + + // all good + return std::error_condition(); + } -uint8_t gb_rom_mbc_device::read_rom(offs_t offset) -{ - return m_rom[rom_bank_map[m_latch_bank] + offset]; -} +protected: + mbc5_logo_spoof_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + mbc5_device_base(mconfig, type, tag, owner, clock), + m_ram_tap(), + m_notif_cart_space(), + m_counter(0U), + m_spoof_logo(0U), + m_installing_tap(false) + { + } -uint8_t gb_rom_mbc_device::read_ram(offs_t offset) -{ - if (!m_ram.empty()) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + offset]; - else - return 0xff; -} + virtual void device_start() override ATTR_COLD + { + mbc5_device_base::device_start(); -void gb_rom_mbc_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + offset] = data; -} + save_item(NAME(m_counter)); + save_item(NAME(m_spoof_logo)); + } + virtual void device_reset() override ATTR_COLD + { + mbc5_device_base::device_reset(); -// MBC1 + m_counter = 0U; + m_spoof_logo = 0U; -uint8_t gb_rom_mbc1_device::read_rom(offs_t offset) -{ - 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)]; + install_ram_tap(); + add_ram_notifier(); } -} -void gb_rom_mbc1_device::write_bank(offs_t offset, uint8_t data) -{ - // the mapper only uses inputs A15..A13 - switch (offset & 0xe000) + virtual void device_post_load() override ATTR_COLD { - 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; - } -} + mbc5_device_base::device_post_load(); -uint8_t gb_rom_mbc1_device::read_ram(offs_t offset) -{ - 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]; + install_ram_tap(); + if (!m_spoof_logo) + add_ram_notifier(); } - else - return 0xff; -} -void gb_rom_mbc1_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty() && m_ram_enable) + u8 read_rom(offs_t offset) { - int bank = (m_mode == MODE_4M_256k) ? m_ram_bank : 0; - m_ram[ram_bank_map[bank] * 0x2000 + offset] = data; + offset = rom_access(offset); + return (BIT(offset, 14) ? bank_rom_high_base() : bank_rom_low_base())[offset & 0x3fff]; } -} + offs_t rom_access(offs_t offset) + { + if (!machine().side_effects_disabled() && (3U > m_spoof_logo)) + { + // These cartridges work by counting low-to-high transitions on A15. + // CPU keeps A15 high while reading internal bootstrap ROM. + // Doing this on ROM reads is a good enough approximation for it to work. + if (0x30 != m_counter) + { + ++m_counter; + } + else + { + switch (m_spoof_logo) + { + case 0U: + m_counter = 1U; + [[fallthrough]]; + case 1U: + ++m_spoof_logo; + m_notif_cart_space.reset(); + m_ram_tap.remove(); + LOG( + "%s: Spoof logo step %u\n", + machine().describe_context(), + m_spoof_logo); + } + } -// MBC2 - -uint8_t gb_rom_mbc2_device::read_rom(offs_t offset) -{ - 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)]; -} + // accessing entry point disables logo spoofing logic + if (0x0100 == offset) + m_spoof_logo = 3U; + } -void gb_rom_mbc2_device::write_bank(offs_t offset, uint8_t data) -{ - // the mapper only has data lines D3..D0 - data &= 0x0f; + // A7 is held high to show pirate logo + if (m_spoof_logo && ((2U != m_spoof_logo) || (cartheader::OFFSET_LOGO > offset) || (cartheader::OFFSET_TITLE <= offset))) + return offset; + else + return offset | 0x0080; + } - // the mapper only uses inputs A15..A14, A8 for register accesses - switch (offset & 0xc100) +private: + void install_ram_tap() { - 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; + if (!m_installing_tap) + { + m_installing_tap = true; + m_ram_tap.remove(); + if (!m_spoof_logo) + { + m_ram_tap = cart_space()->install_write_tap( + 0xc000, 0xdfff, + "ram_tap_w", + [this] (offs_t offset, u8 &data, u8 mem_mask) + { + assert(!m_spoof_logo); + + if (!machine().side_effects_disabled()) + { + m_counter = 0U; + ++m_spoof_logo; + LOG( + "%s: Spoof logo step %u\n", + machine().describe_context(), + m_spoof_logo); + m_notif_cart_space.reset(); + m_ram_tap.remove(); + } + }, + &m_ram_tap); + } + else + { + m_notif_cart_space.reset(); + } + m_installing_tap = false; + } } -} -uint8_t gb_rom_mbc2_device::read_ram(offs_t offset) -{ - if (!m_ram.empty() && m_ram_enable) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x01ff)] | 0xf0; - else - return 0xff; -} - -void gb_rom_mbc2_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty() && m_ram_enable) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x01ff)] = data & 0x0f; -} + void add_ram_notifier() + { + m_notif_cart_space = cart_space()->add_change_notifier( + [this] (read_or_write mode) + { + if (u32(mode) & u32(read_or_write::WRITE)) + install_ram_tap(); + }); + } + memory_passthrough_handler m_ram_tap; + util::notifier_subscription m_notif_cart_space; -// MBC3 + u8 m_counter; + u8 m_spoof_logo; + bool m_installing_tap; +}; -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); -} -uint8_t gb_rom_mbc3_device::read_rom(offs_t offset) -{ - 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-like pirate cartridges with scrambled bank numbers and data +//************************************************************************** -void gb_rom_mbc3_device::write_bank(offs_t offset, uint8_t data) +class mbc5_scrambled_device_base : public mbc5_logo_spoof_device_base { - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset < 0x4000) + virtual std::error_condition load(std::string &message) override ATTR_COLD { - // 7bits - data &= 0x7f; - /* Selecting bank 0 == selecting bank 1 */ - if (data == 0) - data = 1; - - m_latch_bank2 = data; + // install regular MBC5 handlers + std::error_condition const result(mbc5_logo_spoof_device_base::load(message)); + if (result) + return result; + + // bank numbers and ROM contents are scrambled for protection + cart_space()->install_read_handler( + 0x0000, 0x3fff, + emu::rw_delegate(*this, FUNC(mbc5_scrambled_device_base::read_rom_low))); + cart_space()->install_read_handler( + 0x4000, 0x7fff, + emu::rw_delegate(*this, FUNC(mbc5_scrambled_device_base::read_rom_high))); + cart_space()->install_write_handler( + 0x2000, 0x2000, 0x0000, 0x0f00, 0x0000, + emu::rw_delegate(*this, FUNC(mbc5_scrambled_device_base::bank_switch_fine_low_scrambled))); + cart_space()->install_write_handler( + 0x2001, 0x2001, 0x0000, 0x0f00, 0x0000, + emu::rw_delegate(*this, FUNC(mbc5_scrambled_device_base::set_data_scramble))); + cart_space()->install_write_handler( + 0x2080, 0x2080, 0x0000, 0x0f00, 0x0000, + emu::rw_delegate(*this, FUNC(mbc5_scrambled_device_base::set_bank_scramble))); + + // all good + return std::error_condition(); } - else if (offset < 0x6000) + +protected: + using unscramble_function = u8 (*)(u8); + + mbc5_scrambled_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + mbc5_logo_spoof_device_base(mconfig, type, tag, owner, clock), + m_unscramble_data(nullptr), + m_unscramble_bank(nullptr), + m_scramble_mode{ 0U, 0U } { - m_ram_bank = data; } - else if (has_timer) + + virtual void device_start() override ATTR_COLD { - if (m_rtc_ready == 1 && data == 0) - m_rtc_ready = 0; - if (m_rtc_ready == 0 && data == 1) - { - m_rtc_ready = 1; - update_rtc(); - } + mbc5_logo_spoof_device_base::device_start(); + + save_item(NAME(m_scramble_mode)); } -} -uint8_t gb_rom_mbc3_device::read_ram(offs_t offset) -{ - if (m_ram_bank < 4 && m_ram_enable) + virtual void device_reset() override ATTR_COLD { - // RAM - if (!m_ram.empty()) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; + mbc5_logo_spoof_device_base::device_reset(); + + m_unscramble_data = unscramble_data(0U); + m_unscramble_bank = unscramble_bank(0U); + m_scramble_mode[0] = 0U; + m_scramble_mode[1] = 0U; } - if (m_ram_bank >= 0x8 && m_ram_bank <= 0xc) + + virtual void device_post_load() override ATTR_COLD { - // RTC registers - if (has_timer) - return m_rtc_regs[m_ram_bank - 8]; + mbc5_logo_spoof_device_base::device_post_load(); + + m_unscramble_data = unscramble_data(m_scramble_mode[0]); + m_unscramble_bank = unscramble_bank(m_scramble_mode[1]); } - return 0xff; -} -void gb_rom_mbc3_device::write_ram(offs_t offset, uint8_t data) -{ - if (m_ram_bank < 4 && m_ram_enable) + virtual unscramble_function unscramble_data(u8 data) = 0; + virtual unscramble_function unscramble_bank(u8 data) = 0; + + template <unsigned... B> + static u8 unscramble(u8 data) { - // RAM - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; + return bitswap<8>(data, B...); } - if (m_ram_bank >= 0x8 && m_ram_bank <= 0xc && m_ram_enable) + +private: + u8 read_rom_low(offs_t offset) { - // RTC registers are writeable too - if (has_timer) - m_rtc_regs[m_ram_bank - 8] = data; + offset = rom_access(offset); + return bank_rom_low_base()[offset]; } -} - -// MBC5 -uint8_t gb_rom_mbc5_device::read_rom(offs_t offset) -{ - 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)]; -} + u8 read_rom_high(offs_t offset) + { + offset = rom_access(0x4000 | offset) & 0x3fff; + return m_unscramble_data(bank_rom_high_base()[offset]); + } -void gb_rom_mbc5_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset < 0x3000) + void bank_switch_fine_low_scrambled(u8 data) { - // MBC5 has a 9 bit bank select - // Writing into 2000-2fff sets the lower 8 bits - m_latch_bank2 = (m_latch_bank2 & 0x100) | data; + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(m_unscramble_bank(data))); } - else if (offset < 0x4000) + + void set_data_scramble(u8 data) { - // 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); + LOG( + "%s: Set ROM data scrambling mode 0x%02X\n", + machine().describe_context(), + data); + if (data != m_scramble_mode[0]) + { + m_unscramble_data = unscramble_data(data); + m_scramble_mode[0] = data; + } + + // TODO: does this actually trigger a bank switch? + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(data)); } - else if (offset < 0x6000) + + void set_bank_scramble(u8 data) { - data &= 0x0f; - if (has_rumble) + LOG( + "%s: Set ROM bank number scrambling mode 0x%02X\n", + machine().describe_context(), + data); + if (data != m_scramble_mode[1]) { - machine().output().set_value("Rumble", BIT(data, 3)); - data &= 0x7; + m_unscramble_bank = unscramble_bank(data); + m_scramble_mode[1] = data; } - m_ram_bank = data; + + // TODO: does this actually trigger a bank switch? + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(data)); } -} -uint8_t gb_rom_mbc5_device::read_ram(offs_t offset) -{ - if (!m_ram.empty() && m_ram_enable) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; - else - return 0xff; -} + unscramble_function m_unscramble_data; + unscramble_function m_unscramble_bank; -void gb_rom_mbc5_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty() && m_ram_enable) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; -} + u8 m_scramble_mode[2]; +}; -// MBC6 -uint8_t gb_rom_mbc6_device::read_rom(offs_t offset) -{ - 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)]; -} - -void gb_rom_mbc6_device::write_bank(offs_t offset, uint8_t data) + +//************************************************************************** +// MBC1 (max 2 MiB ROM, max 32 KiB SRAM) +//************************************************************************** + +class mbc1_device : public rom_mbc_device_base { - if (offset < 0x2000) +public: + mbc1_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + rom_mbc_device_base(mconfig, GB_ROM_MBC1, tag, owner, clock) { - logerror( "%s write to mbc6 ram enable area: %04X <- 0x%02X\n", machine().describe_context(), offset, data ); } - else if (offset < 0x3000) + + virtual std::error_condition load(std::string &message) override ATTR_COLD { - if (!(offset & 0x0800)) - m_latch1 = data; - else if (data == 0x00) - m_bank_4000 = m_latch1; + // probe for "collection" cartridges wired for 4-bit fine bank number + unsigned banklowbits(5U); + if (is_collection()) + banklowbits = 4U; + + // set up ROM and RAM + std::error_condition err = install_memory(message, 2, banklowbits); + if (err) + return err; + + // install handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + emu::rw_delegate(*this, FUNC(mbc1_device::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + emu::rw_delegate(*this, FUNC(mbc1_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + emu::rw_delegate(*this, FUNC(mbc1_device::bank_switch_coarse))); + cart_space()->install_write_handler( + 0x6000, 0x7fff, + emu::rw_delegate(*this, FUNC(mbc1_device::bank_low_mask))); + + // all good + return std::error_condition(); } - else if (offset < 0x4000) + +protected: + virtual void device_reset() override ATTR_COLD { - if (!(offset & 0x0800)) - m_latch2 = data; - else if (data == 0x00) - m_bank_6000 = m_latch2; + rom_mbc_device_base::device_reset(); + + set_bank_rom_coarse(0); + set_bank_rom_fine(1); + set_bank_rom_low_coarse_mask(0x00); + set_ram_enable(false); + set_bank_ram(0); + set_bank_ram_mask(0x00); } -} - -uint8_t gb_rom_mbc6_device::read_ram(offs_t offset) -{ - if (!m_ram.empty()) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; - else - return 0xff; -} - -void gb_rom_mbc6_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; -} - -// MBC7 -uint8_t gb_rom_mbc7_device::read_rom(offs_t offset) -{ - 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)]; -} +private: + void enable_ram(u8 data) + { + set_ram_enable(0x0a == (data & 0x0f)); + } -void gb_rom_mbc7_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) + void bank_switch_fine(u8 data) { - // FIXME: Add RAM enable support - logerror("%s Write to ram enable register 0x%04X <- 0x%02X\n", machine().describe_context(), offset, data); + data &= 0x1f; + set_bank_rom_fine(data ? data : 1); } - else if (offset < 0x3000) + + void bank_low_mask(u8 data) { - 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; + u8 const mask(data ? 0x03 : 0x00); + LOG( + "%s: RAM/low ROM bank switching %s\n", + machine().describe_context(), + mask ? "enabled" : "disabled"); + set_bank_rom_low_coarse_mask(mask); + set_bank_ram_mask(mask); } - else + + bool is_collection() ATTR_COLD { - 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) + // addressing must be explicitly specified in software lists + if (loaded_through_softlist()) + return false; + + // need ROM to probe + memory_region *const romregion(cart_rom_region()); + if (!romregion) + return false; + + // doesn't make sense without more than 256 KiB, and can address up to 1 MiB in 16 KiB pages + auto const rombytes(romregion->bytes()); + if (((u32(0x4000) << 6) < rombytes) || ((u32(0x4000) << 4) >= rombytes) || (rombytes & 0x3fff)) + return false; + + // reject if the header checksum for the first page fails + u8 const *const rombase(romregion->base()); + if (!cartheader::verify_header_checksum(rombase + 0x100)) + return false; + + // check title in first page header against list of known collections + static constexpr u8 KNOWN_COLLECTIONS[][0x10] = { + { 'B', 'O', 'M', 'C', 'O', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 'B', 'O', 'M', 'S', 'E', 'L', 0, 0, 0, 0, 0, 'B', '2', 'C', 'K', 0xc0 }, + { 'G', 'E', 'N', 'C', 'O', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 'M', 'O', 'M', 'O', 'C', 'O', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 'M', 'O', 'R', 'T', 'A', 'L', 'K', 'O', 'M', 'B', 'A', 'T', ' ', 'D', 'U', 'O' }, + { 'M', 'O', 'R', 'T', 'A', 'L', 'K', 'O', 'M', 'B', 'A', 'T', 'I', '&', 'I', 'I' }, + { 'S', 'U', 'P', 'E', 'R', 'C', 'H', 'I', 'N', 'E', 'S', 'E', ' ', '1', '2', '3' } }; + auto const known( + std::find_if( + std::begin(KNOWN_COLLECTIONS), + std::end(KNOWN_COLLECTIONS), + [rombase] (auto const &name) + { + return std::equal(std::begin(name), std::end(name), &rombase[cartheader::OFFSET_TITLE]); + })); + if (std::end(KNOWN_COLLECTIONS) != known) { - switch (offset & 0x7000) - { - case 0x3000: /* 0x3000-0x3fff */ - case 0x4000: /* 0x4000-0x4fff */ - case 0x5000: /* 0x5000-0x5fff */ - case 0x6000: /* 0x6000-0x6fff */ - case 0x7000: /* 0x7000-0x7fff */ - break; - } + logerror("Detected known multi-game collection with 4-bit fine bank addressing\n"); + return true; } - } -} -uint8_t gb_rom_mbc7_device::read_ram(offs_t offset) -{ - if (!m_ram.empty()) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; - else - return 0xff; -} - -void gb_rom_mbc7_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; -} - - -// M161 - -uint8_t gb_rom_m161_device::read_rom(offs_t offset) -{ - return m_rom[rom_bank_map[m_base_bank] * 0x4000 + (offset & 0x7fff)]; -} + // reject if the header checksum for the second coarse page fails + if (!cartheader::verify_header_checksum(rombase + (u32(0x4000) << 4) + 0x100)) + return false; -void gb_rom_m161_device::write_bank(offs_t offset, uint8_t data) -{ - // the mapper (74HC161A) only has data lines D2..D0 - data &= 0x07; + // assume it's a collection if the type indicates an MBC1 cartridge + switch (rombase[(u32(0x4000) << 4) + cartheader::OFFSET_TYPE]) + { + case cartheader::TYPE_MBC1: + case cartheader::TYPE_MBC1_RAM: + case cartheader::TYPE_MBC1_RAM_BATT: + logerror("Detected MBC1 header in page 0x10, using 4-bit fine bank addressing\n"); + return true; + } - // 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; + // assume anything else uses a different (presumably more conventional) scheme + return false; } -} - - -// MMM01 - -uint8_t gb_rom_mmm01_device::read_rom(offs_t offset) -{ - 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)]; -} +//************************************************************************** +// MBC5 (max 128 MiB ROM, max 128 KiB SRAM) +//************************************************************************** -void gb_rom_mmm01_device::write_bank(offs_t offset, uint8_t data) +class mbc5_device : public mbc5_device_base { - // the mapper only has data lines D6..D0 - data &= 0x7f; +public: + mbc5_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_device_base(mconfig, GB_ROM_MBC5, tag, owner, clock), + m_rumble(*this, "rumble"), + m_has_rumble(false) + { + } - // the mapper only uses inputs A15..A13 - switch (offset & 0xe000) + virtual std::error_condition load(std::string &message) override ATTR_COLD { - case 0x0000: // Map Latch, AA #WE, RAM Enable - if (!m_map) { - m_ramb_nwe = (data & (0x30)) >> 4; - m_map = (data & 0x40); + // check whether rumble motor is present + char const *const rumblefeature(get_feature("rumble")); + if (rumblefeature) + { + // explicitly specified in software list + if (util::streqlower(rumblefeature, "yes") || util::streqlower(rumblefeature, "true")) + { + logerror("Rumble motor present\n"); + m_has_rumble = true; } - 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); + else if (util::streqlower(rumblefeature, "no") || util::streqlower(rumblefeature, "false")) + { + logerror("No rumble motor present\n"); + m_has_rumble = false; } - - 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; + else + { + message = "Invalid 'rumble' feature value (must be yes or no)"; + return image_error::BADSOFTWARE; } + } + else if (loaded_through_softlist()) + { + logerror("No 'rumble' feature found, assuming no rumble motor present\n"); + m_has_rumble = false; + } + else + { + gbxfile::leader_1_0 leader; + u8 const *extra; + u32 extralen; + if (gbxfile::get_data(gbx_footer_region(), leader, extra, extralen)) + { + m_has_rumble = bool(leader.rumble); + logerror("GBX format image specifies %srumble motor present\n", m_has_rumble ? "" : "no "); + } + else + { + // try probing the header + memory_region *const romregion(cart_rom_region()); + if (romregion && (romregion->bytes() > cartheader::OFFSET_TYPE)) + { + u8 const carttype((&romregion->as_u8())[cartheader::OFFSET_TYPE]); + switch (carttype) + { + case cartheader::TYPE_MBC5_RUMBLE: + case cartheader::TYPE_MBC5_RUMBLE_RAM: + case cartheader::TYPE_MBC5_RUMBLE_RAM_BATT: + m_has_rumble = true; + break; + case cartheader::TYPE_MBC5: + case cartheader::TYPE_MBC5_RAM: + case cartheader::TYPE_MBC5_RAM_BATT: + m_has_rumble = false; + break; + default: + osd_printf_warning( + "[%s] Unrecognized cartridge type 0x%02X in header, assuming no rumble motor present\n", + tag(), + carttype); + m_has_rumble = false; + } + logerror( + "Cartridge type 0x%02X in header, %srumble motor present\n", + carttype, + m_has_rumble ? "" : "no "); + } + } + } - if (!m_mode_nwe) - m_mode = data & 0x01; - break; - default: - break; - } -} - -uint8_t gb_rom_mmm01_device::read_ram(offs_t offset) -{ - uint8_t ramb_masked = ((offset & 0x4000) | m_mode ? m_ramb : m_ramb & ~0x03); - uint8_t ramb = ramb_masked; + // install base MBC5 handlers + std::error_condition const result(mbc5_device_base::load(message)); + if (result) + return result; - // multiplex with RA20..RA19 - if (m_mux) - ramb = (ramb & ~0x03) | ((m_romb & 0x60) >> 5); + // install rumble-aware handler if appropriate + if (m_has_rumble) + { + cart_space()->install_write_handler( + 0x4000, 0x5fff, + emu::rw_delegate(*this, FUNC(mbc5_device::bank_switch_coarse))); + } - if (!m_ram.empty() && m_ram_enable) - { - return m_ram[ram_bank_map[ramb] * 0x2000 + (offset & 0x1fff)]; + // all good + return std::error_condition(); } - else - return 0xff; -} -void gb_rom_mmm01_device::write_ram(offs_t offset, uint8_t data) -{ - 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) +protected: + virtual void device_start() override ATTR_COLD { - m_ram[ram_bank_map[ramb] * 0x2000 + (offset & 0x1fff)] = data; - } -} + mbc5_device_base::device_start(); -// Sachen MMC1 + m_rumble.resolve(); + } -uint8_t gb_rom_sachen_mmc1_device::read_rom(offs_t offset) -{ - 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)]; -} - -void gb_rom_sachen_mmc1_device::write_bank(offs_t offset, uint8_t data) -{ - /* Only A15..A6, A4, A1..A0 are connected */ - /* We only decode upper three bits */ - switch ((offset & 0xffd3) & 0xe000) + virtual void device_reset() override ATTR_COLD { - case 0x0000: /* Base ROM Bank Register */ + mbc5_device_base::device_reset(); - 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; + if (m_has_rumble) + m_rumble = 0; + } - case 0x4000: /* ROM Bank Mask Register */ +private: + void bank_switch_coarse(u8 data) + { + assert(m_has_rumble); + mbc5_device_base::bank_switch_coarse(data); + m_rumble = BIT(data, 3); + } - 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; + output_finder<> m_rumble; + bool m_has_rumble; +}; - case 0x6000: - /* nothing happens when writing to 0x6000-0x7fff, as verified by Tauwasser */ - break; - default: +//************************************************************************** +// MBC5 variant used by BBD games +//************************************************************************** - //logerror("write to unknown/unmapped area %04X <= %02X\n", offset, data); - /* did not extensively test other unlikely ranges */ - break; +class bbd_device : public mbc5_scrambled_device_base +{ +public: + bbd_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_scrambled_device_base(mconfig, GB_ROM_BBD, tag, owner, clock) + { } -} -// Sachen MMC2 +protected: + virtual unscramble_function unscramble_data(u8 data) override + { + switch (data & 0x07) + { + case 0x0: // no scrambling + return &bbd_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; + case 0x4: // Garou + return &bbd_device::unscramble<7, 6, 2, 4, 3, 1, 5, 0>; + case 0x5: // Harry Potter + return &bbd_device::unscramble<7, 6, 5, 1, 3, 2, 4, 0>; + case 0x7: // Digimon Adventure + return &bbd_device::unscramble<7, 6, 2, 4, 3, 5, 1, 0>; + } -uint8_t gb_rom_sachen_mmc2_device::read_rom(offs_t offset) -{ - uint16_t off_edit = offset; + logerror( + "%s: Unsupported ROM data scrambling mode 0x%02X\n", + machine().describe_context(), + data); + return &bbd_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; + } - /* 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 */ + virtual unscramble_function unscramble_bank(u8 data) override + { + switch (data & 0x07) + { + case 0x0: // no scrambling + return &bbd_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; + case 0x3: // Digimon Adventure, Garou - two most significant bits unconfirmed + return &bbd_device::unscramble<7, 6, 5, 1, 0, 2, 4, 3>; + case 0x5: // Harry Potter - two most significant bits unconfirmed + return &bbd_device::unscramble<7, 6, 5, 0, 4, 3, 2, 1>; + } - 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; + logerror( + "%s: Unsupported ROM bank number scrambling mode 0x%02X\n", + machine().describe_context(), + data); + return &bbd_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; } +}; - 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); +//************************************************************************** +// MBC5 variant with DSHGGB-81 PCB +//************************************************************************** - 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)]; -} - -uint8_t gb_rom_sachen_mmc2_device::read_ram(offs_t offset) +class dshggb81_device : public mbc5_scrambled_device_base { - if (m_mode == MODE_LOCKED_DMG) { - m_unlock_cnt = 0x00; - m_mode = MODE_LOCKED_CGB; +public: + dshggb81_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_scrambled_device_base(mconfig, GB_ROM_DSHGGB81, tag, owner, clock) + { } - return 0xff; - -} -void gb_rom_sachen_mmc2_device::write_ram(offs_t offset, uint8_t data) -{ - if (m_mode == MODE_LOCKED_DMG) { - m_unlock_cnt = 0x00; - m_mode = MODE_LOCKED_CGB; +protected: + virtual unscramble_function unscramble_data(u8 data) override + { + switch (data & 0x07) + { + default: // just to shut up dumb compilers + case 0x0: return &dshggb81_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; + case 0x1: return &dshggb81_device::unscramble<7, 5, 6, 4, 3, 1, 2, 0>; + case 0x2: return &dshggb81_device::unscramble<7, 1, 2, 4, 3, 5, 6, 0>; + case 0x3: return &dshggb81_device::unscramble<7, 6, 2, 4, 3, 1, 5, 0>; + case 0x4: return &dshggb81_device::unscramble<7, 6, 1, 4, 3, 2, 5, 0>; + case 0x5: return &dshggb81_device::unscramble<7, 1, 5, 4, 3, 6, 2, 0>; + case 0x6: return &dshggb81_device::unscramble<7, 5, 2, 4, 3, 6, 1, 0>; + case 0x7: return &dshggb81_device::unscramble<7, 1, 6, 4, 3, 5, 2, 0>; + } } -} + virtual unscramble_function unscramble_bank(u8 data) override + { + switch (data & 0x07) + { + case 0x0: // no scrambling + return &dshggb81_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; + } + + logerror( + "%s: Unsupported ROM bank number scrambling mode 0x%02X\n", + machine().describe_context(), + data); + return &dshggb81_device::unscramble<7, 6, 5, 4, 3, 2, 1, 0>; + } +}; -// 188 in 1 pirate (only preliminary) -uint8_t gb_rom_188in1_device::read_rom(offs_t offset) -{ - 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)]; -} +//************************************************************************** +// MBC5 variant used by Sintax games +//************************************************************************** -void gb_rom_188in1_device::write_bank(offs_t offset, uint8_t data) +class sintax_device : public mbc5_logo_spoof_device_base { - if (offset == 0x7b00) +public: + sintax_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_logo_spoof_device_base(mconfig, GB_ROM_SINTAX, tag, owner, clock), + m_scramble_mode(0U), + m_xor_index(0U) { - 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) + + virtual std::error_condition load(std::string &message) override ATTR_COLD { - // what do these writes do? - printf("write to 0x%X data 0x%X\n", offset, data); + // install regular MBC5 handlers + std::error_condition const result(mbc5_logo_spoof_device_base::load(message)); + if (result) + return result; + + // bank numbers and ROM contents are scrambled for protection + cart_space()->install_read_handler( + 0x0000, 0x3fff, + emu::rw_delegate(*this, FUNC(sintax_device::read_rom_low))); + cart_space()->install_read_handler( + 0x4000, 0x7fff, + emu::rw_delegate(*this, FUNC(sintax_device::read_rom_high))); + cart_space()->install_write_handler( + 0x2000, 0x2fff, + emu::rw_delegate(*this, FUNC(sintax_device::bank_switch_fine_low_scrambled))); + cart_space()->install_write_handler( + 0x5000, 0x5fff, + emu::rw_delegate(*this, FUNC(sintax_device::set_scramble_mode))); + cart_space()->install_write_handler( + 0x7000, 0x70ff, 0x0000, 0x0f00, 0x0000, + emu::rw_delegate(*this, FUNC(sintax_device::set_xor))); + + // all good + return std::error_condition(); } - else - gb_rom_mbc1_device::write_bank(offset, data); -} +protected: + virtual void device_start() override ATTR_COLD + { + mbc5_logo_spoof_device_base::device_start(); -// MBC5 variant used by Li Cheng / Niutoude games - -void gb_rom_licheng_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset > 0x2100 && offset < 0x3000) - return; + save_item(NAME(m_scramble_mode)); + save_item(NAME(m_xor_index)); + save_item(NAME(m_xor_table)); + } - gb_rom_mbc5_device::write_bank(offset, data); -} + virtual void device_reset() override ATTR_COLD + { + mbc5_logo_spoof_device_base::device_reset(); -// 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) + m_scramble_mode = 0U; + m_xor_index = 0U; + std::fill(std::begin(m_xor_table), std::end(m_xor_table), 0U); + } -uint8_t gb_rom_chongwu_device::read_rom(offs_t offset) -{ - // protection check at the first read here... - if (offset == 0x41c3 && !m_protection_checked) +private: + u8 read_rom_low(offs_t offset) { - m_protection_checked = 1; - return 0x5d; + offset = rom_access(offset); + return bank_rom_low_base()[offset]; } - 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 + u8 read_rom_high(offs_t offset) + { + offset = rom_access(0x4000 | offset) & 0x3fff; + return bank_rom_high_base()[offset] ^ m_xor_table[m_xor_index]; + } -void gb_rom_sintax_device::set_xor_for_bank(uint8_t bank) -{ - switch (bank & 0x0f) + void bank_switch_fine_low_scrambled(u8 data) { - case 0x00: case 0x04: case 0x08: case 0x0c: - m_currentxor = m_xor2; + m_xor_index = data & 0x03; + + switch (m_scramble_mode & 0x0f) + { + case 0x00: // Lion King, Golden Sun + data = bitswap<8>(data, 7, 0, 5, 6, 3, 4, 1, 2); break; - case 0x01: case 0x05: case 0x09: case 0x0d: - m_currentxor = m_xor3; + case 0x01: // Langrisser + data = bitswap<8>(data, 0, 1, 6, 7, 4, 5, 2, 3); break; - case 0x02: case 0x06: case 0x0a: case 0x0e: - m_currentxor = m_xor4; + case 0x05: // Maple Story, Pokemon Platinum + data = bitswap<8>(data, 7, 6, 1, 0, 3, 2, 5, 4); // needs verification break; - case 0x03: case 0x07: case 0x0b: case 0x0f: - m_currentxor = m_xor5; + case 0x07: // Bynasty Warriors 5 + data = bitswap<8>(data, 2, 0, 3, 1, 5, 4, 7, 6); // 5 and 7 unconfirmed break; - } -} + case 0x09: + data = bitswap<8>(data, 4, 5, 2, 3, 0, 1, 6, 7); + break; + case 0x0b: // Shaolin Legend + data = bitswap<8>(data, 2, 3, 0, 1, 6, 7, 4, 5); // 5 and 6 unconfirmed + break; + case 0x0d: // older games + data = bitswap<8>(data, 1, 0, 7, 6, 5, 4, 3, 2); + break; + case 0x0f: // no scrambling + break; + default: + logerror( + "%s: Unsupported ROM bank number scrambling mode 0x%X\n", + machine().describe_context(), + m_scramble_mode & 0x0f); + } -uint8_t gb_rom_sintax_device::read_rom(offs_t offset) -{ - 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; -} + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(data)); + } -void gb_rom_sintax_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset < 0x3000) + void set_scramble_mode(u8 data) { - set_xor_for_bank(data); - - switch (m_sintax_mode & 0x0f) + if (!m_scramble_mode) { - 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; + LOG( + "%s: Set scramble mode = 0x%02X\n", + machine().describe_context(), + data); + m_scramble_mode = data; + bank_switch_fine_low_scrambled(0x01); } - 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) + else { - m_sintax_mode = data; - write_bank(0x2000, 1); //force a fake bank switch + LOG( + "%s: Ignoring scramble mode = 0x%02X when already set to 0x%02X\n", + machine().describe_context(), + data, + m_scramble_mode); } -// printf("sintax mode %x\n", m_sintax_mode & 0xf); } - else if (offset >= 0x7000) + + void set_xor(offs_t offset, u8 data) { - switch ((offset & 0x00f0) >> 4) + u8 const index(offset >> 4); + if ((2 <= index) && (6 > index)) { - case 2: - m_xor2 = data; - break; - case 3: - m_xor3 = data; - break; - case 4: - m_xor4 = data; - break; - case 5: - m_xor5 = data; - break; + LOG( + "%s: Set XOR entry %u = 0x%02X\n", + machine().describe_context(), + index - 2, + data); + m_xor_table[index - 2] = data; } - - if (m_currentxor == 0) - set_xor_for_bank(4); } -} - -uint8_t gb_rom_sintax_device::read_ram(offs_t offset) -{ - if (!m_ram.empty() && m_ram_enable) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; - else - return 0xff; -} - -void gb_rom_sintax_device::write_ram(offs_t offset, uint8_t data) -{ - 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... + u8 m_scramble_mode; + u8 m_xor_index; + u8 m_xor_table[4]; +}; - */ -// MBC5 variant used by Digimon 2 (and maybe 4?) -uint8_t gb_rom_digimon_device::read_rom(offs_t offset) -{ - 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 Chǒngwù Xiǎo Jīnglíng - Jié Jīn Tǎ Zhī Wáng +//************************************************************************** -void gb_rom_digimon_device::write_bank(offs_t offset, uint8_t data) +class chongwu_device : public mbc5_logo_spoof_device_base { - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset == 0x2000) +public: + chongwu_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_logo_spoof_device_base(mconfig, GB_ROM_CHONGWU, tag, owner, clock), + m_protection_checked(0U), + m_installing_tap(false) { -// printf("written $02 %X at %X\n", data, offset); - if (!data) - data++; - m_latch_bank2 = data/2; } - else if (offset < 0x3000) + +protected: + virtual void device_start() override ATTR_COLD { -// printf("written $03 %X at %X\n", data, offset); + mbc5_logo_spoof_device_base::device_start(); + + save_item(NAME(m_protection_checked)); } - else if (offset < 0x4000) + + virtual void device_reset() override ATTR_COLD { -// printf("written $04 %X at %X\n", data, offset); + mbc5_logo_spoof_device_base::device_reset(); + + m_protection_checked = 0U; + + install_protection_tap(); + m_notif_cart_space = cart_space()->add_change_notifier( + [this] (read_or_write mode) + { + if (u32(mode) & u32(read_or_write::READ)) + install_protection_tap(); + }); } - else if (offset < 0x6000) + +private: + void install_protection_tap() { -// printf("written $05-$06 %X at %X\n", data, offset); - data &= 0x0f; - m_ram_bank = data; + if (!m_installing_tap) + { + m_installing_tap = true; + m_protection_tap.remove(); + if (!m_protection_checked) + { + m_protection_tap = cart_space()->install_read_tap( + 0x41c3, 0x41c3, + "protection_tap_r", + [this] (offs_t offset, u8 &data, u8 mem_mask) + { + assert(!m_protection_checked); + + m_protection_checked = 1U; + m_notif_cart_space.reset(); + m_protection_tap.remove(); + + data = 0x5d; + }, + &m_protection_tap); + } + else + { + m_notif_cart_space.reset(); + } + m_installing_tap = false; + } } -// else -// printf("written $07 %X at %X\n", data, offset); -} -uint8_t gb_rom_digimon_device::read_ram(offs_t offset) -{ - if (!m_ram.empty() && m_ram_enable) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; - else - return 0xff; -} + memory_passthrough_handler m_protection_tap; + util::notifier_subscription m_notif_cart_space; -void gb_rom_digimon_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty() && m_ram_enable) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; -} + u8 m_protection_checked; + bool m_installing_tap; +}; -// MBC1 variant used by Yong Yong for Rockman 8 -uint8_t gb_rom_rockman8_device::read_rom(offs_t offset) -{ - if (offset < 0x4000) - return m_rom[m_latch_bank * 0x4000 + (offset & 0x3fff)]; - else - return m_rom[m_latch_bank2 * 0x4000 + (offset & 0x3fff)]; -} +//************************************************************************** +// MBC5 variant used by Li Cheng/Niutoude games +//************************************************************************** -void gb_rom_rockman8_device::write_bank(offs_t offset, uint8_t data) +class licheng_device : public mbc5_logo_spoof_device_base { - if (offset < 0x2000) - return; - else if (offset < 0x4000) +public: + licheng_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_logo_spoof_device_base(mconfig, GB_ROM_LICHENG, tag, owner, clock) { - // 5bits only - data &= 0x1f; - if (data == 0) - data = 1; - if (data > 0xf) - data -= 8; + } - m_latch_bank2 = data; + virtual std::error_condition load(std::string &message) override ATTR_COLD + { + // install regular MBC5 handlers + std::error_condition const result(mbc5_logo_spoof_device_base::load(message)); + if (result) + return result; + + // protection against using a standard MBC5 - actual ignored range uncertain + // 0x2100 must not be ignored for Cannon Fodder sound + // 0x2180 must be ignored for FF DX3 + cart_space()->unmap_write(0x2101, 0x2fff); + + // all good + return std::error_condition(); } -} +}; -uint8_t gb_rom_rockman8_device::read_ram(offs_t offset) -{ - if (!m_ram.empty()) - return m_ram[offset]; - else - return 0xff; -} -void gb_rom_rockman8_device::write_ram(offs_t offset, uint8_t data) -{ - 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...) +//************************************************************************** +// New Game Boy Color cartridges with HK0701 and HK0819 boards +//************************************************************************** -uint8_t gb_rom_sm3sp_device::read_rom(offs_t offset) +class ngbchk_device : public rom_mbc_device_base { - if (offset < 0x4000) - return m_rom[rom_bank_map[0] * 0x4000 + (offset & 0x3fff)]; - else - return m_rom[m_latch_bank2 * 0x4000 + (offset & 0x3fff)]; -} +public: + ngbchk_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + rom_mbc_device_base(mconfig, GB_ROM_NEWGBCHK, tag, owner, clock), + m_view_prot(*this, "protection") + { + } -void gb_rom_sm3sp_device::write_bank(offs_t offset, uint8_t data) -{ -// 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; + virtual std::error_condition load(std::string &message) override ATTR_COLD + { + // TODO: ROM addresses seem to be fully decoded with HK0819 (rather than mirroring) + + // set up ROM and RAM + // TODO: how many RAM bank outputs are actually present? + std::error_condition err = install_memory(message, 2, 7); + if (err) + return err; + + // install handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + emu::rw_delegate(*this, FUNC(ngbchk_device::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + emu::rw_delegate(*this, FUNC(ngbchk_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + emu::rw_delegate(*this, FUNC(ngbchk_device::bank_switch_coarse))); + + // install protection over the top of high ROM bank + cart_space()->install_view( + 0x4000, 0x7fff, + m_view_prot); + m_view_prot[0].install_read_handler( + 0x4000, 0x4fff, 0x0ff0, 0x0000, 0x0000, + emu::rw_delegate(*this, FUNC(ngbchk_device::protection))); + m_view_prot[0].unmap_read(0x5000, 0x7fff); + + // all good + return std::error_condition(); + } - 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; - } - } +protected: + virtual void device_reset() override ATTR_COLD + { + rom_mbc_device_base::device_reset(); + + // TODO: proper reset state + set_bank_rom_coarse(0); + set_bank_rom_fine(0); + set_ram_enable(false); + + m_view_prot.disable(); } - else if (offset < 0x5000) + +private: + void enable_ram(u8 data) { -// printf("write $5 %X at %X\n", data, offset); - //maybe rumble?? + // TODO: how many bits are checked? + set_ram_enable(0x0a == data); } - else if (offset < 0x6000) + + void bank_switch_fine(u8 data) { -// printf("write mode %x\n", data); - m_mode = BIT(data, 5); -// write_bank(0x2000, 1); + set_bank_rom_fine(data & 0x7f); + LOG( + "%s: Protection read %s\n", + machine().describe_context(), + BIT(data, 7) ? "enabled" : "disabled"); + if (BIT(data, 7)) + m_view_prot.select(0); + else + m_view_prot.disable(); } -} -uint8_t gb_rom_sm3sp_device::read_ram(offs_t offset) -{ - if (!m_ram.empty()) - return m_ram[offset]; - else - return 0xff; -} + u8 protection(offs_t offset) + { + offset >>= 4; + switch (offset & 0x007) + { + default: // just to shut up dumb compilers + case 0x000: + return offset; + case 0x001: + return offset ^ 0xaa; + case 0x002: + return offset ^ 0x55; + case 0x003: + return (offset >> 1) | (offset << 7); + case 0x004: + return (offset << 1) | (offset >> 7); + case 0x05: + return bitswap<8>(offset, 0, 1, 2, 3, 4, 5, 6, 7); + case 0x06: + return + (bitswap<4>(offset | (offset >> 1), 6, 4, 2, 0) << 4) | + bitswap<4>(offset & (offset >> 1), 6, 4, 2, 0); + case 0x07: + return + (bitswap<4>(offset ^ (~offset >> 1), 6, 4, 2, 0) << 4) | + bitswap<4>(offset ^ (offset >> 1), 6, 4, 2, 0); + } + } -void gb_rom_sm3sp_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty()) - m_ram[offset] = data; -} + memory_view m_view_prot; +}; -void gb_rom_camera_device::update_camera() -{ - m_camera_regs[0] &= ~0x1; -} -uint8_t gb_rom_camera_device::read_rom(offs_t offset) -{ - 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)]; -} -void gb_rom_camera_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset < 0x4000) +//************************************************************************** +// Fast Fame VF001 MBC5 variant with protection +//************************************************************************** + +class vf001_device : public mbc5_logo_spoof_device_base +{ +public: + vf001_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc5_logo_spoof_device_base(mconfig, GB_ROM_VF001, tag, owner, clock), + m_bank_rom_upper(*this, "upper"), + m_bank_mask_rom_upper(0U), + m_command_preload(0U), + m_prot_cmd_enable(0U), + m_prot_cmd_data(0U), + m_readback_address(0U), + m_readback_bank(0U), + m_readback_length(0U), + m_readback_data{ 0U, 0U, 0U, 0U }, + m_readback_count(0U), + m_split_address(0U), + m_split_enable(0U) { - // 7bits - data &= 0x7f; - /* Selecting bank 0 == selecting bank 1 */ - if (data == 0) - data = 1; + } + + virtual std::error_condition load(std::string &message) override ATTR_COLD + { + // get the command preload value + // TODO: add a way to specify this in software lists + gbxfile::leader_1_0 leader; + u8 const *extra; + u32 extralen; + if (gbxfile::get_data(gbx_footer_region(), leader, extra, extralen)) + { + if (1 <= extralen) + { + m_command_preload = extra[0]; + logerror( + "GBX format image specifies command preload value 0x%02X\n", + m_command_preload); + } + } - m_latch_bank2 = data; + // install regular MBC5 handlers + std::error_condition const result(mbc5_logo_spoof_device_base::load(message)); + if (result) + return result; + + // set up an additional bank for the upper part of the low bank when split + memory_region *const romregion(cart_rom_region()); + m_bank_mask_rom_upper = device_generic_cart_interface::map_non_power_of_two( + unsigned(romregion->bytes() / 0x4000), + [this, base = &romregion->as_u8()] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%06X-0x%06X in upper bank entry %u\n", + page * 0x4000, + (page * 0x4000) + 0x3fff, + entry); + m_bank_rom_upper->configure_entry(entry, &base[page * 0x4000]); + }); + m_bank_rom_upper->set_entry(0); + + // install handlers with protection emulation + cart_space()->install_read_handler( + 0x0000, 0x7fff, + emu::rw_delegate(*this, FUNC(vf001_device::read_rom))); + cart_space()->install_write_handler( + 0x6000, 0x700f, 0x100f, 0x0000, 0x0000, + emu::rw_delegate(*this, FUNC(vf001_device::prot_cmd))); + + // all good + return std::error_condition(); } - else if (offset < 0x6000) + +protected: + virtual void device_start() override ATTR_COLD { - m_ram_bank = data & 0x1f; + mbc5_logo_spoof_device_base::device_start(); + + m_prot_cmd_data = 0U; + m_readback_address = 0U; + m_readback_bank = 0U; + std::fill(std::begin(m_readback_data), std::end(m_readback_data), 0U); + m_split_address = 0U; + + save_item(NAME(m_prot_cmd_enable)); + save_item(NAME(m_prot_cmd_data)); + save_item(NAME(m_readback_address)); + save_item(NAME(m_readback_bank)); + save_item(NAME(m_readback_length)); + save_item(NAME(m_readback_data)); + save_item(NAME(m_readback_count)); + save_item(NAME(m_split_address)); + save_item(NAME(m_split_enable)); } -} -uint8_t gb_rom_camera_device::read_ram(offs_t offset) -{ - if ((m_ram_bank & 0x10) != 0) - return (offset == 0) ? (m_camera_regs[0] & 0x7) : 0; - if (!m_ram.empty() && (m_camera_regs[0] & 0x1) == 0) + virtual void device_reset() override ATTR_COLD { - /* Use first saved image as the snapshot. Top and bottom of snapshot are not saved. */ - if (m_ram_bank == 0 && offset >= 0x100 && offset < 0xf00) - return m_ram[0x1f00 + offset]; - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; + mbc5_logo_spoof_device_base::device_reset(); + + m_prot_cmd_enable = 0U; + m_readback_length = 0U; + m_readback_count = 0U; + m_split_enable = 0U; } - return 0; -} -void gb_rom_camera_device::write_ram(offs_t offset, uint8_t data) -{ - if ((m_ram_bank & 0x10) != 0) +private: + u8 read_rom(offs_t offset) { - if (offset == 0) + u8 const data(mbc5_logo_spoof_device_base::read_rom(offset)); + + if (!m_readback_count && m_readback_length && !machine().side_effects_disabled()) { - m_camera_regs[0] = data & 0x7; - if (data & 0x1) update_camera(); + if ((offset == m_readback_address) && ((BIT(offset, 14) ? bank_rom_fine() : 0) == m_readback_bank)) + { + LOG( + "%s: Readback triggered by read from ROM bank 0x%02X address 0x%04X, length = %u\n", + machine().describe_context(), + m_readback_bank, + m_readback_address, + m_readback_length); + m_readback_count = m_readback_length; + } } - else if (offset < 54) + + if (m_readback_count) { - m_camera_regs[offset] = data; + u8 const index(m_readback_length - m_readback_count); + if (!machine().side_effects_disabled()) + --m_readback_count; + LOG( + "%s: Readback %u = 0x%02X\n", + machine().describe_context(), + index, + m_readback_data[index]); + return m_readback_data[index]; } + + bool const split_upper(m_split_enable && (m_split_address <= offset) && (0x4000 > offset)); + return !split_upper ? data : reinterpret_cast<u8 const *>(m_bank_rom_upper->base())[offset]; } - else if (m_ram_enable && (m_camera_regs[0] & 0x1) == 0) + + void prot_cmd(offs_t offset, u8 data) { - // RAM - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; + if ((0x1000 == offset) && (0x96 == data)) + { + m_prot_cmd_enable = 1U; + m_prot_cmd_data = m_command_preload; + LOG( + "%s: Enabled protection commands, initial data = 0x%02X\n", + machine().describe_context(), + m_prot_cmd_data); + } + else if ((0x100f == offset) && (0x96 == data)) + { + LOG("%s: Disabled protection commands\n", machine().describe_context()); + m_prot_cmd_enable = 0U; + } + else if (m_prot_cmd_enable) + { + if (((0x0001 <= offset) && (0x1000 > offset)) || (0x100b <= offset)) + { + logerror( + "%s: Unknown protection command address 0x%04X = 0x%02X\n", + machine().describe_context(), + offset, + data); + return; + } + + // previous data is rotated right and combined with new data + m_prot_cmd_data = ((m_prot_cmd_data >> 1) | (m_prot_cmd_data << 7)) ^ data; + + switch (offset) + { + case 0x0000: + m_bank_rom_upper->set_entry(m_prot_cmd_data & m_bank_mask_rom_upper); + LOG( + "%s: Bank split upper bank = 0x%02X\n", + machine().describe_context(), + m_prot_cmd_data); + break; + case 0x1000: + if (BIT(m_prot_cmd_data, 2)) + { + m_readback_length = (m_prot_cmd_data & 0x03) + 1; + LOG( + "%s: Readback length = %u\n", + machine().describe_context(), + m_readback_length); + } + break; + case 0x1001: + case 0x1002: + m_readback_address &= BIT(offset, 0) ? 0xff00 : 0x00ff; + m_readback_address |= u16(m_prot_cmd_data) << (BIT(offset, 0) ? 0 : 8); + LOG( + "%s: Readback address = 0x%04X\n", + machine().describe_context(), + m_readback_address); + break; + case 0x1003: + m_readback_bank = m_prot_cmd_data; + LOG( + "%s: Readback bank = 0x%02X\n", + machine().describe_context(), + m_readback_bank); + break; + case 0x1004: + case 0x1005: + case 0x1006: + case 0x1007: + m_readback_data[offset & 0x0003] = m_prot_cmd_data; + LOG( + "%s: Readback data %u = 0x%02X\n", + machine().describe_context(), + offset & 0x0003, + m_prot_cmd_data); + break; + case 0x1008: + m_split_enable = (0x0f == (m_prot_cmd_data & 0x0f)) ? 1U : 0U; + LOG( + "%s: Bank split %s\n", + machine().describe_context(), + m_split_enable ? "enabled" : "disabled"); + break; + case 0x1009: + case 0x100a: + m_split_address &= BIT(offset, 0) ? 0xff00 : 0x00ff; + m_split_address |= u16(m_prot_cmd_data) << (BIT(offset, 0) ? 0 : 8); + LOG( + "%s: Bank split address = 0x%04X\n", + machine().describe_context(), + m_split_address); + break; + } + } } -} + + memory_bank_creator m_bank_rom_upper; + u8 m_bank_mask_rom_upper; + u8 m_command_preload; + + u8 m_prot_cmd_enable; + u8 m_prot_cmd_data; + + u16 m_readback_address; + u8 m_readback_bank; + u8 m_readback_length; + u8 m_readback_data[4]; + u8 m_readback_count; + + u16 m_split_address; + u8 m_split_enable; +}; + +} // anonymous namespace + +} // namespace bus::gameboy + + +// device type definition +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC1, device_gb_cart_interface, bus::gameboy::mbc1_device, "gb_rom_mbc1", "Game Boy MBC1 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC5, device_gb_cart_interface, bus::gameboy::mbc5_device, "gb_rom_mbc5", "Game Boy MBC5 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_BBD, device_gb_cart_interface, bus::gameboy::bbd_device, "gb_rom_bbd", "Game Boy BBD Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_DSHGGB81, device_gb_cart_interface, bus::gameboy::dshggb81_device, "gb_rom_dshggb81", "Game Boy DSHGGB-81 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SINTAX, device_gb_cart_interface, bus::gameboy::sintax_device, "gb_rom_sintax", "Game Boy Sintax MBC5 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_CHONGWU, device_gb_cart_interface, bus::gameboy::chongwu_device, "gb_rom_chongwu", "Game Boy Chongwu Xiao Jingling Pokemon Pikecho Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_LICHENG, device_gb_cart_interface, bus::gameboy::licheng_device, "gb_rom_licheng", "Game Boy Li Cheng MBC5 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_NEWGBCHK, device_gb_cart_interface, bus::gameboy::ngbchk_device, "gb_rom_ngbchk", "Game Boy HK0701/HK0819 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_VF001, device_gb_cart_interface, bus::gameboy::vf001_device, "gb_rom_vf001", "Game Boy Vast Fame VF001 Cartridge") |