From 5db7ffb86a435ddc1dcae08f31379d17f24a3b63 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Tue, 13 Sep 2022 06:27:33 +1000 Subject: Reimplemented Game Boy and Mega Duck cartridge slots: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow cartridges to install themselves rather than putting accesses through trampolines. Use memory banks even when they aren't installed directly, as it exposes script bindings and declares intent. Added support for MBC6 Flash, and MBC7 two-axis accelerometer and 128*16 or 256*16 serial EEPROM. Added basic support for Game Boy Camera image capture (various M64282FP processing features are not implemented). Improved MBC3 real-time clock emulation. Added basic support for Hudson Soft HuC-1 cartridges (infrared I/O not supported) and HuC-3 cartridges (real-time clock and infrared I/O not supported). Added full support for Vast Fame VF001 cartridges. Separated Mega Duck flat and banked ROM cartridge types, and allowed software list items to specify whether a cartridge has fixed and selectable 16K banks or a single selectable 32K bank. Added support for RAM sizes smaller than 8K that will be mirrored in the 0xA000-0xBFFF area. Implemented correct ROM mapping for sizes that are not powers of two. Corrected size of MBC2 internal static RAM. Added support for various hypothetical cartridge wirings, such as MBC5 with outer ROM banking for up to 128M, M161 and Wisdom Tree cartridges with flat RAM, and MBC1 or MBC5 with fewer than the maximum number of inner ROM bank lines used. Fixed logo spoofing logic for many pirate cartridges, including Rocket Games, Sachen MMC1 and MMC2, Sintax, Li Cheng, and Niutoude. Identified fine bank mask register for GBCK003 board. Added basic support for GBX format ROM images. Added heuristic for detecting raw dumps of M161 cartridge images. Removed unreliable unlicensed cartridge detection heuristics - these need to be reimplemented in a better way. Software list items promoted to working --------------------------------------- gameboy.xml: Game Boy Camera (Europe, USA) Game Boy Camera Gold (USA) Pocket Camera (Japan, Rev 1) gbcolor.xml: Command Master (Japan) Kirby Tilt 'n' Tumble (USA) Korokoro Kirby (Japan) Street Hero (Taiwan) Thunder Blast Man (Europe) 4 in 1 + 8 in 1 (World, 4B-001) 4 in 1 + 8 in 1 (World, 4B-002) 4 in 1 + 8 in 1 + 16 in 1 (World, 4B-005) 4 in 1 + 8 in 1 (World, 4B-007) 31 in 1 Mighty Mix (Taiwan) 31-in-1 Mighty Mix (Australia) Nǚwáng Gédòu 2000 (Taiwan) Chāojí Gédòu 2001 Alpha (Taiwan) Gédòu Jiàn Shén - Soul Falchion (Taiwan) --- hash/gameboy.xml | 201 +-- hash/gbcolor.xml | 81 +- scripts/src/bus.lua | 32 +- src/devices/bus/gameboy/camera.cpp | 620 +++++++ src/devices/bus/gameboy/camera.h | 18 + src/devices/bus/gameboy/cartbase.cpp | 435 +++++ src/devices/bus/gameboy/cartbase.h | 244 +++ src/devices/bus/gameboy/cartbase.ipp | 410 +++++ src/devices/bus/gameboy/cartheader.h | 132 ++ src/devices/bus/gameboy/carts.cpp | 117 +- src/devices/bus/gameboy/carts.h | 45 +- src/devices/bus/gameboy/gb_slot.cpp | 832 --------- src/devices/bus/gameboy/gb_slot.h | 245 --- src/devices/bus/gameboy/gbslot.cpp | 1310 ++++++++++++++ src/devices/bus/gameboy/gbslot.h | 87 + src/devices/bus/gameboy/gbxfile.cpp | 59 + src/devices/bus/gameboy/gbxfile.h | 117 ++ src/devices/bus/gameboy/huc1.cpp | 192 +++ src/devices/bus/gameboy/huc1.h | 18 + src/devices/bus/gameboy/huc3.cpp | 410 +++++ src/devices/bus/gameboy/huc3.h | 18 + src/devices/bus/gameboy/mbc.cpp | 3135 ++++++++++++++++------------------ src/devices/bus/gameboy/mbc.h | 13 +- src/devices/bus/gameboy/mbc2.cpp | 225 +++ src/devices/bus/gameboy/mbc2.h | 18 + src/devices/bus/gameboy/mbc6.cpp | 617 +++++++ src/devices/bus/gameboy/mbc6.h | 18 + src/devices/bus/gameboy/mbc7.cpp | 379 ++++ src/devices/bus/gameboy/mbc7.h | 19 + src/devices/bus/gameboy/mdslot.cpp | 144 ++ src/devices/bus/gameboy/mdslot.h | 70 + src/devices/bus/gameboy/mmm01.cpp | 348 ++++ src/devices/bus/gameboy/mmm01.h | 16 + src/devices/bus/gameboy/rom.cpp | 1575 ++++++++++++----- src/devices/bus/gameboy/rom.h | 24 +- src/devices/bus/gameboy/slot.cpp | 84 + src/devices/bus/gameboy/slot.h | 92 + src/devices/bus/gameboy/tama5.cpp | 269 +++ src/devices/bus/gameboy/tama5.h | 11 + src/devices/bus/psx/gamebooster.cpp | 90 +- src/devices/bus/psx/gamebooster.h | 29 +- src/devices/bus/snes/sgb.cpp | 78 +- src/devices/bus/snes/sgb.h | 16 +- src/mame/nintendo/gb.cpp | 162 +- 44 files changed, 9420 insertions(+), 3635 deletions(-) create mode 100644 src/devices/bus/gameboy/camera.cpp create mode 100644 src/devices/bus/gameboy/camera.h create mode 100644 src/devices/bus/gameboy/cartbase.cpp create mode 100644 src/devices/bus/gameboy/cartbase.h create mode 100644 src/devices/bus/gameboy/cartbase.ipp create mode 100644 src/devices/bus/gameboy/cartheader.h delete mode 100644 src/devices/bus/gameboy/gb_slot.cpp delete mode 100644 src/devices/bus/gameboy/gb_slot.h create mode 100644 src/devices/bus/gameboy/gbslot.cpp create mode 100644 src/devices/bus/gameboy/gbslot.h create mode 100644 src/devices/bus/gameboy/gbxfile.cpp create mode 100644 src/devices/bus/gameboy/gbxfile.h create mode 100644 src/devices/bus/gameboy/huc1.cpp create mode 100644 src/devices/bus/gameboy/huc1.h create mode 100644 src/devices/bus/gameboy/huc3.cpp create mode 100644 src/devices/bus/gameboy/huc3.h create mode 100644 src/devices/bus/gameboy/mbc2.cpp create mode 100644 src/devices/bus/gameboy/mbc2.h create mode 100644 src/devices/bus/gameboy/mbc6.cpp create mode 100644 src/devices/bus/gameboy/mbc6.h create mode 100644 src/devices/bus/gameboy/mbc7.cpp create mode 100644 src/devices/bus/gameboy/mbc7.h create mode 100644 src/devices/bus/gameboy/mdslot.cpp create mode 100644 src/devices/bus/gameboy/mdslot.h create mode 100644 src/devices/bus/gameboy/mmm01.cpp create mode 100644 src/devices/bus/gameboy/mmm01.h create mode 100644 src/devices/bus/gameboy/slot.cpp create mode 100644 src/devices/bus/gameboy/slot.h create mode 100644 src/devices/bus/gameboy/tama5.cpp create mode 100644 src/devices/bus/gameboy/tama5.h diff --git a/hash/gameboy.xml b/hash/gameboy.xml index 37a0ef060bd..1c2dca107f4 100644 --- a/hash/gameboy.xml +++ b/hash/gameboy.xml @@ -80,7 +80,7 @@ license:CC0 - + @@ -1085,7 +1085,7 @@ license:CC0 - + @@ -1108,7 +1108,7 @@ license:CC0 - + @@ -1362,7 +1362,7 @@ license:CC0 - + @@ -2713,7 +2713,8 @@ license:CC0 - + + @@ -3762,7 +3763,7 @@ license:CC0 - + @@ -3807,7 +3808,7 @@ license:CC0 - + @@ -4055,7 +4056,7 @@ license:CC0 - + @@ -4791,7 +4792,7 @@ license:CC0 - + @@ -5117,7 +5118,7 @@ license:CC0 - + @@ -5140,7 +5141,7 @@ license:CC0 - + @@ -5427,7 +5428,7 @@ license:CC0 - + @@ -5446,7 +5447,7 @@ license:CC0 - + @@ -5867,7 +5868,7 @@ license:CC0 - + @@ -5885,7 +5886,7 @@ license:CC0 - + @@ -6351,7 +6352,7 @@ license:CC0 - + @@ -6369,7 +6370,7 @@ license:CC0 - + @@ -6623,7 +6624,7 @@ license:CC0 - + @@ -6821,7 +6822,7 @@ license:CC0 - + @@ -6871,7 +6872,7 @@ license:CC0 - + @@ -7875,7 +7876,7 @@ license:CC0 - + @@ -7933,7 +7934,8 @@ license:CC0 - + + @@ -8295,7 +8297,7 @@ license:CC0 - + @@ -9290,7 +9292,7 @@ license:CC0 - + @@ -10237,7 +10239,7 @@ license:CC0 - + @@ -10583,7 +10585,7 @@ license:CC0 - + @@ -10648,7 +10650,7 @@ license:CC0 - + @@ -11320,7 +11322,7 @@ license:CC0 - + @@ -11515,7 +11517,7 @@ license:CC0 - + @@ -11538,7 +11540,7 @@ license:CC0 - + @@ -11620,7 +11622,7 @@ license:CC0 - + @@ -11696,7 +11698,7 @@ license:CC0 - + @@ -11930,7 +11932,7 @@ license:CC0 - + @@ -11962,7 +11964,7 @@ license:CC0 - + @@ -12272,7 +12274,7 @@ license:CC0 - + @@ -12306,7 +12308,7 @@ license:CC0 - + @@ -12660,7 +12662,7 @@ license:CC0 - + @@ -12920,7 +12922,7 @@ license:CC0 - + @@ -13013,7 +13015,7 @@ license:CC0 - + @@ -13103,7 +13105,7 @@ license:CC0 - + @@ -13125,7 +13127,7 @@ license:CC0 - + @@ -13401,7 +13403,7 @@ license:CC0 - + @@ -14466,7 +14468,7 @@ license:CC0 - + @@ -14548,7 +14550,8 @@ license:CC0 - + + @@ -14625,7 +14628,7 @@ license:CC0 - + @@ -14746,7 +14749,7 @@ license:CC0 - + @@ -15014,7 +15017,8 @@ license:CC0 - + + @@ -15030,7 +15034,8 @@ license:CC0 - + + @@ -15200,7 +15205,7 @@ license:CC0 - + @@ -15335,7 +15340,7 @@ license:CC0 - + @@ -15349,7 +15354,7 @@ license:CC0 - + @@ -15369,7 +15374,7 @@ license:CC0 - + @@ -15415,7 +15420,7 @@ license:CC0 - + @@ -15834,7 +15839,7 @@ license:CC0 - + @@ -16415,7 +16420,7 @@ license:CC0 - + @@ -16429,7 +16434,7 @@ license:CC0 - + @@ -16443,7 +16448,7 @@ license:CC0 - + @@ -16847,7 +16852,7 @@ license:CC0 - + @@ -17146,7 +17151,7 @@ license:CC0 - + @@ -17224,7 +17229,7 @@ license:CC0 - + @@ -18174,7 +18179,7 @@ license:CC0 - + @@ -18230,7 +18235,7 @@ license:CC0 - + @@ -19375,7 +19380,7 @@ license:CC0 - + @@ -19392,7 +19397,7 @@ license:CC0 - + @@ -20761,7 +20766,7 @@ license:CC0 - + @@ -20922,7 +20927,7 @@ license:CC0 - + @@ -21147,7 +21152,7 @@ license:CC0 - + @@ -21235,7 +21240,7 @@ license:CC0 - + @@ -21257,7 +21262,7 @@ license:CC0 - + @@ -21327,7 +21332,7 @@ license:CC0 - + @@ -21397,7 +21402,7 @@ license:CC0 - + @@ -21897,7 +21902,7 @@ license:CC0 - + @@ -22178,7 +22183,7 @@ license:CC0 - + @@ -23343,7 +23348,8 @@ license:CC0 - + + @@ -23672,7 +23678,7 @@ license:CC0 - + @@ -23769,7 +23775,7 @@ license:CC0 - + @@ -24556,7 +24562,7 @@ license:CC0 - + @@ -24591,7 +24597,7 @@ license:CC0 - + @@ -25237,7 +25243,7 @@ license:CC0 - + @@ -25257,7 +25263,7 @@ license:CC0 - + @@ -25274,7 +25280,7 @@ license:CC0 - + @@ -25289,7 +25295,7 @@ license:CC0 - + @@ -25852,7 +25858,7 @@ license:CC0 - + @@ -25867,7 +25873,7 @@ license:CC0 - + @@ -25884,7 +25890,7 @@ license:CC0 - + @@ -25901,7 +25907,7 @@ license:CC0 - + @@ -25916,7 +25922,7 @@ license:CC0 - + @@ -25930,7 +25936,7 @@ license:CC0 - + @@ -26119,7 +26125,7 @@ license:CC0 - + @@ -26207,7 +26213,7 @@ license:CC0 - + @@ -26375,7 +26381,7 @@ license:CC0 - + @@ -26537,7 +26543,7 @@ license:CC0 - + @@ -27017,7 +27023,7 @@ license:CC0 - + @@ -28037,7 +28043,8 @@ patch the rom to 0x00 and 0x00....and at 0x0B3D also patch it to - + + Game Boy Camera (Europe, USA) 1998 Nintendo @@ -28058,7 +28065,8 @@ patch the rom to 0x00 and 0x00....and at 0x0B3D also patch it to - + + Pocket Camera (Japan, Rev 1) 1998 Nintendo @@ -28076,7 +28084,8 @@ patch the rom to 0x00 and 0x00....and at 0x0B3D also patch it to - + + Game Boy Camera Gold (USA) 19?? <unknown> diff --git a/hash/gbcolor.xml b/hash/gbcolor.xml index 547af781157..b461c2c6375 100644 --- a/hash/gbcolor.xml +++ b/hash/gbcolor.xml @@ -2467,7 +2467,8 @@ license:CC0 - + + @@ -3398,7 +3399,7 @@ license:CC0 - + Command Master (Japan) 2000 @@ -3413,7 +3414,7 @@ license:CC0 - + @@ -10006,7 +10007,7 @@ license:CC0 - + Kirby Tilt 'n' Tumble (USA) 2001 @@ -10019,7 +10020,7 @@ license:CC0 - + @@ -10321,7 +10322,7 @@ license:CC0 - + Korokoro Kirby (Japan) 2000 @@ -10336,7 +10337,7 @@ license:CC0 - + @@ -14634,7 +14635,7 @@ license:CC0 - + Net de Get - Minigame @ 100 (Japan) 2001 @@ -15712,7 +15713,7 @@ license:CC0 - + @@ -18507,7 +18508,7 @@ license:CC0 - + @@ -18538,7 +18539,7 @@ license:CC0 - + @@ -18568,7 +18569,7 @@ license:CC0 - + @@ -18598,7 +18599,7 @@ license:CC0 - + @@ -18619,7 +18620,7 @@ license:CC0 - + @@ -19867,7 +19868,7 @@ license:CC0 - + @@ -24791,7 +24792,6 @@ license:CC0 ATV Racing & Karate Joe (Europe) 200? Rocket Games - @@ -24808,7 +24808,6 @@ license:CC0 ATV Racing & Karate Joe (Europe, alt) 200? Rocket Games - @@ -24824,7 +24823,6 @@ license:CC0 ATV Racing (Europe) 2001 Rocket Games - @@ -24840,7 +24838,6 @@ license:CC0 Full Time Soccer & Hang Time Basketball (Europe) 200? Rocket Games - @@ -24856,7 +24853,6 @@ license:CC0 Full Time Soccer (Europe) 2000 Rocket Games - @@ -24871,7 +24867,6 @@ license:CC0 Hang Time Basketball (Europe) 200? Rocket Games - @@ -24888,7 +24883,6 @@ license:CC0 Pocket Smash Out & Race Time (Europe) 200? Rocket Games - @@ -24902,7 +24896,6 @@ license:CC0 Pocket Smash Out (Europe) 200? Rocket Games - @@ -24919,7 +24912,6 @@ license:CC0 Karate Joe (Europe) 200? Rocket Games - @@ -24933,7 +24925,6 @@ license:CC0 Painter (Europe) 200? Rocket Games - @@ -24946,7 +24937,6 @@ license:CC0 Race Time (Europe) 200? Rocket Games - @@ -24961,7 +24951,6 @@ license:CC0 Space Invasion & Karate Joe (Europe) 200? Rocket Games - @@ -24978,7 +24967,6 @@ license:CC0 Space Invasion & Painter (Europe) 200? Rocket Games - @@ -24995,7 +24983,6 @@ license:CC0 Space Invasion (Europe) 200? Rocket Games - @@ -25090,7 +25077,7 @@ license:CC0 - + Street Hero (Taiwan) 19?? Sachen @@ -25103,7 +25090,7 @@ license:CC0 - + Thunder Blast Man (Europe) 19?? Sachen @@ -25129,7 +25116,7 @@ license:CC0 - + 4 in 1 + 8 in 1 (World, 4B-001) 20?? Sachen @@ -25141,7 +25128,7 @@ license:CC0 - + 4 in 1 + 8 in 1 (World, 4B-002) 19?? Sachen @@ -25154,7 +25141,7 @@ license:CC0 - + 4 in 1 + 8 in 1 + 16 in 1 (World, 4B-005) 19?? Sachen @@ -25167,7 +25154,8 @@ license:CC0 - + + 4 in 1 + 8 in 1 (World, 4B-007) 19?? Sachen @@ -25180,7 +25168,8 @@ license:CC0 - + + Super 6 in 1 (Taiwan) 19?? Sachen @@ -25205,7 +25194,8 @@ license:CC0 - + + 31 in 1 Mighty Mix (Taiwan) 19?? <unknown> @@ -25217,7 +25207,8 @@ license:CC0 - + + 31-in-1 Mighty Mix (Australia) 19?? Sachen @@ -25947,7 +25938,7 @@ license:CC0 <unknown> - + @@ -26319,26 +26310,26 @@ license:CC0 - + Nǚwáng Gédòu 2000 (Taiwan) 200? Vast Fame - + - + Chāojí Gédòu 2001 Alpha (Taiwan) 200? Vast Fame - + @@ -26907,13 +26898,13 @@ license:CC0 - + Gédòu Jiàn Shén - Soul Falchion (Taiwan) 2002 V.Fame - + diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua index 847232ec003..077ae634d96 100644 --- a/scripts/src/bus.lua +++ b/scripts/src/bus.lua @@ -3684,19 +3684,45 @@ end --------------------------------------------------- -- ---@src/devices/bus/gameboy/gb_slot.h,BUSES["GAMEBOY"] = true +--@src/devices/bus/gameboy/slot.h,BUSES["GAMEBOY"] = true --------------------------------------------------- if (BUSES["GAMEBOY"]~=null) then files { + MAME_DIR .. "src/devices/bus/gameboy/camera.cpp", + MAME_DIR .. "src/devices/bus/gameboy/camera.h", + MAME_DIR .. "src/devices/bus/gameboy/cartbase.cpp", + MAME_DIR .. "src/devices/bus/gameboy/cartbase.h", + MAME_DIR .. "src/devices/bus/gameboy/cartbase.ipp", + MAME_DIR .. "src/devices/bus/gameboy/cartheader.h", MAME_DIR .. "src/devices/bus/gameboy/carts.cpp", MAME_DIR .. "src/devices/bus/gameboy/carts.h", + MAME_DIR .. "src/devices/bus/gameboy/gbslot.cpp", + MAME_DIR .. "src/devices/bus/gameboy/gbslot.h", + MAME_DIR .. "src/devices/bus/gameboy/gbxfile.cpp", + MAME_DIR .. "src/devices/bus/gameboy/gbxfile.h", + MAME_DIR .. "src/devices/bus/gameboy/huc1.cpp", + MAME_DIR .. "src/devices/bus/gameboy/huc1.h", + MAME_DIR .. "src/devices/bus/gameboy/huc3.cpp", + MAME_DIR .. "src/devices/bus/gameboy/huc3.h", MAME_DIR .. "src/devices/bus/gameboy/mbc.cpp", MAME_DIR .. "src/devices/bus/gameboy/mbc.h", - MAME_DIR .. "src/devices/bus/gameboy/gb_slot.cpp", - MAME_DIR .. "src/devices/bus/gameboy/gb_slot.h", + MAME_DIR .. "src/devices/bus/gameboy/mbc2.cpp", + MAME_DIR .. "src/devices/bus/gameboy/mbc2.h", + MAME_DIR .. "src/devices/bus/gameboy/mbc6.cpp", + MAME_DIR .. "src/devices/bus/gameboy/mbc6.h", + MAME_DIR .. "src/devices/bus/gameboy/mbc7.cpp", + MAME_DIR .. "src/devices/bus/gameboy/mbc7.h", + MAME_DIR .. "src/devices/bus/gameboy/mdslot.cpp", + MAME_DIR .. "src/devices/bus/gameboy/mdslot.h", + MAME_DIR .. "src/devices/bus/gameboy/mmm01.cpp", + MAME_DIR .. "src/devices/bus/gameboy/mmm01.h", MAME_DIR .. "src/devices/bus/gameboy/rom.cpp", MAME_DIR .. "src/devices/bus/gameboy/rom.h", + MAME_DIR .. "src/devices/bus/gameboy/slot.cpp", + MAME_DIR .. "src/devices/bus/gameboy/slot.h", + MAME_DIR .. "src/devices/bus/gameboy/tama5.cpp", + MAME_DIR .. "src/devices/bus/gameboy/tama5.h", } end diff --git a/src/devices/bus/gameboy/camera.cpp b/src/devices/bus/gameboy/camera.cpp new file mode 100644 index 00000000000..2faacd2d424 --- /dev/null +++ b/src/devices/bus/gameboy/camera.cpp @@ -0,0 +1,620 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Camera + + Major components: + * U1 MAC-GBD Game Boy bus interface + * U2 program ROM + * U3 128K*8 static RAM + * U4 backup power controller + * CR2025 coin cell + * Mitsubishi M64282FP 128*123 pixel CMOS image sensor and processor + + Static RAM is not accessible while image capture is in progress. Reads + will return 0x00 and writes will be ignored. Camera registers only respond + to A6-A0. Reading non-existent or write-only registers returns 0x00. + + Note that unlike most MBC chips, only writing to cartridge RAM can be + disabled. It is still possible to read cartridge RAM while writing is + disabled (provided image capture is not in progress). Another unusual + feature is that ROM page 0 is selectable (it isn't automatically remapped + to page 1), but page 1 is initially selected. + + 0x0000-3FFF R - Fixed ROM bank, always first page of ROM. + 0x4000-7FFF R - Selectable ROM bank, page 0-63 of ROM. + 0xA000-A1FF RW - Selectable static RAM page of camera registers. + + 0x0000-1FFF W - Enable (0x0A) or disable (not 0x0A) writing to + cartridge RAM. + 0x2000-3FFF W - Select ROM page mapped at 0x4000. + 0x4000-5FFF W - ---X---- Select RAM (clear) or camera registers (set). + ----XXXX Select RAM page. + + 0xA000 RW - -----XX- Select one-dimensional filter values (P, M). + R - -------X Capture in progress. + W - -------X Start capture. + 0xA001 W - X------- Exclusive edge enhancement mode (N). + W - -XX----- Vertical-horizontal edge operation mode (VH). + W - ---XXXXX Analog output gain (G). + 0xA002 W - XXXXXXXX Exposure most significant byte (C1). + 0xA003 W - XXXXXXXX Exposure least significant byte (C0). + 0xA004 W - X------- Edge enhancement (0) or extraction (1) (E3). + W - -XXX---- Edge enhancement ratio (E2-E0). + W - ----X--- Select inverted/non-inverted output (I). + W - -----XXX Output node bias voltage (V). + 0xA005 W - XX------ Zero point calibration (Z). + W - --XXXXXX Output reference voltage (O). + 0xA006-A035 W 4*4 matrix of three threshold values each. + + TODO: + * Emulate more M64282FP processing modes. + * It's supposedly possible to cancel a capture before it completes. + * What do filters do at the edges of the image area? + * Adjust levels when it sweeps the parameters on start. + + ***************************************************************************/ + +#include "emu.h" +#include "camera.h" + +#include "cartbase.ipp" + +#include "imagedev/picture.h" + +#include "bitmap.h" + +#include +#include +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class camera_device : public mbc_ram_device_base +{ +public: + static constexpr feature_type imperfect_features() { return feature::CAMERA; } + + camera_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual image_init_result load(std::string &message) override ATTR_COLD; + +protected: + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + +private: + static inline constexpr unsigned SENSOR_WIDTH{ 128U }; + static inline constexpr unsigned SENSOR_HEIGHT{ 123U }; + static inline constexpr unsigned OUTPUT_WIDTH{ 128U }; + static inline constexpr unsigned OUTPUT_HEIGHT{ 112U }; + static inline constexpr int EDGE_RATIO[8]{ 2U, 3U, 4U, 5U, 8U, 12U, 16U, 20U }; + static inline constexpr u8 P_MASK[4]{ 0x00U, 0x01U, 0x01U, 0x01U }; + static inline constexpr u8 M_MASK[4]{ 0x01U, 0x00U, 0x02U, 0x02U }; + + void enable_ram(u8 data); + void bank_switch_rom(u8 data); + void bank_switch_ram(u8 data); + u8 read_ram(offs_t offset); + void write_ram(offs_t offset, u8 data); + u8 read_camera(offs_t offset); + void write_camera(offs_t offset, u8 data); + + TIMER_CALLBACK_MEMBER(capture_complete); + + void start_capture() + { + // the controller shifts out the parameters and starts the capture + // we're over-simplifying the timings here + LOG("%s: Start capture\n", machine().describe_context()); + + // calculate total capture time + u32 cycles = 31'166; // time to start capture, read out image and apply thresholds + cycles += 5 * 256; // 256 cycles to set a register, registers 1, 2, 3, 7 and 0 always set + if (!m_n[0]) + cycles += 2 * 256; // registers 4 and 5 also set if N is not clear + cycles += 16 * m_c[0]; // exposure units are 16 microseconds + + // parameters are sent to the sensor serially - pretend it's instant + m_n[1] = m_n[0]; + m_vh[1] = m_vh[0]; + m_e[1] = m_e[0]; + m_z[1] = m_z[0]; + m_i[1] = m_i[0]; + m_c[1] = m_c[0]; + m_o[1] = m_o[0]; + m_v[1] = m_v[0]; + m_g[1] = m_g[0]; + if (!m_n[0]) + { + m_p = P_MASK[m_sel_pm]; + m_m = M_MASK[m_sel_pm]; + } + + // set timer for when capture will finish + m_busy = 1U; + m_timer_capture->adjust(attotime::from_ticks(4 * cycles, 4.194304_MHz_XTAL)); // FIXME: actually from incoming phi clock + } + + void acquire(s16 (&buffer)[SENSOR_HEIGHT][SENSOR_WIDTH]) + { + bitmap_argb32 const &source(m_picture->get_bitmap()); + if (source.valid()) + { + LOG("Point-sampling %d*%d source bitmap\n", source.width(), source.height()); + double const xstep(source.width() / double(SENSOR_WIDTH)); + double const ystep(source.height() / double(SENSOR_HEIGHT)); + for (unsigned y = 0U; SENSOR_HEIGHT > y; ++y) + { + u32 const *const srcline(&source.pix(s32((y * ystep) + 0.5))); + s16 *const dstline(buffer[y]); + for (unsigned x = 0U; SENSOR_WIDTH > x; ++x) + { + // extract luminance - output ranges from 0 to 31875 + rgb_t const colour(srcline[s32((x * xstep) + 0.5)]); + u32 const mono((u32(299) * colour.r() + u32(587) * colour.g() + u32(114) * colour.b()) >> 3); + + // starts with C = 0x1000 (65.536 ms) before auto exposure adjustment + // convert to 10-bit signed for processing + s16 const exposure(u16(std::min((mono * m_c[1]) / (u32(125) << 10), 0x03ff))); + dstline[x] = m_i[1] ? (511 - exposure) : (exposure - 512); + } + } + } + else + { + LOG("No source bitmap - filling sensor bitmap with pattern\n"); + for (unsigned y = 0U; SENSOR_HEIGHT > y; ++y) + { + s16 *const dstline(buffer[y]); + for (unsigned x = 0U; SENSOR_WIDTH > x; ++x) + { + // values chosen to show dithering effects with default brightness/contrast + s16 mono = 0; + switch (((x >> 3) + (y >> 3)) & 0x03) + { + case 1: mono = 0x0240; break; + case 2: mono = 0x0340; break; + case 3: mono = 0x03ff; break; + } + dstline[x] = m_i[1] ? (511 - mono) : (mono - 512); + } + } + } + } + + void apply_thresholds(s16 const (&buffer)[SENSOR_HEIGHT][SENSOR_WIDTH]) + { + // always stored at offset 0x0100 in RAM page 0 (appears at 0xa100) + u8 const bank(bank_ram()); + set_bank_ram(0); + u8 *dst(bank_ram_base() + 0x100); + set_bank_ram(bank); + + // convert row-major chunky bitmap to 8*8 planar tiles + for (unsigned i = 0U; ((SENSOR_WIDTH * SENSOR_HEIGHT) / 8) > i; ++i, dst += 2) + { + unsigned const y(((i >> 4) & 0x78) | (i & 7)); + unsigned const x(i & 0x78); + auto const &threshline(m_threshold[y & 0x03]); + s16 const *src(&buffer[y][x]); + dst[0] = 0U; + dst[1] = 0U; + + // extract the columns of this tile row + for (unsigned col = 0U; 8U > col; ++col) + { + u8 const pixel(u16(src[col] + 512) >> 2); + auto const &thresh(threshline[col & 0x03]); + u8 const quantised( + (thresh[0] > pixel) ? 3U : + (thresh[1] > pixel) ? 2U : + (thresh[2] > pixel) ? 1U : + 0U); + if (BIT(quantised, 0)) + dst[0] |= 1U << (7 - col); + if (BIT(quantised, 1)) + dst[1] |= 1U << (7 - col); + } + } + } + + template + static void scan_bitmap(T &&op) + { + // effects scan the sensor from the bottom up + for (int y = 0; SENSOR_HEIGHT < y; ++y) + { + for (int x = 0; SENSOR_WIDTH < x; ++x) + op(x, SENSOR_HEIGHT - y); + } + } + + static char const *edge_operation_text(u8 value) + { + static char const *const NAMES[4]{ "none", "horizontal", "vertical", "2D" }; + return NAMES[value]; + } + + static char const *zero_point_text(u8 value) + { + static char const *const NAMES[4]{ "none", "positive signal", "negative signal", "invalid" }; + return NAMES[value]; + } + + static double output_ref_volts(u8 value) + { + return BIT(value, 0, 5) / double(BIT(value, 5) ? 0x1f : -0x1f); + } + + static double output_node_bias_volts(u8 value) + { + return 0.5 * value; + } + + static double output_gain_db(u8 value) + { + return (((14 * 2) + (BIT(value, 0, 4) * 3)) + (BIT(value, 4) * 6 * 2)) * 0.5; + } + + required_device m_picture; + memory_view m_view_cam; + emu_timer *m_timer_capture; + + u8 m_busy; + u8 m_ram_writable; + u8 m_threshold[4][4][3]; + u8 m_sel_pm; + + u8 m_n[2]; + u8 m_vh[2]; + u8 m_e[2]; + u8 m_z[2]; + u8 m_i[2]; + u16 m_c[2]; + u8 m_o[2]; + u8 m_v[2]; + u8 m_g[2]; + u8 m_p; + u8 m_m; +}; + + +camera_device::camera_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + mbc_ram_device_base(mconfig, GB_ROM_CAMERA, tag, owner, clock), + m_picture(*this, "picture"), + m_view_cam(*this, "cam"), + m_timer_capture(nullptr), + m_busy(0U), + m_ram_writable(0U), + m_sel_pm(0U), + m_n{ 0U, 0U }, + m_vh{ 0U, 0U }, + m_e{ 0U, 0U }, + m_z{ 0U, 0U }, + m_i{ 0U, 0U }, + m_c{ 0U, 0U }, + m_o{ 0U, 0U }, + m_v{ 0U, 0U }, + m_g{ 0U, 0U }, + m_p(0U), + m_m(0U) +{ +} + + +image_init_result camera_device::load(std::string &message) +{ + // set up ROM and RAM + set_bank_bits_rom(6); + set_bank_bits_ram(4); + if (!check_rom(message) || !configure_bank_ram(message)) + return image_init_result::FAIL; + install_rom(); + + // install memory map control handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(camera_device::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(camera_device::bank_switch_rom))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(camera_device::bank_switch_ram))); + + // put RAM through trampolines so it can be locked when necessary + cart_space()->install_readwrite_handler( + 0xa000, 0xbfff, + read8sm_delegate(*this, FUNC(camera_device::read_ram)), + write8sm_delegate(*this, FUNC(camera_device::write_ram))); + + // camera control overlays cartridge RAM + cart_space()->install_view( + 0xa000, 0xbfff, + m_view_cam); + m_view_cam[0].install_read_handler( + 0xa000, 0xa07f, 0x0000, 0x1f80, 0x0000, + read8sm_delegate(*this, FUNC(camera_device::read_camera))); + m_view_cam[0].install_write_handler( + 0xa000, 0xa005, 0x0000, 0x1f80, 0x0000, + write8sm_delegate(*this, FUNC(camera_device::write_camera))); + m_view_cam[0].install_writeonly( + 0xa006, 0xa035, 0x1f80, + &m_threshold[0][0][0]); + + // all good + return image_init_result::PASS; +} + + +void camera_device::device_add_mconfig(machine_config &config) +{ + IMAGE_PICTURE(config, m_picture); +} + + +void camera_device::device_start() +{ + mbc_ram_device_base::device_start(); + + m_timer_capture = timer_alloc(FUNC(camera_device::capture_complete), this); + + for (auto &row : m_threshold) + { + for (auto &col : row) + std::fill(std::begin(col), std::end(col), 0U); + } + + m_n[0] = 0U; + m_vh[0] = 0U; + m_e[0] = 0U; + m_z[0] = 0U; + m_i[0] = 0U; + m_c[0] = 0U; + m_o[0] = 0U; + m_v[0] = 0U; + m_g[0] = 0U; + m_p = 0U; + m_m = 0U; + + save_item(NAME(m_busy)); + save_item(NAME(m_ram_writable)); + save_item(NAME(m_threshold)); + save_item(NAME(m_sel_pm)); + save_item(NAME(m_n)); + save_item(NAME(m_vh)); + save_item(NAME(m_e)); + save_item(NAME(m_z)); + save_item(NAME(m_i)); + save_item(NAME(m_c)); + save_item(NAME(m_o)); + save_item(NAME(m_v)); + save_item(NAME(m_g)); + save_item(NAME(m_p)); + save_item(NAME(m_m)); +} + + +void camera_device::device_reset() +{ + mbc_ram_device_base::device_reset(); + + m_view_cam.disable(); + m_timer_capture->reset(); + m_busy = 0U; + m_ram_writable = 0U; + m_sel_pm = 0U; + + set_bank_rom(1); + set_bank_ram(0); +} + + +void camera_device::enable_ram(u8 data) +{ + m_ram_writable = (0x0a == (data & 0x0f)) ? 1U : 0U; + LOG("Cartridge RAM write %s\n", m_ram_writable ? "enabled" : "disabled"); +} + + +void camera_device::bank_switch_rom(u8 data) +{ + set_bank_rom(data & 0x3f); +} + + +void camera_device::bank_switch_ram(u8 data) +{ + set_bank_ram(data & 0x0f); + LOG("%s selected\n", BIT(data, 4) ? "Camera control" : "Cartridge RAM"); + if (BIT(data, 4)) + m_view_cam.select(0); + else + m_view_cam.disable(); +} + + +u8 camera_device::read_ram(offs_t offset) +{ + return !m_busy ? bank_ram_base()[offset] : 0x00; +} + + +void camera_device::write_ram(offs_t offset, u8 data) +{ + if (!m_busy && m_ram_writable) + bank_ram_base()[offset] = data; +} + + +u8 camera_device::read_camera(offs_t offset) +{ + switch (offset) + { + case 0x00: + return (m_sel_pm << 1) | m_busy; + default: + return 0x00; + } +} + + +void camera_device::write_camera(offs_t offset, u8 data) +{ + switch (offset) + { + case 0x0: + m_sel_pm = BIT(data, 1, 2); + LOG( + "%s: Set up plus mask = 0x%02X, minus mask = 0x%02X\n", + machine().describe_context(), + P_MASK[m_sel_pm], + M_MASK[m_sel_pm]); + if (BIT(data, 0)) + { + if (!m_busy) + start_capture(); + else + logerror("%s: Attempt to start capture while busy\n", machine().describe_context()); + } + break; + case 0x1: + m_n[0] = BIT(data, 7); + m_vh[0] = BIT(data, 5, 2); + m_g[0] = BIT(data, 0, 5); + LOG( + "%s: Set up exclusive edge enhancement %s, edge operation: %s, gain %.1fdB\n", + machine().describe_context(), + m_n[0] ? "on" : "off", + edge_operation_text(m_vh[0]), + output_gain_db(m_g[0])); + break; + case 0x2: + m_c[0] = (m_c[0] & 0x00ff) | (u16(data) << 8); + LOG("%s: Set up exposure = %u microseconds\n", machine().describe_context(), m_c[0] * 16); + break; + case 0x3: + m_c[0] = (m_c[0] & 0xff00) | data; + LOG("%s: Set up exposure = %u microseconds\n", machine().describe_context(), m_c[0] * 16); + break; + case 0x4: + m_e[0] = BIT(data, 4, 4); + m_i[0] = BIT(data, 3); + m_v[0] = BIT(data, 0, 3); + LOG( + "%s: Set up edge %s ratio %d%%, %sinverted output, output node bias = %.1fV\n", + machine().describe_context(), + BIT(m_e[0], 3) ? "extraction" : "enhancement", + EDGE_RATIO[BIT(m_e[0], 0, 3)] * 25, + m_i[0] ? "" : "non-", + output_node_bias_volts(m_v[0])); + break; + case 0x5: + m_z[0] = BIT(data, 6, 2); + m_o[0] = BIT(data, 0, 6); + LOG( + "%s: Set up zero point calibration: %s, output reference voltage: %.2fV\n", + machine().describe_context(), + zero_point_text(m_z[0]), + output_ref_volts(m_o[0])); + break; + } +} + + +TIMER_CALLBACK_MEMBER(camera_device::capture_complete) +{ + // this really takes time, but we'll pretend it happens all at once + LOG("Capture complete\n"); + s16 raw[SENSOR_HEIGHT][SENSOR_WIDTH]; + acquire(raw); + + // apply processing + if (m_n[1]) + { + if (m_vh[1]) + { + int const ratio(EDGE_RATIO[BIT(m_e[1], 0, 3)]); + switch (m_vh[1]) + { + case 1U: + LOG("H-Edge %s\n", BIT(m_e[1], 3) ? "Extraction" : "Enhancement"); + scan_bitmap( + [this, &raw, ratio] (int x, int y) + { + s16 const mw(raw[y][x]); + s16 const p(raw[y][std::min(x + 1, SENSOR_WIDTH - 1)]); + s16 const me(raw[y][std::min(x + 2, SENSOR_WIDTH - 1)]); + raw[y][x] = ((2 * p) - mw - me) * ratio; + if (!BIT(m_e[1], 3)) + raw[y][x] += p * 4; + raw[y][x] = std::clamp(raw[y][x] / 4, -512, 511); + }); + break; + case 2U: + LOG("V-Edge %s\n", BIT(m_e[1], 3) ? "Extraction" : "Enhancement"); + scan_bitmap( + [this, &raw, ratio] (int x, int y) + { + s16 const ms(raw[y][x]); + s16 const p(raw[std::max(y - 1, 0)][x]); + s16 const mn(raw[std::max(y - 2, 0)][x]); + raw[y][x] = ((2 * p) - mn - ms) * ratio; + if (!BIT(m_e[1], 3)) + raw[y][x] += p * 4; + raw[y][x] = std::clamp(raw[y][x] / 4, -512, 511); + }); + break; + case 3U: + LOG("2D-Edge %s\n", BIT(m_e[1], 3) ? "Extraction" : "Enhancement"); + scan_bitmap( + [this, &raw, ratio] (int x, int y) + { + s16 const ms(raw[y][std::min(x + 1, SENSOR_WIDTH - 1)]); + s16 const mw(raw[std::max(y - 1, 0)][x]); + s16 const p(raw[std::max(y - 1, 0)][std::min(x + 1, SENSOR_WIDTH - 1)]); + s16 const me(raw[std::max(y - 1, 0)][std::min(x + 2, SENSOR_WIDTH - 1)]); + s16 const mn(raw[std::max(y - 2, 0)][std::min(x + 1, SENSOR_WIDTH - 1)]); + raw[y][x] = ((4 * p) - mn - ms - me - mw) * ratio; + if (!BIT(m_e[1], 3)) + raw[y][x] += p * 4; + raw[y][x] = std::clamp(raw[y][x] / 4, -512, 511); + }); + break; + } + } + else + { + LOG("N set for exclusive edge mode with VH set for no operation\n"); + } + } + else + { + logerror("Unsupported processing mode\n"); + } + + // quantise and convert to tiles in cartridge RAM, and clear busy flag + apply_thresholds(raw); + m_busy = 0U; +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_CAMERA, device_gb_cart_interface, bus::gameboy::camera_device, "gb_rom_camera", "Game Boy Camera Cartridge") diff --git a/src/devices/bus/gameboy/camera.h b/src/devices/bus/gameboy/camera.h new file mode 100644 index 00000000000..30ffac8df08 --- /dev/null +++ b/src/devices/bus/gameboy/camera.h @@ -0,0 +1,18 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Camera cartridge + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_CAMERA_H +#define MAME_BUS_GAMEBOY_CAMERA_H + +#pragma once + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_CAMERA, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_CAMERA_H diff --git a/src/devices/bus/gameboy/cartbase.cpp b/src/devices/bus/gameboy/cartbase.cpp new file mode 100644 index 00000000000..8e6796603a3 --- /dev/null +++ b/src/devices/bus/gameboy/cartbase.cpp @@ -0,0 +1,435 @@ + +// copyright-holders:Vas Crabb +/*************************************************************************** + + Shared Game Boy cartridge helpers + + ***************************************************************************/ + +#include "emu.h" +#include "cartbase.h" + +#include "bus/generic/slot.h" + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + + +namespace bus::gameboy { + +//************************************************************************** +// 16 KiB fixed at 0x0000, 16 KiB switchable at 0x4000 +//************************************************************************** + +mbc_device_base::mbc_device_base( + machine_config const &mconfig, + device_type type, + char const *tag, + device_t *owner, + u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_gb_cart_interface(mconfig, *this), + m_bank_rom(*this, "rom"), + m_bank_bits_rom(0U), + m_bank_mask_rom(0U) +{ +} + + +void mbc_device_base::device_start() +{ +} + + +bool mbc_device_base::check_rom(std::string &message) +{ + memory_region *const romregion(cart_rom_region()); + if (!romregion || !romregion->bytes()) + { + // setting default bank on reset causes a fatal error when no banks are configured + message = "No ROM data found"; + return false; + } + + auto const rombytes(romregion->bytes()); + if ((rombytes & (PAGE_ROM_SIZE - 1)) || ((u32(PAGE_ROM_SIZE) << m_bank_bits_rom) < rombytes)) + { + message = util::string_format( + "Unsupported cartridge ROM size (must be a multiple of 16 KiB and no larger than %u pages)", + 1U << m_bank_bits_rom); + return false; + } + + return true; +} + + +void mbc_device_base::install_rom() +{ + configure_bank_rom(); + + logerror("Installing fixed ROM at 0x0000-0x3FFF and banked ROM at 0x4000-0x7FFF\n"); + cart_space()->install_rom(0x0000, 0x3fff, cart_rom_region()->base()); + cart_space()->install_read_bank(0x4000, 0x7fff, m_bank_rom); +} + + +void mbc_device_base::configure_bank_rom() +{ + memory_region *const romregion(cart_rom_region()); + m_bank_mask_rom = device_generic_cart_interface::map_non_power_of_two( + unsigned(romregion->bytes() / PAGE_ROM_SIZE), + [this, base = &romregion->as_u8()] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%06X-0x%06X in bank entry %u\n", + page * PAGE_ROM_SIZE, + (page * PAGE_ROM_SIZE) + (PAGE_ROM_SIZE - 1), + entry); + m_bank_rom->configure_entry(entry, &base[page * PAGE_ROM_SIZE]); + }); +} + + +void mbc_device_base::set_bank_rom(u16 entry) +{ + entry &= m_bank_mask_rom; + LOG( + "%s: Set ROM bank = 0x%06X\n", + machine().describe_context(), + entry * PAGE_ROM_SIZE); + m_bank_rom->set_entry(entry); +} + + + +//************************************************************************** +// 16 KiB switchable coarse at 0x0000, 16 KiB switchable fine at 0x4000 +//************************************************************************** + +mbc_dual_device_base::mbc_dual_device_base( + machine_config const &mconfig, + device_type type, + char const *tag, + device_t *owner, + u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_gb_cart_interface(mconfig, *this), + m_bank_rom(*this, { "low", "high" }), + m_bank_bits_rom{ 0U, 0U }, + m_bank_mask_rom{ 0U, 0U }, + m_bank_sel_rom{ 0U, 0U }, + m_bank_rom_coarse_mask{ 0xffffU, 0xffffU } +{ +} + + +void mbc_dual_device_base::device_start() +{ + save_item(NAME(m_bank_sel_rom)); + save_item(NAME(m_bank_rom_coarse_mask)); +} + + +bool mbc_dual_device_base::check_rom(std::string &message) +{ + memory_region *const romregion(cart_rom_region()); + if (!romregion || !romregion->bytes()) + { + // setting default bank on reset causes a fatal error when no banks are configured + message = "No ROM data found"; + return false; + } + + auto const rombytes(romregion->bytes()); + auto const pages(u32(1) << (m_bank_bits_rom[0] + m_bank_bits_rom[1])); + if ((rombytes & (PAGE_ROM_SIZE - 1)) || ((PAGE_ROM_SIZE * pages) < rombytes)) + { + message = util::string_format( + "Unsupported cartridge ROM size (must be a multiple of 16 KiB and no larger than %u pages)", + pages); + return false; + } + + return true; +} + + +void mbc_dual_device_base::install_rom() +{ + configure_bank_rom(); + + logerror("Installing banked ROM at 0x0000-0x3FFF and 0x4000-0x7FFF\n"); + cart_space()->install_read_bank(0x0000, 0x3fff, m_bank_rom[0]); + cart_space()->install_read_bank(0x4000, 0x7fff, m_bank_rom[1]); +} + + +void mbc_dual_device_base::configure_bank_rom() +{ + memory_region *const romregion(cart_rom_region()); + auto const rombytes(romregion->bytes()); + u8 *const rombase(&romregion->as_u8()); + auto const multiplier(1U << m_bank_bits_rom[1]); + + m_bank_mask_rom[0] = device_generic_cart_interface::map_non_power_of_two( + unsigned(((rombytes / PAGE_ROM_SIZE) + multiplier - 1) / multiplier), + [this, rombase, multiplier] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%06X-0x%06X in low bank entry %u\n", + page * PAGE_ROM_SIZE * multiplier, + (page * PAGE_ROM_SIZE * multiplier) + (PAGE_ROM_SIZE - 1), + entry); + m_bank_rom[0]->configure_entry(entry, &rombase[page * PAGE_ROM_SIZE * multiplier]); + }); + + m_bank_mask_rom[1] = device_generic_cart_interface::map_non_power_of_two( + unsigned(rombytes / PAGE_ROM_SIZE), + [this, rombase] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%06X-0x%06X in high bank entry %u\n", + page * PAGE_ROM_SIZE, + (page * PAGE_ROM_SIZE) + (PAGE_ROM_SIZE - 1), + entry); + m_bank_rom[1]->configure_entry(entry, &rombase[page * PAGE_ROM_SIZE]); + }); +} + + +void mbc_dual_device_base::set_bank_rom_coarse(u16 entry) +{ + m_bank_sel_rom[0] = entry; + u16 const lo(bank_rom_entry_low()); + u16 const hi(bank_rom_entry_high()); + LOG( + "%s: Set ROM banks low = 0x%06X, high = 0x%06X\n", + machine().describe_context(), + (u32(lo) * PAGE_ROM_SIZE) << m_bank_bits_rom[1], + u32(hi) * PAGE_ROM_SIZE); + m_bank_rom[0]->set_entry(lo); + m_bank_rom[1]->set_entry(hi); +} + + +void mbc_dual_device_base::set_bank_rom_fine(u16 entry) +{ + m_bank_sel_rom[1] = entry; + u16 const hi(bank_rom_entry_high()); + LOG( + "%s: Set ROM bank high = 0x%06X\n", + machine().describe_context(), + u32(hi) * PAGE_ROM_SIZE); + m_bank_rom[1]->set_entry(hi); +} + + +void mbc_dual_device_base::set_bank_rom_low_coarse_mask(u16 mask) +{ + m_bank_rom_coarse_mask[0] = mask; + u16 const lo(bank_rom_entry_low()); + LOG( + "%s: Set ROM bank low = 0x%06X\n", + machine().describe_context(), + (u32(lo) * PAGE_ROM_SIZE) << m_bank_bits_rom[1]); + m_bank_rom[0]->set_entry(lo); +} + + +void mbc_dual_device_base::set_bank_rom_high_coarse_mask(u16 mask) +{ + m_bank_rom_coarse_mask[1] = mask; + u16 const hi(bank_rom_entry_high()); + LOG( + "%s: Set ROM bank high = 0x%06X\n", + machine().describe_context(), + u32(hi) * PAGE_ROM_SIZE); + m_bank_rom[1]->set_entry(hi); +} + + + +//************************************************************************** +// 16 KiB switchable at 0x0000, 16 KiB switchable at 0x4000 +//************************************************************************** + +mbc_dual_uniform_device_base::mbc_dual_uniform_device_base( + machine_config const &mconfig, + device_type type, + char const *tag, + device_t *owner, + u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_gb_cart_interface(mconfig, *this), + m_bank_rom(*this, { "low", "high" }), + m_bank_bits_rom(0U), + m_bank_mask_rom(0U) +{ +} + + +void mbc_dual_uniform_device_base::device_start() +{ +} + + +bool mbc_dual_uniform_device_base::check_rom(std::string &message) +{ + memory_region *const romregion(cart_rom_region()); + if (!romregion || !romregion->bytes()) + { + // setting default bank on reset causes a fatal error when no banks are configured + message = "No ROM data found"; + return false; + } + + auto const rombytes(romregion->bytes()); + if ((rombytes & (PAGE_ROM_SIZE - 1)) || ((u32(PAGE_ROM_SIZE) << m_bank_bits_rom) < rombytes)) + { + message = util::string_format( + "Unsupported cartridge ROM size (must be a multiple of 16 KiB and no larger than %u pages)", + 1U << m_bank_bits_rom); + return false; + } + + return true; +} + + +void mbc_dual_uniform_device_base::install_rom() +{ + configure_bank_rom(); + + logerror("Installing banked ROM at 0x0000-0x3FFF and 0x4000-0x7FFF\n"); + cart_space()->install_read_bank(0x0000, 0x3fff, m_bank_rom[0]); + cart_space()->install_read_bank(0x4000, 0x7fff, m_bank_rom[1]); +} + + +void mbc_dual_uniform_device_base::configure_bank_rom() +{ + memory_region *const romregion(cart_rom_region()); + m_bank_mask_rom = device_generic_cart_interface::map_non_power_of_two( + unsigned(romregion->bytes() / PAGE_ROM_SIZE), + [this, base = &romregion->as_u8()] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%06X-0x%06X in bank entry %u\n", + page * PAGE_ROM_SIZE, + (page * PAGE_ROM_SIZE) + (PAGE_ROM_SIZE - 1), + entry); + m_bank_rom[0]->configure_entry(entry, &base[page * PAGE_ROM_SIZE]); + m_bank_rom[1]->configure_entry(entry, &base[page * PAGE_ROM_SIZE]); + }); +} + + +void mbc_dual_uniform_device_base::set_bank_rom_low(u16 entry) +{ + entry &= m_bank_mask_rom; + LOG( + "%s: Set ROM bank low = 0x%06X\n", + machine().describe_context(), + entry * PAGE_ROM_SIZE); + m_bank_rom[0]->set_entry(entry); +} + + +void mbc_dual_uniform_device_base::set_bank_rom_high(u16 entry) +{ + entry &= m_bank_mask_rom; + LOG( + "%s: Set ROM bank high = 0x%06X\n", + machine().describe_context(), + entry * PAGE_ROM_SIZE); + m_bank_rom[1]->set_entry(entry); +} + + + +//************************************************************************** +// 32 KiB switchable at 0x0000 +//************************************************************************** + +banked_32k_device_base::banked_32k_device_base( + machine_config const &mconfig, + device_type type, + char const *tag, + device_t *owner, + u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_gb_cart_interface(mconfig, *this), + m_bank_rom(*this, "rom"), + m_bank_bits_rom(0U), + m_bank_mask_rom(0U) +{ +} + + +void banked_32k_device_base::device_start() +{ +} + +bool banked_32k_device_base::check_rom(std::string &message) +{ + memory_region *const romregion(cart_rom_region()); + if (!romregion || !romregion->bytes()) + { + // setting default bank on reset causes a fatal error when no banks are configured + message = "No ROM data found"; + return false; + } + + auto const rombytes(romregion->bytes()); + if ((rombytes & (PAGE_ROM_SIZE - 1)) || ((u32(PAGE_ROM_SIZE) << m_bank_bits_rom) < rombytes)) + { + message = util::string_format( + "Unsupported cartridge ROM size (must be a multiple of 32 KiB and no larger than %u pages)", + 1U << m_bank_bits_rom); + return false; + } + + return true; +} + + +void banked_32k_device_base::install_rom() +{ + configure_bank_rom(); + + logerror("Installing banked ROM at 0x0000-0x7FFF\n"); + cart_space()->install_read_bank(0x0000, 0x7fff, m_bank_rom); +} + + +void banked_32k_device_base::configure_bank_rom() +{ + memory_region *const romregion(cart_rom_region()); + m_bank_mask_rom = device_generic_cart_interface::map_non_power_of_two( + unsigned(romregion->bytes() / PAGE_ROM_SIZE), + [this, base = &romregion->as_u8()] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%06X-0x%06X in bank entry %u\n", + page * PAGE_ROM_SIZE, + (page * PAGE_ROM_SIZE) + (PAGE_ROM_SIZE - 1), + entry); + m_bank_rom->configure_entry(entry, &base[page * PAGE_ROM_SIZE]); + }); +} + + +void banked_32k_device_base::set_bank_rom(u16 entry) +{ + entry &= m_bank_mask_rom; + LOG( + "%s: Set ROM bank = 0x%06X\n", + machine().describe_context(), + entry * PAGE_ROM_SIZE); + m_bank_rom->set_entry(entry); +} + +} // namespace bus::gameboy diff --git a/src/devices/bus/gameboy/cartbase.h b/src/devices/bus/gameboy/cartbase.h new file mode 100644 index 00000000000..760d2911a02 --- /dev/null +++ b/src/devices/bus/gameboy/cartbase.h @@ -0,0 +1,244 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Shared Game Boy cartridge helpers + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_CARTBASE_H +#define MAME_BUS_GAMEBOY_CARTBASE_H + +#pragma once + +#include "slot.h" + +#include + + +namespace bus::gameboy { + +//************************************************************************** +// 16 KiB fixed at 0x0000, 16 KiB switchable at 0x4000 +//************************************************************************** + +class mbc_device_base : public device_t, public device_gb_cart_interface +{ +protected: + mbc_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + virtual void device_start() override ATTR_COLD; + + void set_bank_bits_rom(unsigned bits) + { + m_bank_bits_rom = bits; + } + + bool check_rom(std::string &message) ATTR_COLD; + void install_rom() ATTR_COLD; + void configure_bank_rom() ATTR_COLD; + + void set_bank_rom(u16 entry); + +private: + static inline constexpr unsigned PAGE_ROM_SIZE = 0x4000; + + memory_bank_creator m_bank_rom; + + unsigned m_bank_bits_rom; + u16 m_bank_mask_rom; +}; + + + +//************************************************************************** +// 16 KiB switchable coarse at 0x0000, 16 KiB switchable fine at 0x4000 +//************************************************************************** + +class mbc_dual_device_base : public device_t, public device_gb_cart_interface +{ +protected: + mbc_dual_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + virtual void device_start() override ATTR_COLD; + + void set_bank_bits_rom(unsigned coarse, unsigned fine) + { + m_bank_bits_rom[0] = coarse; + m_bank_bits_rom[1] = fine; + } + + bool check_rom(std::string &message) ATTR_COLD; + void install_rom() ATTR_COLD; + void configure_bank_rom() ATTR_COLD; + + void set_bank_rom_coarse(u16 entry); + void set_bank_rom_fine(u16 entry); + void set_bank_rom_low_coarse_mask(u16 mask); + void set_bank_rom_high_coarse_mask(u16 mask); + + u16 bank_rom_coarse() const { return m_bank_sel_rom[0]; } + u16 bank_rom_fine() const { return m_bank_sel_rom[1]; } + u8 const *bank_rom_low_base() const { return reinterpret_cast(m_bank_rom[0]->base()); } + u8 const *bank_rom_high_base() const { return reinterpret_cast(m_bank_rom[1]->base()); } + +private: + static inline constexpr unsigned PAGE_ROM_SIZE = 0x4000; + + u16 bank_rom_entry_low() const noexcept + { + return m_bank_sel_rom[0] & m_bank_rom_coarse_mask[0] & m_bank_mask_rom[0]; + } + + u16 bank_rom_entry_high() const noexcept + { + u16 const coarse(m_bank_sel_rom[0] & m_bank_rom_coarse_mask[1]); + return (m_bank_sel_rom[1] | (coarse << m_bank_bits_rom[1])) & m_bank_mask_rom[1]; + } + + memory_bank_array_creator<2> m_bank_rom; + + unsigned m_bank_bits_rom[2]; + u16 m_bank_mask_rom[2]; + u16 m_bank_sel_rom[2]; + u16 m_bank_rom_coarse_mask[2]; +}; + + + +//************************************************************************** +// 16 KiB switchable at 0x0000, 16 KiB switchable at 0x4000 +//************************************************************************** + +class mbc_dual_uniform_device_base : public device_t, public device_gb_cart_interface +{ +protected: + mbc_dual_uniform_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + virtual void device_start() override ATTR_COLD; + + void set_bank_bits_rom(unsigned bits) + { + m_bank_bits_rom = bits; + } + + bool check_rom(std::string &message) ATTR_COLD; + void install_rom() ATTR_COLD; + void configure_bank_rom() ATTR_COLD; + + void set_bank_rom_low(u16 entry); + void set_bank_rom_high(u16 entry); + + u8 const *bank_rom_low_base() const { return reinterpret_cast(m_bank_rom[0]->base()); } + u8 const *bank_rom_high_base() const { return reinterpret_cast(m_bank_rom[1]->base()); } + +private: + static inline constexpr unsigned PAGE_ROM_SIZE = 0x4000; + + memory_bank_array_creator<2> m_bank_rom; + + unsigned m_bank_bits_rom; + u16 m_bank_mask_rom; +}; + + +//************************************************************************** +// 32 KiB switchable at 0x0000 +//************************************************************************** + +class banked_32k_device_base : public device_t, public device_gb_cart_interface +{ +protected: + banked_32k_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + virtual void device_start() override ATTR_COLD; + + void set_bank_bits_rom(unsigned bits) { m_bank_bits_rom = bits; } + + bool check_rom(std::string &message) ATTR_COLD; + void install_rom() ATTR_COLD; + void configure_bank_rom() ATTR_COLD; + + void set_bank_rom(u16 entry); + +private: + static inline constexpr unsigned PAGE_ROM_SIZE = 0x8000; + + memory_bank_creator m_bank_rom; + + unsigned m_bank_bits_rom; + u16 m_bank_mask_rom; +}; + + + +//************************************************************************** +// 8 KiB flat RAM helper +//************************************************************************** + +template +class flat_ram_device_base : public Base +{ +public: + virtual void unload() override ATTR_COLD; + +protected: + using Base::Base; + + bool check_ram(std::string &message) ATTR_COLD; + void install_ram() ATTR_COLD; +}; + + + +//************************************************************************** +// 8 KiB switchable RAM helper +//************************************************************************** + +template +class mbc_ram_device_base : public Base +{ +public: + virtual void unload() override ATTR_COLD; + +protected: + mbc_ram_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + virtual void device_start() override ATTR_COLD; + + void set_bank_bits_ram(unsigned bits) + { + m_bank_bits_ram = bits; + } + + bool check_ram(std::string &message) ATTR_COLD; + void install_ram(address_space_installer &space) ATTR_COLD; + void install_ram(address_space_installer &rospace, address_space_installer &rwspace) ATTR_COLD; + bool configure_bank_ram(std::string &message) ATTR_COLD; + + void set_bank_ram(u8 entry); + void set_bank_ram_mask(u8 mask); + + u8 bank_ram() const { return m_bank_sel_ram; } + u8 *bank_ram_base() const { return reinterpret_cast(m_bank_ram->base()); } + +private: + static inline constexpr unsigned PAGE_RAM_SIZE = 0x2000; + + void install_ram(address_space_installer *rospace, address_space_installer *rwspace) ATTR_COLD; + + u8 bank_ram_entry() const noexcept + { + return m_bank_sel_ram & m_bank_ram_mask & m_bank_mask_ram; + } + + memory_bank_creator m_bank_ram; + + unsigned m_bank_bits_ram; + u8 m_bank_mask_ram; + u8 m_bank_sel_ram; + u8 m_bank_ram_mask; +}; + +} // namespace bus::gameboy + +#endif // MAME_BUS_GAMEBOY_CARTBASE_H diff --git a/src/devices/bus/gameboy/cartbase.ipp b/src/devices/bus/gameboy/cartbase.ipp new file mode 100644 index 00000000000..39bde1c857f --- /dev/null +++ b/src/devices/bus/gameboy/cartbase.ipp @@ -0,0 +1,410 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Shared Game Boy cartridge helpers + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_CARTBASE_IPP +#define MAME_BUS_GAMEBOY_CARTBASE_IPP + +#pragma once + +#include "cartbase.h" + +#include "bus/generic/slot.h" + + +namespace bus::gameboy { + +//************************************************************************** +// 8 KiB flat RAM helper +//************************************************************************** + +template +void flat_ram_device_base::unload() +{ + memory_region *const nvramregion(this->cart_nvram_region()); + if (nvramregion && nvramregion->bytes()) + this->battery_save(nvramregion->base(), nvramregion->bytes()); +} + + +template +bool flat_ram_device_base::check_ram(std::string &message) +{ + memory_region *const ramregion(this->cart_ram_region()); + memory_region *const nvramregion(this->cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + if (rambytes && nvrambytes && ((rambytes > nvrambytes) || ((nvrambytes - 1) & nvrambytes))) + { + message = "Unsupported cartridge RAM size (if not all RAM is battery-backed, battery backed RAM size must be a power of two, and at least half the total RAM must be battery-backed)"; + return false; + } + + auto const ramtotal(rambytes + nvrambytes); + if (0x2000 < ramtotal) + { + message = "Unsupported cartridge RAM size (must be no larger than 8 KiB)"; + return false; + } + + return true; +} + + +template +void flat_ram_device_base::install_ram() +{ + memory_region *const ramregion(this->cart_ram_region()); + memory_region *const nvramregion(this->cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + + // assume battery-backed RAM is mapped low if present + if (nvrambytes) + { + if (0x2000 < nvrambytes) + { + osd_printf_warning( + "[%s] Cartridge RAM banking is unsupported - only 0x2000 bytes of 0x%X bytes will be accessible\n", + this->tag(), + nvrambytes); + } + u8 *const nvrambase(&nvramregion->as_u8()); + device_generic_cart_interface::install_non_power_of_two<0>( + nvrambytes, + 0x1fff, + 0, + 0, + 0xa000, + [this, nvrambase] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + this->logerror( + "Install NVRAM 0x%04X-0x%04X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + mirror); + this->cart_space()->install_ram(begin, end, mirror, &nvrambase[src]); + }); + this->save_pointer(NAME(nvrambase), nvrambytes); + this->battery_load(nvrambase, nvrambytes, nullptr); + } + + // install the rest of the RAM if possible + if (rambytes) + { + u8 *const rambase(&ramregion->as_u8()); + if (!nvrambytes) + { + if (0x2000 < rambytes) + { + osd_printf_warning( + "[%s] Cartridge RAM banking is unsupported - only 0x2000 bytes of 0x%X bytes will be accessible\n", + this->tag(), + rambytes); + } + device_generic_cart_interface::install_non_power_of_two<0>( + rambytes, + 0x1fff, + 0, + 0, + 0xa000, + [this, rambase] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + this->logerror( + "Install RAM 0x%04X-0x%04X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + mirror); + this->cart_space()->install_ram(begin, end, mirror, &rambase[src]); + }); + } + else if ((0x2000 > nvrambytes) && !((nvrambytes - 1) & nvrambytes) && (nvrambytes >= rambytes)) + { + device_generic_cart_interface::install_non_power_of_two<0>( + rambytes, + nvrambytes - 1, + 0, + 0, + 0xa000 | nvrambytes, + [this, rambase, highmirror = 0x1fff & ~(nvrambytes | (nvrambytes - 1))] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + this->logerror( + "Install RAM 0x%04X-0x%04X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + highmirror | mirror); + this->cart_space()->install_ram(begin, end, highmirror | mirror, &rambase[src]); + }); + } + else + { + osd_printf_warning( + "[%s] Cannot install 0x%X bytes of RAM in addition to 0x%X bytes of battery-backed RAM\n", + this->tag(), + rambytes, + nvrambytes); + } + this->save_pointer(NAME(rambase), rambytes); + } +} + + + +//************************************************************************** +// 8 KiB switchable RAM helper +//************************************************************************** + +template +void mbc_ram_device_base::unload() +{ + memory_region *const nvramregion(this->cart_nvram_region()); + if (nvramregion && nvramregion->bytes()) + this->battery_save(nvramregion->base(), nvramregion->bytes()); +} + + +template +mbc_ram_device_base::mbc_ram_device_base( + machine_config const &mconfig, + device_type type, + char const *tag, + device_t *owner, + u32 clock) : + Base(mconfig, type, tag, owner, clock), + m_bank_ram(*this, "ram"), + m_bank_bits_ram(0U), + m_bank_mask_ram(0U), + m_bank_sel_ram(0U), + m_bank_ram_mask(0xffU) +{ +} + + +template +bool mbc_ram_device_base::check_ram(std::string &message) +{ + memory_region *const ramregion(this->cart_ram_region()); + memory_region *const nvramregion(this->cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + if (rambytes && nvrambytes && ((rambytes & (PAGE_RAM_SIZE - 1)) || (nvrambytes & (PAGE_RAM_SIZE - 1)) || ((nvrambytes - 1) & nvrambytes))) + { + message = "Unsupported cartridge RAM size (if not all RAM is battery-backed, battery backed RAM size must be a power of two no smaller than 8 KiB, and total RAM size must be a multiple of 8 KiB)"; + return false; + } + + auto const ramtotal(rambytes + nvrambytes); + if (((PAGE_RAM_SIZE < ramtotal) && (ramtotal & (PAGE_RAM_SIZE - 1))) || ((u32(PAGE_RAM_SIZE) << m_bank_bits_ram) < ramtotal)) + { + message = util::string_format( + "Unsupported cartridge RAM size (must be no larger than 8 KiB, or a multiple of 8 KiB no larger than %u pages)", + 1U << m_bank_bits_ram); + return false; + } + + return true; +} + + +template +void mbc_ram_device_base::install_ram(address_space_installer &space) +{ + install_ram(nullptr, &space); +} + + +template +void mbc_ram_device_base::install_ram( + address_space_installer &rospace, + address_space_installer &rwspace) +{ + install_ram(&rospace, &rwspace); +} + + +template +bool mbc_ram_device_base::configure_bank_ram(std::string &message) +{ + memory_region *const ramregion(this->cart_ram_region()); + memory_region *const nvramregion(this->cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + auto const ramtotal(rambytes + nvrambytes); + if ((rambytes & (PAGE_RAM_SIZE - 1)) || (nvrambytes & (PAGE_RAM_SIZE - 1)) || ((u32(PAGE_RAM_SIZE) << m_bank_bits_ram) < ramtotal)) + { + message = util::string_format( + "Unsupported cartridge RAM size (battery-backed RAM and additional RAM must be multiples of 8 KiB with a total size no larger than %u pages)", + 1U << m_bank_bits_ram); + return false; + } + else if (rambytes && nvrambytes && ((nvrambytes - 1) & nvrambytes)) + { + message = "Unsupported cartridge RAM size (if not all RAM is battery-backed, battery backed RAM size must be a power of two no smaller than 8 KiB, and total RAM size must be a multiple of 8 KiB)"; + return false; + } + + + if (!ramtotal) + { + // need to avoid fatal errors + m_bank_mask_ram = 0U; + } + else + { + // configure banked RAM, assuming lower pages are battery-backed + u8 *const rambase(rambytes ? &ramregion->as_u8() : nullptr); + u8 *const nvrambase(nvrambytes ? &nvramregion->as_u8() : nullptr); + m_bank_mask_ram = device_generic_cart_interface::map_non_power_of_two( + unsigned(ramtotal / PAGE_RAM_SIZE), + [this, nvrampages = nvrambytes / PAGE_RAM_SIZE, rambase, nvrambase] (unsigned entry, unsigned page) + { + bool const nonvolatile(page < nvrampages); + u8 *const base(nonvolatile ? nvrambase : rambase); + auto const offset((page - (nonvolatile ? 0 : nvrampages)) * PAGE_RAM_SIZE); + this->logerror( + "Install %s 0x%05X-0x%05X in bank entry %u\n", + nonvolatile ? "NVRAM" : "RAM", + offset, + offset + (PAGE_RAM_SIZE - 1), + entry); + m_bank_ram->configure_entry(entry, &base[offset]); + }); + + if (nvrambytes) + { + this->save_pointer(NAME(nvrambase), nvrambytes); + this->battery_load(nvrambase, nvrambytes, nullptr); + } + if (rambytes) + this->save_pointer(NAME(rambase), rambytes); + } + + return true; +} + + +template +void mbc_ram_device_base::device_start() +{ + Base::device_start(); + + this->save_item(NAME(m_bank_sel_ram)); + this->save_item(NAME(m_bank_mask_ram)); +} + + +template +void mbc_ram_device_base::set_bank_ram(u8 entry) +{ + m_bank_sel_ram = entry; + if (m_bank_mask_ram) + { + entry = bank_ram_entry(); + //this->logerror("%s: Set RAM bank = 0x%05X\n", machine().describe_context(), entry * PAGE_RAM_SIZE); + m_bank_ram->set_entry(entry); + } +} + + +template +void mbc_ram_device_base::set_bank_ram_mask(u8 mask) +{ + m_bank_ram_mask = mask; + if (m_bank_mask_ram) + { + u8 const entry(bank_ram_entry()); + //this->logerror("%s: Set RAM bank = 0x%05X\n", machine().describe_context(), entry * PAGE_RAM_SIZE); + m_bank_ram->set_entry(entry); + } +} + + +template +void mbc_ram_device_base::install_ram( + address_space_installer *rospace, + address_space_installer *rwspace) +{ + memory_region *const ramregion(this->cart_ram_region()); + memory_region *const nvramregion(this->cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + auto const ramtotal(rambytes + nvrambytes); + u8 *const rambase(rambytes ? &ramregion->as_u8() : nullptr); + u8 *const nvrambase(nvrambytes ? &nvramregion->as_u8() : nullptr); + + if (!ramtotal) + { + // need to avoid fatal errors + m_bank_mask_ram = 0U; + } + else if (PAGE_RAM_SIZE >= ramtotal) + { + // install small amounts of RAM directly - mixed volatile and non-volatile RAM is not supported + device_generic_cart_interface::install_non_power_of_two<0>( + nvrambytes ? nvrambytes : rambytes, + PAGE_RAM_SIZE - 1, + 0, + 0, + 0xa000, + [this, rospace, rwspace, base = nvrambase ? nvrambase : rambase] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + this->logerror( + "Install RAM 0x%05X-0x%05X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + mirror); + if (rospace) + rospace->install_rom(begin, end, mirror, &base[src]); + if (rwspace) + rwspace->install_ram(begin, end, mirror, &base[src]); + }); + m_bank_mask_ram = 0U; + } + else + { + // install banked RAM, assuming lower pages are battery-backed + m_bank_mask_ram = device_generic_cart_interface::map_non_power_of_two( + unsigned(ramtotal / PAGE_RAM_SIZE), + [this, nvrampages = nvrambytes / PAGE_RAM_SIZE, rambase, nvrambase] (unsigned entry, unsigned page) + { + bool const nonvolatile(page < nvrampages); + u8 *const base(nonvolatile ? nvrambase : rambase); + auto const offset((page - (nonvolatile ? 0 : nvrampages)) * PAGE_RAM_SIZE); + this->logerror( + "Install %s 0x%05X-0x%05X in bank entry %u\n", + nonvolatile ? "NVRAM" : "RAM", + offset, + offset + (PAGE_RAM_SIZE - 1), + entry); + m_bank_ram->configure_entry(entry, &base[offset]); + }); + this->logerror("Installing banked RAM at 0xA000-0xBFFF\n"); + if (rospace) + rospace->install_read_bank(0xa000, 0xbfff, m_bank_ram); + if (rwspace) + rwspace->install_readwrite_bank(0xa000, 0xbfff, m_bank_ram); + } + + if (nvrambytes) + { + this->save_pointer(NAME(nvrambase), nvrambytes); + this->battery_load(nvrambase, nvrambytes, nullptr); + } + if (rambytes) + this->save_pointer(NAME(rambase), rambytes); +} + +} // namespace bus::gameboy + +#endif // MAME_BUS_GAMEBOY_CARTBASE_IPP diff --git a/src/devices/bus/gameboy/cartheader.h b/src/devices/bus/gameboy/cartheader.h new file mode 100644 index 00000000000..f0d3294372a --- /dev/null +++ b/src/devices/bus/gameboy/cartheader.h @@ -0,0 +1,132 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy cartridge header constants + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_CARTHEADER_H +#define MAME_BUS_GAMEBOY_CARTHEADER_H + +#pragma once + +#include + + +namespace bus::gameboy::cartheader { + +enum : unsigned +{ + OFFSET_ENTRYPOINT = 0x0100, // 4-byte instructions + OFFSET_LOGO = 0x0104, // 48-byte bitmap + OFFSET_TITLE = 0x0134, // originally 16-byte ASCII zero-padded, reduced to 11-byte + OFFSET_MANUFACTURER = 0x013f, // 4-byte ASCII + OFFSET_CGB = 0x0143, // 1-byte - 0x80 = CGB enhanced, 0xc0 = CGB required + OFFSET_LICENSEE_EXT = 0x0144, // 2-byte ASCII + OFFSET_SGB = 0x0146, // 1-byte - 0x00 = no SGB support, 0x03 = SGB enhanced + OFFSET_TYPE = 0x0147, // 1-byte - see values below + OFFSET_ROM_SIZE = 0x0148, // 1-byte - see values below + OFFSET_RAM_SIZE = 0x0149, // 1-byte - see values below + OFFSET_REGION = 0x014a, // 1-byte - 0x00 = Japan, 0x01 = export + OFFSET_LICENSEE = 0x014b, // 1-byte, 0x33 = ext + OFFSET_REVISION = 0x014c, // 1-byte + OFFSET_CHECKSUM_HEADER = 0x014d, // 1-byte - checksum of bytes 0x0134 to 0x014c + OFFSET_CHECKSUM_ROM = 0x014e, // 2-byte - big-Endian checksum of ROM excluding checksum bytes +}; + + +enum : u8 +{ + TYPE_ROM = 0x00, + TYPE_MBC1 = 0x01, + TYPE_MBC1_RAM = 0x02, + TYPE_MBC1_RAM_BATT = 0x03, + + TYPE_MBC2 = 0x05, + TYPE_MBC2_BATT = 0x06, + + TYPE_ROM_RAM = 0x08, + TYPE_ROM_RAM_BATT = 0x09, + + TYPE_MMM01 = 0x0b, + TYPE_MMM01_RAM = 0x0c, + TYPE_MMM01_RAM_BATT = 0x0d, + + TYPE_MBC3_RTC_BATT = 0x0f, + TYPE_MBC3_RTC_RAM_BATT = 0x10, + TYPE_MBC3 = 0x11, + TYPE_MBC3_RAM = 0x12, + TYPE_MBC3_RAM_BATT = 0x13, + + TYPE_MBC5 = 0x19, + TYPE_MBC5_RAM = 0x1a, + TYPE_MBC5_RAM_BATT = 0x1b, + TYPE_MBC5_RUMBLE = 0x1c, + TYPE_MBC5_RUMBLE_RAM = 0x1d, + TYPE_MBC5_RUMBLE_RAM_BATT = 0x1e, + + TYPE_MBC6 = 0x20, + + TYPE_MBC7_ACCEL_EEPROM = 0x22, + + TYPE_UNLICENSED_YONGYONG = 0xea, + + TYPE_CAMERA = 0xfc, + TYPE_TAMA5 = 0xfd, + TYPE_HUC3 = 0xfe, + TYPE_HUC1_RAM_BATT = 0xff +}; + + +enum : u8 +{ + ROM_SIZE_32K = 0x00, + ROM_SIZE_64K = 0x01, + ROM_SIZE_128K = 0x02, + ROM_SIZE_256K = 0x03, + ROM_SIZE_512K = 0x04, + ROM_SIZE_1024K = 0x05, + ROM_SIZE_2048K = 0x06, + ROM_SIZE_4096K = 0x07, + ROM_SIZE_8192K = 0x08, + + ROM_SIZE_1152K = 0x52, + ROM_SIZE_1280K = 0x53, + ROM_SIZE_1536K = 0x54 +}; + + +enum : u8 +{ + RAM_SIZE_0K = 0x00, + RAM_SIZE_2K = 0x01, + RAM_SIZE_8K = 0x02, + RAM_SIZE_32K = 0x03, + RAM_SIZE_128K = 0x04, + RAM_SIZE_64K = 0x05 +}; + + +template +inline u8 checksum(It begin, It end) +{ + u8 result(0U); + while (begin != end) + result += u8(~*begin++ & 0xff); + return result; +} + + +template +inline bool verify_header_checksum(It begin) +{ + std::advance(begin, OFFSET_TITLE - 0x100); + u8 checksum(0U); + for (unsigned i = 0U; (OFFSET_CHECKSUM_HEADER - OFFSET_TITLE) > i; ++i, ++begin) + checksum += u8(~*begin & 0xff); + return *begin == checksum; +} + +} // namespace bus::gameboy::cartheader + +#endif // MAME_BUS_GAMEBOY_CARTHEADER_H diff --git a/src/devices/bus/gameboy/carts.cpp b/src/devices/bus/gameboy/carts.cpp index 69655ab1145..b2ea83788d8 100644 --- a/src/devices/bus/gameboy/carts.cpp +++ b/src/devices/bus/gameboy/carts.cpp @@ -1,48 +1,103 @@ // license:BSD-3-Clause -// copyright-holders:Wilbert Pol +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy and Mega Duck cartridge slot options + + ***************************************************************************/ #include "emu.h" #include "carts.h" +#include "camera.h" +#include "huc1.h" +#include "huc3.h" #include "mbc.h" +#include "mbc2.h" +#include "mbc6.h" +#include "mbc7.h" +#include "mmm01.h" #include "rom.h" +#include "tama5.h" + + +namespace bus::gameboy::slotoptions { + +char const *const GB_STD = "rom"; +char const *const GB_M161 = "rom_m161"; +char const *const GB_WISDOM = "rom_wisdom"; +char const *const GB_YONG = "rom_yong"; +char const *const GB_ROCKMAN8 = "rom_rock8"; +char const *const GB_SM3SP = "rom_sm3sp"; +char const *const GB_SACHEN1 = "rom_sachen1"; +char const *const GB_SACHEN2 = "rom_sachen2"; +char const *const GB_ROCKET = "rom_atvrac"; +char const *const GB_LASAMA = "rom_lasama"; +char const *const GB_GBCK003 = "rom_gbck003"; +char const *const GB_MBC1 = "rom_mbc1"; +char const *const GB_MBC2 = "rom_mbc2"; +char const *const GB_MBC3 = "rom_mbc3"; +char const *const GB_MBC5 = "rom_mbc5"; +char const *const GB_MBC6 = "rom_mbc6"; +char const *const GB_MBC7_2K = "rom_mbc7_2k"; +char const *const GB_MBC7_4K = "rom_mbc7_4k"; +char const *const GB_MMM01 = "rom_mmm01"; +char const *const GB_HUC1 = "rom_huc1"; +char const *const GB_HUC3 = "rom_huc3"; +char const *const GB_TAMA5 = "rom_tama5"; +char const *const GB_CAMERA = "rom_camera"; +char const *const GB_SINTAX = "rom_sintax"; +char const *const GB_CHONGWU = "rom_chong"; +char const *const GB_LICHENG = "rom_licheng"; +char const *const GB_VF001 = "rom_vf001"; +char const *const GB_DIGIMON = "rom_digimon"; + +char const *const MEGADUCK_STD = "rom"; +char const *const MEGADUCK_BANKED = "rom_banked"; + +} // namespace bus::gameboy::slotoptions + void gameboy_cartridges(device_slot_interface &device) { - device.option_add_internal("rom", GB_STD_ROM); - device.option_add_internal("rom_mbc1", GB_ROM_MBC1); - device.option_add_internal("rom_mbc1col", GB_ROM_MBC1); - device.option_add_internal("rom_mbc2", GB_ROM_MBC2); - device.option_add_internal("rom_mbc3", GB_ROM_MBC3); - device.option_add_internal("rom_huc1", GB_ROM_MBC3); - device.option_add_internal("rom_huc3", GB_ROM_MBC3); - device.option_add_internal("rom_mbc5", GB_ROM_MBC5); - device.option_add_internal("rom_mbc6", GB_ROM_MBC6); - device.option_add_internal("rom_mbc7", GB_ROM_MBC7); - device.option_add_internal("rom_tama5", GB_ROM_TAMA5); - device.option_add_internal("rom_mmm01", GB_ROM_MMM01); - device.option_add_internal("rom_m161", GB_ROM_M161); - device.option_add_internal("rom_sachen1", GB_ROM_SACHEN1); - device.option_add_internal("rom_sachen2", GB_ROM_SACHEN2); - device.option_add_internal("rom_wisdom", GB_ROM_WISDOM); - device.option_add_internal("rom_yong", GB_ROM_YONG); - device.option_add_internal("rom_lasama", GB_ROM_LASAMA); - device.option_add_internal("rom_atvrac", GB_ROM_ATVRAC); - device.option_add_internal("rom_camera", GB_ROM_CAMERA); - device.option_add_internal("rom_188in1", GB_ROM_188IN1); - device.option_add_internal("rom_sintax", GB_ROM_SINTAX); - device.option_add_internal("rom_chong", GB_ROM_CHONGWU); - device.option_add_internal("rom_licheng", GB_ROM_LICHENG); - device.option_add_internal("rom_digimon", GB_ROM_DIGIMON); - device.option_add_internal("rom_rock8", GB_ROM_ROCKMAN8); - device.option_add_internal("rom_sm3sp", GB_ROM_SM3SP); -// device.option_add_internal("rom_dkong5", GB_ROM_DKONG5); -// device.option_add_internal("rom_unk01", GB_ROM_UNK01); + using namespace bus::gameboy; + + device.option_add_internal(slotoptions::GB_STD, GB_ROM_STD); + device.option_add_internal(slotoptions::GB_WISDOM, GB_ROM_WISDOM); + device.option_add_internal(slotoptions::GB_YONG, GB_ROM_YONG); + device.option_add_internal(slotoptions::GB_ROCKMAN8, GB_ROM_ROCKMAN8); + device.option_add_internal(slotoptions::GB_SM3SP, GB_ROM_SM3SP); + device.option_add_internal(slotoptions::GB_SACHEN1, GB_ROM_SACHEN1); + device.option_add_internal(slotoptions::GB_SACHEN2, GB_ROM_SACHEN2); + device.option_add_internal(slotoptions::GB_ROCKET, GB_ROM_ROCKET); + device.option_add_internal(slotoptions::GB_LASAMA, GB_ROM_LASAMA); + device.option_add_internal(slotoptions::GB_GBCK003, GB_ROM_GBCK003); + device.option_add_internal(slotoptions::GB_MBC1, GB_ROM_MBC1); + device.option_add_internal(slotoptions::GB_MBC2, GB_ROM_MBC2); + device.option_add_internal(slotoptions::GB_MBC3, GB_ROM_MBC3); + device.option_add_internal(slotoptions::GB_MBC5, GB_ROM_MBC5); + device.option_add_internal(slotoptions::GB_MBC6, GB_ROM_MBC6); + device.option_add_internal(slotoptions::GB_MBC7_2K, GB_ROM_MBC7_2K); + device.option_add_internal(slotoptions::GB_MBC7_4K, GB_ROM_MBC7_4K); + device.option_add_internal(slotoptions::GB_M161, GB_ROM_M161); + device.option_add_internal(slotoptions::GB_MMM01, GB_ROM_MMM01); + device.option_add_internal(slotoptions::GB_CAMERA, GB_ROM_CAMERA); + device.option_add_internal(slotoptions::GB_HUC1, GB_ROM_HUC1); + device.option_add_internal(slotoptions::GB_HUC3, GB_ROM_HUC3); + device.option_add_internal(slotoptions::GB_TAMA5, GB_ROM_TAMA5); + device.option_add_internal(slotoptions::GB_SINTAX, GB_ROM_SINTAX); + device.option_add_internal(slotoptions::GB_CHONGWU, GB_ROM_CHONGWU); + device.option_add_internal(slotoptions::GB_LICHENG, GB_ROM_LICHENG); + device.option_add_internal(slotoptions::GB_VF001, GB_ROM_VF001); + device.option_add_internal(slotoptions::GB_DIGIMON, GB_ROM_DIGIMON); } void megaduck_cartridges(device_slot_interface &device) { - device.option_add_internal("rom", MEGADUCK_ROM); + using namespace bus::gameboy; + + device.option_add_internal(slotoptions::MEGADUCK_STD, MEGADUCK_ROM_STD); + device.option_add_internal(slotoptions::MEGADUCK_BANKED, MEGADUCK_ROM_BANKED); } diff --git a/src/devices/bus/gameboy/carts.h b/src/devices/bus/gameboy/carts.h index 269a9a9be31..02340527bd1 100644 --- a/src/devices/bus/gameboy/carts.h +++ b/src/devices/bus/gameboy/carts.h @@ -1,5 +1,10 @@ // license:BSD-3-Clause -// copyright-holders:Wilbert Pol +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy and Mega Duck cartridge slot options + + ***************************************************************************/ #ifndef MAME_BUS_GAMEBOY_CARTS_H #define MAME_BUS_GAMEBOY_CARTS_H @@ -8,4 +13,42 @@ void gameboy_cartridges(device_slot_interface &device); void megaduck_cartridges(device_slot_interface &device); + +namespace bus::gameboy::slotoptions { + +extern char const *const GB_STD; +extern char const *const GB_M161; +extern char const *const GB_WISDOM; +extern char const *const GB_YONG; +extern char const *const GB_ROCKMAN8; +extern char const *const GB_SM3SP; +extern char const *const GB_SACHEN1; +extern char const *const GB_SACHEN2; +extern char const *const GB_ROCKET; +extern char const *const GB_LASAMA; +extern char const *const GB_GBCK003; +extern char const *const GB_MBC1; +extern char const *const GB_MBC2; +extern char const *const GB_MBC3; +extern char const *const GB_MBC3; +extern char const *const GB_MBC5; +extern char const *const GB_MBC6; +extern char const *const GB_MBC7_2K; +extern char const *const GB_MBC7_4K; +extern char const *const GB_MMM01; +extern char const *const GB_CAMERA; +extern char const *const GB_HUC1; +extern char const *const GB_HUC3; +extern char const *const GB_TAMA5; +extern char const *const GB_SINTAX; +extern char const *const GB_CHONGWU; +extern char const *const GB_LICHENG; +extern char const *const GB_VF001; +extern char const *const GB_DIGIMON; + +extern char const *const MEGADUCK_STD; +extern char const *const MEGADUCK_BANKED; + +} // namespace bus::gameboy::slotoptions + #endif // MAME_BUS_GAMEBOY_CARTS_H diff --git a/src/devices/bus/gameboy/gb_slot.cpp b/src/devices/bus/gameboy/gb_slot.cpp deleted file mode 100644 index cda1283254b..00000000000 --- a/src/devices/bus/gameboy/gb_slot.cpp +++ /dev/null @@ -1,832 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Fabio Priuli, Wilbert Pol -/*********************************************************************************************************** - - - 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 "gb_slot.h" - -//************************************************************************** -// GLOBAL VARIABLES -//************************************************************************** - -DEFINE_DEVICE_TYPE(GB_CART_SLOT, gb_cart_slot_device, "gb_cart_slot", "Game Boy Cartridge Slot") -DEFINE_DEVICE_TYPE(MEGADUCK_CART_SLOT, megaduck_cart_slot_device, "megaduck_cart_slot", "Megaduck Cartridge Slot") - -//************************************************************************** -// GB cartridges Interface -//************************************************************************** - -//------------------------------------------------- -// device_gb_cart_interface - constructor -//------------------------------------------------- - -device_gb_cart_interface::device_gb_cart_interface(const machine_config &mconfig, device_t &device) : - device_interface(device, "gbcart"), - m_rom(nullptr), - m_rom_size(0), m_ram_bank(0), m_latch_bank(0), m_latch_bank2(0), - has_rumble(false), - has_timer(false), - has_battery(false) -{ -} - - -//------------------------------------------------- -// ~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(uint32_t size) -{ - if (m_rom == nullptr) - { - m_rom = device().machine().memory().region_alloc(device().subtag("^cart:rom"), size, 1, ENDIANNESS_LITTLE)->base(); - m_rom_size = size; - } -} - - -//------------------------------------------------- -// ram_alloc - alloc the space for the ram -//------------------------------------------------- - -void device_gb_cart_interface::ram_alloc(uint32_t size) -{ - m_ram.resize(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_t 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_t 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 -//************************************************************************** - -//------------------------------------------------- -// gb_cart_slot_device_base - constructor -//------------------------------------------------- -gb_cart_slot_device_base::gb_cart_slot_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : - device_t(mconfig, type, tag, owner, clock), - device_cartrom_image_interface(mconfig, *this), - device_single_card_slot_interface(mconfig, *this), - m_type(GB_MBC_UNKNOWN), - m_cart(nullptr) -{ -} - -gb_cart_slot_device::gb_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : - gb_cart_slot_device_base(mconfig, GB_CART_SLOT, tag, owner, clock) -{ -} - -megaduck_cart_slot_device::megaduck_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : - gb_cart_slot_device_base(mconfig, MEGADUCK_CART_SLOT, tag, owner, clock) -{ -} - -//------------------------------------------------- -// gb_cart_slot_device_base - destructor -//------------------------------------------------- - -gb_cart_slot_device_base::~gb_cart_slot_device_base() -{ -} - -//------------------------------------------------- -// device_start - device-specific startup -//------------------------------------------------- - -void gb_cart_slot_device_base::device_start() -{ - m_cart = get_card_device(); -} - - -//------------------------------------------------- -// 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_M161, "rom_m161" }, - { 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_SACHEN1, "rom_sachen1" }, - { GB_MBC_SACHEN2, "rom_sachen2" }, - { 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_LICHENG, "rom_licheng" }, - { 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" }, - { GB_MBC_188IN1, "rom_188in1" } -}; - -static int gb_get_pcb_id(const char *slot) -{ - for (auto & elem : slot_list) - { - if (!strcmp(elem.slot_option, slot)) - return elem.pcb_id; - } - - return GB_MBC_NONE; -} - -static const char *gb_get_slot(int type) -{ - for (auto & elem : slot_list) - { - if (elem.pcb_id == type) - return elem.slot_option; - } - - return "rom"; -} - - -/*------------------------------------------------- - call load - -------------------------------------------------*/ - - -image_init_result gb_cart_slot_device_base::call_load() -{ - if (m_cart) - { - uint32_t offset; - uint32_t len = !loaded_through_softlist() ? length() : get_software_region_length("rom"); - uint8_t *ROM; - int rambanks = 0; - - // From fullpath, check for presence of a header and skip it + check filesize is valid - if (!loaded_through_softlist()) - { - 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::INVALIDIMAGE, "Invalid ROM file size\n"); - return image_init_result::FAIL; - } - } - - m_cart->rom_alloc(len); - ROM = m_cart->get_rom_base(); - - if (!loaded_through_softlist()) - 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; - - if (loaded_through_softlist()) - m_type = gb_get_pcb_id(get_feature("slot") ? get_feature("slot") : "rom"); - else - m_type = get_cart_type(ROM + offset, len - offset); - - // setup additional mask/shift for MBC1 variants: - // a few game collections use the same mapper with slightly - // different lines connection with the ROM / RAM - if (m_type == GB_MBC_MBC1 || m_type == GB_MBC_188IN1) - m_cart->set_additional_wirings(0x1f, 0); - if (m_type == GB_MBC_MBC1_COL) - m_cart->set_additional_wirings(0x0f, -1); - - // setup RAM/NVRAM/RTC/RUMBLE - if (loaded_through_softlist()) - { - // 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 (!strcmp(get_feature("rumble"), "yes")) - m_cart->set_has_rumble(true); - } - - if (get_feature("rtc")) - { - if (!strcmp(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 (m_type == GB_MBC_MBC2 || m_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(m_type)); - - internal_header_logging(ROM + offset, len - offset); - - return image_init_result::PASS; - } - - return image_init_result::PASS; -} - -image_init_result megaduck_cart_slot_device::call_load() -{ - if (m_cart) - { - uint32_t len = !loaded_through_softlist() ? length() : get_software_region_length("rom"); - - m_cart->rom_alloc(len); - - if (!loaded_through_softlist()) - fread(m_cart->get_rom_base(), len); - else - memcpy(m_cart->get_rom_base(), 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_result::PASS; - } - - return image_init_result::PASS; -} - - -/*------------------------------------------------- - call_unload - -------------------------------------------------*/ - -void gb_cart_slot_device_base::call_unload() -{ - if (m_cart && m_cart->get_ram_base() && m_cart->get_ram_size() && m_cart->get_has_battery()) - battery_save(m_cart->get_ram_base(), m_cart->get_ram_size()); -} - -void gb_cart_slot_device_base::setup_ram(uint8_t banks) -{ - m_cart->ram_alloc(banks * 0x2000); - memset(m_cart->get_ram_base(), 0xff, m_cart->get_ram_size()); - m_cart->ram_map_setup(banks); -} - - - -// 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 gb_cart_slot_device_base::get_mmm01_candidate(const uint8_t *ROM, uint32_t len) -{ - if (len < 0x8147) - return false; - - static const uint8_t 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; -} - -bool gb_cart_slot_device_base::is_mbc1col_game(const uint8_t *ROM, uint32_t len) -{ - const uint8_t name_length = 0x10u; - static const uint8_t internal_names[][name_length + 1] = { - /* Bomberman Collection */ - "BOMCOL\0\0\0\0\0\0\0\0\0\0", - /* Bomberman Selection */ - "BOMSEL\0\0\0\0\0B2CK\xC0", - /* Genjin Collection */ - "GENCOL\0\0\0\0\0\0\0\0\0\0", - /* Momotarou Collection */ - "MOMOCOL\0\0\0\0\0\0\0\0\0", - /* Mortal Kombat I & II Japan */ - "MORTALKOMBAT DUO", - /* Mortal Kombat I & II US */ - "MORTALKOMBATI&II", - /* Super Chinese Land 1,2,3' */ - "SUPERCHINESE 123" - }; - - const uint8_t rows = std::size(internal_names); - - for (uint8_t i = 0x00; i < rows; ++i) { - if (0 == memcmp(&ROM[0x134], &internal_names[i][0], name_length)) - return true; - } - - return false; -} - -int gb_cart_slot_device_base::get_cart_type(const uint8_t *ROM, uint32_t 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) - if (len >= 0x184 + 0x30) - { - int count = 0; - for (int i = 0x0184; i < 0x0184 + 0x30; i++) - { - count += ROM[i]; - } - - if (count == 4876) - { -// printf("Li Cheng %d\n", count); - type = GB_MBC_LICHENG; - } - if ((count == 4138 || count == 4125) && len >= 2097152) - { - // All known sintax (raw) dumps are at least 2097152 bytes in size - // 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 && is_mbc1col_game(ROM, len)) - type = GB_MBC_MBC1_COL; - - return type; -} - -/*------------------------------------------------- - get default card software - -------------------------------------------------*/ - -std::string gb_cart_slot_device_base::get_default_card_software(get_default_card_software_hook &hook) const -{ - if (hook.image_file()) - { - uint64_t len; - hook.image_file()->length(len); // FIXME: check error return, guard against excessively large files - std::vector rom(len); - - size_t actual; - hook.image_file()->read(&rom[0], len, actual); // FIXME: check error return or read returning short - - uint32_t offset = 0; - if ((len % 0x4000) == 512) - offset = 512; - if (get_mmm01_candidate(&rom[offset], len - offset)) - offset += (len - 0x8000); - - int const type = get_cart_type(&rom[offset], len - offset); - char const *const slot_string = gb_get_slot(type); - - //printf("type: %s\n", slot_string); - - return std::string(slot_string); - } - - return software_get_default_slot("rom"); -} - - -std::string megaduck_cart_slot_device::get_default_card_software(get_default_card_software_hook &hook) const -{ - if (hook.image_file()) - return std::string("rom"); - - return software_get_default_slot("rom"); -} - - - -/*------------------------------------------------- - read - -------------------------------------------------*/ - -uint8_t gb_cart_slot_device_base::read_rom(offs_t offset) -{ - if (m_cart) - return m_cart->read_rom(offset); - else - return 0xff; -} - -uint8_t gb_cart_slot_device_base::read_ram(offs_t offset) -{ - if (m_cart) - return m_cart->read_ram(offset); - else - return 0xff; -} - - -/*------------------------------------------------- - write - -------------------------------------------------*/ - -void gb_cart_slot_device_base::write_bank(offs_t offset, uint8_t data) -{ - if (m_cart) - m_cart->write_bank(offset, data); -} - -void gb_cart_slot_device_base::write_ram(offs_t offset, uint8_t data) -{ - if (m_cart) - m_cart->write_ram(offset, data); -} - - -/*------------------------------------------------- - Internal header logging - -------------------------------------------------*/ - -void gb_cart_slot_device_base::internal_header_logging(uint8_t *ROM, uint32_t 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_t 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"} - }; - static const int ramsize[8] = { 0, 2, 8, 32, 128, 64, 0, 0 }; - - char soft[17]; - uint32_t tmp; - 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%02X]\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%02X]\n", (ROM[0x0146] == 0x03) ? "Yes" : "No", ROM[0x0146] ); - logerror("\tColor GB: %s [0x%02X]\n", (ROM[0x0143] == 0x80 || ROM[0x0143] == 0xc0) ? "Yes" : "No", ROM[0x0143] ); - logerror("\tROM Size: %d 16kB Banks [0x%02X]\n", rom_banks, ROM[0x0148]); - logerror("\tRAM Size: %d kB [0x%02X]\n", ramsize[ROM[0x0149] & 0x07], ROM[0x0149]); - logerror("\tLicense code: 0x%02X%02X\n", ROM[0x0145], ROM[0x0144] ); - tmp = (ROM[0x014b] << 8) + ROM[0x014a]; - for (i = 0; i < std::size(companies); i++) - if (tmp == companies[i].code) - break; - logerror("\tManufacturer ID: 0x%02X [%s]\n", tmp, (i < std::size(companies)) ? companies[i].name : "?"); - logerror("\tVersion Number: 0x%02X\n", ROM[0x014c]); - logerror("\tComplement Check: 0x%02X\n", ROM[0x014d]); - logerror("\tChecksum: 0x%04X\n", ((ROM[0x014e] << 8) + ROM[0x014f])); - tmp = (ROM[0x0103] << 8) + ROM[0x0102]; - logerror("\tStart Address: 0x%04X\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 0x%04X vs Internal 0x%04X)\n", csum, tmp); -} diff --git a/src/devices/bus/gameboy/gb_slot.h b/src/devices/bus/gameboy/gb_slot.h deleted file mode 100644 index d12fb86f6d5..00000000000 --- a/src/devices/bus/gameboy/gb_slot.h +++ /dev/null @@ -1,245 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Fabio Priuli, Wilbert Pol -/*************************************************************************** - -Cardridge port pinouts: - Pin Name Description - 1 VCC +5 VDC - 2 PHI CPU clock ? - 3 /WR Write - 4 /RD Read - 5 /CS SRAM select - 6 A0 Address 0 - 7 A1 Address 1 - 8 A2 Address 2 - 9 A3 Address 3 - 10 A4 Address 4 - 11 A5 Address 5 - 12 A6 Address 6 - 13 A7 Address 7 - 14 A8 Address 8 - 15 A9 Address 9 - 16 A10 Address 10 - 17 A11 Address 11 - 18 A12 Address 12 - 19 A13 Address 13 - 20 A14 Address 14 - 21 A15 Address 15 - 22 D0 Data 0 - 23 D1 Data 1 - 24 D2 Data 2 - 25 D3 Data 3 - 26 D4 Data 4 - 27 D5 Data 5 - 28 D6 Data 6 - 29 D7 Data 7 - 30 /RST Reset - 31 AUDIOIN Never used ? - 32 GND Ground - -***************************************************************************/ -#ifndef MAME_BUS_GAMEBOY_GB_SLOT_H -#define MAME_BUS_GAMEBOY_GB_SLOT_H - -#include "imagedev/cartrom.h" - - -/*************************************************************************** - TYPE DEFINITIONS - ***************************************************************************/ - - -/* PCB */ -enum -{ - GB_MBC_NONE = 0, /* 32KB ROM - No memory bank controller */ - GB_MBC_MBC1, /* 2MB ROM, 8KB RAM -or- 512KB ROM, 32KB RAM */ - GB_MBC_MBC2, /* 256KB ROM, 32KB RAM */ - GB_MBC_MBC3, /* 2MB ROM, 32KB RAM, RTC */ - GB_MBC_MBC4, /* ?? ROM, ?? RAM */ - GB_MBC_MBC5, /* 8MB ROM, 128KB RAM (32KB w/ Rumble) */ - GB_MBC_TAMA5, /* ?? ROM ?? RAM - What is this? */ - GB_MBC_HUC1, /* ?? ROM, ?? RAM - Hudson Soft Controller */ - GB_MBC_HUC3, /* ?? ROM, ?? RAM - Hudson Soft Controller */ - GB_MBC_MBC6, /* ?? ROM, 32KB SRAM */ - GB_MBC_MBC7, /* ?? ROM, ?? RAM */ - GB_MBC_M161, /* 256kB ROM, No RAM */ - GB_MBC_MMM01, /* 8MB ROM, 128KB RAM */ - GB_MBC_WISDOM, /* ?? ROM, ?? RAM - Wisdom tree controller */ - GB_MBC_MBC1_COL, /* 1MB ROM, 32KB RAM - workaround for MBC1 on PCB that maps rom address lines differently */ - GB_MBC_SACHEN1, /* 4MB ROM, No RAM - Sachen MMC-1 variant */ - GB_MBC_SACHEN2, /* 4MB ROM, No RAM - Sachen MMC-2 variant */ - GB_MBC_YONGYONG, /* ?? ROM, ?? RAM - Appears in Sonic 3D Blast 5 pirate */ - GB_MBC_LASAMA, /* ?? ROM, ?? RAM - Appears in La Sa Ma */ - GB_MBC_ATVRACIN, - GB_MBC_CAMERA, - GB_MBC_188IN1, - GB_MBC_SINTAX, - GB_MBC_CHONGWU, - GB_MBC_LICHENG, - GB_MBC_DIGIMON, - GB_MBC_ROCKMAN8, - GB_MBC_SM3SP, - GB_MBC_DKONG5, - GB_MBC_UNK01, - GB_MBC_MEGADUCK, /* MEGADUCK style banking */ - GB_MBC_UNKNOWN /* Unknown mapper */ -}; - - -// ======================> device_gb_cart_interface - -class device_gb_cart_interface : public device_interface -{ -public: - // construction/destruction - virtual ~device_gb_cart_interface(); - - // reading and writing - virtual uint8_t read_rom(offs_t offset) { return 0xff; } - virtual void write_bank(offs_t offset, uint8_t data) {} - virtual uint8_t read_ram(offs_t offset) { return 0xff; } - virtual void write_ram(offs_t offset, uint8_t data) {} - - void rom_alloc(uint32_t size); - void ram_alloc(uint32_t size); - uint8_t* get_rom_base() { return m_rom; } - uint8_t* get_ram_base() { return &m_ram[0]; } - uint32_t get_rom_size() { return m_rom_size; } - uint32_t get_ram_size() { return m_ram.size(); } - - void rom_map_setup(uint32_t size); - void ram_map_setup(uint8_t banks); - - virtual void set_additional_wirings(uint8_t mask, int shift) { } // MBC-1 will then overwrite this! - void set_has_timer(bool val) { has_timer = val; } - void set_has_rumble(bool val) { has_rumble = val; } - void set_has_battery(bool val) { has_battery = val; } - bool get_has_battery() { return has_battery; } - - void save_ram() { device().save_item(NAME(m_ram)); } - -protected: - device_gb_cart_interface(const machine_config &mconfig, device_t &device); - - // internal state - uint8_t *m_rom; - uint32_t m_rom_size; - std::vector m_ram; - - // bankswitch variables - // we access ROM/RAM banks through these bank maps - // default accesses are: - // 0x0000-0x3fff = rom_bank_map[m_latch_bank] (generally defaults to m_latch_bank = 0) - // 0x4000-0x7fff = rom_bank_map[m_latch_bank2] (generally defaults to m_latch_bank2 = 1) - // 0xa000-0xbfff = ram_bank_map[m_ram_bank] (generally defaults to m_ram_bank = 0) - // suitable writes to 0x0000-0x7fff can then modify m_latch_bank/m_latch_bank2 - uint8_t rom_bank_map[512]; // 16K chunks of ROM - uint8_t ram_bank_map[256]; // 16K chunks of RAM - uint8_t m_ram_bank; - uint16_t m_latch_bank, m_latch_bank2; - - bool has_rumble, has_timer, has_battery; -}; - - -// ======================> gb_cart_slot_device_base - -class gb_cart_slot_device_base : public device_t, - public device_cartrom_image_interface, - public device_single_card_slot_interface -{ -public: - // construction/destruction - virtual ~gb_cart_slot_device_base(); - - // image-level overrides - virtual image_init_result call_load() override; - virtual void call_unload() override; - - virtual bool is_reset_on_load() const noexcept override { return true; } - virtual const char *image_interface() const noexcept override { return "gameboy_cart"; } - virtual const char *file_extensions() const noexcept override { return "bin,gb,gbc"; } - - // slot interface overrides - virtual std::string get_default_card_software(get_default_card_software_hook &hook) const override; - - int get_type() { return m_type; } - static int get_cart_type(const uint8_t *ROM, uint32_t len); - static bool get_mmm01_candidate(const uint8_t *ROM, uint32_t len); - static bool is_mbc1col_game(const uint8_t *ROM, uint32_t len); - - void setup_ram(uint8_t banks); - void internal_header_logging(uint8_t *ROM, uint32_t len); - void save_ram() { if (m_cart && m_cart->get_ram_size()) m_cart->save_ram(); } - - // reading and writing - virtual uint8_t read_rom(offs_t offset); - virtual void write_bank(offs_t offset, uint8_t data); - virtual uint8_t read_ram(offs_t offset); - virtual void write_ram(offs_t offset, uint8_t data); - - -protected: - gb_cart_slot_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); - - // device-level overrides - virtual void device_start() override; - - int m_type; - device_gb_cart_interface* m_cart; -}; - -// ======================> gb_cart_slot_device - -class gb_cart_slot_device : public gb_cart_slot_device_base -{ -public: - // construction/destruction - template - gb_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, T &&opts, char const *dflt) - : gb_cart_slot_device(mconfig, tag, owner, (uint32_t)0) - { - option_reset(); - opts(*this); - set_default_option(dflt); - set_fixed(false); - } - gb_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); -}; - - -// ======================> megaduck_cart_slot_device - -class megaduck_cart_slot_device : public gb_cart_slot_device_base -{ -public: - // construction/destruction - template - megaduck_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, T &&opts, char const *dflt) - : megaduck_cart_slot_device(mconfig, tag, owner, (uint32_t)0) - { - option_reset(); - opts(*this); - set_default_option(dflt); - set_fixed(false); - } - megaduck_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - // image-level overrides - virtual image_init_result call_load() override; - virtual const char *image_interface() const noexcept override { return "megaduck_cart"; } - virtual const char *file_extensions() const noexcept override { return "bin"; } - - // slot interface overrides - virtual std::string get_default_card_software(get_default_card_software_hook &hook) const override; -}; - - - - -// device type definition -DECLARE_DEVICE_TYPE(GB_CART_SLOT, gb_cart_slot_device) -DECLARE_DEVICE_TYPE(MEGADUCK_CART_SLOT, megaduck_cart_slot_device) - -#endif // MAME_BUS_GAMEBOY_GB_SLOT_H diff --git a/src/devices/bus/gameboy/gbslot.cpp b/src/devices/bus/gameboy/gbslot.cpp new file mode 100644 index 00000000000..43f7b7931f7 --- /dev/null +++ b/src/devices/bus/gameboy/gbslot.cpp @@ -0,0 +1,1310 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy cartridge slot + + ***************************************************************************/ + +#include "emu.h" +#include "gbslot.h" + +#include "cartheader.h" +#include "carts.h" +#include "gbxfile.h" +#include "mmm01.h" + +#include "bus/generic/slot.h" +#include "cpu/lr35902/lr35902d.h" + +#include "disasmintf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class dasm_helper : public util::disasm_interface::data_buffer +{ +public: + dasm_helper(u8 const *data, offs_t base_pc) : m_data(data), m_base_pc(base_pc) + { } + + virtual u8 r8(offs_t pc) const override + { return m_data[pc - m_base_pc]; } + + virtual u16 r16(offs_t pc) const override + { return r8(pc) | (u16(r8(pc + 1)) << 8); } + + virtual u32 r32(offs_t pc) const override + { return r16(pc) | (u32(r16(pc + 2)) << 16); } + + virtual u64 r64(offs_t pc) const override + { return r32(pc) | (u64(r32(pc + 4)) << 32); } + +private: + u8 const *const m_data; + offs_t const m_base_pc; +}; + + +std::string disassemble_entry_point(u8 const *header) +{ + dasm_helper helper(header, 0x0100); + lr35902_disassembler dasm; + std::ostringstream str; + auto const result = dasm.disassemble(str, 0x0100, helper, helper); + str << " ("; + bool first = true; + for (unsigned i = 0; (result & util::disasm_interface::LENGTHMASK) > i; ++i) + { + util::stream_format(str, first ? "%02X" : " %02X", header[i]); + first = false; + } + str << ')'; + return std::move(str).str(); +} + + +std::string cart_title_text(u8 const *header) +{ + std::string_view title( + reinterpret_cast(&header[cartheader::OFFSET_TITLE - 0x100]), + cartheader::OFFSET_LICENSEE_EXT - cartheader::OFFSET_TITLE); + + // best effort to strip off repurposed fields + if (!title[cartheader::OFFSET_MANUFACTURER - cartheader::OFFSET_TITLE - 1]) + title.remove_suffix(cartheader::OFFSET_LICENSEE_EXT - cartheader::OFFSET_MANUFACTURER); + if (BIT(title.back(), 7)) + title.remove_suffix(1); + + // strip NUL padding, short-circuit empty titles + auto const last = title.find_last_not_of('\0'); + if (std::string_view::npos == last) + return "\"\""; + title.remove_suffix(title.length() - last - 1); + + // replace non-ASCII characters with octal escapes + std::ostringstream str; + str.setf(std::ios_base::oct, std::ios_base::basefield); + str.fill('0'); + str << '"'; + while (!title.empty()) + { + auto const invalid = std::find_if( + title.begin(), + title.end(), + [] (char ch) { return (' ' > ch) || ('~' < ch); }); + auto const len = std::distance(title.begin(), invalid); + if (len) + { + str << title.substr(0, len); + title.remove_prefix(len); + } + else + { + str << '\\'; + str.width(3); + str << unsigned(u8(title.front())); + title.remove_prefix(1); + } + } + str << '"'; + return std::move(str).str(); +} + + +char const *cart_cgb_support_description(u8 const flags) +{ + if (!BIT(flags, 7)) + return "unsupported"; + else if (BIT(flags, 2, 2)) + return "PGB mode"; + else if (BIT(flags, 6)) + return "required"; + else + return "enhanced"; +} + + +char const *cart_sgb_support_description(u8 const *header) +{ + if (0x33 != header[cartheader::OFFSET_LICENSEE - 0x100]) + return "unaware"; + else if (0x03 != header[cartheader::OFFSET_SGB - 0x100]) + return "unsupported"; + else + return "enhanced"; +} + + +char const *cart_type_description(u8 type) +{ + switch (type) + { + case cartheader::TYPE_ROM: + return "flat ROM"; + case cartheader::TYPE_MBC1: + return "Nintendo MBC1"; + case cartheader::TYPE_MBC1_RAM: + return "Nintendo MBC1 with RAM"; + case cartheader::TYPE_MBC1_RAM_BATT: + return "Nintendo MBC1 with battery-backed RAM"; + case cartheader::TYPE_MBC2: + return "Nintendo MBC2"; + case cartheader::TYPE_MBC2_BATT: + return "Nintendo MBC2 with backup battery"; + case cartheader::TYPE_ROM_RAM: + return "flat ROM with RAM"; + case cartheader::TYPE_ROM_RAM_BATT: + return "flat ROM with battery-backed RAM"; + case cartheader::TYPE_MMM01: + return "Nintendo MMM01"; + case cartheader::TYPE_MMM01_RAM: + return "Nintendo MMM01 with RAM"; + case cartheader::TYPE_MMM01_RAM_BATT: + return "Nintendo MMM01 with battery-backed RAM"; + case cartheader::TYPE_MBC3_RTC_BATT: + return "Nintendo MBC3 with backup battery and real-time clock crystal"; + case cartheader::TYPE_MBC3_RTC_RAM_BATT: + return "Nintendo MBC3 with battery-backed RAM and real-time clock crystal"; + case cartheader::TYPE_MBC3: + return "Nintendo MBC3"; + case cartheader::TYPE_MBC3_RAM: + return "Nintendo MBC3 with RAM"; + case cartheader::TYPE_MBC3_RAM_BATT: + return "Nintendo MBC3 with battery-backed RAM"; + case cartheader::TYPE_MBC5: + return "Nintendo MBC5"; + case cartheader::TYPE_MBC5_RAM: + return "Nintendo MBC5 with RAM"; + case cartheader::TYPE_MBC5_RAM_BATT: + return "Nintendo MBC5 with battery-backed RAM"; + case cartheader::TYPE_MBC5_RUMBLE: + return "Nintendo MBC5 with vibration motor"; + case cartheader::TYPE_MBC5_RUMBLE_RAM: + return "Nintendo MBC5 with RAM and vibration motor"; + case cartheader::TYPE_MBC5_RUMBLE_RAM_BATT: + return "Nintendo MBC5 with battery-backed RAM and vibration motor"; + case cartheader::TYPE_MBC6: + return "Nintendo MBC6 with Flash memory and battery-backed RAM"; + case cartheader::TYPE_MBC7_ACCEL_EEPROM: + return "Nintendo MBC7 with EEPROM and two-axis accelerometer"; + case cartheader::TYPE_UNLICENSED_YONGYONG: + return "Yong Yong pirate"; + case cartheader::TYPE_CAMERA: + return "Nintendo Game Boy Camera with battery-backed RAM"; + case cartheader::TYPE_TAMA5: + return "Bandai TAMA5 with battery-backed TAMA6 and real-time clock"; + case cartheader::TYPE_HUC3: + return "Hudson Soft HuC-3 with battery-baked RAM"; + case cartheader::TYPE_HUC1_RAM_BATT: + return "Hudson Soft HuC-1 with battery-baked RAM"; + default: + return "unknown"; + } +} + + +std::string cart_rom_size_description(u8 size, memory_region *romregion) +{ + auto const actual = romregion ? romregion->bytes() : 0U; + switch (size) + { + case cartheader::ROM_SIZE_32K: + case cartheader::ROM_SIZE_64K: + case cartheader::ROM_SIZE_128K: + case cartheader::ROM_SIZE_256K: + case cartheader::ROM_SIZE_512K: + return util::string_format( + "%u KiB actual %u%s", + 32U << (size - cartheader::ROM_SIZE_32K), + actual, + ((u32(0x8000) << (size - cartheader::ROM_SIZE_32K)) != actual) ? " MISMATCH!" : ""); + case cartheader::ROM_SIZE_1024K: + case cartheader::ROM_SIZE_2048K: + case cartheader::ROM_SIZE_4096K: + case cartheader::ROM_SIZE_8192K: + return util::string_format( + "%u MiB actual %u%s", + 1U << (size - cartheader::ROM_SIZE_1024K), + actual, + ((u32(0x8000) << (size - cartheader::ROM_SIZE_32K)) != actual) ? " MISMATCH!" : ""); + case cartheader::ROM_SIZE_1152K: + case cartheader::ROM_SIZE_1280K: + case cartheader::ROM_SIZE_1536K: + return util::string_format( + "%u KiB actual %u%s", + 0x400U | (0x80U << (size - cartheader::ROM_SIZE_1152K)), + actual, + ((u32(0x10'0000) | (u32(0x2'0000) << (size - cartheader::ROM_SIZE_1152K))) != actual) ? " MISMATCH!" : ""); + default: + return util::string_format("unknown actual %u", actual); + } +} + + +std::string cart_ram_size_description(u8 size) +{ + switch (size) + { + case cartheader::RAM_SIZE_0K: + return "none"; + case cartheader::RAM_SIZE_2K: + case cartheader::RAM_SIZE_8K: + case cartheader::RAM_SIZE_32K: + case cartheader::RAM_SIZE_128K: + return util::string_format("%u KiB", 2U << ((size - cartheader::RAM_SIZE_2K) << 1)); + case cartheader::RAM_SIZE_64K: + return "64 KiB"; + default: + return "unknown"; + } +} + + +char const *cart_region_description(u8 region) +{ + switch (region) + { + case 0: return "Japan"; + case 1: return "export"; + default: return "unknown"; + } +} + + +std::string cart_licensee_description(u8 const *header) +{ + // TODO: fill in and correct lists, distinguish duplicates + static constexpr std::pair BASE[] = { + { 0x01, "Nintendo" }, + { 0x08, "Capcom" }, { 0x09, "Hot-B" }, { 0x0a, "Jaleco" }, { 0x0b, "Coconuts Japan" }, + { 0x0c, "Elite Systems" }, + { 0x13, "Electronic Arts" }, + { 0x18, "Hudson Soft" }, { 0x19, "ITC Entertainment" }, { 0x1a, "Yanoman" }, + { 0x1d, "Japan Clary" }, { 0x1f, "Virgin" }, + { 0x24, "PCM Complete" }, { 0x25, "San-X" }, + { 0x28, "KEMCO" }, { 0x29, "Seta" }, + { 0x30, "Infogrames" }, { 0x31, "Nintendo" }, { 0x32, "Bandai" }, + { 0x34, "Konami" }, { 0x35, "HectorSoft" }, + { 0x38, "Capcom" }, { 0x39, "Banpresto" }, + { 0x3e, "Gremlin" }, + { 0x41, "Ubi Soft" }, { 0x42, "Atlus" }, + { 0x44, "Malibu" }, { 0x46, "Angel" }, { 0x47, "Spectrum Holobyte" }, + { 0x49, "Irem" }, { 0x4a, "Virgin" }, + { 0x4d, "Malibu" }, { 0x4f, "U.S. Gold" }, + { 0x50, "Absolute" }, { 0x51, "Acclaim" }, { 0x52, "Activision" }, { 0x53, "American Sammy" }, + { 0x54, "GameTek" }, { 0x55, "Park Place Productions" }, { 0x56, "LJN Entertainment" }, { 0x57, "Matchbox" }, + { 0x59, "Milton Bradley" }, { 0x5a, "Mindscape" }, { 0x5b, "Romstar" }, + { 0x5c, "Naxat Soft" }, { 0x5d, "Tradewest" }, + { 0x60, "Titus" }, { 0x61, "Virgin" }, + { 0x67, "Ocean" }, + { 0x69, "Electronic Arts" }, + { 0x6e, "Elite Systems" }, { 0x6f, "Electro Brain Corp." }, + { 0x70, "Infogrames" }, { 0x71, "Interplay" }, { 0x72, "Broderbund" }, { 0x73, "Sculptured Software" }, + { 0x75, "SCi Games" }, + { 0x78, "THQ" }, { 0x79, "Accolade" }, { 0x7a, "Triffix Entertainment" }, + { 0x7c, "Microprose" }, { 0x7f, "Kemco" }, + { 0x80, "Misawa Entertainment" }, { 0x83, "LOZC" }, + { 0x86, "Tokuma Shoten Intermedia " }, + { 0x8b, "Bullet-Proof Software" }, + { 0x8c, "Vic Tokai" }, { 0x8e, "Ape" }, { 0x8f, "I'Max Corp." }, + { 0x91, "Chunsoft Co." }, { 0x92, "Video System" }, { 0x93, "Tsuburaya Productions Co." }, + { 0x95, "Varie Corporation" }, { 0x97, "Kaneko" }, + { 0x99, "Arc" }, { 0x9a, "Nihon Bussan Co." }, { 0x9b, "Tecmo" }, + { 0x9c, "Imagineer" }, { 0x9d, "Banpresto" }, { 0x9f, "Nova" }, + { 0xa1, "Hori Electric Co." }, { 0xa2, "Bandai" }, + { 0xa4, "Konami" }, { 0xa6, "Kawada Co." }, { 0xa7, "Takara" }, + { 0xa9, "Technos Japan" }, { 0xaa, "Broderbund" }, + { 0xac, "Toei Animation" }, { 0xad, "Toho Co." }, { 0xaf, "Namco" }, + { 0xb0, "Acclaim" }, { 0xb1, "ASCII Corporation" }, { 0xb2, "Bandai" }, + { 0xb4, "Enix" }, { 0xb6, "HAL Laboratory" }, { 0xb7, "SNK" }, + { 0xb9, "Pony Canyon" }, { 0xba, "Culture Brain" }, { 0xbb, "Sunsoft" }, + { 0xbd, "Sony Imagesoft" }, { 0xbf, "Sammy" }, + { 0xc0, "Taito" }, { 0xc2, "Kemco" }, { 0xc3, "Square Soft" }, + { 0xc4, "Tokuma Shoten Intermedia" }, { 0xc5, "Data East" }, { 0xc6, "Tonkinhouse" }, + { 0xc8, "Koei" }, { 0xca, "Ultra" }, { 0xcb, "Vap" }, + { 0xcc, "Use Corporation" }, { 0xcd, "Meldac" }, { 0xce, "Pony Canyon" }, { 0xcf, "Angel" }, + { 0xd0, "Taito" }, { 0xd1, "Sofel" }, { 0xd2, "quest" }, { 0xd3, "Sigma Enterprises" }, + { 0xd4, "ASK Kodansha Co." }, { 0xd6, "Naxat Soft" }, { 0xd7, "Copya System" }, + { 0xd9, "Banpresto" }, { 0xda, "Tomy" }, { 0xdb, "LJN Entertainment" }, + { 0xdd, "NCS Corporation" }, { 0xde, "Human" }, { 0xdf, "Altron Corporation" }, + { 0xe0, "Jaleco" }, { 0xe1, "Towa Chiki" }, { 0xe2, "Yutaka" }, { 0xe3, "Varie Corporation" }, + { 0xe5, "Epoch Co." }, { 0xe7, "Athena" }, + { 0xe8, "Asmik Ace Entertainment" }, { 0xe9, "Natsume" }, { 0xea, "King Records Co." }, { 0xeb, "Atlus" }, + { 0xec, "Epic/Sony Records" }, { 0xee, "IGS Co." }, + { 0xf0, "A Wave" }, { 0xf3, "Extreme Entertainment Group" }, + { 0xff, "LJN Entertainment" } }; + static constexpr std::pair EXTENDED[] = { + { 0x3031, "Nintendo R&D1" }, + { 0x3038, "Capcom" }, + { 0x3133, "Electronic Arts" }, + { 0x3138, "Hudson Soft" }, + { 0x3139, "b-ai" }, + { 0x3230, "KSS" }, + { 0x3232, "pow" }, + { 0x3234, "PCM Complete" }, + { 0x3235, "San-X" }, + { 0x3238, "Kemco Japan" }, + { 0x3239, "Seta" }, + { 0x3330, "Viacom" }, + { 0x3331, "Nintendo" }, + { 0x3332, "Bandai" }, + { 0x3333, "Ocean/Acclaim" }, + { 0x3334, "Konami" }, + { 0x3335, "HectorSoft" }, + { 0x3337, "Taito" }, + { 0x3338, "Hudson" }, + { 0x3339, "Banpresto" }, + { 0x3431, "Ubi Soft" }, + { 0x3432, "Atlus" }, + { 0x3434, "Malibu" }, + { 0x3436, "Angel" }, + { 0x3437, "Bullet-Proof Software" }, + { 0x3439, "Irem" }, + { 0x3530, "Absolute" }, + { 0x3531, "Acclaim" }, + { 0x3532, "Activision" }, + { 0x3533, "American Sammy" }, + { 0x3534, "Konami" }, + { 0x3535, "Hi Tech Entertainment" }, + { 0x3536, "LJN Entertainment" }, + { 0x3537, "Matchbox" }, + { 0x3538, "Mattel" }, + { 0x3539, "Milton Bradley" }, + { 0x3630, "Titus" }, + { 0x3631, "Virgin" }, + { 0x3634, "LucasArts" }, + { 0x3637, "Ocean" }, + { 0x3639, "Electronic Arts" }, + { 0x3730, "Infogrames" }, + { 0x3731, "Interplay" }, + { 0x3732, "Broderbund" }, + { 0x3733, "Sculptured Software" }, + { 0x3735, "SCi Games" }, + { 0x3738, "THQ" }, + { 0x3739, "Accolade" }, + { 0x3830, "Misawa Entertainment" }, + { 0x3833, "LOZC" }, + { 0x3836, "Tokuma Shoten Intermedia" }, + { 0x3837, "Tsukuda Original" }, + { 0x3931, "Chunsoft Co." }, + { 0x3932, "Video System" }, + { 0x3933, "Ocean/Acclaim" }, + { 0x3935, "Varie Corporation" }, + { 0x3936, "Yonezawa/s’pal" }, + { 0x3937, "Kaneko" }, + { 0x3939, "Pack-In-Video Co." }, + { 0x4134, "Konami (Yu-Gi-Oh!)" } }; + + // 0x33 means two bytes of the title have been repurposed + u8 const licensee = header[cartheader::OFFSET_LICENSEE - 0x100]; + if (0x33 == licensee) + { + u16 const ext = (u16(header[cartheader::OFFSET_LICENSEE_EXT - 0x100]) << 8) | header[cartheader::OFFSET_LICENSEE_EXT + 1 - 0x100]; + auto const found = std::lower_bound( + std::begin(EXTENDED), + std::end(EXTENDED), + ext, + [] (auto const &x, auto const &y) { return x.first < y; }); + if ((std::end(EXTENDED) != found) && (found->first == ext)) + return util::string_format("0x%02X (%s)", ext, found->second); + else + return util::string_format("0x%04X (unknown)", ext); + } + else + { + auto const found = std::lower_bound( + std::begin(BASE), + std::end(BASE), + licensee, + [] (auto const &x, auto const &y) { return x.first < y; }); + if ((std::end(BASE) != found) && (found->first == licensee)) + return util::string_format("0x%02X (%s)", licensee, found->second); + else + return util::string_format("0x%02X (unknown)", licensee); + } +} + + +u32 get_mmm01_initial_low_page(u64 length) +{ + // this is inefficient but it works, offloading the logic to a common helper + std::vector pages; + auto const mask = device_generic_cart_interface::map_non_power_of_two( + unsigned(length / 0x4000), + [&pages] (unsigned entry, unsigned page) { pages.emplace_back(page); }); + return u32(pages[0x01fe & mask] * u32(0x4000)); +} + + +bool is_wisdom_tree(std::string_view tag, util::random_read &file, u64 length, u64 offset, u8 const *header) +{ + // all Wisdom Tree games declare 32 KiB ROM + if (cartheader::ROM_SIZE_32K != header[cartheader::OFFSET_ROM_SIZE - 0x100]) + return false; + + // no Wisdom Tree game declares cartridge RAM + if (cartheader::RAM_SIZE_0K != header[cartheader::OFFSET_RAM_SIZE - 0x100]) + return false; + + // 8 MiB maximum supported program ROM + if ((u32(0x8000) << 8) < length) + return false; + + // only makes sense with more than 32 KiB program ROM + if (0x8000 >= length) + return false; + + // must be multiple of 32 KiB program ROM page size + if (length & 0x7fff) + return false; + + // assume Wisdom Tree + osd_printf_verbose( + "[%s] Assuming 0x%06X-byte cartridge declaring 32 KiB ROM and no RAM uses Wisdom Tree banking\n", + tag, + length); + return true; +} + + +bool is_m161(std::string_view tag, util::random_read &file, u64 length, u64 offset, u8 const *header) +{ + // supports eight 32 KiB banks at most, doesn't make sense without at least two banks + if (((u32(0x8000) << 3) < length) || (0x8000 >= length) || (length & 0x7fff)) + return false; + + // doesn't support banked RAM + u8 const ramsize(header[cartheader::OFFSET_RAM_SIZE - 0x100]); + switch (ramsize) + { + case cartheader::RAM_SIZE_0K: + case cartheader::RAM_SIZE_2K: + case cartheader::RAM_SIZE_8K: + break; + default: + return false; + } + + // check header checksum for the first page + if (!cartheader::verify_header_checksum(header)) + return false; + + // there's one known cartridge using this scheme + static constexpr u8 KNOWN_COLLECTIONS[][0x10] = { + { 'T', 'E', 'T', 'R', 'I', 'S', ' ', 'S', 'E', 'T', 0, 0, 0, 0, 0, 0 } }; + auto const known( + std::find_if( + std::begin(KNOWN_COLLECTIONS), + std::end(KNOWN_COLLECTIONS), + [header] (auto const &name) + { + return std::equal(std::begin(name), std::end(name), &header[cartheader::OFFSET_TITLE - 0x100]); + })); + if (std::end(KNOWN_COLLECTIONS) != known) + { + osd_printf_verbose("[%s] Detected known M161 game title\n", tag); + return true; + } + + // look for a valid header in the second page + u8 header2[0x50]; + size_t actual; + if (file.read_at(offset + 0x8000 + 0x0100, header2, sizeof(header2), actual) || (sizeof(header2) != actual)) + return false; + if (!cartheader::verify_header_checksum(header2)) + return false; + + // make sure it fits with the cartridge RAM size declaration + switch (header2[cartheader::OFFSET_TYPE - 0x100]) + { + case cartheader::TYPE_ROM: + osd_printf_verbose("[%s] Detected valid plain ROM game header at 0x8000, assuming M161 cartridge\n", tag); + return true; + case cartheader::TYPE_ROM_RAM: + case cartheader::TYPE_ROM_RAM_BATT: + switch (header[cartheader::OFFSET_RAM_SIZE - 0x100]) + { + case cartheader::RAM_SIZE_2K: + if (cartheader::RAM_SIZE_0K != ramsize) + { + osd_printf_verbose( + "[%s] Detected valid plain ROM + 2 KiB RAM game header at 0x8000, assuming M161 cartridge\n", + tag); + return true; + } + return false; + case cartheader::RAM_SIZE_8K: + if (cartheader::RAM_SIZE_8K == ramsize) + { + osd_printf_verbose( + "[%s] Detected valid plain ROM + 8 KiB RAM game header at 0x8000, assuming M161 cartridge\n", + tag); + return true; + } + return false; + } + return false; + default: + return false; + } +} + + +char const *guess_mbc7_type(std::string_view tag, util::random_read &file, u64 length, u64 offset, u8 const *header) +{ + // there are only two games using MBC7 - by coincidence, the larger program requires the larger EEPROM + if (0x100000 < length) + { + osd_printf_verbose("[%s] Assuming 0x%06X-byte MBC7 program expects 4 Kibit 93LC66 EEPROM\n", tag, length); + return slotoptions::GB_MBC7_4K; + } + else + { + osd_printf_verbose("[%s] Assuming 0x%06X-byte MBC7 program expects 2 Kibit 93LC56 EEPROM\n", tag, length); + return slotoptions::GB_MBC7_2K; + } +} + + +bool read_gbx_footer_trailer(util::random_read &file, u64 length, gbxfile::trailer &trailer) +{ + if (sizeof(trailer) >= length) + return false; + + size_t actual; + if (file.read_at(length - sizeof(trailer), &trailer, sizeof(trailer), actual)) + return false; + if (sizeof(trailer) != actual) + return false; + + trailer.swap(); + return (gbxfile::MAGIC_GBX == trailer.magic) && (length >= trailer.size); +} + + +std::optional probe_gbx_footer(std::string_view tag, util::random_read &file, u64 &length, u64 &offset) +{ + // first try to read the trailer and look for a recognised version + gbxfile::trailer trailer; + if (!read_gbx_footer_trailer(file, length, trailer)) + return std::nullopt; + else if ((1 != trailer.ver_maj) || ((sizeof(trailer) + sizeof(gbxfile::leader_1_0)) > trailer.size)) + return std::nullopt; + + // need to read the footer leader + gbxfile::leader_1_0 leader; + size_t actual; + if (file.read_at(length - trailer.size, &leader, sizeof(leader), actual) || (sizeof(leader) != actual)) + { + osd_printf_warning( + "[%s] Error reading GBX trailer leader - assuming file is not GBX format\n", + tag); + return std::nullopt; + } + leader.swap(); + + // if the ROM would overlap the footer, assume it isn't GBX format + if ((length - trailer.size) < leader.rom_bytes) + return std::nullopt; + + // if we got this far, trust the program ROM size declared in the footer + osd_printf_verbose( + "[%s] Found GBX footer version %u.%u declaring 0x%06X-byte ROM\n", + tag, + trailer.ver_maj, + trailer.ver_min, + leader.rom_bytes); + length = leader.rom_bytes; + offset = 0; + + // map known GBX cartridge types to slot options + char const *result = nullptr; + switch (leader.cart_type) + { + case gbxfile::TYPE_PLAIN: + result = slotoptions::GB_STD; + break; + case gbxfile::TYPE_M161: + result = slotoptions::GB_M161; + break; + case gbxfile::TYPE_TAMA5: + result = slotoptions::GB_TAMA5; + break; + case gbxfile::TYPE_WISDOM: + result = slotoptions::GB_WISDOM; + break; + case gbxfile::TYPE_SACHEN1: + result = slotoptions::GB_SACHEN1; + break; + case gbxfile::TYPE_SACHEN2: + result = slotoptions::GB_SACHEN2; + break; + case gbxfile::TYPE_ROCKET: + result = slotoptions::GB_ROCKET; + break; + case gbxfile::TYPE_MBC1: + case gbxfile::TYPE_MBC1_COLL: + result = slotoptions::GB_MBC1; + break; + case gbxfile::TYPE_MBC2: + result = slotoptions::GB_MBC2; + break; + case gbxfile::TYPE_MBC3: + result = slotoptions::GB_MBC3; + break; + case gbxfile::TYPE_MBC5: + result = slotoptions::GB_MBC5; + break; + case gbxfile::TYPE_MBC7: + { + // need to probe for EEPROM size + // TODO: does the GBX footer declare the EEPROM size as cartridge RAM size? + u8 header[0x50]; + if (!file.read_at(offset + 0x100, header, sizeof(header), actual) && (sizeof(header) == actual)) + { + result = guess_mbc7_type(tag, file, length, offset, header); + } + else + { + osd_printf_warning( + "[%s] Error reading cartridge header - defaulting to 93LC66 EEPROM for MBC7\n", + tag); + result = slotoptions::GB_MBC7_4K; + } + } + break; + case gbxfile::TYPE_MMM01: + result = slotoptions::GB_MMM01; + break; + case gbxfile::TYPE_HUC1: + result = slotoptions::GB_HUC1; + break; + case gbxfile::TYPE_HUC3: + result = slotoptions::GB_HUC3; + break; + case gbxfile::TYPE_CAMERA: + result = slotoptions::GB_CAMERA; + break; + case gbxfile::TYPE_LICHENG: + result = slotoptions::GB_LICHENG; + break; + case gbxfile::TYPE_SINTAX: + result = slotoptions::GB_SINTAX; + break; + case gbxfile::TYPE_VF001: + result = slotoptions::GB_VF001; + break; + } + if (result) + { + osd_printf_verbose( + "[%s] GBX cartridge type 0x%08X mapped to slot option '%s'\n", + tag, + leader.cart_type, + result); + } + else + { + osd_printf_verbose( + "[%s] Unknown GBX cartridge type 0x%08X\n", + tag, + leader.cart_type); + } + return result; +} + + +char const *guess_image_format(std::string_view tag, util::random_read &file, u64 &length, u64 &offset) +{ + // probe for GBX format - this can set the length and offset while still failing to select a slot option + std::optional const gbxoption = probe_gbx_footer(tag, file, length, offset); + if (gbxoption) + return *gbxoption; + + // some ROM images apparently have a 512-byte header + if ((0x4000 < length) && ((length & 0x3fff) == 0x0200)) + { + osd_printf_verbose("[%s] Assuming 0x%06X-byte cartridge file contains 0x0200-byte header\n", tag, length); + length -= 0x2000; + offset = 0x0200; + return nullptr; + } + + // assume it's a common .gb plain ROM image file + osd_printf_verbose("[%s] Assuming 0x%06X-byte cartridge file is a plain ROM image\n", tag, length); + offset = 0; + return nullptr; +} + + +bool detect_mmm01(std::string_view tag, util::random_read &file, u64 length, u64 offset, u8 const *header) +{ + // MMM01 cartridges must be a multiple of 16 KiB no larger than 8 MiB + if ((length & 0x3fff) || ((u32(0x4000) << 9) < length)) + return false; + + + // now check for a valid cartridge header + u32 const lastpage(get_mmm01_initial_low_page(length)); + u8 backheader[0x50]; + size_t actual; + if (file.read_at(lastpage + 0x100, backheader, sizeof(backheader), actual) || (sizeof(backheader) != actual)) + { + osd_printf_warning( + "[%s] Error reading last page of program ROM - assuming cartridge does not use MMM01 controller\n", + tag); + return false; + } + if (!cartheader::verify_header_checksum(backheader)) + return false; + + + // well-behaved game that declares MMM01 here + u8 const backtype(backheader[cartheader::OFFSET_TYPE - 0x100]); + switch (backtype) + { + case cartheader::TYPE_MMM01: + case cartheader::TYPE_MMM01_RAM: + case cartheader::TYPE_MMM01_RAM_BATT: + osd_printf_verbose( + "[%s] Found MMM01 header at 0x%06X, assuming cartridge uses MMM01 header\n", + tag, + lastpage); + return true; + case cartheader::TYPE_MBC3: + { + static constexpr u8 KNOWN_COLLECTIONS[][0x10] = { + { 'B', 'O', 'U', 'K', 'E', 'N', 'J', 'I', 'M', 'A', '2', ' ', 'S', 'E', 'T', 0 }, + { 'B', 'U', 'B', 'B', 'L', 'E', 'B', 'O', 'B', 'B', 'L', 'E', ' ', 'S', 'E', 'T' }, + { 'G', 'A', 'N', 'B', 'A', 'R', 'U', 'G', 'A', ' ', 'S', 'E', 'T', 0, 0, 0 }, + { 'R', 'T', 'Y', 'P', 'E', ' ', '2', ' ', 'S', 'E', 'T', 0, 0, 0, 0, 0 } }; + auto const known( + std::find_if( + std::begin(KNOWN_COLLECTIONS), + std::end(KNOWN_COLLECTIONS), + [backheader] (auto const &name) + { + return std::equal(std::begin(name), std::end(name), &backheader[cartheader::OFFSET_TITLE - 0x100]); + })); + if (std::end(KNOWN_COLLECTIONS) != known) + { + osd_printf_verbose("[%s] Detected known MMM01 game title\n", tag); + return true; + } + } + break; + } + + // not a recognised MMM01 game + return false; +} + + +char const *guess_cart_type(std::string_view tag, util::random_read &file, u64 length, u64 offset, u8 const *header) +{ + u8 const carttype = header[cartheader::OFFSET_TYPE - 0x0100]; + switch (carttype) + { + case cartheader::TYPE_ROM: + if (is_wisdom_tree(tag, file, length, offset, header)) + return slotoptions::GB_WISDOM; + return slotoptions::GB_STD; + case cartheader::TYPE_MBC1: + case cartheader::TYPE_MBC1_RAM: + case cartheader::TYPE_MBC1_RAM_BATT: + return slotoptions::GB_MBC1; + // 0x04 + case cartheader::TYPE_MBC2: + case cartheader::TYPE_MBC2_BATT: + return slotoptions::GB_MBC2; + case cartheader::TYPE_ROM_RAM: + case cartheader::TYPE_ROM_RAM_BATT: + return slotoptions::GB_STD; + // 0x09 + // 0x0a + case cartheader::TYPE_MMM01: + case cartheader::TYPE_MMM01_RAM: + case cartheader::TYPE_MMM01_RAM_BATT: + return slotoptions::GB_MMM01; + // 0x0e + case cartheader::TYPE_MBC3_RTC_BATT: + return slotoptions::GB_MBC3; + case cartheader::TYPE_MBC3_RTC_RAM_BATT: + if (is_m161(tag, file, length, offset, header)) + return slotoptions::GB_M161; + return slotoptions::GB_MBC3; + case cartheader::TYPE_MBC3: + case cartheader::TYPE_MBC3_RAM: + case cartheader::TYPE_MBC3_RAM_BATT: + return slotoptions::GB_MBC3; + // 0x14 + case cartheader::TYPE_MBC5: + case cartheader::TYPE_MBC5_RAM: + case cartheader::TYPE_MBC5_RAM_BATT: + case cartheader::TYPE_MBC5_RUMBLE: + case cartheader::TYPE_MBC5_RUMBLE_RAM: + case cartheader::TYPE_MBC5_RUMBLE_RAM_BATT: + return slotoptions::GB_MBC5; + // 0x1f + case cartheader::TYPE_MBC6: + return slotoptions::GB_MBC6; + // 0x21 + case cartheader::TYPE_MBC7_ACCEL_EEPROM: + return guess_mbc7_type(tag, file, length, offset, header); + + case cartheader::TYPE_UNLICENSED_YONGYONG: + return slotoptions::GB_YONG; + + case cartheader::TYPE_CAMERA: + return slotoptions::GB_CAMERA; + case cartheader::TYPE_TAMA5: + return slotoptions::GB_TAMA5; + case cartheader::TYPE_HUC3: + return slotoptions::GB_HUC3; + case cartheader::TYPE_HUC1_RAM_BATT: + return slotoptions::GB_HUC1; + } + + if (0x8000 >= length) + { + osd_printf_warning( + "[%s] Unknown cartridge type 0x%02X - defaulting to flat ROM for 0x%04X-byte cartridge\n", + tag, + carttype, + length); + return slotoptions::GB_STD; + } + else if ((u32(0x4000) << 7) >= length) + { + osd_printf_warning( + "[%s] Unknown cartridge type 0x%02X - defaulting to MBC1 for 0x%06X-byte cartridge\n", + tag, + carttype, + length); + return slotoptions::GB_MBC1; + } + else + { + osd_printf_warning( + "[%s] Unknown cartridge type 0x%02X - defaulting to MBC5 for 0x%06X-byte cartridge\n", + tag, + carttype, + length); + return slotoptions::GB_MBC5; + } +} + +} // anonymous namespace + +} // namespace bus::gameboy + + + +//************************************************************************** +// gb_cart_slot_device +//************************************************************************** + +gb_cart_slot_device::gb_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + gb_cart_slot_device_base(mconfig, GB_CART_SLOT, tag, owner, clock) +{ +} + + +std::string gb_cart_slot_device::get_default_card_software(get_default_card_software_hook &hook) const +{ + using namespace bus::gameboy; + + if (hook.image_file()) + { + u64 len; + if (hook.image_file()->length(len)) + { + // if we can't get the file, use MBC1 - it's a common mapper and also works with flat ROMs + osd_printf_warning("[%s] Error getting cartridge ROM length - defaulting to MBC1\n", tag()); + return slotoptions::GB_MBC1; + } + + u64 offset; + char const *const guess(guess_image_format(tag(), *hook.image_file(), len, offset)); + if (guess) + return guess; + + u8 header[0x50]; + size_t actual; + if (hook.image_file()->read_at(offset + 0x0100, header, sizeof(header), actual) || (sizeof(header) != actual)) + { + // reading header failed - guess based on size + if (0x8000 >= len) + { + osd_printf_warning( + "[%s] Error reading cartridge header - defaulting to flat ROM for 0x%04X-byte cartridge\n", + tag(), + len); + return slotoptions::GB_STD; + } + else if ((u32(0x4000) << 7) >= len) + { + osd_printf_warning( + "[%s] Error reading cartridge header - defaulting to MBC1 for 0x%06X-byte cartridge\n", + tag(), + len); + return slotoptions::GB_MBC1; + } + else + { + osd_printf_warning( + "[%s] Error reading cartridge header - defaulting to MBC5 for 0x%06X-byte cartridge\n", + tag(), + len); + return slotoptions::GB_MBC5; + } + } + + // MMM01 controller doesn't initially map the first page + if (detect_mmm01(tag(), *hook.image_file(), len, offset, header)) + return slotoptions::GB_MMM01; + else + return guess_cart_type(tag(), *hook.image_file(), len, offset, header); + } + + // this will return the explicit setting for a software list item + return software_get_default_slot(slotoptions::GB_STD); +} + + +void gb_cart_slot_device::device_reset_after_children() +{ + using namespace bus::gameboy; + + gb_cart_slot_device_base::device_reset_after_children(); + + // read the header as seen by the system on reset + u8 header[0x50]; + { + auto const suppressor = machine().disable_side_effects(); + for (unsigned i = 0; std::size(header) > i; ++i) + header[i] = cart_space().read_byte(0x100 + i); + } + + // get some fields that are used multiple times + u8 const cgb = header[cartheader::OFFSET_CGB - 0x100]; + u8 const type = header[cartheader::OFFSET_TYPE - 0x100]; + u8 const romsize = header[cartheader::OFFSET_ROM_SIZE - 0x100]; + u8 const ramsize = header[cartheader::OFFSET_RAM_SIZE - 0x100]; + u8 const region = header[cartheader::OFFSET_REGION - 0x100]; + u8 const headerchecksum = header[cartheader::OFFSET_CHECKSUM_HEADER - 0x100]; + u16 const romchecksum = (u16(header[cartheader::OFFSET_CHECKSUM_ROM - 0x100]) << 8) | header[cartheader::OFFSET_CHECKSUM_ROM + 1 - 0x100]; + + // calculate checksums + u8 const headercalc = cartheader::checksum( + &header[cartheader::OFFSET_TITLE - 0x100], + &header[cartheader::OFFSET_CHECKSUM_HEADER - 0x100]); + u16 romcalc = 0U - (romchecksum >> 8) - (romchecksum & 0x00ff); + memory_region *const romregion = memregion("rom"); + if (romregion) + { + u8 const *const data = &romregion->as_u8(); + romcalc = std::accumulate(&data[0], &data[romregion->bytes()], romcalc); + } + + // log parsed header for informational purposes + logerror( + "Cartridge header information:\n" + " * Entry point: %s\n" + " * Title: %s\n" + " * Game Boy Color: %s\n" + " * Super Game Boy: 0x%02X (%s)\n" + " * Type: 0x%02X (%s)\n" + " * ROM size: 0x%02X (%s)\n" + " * RAM size: 0x%02X (%s)\n" + " * Region: 0x%02X (%s)\n" + " * Licensee: %s\n" + " * Revision: 0x%02X\n" + " * Header checksum: 0x%02X (calculated 0x%02X%s)\n" + " * ROM checksum: 0x%04X (calculated 0x%04X%s)\n", + disassemble_entry_point(header), + cart_title_text(header), + cart_cgb_support_description(cgb), + header[cartheader::OFFSET_SGB - 0x100], + cart_sgb_support_description(header), + type, + cart_type_description(type), + romsize, + cart_rom_size_description(romsize, romregion), + ramsize, + cart_ram_size_description(ramsize), + region, + cart_region_description(region), + cart_licensee_description(header), + header[cartheader::OFFSET_REVISION - 0x100], + headerchecksum, + headercalc, + (headerchecksum != headercalc) ? " MISMATCH!" : "", + romchecksum, + romcalc, + (romchecksum != romcalc) ? " MISMATCH!" : ""); +} + + +image_init_result gb_cart_slot_device::load_image_file(util::random_read &file) +{ + using namespace bus::gameboy; + + bool proberam = true; + auto len = length(); + u64 offset; + size_t actual; + + // probe for GBX format + memory_region *gbxregion = nullptr; + gbxfile::trailer gbxtrailer; + if (read_gbx_footer_trailer(file, len, gbxtrailer)) + { + // try reading the GBX footer into temporary space before more checks + std::unique_ptr const footer(new (std::nothrow) u8 [gbxtrailer.size]); + if (!footer) + { + seterror(image_error::UNSPECIFIED, "Error allocating memory to read GBX file footer"); + return image_init_result::FAIL; + } + if (file.read_at(len - gbxtrailer.size, footer.get(), gbxtrailer.size, actual) || (gbxtrailer.size != actual)) + { + seterror(image_error::UNSPECIFIED, "Error reading GBX file footer"); + return image_init_result::FAIL; + } + if (1 != gbxtrailer.ver_maj) + { + // some unsupported GBX version - assume footer immediately follows ROM + osd_printf_verbose( + "[%s] Found found unsupported GBX file footer version %u.%u (%u bytes)\n", + tag(), + gbxtrailer.ver_maj, + gbxtrailer.ver_min, + gbxtrailer.size); + LOG("Allocating %u byte GBX file footer region\n", gbxtrailer.size); + gbxregion = machine().memory().region_alloc(subtag("gbx"), gbxtrailer.size, 1, ENDIANNESS_BIG); + std::memcpy(gbxregion->base(), footer.get(), gbxtrailer.size); + offset = 0; + len -= gbxtrailer.size; + } + else if ((sizeof(gbxfile::leader_1_0) + sizeof(gbxtrailer)) > gbxtrailer.size) + { + osd_printf_verbose( + "[%s] %u-byte GBX footer is too small to contain %u-byte leader and %u-byte trailer, assuming file is not GBX format\n", + tag(), + gbxtrailer.size, + sizeof(gbxfile::leader_1_0), + sizeof(gbxtrailer)); + } + else + { + // ensure ROM doesn't overlap leader + gbxfile::leader_1_0 leader; + std::memcpy(&leader, footer.get(), sizeof(leader)); + leader.swap(); + if ((len - gbxtrailer.size) < leader.rom_bytes) + { + osd_printf_verbose( + "[%s] GBX footer specifies 0x%06X-byte ROM which exceeds 0x%06X-byte content, assuming file is not GBX format\n", + tag(), + leader.rom_bytes, + len - gbxtrailer.size); + } + else + { + // looks like supported GBX format + osd_printf_verbose( + "[%s] Found GBX file footer version %u.%u (%u bytes):\n" + " * %u bytes ROM\n" + " * %u bytes RAM\n" + "%s" + "%s" + "%s", + tag(), + gbxtrailer.ver_maj, + gbxtrailer.ver_min, + gbxtrailer.size, + leader.rom_bytes, + leader.ram_bytes, + leader.batt ? " * backup battery\n" : "", + leader.rumble ? " * vibration motor\n" : "", + leader.rtc ? " * real-time clock oscillator\n" : ""); + proberam = false; + offset = 0; + len = leader.rom_bytes; + + // copy the footer in case the cart needs it + LOG("Allocating %u byte GBX file footer region\n", gbxtrailer.size); + gbxregion = machine().memory().region_alloc(subtag("gbx"), gbxtrailer.size, 1, ENDIANNESS_BIG); + std::memcpy(gbxregion->base(), footer.get(), gbxtrailer.size); + + // allocate RAM if appropriate + if (leader.ram_bytes) + { + LOG( + "Allocating %u byte cartridge %sRAM region\n", + leader.ram_bytes, + leader.batt ? "non-volatile " : ""); + machine().memory().region_alloc(subtag(leader.batt ? "nvram" : "ram"), leader.ram_bytes, 1, ENDIANNESS_LITTLE); + } + } + } + } + + // if it doesn't appear to be GBX format, check for apparent 512-byte header + if (!gbxregion) + { + if ((0x4000 < len) && ((len & 0x3fff) == 0x0200)) + { + osd_printf_verbose("[%s] Assuming 0x%06X-byte cartridge file contains 0x0200-byte header\n", tag(), len); + len -= 0x2000; + offset = 0x0200; + } + else + { + offset = 0; + } + } + + // load program ROM + if (len) + { + LOG("Allocating %u byte cartridge ROM region\n", len); + memory_region *const romregion = machine().memory().region_alloc(subtag("rom"), len, 1, ENDIANNESS_LITTLE); + if (file.read_at(offset, romregion->base(), len, actual) || (len != actual)) + { + seterror(image_error::UNSPECIFIED, "Error reading ROM data from cartridge file"); + return image_init_result::FAIL; + } + + // allocate cartridge RAM based on header if necessary + if (proberam) + { + u8 const *const rombase = &romregion->as_u8(); + u32 basepage = 0; + if (get_card_device()->device().type() == GB_ROM_MMM01) + basepage = get_mmm01_initial_low_page(len); + if ((basepage + cartheader::OFFSET_CHECKSUM_ROM) <= len) + allocate_cart_ram(&rombase[basepage]); + } + } + + return image_init_result::PASS; +} + + +void gb_cart_slot_device::allocate_cart_ram(u8 const *basepage) +{ + using namespace bus::gameboy; + + if (cartheader::verify_header_checksum(&basepage[0x100])) + { + osd_printf_verbose("[%s] Detecting cartridge RAM size/type from header\n", tag()); + bool nonvolatile; + u8 const carttype = basepage[cartheader::OFFSET_TYPE]; + switch (carttype) + { + case cartheader::TYPE_MBC1_RAM_BATT: + case cartheader::TYPE_MBC2_BATT: + case cartheader::TYPE_ROM_RAM_BATT: + case cartheader::TYPE_MMM01_RAM_BATT: + case cartheader::TYPE_MBC3_RTC_BATT: + case cartheader::TYPE_MBC3_RTC_RAM_BATT: + case cartheader::TYPE_MBC3_RAM_BATT: + case cartheader::TYPE_MBC5_RAM_BATT: + case cartheader::TYPE_MBC5_RUMBLE_RAM_BATT: + case cartheader::TYPE_CAMERA: + case cartheader::TYPE_TAMA5: + case cartheader::TYPE_HUC3: + case cartheader::TYPE_HUC1_RAM_BATT: + osd_printf_verbose( + "[%s] Cartridge type 0x%02X assumed to preserve cartridge RAM contents\n", + tag(), + carttype); + nonvolatile = true; + break; + default: + nonvolatile = false; + } + + u32 rambytes; + if ((cartheader::TYPE_MBC2 == carttype) || (cartheader::TYPE_MBC2_BATT == carttype)) + { + osd_printf_verbose( + "[%s] Cartridge type 0x%02X has internal 512 nybble static RAM\n", + tag(), + carttype); + rambytes = 512; + } + else + { + u8 const ramsize = basepage[cartheader::OFFSET_RAM_SIZE]; + switch (ramsize) + { + default: + osd_printf_warning("[%s] Cartridge declares unknown RAM size 0x%02X\n", tag(), ramsize); + [[fallthrough]]; + case cartheader::RAM_SIZE_0K: + rambytes = 0; + break; + case cartheader::RAM_SIZE_2K: + rambytes = 2 * 1024; + break; + case cartheader::RAM_SIZE_8K: + rambytes = 8 * 1024; + break; + case cartheader::RAM_SIZE_32K: + rambytes = 32 * 1024; + break; + case cartheader::RAM_SIZE_128K: + rambytes = 128 * 1024; + break; + case cartheader::RAM_SIZE_64K: + rambytes = 64 * 1024; + break; + } + if (rambytes) + { + osd_printf_verbose( + "[%s] Cartridge declares RAM size 0x%02X corresponding to %u bytes\n", + tag(), + ramsize, + rambytes); + } + } + + if (rambytes) + { + LOG( + "Allocating %u byte cartridge %sRAM region\n", + rambytes, + nonvolatile ? "non-volatile " : ""); + machine().memory().region_alloc(subtag(nonvolatile ? "nvram" : "ram"), rambytes, 1, ENDIANNESS_LITTLE); + } + } +} + + + +//************************************************************************** +// Device type definitions +//************************************************************************** + +DEFINE_DEVICE_TYPE(GB_CART_SLOT, gb_cart_slot_device, "gb_cart_slot", "Game Boy Cartridge Slot") diff --git a/src/devices/bus/gameboy/gbslot.h b/src/devices/bus/gameboy/gbslot.h new file mode 100644 index 00000000000..c1444a7905d --- /dev/null +++ b/src/devices/bus/gameboy/gbslot.h @@ -0,0 +1,87 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy cartridge slot + + Cartridge edge connector (0.05" pitch single-sided): + Pin Name Description + 1 VCC +5 V DC power + 2 PHI CPU clock output + 3 /WR write strobe + 4 /RD read strobe + 5 /CS RAM chip select (active for address 0xa000-0xdfff) + 6 A0 address bit 0 (least significant) + 7 A1 address bit 1 + 8 A2 address bit 2 + 9 A3 address bit 3 + 10 A4 address bit 4 + 11 A5 address bit 5 + 12 A6 address bit 6 + 13 A7 address bit 7 + 14 A8 address bit 8 + 15 A9 address bit 9 + 16 A10 address bit 10 + 17 A11 address bit 11 + 18 A12 address bit 12 + 19 A13 address bit 13 + 20 A14 address bit 14 + 21 A15 address bit 15 (most significant) + 22 D0 data bit 0 (least significant) + 23 D1 data bit 1 + 24 D2 data bit 2 + 25 D3 data bit 3 + 26 D4 data bit 4 + 27 D5 data bit 5 + 28 D6 data bit 6 + 29 D7 data bit 7 (most significant) + 30 /RST system reset + 31 AUDIOIN connected to SoC VIN pin (only used by some music players) + 32 GND Ground + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_GBSLOT_H +#define MAME_BUS_GAMEBOY_GBSLOT_H + +#pragma once + +#include "slot.h" + +#include + + +class gb_cart_slot_device : public gb_cart_slot_device_base +{ +public: + template + gb_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, T &&opts, char const *dflt) : + gb_cart_slot_device(mconfig, tag, owner, 0U) + { + option_reset(); + opts(*this); + set_default_option(dflt); + set_fixed(false); + } + gb_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + // device_image_interface implementation + virtual const char *image_interface() const noexcept override { return "gameboy_cart"; } + virtual const char *file_extensions() const noexcept override { return "bin,gb,gbc,gbx"; } + + // device_slot_interface implementation + virtual std::string get_default_card_software(get_default_card_software_hook &hook) const override ATTR_COLD; + +protected: + // device_t implementation + virtual void device_reset_after_children() override ATTR_COLD; + + // gb_cart_slot_device_base implementation + virtual image_init_result load_image_file(util::random_read &file) override ATTR_COLD; + + void allocate_cart_ram(u8 const *basepage) ATTR_COLD; +}; + + +DECLARE_DEVICE_TYPE(GB_CART_SLOT, gb_cart_slot_device) + +#endif // MAME_BUS_GAMEBOY_GBSLOT_H diff --git a/src/devices/bus/gameboy/gbxfile.cpp b/src/devices/bus/gameboy/gbxfile.cpp new file mode 100644 index 00000000000..9058697b709 --- /dev/null +++ b/src/devices/bus/gameboy/gbxfile.cpp @@ -0,0 +1,59 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + GBX file format helpers + + ***************************************************************************/ + +#include "emu.h" +#include "gbxfile.h" + +#include + + +namespace bus::gameboy::gbxfile { + +bool get_data( + memory_region *region, + leader_1_0 &leader, + u8 const *&extra, + u32 &extralen) +{ + // no region, no dice + if (!region) + return false; + + // needs to contain enough data for the trailer at the very least + auto const bytes(region->bytes()); + if (bytes < sizeof(trailer)) + return false; + + // check for supported format + u8 *const base(®ion->as_u8()); + trailer t; + std::memcpy(&t, &base[bytes - sizeof(t)], sizeof(t)); + t.swap(); + if ((MAGIC_GBX != t.magic) || (1 != t.ver_maj)) + return false; + + // check that the footer fits and the leader doesn't overlap the trailer + if ((bytes < t.size) || ((sizeof(leader) + sizeof(t)) > t.size)) + return false; + + // get leader in host byte order + std::memcpy(&leader, &base[bytes - t.size], sizeof(leader)); + leader.swap(); + + // get pointer to extra data if there's any + extralen = t.size - sizeof(leader) - sizeof(t); + if (extralen) + extra = &base[bytes - t.size + sizeof(leader)]; + else + extra = nullptr; + + // all good + return true; +} + +} // namespace bus::gameboy::gbxfile diff --git a/src/devices/bus/gameboy/gbxfile.h b/src/devices/bus/gameboy/gbxfile.h new file mode 100644 index 00000000000..39ef74e5d17 --- /dev/null +++ b/src/devices/bus/gameboy/gbxfile.h @@ -0,0 +1,117 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + GBX file format helpers + + GBX files start with the program ROM contents, and end with a footer. + Multi-byte values in the footer are stored in big-Endian order. Fields are + arranged for natural alignment. Version 1.0 seems to be the only + widespread version. + + The footer ends with a 16-byte trailer: + * Offset from end of file to start of footer (4 bytes). + * Major version of footer format (4 bytes). + * Minor version of footer format (4 bytes). + * Magic number 0x47425821 or FourCC 'GBX!' (4 bytes). + + The footer starts with a leader. Version 1.0 uses a 16-byte leader: + * A FourCC indicating the cartridge type (4 bytes). + * Flag indicating whether cartridge RAM contents is preserved (1 byte). + * Flag indicating whether a vibration motor is present (1 byte). + * Flag indicating whether a real-time clock oscillator is present (1 byte). + * Always seems to be zero - possibly unused padding (1 byte). + * Program ROM size in bytes (4 bytes). + * Cartridge RAM size in bytes (4 bytes). + + There is additional data between the leader and trailer that contains + additional wiring or configuration details for the cartridge if necessary. + In general, it's 32 bytes long, and unused portions are filled with 0x00. + It seems to be entirely unused for most cartridge types. + + Vast Fame VF001 (FourCC 'VF01') additional data: + * Command preload value (1 byte). + + The list of cartridge type FourCC values here is incomplete. + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_GBXFILE_H +#define MAME_BUS_GAMEBOY_GBXFILE_H + +#pragma once + + +namespace bus::gameboy::gbxfile { + +enum : u32 +{ + MAGIC_GBX = 0x47425821 // 'GBX!' +}; + +enum : u32 +{ + TYPE_CAMERA = 0x43414d52, // 'CAMR' + TYPE_HUC1 = 0x48554331, // 'HUC1' + TYPE_HUC3 = 0x48554333, // 'HUC3' + TYPE_LICHENG = 0x4c494348, // 'LICH' + TYPE_M161 = 0x4d313631, // 'M161' + TYPE_MBC1_COLL = 0x4d42314d, // 'MB1M' + TYPE_MBC1 = 0x4d424331, // 'MBC1' + TYPE_MBC2 = 0x4d424332, // 'MBC2' + TYPE_MBC3 = 0x4d424333, // 'MBC3' + TYPE_MBC5 = 0x4d424335, // 'MBC5' + TYPE_MBC7 = 0x4d424337, // 'MBC7' + TYPE_MMM01 = 0x4d4d4d31, // 'MMM1' + TYPE_ROCKET = 0x524f434b, // 'ROCK' + TYPE_PLAIN = 0x524f4d00, // 'ROM\0' + TYPE_SACHEN1 = 0x53414d31, // 'SAM1' + TYPE_SACHEN2 = 0x53414d32, // 'SAM2' + TYPE_SINTAX = 0x534e5458, // 'SNTX' + TYPE_TAMA5 = 0x54414d35, // 'TAM5' + TYPE_VF001 = 0x56463031, // 'VF01' + TYPE_WISDOM = 0x57495344 // 'WISD' +}; + +struct leader_1_0 +{ + u32 cart_type; + u8 batt; + u8 rumble; + u8 rtc; + u8 unknown; // maybe padding? + u32 rom_bytes; + u32 ram_bytes; + + void swap() + { + cart_type = big_endianize_int32(cart_type); + rom_bytes = big_endianize_int32(rom_bytes); + ram_bytes = big_endianize_int32(ram_bytes); + } +}; + +struct trailer +{ + u32 size; + u32 ver_maj; + u32 ver_min; + u32 magic; + + void swap() + { + size = big_endianize_int32(size); + ver_maj = big_endianize_int32(ver_maj); + ver_min = big_endianize_int32(ver_min); + magic = big_endianize_int32(magic); + } +}; + +bool get_data( + memory_region *region, + leader_1_0 &leader, + u8 const *&extra, + u32 &extralen); + +} // namespace bus::gameboy::gbxfile + +#endif // MAME_BUS_GAMEBOY_GBXFILE_H diff --git a/src/devices/bus/gameboy/huc1.cpp b/src/devices/bus/gameboy/huc1.cpp new file mode 100644 index 00000000000..ccaaf477992 --- /dev/null +++ b/src/devices/bus/gameboy/huc1.cpp @@ -0,0 +1,192 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Hudson Soft HuC-1 Memory Controller + + Provides ROM and RAM banking and infrared I/O. Supports up to 1 MiB ROM + (64 16 KiB pages) and up to 32 KiB static RAM (4 8 KiB pages). If RAM bank + lines are used for coarse ROM banking, up to 4 MiB ROM (256 16 KiB pages) + can be supported. + + The HuC-1 controller appears to only respond to A15-A13 and D6-D0, i.e. + addresses are effectively masked with 0xE000 and data is effectively masked + with 0x3F. + + The HuC-1 controller doesn't support disabling cartridge RAM without + selecting infrared I/O. However, some games still write 0x00 or 0x0A to + the infrared/RAM select register as if it behaved like the Nintendo MBC + series RAM enable register. Some games write to the 0x6000-0x7FFF range, + but this has no detectable effect. + + 0x0000-3FFF R - Fixed ROM bank, always first page of ROM. + 0x4000-7FFF R - Selectable ROM bank, page 0-255 of ROM. + 0xA000-BFFF RW - Static RAM or infrared I/O. + + 0x0000-1FFF W - Select infrared (0x0E) or RAM (not 0x0E) at 0xA000. + 0x2000-3FFF W - Select ROM page mapped at 0x4000. + 0x4000-5FFF W - Select RAM page mapped at 0xA000. + + TODO: + * If you try to use the infrared link feature in gbkiss, it says to press + the B button to stop communication, but it doesn't respond to any inputs. + Is this normal? + * What is the default state for banking and infrared select on reset? + * Does ROM bank 0 map to bank 1 like MBC1? + * How many RAM page lines are there? No games use more than 2. + * Do any bits of the infrared I/O registers have any significance besides + the least significant bit? + * Do any values besides 0x0E have special significance for infrared/RAM + select? + + ***************************************************************************/ + +#include "emu.h" +#include "huc1.h" + +#include "cartbase.ipp" + +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class huc1_device : public mbc_ram_device_base +{ +public: + static constexpr feature_type unemulated_features() { return feature::COMMS; } + + huc1_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual image_init_result load(std::string &message) override ATTR_COLD; + +protected: + virtual void device_reset() override ATTR_COLD; + +private: + void infrared_select(u8 data); + void bank_switch_fine(u8 data); + void bank_switch_coarse(u8 data); + u8 read_ir(address_space &space); + void write_ir(u8 data); + + memory_view m_view_ir; +}; + + +huc1_device::huc1_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + mbc_ram_device_base(mconfig, GB_ROM_HUC1, tag, owner, clock), + m_view_ir(*this, "ir") +{ +} + + +image_init_result huc1_device::load(std::string &message) +{ + // check for valid ROM/RAM regions + set_bank_bits_rom(2, 6); + set_bank_bits_ram(2); + if (!check_rom(message) || !check_ram(message)) + return image_init_result::FAIL; + + // if that checked out, install memory + install_rom(); + install_ram(*cart_space()); + + // install memory controller handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(huc1_device::infrared_select))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(huc1_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(huc1_device::bank_switch_coarse))); + + // install infrared handlers + cart_space()->install_view(0xa000, 0xbfff, m_view_ir); + m_view_ir[0].install_read_handler( + 0xa000, 0xbfff, + read8mo_delegate(*this, FUNC(huc1_device::read_ir))); + m_view_ir[0].install_write_handler( + 0xa000, 0xbfff, + write8smo_delegate(*this, FUNC(huc1_device::write_ir))); + + // all good + return image_init_result::PASS; +} + + +void huc1_device::device_reset() +{ + mbc_ram_device_base::device_reset(); + + // TODO: what's the proper reset state? + m_view_ir.disable(); + + set_bank_rom_fine(0); + set_bank_rom_coarse(0); + set_bank_ram(0); +} + + +void huc1_device::infrared_select(u8 data) +{ + if (0x0e == (data & 0x0e)) + { + LOG("%s: Infrared I/O selected\n", machine().describe_context()); + m_view_ir.select(0); + } + else + { + LOG("%s: Cartridge RAM selected\n", machine().describe_context()); + m_view_ir.disable(); + } +} + + +void huc1_device::bank_switch_fine(u8 data) +{ + // TODO: does zero map to bank 1 like MBC1? + set_bank_rom_fine(data & 0x3f); +} + + +void huc1_device::bank_switch_coarse(u8 data) +{ + // TODO: how many output lines are physically present? + set_bank_rom_coarse(data & 0x03); + set_bank_ram(data & 0x03); +} + + +u8 huc1_device::read_ir(address_space &space) +{ + LOG("%s: Infrared read\n"); + return (space.unmap() & 0xc0) | 0x00; // least significant bit clear - dark +} + + +void huc1_device::write_ir(u8 data) +{ + // bit zero high to turn on the IR LED, or low to turn it off + LOG("%s: Infrared write 0x%02X\n", machine().describe_context(), data); +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_HUC1, device_gb_cart_interface, bus::gameboy::huc1_device, "gb_rom_huc1", "Game Boy Hudson Soft HuC-1 Cartridge") diff --git a/src/devices/bus/gameboy/huc1.h b/src/devices/bus/gameboy/huc1.h new file mode 100644 index 00000000000..b1037eadb04 --- /dev/null +++ b/src/devices/bus/gameboy/huc1.h @@ -0,0 +1,18 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Hudson Soft HuC-1 Memory Controller + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_HUC1_H +#define MAME_BUS_GAMEBOY_HUC1_H + +#pragma once + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_HUC1, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_HUC1_H diff --git a/src/devices/bus/gameboy/huc3.cpp b/src/devices/bus/gameboy/huc3.cpp new file mode 100644 index 00000000000..9b420ec0df1 --- /dev/null +++ b/src/devices/bus/gameboy/huc3.cpp @@ -0,0 +1,410 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Hudson Soft HuC-3 Memory Controller + + Provides ROM and RAM banking, infrared I/O, a real-time clock and a melody + generator. + + The HuC-3 controller appears to only respond to A15-A13 and D6-D0, i.e. + addresses are effectively masked with 0xE000 and data is effectively masked + with 0x3F. + + Major components in the cartridge include: + * U1 program ROM + * U2 HuC-3 controller + * U3 LH52256CT-10LL 32K*8 static RAM + * U4 MM1026A or MM1134A system reset/backup power switch + * U5 TC74LVX04FT hex inverter + * D1 infrared LED + * Q1 infrared phototransistor + * X1 real-time clock crystal + * BATT CR2025 coin cell (user-replaceable) + + HuC-3 48-pin QFP known connectons: + 1 13 D1 25 37 + 2 Audio out 14 D0 26 38 XTAL out + 3 Audio out 15 27 39 XTAL in + 4 Audio out 16 28 40 GND + 5 17 29 41 GND + 6 18 A15 30 42 GND + 7 GND 19 GND 31 GND 43 + 8 D6 20 32 44 + 9 D5 21 33 45 /WR + 10 D4 22 34 46 /RD + 11 D3 23 A13 35 GND 47 + 12 D2 24 IR out 36 GND 48 + + 0x0000-3FFF R - Fixed ROM bank, always first page of ROM. + 0x4000-7FFF R - Selectable ROM bank, page 0-255 of ROM. + 0xA000-BFFF RW - Static RAM or I/O. + + 0x0000-1FFF W - Select RAM or I/O at 0xA000. + 0x2000-3FFF W - Select ROM page mapped at 0x4000. + 0x4000-5FFF W - Select RAM page mapped at 0xA000. + + Only the four least significant bits of the RAM or I/O selection value are + significant. The ten unused values will map nothing into 0xA000-0xBFFF. + Used values: + 0x0 - RAM (read-only) + 0xA - RAM (read/write) + 0xB - Write command/data + 0xC - Read command/data + 0xD - Clear least significant bit to execute command + 0xE - Infrared I/O + + The HuC-3 chip likely contains a 4-bit microcontroller that implements the + real-time clock and melody generator functionality. The game communicates + with the microcontroller via I/O 0xB, 0xC and 0xD. + + Conceptually, the value written to 0xB contains two values: a 3-bit command + in bits 6-4, and a 4-bit value in bits 3-0. Reading 0x0C gives the same + command in bits 6-4 and a response value in bits 3-0. Writing to 0xB or + reading from 0xC has no immediate side effects. + + Bit 0 for 0xD reads high when the microcontroller is ready to accept a + command. Writing with bit 0 clear causes the microcontroller to execute + the command previously written to 0xB. The microcontroller will set bit 0 + when it has completed the command and is ready to execute another command. + + Five of the eight possible commands are used by the games: + 0x1 - Read register and increment address (value put in bits 3-0 of 0xC) + 0x3 - Write register and increment address (value from bits 3-0 of 0xB) + 0x4 - Set register address low nybble + 0x5 - Set register address high nybble + 0x6 - Execute extended command (selector from bits 3-0 of 0xB) + + The games use four of the sixteen possible extended commands: + 0x0 - Atomically read real-time clock to registers 0-7 + 0x1 - Atomically write real-time clock from registers 0-7 + 0x2 - Some kind of handshake/status request - sets result to 0x1 + 0xe - Sent twice to trigger melody generator + + Registers are likely a window into the microcontroller's memory. Known + registers: + 0x00-02 - Minute counter read/write (least significant nybble low) + 0x03-05 - Day counter read/write (least significant nybble low) + 0x10-12 - Minute counter (least significant nybble low) + 0x13-15 - Day counter (least significant nybble low) + 0x26 - Bits 1-0 select melody + 0x27 - Enable (0x1) or disable (not 0x1) melody + + TODO: + * Implement real-time clock. + * Simulate melody generator? + * Which of the internal registers are battery-backed? + * What is the default state for banking and infrared select on reset? + * Does ROM bank 0 map to bank 1 like MBC1? + * How many RAM page lines are there? No games use more than 2. + + ***************************************************************************/ + +#include "emu.h" +#include "huc3.h" + +#include "cartbase.ipp" + +#include +#include +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class huc3_device : public mbc_ram_device_base +{ +public: + static constexpr feature_type unemulated_features() { return feature::SOUND | feature::COMMS; } + + huc3_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + virtual image_init_result load(std::string &message) override ATTR_COLD; + +protected: + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + +private: + void io_select(u8 data); + void bank_switch_fine(u8 data); + void bank_switch_coarse(u8 data); + void write_command(u8 data); + u8 read_command(address_space &space); + u8 read_status(address_space &space); + void write_control(u8 data); + u8 read_ir(address_space &space); + void write_ir(u8 data); + + void execute_instruction() + { + switch (m_ctrl_data & 0x0f) + { + case 0x0: + LOG("Instruction 0x0 - atomic RTC read\n"); + std::copy_n(&m_registers[0x10], 7, &m_registers[0x00]); + break; + case 0x1: + LOG("Instruction 0x2 - atomic RTC write\n"); + std::copy_n(&m_registers[0x00], 7, &m_registers[0x10]); + break; + case 0x2: + logerror("Instruction 0x2 - setting data to 0x1\n"); + m_ctrl_data = 0x01U; + break; + case 0xe: + logerror("Instruction 0xE - play melody\n"); + break; + default: + logerror( + "%s: Unknown instruction 0x%X\n", + machine().describe_context(), + m_ctrl_data); + } + } + + memory_view m_view_io; + + u8 m_ctrl_cmd; + u8 m_ctrl_data; + u8 m_ctrl_addr; + u8 m_registers[0x100]; +}; + + +huc3_device::huc3_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + mbc_ram_device_base(mconfig, GB_ROM_HUC3, tag, owner, clock), + m_view_io(*this, "io"), + m_ctrl_cmd(0U), + m_ctrl_data(0U), + m_ctrl_addr(0U) +{ +} + + +image_init_result huc3_device::load(std::string &message) +{ + // check for valid ROM/RAM regions + set_bank_bits_rom(2, 7); + set_bank_bits_ram(2); + if (!check_rom(message) || !check_ram(message)) + return image_init_result::FAIL; + + // if that checked out, install memory + cart_space()->install_view(0xa000, 0xbfff, m_view_io); + install_rom(); + install_ram(m_view_io[0], m_view_io[1]); + + // install memory controller handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(huc3_device::io_select))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(huc3_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(huc3_device::bank_switch_coarse))); + + // install I/O handlers + m_view_io[2].install_write_handler( + 0xa000, 0xbfff, + write8smo_delegate(*this, FUNC(huc3_device::write_command))); + m_view_io[3].install_read_handler( + 0xa000, 0xbfff, + read8mo_delegate(*this, FUNC(huc3_device::read_command))); + m_view_io[4].install_read_handler( + 0xa000, 0xbfff, + read8mo_delegate(*this, FUNC(huc3_device::read_status))); + m_view_io[4].install_write_handler( + 0xa000, 0xbfff, + write8smo_delegate(*this, FUNC(huc3_device::write_control))); + m_view_io[5].install_read_handler( + 0xa000, 0xbfff, + read8mo_delegate(*this, FUNC(huc3_device::read_ir))); + m_view_io[5].install_write_handler( + 0xa000, 0xbfff, + write8smo_delegate(*this, FUNC(huc3_device::write_ir))); + + // all good + return image_init_result::PASS; +} + + +void huc3_device::device_start() +{ + mbc_ram_device_base::device_start(); + + std::fill(std::begin(m_registers), std::end(m_registers), 0U); + + save_item(NAME(m_ctrl_cmd)); + save_item(NAME(m_ctrl_data)); + save_item(NAME(m_ctrl_addr)); + save_item(NAME(m_registers)); +} + + +void huc3_device::device_reset() +{ + mbc_ram_device_base::device_reset(); + + // TODO: what's the proper reset state? + m_view_io.disable(); + + set_bank_rom_fine(0); + set_bank_rom_coarse(0); + set_bank_ram(0); + + m_ctrl_cmd = 0U; + m_ctrl_data = 0U; + m_ctrl_addr = 0U; +} + + +void huc3_device::io_select(u8 data) +{ + switch (data & 0x0f) + { + case 0x00: + LOG("%s: Select RAM (read-only)\n", machine().describe_context()); + m_view_io.select(0); + break; + case 0x0a: + LOG("%s: Select RAM (read/write)\n", machine().describe_context()); + m_view_io.select(1); + break; + case 0x0b: + LOG("%s: Select control data write\n", machine().describe_context()); + m_view_io.select(2); + break; + case 0x0c: + LOG("%s: Select control data read\n", machine().describe_context()); + m_view_io.select(3); + break; + case 0x0d: + LOG("%s: Select control command/status\n", machine().describe_context()); + m_view_io.select(4); + break; + case 0x0e: + LOG("%s: Select infrared I/O\n", machine().describe_context()); + m_view_io.select(5); + break; + default: + LOG("%s: Select unused I/O 0x%X\n", machine().describe_context(), data & 0x0f); + m_view_io.disable(); + } +} + + +void huc3_device::bank_switch_fine(u8 data) +{ + // TODO: does zero map to bank 1 like MBC1? + set_bank_rom_fine(data & 0x7f); +} + + +void huc3_device::bank_switch_coarse(u8 data) +{ + // TODO: how many output lines are physically present? + set_bank_rom_coarse(data & 0x03); + set_bank_ram(data & 0x03); +} + + +void huc3_device::write_command(u8 data) +{ + m_ctrl_cmd = BIT(data, 4, 3); + m_ctrl_data = BIT(data, 0, 4); + LOG( + "%s: Write command = 0x%X data = 0x%X\n", + machine().describe_context(), + m_ctrl_cmd, + m_ctrl_data); +} + + +u8 huc3_device::read_command(address_space &space) +{ + LOG( + "%s: Read command = 0x%X data = 0x%X\n", + machine().describe_context(), + m_ctrl_cmd, + m_ctrl_data); + return (space.unmap() & 0x80) | (m_ctrl_cmd << 4) | m_ctrl_data; +} + + +u8 huc3_device::read_status(address_space &space) +{ + LOG("%s: Read status\n", machine().describe_context()); + return (space.unmap() & 0x80) | 0x7f; // least significant bit set when ready to receive a command +} + + +void huc3_device::write_control(u8 data) +{ + // TODO: Is there more to this? + LOG("%s: Write control = 0x%02X\n", machine().describe_context(), data); + if (!BIT(data, 0)) + { + switch (m_ctrl_cmd) + { + case 0x1: + LOG("Command 0x1 - read register 0x%02X\n", m_ctrl_addr); + m_ctrl_data = m_registers[m_ctrl_addr++] & 0x0f; + break; + case 0x3: + LOG("Command 0x3 - write register 0x%02X = 0x%X\n", m_ctrl_addr, m_ctrl_data); + m_registers[m_ctrl_addr++] = m_ctrl_data & 0x0f; + break; + case 0x4: + m_ctrl_addr = (m_ctrl_addr & 0xf0) | (m_ctrl_data & 0x0f); + LOG("Command 0x4 - set register address = 0x%02X\n", m_ctrl_addr); + break; + case 0x5: + m_ctrl_addr = (m_ctrl_addr & 0x0f) | (m_ctrl_data << 4); + LOG("Command 0x5 - set register address = 0x%02X\n", m_ctrl_addr); + break; + case 0x6: + LOG("Command 0x6 - execute instruction 0x%X\n", m_ctrl_data); + execute_instruction(); + break; + default: + logerror( + "%s: Unknown command 0x%X data = 0x%X\n", + machine().describe_context(), + m_ctrl_cmd, + m_ctrl_data); + } + } +} + + +u8 huc3_device::read_ir(address_space &space) +{ + LOG("%s: Infrared read\n", machine().describe_context()); + return (space.unmap() & 0xc0) | 0x00; // least significant bit clear - dark +} + + +void huc3_device::write_ir(u8 data) +{ + // bit zero high to turn on the IR LED, or low to turn it off + LOG("%s: Infrared write 0x%02X\n", machine().describe_context(), data); +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_HUC3, device_gb_cart_interface, bus::gameboy::huc3_device, "gb_rom_huc3", "Game Boy Hudson Soft HuC-3 Cartridge") diff --git a/src/devices/bus/gameboy/huc3.h b/src/devices/bus/gameboy/huc3.h new file mode 100644 index 00000000000..13f73056b5c --- /dev/null +++ b/src/devices/bus/gameboy/huc3.h @@ -0,0 +1,18 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Hudson Soft HuC-3 Memory Controller + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_HUC3_H +#define MAME_BUS_GAMEBOY_HUC3_H + +#pragma once + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_HUC3, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_HUC3_H diff --git a/src/devices/bus/gameboy/mbc.cpp b/src/devices/bus/gameboy/mbc.cpp index 2a07d390a60..a34b215052a 100644 --- a/src/devices/bus/gameboy/mbc.cpp +++ b/src/devices/bus/gameboy/mbc.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Fabio Priuli, Wilbert Pol +// copyright-holders:Vas Crabb, Wilbert Pol /*********************************************************************************************************** Game Boy carts with MBC (Memory Bank Controller) @@ -15,11 +15,11 @@ 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 autmatically be + 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 moddel chosen. + 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). @@ -31,26 +31,6 @@ Regular ROM aliasing rules apply. - MBC2 Mapper - =========== - - The MBC2 mapper includes 512x4bits of builtin RAM. - - 0000-3FFF - Writing to this area enables (value 0bXXXX1010) or disables (any - other value than 0bXXXX1010) the RAM. In order to perform this - function bit 8 of the address must be reset, so usable areas are - 0000-00FF, 0200-02FF, 0400-04FF, 0600-06FF, ..., 3E00-3EFF, - 0000-3FFF - Writing to this area selects the rom bank to appear at 4000-7FFF. - Only bits 3-0 are used to select the bank number. If a value of - 0bXXXX0000 is written then this is automatically changed into - 0bXXXX0001 by the mapper. - In order to perform the rom banking bit 8 of the address must be - set, so usable areas are 0100-01FF, 0300-03FF, 0500-05FF, 0700- - 07FF,..., 3F00-3FFF, - - Regular ROM aliasing rules apply. - - MBC3 Mapper =========== @@ -58,9 +38,9 @@ 0000-1FFF - Writing to this area enables (value 0x0A) or disables (not 0x0A) the SRAM and RTC registers. - 2000-3FFF - Writing to this area selects the rom bank to appear at 4000-7FFF. + 2000-3FFF - Writing to this area selects the ROM bank to appear at 4000-7FFF. Bits 6-0 are used to select the bank number. If a value of - 0bX0000000 is written then this is autmatically changed into + 0bX0000000 is written then this is automatically changed into 0bX0000001 by the mapper. 4000-5FFF - Writing to this area selects the RAM bank or the RTC register to read. @@ -92,92 +72,12 @@ rumble motor (0 - disable motor, 1 - enable motor). - MBC7 Mapper (Used by Kirby's Tilt n' Tumble, Command Master) - =========== - - Status: Partial support (only ROM banking supported at the moment) - - The MBC7 mapper has 0x0200(?) bytes of RAM built in. - - 0000-1FFF - Probably enable/disable RAM - In order to use this area bit 12 of the address be set. - Values written: 00, 0A - 2000-2FFF - Writing to this area selects the ROM bank to appear at - 4000-7FFF. - In order to use this area bit 12 of the address be set. - Values written: 01, 07, 01, 1C - 3000-3FFF - Unknown - In order to use this area bit 12 of the address be set. - Values written: 00 - 4000-4FFF - Unknown - In order to use this area bit 12 of the address be set. - Values written: 00, 40, 3F - - - MMM01 mapper - ============ - - Used by: Momotarou Collection 2, Taito Pack - - Status: not supported yet. - - Momotarou Collection 2: - - MOMOTARODENGEKI2, 0x00000, blocks 0x00 - 0x1F - 0x147: 01 04 00 00 33 00 - MOMOTAROU GAIDEN, 0x80000, blocks 0x20 - 0x3F - 0x147: 06 03 00 00 18 00 - - When picking top option: - 3FFF <- 20 - 5FFF <- 40 - 7FFF <- 21 - 1FFF <- 3A - 1FFF <- 7A - - When picking bottom option: - 3FFF <- 00 - 5FFF <- 01 - 7FFF <- 01 - 1FFF <- 3A - 1FFF <- 7A - - Taito Pack (MMM01+RAM, 512KB, 64KB RAM): - 1st option (BUBBLE BOBBLE, blocks 0x10 - 0x17, MBC1+RAM, 128KB, 8KB RAM): - 2000 <- 70 01110000 => starting block, 10000 - 6000 <- 30 00110000 => 8 blocks - 4000 <- 70 01110000 => ??? - 0000 <- 40 01000000 => upper 3 bits determine lower 3 bits of starting block? - - 2nd option (ELEVATOR ACTION, blocks 0x18 - 0x1B, MBC1, 64KB, 2KB RAM): - 2000 <- 78 01111000 => starting block, 11000 - 6000 <- 38 00111000 => 4 blocks - 4000 <- 70 01110000 => ??? - 0000 <- 40 01000000 => upper 3 bits determine lower 3 bits of starting block? - - 3rd option (CHASE HQ, blocks 0x08 - 0x0F, MBC1+RAM, 128KB, 8KB RAM): - 2000 <- 68 01101000 => starting block, 01000 - 6000 <- 30 00110000 => 8 blocks - 4000 <- 70 01110000 => ??? - 0000 <- 40 01000000 => upper 3 bits determine lower 3 bits of starting block? - - 4th option (SAGAIA, blocks 0x00 - 0x07, MBC1+RAM, 128KB, 8KB RAM): - 2000 <- 60 01100000 => starting block, 00000 - 6000 <- 30 00110000 => 8 blocks - 4000 <- 70 01110000 => ??? - 0000 <- 40 01000000 => upper 3 bits determine lower 3 bits of starting block? - - Known: - The last 2 banks in a MMM01 dump are actually the starting banks for a MMM01 image. - - 0000-1FFF => bit6 set => perform mapping - - Possible mapping registers: - 1FFF - Enable RAM ??? - 3FFF - xxxbbbbb - Bit0-5 of the rom bank to select at 0x4000-0x7FFF ? - - - TODO: RTC runs too fast while in-game, in MBC-3 games... find the problem! + TODO: + * What does MBC3 do with the RAM bank outputs when RTC is selected? + * How do MBC3 invalid second/minute/hour values roll over? + * 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. + * Digimon 2 mapper doesn't work ***********************************************************************************************************/ @@ -185,1841 +85,1686 @@ #include "emu.h" #include "mbc.h" +#include "cartbase.ipp" +#include "cartheader.h" +#include "gbxfile.h" -namespace { - -class gb_rom_mbc_device : public device_t, public device_gb_cart_interface -{ -public: - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; +#include "bus/generic/slot.h" -protected: - // construction/destruction - gb_rom_mbc_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); +#include "dirtc.h" - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } +#include "corestr.h" - void shared_start(); - void shared_reset(); +#include +#include +#include +#include +#include +#include - uint8_t m_ram_enable; -}; +#define VERBOSE 1 +#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" -class gb_rom_mbc1_device : public gb_rom_mbc_device -{ -public: +namespace bus::gameboy { - // construction/destruction - gb_rom_mbc1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); +namespace { - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; +//************************************************************************** +// MBC1/MBC3/MBC5 banked RAM support helper class +//************************************************************************** +class rom_mbc_device_base : public mbc_ram_device_base +{ protected: - enum { - MODE_16M_64k = 0, /// 16Mbit ROM, 64kBit RAM - MODE_4M_256k = 1 /// 4Mbit ROM, 256kBit RAM - }; - - gb_rom_mbc1_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); - - // device-level overrides - virtual void device_start() override { shared_start(); save_item(NAME(m_mode)); } - virtual void device_reset() override { shared_reset(); m_mode = MODE_16M_64k; } - virtual void set_additional_wirings(uint8_t mask, int shift) override { m_mask = mask; m_shift = shift; } // these get set at cart loading - - uint8_t m_mode, m_mask; - int m_shift; -}; - + rom_mbc_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + mbc_ram_device_base(mconfig, type, tag, owner, clock), + m_view_ram(*this, "ram"), + m_bank_lines{ 0U, 0U } + { + } -class gb_rom_mbc2_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_mbc2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + bool 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 false; + } + lowbits = b; + } - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + // 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 false; + + // everything checked out - install memory and return success + m_bank_lines[0] = util::make_bitmask(lowbits); + m_bank_lines[1] = util::make_bitmask(highbits); + cart_space()->install_view(0xa000, 0xbfff, m_view_ram); + install_rom(); + install_ram(m_view_ram[0]); + return true; + } -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } -}; + void set_bank_rom_fine(u16 entry) + { + mbc_ram_device_base::set_bank_rom_fine(entry & m_bank_lines[0]); + } + 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(); + } -class gb_rom_mbc3_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_mbc3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + void enable_ram(u8 data) + { + set_ram_enable(0x0a == (data & 0x0f)); + } - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + void bank_switch_coarse(u8 data) + { + set_bank_rom_coarse(data & m_bank_lines[1]); + set_bank_ram(data & m_bank_lines[1]); + } -protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + auto &view_aux(unsigned entry) { return m_view_ram[entry + 1]; } + void set_view_aux(unsigned entry) { m_view_ram.select(entry + 1); } private: - void update_rtc(); - uint8_t m_rtc_regs[5]; - int m_rtc_ready; -}; - - -class gb_rom_mbc5_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_mbc5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + static inline constexpr unsigned PAGE_RAM_SIZE = 0x2000; - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + memory_view m_view_ram; -protected: - gb_rom_mbc5_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + u16 m_bank_lines[2]; +}; - // device-level overrides - virtual void device_start() override { shared_start(); m_rumble.resolve(); } - virtual void device_reset() override { shared_reset(); } - output_finder<> m_rumble; -}; +//************************************************************************** +// MBC5 and clones (max 128 MiB ROM, max 128 KiB SRAM) +//************************************************************************** -class gb_rom_mbc6_device : public gb_rom_mbc_device +class mbc5_device_base : public rom_mbc_device_base { public: - // construction/destruction - gb_rom_mbc6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM and RAM + if (!install_memory(message)) + return image_init_result::FAIL; + + // install handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(mbc5_device_base::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x2fff, + write8smo_delegate(*this, FUNC(mbc5_device_base::bank_switch_fine_low))); + cart_space()->install_write_handler( + 0x3000, 0x3fff, + write8smo_delegate(*this, FUNC(mbc5_device_base::bank_switch_fine_high))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(mbc5_device_base::bank_switch_coarse))); + + // all good + return image_init_result::PASS; + } protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + 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) + { + } - uint16_t m_latch1, m_latch2; - uint8_t m_bank_4000, m_bank_6000; -}; + virtual void device_reset() override ATTR_COLD + { + rom_mbc_device_base::device_reset(); + set_bank_rom_coarse(0); + set_bank_rom_fine(0); + set_ram_enable(false); + set_bank_ram(0); + } -class gb_rom_mbc7_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_mbc7_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + bool install_memory(std::string &message) ATTR_COLD + { + return rom_mbc_device_base::install_memory(message, 4, 9); + } - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + void bank_switch_fine_low(u8 data) + { + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(data)); + } -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } + void bank_switch_fine_high(u8 data) + { + set_bank_rom_fine((bank_rom_fine() & 0x00ff) | (u16(data & 0x01) << 8)); + } }; -class gb_rom_m161_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_m161_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override { return 0xff; } - virtual void write_ram(offs_t offset, uint8_t data) override { } - -protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; - - uint8_t m_base_bank; - uint8_t m_load_disable; -}; +//************************************************************************** +// MBC5-like pirate cartridges with logo spoofing +//************************************************************************** -class gb_rom_mmm01_device : public gb_rom_mbc_device +class mbc5_logo_spoof_device_base : public mbc5_device_base { public: - // construction/destruction - gb_rom_mmm01_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // install regular MBC5 handlers + image_init_result const result(mbc5_device_base::load(message)); + if (image_init_result::PASS != result) + return result; + + // intercept ROM reads for logo spoofing + cart_space()->install_read_handler( + 0x0000, 0x7fff, + read8sm_delegate(*this, FUNC(mbc5_logo_spoof_device_base::read_rom))); + + // all good + return image_init_result::PASS; + } protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; - - uint16_t m_romb; - uint8_t m_romb_nwe; - uint8_t m_ramb; - uint8_t m_ramb_nwe; - uint8_t m_mode; - uint8_t m_mode_nwe; - uint8_t m_map; - uint8_t m_mux; -}; + 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) + { + } + virtual void device_start() override ATTR_COLD + { + mbc5_device_base::device_start(); -class gb_rom_sachen_mmc1_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_sachen_mmc1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + save_item(NAME(m_counter)); + save_item(NAME(m_spoof_logo)); + } - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override { return 0xff; } - virtual void write_ram(offs_t offset, uint8_t data) override { } + virtual void device_reset() override ATTR_COLD + { + mbc5_device_base::device_reset(); + + m_counter = 0U; + m_spoof_logo = 0U; + + install_ram_tap(); + 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(); + }); + } -protected: - enum { - MODE_LOCKED, - MODE_UNLOCKED - }; + u8 read_rom(offs_t offset) + { + offset = rom_access(offset); + return (BIT(offset, 14) ? bank_rom_high_base() : bank_rom_low_base())[offset & 0x3fff]; + } - gb_rom_sachen_mmc1_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + 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); + } + } - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + // accessing entry point disables logo spoofing logic + if (0x0100 == offset) + m_spoof_logo = 3U; + } - uint8_t m_base_bank, m_mask, m_mode, m_unlock_cnt; -}; + // 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; + } +private: + void install_ram_tap() + { + 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; + } + } -class gb_rom_sachen_mmc2_device : public gb_rom_sachen_mmc1_device -{ -public: - // construction/destruction - gb_rom_sachen_mmc2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + memory_passthrough_handler m_ram_tap; + util::notifier_subscription m_notif_cart_space; - virtual uint8_t read_rom(offs_t offset) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + u8 m_counter; + u8 m_spoof_logo; + bool m_installing_tap; +}; -protected: - enum { - MODE_LOCKED_DMG, - MODE_LOCKED_CGB, - MODE_UNLOCKED - }; - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; -}; +//************************************************************************** +// MBC1 (max 2 MiB ROM, max 32 KiB SRAM) +//************************************************************************** -class gb_rom_188in1_device : public gb_rom_mbc1_device +class mbc1_device : public rom_mbc_device_base { public: - // construction/destruction - gb_rom_188in1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + 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) + { + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // probe for "collection" cartridges wired for 4-bit fine bank number + unsigned banklowbits(5U); + if (is_collection()) + banklowbits = 4U; + + // set up ROM and RAM + if (!install_memory(message, 2, banklowbits)) + return image_init_result::FAIL; + + // install handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(mbc1_device::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(mbc1_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(mbc1_device::bank_switch_coarse))); + cart_space()->install_write_handler( + 0x6000, 0x7fff, + write8smo_delegate(*this, FUNC(mbc1_device::bank_low_mask))); + + // all good + return image_init_result::PASS; + } protected: - // device-level overrides - virtual void device_start() override { shared_start(); save_item(NAME(m_game_base)); } - virtual void device_reset() override { shared_reset(); m_game_base = 0; } + virtual void device_reset() override ATTR_COLD + { + 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); + } private: - uint32_t m_game_base; -}; - + void bank_switch_fine(u8 data) + { + data &= 0x1f; + set_bank_rom_fine(data ? data : 1); + } -class gb_rom_sintax_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_sintax_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + void bank_low_mask(u8 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); + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + bool is_collection() ATTR_COLD + { + // 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) + { + logerror("Detected known multi-game collection with 4-bit fine bank addressing\n"); + return true; + } -protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; - void set_xor_for_bank(uint8_t bank); + // reject if the header checksum for the second coarse page fails + if (!cartheader::verify_header_checksum(rombase + (u32(0x4000) << 4) + 0x100)) + return false; - uint8_t m_bank_mask, m_bank, m_reg; + // 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; + } - uint8_t m_currentxor, m_xor2, m_xor3, m_xor4, m_xor5, m_sintax_mode; + // assume anything else uses a different (presumably more conventional) scheme + return false; + } }; -class gb_rom_chongwu_device : public gb_rom_mbc5_device -{ -public: - // construction/destruction - gb_rom_chongwu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - virtual uint8_t read_rom(offs_t offset) override; - -protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; - - uint8_t m_protection_checked; -}; +//************************************************************************** +// MBC3 (max 8 MiB ROM, max 32 KiB SRAM, RTC) +//************************************************************************** -class gb_rom_licheng_device : public gb_rom_mbc5_device +class mbc3_device : public rom_mbc_device_base, public device_rtc_interface, public device_nvram_interface { public: - // construction/destruction - gb_rom_licheng_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - virtual void write_bank(offs_t offset, uint8_t data) override; -}; + mbc3_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + rom_mbc_device_base(mconfig, GB_ROM_MBC3, tag, owner, clock), + device_rtc_interface(mconfig, *this), + device_nvram_interface(mconfig, *this), + m_timer_rtc(nullptr), + m_machine_seconds(0), + m_has_rtc_xtal(false), + m_has_battery(false), + m_rtc_enable(0U), + m_rtc_select(0U), + m_rtc_latch(0U) + { + } + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // check for RTC oscillator and backup battery + if (loaded_through_softlist()) + { + // there's a feature tag indicating presence or absence of RTC crystal + char const *const rtcfeature(get_feature("rtc")); + if (rtcfeature) + { + // explicitly specified in software list + if (util::streqlower(rtcfeature, "yes") || util::streqlower(rtcfeature, "true")) + { + logerror("Real-time clock crystal present\n"); + m_has_rtc_xtal = true; + } + else if (util::streqlower(rtcfeature, "no") || util::streqlower(rtcfeature, "false")) + { + logerror("No real-time clock crystal present\n"); + m_has_rtc_xtal = false; + } + else + { + message = "Invalid 'rtc' feature value (must be yes or no)"; + return image_init_result::FAIL; + } + } + else + { + logerror("No 'rtc' feature found, assuming no real-time clock crystal present\n"); + m_has_rtc_xtal = false; + } -class gb_rom_digimon_device : public gb_rom_mbc5_device -{ -public: - // construction/destruction - gb_rom_digimon_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + // if there's an NVRAM region, there must be a backup battery + if (cart_nvram_region()) + { + logerror("Found 'nvram' region, backup battery must be present\n"); + m_has_battery = true; + } + else + { + logerror("No 'nvram' region found, assuming no backup battery present\n"); + m_has_battery = true; + } + } + else + { + gbxfile::leader_1_0 leader; + u8 const *extra; + u32 extralen; + if (gbxfile::get_data(gbx_footer_region(), leader, extra, extralen)) + { + m_has_rtc_xtal = bool(leader.rtc); + m_has_battery = bool(leader.batt); + logerror( + "GBX format image specifies %sreal-time clock crystal present, %sbackup battery present\n", + m_has_rtc_xtal ? "" : "no ", + m_has_battery ? "" : "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_MBC3_RTC_BATT: + case cartheader::TYPE_MBC3_RTC_RAM_BATT: + m_has_rtc_xtal = true; + m_has_battery = true; + break; + case cartheader::TYPE_MBC3: + m_has_rtc_xtal = false; + m_has_battery = false; + break; + case cartheader::TYPE_MBC3_RAM: + case cartheader::TYPE_MBC3_RAM_BATT: + m_has_rtc_xtal = false; + m_has_battery = true; + break; + default: + osd_printf_warning( + "[%s] Unrecognized cartridge type 0x%02X in header, assuming no real-time clock crystal or backup battery present\n", + tag(), + carttype); + m_has_rtc_xtal = false; + m_has_battery = false; + } + logerror( + "Cartridge type 0x%02X in header, %sreal-time clock crystal present, %sbackup battery present\n", + carttype, + m_has_rtc_xtal ? "" : "no ", + m_has_battery ? "" : "no "); + } + } + } - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; + // set up ROM and RAM + if (!install_memory(message, 2, 7)) + return image_init_result::FAIL; + + // install bank switching handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(mbc3_device::enable_ram_rtc))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(mbc3_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(mbc3_device::select_ram_rtc))); + cart_space()->install_write_handler( + 0x6000, 0x7fff, + write8smo_delegate(*this, FUNC(mbc3_device::latch_rtc))); + + // install real-time clock handlers + view_aux(0).install_read_handler( + 0xa000, 0xbfff, + read8mo_delegate(*this, FUNC(mbc3_device::read_rtc))); + view_aux(0).install_write_handler( + 0xa000, 0xbfff, + write8smo_delegate(*this, FUNC(mbc3_device::write_rtc))); + + // all good + return image_init_result::PASS; + }; protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } - virtual void write_ram(offs_t offset, uint8_t data) override; -}; - - -class gb_rom_rockman8_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_rockman8_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + virtual void device_start() override ATTR_COLD + { + rom_mbc_device_base::device_start(); -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } + m_timer_rtc = timer_alloc(FUNC(mbc3_device::rtc_advance_seconds), this); - uint8_t m_bank_mask, m_bank, m_reg; -}; + save_item(NAME(m_rtc_regs)); + save_item(NAME(m_rtc_enable)); + save_item(NAME(m_rtc_select)); + save_item(NAME(m_rtc_latch)); + m_timer_rtc->adjust(attotime(1, 0), 0, attotime(1, 0)); + } -class gb_rom_sm3sp_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_sm3sp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + virtual void device_reset() override ATTR_COLD + { + rom_mbc_device_base::device_reset(); - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + m_rtc_enable = 0U; + m_rtc_select = 0U; + m_rtc_latch = 0U; -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } + set_bank_rom_coarse(0); + set_bank_rom_fine(1); + set_ram_enable(false); + set_bank_ram(0); + } - uint8_t m_bank_mask, m_bank, m_reg, m_mode; -}; + virtual void rtc_clock_updated( + int year, + int month, + int day, + int day_of_week, + int hour, + int minute, + int second) override + { + if (!m_has_rtc_xtal && !m_has_battery) + { + logerror("No real-time clock crystal or no battery present, not updating for elapsed time\n"); + } + else if (std::numeric_limits::min() == m_machine_seconds) + { + logerror("Failed to load machine time from previous session, not updating for elapsed time\n"); + } + else if (BIT(m_rtc_regs[0][4], 6)) + { + logerror("Real-time clock halted, not updating for elapsed time\n"); + } + else + { + // do a simple seconds elapsed since last run calculation + system_time current; + machine().current_datetime(current); + s64 delta(std::make_signed_t(current.time) - m_machine_seconds); + logerror("Previous session time, %d current time %d, delta %d\n", current.time, m_machine_seconds, delta); + if (0 > delta) + { + // This happens if the user runs the emulation faster + // than real time, exits, and then starts again without + // waiting for the difference between emulated and real + // time to elapse. + logerror("Previous session ended in the future, not updating for elapsed time\n"); + } + else + { + logerror( + "Time before applying delta %u %02u:%02u:%02u%s\n", + (u16(BIT(m_rtc_regs[0][4], 0)) << 8) | m_rtc_regs[0][3], + m_rtc_regs[0][2], + m_rtc_regs[0][1], + m_rtc_regs[0][0], + BIT(m_rtc_regs[0][4], 7) ? " (overflow)" : ""); + + // annoyingly, we can get two rollovers if we started with an invalid value + unsigned seconds(delta % 60); + delta /= 60; + if (60 <= m_rtc_regs[0][0]) + { + m_rtc_regs[0][0] = 0U; + --seconds; + ++delta; + } + if (60 <= (m_rtc_regs[0][0] + seconds)) + ++delta; + m_rtc_regs[0][0] = (m_rtc_regs[0][0] + seconds) % 60; + + // minutes is the same + unsigned minutes(delta % 60); + delta /= 60; + if (60 <= m_rtc_regs[0][1]) + { + m_rtc_regs[0][1] = 0U; + --minutes; + ++delta; + } + if (60 <= (m_rtc_regs[0][1] + minutes)) + ++delta; + m_rtc_regs[0][1] = (m_rtc_regs[0][1] + minutes) % 60; + + // hours just has a different rollover point + unsigned hours(delta % 24); + delta /= 24; + if (24 <= m_rtc_regs[0][2]) + { + m_rtc_regs[0][2] = 0U; + --hours; + ++delta; + } + if (24 <= (m_rtc_regs[0][2] + hours)) + ++delta; + m_rtc_regs[0][2] = (m_rtc_regs[0][2] + hours) % 24; + + // days has simple binary rollover + unsigned days(delta % 256); + if (256 <= (m_rtc_regs[0][3] + days)) + ++delta; + m_rtc_regs[0][3] += days; + + // set overflow flag if appropriate + if ((1 < delta) || (BIT(m_rtc_regs[0][4], 0) && delta)) + m_rtc_regs[0][4] |= 0x80; + m_rtc_regs[0][4] ^= BIT(delta, 0); + + logerror( + "Time after applying delta %u %02u:%02u:%02u%s\n", + (u16(BIT(m_rtc_regs[0][4], 0)) << 8) | m_rtc_regs[0][3], + m_rtc_regs[0][2], + m_rtc_regs[0][1], + m_rtc_regs[0][0], + BIT(m_rtc_regs[0][4], 7) ? " (overflow)" : ""); + } + } + } + virtual void nvram_default() override ATTR_COLD + { + // TODO: proper cold RTC state + m_machine_seconds = std::numeric_limits::min(); + for (unsigned i = 0U; std::size(m_rtc_regs[0]) > i; ++i) + m_rtc_regs[0][i] = RTC_MASK[i]; + } -class gb_rom_camera_device : public gb_rom_mbc_device -{ -public: - // construction/destruction - gb_rom_camera_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + virtual bool nvram_read(util::read_stream &file) override ATTR_COLD + { + if (m_has_battery) + { + // read previous machine time (seconds since epoch) and RTC registers + u64 seconds; + std::size_t actual; + if (file.read(&seconds, sizeof(seconds), actual) || (sizeof(seconds) != actual)) + return false; + m_machine_seconds = big_endianize_int64(seconds); + + if (file.read(&m_rtc_regs[0][0], sizeof(m_rtc_regs[0]), actual) || (sizeof(m_rtc_regs[0]) != actual)) + return false; + } + else + { + logerror("No battery present, not loading real-time clock register contents\n"); + } + return true; + } - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + virtual bool nvram_write(util::write_stream &file) override ATTR_COLD + { + // save current machine time as seconds since epoch and RTC registers + system_time current; + machine().current_datetime(current); + u64 const seconds(big_endianize_int64(s64(std::make_signed_t(current.time)))); + std::size_t written; + if (file.write(&seconds, sizeof(seconds), written) || (sizeof(seconds) != written)) + return false; + if (file.write(&m_rtc_regs[0][0], sizeof(m_rtc_regs[0]), written) || (sizeof(m_rtc_regs[0]) != written)) + return false; + return true; + } -protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + virtual bool nvram_can_write() const override ATTR_COLD + { + return m_has_battery; + } private: - void update_camera(); - uint8_t m_camera_regs[54]; -}; - - -//------------------------------------------------- -// gb_rom_mbc*_device - constructor -//------------------------------------------------- - -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) -{ -} + static inline constexpr u8 RTC_MASK[]{ 0x3f, 0x3f, 0x1f, 0xff, 0xc1 }; + static inline constexpr u8 RTC_ROLLOVER[]{ 0x3c, 0x3c, 0x18, 0x00, 0x00 }; -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) -{ -} + TIMER_CALLBACK_MEMBER(rtc_advance_seconds) + { + if (BIT(m_rtc_regs[0][4], 6)) + return; + + if (rtc_increment(0)) + return; + if (rtc_increment(1)) + return; + if (rtc_increment(2)) + return; + if (++m_rtc_regs[0][3]) + return; + + if (BIT(m_rtc_regs[0][4], 0)) + { + LOG("Day counter overflow"); + m_rtc_regs[0][4] |= 0x80; + } + m_rtc_regs[0][4] ^= 0x01; + } -gb_rom_mbc2_device::gb_rom_mbc2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_MBC2, tag, owner, clock) -{ -} - -gb_rom_mbc3_device::gb_rom_mbc3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_MBC3, tag, owner, clock) - , m_rtc_ready(0) -{ -} - -gb_rom_mbc5_device::gb_rom_mbc5_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, type, tag, owner, clock) - , m_rumble(*this, "Rumble") -{ -} - -gb_rom_mbc5_device::gb_rom_mbc5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc5_device(mconfig, GB_ROM_MBC5, tag, owner, clock) -{ -} - -gb_rom_mbc6_device::gb_rom_mbc6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_MBC6, tag, owner, clock), m_latch1(0), m_latch2(0), m_bank_4000(0), m_bank_6000(0) -{ -} - -gb_rom_mbc7_device::gb_rom_mbc7_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_MBC7, tag, owner, clock) -{ -} - -gb_rom_m161_device::gb_rom_m161_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_M161, tag, owner, clock), m_base_bank(0), m_load_disable(0) -{ -} - -gb_rom_mmm01_device::gb_rom_mmm01_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_MMM01, tag, owner, clock), m_romb(0), m_romb_nwe(0), m_ramb(0), m_ramb_nwe(0), m_mode(0), m_mode_nwe(0), m_map(0), m_mux(0) -{ -} - -gb_rom_sachen_mmc1_device::gb_rom_sachen_mmc1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_sachen_mmc1_device(mconfig, GB_ROM_SACHEN1, tag, owner, clock) -{ -} - -gb_rom_sachen_mmc1_device::gb_rom_sachen_mmc1_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, type, tag, owner, clock), m_base_bank(0), m_mask(0), m_mode(0), m_unlock_cnt(0) -{ -} - -gb_rom_sachen_mmc2_device::gb_rom_sachen_mmc2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_sachen_mmc1_device(mconfig, GB_ROM_SACHEN2, tag, owner, clock) -{ -} - -gb_rom_188in1_device::gb_rom_188in1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc1_device(mconfig, GB_ROM_188IN1, tag, owner, clock), m_game_base(0) -{ -} - -gb_rom_sintax_device::gb_rom_sintax_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_SINTAX, tag, owner, clock), m_bank_mask(0), m_bank(0), m_reg(0), m_currentxor(0), m_xor2(0), m_xor3(0), m_xor4(0), m_xor5(0), m_sintax_mode(0) -{ -} - -gb_rom_chongwu_device::gb_rom_chongwu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc5_device(mconfig, GB_ROM_CHONGWU, tag, owner, clock), m_protection_checked(0) -{ -} - -gb_rom_licheng_device::gb_rom_licheng_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc5_device(mconfig, GB_ROM_LICHENG, tag, owner, clock) -{ -} - -gb_rom_digimon_device::gb_rom_digimon_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc5_device(mconfig, GB_ROM_DIGIMON, tag, owner, clock) -{ -} - -gb_rom_rockman8_device::gb_rom_rockman8_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_ROCKMAN8, tag, owner, clock), m_bank_mask(0), m_bank(0), m_reg(0) -{ -} - -gb_rom_sm3sp_device::gb_rom_sm3sp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_mbc_device(mconfig, GB_ROM_SM3SP, tag, owner, clock), m_bank_mask(0), m_bank(0), m_reg(0), m_mode(0) -{ -} - -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 -//------------------------------------------------- - -void gb_rom_mbc_device::shared_start() -{ - save_item(NAME(m_latch_bank)); - save_item(NAME(m_latch_bank2)); - save_item(NAME(m_ram_bank)); - save_item(NAME(m_ram_enable)); -} - -//------------------------------------------------- -// shared_reset -//------------------------------------------------- - -void gb_rom_mbc_device::shared_reset() -{ - m_latch_bank = 0; - m_latch_bank2 = 1; - m_ram_bank = 0; - m_ram_enable = 0; -} - -//------------------------------------------------- -// mapper specific start/reset -//------------------------------------------------- - -void gb_rom_mbc3_device::device_start() -{ - shared_start(); - save_item(NAME(m_rtc_regs)); - save_item(NAME(m_rtc_ready)); -} - -void gb_rom_mbc3_device::device_reset() -{ - shared_reset(); - memset(m_rtc_regs, 0, sizeof(m_rtc_regs)); - m_rtc_ready = 0; -} - -void gb_rom_mbc6_device::device_start() -{ - save_item(NAME(m_bank_4000)); - save_item(NAME(m_bank_6000)); - save_item(NAME(m_latch1)); - save_item(NAME(m_latch2)); - save_item(NAME(m_latch_bank)); - save_item(NAME(m_latch_bank2)); - save_item(NAME(m_ram_bank)); - save_item(NAME(m_ram_enable)); -} - -void gb_rom_mbc6_device::device_reset() -{ - m_bank_4000 = 2; // correct default? - m_bank_6000 = 3; // correct default? - m_latch1 = 0; // correct default? - m_latch2 = 0; // correct default? - - m_latch_bank = 2; // correct default? - m_latch_bank2 = 3; // correct default? - m_ram_bank = 0; - m_ram_enable = 0; -} - -void gb_rom_m161_device::device_start() -{ - shared_start(); - save_item(NAME(m_base_bank)); - save_item(NAME(m_load_disable)); -} - -void gb_rom_m161_device::device_reset() -{ - shared_reset(); - m_base_bank = 0; - m_load_disable = 0; -} - -void gb_rom_mmm01_device::device_start() -{ - shared_start(); - save_item(NAME(m_romb)); - save_item(NAME(m_romb_nwe)); - save_item(NAME(m_ramb)); - save_item(NAME(m_ramb_nwe)); - save_item(NAME(m_mode)); - save_item(NAME(m_mode_nwe)); - save_item(NAME(m_map)); - save_item(NAME(m_mux)); -} - -void gb_rom_mmm01_device::device_reset() -{ - m_romb = 0x000; - m_romb_nwe = 0x00; - m_ramb = 0x00; - m_ramb_nwe = 0x00; - m_mode = 0x00; - m_map = 0x00; - m_mux = 0x00; -} - -void gb_rom_sachen_mmc1_device::device_start() -{ - shared_start(); - save_item(NAME(m_base_bank)); - save_item(NAME(m_mask)); - save_item(NAME(m_mode)); - save_item(NAME(m_unlock_cnt)); -} - -void gb_rom_sachen_mmc1_device::device_reset() -{ - shared_reset(); - m_base_bank = 0x00; - m_mask = 0x00; - m_mode = MODE_LOCKED; - m_unlock_cnt = 0x00; -} - -void gb_rom_sachen_mmc2_device::device_start() -{ - shared_start(); - save_item(NAME(m_base_bank)); - save_item(NAME(m_mask)); - save_item(NAME(m_mode)); - save_item(NAME(m_unlock_cnt)); -} - -void gb_rom_sachen_mmc2_device::device_reset() -{ - shared_reset(); - m_base_bank = 0x00; - m_mask = 0x00; - m_mode = MODE_LOCKED_DMG; - m_unlock_cnt = 0x00; -} - -void gb_rom_sintax_device::device_start() -{ - shared_start(); - save_item(NAME(m_sintax_mode)); - save_item(NAME(m_currentxor)); - save_item(NAME(m_xor2)); - save_item(NAME(m_xor3)); - save_item(NAME(m_xor4)); - save_item(NAME(m_xor5)); -} - -void gb_rom_sintax_device::device_reset() -{ - shared_reset(); - m_sintax_mode = 0; - m_currentxor = 0; - m_xor2 = 0; - m_xor3 = 0; - m_xor4 = 0; - m_xor5 = 0; -} - -void gb_rom_chongwu_device::device_start() -{ - shared_start(); - save_item(NAME(m_protection_checked)); -} - -void gb_rom_chongwu_device::device_reset() -{ - shared_reset(); - m_protection_checked = 0; -} - -void gb_rom_camera_device::device_start() -{ - shared_start(); - save_item(NAME(m_camera_regs)); -} - -void gb_rom_camera_device::device_reset() -{ - shared_reset(); - memset(m_camera_regs, 0, sizeof(m_camera_regs)); -} - - -/*------------------------------------------------- - mapper specific handlers - -------------------------------------------------*/ - -uint8_t gb_rom_mbc_device::read_rom(offs_t offset) -{ - return m_rom[rom_bank_map[m_latch_bank] + offset]; -} - -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; -} - -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; -} - - -// MBC1 - -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)]; - } -} - -void gb_rom_mbc1_device::write_bank(offs_t offset, uint8_t data) -{ - // the mapper only uses inputs A15..A13 - switch (offset & 0xe000) - { - case 0x0000: // RAM Enable Register - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - break; - case 0x2000: // ROM Bank Register - data &= 0x1f; - m_latch_bank2 = data ? data : 1; - m_latch_bank2 &= m_mask; - break; - case 0x4000: // RAM Bank Register - m_ram_bank = data & 0x3; - break; - case 0x6000: // MBC1 Mode Register - m_mode = (data & 0x1) ? MODE_4M_256k : MODE_16M_64k; - break; - } -} - -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]; - } - else - return 0xff; -} - -void gb_rom_mbc1_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty() && m_ram_enable) + void enable_ram_rtc(u8 data) { - int bank = (m_mode == MODE_4M_256k) ? m_ram_bank : 0; - m_ram[ram_bank_map[bank] * 0x2000 + offset] = data; - } -} - - -// 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)]; -} - -void gb_rom_mbc2_device::write_bank(offs_t offset, uint8_t data) -{ - // the mapper only has data lines D3..D0 - data &= 0x0f; - - // the mapper only uses inputs A15..A14, A8 for register accesses - switch (offset & 0xc100) - { - case 0x0000: // RAM Enable Register - m_ram_enable = (data == 0x0a) ? 1 : 0; - break; - case 0x0100: // ROM Bank Register - m_latch_bank2 = (data == 0x00) ? 0x01 : data; - break; + m_rtc_enable = (0x0a == (data & 0x0f)) ? 1U : 0U; + if (!m_rtc_enable) + { + set_ram_enable(false); + } + else if (BIT(m_rtc_select, 3)) + { + LOG( + "%s: RTC register %u enabled\n", + machine().describe_context(), + m_rtc_select & 0x07); + set_view_aux(0); + } + else + { + set_ram_enable(true); + } } -} - -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; -} - - -// MBC3 -void gb_rom_mbc3_device::update_rtc() -{ - system_time curtime; - machine().current_datetime(curtime); - - m_rtc_regs[0] = curtime.local_time.second; - m_rtc_regs[1] = curtime.local_time.minute; - m_rtc_regs[2] = curtime.local_time.hour; - m_rtc_regs[3] = curtime.local_time.day & 0xff; - m_rtc_regs[4] = (m_rtc_regs[4] & 0xf0) | (curtime.local_time.day >> 8); -} - -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)]; -} - -void gb_rom_mbc3_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset < 0x4000) + void bank_switch_fine(u8 data) { - // 7bits data &= 0x7f; - /* Selecting bank 0 == selecting bank 1 */ - if (data == 0) - data = 1; - - m_latch_bank2 = data; + set_bank_rom_fine(data ? data : 1); } - else if (offset < 0x6000) - { - m_ram_bank = data; - } - else if (has_timer) + + void select_ram_rtc(u8 data) { - if (m_rtc_ready == 1 && data == 0) - m_rtc_ready = 0; - if (m_rtc_ready == 0 && data == 1) + // TODO: what happens with the RAM bank outputs when the RTC is selected? + // TODO: what happens for 4-7? + // TODO: is the high nybble ignored altogether? + bank_switch_coarse(data & 0x03); + m_rtc_select = data; + if (m_rtc_enable) { - m_rtc_ready = 1; - update_rtc(); + if (BIT(data, 3)) + { + LOG( + "%s: RTC register %u enabled\n", + machine().describe_context(), + data & 0x07); + set_view_aux(0); + } + else + { + set_ram_enable(true); + } } } -} -uint8_t gb_rom_mbc3_device::read_ram(offs_t offset) -{ - if (m_ram_bank < 4 && m_ram_enable) - { - // RAM - if (!m_ram.empty()) - return m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)]; - } - if (m_ram_bank >= 0x8 && m_ram_bank <= 0xc) + void latch_rtc(u8 data) { - // RTC registers - if (has_timer) - return m_rtc_regs[m_ram_bank - 8]; + // FIXME: does it just check the least significant bit, or does it look for 0x00 and 0x01? + LOG("Latch RTC 0x%02X -> 0x%02X\n", m_rtc_latch, data); + if (!BIT(m_rtc_latch, 0) && BIT(data, 0)) + { + LOG("%s: Latching RTC registers\n", machine().describe_context()); + std::copy(std::begin(m_rtc_regs[0]), std::end(m_rtc_regs[0]), std::begin(m_rtc_regs[1])); + } + m_rtc_latch = data; } - return 0xff; -} -void gb_rom_mbc3_device::write_ram(offs_t offset, uint8_t data) -{ - if (m_ram_bank < 4 && m_ram_enable) + u8 read_rtc(address_space &space) { - // RAM - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; - } - if (m_ram_bank >= 0x8 && m_ram_bank <= 0xc && m_ram_enable) - { - // RTC registers are writeable too - if (has_timer) - m_rtc_regs[m_ram_bank - 8] = data; + u8 const reg(m_rtc_select & 0x07); + if (std::size(m_rtc_regs[1]) > reg) + { + LOG( + "%s: Read RTC register %u = 0x%02X\n", + machine().describe_context(), + reg, + m_rtc_regs[1][reg]); + return m_rtc_regs[1][reg]; + } + else + { + LOG( + "%s: Read invalid RTC register %u\n", + machine().describe_context(), + reg); + return space.unmap(); + } } -} -// 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)]; -} - -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 write_rtc(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; - } - else if (offset < 0x4000) - { - // MBC5 has a 9 bit bank select - // Writing into 3000-3fff sets the 9th bit - m_latch_bank2 = (m_latch_bank2 & 0xff) | ((data & 0x01) << 8); - } - else if (offset < 0x6000) - { - data &= 0x0f; - if (has_rumble) + u8 const reg(m_rtc_select & 0x07); + if (std::size(m_rtc_regs[0]) > reg) { - m_rumble = BIT(data, 3); - data &= 0x7; + LOG( + "%s: Write RTC register %u = 0x%02X\n", + machine().describe_context(), + reg, + data); + if (4U == reg) + { + // TODO: are bits 5-1 physically present, and if not, what do they read as? + // TODO: how does halting the RTC interact with the prescaler? + data &= 0xc1; + m_rtc_regs[0][reg] = data; + } + else + { + m_rtc_regs[0][reg] = data; + } + } + else + { + LOG( + "%s: Write invalid RTC register %u = 0x%02X\n", + machine().describe_context(), + reg, + data); } - m_ram_bank = 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; -} - -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; -} - -// 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) -{ - if (offset < 0x2000) - { - logerror( "%s write to mbc6 ram enable area: %04X <- 0x%02X\n", machine().describe_context(), offset, data ); - } - else if (offset < 0x3000) + u8 rtc_increment(unsigned index) { - if (!(offset & 0x0800)) - m_latch1 = data; - else if (data == 0x00) - m_bank_4000 = m_latch1; + m_rtc_regs[0][index] = (m_rtc_regs[0][index] + 1) & RTC_MASK[index]; + if (RTC_ROLLOVER[index] == (m_rtc_regs[0][index] & RTC_ROLLOVER[index])) + m_rtc_regs[0][index] = 0U; + return m_rtc_regs[0][index]; } - else if (offset < 0x4000) - { - if (!(offset & 0x0800)) - m_latch2 = data; - else if (data == 0x00) - m_bank_6000 = m_latch2; - } -} -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; -} + emu_timer *m_timer_rtc; + s64 m_machine_seconds; + bool m_has_rtc_xtal; + bool m_has_battery; -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; -} + u8 m_rtc_regs[2][5]; + u8 m_rtc_enable; + u8 m_rtc_select; + u8 m_rtc_latch; +}; -// 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)]; -} -void gb_rom_mbc7_device::write_bank(offs_t offset, uint8_t data) +//************************************************************************** +// MBC5 (max 128 MiB ROM, max 128 KiB SRAM) +//************************************************************************** + +class mbc5_device : public mbc5_device_base { - if (offset < 0x2000) - { - // FIXME: Add RAM enable support - logerror("%s Write to ram enable register 0x%04X <- 0x%02X\n", machine().describe_context(), offset, data); - } - else if (offset < 0x3000) +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) { - logerror( "%s write to mbc7 rom select register: 0x%04X <- 0x%02X\n", machine().describe_context(), 0x2000 + offset, data ); - /* Bit 12 must be set for writing to the mbc register */ - if (offset & 0x0100) - m_latch_bank2 = data; } - else + + virtual image_init_result load(std::string &message) override 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) + // check whether rumble motor is present + char const *const rumblefeature(get_feature("rumble")); + if (rumblefeature) { - switch (offset & 0x7000) + // explicitly specified in software list + if (util::streqlower(rumblefeature, "yes") || util::streqlower(rumblefeature, "true")) + { + logerror("Rumble motor present\n"); + m_has_rumble = true; + } + else if (util::streqlower(rumblefeature, "no") || util::streqlower(rumblefeature, "false")) { - case 0x3000: /* 0x3000-0x3fff */ - case 0x4000: /* 0x4000-0x4fff */ - case 0x5000: /* 0x5000-0x5fff */ - case 0x6000: /* 0x6000-0x6fff */ - case 0x7000: /* 0x7000-0x7fff */ - break; + logerror("No rumble motor present\n"); + m_has_rumble = false; + } + else + { + message = "Invalid 'rumble' feature value (must be yes or no)"; + return image_init_result::FAIL; + } + } + 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 "); + } } } - } -} - -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)]; -} + // install base MBC5 handlers + image_init_result const result(mbc5_device_base::load(message)); + if (image_init_result::PASS != result) + return result; -void gb_rom_m161_device::write_bank(offs_t offset, uint8_t data) -{ - // the mapper (74HC161A) only has data lines D2..D0 - data &= 0x07; + // install rumble-aware handler if appropriate + if (m_has_rumble) + { + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(mbc5_device::bank_switch_coarse))); + } - // 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; + // all good + return image_init_result::PASS; } -} +protected: + virtual void device_start() override ATTR_COLD + { + mbc5_device_base::device_start(); -// MMM01 + m_rumble.resolve(); + } -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); + virtual void device_reset() override ATTR_COLD + { + mbc5_device_base::device_reset(); - // zero-adjust RA18..RA14 - romb = (romb ? romb : 0x01); - // if unmapped, force - romb = (m_map ? romb : 0x01); + if (m_has_rumble) + m_rumble = 0; + } - // RB 0 logic - if (!(offset & 0x4000)) - romb = 0x00; +private: + void bank_switch_coarse(u8 data) + { + assert(m_has_rumble); + mbc5_device_base::bank_switch_coarse(data); + m_rumble = BIT(data, 3); + } - // combine with base - romb |= romb_base; + output_finder<> m_rumble; + bool m_has_rumble; +}; - // 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 variant used by Sintax games +//************************************************************************** -void gb_rom_mmm01_device::write_bank(offs_t offset, uint8_t data) +class sintax_device : public mbc5_logo_spoof_device_base { - // the mapper only has data lines D6..D0 - data &= 0x7f; - - // the mapper only uses inputs A15..A13 - switch (offset & 0xe000) +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) { - case 0x0000: // Map Latch, AA #WE, RAM Enable - if (!m_map) { - m_ramb_nwe = (data & (0x30)) >> 4; - m_map = (data & 0x40); - } - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - break; - case 0x2000: // RA20..RA19 RA18..RA14 - if (!m_map) - m_romb = (m_romb & ~0x60) | (data & 0x60); - - m_romb = (m_romb & (~0x1f | m_romb_nwe)) | (data & (0x1f & ~m_romb_nwe)); - break; - case 0x4000: // Mode #WE, RA22..RA21, AA16..AA15, AA14..AA13 - if (!m_map) { - m_mode_nwe = data & 0x40; - m_romb = (m_romb & ~0x180) | ((data & 0x30) << 3); - m_ramb = (m_ramb & ~0x0c) | (data & 0x0c); - } - - m_ramb = (m_ramb & (~0x03 | m_ramb_nwe)) | (data & (0x03 & ~m_ramb_nwe)); - break; - case 0x6000: // Mux, RA18..RA15 #WE/Mask, ???, MBC1 Mode - if (!m_map) { - m_mux = data & 0x40; - // m_romb_nwe is aligned to RA14, hence >> 1 instead of >> 2 - m_romb_nwe = (data & 0x3c) >> 1; - } - - if (!m_mode_nwe) - m_mode = data & 0x01; - break; - default: - break; } -} - -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; - // multiplex with RA20..RA19 - if (m_mux) - ramb = (ramb & ~0x03) | ((m_romb & 0x60) >> 5); - - if (!m_ram.empty() && m_ram_enable) + virtual image_init_result load(std::string &message) override ATTR_COLD { - return m_ram[ram_bank_map[ramb] * 0x2000 + (offset & 0x1fff)]; + // install regular MBC5 handlers + image_init_result const result(mbc5_logo_spoof_device_base::load(message)); + if (image_init_result::PASS != result) + return result; + + // bank numbers and ROM contents are scrambled for protection + cart_space()->install_read_handler( + 0x0000, 0x7fff, + read8sm_delegate(*this, FUNC(sintax_device::read_rom))); + cart_space()->install_write_handler( + 0x2000, 0x2fff, + write8smo_delegate(*this, FUNC(sintax_device::bank_switch_fine_low_scrambled))); + cart_space()->install_write_handler( + 0x5000, 0x5fff, + write8smo_delegate(*this, FUNC(sintax_device::set_scramble_mode))); + cart_space()->install_write_handler( + 0x7000, 0x70ff, 0x0000, 0x0f00, 0x0000, + write8sm_delegate(*this, FUNC(sintax_device::set_xor))); + + // all good + return image_init_result::PASS; } - 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; +protected: + virtual void device_start() override ATTR_COLD + { + mbc5_logo_spoof_device_base::device_start(); - // multiplex with RA20..RA19 - if (m_mux) - ramb = (ramb & ~0x03) | ((m_romb & 0x60) >> 5); + save_item(NAME(m_scramble_mode)); + save_item(NAME(m_xor_index)); + save_item(NAME(m_xor_table)); + } - if (!m_ram.empty() && m_ram_enable) + virtual void device_reset() override ATTR_COLD { - m_ram[ram_bank_map[ramb] * 0x2000 + (offset & 0x1fff)] = data; + mbc5_logo_spoof_device_base::device_reset(); + + m_scramble_mode = 0U; + m_xor_index = 0U; + std::fill(std::begin(m_xor_table), std::end(m_xor_table), 0U); } -} -// Sachen MMC1 +private: + u8 read_rom(offs_t offset) + { + offset = rom_access(offset); + if (BIT(offset, 14)) + return bank_rom_high_base()[offset & 0x3fff] ^ m_xor_table[m_xor_index]; + else + return bank_rom_low_base()[offset]; + } -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) + void bank_switch_fine_low_scrambled(u8 data) { - case 0x0000: /* Base ROM Bank Register */ + m_xor_index = data & 0x03; - 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)); + 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 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)); + case 0x01: // Langrisser + data = bitswap<8>(data, 0, 1, 6, 7, 4, 5, 2, 3); break; - - case 0x4000: /* ROM Bank Mask Register */ - - if ((m_latch_bank2 & 0x30) == 0x30) - m_mask = data; - //logerror("write to mask %X - %X\n", data, (m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask)); + case 0x05: // Maple Story, Pokemon Platinum + data = bitswap<8>(data, 7, 6, 1, 0, 3, 2, 5, 4); // needs verification break; - - case 0x6000: - - /* nothing happens when writing to 0x6000-0x7fff, as verified by Tauwasser */ + 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 default: - - //logerror("write to unknown/unmapped area %04X <= %02X\n", offset, data); - /* did not extensively test other unlikely ranges */ break; - } -} - -// Sachen MMC2 + } -uint8_t gb_rom_sachen_mmc2_device::read_rom(offs_t offset) -{ - uint16_t off_edit = offset; + set_bank_rom_fine((bank_rom_fine() & 0x0100) | u16(data)); + } - /* 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 */ + void set_scramble_mode(u8 data) + { + if (!m_scramble_mode) + { + LOG( + "%s: Set scramble mode = 0x%02X\n", + machine().describe_context(), + data); + m_scramble_mode = data; + bank_switch_fine_low_scrambled(0x01); + } + else + { + LOG( + "%s: Ignoring scramble mode = 0x%02X when already set to 0x%02X\n", + machine().describe_context(), + data, + m_scramble_mode); + } + } - 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; + void set_xor(offs_t offset, u8 data) + { + u8 const index(offset >> 4); + if ((2 <= index) && (6 > index)) + { + LOG( + "%s: Set XOR entry %u = 0x%02X\n", + machine().describe_context(), + index - 2, + data); + m_xor_table[index - 2] = data; + } } - if (m_unlock_cnt != 0x30) - m_unlock_cnt++; + u8 m_scramble_mode; + u8 m_xor_index; + u8 m_xor_table[4]; +}; - /* Logo Switch */ - if (m_mode == MODE_LOCKED_CGB) - off_edit |= 0x80; - /* Header Un-Scramble */ - if ((off_edit & 0xff00) == 0x0100) { - off_edit &= 0xffac; - off_edit |= ((offset >> 6) & 0x01) << 0; - off_edit |= ((offset >> 4) & 0x01) << 1; - off_edit |= ((offset >> 1) & 0x01) << 4; - off_edit |= ((offset >> 0) & 0x01) << 6; - } - //logerror("read from %04X (%04X) cnt: %02X\n", offset, off_edit, m_unlock_cnt); - if (offset & 0x4000) /* RB1 */ - return m_rom[rom_bank_map[(m_base_bank & m_mask) | (m_latch_bank2 & ~m_mask)] * 0x4000 + (offset & 0x3fff)]; - else /* RB0 */ - return m_rom[rom_bank_map[(m_base_bank & m_mask) | (m_latch_bank & ~m_mask)] * 0x4000 + (off_edit & 0x3fff)]; -} +//************************************************************************** +// MBC5 variant used by Chǒngwù Xiǎo Jīnglíng - Jié Jīn Tǎ Zhī Wáng +//************************************************************************** -uint8_t gb_rom_sachen_mmc2_device::read_ram(offs_t offset) +class chongwu_device : public mbc5_logo_spoof_device_base { - if (m_mode == MODE_LOCKED_DMG) { - m_unlock_cnt = 0x00; - m_mode = MODE_LOCKED_CGB; +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) + { } - return 0xff; -} +protected: + virtual void device_start() override ATTR_COLD + { + mbc5_logo_spoof_device_base::device_start(); -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; + save_item(NAME(m_protection_checked)); } -} - + virtual void device_reset() override ATTR_COLD + { + mbc5_logo_spoof_device_base::device_reset(); -// 188 in 1 pirate (only preliminary) + m_protection_checked = 0U; -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)]; -} + 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(); + }); + } -void gb_rom_188in1_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset == 0x7b00) +private: + void install_protection_tap() { - if (data < 0x80) - logerror("write to 0x%X data 0x%X\n", offset, data); - else + if (!m_installing_tap) { - data -= 0x80; - m_game_base = 0x400000 + (data * 0x8000); - //logerror("offset 0x%X\n", m_game_base); + 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 if (offset == 0x7b01 || offset == 0x7b02) - { - // what do these writes do? - printf("write to 0x%X data 0x%X\n", offset, data); - } - else - gb_rom_mbc1_device::write_bank(offset, data); -} + memory_passthrough_handler m_protection_tap; + util::notifier_subscription m_notif_cart_space; -// MBC5 variant used by Li Cheng / Niutoude games + u8 m_protection_checked; + bool m_installing_tap; +}; -void gb_rom_licheng_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset > 0x2100 && offset < 0x3000) - return; - gb_rom_mbc5_device::write_bank(offset, data); -} -// MBC5 variant used by Chong Wu Xiao Jing Ling (this appears to be a re-release of a Li Cheng / Niutoude game, -// given that it contains the Niutoude logo, with most protection checks patched out) +//************************************************************************** +// MBC5 variant used by Li Cheng/Niutoude games +//************************************************************************** -uint8_t gb_rom_chongwu_device::read_rom(offs_t offset) +class licheng_device : public mbc5_logo_spoof_device_base { - // protection check at the first read here... - if (offset == 0x41c3 && !m_protection_checked) +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) { - m_protection_checked = 1; - return 0x5d; } - if (offset < 0x4000) - return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)]; - else - return m_rom[rom_bank_map[m_latch_bank2] * 0x4000 + (offset & 0x3fff)]; -} - -// MBC5 variant used by Sintax games - -void gb_rom_sintax_device::set_xor_for_bank(uint8_t bank) -{ - switch (bank & 0x0f) + virtual image_init_result load(std::string &message) override ATTR_COLD { - case 0x00: case 0x04: case 0x08: case 0x0c: - m_currentxor = m_xor2; - break; - case 0x01: case 0x05: case 0x09: case 0x0d: - m_currentxor = m_xor3; - break; - case 0x02: case 0x06: case 0x0a: case 0x0e: - m_currentxor = m_xor4; - break; - case 0x03: case 0x07: case 0x0b: case 0x0f: - m_currentxor = m_xor5; - break; + // install regular MBC5 handlers + image_init_result const result(mbc5_logo_spoof_device_base::load(message)); + if (image_init_result::PASS != 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 image_init_result::PASS; } -} +}; + -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; -} -void gb_rom_sintax_device::write_bank(offs_t offset, uint8_t data) +//************************************************************************** +// Fast Fame VF001 MBC5 variant with protection +//************************************************************************** + +class vf001_device : public mbc5_logo_spoof_device_base { - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset < 0x3000) +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) { - set_xor_for_bank(data); + } - switch (m_sintax_mode & 0x0f) + virtual image_init_result 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)) { - 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; + 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 = (m_latch_bank2 & 0x100) | data; + + // install regular MBC5 handlers + image_init_result const result(mbc5_logo_spoof_device_base::load(message)); + if (image_init_result::PASS != 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, + read8sm_delegate(*this, FUNC(vf001_device::read_rom))); + cart_space()->install_write_handler( + 0x6000, 0x700f, 0x100f, 0x0000, 0x0000, + write8sm_delegate(*this, FUNC(vf001_device::prot_cmd))); + + // all good + return image_init_result::PASS; } - else if (offset < 0x4000) + +protected: + virtual void device_start() override ATTR_COLD { - m_latch_bank2 = (m_latch_bank2 & 0xff) | ((data & 0x01) << 8); + 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)); } - else if (offset < 0x5000) + + virtual void device_reset() override ATTR_COLD { - data &= 0x0f; - m_ram_bank = data; + mbc5_logo_spoof_device_base::device_reset(); + + m_prot_cmd_enable = 0U; + m_readback_length = 0U; + m_readback_count = 0U; + m_split_enable = 0U; } - else if (offset < 0x6000) + +private: + u8 read_rom(offs_t offset) { - if (!m_sintax_mode) + u8 const data(mbc5_logo_spoof_device_base::read_rom(offset)); + + if (!m_readback_count && m_readback_length && !machine().side_effects_disabled()) { - m_sintax_mode = data; - write_bank(0x2000, 1); //force a fake bank switch + 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; + } } -// printf("sintax mode %x\n", m_sintax_mode & 0xf); + + if (m_readback_count) + { + 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(m_bank_rom_upper->base())[offset]; } - else if (offset >= 0x7000) + + void prot_cmd(offs_t offset, u8 data) { - switch ((offset & 0x00f0) >> 4) + 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) { - case 2: - m_xor2 = data; + 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 3: - m_xor3 = data; + 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 4: - m_xor4 = data; + case 0x1003: + m_readback_bank = m_prot_cmd_data; + LOG( + "%s: Readback bank = 0x%02X\n", + machine().describe_context(), + m_readback_bank); break; - case 5: - m_xor5 = data; + 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; + } } - - if (m_currentxor == 0) - set_xor_for_bank(4); } -} + memory_bank_creator m_bank_rom_upper; + u8 m_bank_mask_rom_upper; + u8 m_command_preload; -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; -} + 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; - Further MBC5 variants to emulate: + u16 m_split_address; + u8 m_split_enable; +}; - 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... +//************************************************************************** +// Yong Yong Digimon 2 (and maybe 4?) +//************************************************************************** +/* + Digimon 2 writes to 0x2000 to set the fine ROM bank, then writes a series + of values to 0x2400 that the patched version does not write. + Digimon 4 seems to share part of the 0x2000 behavior, but does not write + to 0x2400. */ -// MBC5 variant used by Digimon 2 (and maybe 4?) - -uint8_t gb_rom_digimon_device::read_rom(offs_t offset) +class digimon_device : public rom_mbc_device_base { - 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_digimon_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) - m_ram_enable = ((data & 0x0f) == 0x0a) ? 1 : 0; - else if (offset == 0x2000) - { -// printf("written $02 %X at %X\n", data, offset); - if (!data) - data++; - m_latch_bank2 = data/2; - } - else if (offset < 0x3000) - { -// printf("written $03 %X at %X\n", data, offset); - } - else if (offset < 0x4000) - { -// printf("written $04 %X at %X\n", data, offset); - } - else if (offset < 0x6000) - { -// printf("written $05-$06 %X at %X\n", data, offset); - data &= 0x0f; - m_ram_bank = data; - } -// else -// printf("written $07 %X at %X\n", data, offset); -} - -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; -} - -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; -} - - -// 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)]; -} - -void gb_rom_rockman8_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset < 0x2000) - return; - else if (offset < 0x4000) +public: + digimon_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + rom_mbc_device_base(mconfig, GB_ROM_DIGIMON, tag, owner, clock) { - // 5bits only - data &= 0x1f; - if (data == 0) - data = 1; - if (data > 0xf) - data -= 8; - - m_latch_bank2 = data; } -} - -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...) -uint8_t gb_rom_sm3sp_device::read_rom(offs_t offset) -{ - if (offset < 0x4000) - return m_rom[rom_bank_map[0] * 0x4000 + (offset & 0x3fff)]; - else - return m_rom[m_latch_bank2 * 0x4000 + (offset & 0x3fff)]; -} - -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; - - m_latch_bank2 = smb3_table1[data]; - if (m_mode) - { - switch (m_latch_bank2) - { - case 0x02: m_latch_bank2 = 4; break; - case 0x03: m_latch_bank2 = 6; break; - case 0x04: m_latch_bank2 = 2; break; - case 0x05: m_latch_bank2 = 3; break; - case 0x06: m_latch_bank2 = 5; break; - case 0x0b: m_latch_bank2 = 0xd; break; - case 0x0c: m_latch_bank2 = 0xb; break; - case 0x0d: m_latch_bank2 = 0xc; break; - - case 0x00: - case 0x01: - case 0x08: - case 0x0f: - case 0x13: - default: - break; - } - } - } - else if (offset < 0x5000) + virtual image_init_result load(std::string &message) override ATTR_COLD { -// printf("write $5 %X at %X\n", data, offset); - //maybe rumble?? + // set up ROM and RAM + if (!install_memory(message, 4, 7)) + return image_init_result::FAIL; + + // install handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(digimon_device::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x2000, + write8smo_delegate(*this, FUNC(digimon_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(digimon_device::bank_switch_coarse))); + + // all good + return image_init_result::PASS; } - else if (offset < 0x6000) - { -// printf("write mode %x\n", data); - m_mode = BIT(data, 5); -// write_bank(0x2000, 1); - } -} -uint8_t gb_rom_sm3sp_device::read_ram(offs_t offset) -{ - if (!m_ram.empty()) - return m_ram[offset]; - else - return 0xff; -} - -void gb_rom_sm3sp_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty()) - m_ram[offset] = data; -} - -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) - { - // 7bits - data &= 0x7f; - /* Selecting bank 0 == selecting bank 1 */ - if (data == 0) - data = 1; - - m_latch_bank2 = data; - } - else if (offset < 0x6000) +protected: + virtual void device_reset() override ATTR_COLD { - m_ram_bank = data & 0x1f; - } -} + rom_mbc_device_base::device_reset(); -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) - { - /* 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)]; + set_bank_rom_coarse(0); + set_bank_rom_fine(1); + set_ram_enable(false); + set_bank_ram(0); } - return 0; -} -void gb_rom_camera_device::write_ram(offs_t offset, uint8_t data) -{ - if ((m_ram_bank & 0x10) != 0) - { - if (offset == 0) - { - m_camera_regs[0] = data & 0x7; - if (data & 0x1) update_camera(); - } - else if (offset < 54) - { - m_camera_regs[offset] = data; - } - } - else if (m_ram_enable && (m_camera_regs[0] & 0x1) == 0) +private: + void bank_switch_fine(u8 data) { - // RAM - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + (offset & 0x1fff)] = data; + data >>= 1; + set_bank_rom_fine(data ? data : 1); } -} +}; } // anonymous namespace +} // namespace bus::gameboy + // device type definition -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC1, device_gb_cart_interface, gb_rom_mbc1_device, "gb_rom_mbc1", "GB MBC1 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC2, device_gb_cart_interface, gb_rom_mbc2_device, "gb_rom_mbc2", "GB MBC2 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC3, device_gb_cart_interface, gb_rom_mbc3_device, "gb_rom_mbc3", "GB MBC3 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC5, device_gb_cart_interface, gb_rom_mbc5_device, "gb_rom_mbc5", "GB MBC5 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC6, device_gb_cart_interface, gb_rom_mbc6_device, "gb_rom_mbc6", "GB MBC6 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC7, device_gb_cart_interface, gb_rom_mbc7_device, "gb_rom_mbc7", "GB MBC7 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_M161, device_gb_cart_interface, gb_rom_m161_device, "gb_rom_m161", "GB M161 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MMM01, device_gb_cart_interface, gb_rom_mmm01_device, "gb_rom_mmm01", "GB MMM01 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SACHEN1, device_gb_cart_interface, gb_rom_sachen_mmc1_device, "gb_rom_sachen1", "GB Sachen MMC1 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SACHEN2, device_gb_cart_interface, gb_rom_sachen_mmc2_device, "gb_rom_sachen2", "GB Sachen MMC2 Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_188IN1, device_gb_cart_interface, gb_rom_188in1_device, "gb_rom_188in1", "GB 188in1") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SINTAX, device_gb_cart_interface, gb_rom_sintax_device, "gb_rom_sintax", "GB MBC5 Sintax Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_CHONGWU, device_gb_cart_interface, gb_rom_chongwu_device, "gb_rom_chongwu", "GB Chong Wu Xiao Jing Ling") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_LICHENG, device_gb_cart_interface, gb_rom_licheng_device, "gb_rom_licheng", "GB MBC5 Li Cheng Carts") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_DIGIMON, device_gb_cart_interface, gb_rom_digimon_device, "gb_rom_digimon", "GB Digimon") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_ROCKMAN8, device_gb_cart_interface, gb_rom_rockman8_device, "gb_rom_rockman8", "GB MBC1 Rockman 8") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SM3SP, device_gb_cart_interface, gb_rom_sm3sp_device, "gb_sm3sp", "GB MBC1 Super Mario 3 Special") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_CAMERA, device_gb_cart_interface, gb_rom_camera_device, "gb_rom_camera", "GB Camera") +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_MBC3, device_gb_cart_interface, bus::gameboy::mbc3_device, "gb_rom_mbc3", "Game Boy MBC3 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_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_VF001, device_gb_cart_interface, bus::gameboy::vf001_device, "gb_rom_vf001", "Game Boy Vast Fame VF001 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_DIGIMON, device_gb_cart_interface, bus::gameboy::digimon_device, "gb_rom_digimon", "Game Boy Digimon 2 Cartridge") diff --git a/src/devices/bus/gameboy/mbc.h b/src/devices/bus/gameboy/mbc.h index 7b0c980ace0..6185b5bf7cb 100644 --- a/src/devices/bus/gameboy/mbc.h +++ b/src/devices/bus/gameboy/mbc.h @@ -3,26 +3,17 @@ #ifndef MAME_BUS_GAMEBOY_MBC_H #define MAME_BUS_GAMEBOY_MBC_H -#include "gb_slot.h" +#include "slot.h" DECLARE_DEVICE_TYPE(GB_ROM_MBC1, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_MBC2, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_MBC3, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_MBC5, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_MBC6, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_MBC7, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_M161, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_MMM01, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_SACHEN1, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_SACHEN2, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_188IN1, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_SINTAX, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_CHONGWU, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_LICHENG, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_VF001, device_gb_cart_interface) DECLARE_DEVICE_TYPE(GB_ROM_DIGIMON, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_ROCKMAN8, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_SM3SP, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_CAMERA, device_gb_cart_interface) #endif // MAME_BUS_GAMEBOY_MBC_H diff --git a/src/devices/bus/gameboy/mbc2.cpp b/src/devices/bus/gameboy/mbc2.cpp new file mode 100644 index 00000000000..b28828e560a --- /dev/null +++ b/src/devices/bus/gameboy/mbc2.cpp @@ -0,0 +1,225 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Memory Bank Controller 2 + + Low-cost controller for games with modest ROM requirements and save file + support. Supports up to 256 KiB ROM (16 16 KiB pages), and has 512 nybbles + of internal static RAM. + + Only A15-A14, A8-A0, D3-D0, and the read/write/RAM select lines are + connected to the MBC2 chip, i.e. addresses are effectively masked with + 0xC01F and data is effectively masked with 0x0F. + + 0x0000-3FFF R - Fixed ROM bank, always first page of ROM. + 0x4000-7FFF R - Selectable ROM bank, page 1-15 of ROM. + 0xA000-A1FF RW - Internal static RAM on low nybble if enabled. + Mirrored up to 0xBFFF. + + 0b0011xxx0xxxx W - Static RAM enable - write 0x0A on low nybble to enable, + any other value to disable. + 0b0011xxx1xxxx W - Select ROM page mapped at 0x4000. Only low nybble is + significant. Writing 0 selects page 1. + + ***************************************************************************/ + +#include "emu.h" +#include "mbc2.h" + +#include "cartbase.h" +#include "cartheader.h" + +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class mbc2_device : public mbc_device_base +{ +public: + mbc2_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual image_init_result load(std::string &message) override ATTR_COLD; + virtual void unload() override ATTR_COLD; + +protected: + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + +private: + static inline constexpr unsigned RAM_SIZE = 512; + + u8 ram_mbc_read(address_space &space, offs_t offset); + void ram_mbc_write(offs_t offset, u8 data); + void ram_mbc_enable(u8 data); + void bank_rom_switch(u8 data); + + std::unique_ptr m_ram_mbc; + u8 m_ram_mbc_enable; + bool m_battery_present; +}; + + +mbc2_device::mbc2_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + mbc_device_base(mconfig, GB_ROM_MBC2, tag, owner, clock), + m_ram_mbc_enable(0U), + m_battery_present(false) +{ +} + + +image_init_result mbc2_device::load(std::string &message) +{ + // first check ROM + set_bank_bits_rom(4); + if (!check_rom(message)) + return image_init_result::FAIL; + + // decide whether to enable battery backup + memory_region *const nvramregion(cart_nvram_region()); + if (nvramregion) + { + if (nvramregion->bytes() != RAM_SIZE) + { + message = "Unsupported cartridge NVRAM size (only MBC2 internal 512 nybble RAM is supported)"; + return image_init_result::FAIL; + } + m_battery_present = true; + logerror("Found 'nvram' region, battery backup enabled\n"); + } + else if (loaded_through_softlist()) + { + logerror("No 'nvram' region found, battery backup disabled\n"); + m_battery_present = false; + } + else + { + // guess based on header + u8 const carttype((&cart_rom_region()->as_u8())[cartheader::OFFSET_TYPE]); + switch (carttype) + { + case cartheader::TYPE_MBC2: + m_battery_present = false; + break; + case cartheader::TYPE_MBC2_BATT: + m_battery_present = true; + break; + default: + osd_printf_warning( + "[%s] Unrecognized cartridge type 0x%02X in header, assuming no battery present\n", + tag(), + carttype); + m_battery_present = false; + } + osd_printf_verbose( + "[%s] Cartridge type 0x%02X in header, battery backup %s\n", + tag(), + carttype, + m_battery_present ? "enabled" : "disabled"); + } + + // install ROM, RAM and handlers + install_rom(); + cart_space()->install_read_handler( + 0xa000, 0xa1ff, 0x0000, 0x1e00, 0x0000, + read8m_delegate(*this, FUNC(mbc2_device::ram_mbc_read))); + cart_space()->install_write_handler( + 0xa000, 0xa1ff, 0x0000, 0x1e00, 0x0000, + write8sm_delegate(*this, FUNC(mbc2_device::ram_mbc_write))); + cart_space()->install_write_handler( + 0x0000, 0x00ff, 0x0000, 0x3e00, 0x0000, + write8smo_delegate(*this, FUNC(mbc2_device::ram_mbc_enable))); + cart_space()->install_write_handler( + 0x0100, 0x01ff, 0x0000, 0x3e00, 0x0000, + write8smo_delegate(*this, FUNC(mbc2_device::bank_rom_switch))); + + // load battery backup if appropriate + if (nvramregion) + battery_load(m_ram_mbc.get(), RAM_SIZE, nvramregion->base()); + else if (m_battery_present) + battery_load(m_ram_mbc.get(), RAM_SIZE, 0x0f); + for (unsigned i = 0U; RAM_SIZE > i; ++i) + m_ram_mbc[i] &= 0x0f; + + // all good + return image_init_result::PASS; +} + + +void mbc2_device::unload() +{ + if (m_battery_present) + battery_save(m_ram_mbc.get(), RAM_SIZE); +} + + +void mbc2_device::device_start() +{ + mbc_device_base::device_start(); + + m_ram_mbc = std::make_unique(RAM_SIZE); + + save_pointer(NAME(m_ram_mbc), RAM_SIZE); + save_item(NAME(m_ram_mbc_enable)); +} + + +void mbc2_device::device_reset() +{ + mbc_device_base::device_reset(); + + m_ram_mbc_enable = 0U; + + set_bank_rom(1); +} + + +u8 mbc2_device::ram_mbc_read(address_space &space, offs_t offset) +{ + if (m_ram_mbc_enable) + return (m_ram_mbc[offset] & 0x0f) | (space.unmap() & 0xf0); + else + return space.unmap(); +} + + +void mbc2_device::ram_mbc_write(offs_t offset, u8 data) +{ + if (m_ram_mbc_enable) + m_ram_mbc[offset] = data & 0x0f; +} + + +void mbc2_device::ram_mbc_enable(u8 data) +{ + m_ram_mbc_enable = (0x0a == (data & 0x0f)) ? 1U : 0U; + LOG( + "%s: Internal MBC2 RAM %s\n", + machine().describe_context(), + m_ram_mbc_enable ? "enabled" : "disabled"); +} + + +void mbc2_device::bank_rom_switch(u8 data) +{ + data &= 0x0f; + set_bank_rom(data ? data : 1); +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC2, device_gb_cart_interface, bus::gameboy::mbc2_device, "gb_rom_mbc2", "Game Boy MBC2 Cartridge") diff --git a/src/devices/bus/gameboy/mbc2.h b/src/devices/bus/gameboy/mbc2.h new file mode 100644 index 00000000000..823347720a6 --- /dev/null +++ b/src/devices/bus/gameboy/mbc2.h @@ -0,0 +1,18 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Memory Bank Controller 2 + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_MBC2_H +#define MAME_BUS_GAMEBOY_MBC2_H + +#pragma once + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_MBC2, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_MBC2_H diff --git a/src/devices/bus/gameboy/mbc6.cpp b/src/devices/bus/gameboy/mbc6.cpp new file mode 100644 index 00000000000..e8072bf10f8 --- /dev/null +++ b/src/devices/bus/gameboy/mbc6.cpp @@ -0,0 +1,617 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Memory Bank Controller 6 + + Only used for one game, the cartridges contain a Macronix MX29F008TC-14 + Flash ROM (8-bit, 1 MiB), as well as the MBC6 chip, program ROM, + W24257S-70LL static RAM, MM1134, and CR1616 backup cell. Supports up to + 1 MiB ROM (128 8 KiB pages) and 32 KiB static RAM (8 4 KiB pages). + + The MBC6 chip seems to have support for an additional ROM chip, as setting + bit 7 of the page seems to deselect the program ROM. RAM banking lines + could theoretically be used for outer ROM banking, allowing up to 8 MiB + program ROM (1024 8 KiB pages), or 16 MiB program ROM (2048 8 KiB pages) + if dual ROM chips are supported. + + The MX29F008TC doesn't use either of the standard "boot sector" layouts + used by MX29LV008CT and MX29F008CB chips. It seems to use 4 KiB sectors. + The reported silicon ID is 0xC2 0x81. It's not clear how write protection + works. The game doesn't seem to set up conventional sector protection + (command 0x60). It's possible the Flash chips had all sectors protected + before being installed in cartridges and the Flash write enable output just + drives RESET# to Vhv to temporarily disable sector protection. + + 0x0000-3FFF R - Fixed ROM bank, always first 16 KiB of ROM. + 0x4000-5FFF RW - Lower selectable ROM/Flash bank. + 0x6000-7FFF RW - Upper selectable ROM/Flash bank. + + 0xA000-AFFF RW - Lower selectable static RAM bank if enabled. + 0xB000-BFFF RW - Upper selectable static RAM bank if enabled. + + 0x0000-03FF W - Static RAM enable - write 0x0A on low nybble to enable, + any other value to disable. + 0x0400-07FF W - Select static RAM page mapped at 0xA000. + 0x0800-0BFF W - Select static RAM page mapped at 0xB000. + 0x0C00-0FFF W - Least significant bit enables Flash access. + 0x1000-???? W - Least significant bit enables Flash write. Seems to be + required to enable or disable Flash access, too. Doesn't + prevent writing commands to Flash, only erasing or writing + data (can read the silicon ID without setting this). + 0x2000-27FF W - Select ROM/Flash page mapped at 0x4000. + 0x2800-2FFF W - Select ROM (0x00) or Flash (0x08) at 0x4000. + 0x3000-37FF W - Select ROM/Flash page mapped at 0x6000. + 0x3800-3FFF W - Select ROM (0x00) or Flash (0x08) at 0x6000. + + TODO: + * Hook up Flash write enable. + * What's the correct reset state? + * What happens if bit 7 of the page number is set when Flash is selected? + * What do the unknown bits in the Flash enable register do? + + ***************************************************************************/ + +#include "emu.h" +#include "mbc6.h" + +#include "bus/generic/slot.h" +#include "machine/intelfsh.h" + +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class mbc6_device : public device_t, public device_gb_cart_interface +{ +public: + static constexpr feature_type imperfect_features() { return feature::ROM; } + + mbc6_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual image_init_result load(std::string &message) override ATTR_COLD; + virtual void unload() override ATTR_COLD; + +protected: + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + +private: + static inline constexpr unsigned PAGE_ROM_SIZE = 0x2000; + static inline constexpr unsigned PAGE_RAM_SIZE = 0x1000; + + template u8 read_flash(offs_t offset); + template void write_flash(offs_t offset, u8 data); + void bank_switch_rom(offs_t offset, u8 data); + void select_flash(offs_t offset, u8 data); + template void bank_switch_ram(u8 data); + void enable_flash(u8 data); + void enable_flash_write(u8 data); + void enable_ram(u8 data); + + bool check_rom(std::string &message) ATTR_COLD; + bool check_ram(std::string &message) ATTR_COLD; + void install_rom() ATTR_COLD; + void install_ram() ATTR_COLD; + + required_device m_flash; + memory_view m_view_rom[2]; + memory_view m_view_ram; + memory_bank_array_creator<2> m_bank_rom; + memory_bank_array_creator<2> m_bank_ram; + u8 m_bank_mask_rom; + u8 m_bank_mask_ram; + + u8 m_bank_sel_rom[2]; + u8 m_bank_sel_ram[2]; + u8 m_flash_select[2]; + u8 m_flash_enable; + u8 m_flash_writable; +}; + + +mbc6_device::mbc6_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + device_t(mconfig, GB_ROM_MBC6, tag, owner, clock), + device_gb_cart_interface(mconfig, *this), + m_flash(*this, "flash"), + m_view_rom{ { *this, "romlow" }, { *this, "romhigh" } }, + m_view_ram(*this, "ram"), + m_bank_rom(*this, { "romlow", "romhigh" }), + m_bank_ram(*this, { "ramlow", "ramhigh" }), + m_bank_mask_rom(0U), + m_bank_mask_ram(0U), + m_bank_sel_rom{ 0U, 0U }, + m_bank_sel_ram{ 0U, 0U }, + m_flash_select{ 0U, 0U }, + m_flash_enable(0U), + m_flash_writable(0U) +{ +} + + +image_init_result mbc6_device::load(std::string &message) +{ + // first check that ROM/RAM regions are supportable + if (!check_rom(message) || !check_ram(message)) + return image_init_result::FAIL; + + // install views for ROM/flash and RAM + cart_space()->install_view(0x4000, 0x5fff, m_view_rom[0]); + cart_space()->install_view(0x6000, 0x7fff, m_view_rom[1]); + cart_space()->install_view(0xa000, 0xbfff, m_view_ram); + + // set up ROM and RAM as appropriate + install_rom(); + install_ram(); + + // install memory controller handlers + cart_space()->install_write_handler( + 0x0000, 0x03ff, + write8smo_delegate(*this, FUNC(mbc6_device::enable_ram))); + cart_space()->install_write_handler( + 0x0400, 0x07ff, + write8smo_delegate(*this, FUNC(mbc6_device::bank_switch_ram<0>))); + cart_space()->install_write_handler( + 0x0800, 0x0bff, + write8smo_delegate(*this, FUNC(mbc6_device::bank_switch_ram<1>))); + cart_space()->install_write_handler( + 0x0c00, 0x0fff, + write8smo_delegate(*this, FUNC(mbc6_device::enable_flash))); + cart_space()->install_write_handler( + 0x1000, 0x1000, // TODO: what range does this actually respond to? + write8smo_delegate(*this, FUNC(mbc6_device::enable_flash_write))); + cart_space()->install_write_handler( + 0x2000, 0x27ff, 0x0000, 0x0000, 0x1000, + write8sm_delegate(*this, FUNC(mbc6_device::bank_switch_rom))); + cart_space()->install_write_handler( + 0x2800, 0x2fff, 0x0000, 0x0000, 0x1000, + write8sm_delegate(*this, FUNC(mbc6_device::select_flash))); + + // install Flash handlers + m_view_rom[0][1].install_readwrite_handler( + 0x4000, 0x5fff, + read8sm_delegate(*this, FUNC(mbc6_device::read_flash<0>)), + write8sm_delegate(*this, FUNC(mbc6_device::write_flash<0>))); + m_view_rom[1][1].install_readwrite_handler( + 0x6000, 0x7fff, + read8sm_delegate(*this, FUNC(mbc6_device::read_flash<1>)), + write8sm_delegate(*this, FUNC(mbc6_device::write_flash<1>))); + + // all good + return image_init_result::PASS; +} + + +void mbc6_device::unload() +{ + memory_region *const nvramregion(cart_nvram_region()); + if (nvramregion && nvramregion->bytes()) + battery_save(nvramregion->base(), nvramregion->bytes()); +} + + +void mbc6_device::device_add_mconfig(machine_config &config) +{ + MACRONIX_29F008TC(config, m_flash); +} + +void mbc6_device::device_start() +{ + save_item(NAME(m_bank_sel_rom)); + save_item(NAME(m_bank_sel_ram)); + save_item(NAME(m_flash_select)); + save_item(NAME(m_flash_enable)); + save_item(NAME(m_flash_writable)); +} + +void mbc6_device::device_reset() +{ + m_bank_sel_rom[0] = 0U; + m_bank_sel_rom[1] = 0U; + m_bank_sel_ram[0] = 0U; + m_bank_sel_ram[1] = 0U; + m_flash_select[0] = 0U; + m_flash_select[1] = 0U; + m_flash_enable = 0U; + m_flash_writable = 0U; + + m_view_rom[0].select(0); + m_view_rom[1].select(0); + m_view_ram.disable(); + + if (m_bank_mask_rom) + { + m_bank_rom[0]->set_entry(0); + m_bank_rom[1]->set_entry(0); + } + + if (m_bank_mask_ram) + { + m_bank_ram[0]->set_entry(0); + m_bank_ram[1]->set_entry(0); + } +} + + +template +u8 mbc6_device::read_flash(offs_t offset) +{ + offset |= offs_t(m_bank_sel_rom[Bank] & 0x7f) << 13; + return m_flash->read(offset); +} + + +template +void mbc6_device::write_flash(offs_t offset, u8 data) +{ + offset |= offs_t(m_bank_sel_rom[Bank] & 0x7f) << 13; + LOG( + "%s: Write Flash %s 0x%05X = 0x%02X\n", + machine().describe_context(), + Bank ? "high" : "low", + offset, + data); + m_flash->write(offset, data); +} + + +void mbc6_device::bank_switch_rom(offs_t offset, u8 data) +{ + auto const bank(BIT(offset, 12)); + m_bank_sel_rom[bank] = data; + + if (!m_flash_select[bank]) + { + if (BIT(data, 7)) + { + LOG( + "%s: ROM bank %s unmapped\n", + machine().describe_context(), + bank ? "high" : "low"); + m_view_rom[bank].disable(); // is there a chip select for a second program ROM? + } + else + { + m_view_rom[bank].select(0); + } + } + + if (m_bank_mask_rom) + { + u8 const entry(data & m_bank_mask_rom); + LOG( + "%s: Set ROM bank %s = 0x%05X\n", + machine().describe_context(), + bank ? "high" : "low", + entry * PAGE_ROM_SIZE); + m_bank_rom[bank]->set_entry(entry); + } +} + + +void mbc6_device::select_flash(offs_t offset, u8 data) +{ + auto const bank(BIT(offset, 12)); + m_flash_select[bank] = BIT(data, 3); + if (m_flash_select[bank]) + { + LOG( + "%s: Flash bank %s selected (%s)\n", + machine().describe_context(), + bank ? "high" : "low", + m_flash_enable ? "enabled" : "disabled"); + if (m_flash_enable) + m_view_rom[bank].select(1); + else + m_view_rom[bank].disable(); + } + else if (BIT(m_bank_sel_rom[bank], 7)) + { + LOG( + "%s: ROM bank %s unmapped\n", + machine().describe_context(), + bank ? "high" : "low"); + m_view_rom[bank].disable(); // is there a chip select for a second program ROM? + } + else + { + LOG( + "%s: ROM bank %s selected\n", + machine().describe_context(), + bank ? "high" : "low"); + m_view_rom[bank].select(0); + } + + // game writes 0xc6 when selecting ROM during boot - what do the other bits do? + if (data & ~0x08) + { + LOG( + "%s: ROM/Flash select %s with unknown bits set 0x%02X\n", + machine().describe_context(), + bank ? "high" : "low", + data); + } +} + + +template +void mbc6_device::bank_switch_ram(u8 data) +{ + if (m_bank_mask_ram) + { + data &= m_bank_mask_ram; + LOG( + "%s: Set RAM bank %s = 0x%04X\n", + machine().describe_context(), + Bank ? "high" : "low", + data * PAGE_RAM_SIZE); + m_bank_ram[Bank]->set_entry(data); + } +} + + +void mbc6_device::enable_flash(u8 data) +{ + // game seems to always set Flash write enable before changing this + if (m_flash_writable) + { + m_flash_enable = BIT(data, 0); + if (m_flash_enable) + { + LOG("%s: Flash enabled\n", machine().describe_context()); + if (m_flash_select[0]) + m_view_rom[0].select(1); + if (m_flash_select[1]) + m_view_rom[1].select(1); + } + else + { + LOG("%s: Flash disabled\n", machine().describe_context()); + if (m_flash_select[0]) + m_view_rom[0].disable(); + if (m_flash_select[1]) + m_view_rom[1].disable(); + } + + if (data & ~0x01) + { + logerror( + "%s: Flash enable with unknown bits set 0x%02X\n", + machine().describe_context(), + data); + } + } + else + { + logerror( + "%s: Flash enable 0x%02X when Flash write disabled\n", + machine().describe_context(), + data); + } +} + + +void mbc6_device::enable_flash_write(u8 data) +{ + // FIXME: this should also control whether Flash can be erased/written + m_flash_writable = BIT(data, 0); + LOG( + "%s: Flash write %s\n", + machine().describe_context(), + m_flash_writable ? "enabled" : "disabled"); + + if (data & ~0x01) + { + logerror( + "%s: Flash write enable with unknown bits set 0x%02X\n", + machine().describe_context(), + data); + } +} + + +void mbc6_device::enable_ram(u8 data) +{ + if (0x0a == (data & 0x0f)) + { + LOG("%s: Cartridge RAM enabled\n", machine().describe_context()); + m_view_ram.select(0); + } + else + { + LOG("%s: Cartridge RAM disabled\n", machine().describe_context()); + m_view_ram.disable(); + } +} + + +bool mbc6_device::check_rom(std::string &message) +{ + memory_region *const romregion(cart_rom_region()); + if (romregion) + { + auto const rombytes(romregion->bytes()); + if (((PAGE_ROM_SIZE < rombytes) && (rombytes & (PAGE_ROM_SIZE - 1))) || ((u32(PAGE_ROM_SIZE) << 7) < rombytes)) + { + message = "Unsupported cartridge ROM size (must be no larger than 8 KiB, or a multiple of 8 KiB and no larger than 2 MiB)"; + return false; + } + } + + return true; +} + + +bool mbc6_device::check_ram(std::string &message) +{ + memory_region *const ramregion(cart_ram_region()); + memory_region *const nvramregion(cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + if (rambytes && nvrambytes && ((rambytes & (PAGE_RAM_SIZE - 1)) || (nvrambytes & (PAGE_RAM_SIZE - 1)) || ((nvrambytes - 1) & nvrambytes))) + { + message = "Unsupported cartridge RAM size (if not all RAM is battery-backed, battery backed RAM size must be a power of two no smaller than 4 KiB, and total RAM size must be a multiple of 4 KiB)"; + return false; + } + + auto const ramtotal(rambytes + nvrambytes); + if (((PAGE_RAM_SIZE < ramtotal) && (ramtotal & (PAGE_RAM_SIZE - 1))) && ((u32(PAGE_RAM_SIZE) << 3) < ramtotal)) + { + message = "Unsupported cartridge RAM size (must be no larger than 4 KiB, or a multiple of 4 KiB no larger than 32 KiB)"; + return false; + } + + return true; +} + + +void mbc6_device::install_rom() +{ + memory_region *const romregion(cart_rom_region()); + auto const rombytes(romregion ? romregion->bytes() : 0); + if (!rombytes) + { + // just avoid fatal errors + m_view_rom[0][0]; + m_view_rom[1][0]; + m_bank_mask_rom = 0U; + } + else if (PAGE_ROM_SIZE >= rombytes) + { + // not big enough to need banking - install directly + device_generic_cart_interface::install_non_power_of_two<0>( + rombytes, + PAGE_ROM_SIZE - 1, + 0, + 0, + 0x0000, + [this, base = &romregion->as_u8()] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + LOG( + "Install ROM 0x%04X-0x%04X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + mirror); + cart_space()->install_rom(begin, end, 0x2000 | mirror, &base[src]); + m_view_rom[0][0].install_rom(0x4000 | begin, 0x4000 | end, mirror, &base[src]); + m_view_rom[1][0].install_rom(0x6000 | begin, 0x6000 | end, mirror, &base[src]); + }); + m_bank_mask_rom = 0U; + } + else + { + // install the fixed ROM, mirrored if it’s too small + if (0x4000 > rombytes) + cart_space()->install_rom(0x0000, 0x1fff, 0x2000, romregion->base()); + else + cart_space()->install_rom(0x0000, 0x3fff, 0x0000, romregion->base()); + + // configure both banks as views of the ROM + m_bank_mask_rom = device_generic_cart_interface::map_non_power_of_two( + unsigned(rombytes / PAGE_ROM_SIZE), + [this, base = &romregion->as_u8()] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%05X-0x%05X in bank entry %u\n", + page * PAGE_ROM_SIZE, + (page * PAGE_ROM_SIZE) + (PAGE_ROM_SIZE - 1), + entry); + m_bank_rom[0]->configure_entry(entry, &base[page * PAGE_ROM_SIZE]); + m_bank_rom[1]->configure_entry(entry, &base[page * PAGE_ROM_SIZE]); + }); + m_view_rom[0][0].install_read_bank(0x4000, 0x5fff, m_bank_rom[0]); + m_view_rom[1][0].install_read_bank(0x6000, 0x7fff, m_bank_rom[1]); + } +} + + +void mbc6_device::install_ram() +{ + memory_region *const ramregion(this->cart_ram_region()); + memory_region *const nvramregion(this->cart_nvram_region()); + auto const rambytes(ramregion ? ramregion->bytes() : 0); + auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0); + auto const ramtotal(rambytes + nvrambytes); + u8 *const rambase(rambytes ? &ramregion->as_u8() : nullptr); + u8 *const nvrambase(nvrambytes ? &nvramregion->as_u8() : nullptr); + + if (!ramtotal) + { + // need to avoid fatal errors + m_bank_mask_ram = 0U; + m_view_ram[0]; + } + else if (PAGE_RAM_SIZE >= ramtotal) + { + // install small amounts of RAM directly - mixed volatile and non-volatile RAM is not supported + device_generic_cart_interface::install_non_power_of_two<0>( + nvrambytes ? nvrambytes : rambytes, + PAGE_RAM_SIZE - 1, + 0, + 0, + 0xa000, + [this, base = nvrambase ? nvrambase : rambase] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + LOG( + "Install RAM 0x%05X-0x%05X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + 0x1000 | mirror); + m_view_ram[0].install_ram(begin, end, 0x1000 | mirror, &base[src]); + }); + m_bank_mask_ram = 0U; + } + else + { + // install banked RAM, assuming lower pages are battery-backed + m_bank_mask_ram = device_generic_cart_interface::map_non_power_of_two( + unsigned(ramtotal / PAGE_RAM_SIZE), + [this, nvrampages = nvrambytes / PAGE_RAM_SIZE, rambase, nvrambase] (unsigned entry, unsigned page) + { + bool const nonvolatile(page < nvrampages); + u8 *const base(nonvolatile ? nvrambase : rambase); + auto const offset((page - (nonvolatile ? 0 : nvrampages)) * PAGE_RAM_SIZE); + LOG( + "Install %s 0x%05X-0x%05X in bank entry %u\n", + nonvolatile ? "NVRAM" : "RAM", + offset, + offset + (PAGE_RAM_SIZE - 1), + entry); + m_bank_ram[0]->configure_entry(entry, &base[offset]); + m_bank_ram[1]->configure_entry(entry, &base[offset]); + }); + m_view_ram[0].install_readwrite_bank(0xa000, 0xafff, m_bank_ram[0]); + m_view_ram[0].install_readwrite_bank(0xb000, 0xbfff, m_bank_ram[1]); + } + + if (nvrambytes) + { + save_pointer(NAME(nvrambase), nvrambytes); + battery_load(nvrambase, nvrambytes, nullptr); + } + if (rambytes) + save_pointer(NAME(rambase), rambytes); +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC6, device_gb_cart_interface, bus::gameboy::mbc6_device, "gb_rom_mbc6", "Game Boy MBC6 Cartridge") diff --git a/src/devices/bus/gameboy/mbc6.h b/src/devices/bus/gameboy/mbc6.h new file mode 100644 index 00000000000..07072d72f80 --- /dev/null +++ b/src/devices/bus/gameboy/mbc6.h @@ -0,0 +1,18 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Memory Bank Controller 6 + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_MBC6_H +#define MAME_BUS_GAMEBOY_MBC6_H + +#pragma once + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_MBC6, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_MBC7_H diff --git a/src/devices/bus/gameboy/mbc7.cpp b/src/devices/bus/gameboy/mbc7.cpp new file mode 100644 index 00000000000..4e88004996e --- /dev/null +++ b/src/devices/bus/gameboy/mbc7.cpp @@ -0,0 +1,379 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Memory Bank Controller 7 + + Only used for two games, the cartridges contain an ADXL202E two-axis + accelerometer and a 93LC56 or 93LC66 serial EEPROM, as well as the MBC7 + chip and program ROM. The MBC7 never seems to have been fully utilised. + It appears to have inputs for an additional axis sensor, and an unused + 8-bit port of some kind. Supports up to 2 MiB ROM (128 16 KiB pages). + + 0x0000-3FFF R - Fixed ROM bank, always first page of ROM. + 0x4000-7FFF R - Selectable ROM bank. + + 0x0000-1FFF W - I/O enable - write 0x0A on low nybble to enable, any + other value to disable. + 0x2000-3FFF W - Select ROM page mapped at 0x4000. Only low seven bits are + significant. + 0x4000-5FFF W - I/O select - presumably switches between sensors and + EEPROM, and some unpopulated peripheral. Write 0x40 to + select sensors and EEPROM. Values seen include 0x00, + 0x3F, 0x40 and 0x54. + + When I/O is enabled: + 0xA.0. W - Write 0x55 to clear accelerometer values (subsequent + reads will return 0x8000). + 0xA.1. W - Write 0xAA to acquire and latch accelerometer values + (must be in cleared state, or no effect). + 0xA.2. R - Accelerometer X value least significant byte. + 0xA.3. R - Accelerometer X value most significant byte. + 0xA.4. R - Accelerometer Y value least significant byte. + 0xA.5. R - Accelerometer Y value most significant byte. + 0xA.6. R - Always 0x00 (presumably for unpopulated Z axis sensor). + 0xA.7. R - Always 0xFF (presumably for unpopulated Z axis sensor). + 0xA.8. RW - EEPROM: bit 7 = CS, bit 6 = CLK, bit 1 = DI, bit 0 = DO. + + Accelerometers read 0x81D0 in neutral orientation, and 1 g produces a + displacement of about 0x70 in either direction. The ADXL202E is rated for + full-scale output at 2 g in either direction. Inputs are set up to + simulate up to about 1.5 g. + + TODO: + * Can ROM page 0 be selected like MBC5, or does it map to 1 like MBC1? + * EEPROM initialisation from software list item 'nvram' region. + * Reset state for EEPROM outputs. + * How is direction controlled for I/O lines? + + ***************************************************************************/ + +#include "emu.h" +#include "mbc7.h" + +#include "cartbase.h" + +#include "machine/eepromser.h" + +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +//************************************************************************** +// Class declarations +//************************************************************************** + +class mbc7_device_base : public mbc_device_base +{ +public: + virtual image_init_result load(std::string &message) override ATTR_COLD; + +protected: + mbc7_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + virtual ioport_constructor device_input_ports() const override ATTR_COLD; + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + + required_device m_eeprom; + +private: + void bank_rom_switch(u8 data); + template void enable_io(u8 data); + void clear_accel(u8 data); + void acquire_accel(u8 data); + template u8 read_accel(offs_t offset); + u8 read_unknown(offs_t offset); + u8 read_eeprom(); + void write_eeprom(u8 data); + + required_ioport_array<2> m_accel; + memory_view m_view_io; + + u8 m_io_enable[2]; + + u16 m_accel_val[2]; + u8 m_accel_latched; + + u8 m_eeprom_out; +}; + + +class mbc7_2k_device : public mbc7_device_base +{ +public: + mbc7_2k_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc7_device_base(mconfig, GB_ROM_MBC7_2K, tag, owner, clock) + { + } + +protected: + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD + { + EEPROM_93C56_16BIT(config, m_eeprom, 0); + } +}; + + +class mbc7_4k_device : public mbc7_device_base +{ +public: + mbc7_4k_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + mbc7_device_base(mconfig, GB_ROM_MBC7_4K, tag, owner, clock) + { + } + +protected: + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD + { + EEPROM_93C66_16BIT(config, m_eeprom, 0); + } +}; + + + +//************************************************************************** +// Input port definitions +//************************************************************************** + +INPUT_PORTS_START(mbc7) + PORT_START("ACCELX") + PORT_BIT(0x01ff, 0x00c0, IPT_AD_STICK_X) PORT_REVERSE PORT_MINMAX(0x0000, 0x0180) PORT_SENSITIVITY(50) PORT_KEYDELTA(30) + + PORT_START("ACCELY") + PORT_BIT(0x01ff, 0x00c0, IPT_AD_STICK_Y) PORT_REVERSE PORT_MINMAX(0x0000, 0x0180) PORT_SENSITIVITY(50) PORT_KEYDELTA(30) +INPUT_PORTS_END + + + +//************************************************************************** +// mbc7_device_base +//************************************************************************** + +image_init_result mbc7_device_base::load(std::string &message) +{ + // first check ROM + set_bank_bits_rom(7); + if (!check_rom(message)) + return image_init_result::FAIL; + + // install ROM and handlers + install_rom(); + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, NAME((&mbc7_device_base::enable_io<0, 0x0a, 0x0f>)))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(mbc7_device_base::bank_rom_switch))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, NAME((&mbc7_device_base::enable_io<1, 0x40, 0xff>)))); + cart_space()->install_view( + 0xa000, 0xafff, + m_view_io); + m_view_io[0].install_write_handler( + 0xa000, 0xa00f, 0x0000, 0x0f00, 0x0000, + write8smo_delegate(*this, FUNC(mbc7_device_base::clear_accel))); + m_view_io[0].install_write_handler( + 0xa010, 0xa01f, 0x0000, 0x0f00, 0x0000, + write8smo_delegate(*this, FUNC(mbc7_device_base::acquire_accel))); + m_view_io[0].install_read_handler( + 0xa020, 0xa03f, 0x0000, 0x0f00, 0x0000, + read8sm_delegate(*this, FUNC(mbc7_device_base::read_accel<0>))); + m_view_io[0].install_read_handler( + 0xa040, 0xa05f, 0x0000, 0x0f00, 0x0000, + read8sm_delegate(*this, FUNC(mbc7_device_base::read_accel<1>))); + m_view_io[0].install_read_handler( + 0xa060, 0xa07f, 0x0000, 0x0f00, 0x0000, + read8sm_delegate(*this, FUNC(mbc7_device_base::read_unknown))); + m_view_io[0].install_readwrite_handler( + 0xa080, 0xa08f, 0x0000, 0x0f00, 0x0000, + read8smo_delegate(*this, FUNC(mbc7_device_base::read_eeprom)), + write8smo_delegate(*this, FUNC(mbc7_device_base::write_eeprom))); + + // all good + return image_init_result::PASS; +} + + +mbc7_device_base::mbc7_device_base( + machine_config const &mconfig, + device_type type, + char const *tag, + device_t *owner, + u32 clock) : + mbc_device_base(mconfig, type, tag, owner, clock), + m_eeprom(*this, "eeprom"), + m_accel(*this, "ACCEL%c", 'X'), + m_view_io(*this, "io"), + m_io_enable{ 0U, 0U }, + m_accel_val{ 0U, 0U }, + m_accel_latched(0U), + m_eeprom_out(0U) +{ +} + + +ioport_constructor mbc7_device_base::device_input_ports() const +{ + return INPUT_PORTS_NAME(mbc7); +} + + +void mbc7_device_base::device_start() +{ + mbc_device_base::device_start(); + + save_item(NAME(m_io_enable)); + save_item(NAME(m_accel_val)); + save_item(NAME(m_accel_latched)); + save_item(NAME(m_eeprom_out)); +} + + +void mbc7_device_base::device_reset() +{ + mbc_device_base::device_reset(); + + std::fill(std::begin(m_io_enable), std::end(m_io_enable), 0U); + std::fill(std::begin(m_io_enable), std::end(m_io_enable), 0x8000U); + m_accel_latched = 0U; + + set_bank_rom(0); + m_view_io.disable(); +} + + +void mbc7_device_base::bank_rom_switch(u8 data) +{ + set_bank_rom(data & 0x7f); +} + + +template +void mbc7_device_base::enable_io(u8 data) +{ + m_io_enable[N] = (Value == (data & Mask)) ? 1U : 0U; + bool const enable(m_io_enable[0] && m_io_enable[1]); + LOG( + "%s: I/O enable %u = 0x%02X, I/O %s\n", + machine().describe_context(), + N, + data, + enable ? "enabled" : "disabled"); + if (enable) + m_view_io.select(0); + else + m_view_io.disable(); +} + + +void mbc7_device_base::clear_accel(u8 data) +{ + if (0x55 != data) + { + logerror( + "%s: Ignoring unknown command 0x%02X written to accelerometer clear register\n", + machine().describe_context(), + data); + } + else + { + m_accel_val[0] = 0x8000U; + m_accel_val[1] = 0x8000U; + m_accel_latched = 0U; + LOG("%s: Accelerometer data cleared\n", machine().describe_context()); + } +} + + +void mbc7_device_base::acquire_accel(u8 data) +{ + if (0xaa != data) + { + logerror( + "%s: Ignoring unknown command 0x%02X written to accelerometer acquire register\n", + machine().describe_context(), + data); + } + else if (m_accel_latched) + { + logerror( + "%s: Ignoring accelerometer acquire command while data latched\n", + machine().describe_context()); + } + else + { + m_accel_val[0] = 0x81d0 - 0x00c0 + m_accel[0]->read(); + m_accel_val[1] = 0x81d0 - 0x00c0 + m_accel[1]->read(); + m_accel_latched = 1U; + LOG( + machine().describe_context(), + "%s: Accelerometer data acquired X = 0x%04X Y = 0x%04X\n", + m_accel_val[0], + m_accel_val[1]); + } +} + + +template +u8 mbc7_device_base::read_accel(offs_t offset) +{ + u8 const result(m_accel_val[N] >> (BIT(offset, 4) ? 8 : 0)); + LOG( + "Read accelerometer %c %cSB = 0x%02X\n", + machine().describe_context(), + 'X' + N, + BIT(offset, 4) ? 'M' : 'L', + result); + return result; +} + + +u8 mbc7_device_base::read_unknown(offs_t offset) +{ + return BIT(offset, 4) ? 0xff : 0x00; +} + + +u8 mbc7_device_base::read_eeprom() +{ + u8 const dout(m_eeprom->do_read() ? 0x01U : 0x00U); + LOG("%s: Read EEPROM DO = %u\n", machine().describe_context(), dout); + return (m_eeprom_out & 0xfe) | dout; +} + + +void mbc7_device_base::write_eeprom(u8 data) +{ + LOG( + "%s: Write EEPROM CS = %u CLK = %u DI = %u\n", + machine().describe_context(), + BIT(data, 7), + BIT(data, 6), + BIT(data, 1)); + m_eeprom_out = data; + m_eeprom->cs_write(BIT(data, 7)); + m_eeprom->clk_write(BIT(data, 6)); + m_eeprom->di_write(BIT(data, 1)); +} + +} // anonymous namespace + +} // namespace bus::gameboy + + + +//************************************************************************** +// Device type definitions +//************************************************************************** + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC7_2K, device_gb_cart_interface, bus::gameboy::mbc7_2k_device, "gb_rom_mbc7_2k", "Game Boy MBC7 Cartridge with 93LC56") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC7_4K, device_gb_cart_interface, bus::gameboy::mbc7_4k_device, "gb_rom_mbc7_4k", "Game Boy MBC7 Cartridge with 93LC66") diff --git a/src/devices/bus/gameboy/mbc7.h b/src/devices/bus/gameboy/mbc7.h new file mode 100644 index 00000000000..bd5952a976b --- /dev/null +++ b/src/devices/bus/gameboy/mbc7.h @@ -0,0 +1,19 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy Memory Bank Controller 7 + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_MBC7_H +#define MAME_BUS_GAMEBOY_MBC7_H + +#pragma once + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_MBC7_2K, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_MBC7_4K, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_MBC7_H diff --git a/src/devices/bus/gameboy/mdslot.cpp b/src/devices/bus/gameboy/mdslot.cpp new file mode 100644 index 00000000000..8a59c50b774 --- /dev/null +++ b/src/devices/bus/gameboy/mdslot.cpp @@ -0,0 +1,144 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Mega Duck cartridge slot + + ***************************************************************************/ + +#include "emu.h" +#include "mdslot.h" + +#include "carts.h" + +#include "emuopts.h" +#include "romload.h" +#include "softlist.h" + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +//************************************************************************** +// megaduck_cart_slot_device +//************************************************************************** + +megaduck_cart_slot_device::megaduck_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + gb_cart_slot_device_base(mconfig, MEGADUCK_CART_SLOT, tag, owner, clock) +{ +} + + +std::string megaduck_cart_slot_device::get_default_card_software(get_default_card_software_hook &hook) const +{ + using namespace bus::gameboy; + + if (hook.image_file()) + { + // loading from file - guess banking scheme using heuristics + u64 length; + if (!hook.image_file()->length(length)) + { + if (0x8000 < length) + { + osd_printf_verbose("[%s] 0x%X-byte cartridge, requires ROM banking\n", tag(), length); + return slotoptions::MEGADUCK_BANKED; + } + else + { + osd_printf_verbose("[%s] 0x%X-byte cartridge, assuming flat ROM\n", tag(), length); + return slotoptions::MEGADUCK_STD; + } + } + else + { + osd_printf_warning("[%s] Error getting cartridge ROM length - assuming banked ROM\n", tag()); + return slotoptions::MEGADUCK_BANKED; + } + } + else + { + // loading from software list - try to find matching software part + std::string const image_name(mconfig().options().image_option(instance_name()).value()); + software_part const *const part(!image_name.empty() ? find_software_item(image_name, true) : nullptr); + if (part) + { + osd_printf_verbose("[%s] Found software part for image name '%s'\n", tag(), image_name); + + // if there's an explicit "slot" feature, use it + char const *const slot(part->feature("slot")); + if (slot) + { + osd_printf_verbose("[%s] Using specified cartridge device '%s'\n", tag(), slot); + return slot; + } + + // presence or absence of a fixed bank implies banking is in use + char const *const fixedbank(part->feature("fixedbank")); + if (fixedbank) + { + osd_printf_verbose("[%s] fixedbank='%s' specified, assuming banked ROM\n", tag(), fixedbank); + return slotoptions::MEGADUCK_BANKED; + } + + // fall back to guessing based on length + for (rom_entry const &entry : part->romdata()) + { + if (ROMENTRY_ISREGION(entry) && (entry.name() == "rom")) + { + auto const length(ROMREGION_GETLENGTH(entry)); + if (0x8000 < length) + { + osd_printf_verbose("[%s] Found 0x%X-byte 'rom' region, requires ROM banking\n", tag(), length); + return slotoptions::MEGADUCK_BANKED; + } + else + { + osd_printf_verbose("[%s] Found 0x%X-byte 'rom' region, assuming flat ROM\n", tag(), length); + return slotoptions::MEGADUCK_STD; + } + } + } + + // a flat ROM cartridge with nothing in it is harmless + osd_printf_verbose("[%s] No ROM region found\n", tag()); + return slotoptions::MEGADUCK_STD; + } + else + { + osd_printf_verbose("[%s] No software part found for image name '%s'\n", tag(), image_name); + } + } + + // leave the slot empty + return std::string(); +} + + +image_init_result megaduck_cart_slot_device::load_image_file(util::random_read &file) +{ + auto const len = length(); + size_t actual; + + if (len) + { + LOG("Allocating %u byte cartridge ROM region\n", len); + memory_region *const romregion = machine().memory().region_alloc(subtag("rom"), len, 1, ENDIANNESS_LITTLE); + if (file.read_at(0, romregion->base(), len, actual) || (len != actual)) + { + seterror(image_error::UNSPECIFIED, "Error reading cartridge file"); + return image_init_result::FAIL; + } + } + + return image_init_result::PASS; +} + + + +//************************************************************************** +// Device type definitions +//************************************************************************** + +DEFINE_DEVICE_TYPE(MEGADUCK_CART_SLOT, megaduck_cart_slot_device, "megaduck_cart_slot", "Mega Duck Cartridge Slot") diff --git a/src/devices/bus/gameboy/mdslot.h b/src/devices/bus/gameboy/mdslot.h new file mode 100644 index 00000000000..03652f1dd5a --- /dev/null +++ b/src/devices/bus/gameboy/mdslot.h @@ -0,0 +1,70 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Mega Duck cartridge slot + + Cartridge edge connector (0.1" pitch double-sided): + VCC 1 2 + /WR 3 4 /RD + 5 6 A0 + A1 7 8 A2 + A3 9 10 A4 + A5 11 12 A6 + A7 13 14 A8 + A9 15 16 A10 + A11 17 18 A12 + A13 19 20 A14 + A15 21 22 D0 + D1 23 24 D2 + D3 25 26 D4 + D5 27 28 D6 + D7 29 30 /RST + 31 32 + 33 34 + GND 35 36 GND + + Pinout based on cartridge connections. Unlabelled contacts may be + connected in console. + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_MDSLOT_H +#define MAME_BUS_GAMEBOY_MDSLOT_H + +#pragma once + +#include "slot.h" + +#include + + +class megaduck_cart_slot_device : public gb_cart_slot_device_base +{ +public: + template + megaduck_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, T &&opts, char const *dflt) : + megaduck_cart_slot_device(mconfig, tag, owner, 0U) + { + option_reset(); + opts(*this); + set_default_option(dflt); + set_fixed(false); + } + megaduck_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + // device_image_interface implementation + virtual char const *image_interface() const noexcept override { return "megaduck_cart"; } + virtual char const *file_extensions() const noexcept override { return "bin"; } + + // device_slot_interface implementation + virtual std::string get_default_card_software(get_default_card_software_hook &hook) const override ATTR_COLD; + +protected: + // gb_cart_slot_device_base implementation + virtual image_init_result load_image_file(util::random_read &file) override ATTR_COLD; +}; + + +DECLARE_DEVICE_TYPE(MEGADUCK_CART_SLOT, megaduck_cart_slot_device) + +#endif // MAME_BUS_GAMEBOY_MDSLOT_H diff --git a/src/devices/bus/gameboy/mmm01.cpp b/src/devices/bus/gameboy/mmm01.cpp new file mode 100644 index 00000000000..a70cbde2664 --- /dev/null +++ b/src/devices/bus/gameboy/mmm01.cpp @@ -0,0 +1,348 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy MMM01 memory controller + + Designed to support multi-game cartridges. Included games can vary in + program ROM non-volatile RAM size requirements. Starts in configuration + mode, where coarse ROM and RAM page selection can be configured. After + leaving configuration mode, the only way to re-enter it is to reset the + MMM01 chip. + + After leaving configuration mode, the behaviour will be similar to one of + the two standard wiring arrangements for cartridges using the MBC1 chip. + + Supports up to 8 MiB ROM (512 16 KiB pages) and 128 KiB static RAM (16 + 8 KiB pages) total. Each included game can use up to 2 MiB ROM (128 16 KiB + pages) and 8 KiB static RAM (one page), or up to 512 KiB ROM (32 16 KiB + pages) and 32 KiB static RAM (4 8 KiB pages). In configuration mode, up to + 32 KiB ROM and 128 KiB static RAM (16 8 KiB pages) can be used. + + The MMM01 controller only responds to A15-A13 and D6-D0, i.e. addresses are + effectively masked with 0xE000 and data is effectively masked with 0x7F. + In configuration mode, most of the control register bits that are unused by + the MBC1 chip are repurposed. + + 0x0000-3FFF R - Low ROM bank. + 0x4000-7FFF R - High ROM bank. + 0xA000-BFFF RW - Static RAM if enabled. + + 0x0000-1FFF W - -X------ * Exit configuration mode. + --XX---- * Static RAM bank fine protect. + ----XXXX Static RAM enable. + 0x2000-3FFF W - -XX----- * ROM bank mid (RA20-RA19) or static RAM bank + fine (AA14-AA13), depending on mode. + ---XXXXX ROM bank fine (RA18-RA14). + 0x4000-5FFF W - -X------ * Low bank coarse enable protect. + --XX---- * ROM bank coarse (RA22-RA21). + ----XX-- * Static RAM bank coarse (AA16-AA15). + ------XX Static RAM bank fine (AA14-AA13) or ROM bank + mid (RA20-RA19), depending on mode. + 0x6000-7FFF W - -X------ * ROM bank mid/RAM bank coarse select. + --XXXX-- * ROM bank fine protect. + -------X Low bank coarse enable. + + * Fields only writable in configuration mode. + + ***************************************************************************/ + +#include "emu.h" +#include "mmm01.h" + +#include "cartbase.ipp" + +#include + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class mmm01_device : public mbc_ram_device_base +{ +public: + mmm01_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual image_init_result load(std::string &message) override ATTR_COLD; + +protected: + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + +private: + void enable_ram(u8 data); + void bank_switch_fine(u8 data); + void bank_switch_coarse(u8 data); + void enable_bank_low_mid(u8 data); + + void update_bank_rom_low() + { + u16 entry; + if (!m_config) + { + entry = (u16(m_bank_sel_rom_coarse) << 7) | (m_bank_sel_rom_fine & m_bank_prot_sel[1]); + if (!m_mode_large_prg) + entry |= m_bank_sel_mid[1] << 5; + else if (m_bank_low_mid) + entry |= m_bank_sel_mid[0] << 5; + } + else + { + entry = 0x01fe; + } + set_bank_rom_low(entry); + } + + void update_bank_rom_high() + { + u16 entry; + if (!m_config) + { + entry = (u16(m_bank_sel_rom_coarse) << 7) | m_bank_sel_rom_fine; + if (!m_mode_large_prg) + entry |= m_bank_sel_mid[1] << 5; + else + entry |= m_bank_sel_mid[0] << 5; + } + else + { + entry = 0x01fe | m_bank_sel_rom_fine; + } + if (!(m_bank_sel_rom_fine & ~m_bank_prot_sel[1])) + entry |= 0x0001; + set_bank_rom_high(entry); + } + + void update_bank_ram() + { + u8 entry(m_bank_sel_ram_coarse << 2); + if (m_mode_large_prg) + entry |= m_bank_sel_mid[1]; + else if (m_bank_low_mid) + entry |= m_bank_sel_mid[0]; + set_bank_ram(entry); + } + + memory_view m_view_ram; + + u8 m_config; + u8 m_mode_large_prg; + u8 m_bank_sel_mid[2]; + u8 m_bank_sel_ram_coarse; + u8 m_bank_sel_rom_coarse; + u8 m_bank_sel_rom_fine; + u8 m_bank_low_mid; + u8 m_bank_prot_sel[2]; + u8 m_bank_prot_low_mid; +}; + + +mmm01_device::mmm01_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + mbc_ram_device_base(mconfig, GB_ROM_MMM01, tag, owner, clock), + m_view_ram(*this, "ram"), + m_config(0U), + m_mode_large_prg(0U), + m_bank_sel_mid{ 0U, 0U }, + m_bank_sel_ram_coarse(0U), + m_bank_sel_rom_coarse(0U), + m_bank_sel_rom_fine(0U), + m_bank_low_mid(0U), + m_bank_prot_sel{ 0U, 0U }, + m_bank_prot_low_mid(0U) +{ +} + + +image_init_result mmm01_device::load(std::string &message) +{ + // set up ROM and RAM + set_bank_bits_rom(9); + if (!check_rom(message) || !check_ram(message)) + return image_init_result::FAIL; + cart_space()->install_view(0xa000, 0xbfff, m_view_ram); + install_rom(); + install_ram(m_view_ram[0]); + + // install memory controller handlers + cart_space()->install_write_handler( + 0x0000, 0x1fff, + write8smo_delegate(*this, FUNC(mmm01_device::enable_ram))); + cart_space()->install_write_handler( + 0x2000, 0x3fff, + write8smo_delegate(*this, FUNC(mmm01_device::bank_switch_fine))); + cart_space()->install_write_handler( + 0x4000, 0x5fff, + write8smo_delegate(*this, FUNC(mmm01_device::bank_switch_coarse))); + cart_space()->install_write_handler( + 0x6000, 0x6fff, + write8smo_delegate(*this, FUNC(mmm01_device::enable_bank_low_mid))); + + // all good + return image_init_result::PASS; +} + + +void mmm01_device::device_start() +{ + mbc_ram_device_base::device_start(); + + save_item(NAME(m_config)); + save_item(NAME(m_mode_large_prg)); + save_item(NAME(m_bank_sel_mid)); + save_item(NAME(m_bank_sel_ram_coarse)); + save_item(NAME(m_bank_sel_rom_coarse)); + save_item(NAME(m_bank_sel_rom_fine)); + save_item(NAME(m_bank_low_mid)); + save_item(NAME(m_bank_prot_sel)); + save_item(NAME(m_bank_prot_low_mid)); +} + + +void mmm01_device::device_reset() +{ + mbc_ram_device_base::device_reset(); + + m_view_ram.disable(); + m_config = 1U; + m_mode_large_prg = 0U; + m_bank_sel_mid[0] = 0U; + m_bank_sel_mid[1] = 0U; + m_bank_sel_ram_coarse = 0U; + m_bank_sel_rom_coarse = 0U; + m_bank_sel_rom_fine = 0U; + m_bank_low_mid = 0U; + m_bank_prot_sel[0] = 0U; + m_bank_prot_sel[1] = 0U; + m_bank_prot_low_mid = 0U; + + set_bank_rom_low(0x01fe); + set_bank_rom_high(0x01ff); + set_bank_ram(0U); +} + + +void mmm01_device::enable_ram(u8 data) +{ + if (0x0a == (data & 0x0f)) + { + LOG("%s: Cartridge RAM enabled\n", machine().describe_context()); + m_view_ram.select(0); + } + else + { + LOG("%s: Cartridge RAM disabled\n", machine().describe_context()); + m_view_ram.disable(); + } + + if (m_config) + { + m_bank_prot_sel[0] = BIT(data, 4, 2); + LOG( + "%s: Mid bank protect = 0x%X\n", + machine().describe_context(), + m_bank_prot_sel[0]); + if (BIT(data, 6)) + { + LOG("%s: Configuration mode disabled\n", machine().describe_context()); + m_config = 0U; + update_bank_rom_low(); + update_bank_rom_high(); + } + } +} + + +void mmm01_device::bank_switch_fine(u8 data) +{ + m_bank_sel_rom_fine = (m_bank_sel_rom_fine & m_bank_prot_sel[1]) | (BIT(data, 0, 5) & ~m_bank_prot_sel[1]); + LOG( + "%s: Fine ROM bank = 0x%02X\n", + machine().describe_context(), + m_bank_sel_rom_fine); + + if (m_config) + { + m_bank_sel_mid[1] = BIT(data, 5, 2); + LOG( + "%s: Configuration mid bank = 0x%X\n", + machine().describe_context(), + m_bank_sel_mid[1]); + } + + update_bank_rom_high(); + if (m_config && m_mode_large_prg) + update_bank_ram(); +} + + +void mmm01_device::bank_switch_coarse(u8 data) +{ + m_bank_sel_mid[0] = (m_bank_sel_mid[0] & m_bank_prot_sel[0]) | (BIT(data, 0, 2) & ~m_bank_prot_sel[0]); + LOG( + "%s: User mid bank = 0x%X\n", + machine().describe_context(), + m_bank_sel_mid[0]); + + if (m_config) + { + m_bank_sel_ram_coarse = BIT(data, 2, 2); + m_bank_sel_rom_coarse = BIT(data, 4, 2); + m_bank_prot_low_mid = BIT(data, 6); + LOG( + "%s: Coarse ROM bank = 0x%X, coarse RAM bank = 0x%X, low bank mid enable %s\n", + machine().describe_context(), + m_bank_sel_rom_coarse, + m_bank_sel_ram_coarse, + m_bank_prot_low_mid ? "locked" : "unlocked"); + } + + if (m_config || (!m_mode_large_prg && m_bank_low_mid)) + update_bank_ram(); +} + + +void mmm01_device::enable_bank_low_mid(u8 data) +{ + if (!m_bank_prot_low_mid) + { + m_bank_low_mid = BIT(data, 0); + LOG( + "%s: Low bank mid %s\n", + machine().describe_context(), + m_bank_low_mid ? "enabled" : "disabled"); + } + + if (m_config) + { + m_bank_prot_sel[1] = BIT(data, 2, 4) << 1; + m_mode_large_prg = BIT(data, 6); + LOG( + "%s: Fine ROM bank protect = 0x%02X, %s mode\n", + machine().describe_context(), + m_bank_prot_sel[1], + m_mode_large_prg ? "large ROM" : "banked RAM"); + } + + if (!m_bank_prot_low_mid && !m_config) + update_bank_rom_low(); + if (m_config) + update_bank_rom_high(); + if (!m_bank_prot_low_mid || m_config) + update_bank_ram(); +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MMM01, device_gb_cart_interface, bus::gameboy::mmm01_device, "gb_rom_mmm01", "Game Boy MMM01 Cartridge") diff --git a/src/devices/bus/gameboy/mmm01.h b/src/devices/bus/gameboy/mmm01.h new file mode 100644 index 00000000000..e1540eb74fb --- /dev/null +++ b/src/devices/bus/gameboy/mmm01.h @@ -0,0 +1,16 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Nintendo Game Boy MMM01 memory controller + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_MMM01_H +#define MAME_BUS_GAMEBOY_MMM01_H + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_MMM01, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_MMM01_H diff --git a/src/devices/bus/gameboy/rom.cpp b/src/devices/bus/gameboy/rom.cpp index f10bdb28ad7..b2d53ff0291 100644 --- a/src/devices/bus/gameboy/rom.cpp +++ b/src/devices/bus/gameboy/rom.cpp @@ -1,578 +1,1309 @@ // license:BSD-3-Clause -// copyright-holders:Fabio Priuli, Wilbert Pol +// copyright-holders:Vas Crabb, Wilbert Pol /*********************************************************************************************************** Game Boy cart emulation - Here we emulate carts with no RAM and simple bankswitch + Here we emulate carts without RAM banking. - TAMA5 Mapper (Used by Tamagotchi 3) - ============ + TODO: + - YongYong: + - During start there are 2 writes to 5000 and 5003, it is still unknown what these do. + - Story of Lasama: + - This should display the Gowin logo on boot on both DMG and CGB (not implemented yet) + - Sachen MMC2: + - Does it monitor the address to detect RAM writes for logo spoofing, or just /CS? + - ATV Racing/Rocket Games: + - Does it monitor the address to detect RAM writes for logo spoofing, or just /CS? + - How does RAM banking work? Are over-size RAM chips just inaccessible? + - GBCK003: + - Are large games that require coarse ROM banking supported? + - Is static RAM supported? If so, is it banked? + - This isn't really a cartridge - does it belong elsewhere? - Status: partially supported. + ***********************************************************************************************************/ - The TAMA5 mapper includes a special RTC chip which communicates through the - RAM area (0xA000-0xBFFF); most notably addresses 0xA000 and 0xA001 seem to - be used. In this setup 0xA001 acts like a control register and 0xA000 like - a data register. - Accepted values by the TAMA5 control register: - 0x00 - Writing to 0xA000 will set bits 3-0 for rom bank selection. - 0x01 - Writing to 0xA000 will set bits (7-?)4 for rom bank selection. +#include "emu.h" +#include "rom.h" - 0x04 - Bits 3-0 of the value to write - 0x05 - Bits 4-7 of the value to write - 0x06 - Address control hi - bit 0 - Bit 4 for the address - bit 3-1 - 000 - Write a byte to the 32 byte memory. The data to be - written must be set in registers 0x04 (lo nibble) and - 0x05 (hi nibble). - - 001 - Read a byte from the 32 byte memory. The data read - will be available in registers 0x0C (lo nibble) and - 0x0D (hi nibble). - - 010 - Unknown (occurs just after having started a game and - entered a date) (execution at address 1A19) - - 011 - Unknown (not encountered yet) - - 100 - Unknown (occurs during booting a game; appears to be - some kind of read command as it is followed by a read - of the 0x0C register) (execution at address 1B5B) - - 101 - Unknown (not encountered yet) - - 110 - Unknown (not encountered yet) - - 111 - Unknown (not encountered yet) - 0x07 - Address control lo - bit 3-0 - bits 3-0 for the address +#include "cartbase.ipp" - 0x0A - After writing this the lowest 2 bits of A000 determine whether the - TAMA5 chip is ready to accept the next command. If the lowest 2 bits - hold the value 01 then the TAMA5 chip is ready for the next command. +#include "bus/generic/slot.h" - 0x0C - Reading from A000 will return bits 3-0 of the data - 0x0D - Reading from A000 will return bits 7-4 of the data +#include "corestr.h" - 0x04 - RTC controls? -> RTC/memory? - 0x05 - Write time/memomry? - 0x06 - RTC controls? - 0x07 - RTC controls? +#include +#include - Unknown sequences: - During booting a game (1B5B: - 04 <- 00, 06 <- 08, 07 <- 01, followed by read 0C - when value read from 0C equals 00 followed by the sequence: - 04 <- 01, 06 <- 08, 07 <- 01, followed by read 0C - the value read from 0C is checked for non-zero, don't know the consequences for either - yet. +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" - Initialization after starting a game: - At address 1A19: - 06 <- 05, 07 <- 02, followed by read 0C, if != 0F => OK, otherwise do something. +namespace bus::gameboy { - Wisdom Tree mapper - ================== +namespace { - The Wisdom Tree mapper is triggered by writes in the 0x0000-0x3FFF area. The - address written to determines the bank to switch in in the 0x000-0x7FFF address - space. This mapper uses 32KB sized banks. +//************************************************************************** +// Sachen MMC1/MMC2 ROM banking helper class (16 KiB * 256) +//************************************************************************** +class sachen_mmc_device_base : public mbc_dual_uniform_device_base +{ +protected: + sachen_mmc_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + mbc_dual_uniform_device_base(mconfig, type, tag, owner, clock), + m_bank_mux_rom(0U), + m_bank_sel_rom{ 0U, 0U } + { + } - TODO: - - YongYong mapper: - - During start there are 2 writes to 5000 and 5003, it is still unknown what these do. - - Story of La Sa Ma mapper: - - This should display the Gowin logo on boot on both DMG and CGB (Not implemented yet) - - ATV Racing/Rocket Games mapper: - - How did this overlay the official Nintendo logo at BIOS check time? (Some Sachen titles use a similar trick) + virtual void device_start() override ATTR_COLD + { + mbc_dual_uniform_device_base::device_start(); - ***********************************************************************************************************/ + save_item(NAME(m_bank_mux_rom)); + save_item(NAME(m_bank_sel_rom)); + } + virtual void device_reset() override ATTR_COLD + { + mbc_dual_uniform_device_base::device_reset(); -#include "emu.h" -#include "rom.h" + m_bank_mux_rom = 0U; + m_bank_sel_rom[0] = 0U; + m_bank_sel_rom[1] = 1U; + update_banks(); + } -namespace { + bool check_rom(std::string &message) ATTR_COLD + { + set_bank_bits_rom(8); + return mbc_dual_uniform_device_base::check_rom(message); + } -class gb_rom_device : public device_t, public device_gb_cart_interface + void install_bank_switch_handlers() ATTR_COLD + { + // A5, A3 and A2 are not connected to the controller (effective address mask 0xffd3) + cart_space()->install_write_handler(0x0000, 0x1fff, write8smo_delegate(*this, FUNC(sachen_mmc_device_base::bank_rom_switch_outer))); + cart_space()->install_write_handler(0x2000, 0x3fff, write8smo_delegate(*this, FUNC(sachen_mmc_device_base::bank_rom_switch_inner))); + cart_space()->install_write_handler(0x4000, 0x5fff, write8smo_delegate(*this, FUNC(sachen_mmc_device_base::bank_rom_mux))); + } + + u8 rom_read(offs_t offset, bool spoof) + { + // holds A7 high when spoofing the logo + if (spoof) + offset |= 0x0080; + + // cartridge header area has address lines scrambled - A6 swapped with A0 and A4 swapped with A1 + if (0x0100 == (offset & 0xff00)) + offset = (offset & 0xff80) | bitswap<7>(offset, 0, 5, 1, 3, 2, 4, 6); + + return (BIT(offset, 14) ? bank_rom_high_base() : bank_rom_low_base())[offset & 0x3fff]; + } + +private: + void bank_rom_switch_outer(u8 data) + { + if (0x30 == (m_bank_sel_rom[1] & 0x30)) + { + m_bank_sel_rom[0] = data; + update_banks(); + } + } + + void bank_rom_switch_inner(u8 data) + { + m_bank_sel_rom[1] = data ? data : 1; + u16 const hi(bank_rom_entry_high()); + LOG( + "%s: Set ROM bank high = 0x%06X (0x%02X & 0x%02X | 0x%02X & 0x%02X)\n", + machine().describe_context(), + u32(hi) * 0x4000, + m_bank_sel_rom[0], + m_bank_mux_rom, + m_bank_sel_rom[1], + ~m_bank_mux_rom); + set_bank_rom_high(hi); + } + + void bank_rom_mux(u8 data) + { + if (0x30 == (m_bank_sel_rom[1] & 0x30)) + { + m_bank_mux_rom = data; + update_banks(); + } + } + + u8 bank_rom_entry_low() const + { + return m_bank_sel_rom[0] & m_bank_mux_rom; + } + + u8 bank_rom_entry_high() const + { + return (m_bank_sel_rom[0] & m_bank_mux_rom) | (m_bank_sel_rom[1] & ~m_bank_mux_rom); + } + + void update_banks() + { + u16 const lo(bank_rom_entry_low()); + u16 const hi(bank_rom_entry_high()); + LOG( + "%s: Set ROM banks low = 0x%1$06X (0x%3$02X & 0x%5$02X), high = 0x%2$06X (0x%3$02X & 0x%5$02X | 0x%4$02X & 0x%6$02X)\n", + machine().describe_context(), + u32(lo) * 0x4000, + u32(hi) * 0x4000, + m_bank_sel_rom[0], + m_bank_sel_rom[1], + m_bank_mux_rom, + ~m_bank_mux_rom); + set_bank_rom_low(lo); + set_bank_rom_high(hi); + } + + u8 m_bank_mux_rom; + u8 m_bank_sel_rom[2]; +}; + + + +//************************************************************************** +// Game Boy Color logo spoof used by Sachen and Rocket Games +//************************************************************************** + +template +class gbc_logo_spoof_device_base : public Base { public: - // construction/destruction - gb_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + using Base::logerror; protected: - gb_rom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + gbc_logo_spoof_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + Base(mconfig, type, tag, owner, clock), + m_ram_tap(), + m_notif_cart_space(), + m_counter(0U), + m_spoof_logo(0U), + m_installing_tap(false) + { + } + + virtual void device_start() override ATTR_COLD + { + Base::device_start(); + + this->save_item(NAME(m_counter)); + this->save_item(NAME(m_spoof_logo)); + } + + virtual void device_reset() override ATTR_COLD + { + Base::device_reset(); + + m_counter = 0U; + m_spoof_logo = 0U; + + install_ram_tap(); + m_notif_cart_space = this->cart_space()->add_change_notifier( + [this] (read_or_write mode) + { + if (u32(mode) & u32(read_or_write::WRITE)) + install_ram_tap(); + }); + } + + bool spoof_logo() const + { + return 1U == m_spoof_logo; + } - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } + void rom_access() + { + if (!this->machine().side_effects_disabled()) + { + // 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", + this->machine().describe_context(), + m_spoof_logo); + } + } + } + } + +private: + void install_ram_tap() + { + if (!m_installing_tap) + { + m_installing_tap = true; + m_ram_tap.remove(); + if (!m_spoof_logo) + { + m_ram_tap = this->cart_space()->install_write_tap( + 0xc000, 0xdfff, + "ram_tap_w", + [this] (offs_t offset, u8 &data, u8 mem_mask) + { + assert(!m_spoof_logo); + + if (!this->machine().side_effects_disabled()) + { + m_counter = 0U; + ++m_spoof_logo; + LOG( + "%s: Spoof logo step %u\n", + this->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; + } + } - void shared_start(); - void shared_reset(); + memory_passthrough_handler m_ram_tap; + util::notifier_subscription m_notif_cart_space; + + u8 m_counter; + u8 m_spoof_logo; + bool m_installing_tap; }; -class gb_rom_tama5_device : public gb_rom_device + +//************************************************************************** +// Mega Duck flat ROM (max 32 KiB) +//************************************************************************** + +class megaduck_flat_device : public device_t, public device_gb_cart_interface { public: - // construction/destruction - gb_rom_tama5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + megaduck_flat_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + megaduck_flat_device(mconfig, MEGADUCK_ROM_STD, tag, owner, clock) + { + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual uint8_t read_ram(offs_t offset) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // if there's no ROM region, there's nothing to do + memory_region *const romregion(cart_rom_region()); + if (!romregion || !romregion->bytes()) + return image_init_result::PASS; + + // check for supported size + auto const bytes(romregion->bytes()); + if (0x8000 < bytes) + { + message = "Unsupported cartridge ROM size (must be no larger than 32 KiB)"; + return image_init_result::FAIL; + } + + // install ROM + device_generic_cart_interface::install_non_power_of_two<0>( + bytes, + 0x7fff, + 0, + 0, + 0x0000, + [this, base = &romregion->as_u8()] (offs_t begin, offs_t end, offs_t mirror, offs_t src) + { + LOG( + "Install ROM 0x%04X-0x%04X at 0x%04X-0x%04X mirror 0x%04X\n", + src, + src + (end - begin), + begin, + end, + mirror); + cart_space()->install_rom(begin, end, mirror, &base[src]); + }); + + // all good + return image_init_result::PASS; + } protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + megaduck_flat_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_gb_cart_interface(mconfig, *this) + { + } - uint16_t m_tama5_data, m_tama5_addr, m_tama5_cmd; - uint8_t m_regs[32]; - uint8_t m_rtc_reg; + virtual void device_start() override ATTR_COLD + { + } }; -class gb_rom_wisdom_device : public gb_rom_device + +//************************************************************************** +// Mega Duck banked ROM (16 KiB fixed + 16 KiB * 256 or 32 KiB * 256) +//************************************************************************** + +class megaduck_banked_device : public device_t, public device_gb_cart_interface { public: - // construction/destruction - gb_rom_wisdom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + megaduck_banked_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, MEGADUCK_ROM_BANKED, tag, owner, clock), + device_gb_cart_interface(mconfig, *this), + m_bank_rom_low(*this, "low"), + m_bank_rom_high(*this, "high"), + m_bank_mask_rom{ 0U, 0U } + { + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // if there's no ROM region, there's nothing to do + memory_region *const romregion(cart_rom_region()); + if (!romregion || !romregion->bytes()) + { + // setting default bank on reset causes a fatal error when no banks are configured + message = "No ROM data found"; + return image_init_result::FAIL; + } + + // work out whether a fixed bank is required + auto const bytes(romregion->bytes()); + char const *const fixedbank_explicit(get_feature("fixedbank")); + std::optional fixedbank; + if (fixedbank_explicit) + { + // explicitly specified in software list + if (util::streqlower(fixedbank_explicit, "yes") || util::streqlower(fixedbank_explicit, "true")) + { + fixedbank = true; + } + else if (util::streqlower(fixedbank_explicit, "no") || util::streqlower(fixedbank_explicit, "false")) + { + fixedbank = false; + } + else + { + message = "Invalid 'fixedbank' feature value (must be yes or no)"; + return image_init_result::FAIL; + } + } + else if (bytes & 0x7fff) + { + // if it isn't a multiple of 32 KiB, it only makes sense with a fixed bank + fixedbank = true; + } + else if ((0x4000 * 0x100) < bytes) + { + // if it's larger than 4 MiB, it doesn't make sense with a fixed bank + fixedbank = false; + } + + // reject unsupported sizes + if ((bytes & 0x3fff) || ((0x8000 * 0x100) < bytes) || (fixedbank && (*fixedbank ? ((0x4000 * 0x100) < bytes) : (bytes & 0x7fff)))) + { + message = "Unsupported cartridge ROM size (must be a multiple of bank size and no larger than 256 entries)"; + return image_init_result::FAIL; + } + + // configure banks - low bank has 32 KiB entries, high bank has 16 KiB entries + auto const base(&romregion->as_u8()); + m_bank_mask_rom[0] = device_generic_cart_interface::map_non_power_of_two( + unsigned(bytes / 0x8000), + [this, base] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%04X-0x%04X in low bank entry %u\n", + page * 0x8000, + (page * 0x8000) + 0x7fff, + entry); + m_bank_rom_low->configure_entry(entry, &base[page * 0x8000]); + }); + m_bank_mask_rom[1] = device_generic_cart_interface::map_non_power_of_two( + unsigned(bytes / 0x4000), + [this, base] (unsigned entry, unsigned page) + { + LOG( + "Install ROM 0x%04X-0x%04X in high bank entry %u\n", + page * 0x4000, + (page * 0x4000) + 0x3fff, + entry); + m_bank_rom_high->configure_entry(entry, &base[page * 0x4000]); + }); + + // install handlers + if (!fixedbank) + { + // can't tell whether this cartridge has a fixed low bank, take a bet each way + logerror("Installing banked ROM at 0x0000-0x3FFF and 0x4000-0x7FFF\n"); + cart_space()->install_read_bank(0x0000, 0x3fff, m_bank_rom_low); + cart_space()->install_read_bank(0x4000, 0x7fff, m_bank_rom_high); + cart_space()->install_write_handler(0x0001, 0x0001, write8smo_delegate(*this, FUNC(megaduck_banked_device::bank_rom_switch_high))); + cart_space()->install_write_handler(0xb000, 0xb000, write8smo_delegate(*this, FUNC(megaduck_banked_device::bank_rom_switch))); + } + else if (*fixedbank) + { + // fixed ROM at 0x0000-0x3fff, banked ROM at 0x4000-0x7fff + logerror("Installing fixed ROM at 0x0000-0x3FFF and banked ROM at 0x4000-0x7FFF\n"); + cart_space()->install_rom(0x0000, 0x3fff, base); + cart_space()->install_read_bank(0x4000, 0x7fff, m_bank_rom_high); + cart_space()->install_write_handler(0x0001, 0x0001, write8smo_delegate(*this, FUNC(megaduck_banked_device::bank_rom_switch_high))); + } + else + { + // banked ROM at 0x0000-0x7fff + logerror("Installing banked ROM at 0x0000-0x7FFF\n"); + cart_space()->install_read_bank(0x0000, 0x7fff, m_bank_rom_low); + cart_space()->install_write_handler(0xb000, 0xb000, write8smo_delegate(*this, FUNC(megaduck_banked_device::bank_rom_switch_low))); + } + + // all good + return image_init_result::PASS; + } protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } + virtual void device_start() override ATTR_COLD + { + } + + virtual void device_reset() override ATTR_COLD + { + m_bank_rom_low->set_entry(0 & m_bank_mask_rom[0]); + m_bank_rom_high->set_entry(1 & m_bank_mask_rom[1]); + } + +private: + void bank_rom_switch_low(u8 data) + { + u8 const lo = data & m_bank_mask_rom[0]; + LOG( + "%s: Set ROM bank low = 0x%04X\n", + machine().describe_context(), + lo * 0x8000); + m_bank_rom_low->set_entry(lo); + } + + void bank_rom_switch_high(u8 data) + { + u8 const hi = data & m_bank_mask_rom[1]; + LOG( + "%s: Set ROM bank high = 0x%04X\n", + machine().describe_context(), + hi * 0x4000); + m_bank_rom_high->set_entry(hi); + } + + void bank_rom_switch(u8 data) + { + u8 const lo = data & m_bank_mask_rom[0]; + u8 const hi = ((data * 2) + 1) & m_bank_mask_rom[1]; + LOG( + "%s: Set ROM banks low = 0x%04X, high = 0x%04X\n", + machine().describe_context(), + lo * 0x8000, + hi * 0x4000); + m_bank_rom_low->set_entry(lo); + m_bank_rom_high->set_entry(hi); + } + + memory_bank_creator m_bank_rom_low; + memory_bank_creator m_bank_rom_high; + u8 m_bank_mask_rom[2]; }; -class gb_rom_yong_device : public gb_rom_device + +//************************************************************************** +// Game Boy flat ROM/RAM (max 32 KiB ROM, 8 KiB RAM) +//************************************************************************** + +class flat_rom_ram_device : public flat_ram_device_base { public: - // construction/destruction - gb_rom_yong_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; + flat_rom_ram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base(mconfig, GB_ROM_STD, tag, owner, clock) + { + } -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // first set up cartridge RAM if any + if (!check_ram(message)) + return image_init_result::FAIL; + + // ROM is installed the same way as a Mega Duck flat ROM cartridge + image_init_result const romresult(megaduck_flat_device::load(message)); + if (image_init_result::PASS != romresult) + return romresult; + install_ram(); + + // all good + return image_init_result::PASS; + } }; -class gb_rom_atvrac_device : public gb_rom_device + +//************************************************************************** +// M161 (32 KiB * 8) +//************************************************************************** + +class m161_device : public flat_ram_device_base { public: - // construction/destruction - gb_rom_atvrac_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + m161_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base(mconfig, GB_ROM_M161, tag, owner, clock), + m_bank_lock(0U) + { + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM + set_bank_bits_rom(8); // 74HC161A - 3-bit bank, 1-bit lockout + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + + // install bank switch handler + cart_space()->install_write_handler(0x0000, 0x7fff, write8sm_delegate(*this, FUNC(m161_device::bank_rom_switch))); + + // all good + return image_init_result::PASS; + } -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } +private: + virtual void device_start() override ATTR_COLD + { + flat_ram_device_base::device_start(); + + save_item(NAME(m_bank_lock)); + } + + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base::device_reset(); + + m_bank_lock = 0U; + + set_bank_rom(0); + } + + void bank_rom_switch(offs_t offset, u8 data) + { + if (!m_bank_lock) + { + m_bank_lock = 1U; // D connected to Vcc + set_bank_rom(data & 0x07); // A/B/C connected to D0/D1/D2 + } + } + + u8 m_bank_lock; }; -class gb_rom_lasama_device : public gb_rom_device + +//************************************************************************** +// Wisdom Tree (theoretical 32 KiB * 16,386, limited to 32 KiB * 256) +//************************************************************************** + +class wisdom_device : public flat_ram_device_base { public: - // construction/destruction - gb_rom_lasama_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + wisdom_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base(mconfig, GB_ROM_WISDOM, tag, owner, clock) + { + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM + set_bank_bits_rom(8); // 8 MiB real-world limit with 74LS377 - change to 14 for 512 MiB theoretical limit + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + set_bank_rom(0); + + // install bank switch handler + cart_space()->install_write_handler(0x0000, 0x3fff, write8sm_delegate(*this, FUNC(wisdom_device::bank_rom_switch))); + + // all good + return image_init_result::PASS; + } -protected: - // device-level overrides - virtual void device_start() override { shared_start(); } - virtual void device_reset() override { shared_reset(); } +private: + void bank_rom_switch(offs_t offset, u8 data) + { + set_bank_rom(offset); + } }; -class megaduck_rom_device : public device_t, public device_gb_cart_interface +//************************************************************************** +// Yong Yong Pirate +//************************************************************************** + +class yong_device : public flat_ram_device_base { public: - // construction/destruction - megaduck_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + yong_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base(mconfig, GB_ROM_YONG, tag, owner, clock) + { + } - // reading and writing - virtual uint8_t read_rom(offs_t offset) override; - virtual void write_bank(offs_t offset, uint8_t data) override; - virtual void write_ram(offs_t offset, uint8_t data) override; + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM/RAM + set_bank_bits_rom(8); + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + + // install bank switch handler + cart_space()->install_write_handler(0x2000, 0x2000, write8smo_delegate(*this, FUNC(yong_device::bank_rom_switch))); + + // all good + return image_init_result::PASS; + } protected: - megaduck_rom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base::device_reset(); - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + set_bank_rom(1); + } + +private: + void bank_rom_switch(u8 data) + { + set_bank_rom(data); + } }; -//------------------------------------------------- -// gb_rom_device - constructor -//------------------------------------------------- -gb_rom_device::gb_rom_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) -{ -} +//************************************************************************** +// MBC1 variant used by Yong Yong for Rockman 8 +//************************************************************************** -gb_rom_device::gb_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_device(mconfig, GB_STD_ROM, tag, owner, clock) +class rockman8_device : public flat_ram_device_base { -} +public: + rockman8_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base(mconfig, GB_ROM_ROCKMAN8, tag, owner, clock) + { + } -gb_rom_tama5_device::gb_rom_tama5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_device(mconfig, GB_ROM_TAMA5, tag, owner, clock), m_tama5_data(0), m_tama5_addr(0), m_tama5_cmd(0), m_rtc_reg(0) -{ -} + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM/RAM + set_bank_bits_rom(5); + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + + // install bank switch handler + cart_space()->install_write_handler(0x2000, 0x3fff, write8smo_delegate(*this, FUNC(rockman8_device::bank_rom_switch))); + + // all good + return image_init_result::PASS; + } -gb_rom_wisdom_device::gb_rom_wisdom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_device(mconfig, GB_ROM_WISDOM, tag, owner, clock) -{ -} +protected: + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base::device_reset(); -gb_rom_yong_device::gb_rom_yong_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_device(mconfig, GB_ROM_YONG, tag, owner, clock) -{ -} + set_bank_rom(1); + } -gb_rom_atvrac_device::gb_rom_atvrac_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_device(mconfig, GB_ROM_ATVRAC, tag, owner, clock) -{ -} +private: + void bank_rom_switch(u8 data) + { + data &= 0x1f; + set_bank_rom(!data ? 1 : (0x0f < data) ? (data - 8) : data); + } +}; -gb_rom_lasama_device::gb_rom_lasama_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : gb_rom_device(mconfig, GB_ROM_LASAMA, tag, owner, clock) -{ -} -megaduck_rom_device::megaduck_rom_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) +//************************************************************************** +// MBC1 variant used by Yong Yong for Super Mario 3 Special +//************************************************************************** +/* + Mario special seems to be a 512k image (mirrored up to 1M or 2M [redump needed to establish this]) + it consists of 13 unique 16k chunks laid 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 + + According to old doc from Brian Provinciano, writing bit 5 in 0x5000-0x5fff should + change the bank layout, in the sense that writing to bank switch 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 bit 4 is written, but changing mode with + bit 4 breaks the graphics). + */ + +class sm3sp_device : public flat_ram_device_base { -} +public: + sm3sp_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base(mconfig, GB_ROM_SM3SP, tag, owner, clock), + m_bank_rom_mode(0U) + { + } -megaduck_rom_device::megaduck_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : megaduck_rom_device(mconfig, MEGADUCK_ROM, tag, owner, clock) -{ -} + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM/RAM + set_bank_bits_rom(5); + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + + // install bank switch handler + cart_space()->install_write_handler(0x2000, 0x2fff, write8smo_delegate(*this, FUNC(sm3sp_device::bank_rom_switch))); + cart_space()->install_write_handler(0x5000, 0x5fff, write8smo_delegate(*this, FUNC(sm3sp_device::bank_rom_mode))); + + // all good + return image_init_result::PASS; + } +protected: + virtual void device_start() override ATTR_COLD + { + flat_ram_device_base::device_start(); -//------------------------------------------------- -// shared_start -//------------------------------------------------- + save_item(NAME(m_bank_rom_mode)); + } -void gb_rom_device::shared_start() -{ - save_item(NAME(m_latch_bank)); - save_item(NAME(m_latch_bank2)); - save_item(NAME(m_ram_bank)); -} + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base::device_reset(); -//------------------------------------------------- -// shared_reset -//------------------------------------------------- + m_bank_rom_mode = 0U; -void gb_rom_device::shared_reset() -{ - m_ram_bank = 0; - m_latch_bank = 0; - m_latch_bank2 = 1; -} + set_bank_rom(1); + } -//------------------------------------------------- -// mapper specific start/reset -//------------------------------------------------- +private: + void bank_rom_switch(u8 data) + { + /* + 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) + + Table 1 confirmed... + 0 -> 0 + 4 -> 2 + 6 -> 3 + 19 -> 5 (level 2 background graphics) + 1b -> 8 (level 3 background graphics) + 1c -> b (bonus house background graphics) + 1d -> d (level 4 background graphics) + 1e -> 6 (level 1 background graphics) + 1 (9 maybe, or 3)? f (5 maybe)? 2->1? + 16 -> 4-8? b? + */ + static constexpr u8 BANK_TABLE[0x20] = { + 0x00,0x04,0x01,0x05, 0x02,0x06,0x03,0x05, + 0x08,0x0c,0x03,0x0d, 0x03,0x0b,0x0b,0x08 /* original doc put 0x0f here (i.e. 11th unique bank) */, + 0x05,0x06,0x0b,0x0d, 0x08,0x06,0x13,0x0b, + 0x08,0x05,0x05,0x08, 0x0b,0x0d,0x06,0x05 }; + + u8 const lookup(BANK_TABLE[data & 0x1f]); + u8 remap(lookup); + if (m_bank_rom_mode) + { + switch (lookup) + { + case 0x02: remap = 0x04; break; + case 0x03: remap = 0x06; break; + case 0x04: remap = 0x02; break; + case 0x05: remap = 0x03; break; + case 0x06: remap = 0x05; break; + case 0x0b: remap = 0x0d; break; + case 0x0c: remap = 0x0b; break; + case 0x0d: remap = 0x0c; break; + + case 0x00: + case 0x01: + case 0x08: + case 0x0f: + case 0x13: + default: + break; + } + } + LOG( + "%s: Bank select 0x%02X -> 0x%02X -> 0x%02X\n", + machine().describe_context(), + data, + lookup, + remap); + set_bank_rom(remap); + } -void gb_rom_tama5_device::device_start() -{ - shared_start(); - save_item(NAME(m_tama5_data)); - save_item(NAME(m_tama5_addr)); - save_item(NAME(m_tama5_cmd)); - save_item(NAME(m_regs)); - save_item(NAME(m_rtc_reg)); -} - -void gb_rom_tama5_device::device_reset() -{ - shared_reset(); - m_tama5_data = 0; - m_tama5_addr= 0; - m_tama5_cmd = 0; - memset(m_regs, 0xff, sizeof(m_regs)); - m_rtc_reg = 0xff; -} + void bank_rom_mode(u8 data) + { + LOG("%s: Bank mode 0x%02X\n", machine().describe_context(), data); + m_bank_rom_mode = BIT(data, 5); + } + u8 m_bank_rom_mode; +}; -// these are identical to shared ones above, but megaduck cart class is not derived from gb cart class... -void megaduck_rom_device::device_start() -{ - save_item(NAME(m_latch_bank)); - save_item(NAME(m_latch_bank2)); - save_item(NAME(m_ram_bank)); -} -void megaduck_rom_device::device_reset() + +//************************************************************************** +// Sachen MMC1 unlicensed +//************************************************************************** + +class sachen_mmc1_device : public sachen_mmc_device_base { - m_ram_bank = 0; - m_latch_bank = 0; - m_latch_bank2 = 1; -} +public: + sachen_mmc1_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + sachen_mmc_device_base(mconfig, GB_ROM_SACHEN1, tag, owner, clock), + m_counter(0U) + { + } + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // configure ROM banks but don't install directly + if (!check_rom(message)) + return image_init_result::FAIL; + configure_bank_rom(); + + // actually install ROM and bank switch handlers + logerror("Installing banked ROM at 0x0000-0x3FFF and 0x4000-0x7FFF\n"); + cart_space()->install_read_handler(0x0000, 0x7fff, read8sm_delegate(*this, FUNC(sachen_mmc1_device::read_rom))); + install_bank_switch_handlers(); + + // all good + return image_init_result::PASS; + } -/*------------------------------------------------- - mapper specific handlers - -------------------------------------------------*/ +protected: + virtual void device_start() override ATTR_COLD + { + sachen_mmc_device_base::device_start(); -uint8_t gb_rom_device::read_rom(offs_t offset) -{ - m_latch_bank = offset / 0x4000; - return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + (offset & 0x3fff)]; -} + save_item(NAME(m_counter)); + } -uint8_t gb_rom_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_reset() override ATTR_COLD + { + sachen_mmc_device_base::device_reset(); -void gb_rom_device::write_ram(offs_t offset, uint8_t data) -{ - if (!m_ram.empty()) - m_ram[ram_bank_map[m_ram_bank] * 0x2000 + offset] = data; -} + m_counter = 0U; + } + +private: + u8 read_rom(offs_t offset) + { + if (!machine().side_effects_disabled()) + { + // 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 (0x31 != m_counter) + ++m_counter; + } + + return rom_read(offset, 0x31 != m_counter); + } + u8 m_counter; +}; -// Tamagotchi -uint8_t gb_rom_tama5_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)]; -} -uint8_t gb_rom_tama5_device::read_ram(offs_t offset) -{ - return m_rtc_reg; -} +//************************************************************************** +// Sachen MMC2 unlicensed +//************************************************************************** -void gb_rom_tama5_device::write_ram(offs_t offset, uint8_t data) +class sachen_mmc2_device : public gbc_logo_spoof_device_base { - switch (offset & 0x0001) +public: + sachen_mmc2_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + gbc_logo_spoof_device_base(mconfig, GB_ROM_SACHEN2, tag, owner, clock) { - case 0x0000: /* Write to data register */ - switch (m_tama5_cmd) - { - case 0x00: /* Bits 0-3 for rom bank selection */ - m_latch_bank2 = (m_latch_bank2 & 0xf0) | (data & 0x0f); - break; - case 0x01: /* Bit 4(-7?) for rom bank selection */ - m_latch_bank2 = (m_latch_bank2 & 0x0f) | ((data & 0x0f) << 4); - break; - case 0x04: /* Data to write lo */ - m_tama5_data = (m_tama5_data & 0xf0) | (data & 0x0f); - break; - case 0x05: /* Data to write hi */ - m_tama5_data = (m_tama5_data & 0x0f) | ((data & 0x0f) << 4); - break; - case 0x06: /* Address selection hi */ - m_tama5_addr = (m_tama5_addr & 0x0f) | ((data & 0x0f) << 4); - break; - case 0x07: /* Address selection lo */ - /* This address always seems to written last, so we'll just - execute the command here */ - m_tama5_addr = (m_tama5_addr & 0xf0) | (data & 0x0f); - switch (m_tama5_addr & 0xe0) - { - case 0x00: /* Write memory */ - //logerror( "Write tama5 memory 0x%02X <- 0x%02X\n", m_tama5_addr & 0x1f, m_tama5_data); - m_regs[m_tama5_addr & 0x1f] = m_tama5_data; - break; - case 0x20: /* Read memory */ - //logerror( "Read tama5 memory 0x%02X\n", m_tama5_addr & 0x1f); - m_tama5_data = m_regs[m_tama5_addr & 0x1f]; - break; - case 0x40: /* Unknown, some kind of read */ - if ((m_tama5_addr & 0x1f) == 0x12) - m_tama5_data = 0xff; - [[fallthrough]]; - case 0x80: /* Unknown, some kind of read (when 07=01)/write (when 07=00/02) */ - default: - logerror( "%s Unknown addressing mode\n", machine().describe_context() ); - break; - } - break; - } - break; - case 0x0001: /* Write to control register */ - switch (data) - { - case 0x00: /* Bits 0-3 for rom bank selection */ - case 0x01: /* Bits 4-7 for rom bank selection */ - case 0x04: /* Data write register lo */ - case 0x05: /* Data write register hi */ - case 0x06: /* Address register hi */ - case 0x07: /* Address register lo */ - break; - case 0x0a: /* Are we ready for the next command? */ - m_rtc_reg = 0x01; - break; - case 0x0c: /* Data read register lo */ - m_rtc_reg = m_tama5_data & 0x0f; - break; - case 0x0d: /* Data read register hi */ - m_rtc_reg = (m_tama5_data & 0xf0) >> 4; - break; - default: - logerror( "%s Unknown tama5 command 0x%02X\n", machine().describe_context(), data ); - break; - } - m_tama5_cmd = data; - break; } -} + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // configure ROM banks but don't install directly + if (!check_rom(message)) + return image_init_result::FAIL; + configure_bank_rom(); + + // actually install ROM and bank switch handlers + logerror("Installing banked ROM at 0x0000-0x3FFF and 0x4000-0x7FFF\n"); + cart_space()->install_read_handler(0x0000, 0x7fff, read8sm_delegate(*this, FUNC(sachen_mmc2_device::read_rom))); + install_bank_switch_handlers(); + + // all good + return image_init_result::PASS; + } + +private: + u8 read_rom(offs_t offset) + { + rom_access(); + return rom_read(offset, spoof_logo()); + } +}; -// Wisdom Tree -uint8_t gb_rom_wisdom_device::read_rom(offs_t offset) -{ - return m_rom[rom_bank_map[m_latch_bank] * 0x4000 + offset]; -} -void gb_rom_wisdom_device::write_bank(offs_t offset, uint8_t data) +//************************************************************************** +// Rocket Games unlicensed (incomplete) +//************************************************************************** + +class rocket_device : public flat_ram_device_base > { - if (offset < 0x4000) - m_latch_bank = (offset << 1) & 0x1ff; -} +public: + rocket_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + flat_ram_device_base >(mconfig, GB_ROM_ROCKET, tag, owner, clock) + { + } + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // configure ROM banks but don't install directly + set_bank_bits_rom(8, 4); + if (!check_rom(message)) + return image_init_result::FAIL; + configure_bank_rom(); + install_ram(); + + // actually install ROM and bank switch handlers + logerror("Installing banked ROM at 0x0000-0x3FFF and 0x4000-0x7FFF\n"); + cart_space()->install_read_handler(0x0000, 0x7fff, read8sm_delegate(*this, FUNC(rocket_device::read_rom))); + cart_space()->install_write_handler(0x3f00, 0x3f00, write8smo_delegate(*this, FUNC(rocket_device::bank_rom_switch_fine))); + cart_space()->install_write_handler(0x3fc0, 0x3fc0, write8smo_delegate(*this, FUNC(rocket_device::bank_rom_switch_coarse))); + + // all good + return image_init_result::PASS; + } -// Yong Yong pirate +protected: + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base >::device_reset(); -uint8_t gb_rom_yong_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)]; -} + set_bank_rom_coarse(0); + set_bank_rom_fine(1); + } -void gb_rom_yong_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset == 0x2000) - m_latch_bank2 = data; -} +private: + u8 read_rom(offs_t offset) + { + // logo spoofing logic + rom_access(); + + // fetch the banked ROM data + u8 const data((BIT(offset, 14) ? bank_rom_high_base() : bank_rom_low_base())[offset & 0x3fff]); + if (spoof_logo() && (0x0104 <= offset) && (0x133 >= offset)) + { + static constexpr u8 TRANSFORM[] = { + 0xdf, 0xce, 0x97, 0x78, 0xcd, 0x2f, 0xf0, 0x0b, 0x0b, 0xea, 0x78, 0x83, 0x08, 0x1d, 0x9a, 0x45, + 0x11, 0x2b, 0xe1, 0x11, 0xf8, 0x88, 0xf8, 0x8e, 0xfe, 0x88, 0x2a, 0xc4, 0xff, 0xfc, 0xd9, 0x87, + 0x22, 0xab, 0x67, 0x7d, 0x77, 0x2c, 0xa8, 0xee, 0xff, 0x9b, 0x99, 0x91, 0xaa, 0x9b, 0x33, 0x3e }; + return data ^ TRANSFORM[offset - 0x104]; + } + else + { + return data; + } + } + void bank_rom_switch_fine(u8 data) + { + set_bank_rom_fine(data ? data : 1); + } -// ATV Racin pirate (incomplete) + void bank_rom_switch_coarse(u8 data) + { + set_bank_rom_coarse(data); + } +}; -uint8_t gb_rom_atvrac_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_atvrac_device::write_bank(offs_t offset, uint8_t data) + +//************************************************************************** +// Story of Lasama pirate (incomplete) +//************************************************************************** + +class lasama_device : public flat_ram_device_base { - if (offset == 0x3f00) +public: + lasama_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock) : + flat_ram_device_base(mconfig, GB_ROM_LASAMA, tag, owner, clock) { - if (data == 0) - data = 1; - m_latch_bank2 = m_latch_bank | data; } - if (offset == 0x3fc0) - m_latch_bank = data * 16; -} -// La Sa Ma pirate (incomplete) + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM/RAM + set_bank_bits_rom(1, 2); + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + + // install bank switch handlers + cart_space()->install_write_handler(0x2080, 0x2080, write8smo_delegate(*this, FUNC(lasama_device::ctrl_2080_w))); + cart_space()->install_write_handler(0x6000, 0x6000, write8smo_delegate(*this, FUNC(lasama_device::ctrl_6000_w))); + + // all good + return image_init_result::PASS; + } -uint8_t gb_rom_lasama_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)]; -} +protected: + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base::device_reset(); -void gb_rom_lasama_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset == 0x2080) + set_bank_rom_coarse(0); + set_bank_rom_fine(1); + } + +private: + void ctrl_2080_w(u8 data) { - // Actual banking? - m_latch_bank2 = m_latch_bank | (data & 0x03); + // high ROM fine bank? + set_bank_rom_fine(data & 0x03); } - if (offset == 0x6000) + + void ctrl_6000_w(u8 data) { // On boot the following two get written right after each other: // 02 // BE - // Disable logo switching? - if (!(data & 0x80)) - m_latch_bank = (data & 0x02) << 1; + // disable logo switching? + if (!BIT(data, 7)) + set_bank_rom_coarse(BIT(data, 1)); } -} +}; -// MegaDuck carts -uint8_t megaduck_rom_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)]; -} +//************************************************************************** +// GBCK003 board soldered inside some units +//************************************************************************** +/* + Seems to be a kind of MBC1 emulator, like MMM01 but less sophisticated. -void megaduck_rom_device::write_bank(offs_t offset, uint8_t data) -{ - if (offset == 0x0001) - m_latch_bank2 = data; -} + Base page for game right shifted by one bit position is written to 0x7B00. + Fine bank mask right shifted by one bit position is written to 0x7B01. + It always seems to write 0xF0 to 0x7B02 before jumping to the game. + */ -void megaduck_rom_device::write_ram(offs_t offset, uint8_t data) +class gbck003_device : public flat_ram_device_base { - m_latch_bank = data * 2; - m_latch_bank2 = data * 2 + 1; -} +public: + gbck003_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock) : + flat_ram_device_base(mconfig, GB_ROM_GBCK003, tag, owner, clock), + m_bank_setting{ 0U, 0U }, + m_bank_mux_rom(0U), + m_config_mode(0U) + { + } + + virtual image_init_result load(std::string &message) override ATTR_COLD + { + // set up ROM/RAM + set_bank_bits_rom(9); + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + install_ram(); + + // install bank switch handlers + cart_space()->install_write_handler(0x2000, 0x3fff, write8smo_delegate(*this, FUNC(gbck003_device::bank_switch_inner))); + cart_space()->install_write_handler(0x7b00, 0x7b00, write8smo_delegate(*this, FUNC(gbck003_device::bank_switch_outer))); + cart_space()->install_write_handler(0x7b01, 0x7b01, write8smo_delegate(*this, FUNC(gbck003_device::bank_set_mux))); + cart_space()->install_write_handler(0x7b02, 0x7b02, write8smo_delegate(*this, FUNC(gbck003_device::exit_config_mode))); + + // all good + return image_init_result::PASS; + } + +protected: + virtual void device_start() override ATTR_COLD + { + flat_ram_device_base::device_start(); + + save_item(NAME(m_bank_setting)); + save_item(NAME(m_bank_mux_rom)); + save_item(NAME(m_config_mode)); + } + + virtual void device_reset() override ATTR_COLD + { + flat_ram_device_base::device_reset(); + + // TODO: proper reset state + m_bank_setting[0] = 0U; + m_bank_setting[1] = 1U; + m_bank_mux_rom = 0xff; + m_config_mode = 1U; + + set_bank_rom_low(0); + set_bank_rom_high(1); + } + +private: + void bank_switch_inner(u8 data) + { + data &= 0x1f; + m_bank_setting[1] = data ? data : 1; + set_bank_rom_high(bank_rom_entry_high()); + } + + void bank_switch_outer(u8 data) + { + if (m_config_mode) + { + LOG( + "%s: Set base page = 0x%03X (0x%06X)\n", + machine().describe_context(), + u16(data) << 1, + u32(data) << 15); + m_bank_setting[0] = data; + set_bank_rom_low(bank_rom_entry_low()); + set_bank_rom_high(bank_rom_entry_high()); + } + else + { + logerror( + "%s: Write 0x7B00 = 0x%02X after disabling configuration mode\n", + machine().describe_context(), + data); + } + } + + void bank_set_mux(u8 data) + { + if (m_config_mode) + { + LOG( + "%s: Set bank multiplexer = 0x%03X\n", + machine().describe_context(), + u16(data) << 1); + m_bank_mux_rom = data; + set_bank_rom_high(bank_rom_entry_high()); + } + else + { + logerror( + "%s: Write 0x7B01 = 0x%02X after disabling configuration mode\n", + machine().describe_context(), + data); + } + } + + void exit_config_mode(u8 data) + { + if (m_config_mode) + { + if (0xf0 == data) + { + LOG("%s: Configuration mode disabled\n", machine().describe_context()); + m_config_mode = 0U; + } + else + { + logerror( + "%s: Unknown write 0x7B02 = 0x%02X in configuration mode\n", + machine().describe_context(), + data); + } + } + else + { + logerror( + "%s: Write 0x7B02 = 0x%02X after disabling configuration mode\n", + machine().describe_context(), + data); + } + } + + u16 bank_rom_entry_low() const + { + // TODO: is this affected by the multiplexer? + return u16(m_bank_setting[0]) << 1; + } + + u16 bank_rom_entry_high() const + { + return (u16(m_bank_setting[0] & m_bank_mux_rom) << 1) | (m_bank_setting[1] & ~(u16(m_bank_mux_rom) << 1)); + } + + u8 m_bank_setting[2]; + u8 m_bank_mux_rom; + u8 m_config_mode; +}; } // anonymous namespace +} // namespace bus::gameboy + // device type definition -DEFINE_DEVICE_TYPE_PRIVATE(GB_STD_ROM, device_gb_cart_interface, gb_rom_device, "gb_rom", "Game Boy Cartridge") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_TAMA5, device_gb_cart_interface, gb_rom_tama5_device, "gb_rom_tama5", "Game Boy Tamagotchi Cartridge") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_WISDOM, device_gb_cart_interface, gb_rom_wisdom_device, "gb_rom_wisdom", "Game Boy Wisdom Tree Cartridge") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_YONG, device_gb_cart_interface, gb_rom_yong_device, "gb_rom_yong", "Game Boy Young Yong Cartridge") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_ATVRAC, device_gb_cart_interface, gb_rom_atvrac_device, "gb_rom_atvrac", "Game Boy ATV Racin' Cartridge") -DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_LASAMA, device_gb_cart_interface, gb_rom_lasama_device, "gb_rom_lasama", "Game Boy LaSaMa Cartridge") - -DEFINE_DEVICE_TYPE_PRIVATE(MEGADUCK_ROM, device_gb_cart_interface, megaduck_rom_device, "megaduck_rom", "MegaDuck Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(MEGADUCK_ROM_STD, device_gb_cart_interface, bus::gameboy::megaduck_flat_device, "megaduck_rom", "Mega Duck Flat ROM Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(MEGADUCK_ROM_BANKED, device_gb_cart_interface, bus::gameboy::megaduck_banked_device, "megaduck_rom_banked", "Mega Duck Banked ROM Cartridge") + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_STD, device_gb_cart_interface, bus::gameboy::flat_rom_ram_device, "gb_rom", "Game Boy Flat ROM Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_M161, device_gb_cart_interface, bus::gameboy::m161_device, "gb_rom_m161", "Game Boy M161 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_WISDOM, device_gb_cart_interface, bus::gameboy::wisdom_device, "gb_rom_wisdom", "Game Boy Wisdom Tree Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_YONG, device_gb_cart_interface, bus::gameboy::yong_device, "gb_rom_yong", "Game Boy Young Yong Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_ROCKMAN8, device_gb_cart_interface, bus::gameboy::rockman8_device, "gb_rom_rockman8", "Game Boy Rockman 8 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SM3SP, device_gb_cart_interface, bus::gameboy::sm3sp_device, "gb_rom_sm3sp", "Game Boy Super Mario 3 Special Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SACHEN1, device_gb_cart_interface, bus::gameboy::sachen_mmc1_device, "gb_rom_sachen1", "Game Boy Sachen MMC1 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SACHEN2, device_gb_cart_interface, bus::gameboy::sachen_mmc2_device, "gb_rom_sachen2", "Game Boy Sachen MMC2 Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_ROCKET, device_gb_cart_interface, bus::gameboy::rocket_device, "gb_rom_rocket", "Game Boy Rocket Games Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_LASAMA, device_gb_cart_interface, bus::gameboy::lasama_device, "gb_rom_lasama", "Game Boy Story of Lasama Cartridge") +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_GBCK003, device_gb_cart_interface, bus::gameboy::gbck003_device, "gb_rom_gbck003", "Game Boy GBCK003 188 in 1 Game Board") diff --git a/src/devices/bus/gameboy/rom.h b/src/devices/bus/gameboy/rom.h index 6ca12e80a60..f523446f72f 100644 --- a/src/devices/bus/gameboy/rom.h +++ b/src/devices/bus/gameboy/rom.h @@ -1,17 +1,23 @@ // license:BSD-3-Clause -// copyright-holders:Fabio Priuli, Wilbert Pol +// copyright-holders:Vas Crabb, Wilbert Pol #ifndef MAME_BUS_GAMEBOY_ROM_H #define MAME_BUS_GAMEBOY_ROM_H -#include "gb_slot.h" +#include "slot.h" -DECLARE_DEVICE_TYPE(GB_STD_ROM, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_TAMA5, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_WISDOM, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_YONG, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_ATVRAC, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(GB_ROM_LASAMA, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(MEGADUCK_ROM_STD, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(MEGADUCK_ROM_BANKED, device_gb_cart_interface) -DECLARE_DEVICE_TYPE(MEGADUCK_ROM, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_STD, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_M161, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_WISDOM, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_YONG, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_ROCKMAN8, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_SM3SP, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_SACHEN1, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_SACHEN2, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_ROCKET, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_LASAMA, device_gb_cart_interface) +DECLARE_DEVICE_TYPE(GB_ROM_GBCK003, device_gb_cart_interface) #endif // MAME_BUS_GAMEBOY_ROM_H diff --git a/src/devices/bus/gameboy/slot.cpp b/src/devices/bus/gameboy/slot.cpp new file mode 100644 index 00000000000..1ae0ab90f67 --- /dev/null +++ b/src/devices/bus/gameboy/slot.cpp @@ -0,0 +1,84 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy/Mega Duck cartridge slot interface + + The following ranges are available for use by the cartridge: + * 0x0000-0x7FFF - Game Boy boot ROM expects to find header at 0x0100. + Writes typically used to control memory mapping. + * 0xA000-0xBFFF - Typically used for additional RAM, sometimes + battery-backed. Sometimes used for I/O. + + **************************************************************************/ + +#include "emu.h" +#include "slot.h" + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +//************************************************************************** +// gb_cart_slot_device_base +//************************************************************************** + +void gb_cart_slot_device_base::call_unload() +{ + if (m_cart) + m_cart->unload(); +} + + +gb_cart_slot_device_base::gb_cart_slot_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_cartrom_image_interface(mconfig, *this), + device_single_card_slot_interface(mconfig, *this), + m_space(*this, finder_base::DUMMY_TAG, 8), + m_cart(nullptr) +{ +} + + +void gb_cart_slot_device_base::device_start() +{ + m_cart = get_card_device(); +} + + +image_init_result gb_cart_slot_device_base::call_load() +{ + if (!m_cart) + return image_init_result::PASS; + + image_init_result result; + if (!loaded_through_softlist()) + { + result = load_image_file(image_core_file()); + if (image_init_result::PASS != result) + return result; + } + std::string message; + result = m_cart->load(message); + if (image_init_result::PASS != result) + seterror(image_error::INVALIDIMAGE, message.c_str()); + return result; +} + + + +//************************************************************************** +// device_gb_cart_interface +//************************************************************************** + +void device_gb_cart_interface::unload() +{ +} + + +device_gb_cart_interface::device_gb_cart_interface(machine_config const &mconfig, device_t &device) : + device_interface(device, "gameboycart"), + m_slot(dynamic_cast(device.owner())) +{ +} diff --git a/src/devices/bus/gameboy/slot.h b/src/devices/bus/gameboy/slot.h new file mode 100644 index 00000000000..921c742f4ba --- /dev/null +++ b/src/devices/bus/gameboy/slot.h @@ -0,0 +1,92 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + Game Boy/Mega Duck cartridge slot interface + + ***************************************************************************/ +#ifndef MAME_BUS_GAMEBOY_SLOT_H +#define MAME_BUS_GAMEBOY_SLOT_H + +#include "imagedev/cartrom.h" + +#include +#include +#include +#include + + +//************************************************************************** +// FORWARD DECLARATIONS +//************************************************************************** + +class device_gb_cart_interface; + + + +//************************************************************************** +// CLASS DECLARATIONS +//************************************************************************** + +class gb_cart_slot_device_base : + public device_t, + public device_cartrom_image_interface, + public device_single_card_slot_interface +{ +public: + // configuration + template void set_space(T &&... args) { m_space.set_tag(std::forward(args)...); } + + // device_image_interface implementation + virtual image_init_result call_load() override ATTR_COLD; + virtual void call_unload() override ATTR_COLD; + virtual bool is_reset_on_load() const noexcept override { return true; } + +protected: + gb_cart_slot_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock); + + // device_t implementation + virtual void device_start() override ATTR_COLD; + + virtual image_init_result load_image_file(util::random_read &file) = 0; + + address_space &cart_space() noexcept { return *m_space; } + +private: + required_address_space m_space; + + device_gb_cart_interface *m_cart; + + friend class device_gb_cart_interface; +}; + + +class device_gb_cart_interface : public device_interface +{ +public: + virtual image_init_result load(std::string &message) ATTR_COLD = 0; + virtual void unload() ATTR_COLD; + +protected: + device_gb_cart_interface(machine_config const &mconfig, device_t &device); + + bool has_slot() const noexcept { return bool(m_slot); } + address_space *cart_space() noexcept { return m_slot ? m_slot->m_space.target() : nullptr; } + + bool loaded_through_softlist() const { return m_slot && m_slot->loaded_through_softlist(); } + char const *get_feature(std::string_view feature_name) const { return m_slot ? m_slot->get_feature(feature_name) : nullptr; } + + memory_region *cart_rom_region() { return m_slot ? m_slot->memregion("rom") : nullptr; } + memory_region *cart_ram_region() { return m_slot ? m_slot->memregion("ram") : nullptr; } + memory_region *cart_nvram_region() { return m_slot ? m_slot->memregion("nvram") : nullptr; } + memory_region *gbx_footer_region() { return m_slot ? m_slot->memregion("gbx") : nullptr; } + + void battery_load(void *buffer, int length, int fill) { assert(m_slot); m_slot->battery_load(buffer, length, fill); } + void battery_load(void *buffer, int length, void *def_buffer) { assert(m_slot); m_slot->battery_load(buffer, length, def_buffer); } + void battery_save(void const *buffer, int length) { assert(m_slot); m_slot->battery_save(buffer, length); } + +private: + gb_cart_slot_device_base *const m_slot; +}; + +#endif // MAME_BUS_GAMEBOY_SLOT_H diff --git a/src/devices/bus/gameboy/tama5.cpp b/src/devices/bus/gameboy/tama5.cpp new file mode 100644 index 00000000000..7db552b9de4 --- /dev/null +++ b/src/devices/bus/gameboy/tama5.cpp @@ -0,0 +1,269 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb, Wilbert Pol +/*************************************************************************** + + TAMA5 Mapper (Used by Tamagotchi 3) + ============ + + Cartridge contains the following chips: + * TAMA5 Game Boy bus interface + * TAMA6 Toshiba TMP47C243M TLCS-47 microcontroller + * TAMA7 mask ROM + * Toshiba TC8521AM real-time clock + * Toshiba M62021P system reset IC with switch for memory backup + + The MCU acts as an interface between the TAMA5 and the RTC. The MCU can + drive a piezo speaker (in response to alarms from the RTC). There is a + mechanical switch that disconnects power from the piezo speaker. The RTC, + MCU and piezo speaker have backup power supplied by a user-replaceable + CR2016 coin cell. + + The TAMA5 chip only responds to A15, A14, A0, and D3-D0, i.e. addresses are + effectively masked with 0xC001, and data is effectively masked with 0x0F. + + Status: partially supported. + + The TAMA5 mapper includes a special RTC chip which communicates through the + RAM area (0xA000-0xBFFF); most notably addresses 0xA000 and 0xA001 seem to + be used. In this setup 0xA001 acts like a control register and 0xA000 like + a data register. + + Accepted values by the TAMA5 control register: + 0x00 - Writing to 0xA000 will set bits 3-0 for ROM bank selection. + 0x01 - Writing to 0xA000 will set bits (7-?)4 for ROM bank selection. + + 0x04 - Bits 3-0 of the value to write + 0x05 - Bits 4-7 of the value to write + 0x06 - Address control hi + bit 0 - Bit 4 for the address + bit 3-1 - 000 - Write a byte to the 32 byte memory. The data to be + written must be set in registers 0x04 (lo nibble) and + 0x05 (hi nibble). + - 001 - Read a byte from the 32 byte memory. The data read + will be available in registers 0x0C (lo nibble) and + 0x0D (hi nibble). + - 010 - Unknown (occurs just after having started a game and + entered a date) (execution at address 1A19) + - 011 - Unknown (not encountered yet) + - 100 - Unknown (occurs during booting a game; appears to be + some kind of read command as it is followed by a read + of the 0x0C register) (execution at address 1B5B) + - 101 - Unknown (not encountered yet) + - 110 - Unknown (not encountered yet) + - 111 - Unknown (not encountered yet) + 0x07 - Address control lo + bit 3-0 - bits 3-0 for the address + + 0x0A - After writing this the lowest 2 bits of A000 determine whether the + TAMA5 chip is ready to accept the next command. If the lowest 2 bits + hold the value 01 then the TAMA5 chip is ready for the next command. + + 0x0C - Reading from A000 will return bits 3-0 of the data + 0x0D - Reading from A000 will return bits 7-4 of the data + + 0x04 - RTC controls? -> RTC/memory? + 0x05 - Write time/memomry? + 0x06 - RTC controls? + 0x07 - RTC controls? + + Unknown sequences: + During booting a game (1B5B: + 04 <- 00, 06 <- 08, 07 <- 01, followed by read 0C + when value read from 0C equals 00 followed by the sequence: + 04 <- 01, 06 <- 08, 07 <- 01, followed by read 0C + the value read from 0C is checked for non-zero, don't know the consequences for either + yet. + + Initialization after starting a game: + At address 1A19: + 06 <- 05, 07 <- 02, followed by read 0C, if != 0F => OK, otherwise do something. + + ***************************************************************************/ + +#include "emu.h" +#include "tama5.h" + +#include "cartbase.h" + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace bus::gameboy { + +namespace { + +class tama5_device : public mbc_device_base +{ +public: + static constexpr feature_type unemulated_features() { return feature::SOUND; } + + tama5_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual image_init_result load(std::string &message) override ATTR_COLD; + +protected: + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + +private: + u8 data_r(address_space &space); + void data_w(u8 data); + void command_w(u8 data); + + u8 m_bank_sel_rom; + + u8 m_registers[0x20]; + u8 m_response; + u8 m_command; + u8 m_data; + u8 m_address; +}; + + +tama5_device::tama5_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock) : + mbc_device_base(mconfig, GB_ROM_TAMA5, tag, owner, clock), + m_bank_sel_rom(0U), + m_response(0U), + m_command(0U), + m_data(0U), + m_address(0U) +{ +} + + +image_init_result tama5_device::load(std::string &message) +{ + // set up ROM + set_bank_bits_rom(5); + if (!check_rom(message)) + return image_init_result::FAIL; + install_rom(); + + // install I/O + cart_space()->install_read_handler(0xa000, 0xa000, 0x0000, 0x1fff, 0x0000, read8mo_delegate(*this, FUNC(tama5_device::data_r))); + cart_space()->install_write_handler(0xa000, 0xa000, 0x0000, 0x1ffe, 0x0000, write8smo_delegate(*this, FUNC(tama5_device::data_w))); + cart_space()->install_write_handler(0xa001, 0xa001, 0x0000, 0x1ffe, 0x0000, write8smo_delegate(*this, FUNC(tama5_device::command_w))); + + // all good + return image_init_result::PASS; +} + + +void tama5_device::device_start() +{ + mbc_device_base::device_start(); + + save_item(NAME(m_bank_sel_rom)); + save_item(NAME(m_registers)); + save_item(NAME(m_response)); + save_item(NAME(m_command)); + save_item(NAME(m_data)); + save_item(NAME(m_address)); +} + + +void tama5_device::device_reset() +{ + mbc_device_base::device_reset(); + + m_bank_sel_rom = 1; + + std::fill(std::begin(m_registers), std::end(m_registers), 0xffU); + m_response = 0xffU; + m_command = 0x00U; + m_data = 0x00U; + m_address = 0x00U; + + set_bank_rom(m_bank_sel_rom); +} + + +u8 tama5_device::data_r(address_space &space) +{ + return (space.unmap() & 0xf0) | (m_response & 0x0f); +} + + +void tama5_device::data_w(u8 data) +{ + switch (m_command) + { + case 0x00: // ROM bank low + m_bank_sel_rom = (m_bank_sel_rom & 0x10) | (data & 0x0f); + set_bank_rom(m_bank_sel_rom); + break; + case 0x01: // ROM bank high + m_bank_sel_rom = (m_bank_sel_rom & 0x0f) | ((data & 0x01) << 4); + set_bank_rom(m_bank_sel_rom); + break; + case 0x04: // value low + m_data = (m_data & 0xf0) | (data & 0x0f); + break; + case 0x05: // value high + m_data = (m_data & 0x0f) | (data << 4); + break; + case 0x06: // address high + m_address = (m_address & 0x0f) | (data << 4); + break; + case 0x07: // address low/trigger + m_address = (m_address & 0xf0) | (data & 0x0f); + switch (m_address & 0xe0) + { + case 0x00: + LOG("%s: write register 0x%02X = 0x%02X\n", machine().describe_context(), m_address & 0x1f, m_data); + m_registers[m_address & 0x1f] = m_data; + break; + case 0x20: + LOG("%s: read register 0x%02X\n", machine().describe_context(), m_address & 0x1f); + m_data = m_registers[m_address & 0x1f]; + break; + case 0x40: // unknown - some kind of read + if ((m_address & 0x1f) == 0x12) + m_data = 0xff; + [[fallthrough]]; + case 0x80: // unknown - some kind of read when 0x07=0x01, or write when 0x07=0x00 or 0x07=0x02 + default: + logerror("%s: Unknown access 0x%02X = 0x%02X\n", machine().describe_context(), m_address, m_data); + } + } +} + + +void tama5_device::command_w(u8 data) +{ + switch (data) + { + case 0x00: // ROM bank low + case 0x01: // ROM bank high + case 0x04: // value low + case 0x05: // value high + case 0x06: // address high + case 0x07: // address low/trigger + break; + case 0x0a: // ready status in least significant bit + m_response = 0x01U; + break; + case 0x0c: // read value low + m_response = m_data & 0x0f; + break; + case 0x0d: // read value high + m_response = m_data >> 4; + break; + default: + logerror("%s: Unknown command 0x%02X\n", machine().describe_context(), data); + } + m_command = data; +} + +} // anonymous namespace + +} // namespace bus::gameboy + + +DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_TAMA5, device_gb_cart_interface, bus::gameboy::tama5_device, "gb_rom_tama5", "Game Boy Bandai Tamagotchi Cartridge") diff --git a/src/devices/bus/gameboy/tama5.h b/src/devices/bus/gameboy/tama5.h new file mode 100644 index 00000000000..26187fcede6 --- /dev/null +++ b/src/devices/bus/gameboy/tama5.h @@ -0,0 +1,11 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb, Wilbert Pol +#ifndef MAME_BUS_GAMEBOY_TAMA5_H +#define MAME_BUS_GAMEBOY_TAMA5_H + +#include "slot.h" + + +DECLARE_DEVICE_TYPE(GB_ROM_TAMA5, device_gb_cart_interface) + +#endif // MAME_BUS_GAMEBOY_TAMA5_H diff --git a/src/devices/bus/psx/gamebooster.cpp b/src/devices/bus/psx/gamebooster.cpp index 1d2f6529dca..1c414ca41a2 100644 --- a/src/devices/bus/psx/gamebooster.cpp +++ b/src/devices/bus/psx/gamebooster.cpp @@ -12,9 +12,15 @@ #include "gamebooster.h" #include "bus/gameboy/carts.h" +#include "bus/gameboy/gbslot.h" #include "softlist_dev.h" +#include + +//#define VERBOSE 1 +#include "logmacro.h" + //************************************************************************** // DEVICE DEFINITIONS @@ -27,18 +33,10 @@ DEFINE_DEVICE_TYPE(PSX_GAMEBOOSTER, psx_gamebooster_device, "psxgboost", "Datel //------------------------------------------------- ROM_START( psxgboost ) - ROM_REGION(0x40000, "rom", 0) + ROM_REGION16_LE(0x40000, "rom", 0) ROM_LOAD("game booster.rom", 0x0000, 0x40000, CRC(c8e459b8) SHA1(c20ab073f61242f37665f12199b95cfa3a83e9fc) ) ROM_END -//------------------------------------------------- -// rom_region - device-specific ROM region -//------------------------------------------------- - -const tiny_rom_entry *psx_gamebooster_device::device_rom_region() const -{ - return ROM_NAME( psxgboost ); -} //************************************************************************** // LIVE DEVICE @@ -51,53 +49,77 @@ const tiny_rom_entry *psx_gamebooster_device::device_rom_region() const psx_gamebooster_device::psx_gamebooster_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, PSX_GAMEBOOSTER, tag, owner, clock) , psx_parallel_interface(mconfig, *this) + , device_memory_interface(mconfig, *this) , m_rom(*this, "rom") - , m_cartslot(*this, "gbslot") + , m_cart_config("cart", ENDIANNESS_LITTLE, 8, 16, 0) { } -//------------------------------------------------- -// device_start - device-specific startup -//------------------------------------------------- -void psx_gamebooster_device::device_start() +//************************************************************************** +// DEVICE_T IMPLEMENTATION +//************************************************************************** + +const tiny_rom_entry *psx_gamebooster_device::device_rom_region() const { + return ROM_NAME( psxgboost ); } -//------------------------------------------------- -// device_reset - device-specific reset -//------------------------------------------------- +void psx_gamebooster_device::device_add_mconfig(machine_config &config) +{ + GB_CART_SLOT(config, "gbslot", gameboy_cartridges, nullptr).set_space(DEVICE_SELF, 0); + + SOFTWARE_LIST(config, "cart_list").set_original("gameboy"); + SOFTWARE_LIST(config, "gbc_list").set_compatible("gbcolor"); +} + +void psx_gamebooster_device::device_start() +{ + space(0).specific(m_cart_space); +} void psx_gamebooster_device::device_reset() { } + +//************************************************************************** +// DEVICE_MEMORY_INTERFACE IMPLEMENTATION +//************************************************************************** + +device_memory_interface::space_config_vector psx_gamebooster_device::memory_space_config() const +{ + return space_config_vector{ std::make_pair(0, &m_cart_config) }; +} + + //************************************************************************** -// IMPLEMENTATION +// PSX_PARALLEL_INTERFACE IMPLEMENTATION //************************************************************************** uint16_t psx_gamebooster_device::exp_r(offs_t offset, uint16_t mem_mask) { if (offset < 0x20000) { - return m_rom->base()[(offset * 2) & 0x3ffff] | (m_rom->base()[((offset * 2) + 1) & 0x3ffff] << 8); + return m_rom[offset & 0x1ffff]; } else if (offset < 0x24000) { offset -= 0x20000; uint16_t retval = 0; - if (mem_mask & 0xff00) retval |= (m_cartslot->read_rom((offset*2)+1))<<8; - if (mem_mask & 0x00ff) retval |= m_cartslot->read_rom((offset*2)+0); + + if (mem_mask & 0xff00) retval |= uint16_t(m_cart_space.read_byte((offset << 1) + 1)) << 8; + if (mem_mask & 0x00ff) retval |= uint16_t(m_cart_space.read_byte((offset << 1) + 0)) << 0; return retval; } else { - logerror("%s: psx_gamebooster_device::exp_r %04x\n", machine().describe_context(), offset*2); - } + logerror("%s: psx_gamebooster_device::exp_r %04x\n", machine().describe_context(), offset << 1); - return 0x0000; + return 0x0000; + } } void psx_gamebooster_device::exp_w(offs_t offset, uint16_t data, uint16_t mem_mask) @@ -105,28 +127,18 @@ void psx_gamebooster_device::exp_w(offs_t offset, uint16_t data, uint16_t mem_ma if (offset < 0x20000) { - logerror("%s: psx_gamebooster_device::exp_w %04x %04x\n", machine().describe_context(), offset*2, data); + logerror("%s: psx_gamebooster_device::exp_w %04x %04x\n", machine().describe_context(), offset << 1, data); } else if (offset < 0x24000) { offset -= 0x20000; - logerror("%s: psx_gamebooster_device::exp_w %04x %04x\n", machine().describe_context(), offset*2, data); - - if (mem_mask & 0xff00) m_cartslot->write_bank((offset*2)+1, data>>8); - if (mem_mask & 0x00ff) m_cartslot->write_bank((offset*2)+0, data); // send this 2nd or it erases the bank with the above + LOG("%s: psx_gamebooster_device::exp_w %04x %04x\n", machine().describe_context(), offset << 1, data); + if (mem_mask & 0xff00) m_cart_space.write_byte((offset << 1) + 1, data >> 8); + if (mem_mask & 0x00ff) m_cart_space.write_byte((offset << 1) + 0, data >> 0); // send this 2nd or it erases the bank with the above } else { - logerror("%s: psx_gamebooster_device::exp_w %04x %04x\n", machine().describe_context(), offset*2, data); + logerror("%s: psx_gamebooster_device::exp_w %04x %04x\n", machine().describe_context(), offset << 1, data); } } - -void psx_gamebooster_device::device_add_mconfig(machine_config &config) -{ - // cartslot - GB_CART_SLOT(config, m_cartslot, gameboy_cartridges, nullptr); - - SOFTWARE_LIST(config, "cart_list").set_original("gameboy"); - SOFTWARE_LIST(config, "gbc_list").set_compatible("gbcolor"); -} diff --git a/src/devices/bus/psx/gamebooster.h b/src/devices/bus/psx/gamebooster.h index e023c9983ad..9e990f726d5 100644 --- a/src/devices/bus/psx/gamebooster.h +++ b/src/devices/bus/psx/gamebooster.h @@ -8,8 +8,6 @@ #include "parallel.h" -#include "bus/gameboy/gb_slot.h" - //************************************************************************** // TYPE DEFINITIONS @@ -19,31 +17,36 @@ class psx_gamebooster_device : public device_t, - public psx_parallel_interface + public psx_parallel_interface, + public device_memory_interface { public: // construction/destruction psx_gamebooster_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); -protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; + // psx_parallel_interface implementation + virtual uint16_t exp_r(offs_t offset, uint16_t mem_mask = ~0) override; + virtual void exp_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; - // optional information overrides +protected: + // device_t implementation virtual const tiny_rom_entry *device_rom_region() const override; virtual void device_add_mconfig(machine_config &config) override; + virtual void device_start() override; + virtual void device_reset() override; - virtual uint16_t exp_r(offs_t offset, uint16_t mem_mask = ~0) override; - virtual void exp_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; + // device_memory_interface implementation + virtual space_config_vector memory_space_config() const override; private: - required_memory_region m_rom; - required_device m_cartslot; + required_region_ptr m_rom; + + const address_space_config m_cart_config; + memory_access<16, 0, 0, ENDIANNESS_LITTLE>::specific m_cart_space; }; -// device type definition +// device type declaration DECLARE_DEVICE_TYPE(PSX_GAMEBOOSTER, psx_gamebooster_device) #endif // MAME_BUS_PSX_GAMEBOOSTER_H diff --git a/src/devices/bus/snes/sgb.cpp b/src/devices/bus/snes/sgb.cpp index 56da7025c66..8ce9ebfab65 100644 --- a/src/devices/bus/snes/sgb.cpp +++ b/src/devices/bus/snes/sgb.cpp @@ -32,7 +32,9 @@ sns_rom_sgb_device::sns_rom_sgb_device(const machine_config &mconfig, device_typ m_sgb_apu(*this, "sgb_apu"), m_sgb_ppu(*this, "sgb_ppu"), m_cartslot(*this, "gb_slot"), - m_region_bios(*this, "sgb_cpu"), + m_region_bootstrap(*this, "sgb_cpu"), + m_view_bootstrap(*this, "sgb_boot"), + m_bootstrap_installed(false), m_sgb_ly(0), m_sgb_row(0), m_vram(0), @@ -43,8 +45,7 @@ sns_rom_sgb_device::sns_rom_sgb_device(const machine_config &mconfig, device_typ m_joy4(0), m_vram_offs(0), m_lcd_row(0), - m_packetsize(0), - m_bios_disabled(false) + m_packetsize(0) { } @@ -63,10 +64,19 @@ sns_rom_sgb2_device::sns_rom_sgb2_device(const machine_config& mconfig, const ch void sns_rom_sgb_device::device_start() { + m_bootstrap_installed = false; } void sns_rom_sgb_device::device_reset() { + // need to install the bootstrap view over the top of the cartridge, which installs *after* start + if (!m_bootstrap_installed) + { + m_sgb_cpu->space(AS_PROGRAM).install_view(0x0000, 0x00ff, m_view_bootstrap); + m_view_bootstrap[0].install_rom(0x0000, 0x00ff, &m_region_bootstrap[0]); + m_bootstrap_installed = true; + } + m_view_bootstrap.select(0); } @@ -77,40 +87,6 @@ void sns_rom_sgb_device::device_reset() // ADDRESS_MAP( supergb_map ) //------------------------------------------------- -uint8_t sns_rom_sgb_device::gb_cart_r(offs_t offset) -{ - if (offset < 0x100 && !m_bios_disabled) - { - return m_region_bios->base()[offset]; - } - return m_cartslot->read_rom(offset); -} - -void sns_rom_sgb_device::gb_bank_w(offs_t offset, uint8_t data) -{ - m_cartslot->write_bank(offset, data); -} - -uint8_t sns_rom_sgb_device::gb_ram_r(offs_t offset) -{ - return m_cartslot->read_ram(offset); -} - -void sns_rom_sgb_device::gb_ram_w(offs_t offset, uint8_t data) -{ - m_cartslot->write_ram(offset, data); -} - -uint8_t sns_rom_sgb_device::gb_echo_r(offs_t offset) -{ - return m_sgb_cpu->space(AS_PROGRAM).read_byte(0xc000 + offset); -} - -void sns_rom_sgb_device::gb_echo_w(offs_t offset, uint8_t data) -{ - return m_sgb_cpu->space(AS_PROGRAM).write_byte(0xc000 + offset, data); -} - uint8_t sns_rom_sgb_device::gb_io_r(offs_t offset) { return 0; @@ -135,19 +111,18 @@ void sns_rom_sgb_device::gb_ie_w(offs_t offset, uint8_t data) void sns_rom_sgb_device::supergb_map(address_map &map) { map.unmap_value_high(); - map(0x0000, 0x7fff).rw(FUNC(sns_rom_sgb_device::gb_cart_r), FUNC(sns_rom_sgb_device::gb_bank_w)); - map(0x8000, 0x9fff).rw("sgb_ppu", FUNC(sgb_ppu_device::vram_r), FUNC(sgb_ppu_device::vram_w)); /* 8k VRAM */ - map(0xa000, 0xbfff).rw(FUNC(sns_rom_sgb_device::gb_ram_r), FUNC(sns_rom_sgb_device::gb_ram_w)); /* 8k switched RAM bank (cartridge) */ - map(0xc000, 0xdfff).ram(); /* 8k low RAM */ - map(0xe000, 0xfdff).rw(FUNC(sns_rom_sgb_device::gb_echo_r), FUNC(sns_rom_sgb_device::gb_echo_w)); - map(0xfe00, 0xfeff).rw("sgb_ppu", FUNC(sgb_ppu_device::oam_r), FUNC(sgb_ppu_device::oam_w)); /* OAM RAM */ - map(0xff00, 0xff0f).rw(FUNC(sns_rom_sgb_device::gb_io_r), FUNC(sns_rom_sgb_device::gb_io_w)); /* I/O */ - map(0xff10, 0xff26).rw("sgb_apu", FUNC(gameboy_sound_device::sound_r), FUNC(gameboy_sound_device::sound_w)); /* sound registers */ - map(0xff27, 0xff2f).noprw(); /* unused */ - map(0xff30, 0xff3f).rw("sgb_apu", FUNC(gameboy_sound_device::wave_r), FUNC(gameboy_sound_device::wave_w)); /* Wave RAM */ - map(0xff40, 0xff7f).rw("sgb_ppu", FUNC(sgb_ppu_device::video_r), FUNC(sgb_ppu_device::video_w)); /* also disable bios?? */ /* Video controller & BIOS flip-flop */ - map(0xff80, 0xfffe).ram(); /* High RAM */ - map(0xffff, 0xffff).rw(FUNC(sns_rom_sgb_device::gb_ie_r), FUNC(sns_rom_sgb_device::gb_ie_w)); /* Interrupt enable register */ + // cartridge ROM and memory controller goes here + map(0x8000, 0x9fff).rw(m_sgb_ppu, FUNC(sgb_ppu_device::vram_r), FUNC(sgb_ppu_device::vram_w)); // 8k VRAM + // cartridge RAM and/or I/O goes here + map(0xc000, 0xdfff).mirror(0x2000).ram(); // 8k low RAM + map(0xfe00, 0xfeff).rw(m_sgb_ppu, FUNC(sgb_ppu_device::oam_r), FUNC(sgb_ppu_device::oam_w)); // OAM RAM + map(0xff00, 0xff0f).rw(FUNC(sns_rom_sgb_device::gb_io_r), FUNC(sns_rom_sgb_device::gb_io_w)); // I/O + map(0xff10, 0xff26).rw(m_sgb_apu, FUNC(gameboy_sound_device::sound_r), FUNC(gameboy_sound_device::sound_w)); /* sound registers */ + map(0xff27, 0xff2f).noprw(); // unused + map(0xff30, 0xff3f).rw(m_sgb_apu, FUNC(gameboy_sound_device::wave_r), FUNC(gameboy_sound_device::wave_w)); /* Wave RAM */ + map(0xff40, 0xff7f).rw(m_sgb_ppu, FUNC(sgb_ppu_device::video_r), FUNC(sgb_ppu_device::video_w)); // also disable bios?? + map(0xff80, 0xfffe).ram(); // High RAM + map(0xffff, 0xffff).rw(FUNC(sns_rom_sgb_device::gb_ie_r), FUNC(sns_rom_sgb_device::gb_ie_w)); // Interrupt enable register } @@ -169,6 +144,7 @@ void sns_rom_sgb1_device::device_add_mconfig(machine_config &config) DMG_APU(config, m_sgb_apu, 4295454); GB_CART_SLOT(config, m_cartslot, gameboy_cartridges, nullptr); + m_cartslot->set_space(m_sgb_cpu, AS_PROGRAM); } @@ -196,6 +172,7 @@ void sns_rom_sgb2_device::device_add_mconfig(machine_config &config) DMG_APU(config, m_sgb_apu, XTAL(4'194'304)); GB_CART_SLOT(config, m_cartslot, gameboy_cartridges, nullptr); + m_cartslot->set_space(m_sgb_cpu, AS_PROGRAM); } @@ -351,5 +328,4 @@ void sns_rom_sgb_device::chip_write(offs_t offset, uint8_t data) m_joy4 = data; return; } - } diff --git a/src/devices/bus/snes/sgb.h b/src/devices/bus/snes/sgb.h index f73cc42cacb..46d600a704b 100644 --- a/src/devices/bus/snes/sgb.h +++ b/src/devices/bus/snes/sgb.h @@ -9,7 +9,7 @@ #include "rom.h" #include "cpu/lr35902/lr35902.h" -#include "bus/gameboy/gb_slot.h" +#include "bus/gameboy/gbslot.h" #include "video/gb_lcd.h" #include "sound/gb.h" @@ -35,12 +35,6 @@ protected: virtual uint8_t chip_read(offs_t offset) override; virtual void chip_write(offs_t offset, uint8_t data) override; - uint8_t gb_cart_r(offs_t offset); - void gb_bank_w(offs_t offset, uint8_t data); - uint8_t gb_ram_r(offs_t offset); - void gb_ram_w(offs_t offset, uint8_t data); - uint8_t gb_echo_r(offs_t offset); - void gb_echo_w(offs_t offset, uint8_t data); uint8_t gb_io_r(offs_t offset); void gb_io_w(offs_t offset, uint8_t data); uint8_t gb_ie_r(offs_t offset); @@ -54,10 +48,12 @@ protected: required_device m_cartslot; private: - required_memory_region m_region_bios; - void lcd_render(uint32_t *source); + required_region_ptr m_region_bootstrap; + memory_view m_view_bootstrap; + bool m_bootstrap_installed; + // ICD2 regs uint8_t m_sgb_ly; uint8_t m_sgb_row; @@ -74,8 +70,6 @@ private: // input bits int m_packetsize; uint8_t m_packet_data[64][16]; - - bool m_bios_disabled; }; diff --git a/src/mame/nintendo/gb.cpp b/src/mame/nintendo/gb.cpp index 82e75be4377..704df9789ad 100644 --- a/src/mame/nintendo/gb.cpp +++ b/src/mame/nintendo/gb.cpp @@ -17,27 +17,13 @@ - SGB should be moved to SNES driver - Emulate OAM corruption bug on 16bit inc/dec in $fe** region - -Mappers used in the Game Boy -=========================== - -HuC1 mapper -=========== - -Status: not supported yet. - - -HuC3 mapper -=========== - -Status: not supported yet. - ***************************************************************************/ #include "emu.h" #include "bus/gameboy/carts.h" -#include "bus/gameboy/gb_slot.h" +#include "bus/gameboy/gbslot.h" +#include "bus/gameboy/mdslot.h" #include "cpu/lr35902/lr35902.h" #include "machine/ram.h" #include "sound/gb.h" @@ -129,6 +115,7 @@ public: gb_state(const machine_config &mconfig, device_type type, const char *tag) : base_state(mconfig, type, tag), m_region_boot(*this, "maincpu"), + m_boot_view(*this, "boot"), m_bios_hack(*this, "SKIP_CHECK") { } @@ -136,33 +123,25 @@ public: void gbpocket(machine_config &config); protected: - virtual void device_post_load() override; - virtual void machine_start() override; virtual void machine_reset() override; - virtual void install_boot(); - virtual void uninstall_boot(); - void disable_boot(); void gb_io2_w(offs_t offset, uint8_t data); required_region_ptr m_region_boot; + memory_view m_boot_view; private: + u8 boot_r(offs_t offset); + void gb_palette(palette_device &palette) const; void gbp_palette(palette_device &palette) const; void gameboy_map(address_map &map); required_ioport m_bios_hack; - - util::notifier_subscription m_prog_notifier; - memory_passthrough_handler m_boot_tap; - - bool m_boot_enabled = false; - bool m_installing_boot = false; }; @@ -213,9 +192,6 @@ protected: virtual void machine_start() override; virtual void machine_reset() override; - virtual void install_boot() override; - virtual void uninstall_boot() override; - private: static constexpr XTAL GBC_CLOCK = 8.388_MHz_XTAL; @@ -275,19 +251,6 @@ void base_state::gb_init_regs() } -void gb_state::device_post_load() -{ - base_state::device_post_load(); - - m_installing_boot = true; - if (m_boot_enabled) - install_boot(); - else - uninstall_boot(); - m_installing_boot = false; -} - - void base_state::machine_start() { save_item(NAME(m_gb_io)); @@ -297,37 +260,22 @@ void base_state::machine_start() save_item(NAME(m_triggering_irq)); save_item(NAME(m_reloading)); save_item(NAME(m_sio_count)); - - m_cartslot->save_ram(); } void gb_state::machine_start() { base_state::machine_start(); - m_boot_enabled = false; - m_installing_boot = false; - m_prog_notifier = m_maincpu->space(AS_PROGRAM).add_change_notifier( - [this] (read_or_write mode) - { - if (!m_installing_boot && (uint32_t(mode) & uint32_t(read_or_write::READ))) - { - m_installing_boot = true; - if (m_boot_enabled) - install_boot(); - else - uninstall_boot(); - m_installing_boot = false; - } - }); - - save_item(NAME(m_boot_enabled)); + m_maincpu->space(AS_PROGRAM).install_view(0x0000, 0x08ff, m_boot_view); + m_boot_view[0].install_read_handler(0x0000, 0x00ff, read8sm_delegate(*this, NAME(&gb_state::boot_r))); } void gbc_state::machine_start() { gb_state::machine_start(); + m_boot_view[0].install_rom(0x0200, 0x08ff, &m_region_boot[0x100]); + m_rambank->configure_entry(0, &m_bankedram[0]); m_rambank->configure_entries(1, 7, &m_bankedram[0], 0x1000); } @@ -372,10 +320,7 @@ void gb_state::machine_reset() { base_state::machine_reset(); - m_installing_boot = true; - m_boot_enabled = true; - install_boot(); - m_installing_boot = false; + m_boot_view.select(0); } void gbc_state::machine_reset() @@ -395,63 +340,9 @@ void sgb_state::machine_reset() } -void gb_state::install_boot() -{ - m_boot_tap.remove(); - m_boot_tap = m_maincpu->space(AS_PROGRAM).install_read_tap( - 0x0000, 0x00ff, - "boot_r", - [this] (offs_t offset, u8 &data, u8 mem_mask) - { - data = m_region_boot[offset]; - if (m_bios_hack->read()) - { - // patch out logo and checksum checks - // useful to run some pirate carts until properly emulated, or to test homebrew - if (offset == 0xe9 || offset == 0xea) - data = 0x00; - if (offset == 0xfa || offset == 0xfb) - data = 0x00; - } - }, - &m_boot_tap); -} - -void gbc_state::install_boot() -{ - gb_state::install_boot(); - - m_boot_high_tap.remove(); - m_boot_high_tap = m_maincpu->space(AS_PROGRAM).install_read_tap( - 0x0200, 0x08ff, - "boot_high_r", - [this] (offs_t offset, u8 &data, u8 mem_mask) - { - data = m_region_boot[0x0100 + offset - 0x0200]; - }, - &m_boot_high_tap); -} - - -void gb_state::uninstall_boot() -{ - m_boot_tap.remove(); -} - -void gbc_state::uninstall_boot() -{ - gb_state::uninstall_boot(); - - m_boot_high_tap.remove(); -} - - void gb_state::disable_boot() { - m_installing_boot = true; - m_boot_enabled = false; - uninstall_boot(); - m_installing_boot = false; + m_boot_view.disable(); } @@ -545,6 +436,20 @@ void gb_state::gb_io2_w(offs_t offset, uint8_t data) m_ppu->video_w(offset, data); } +u8 gb_state::boot_r(offs_t offset) +{ + if (m_bios_hack->read()) + { + // patch out logo and checksum checks + // useful to run some pirate carts until properly emulated, or to test homebrew + if (offset == 0xe9 || offset == 0xea) + return 0x00; + if (offset == 0xfa || offset == 0xfb) + return 0x00; + } + return m_region_boot[offset]; +} + #ifdef MAME_DEBUG static const char *const sgbcmds[32] = { @@ -994,9 +899,7 @@ uint8_t megaduck_state::megaduck_sound_r2(offs_t offset) void gb_state::gameboy_map(address_map &map) { map.unmap_value_high(); - map(0x0000, 0x7fff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_rom), FUNC(gb_cart_slot_device::write_bank)); map(0x8000, 0x9fff).rw(m_ppu, FUNC(dmg_ppu_device::vram_r), FUNC(dmg_ppu_device::vram_w)); - map(0xa000, 0xbfff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_ram), FUNC(gb_cart_slot_device::write_ram)); map(0xc000, 0xdfff).mirror(0x2000).ram(); map(0xfe00, 0xfeff).rw(m_ppu, FUNC(dmg_ppu_device::oam_r), FUNC(dmg_ppu_device::oam_w)); map(0xff00, 0xff0f).rw(FUNC(gb_state::gb_io_r), FUNC(gb_state::gb_io_w)); @@ -1011,9 +914,7 @@ void gb_state::gameboy_map(address_map &map) void sgb_state::sgb_map(address_map &map) { map.unmap_value_high(); - map(0x0000, 0x7fff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_rom), FUNC(gb_cart_slot_device::write_bank)); map(0x8000, 0x9fff).rw(m_ppu, FUNC(sgb_ppu_device::vram_r), FUNC(sgb_ppu_device::vram_w)); - map(0xa000, 0xbfff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_ram), FUNC(gb_cart_slot_device::write_ram)); map(0xc000, 0xdfff).mirror(0x2000).ram(); map(0xfe00, 0xfeff).rw(m_ppu, FUNC(sgb_ppu_device::oam_r), FUNC(sgb_ppu_device::oam_w)); map(0xff00, 0xff0f).rw(FUNC(sgb_state::gb_io_r), FUNC(sgb_state::sgb_io_w)); @@ -1028,9 +929,7 @@ void sgb_state::sgb_map(address_map &map) void gbc_state::gbc_map(address_map &map) { map.unmap_value_high(); - map(0x0000, 0x7fff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_rom), FUNC(gb_cart_slot_device::write_bank)); map(0x8000, 0x9fff).rw(m_ppu, FUNC(cgb_ppu_device::vram_r), FUNC(cgb_ppu_device::vram_w)); - map(0xa000, 0xbfff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_ram), FUNC(gb_cart_slot_device::write_ram)); map(0xc000, 0xcfff).mirror(0x2000).ram(); map(0xd000, 0xdfff).mirror(0x2000).bankrw(m_rambank); map(0xfe00, 0xfeff).rw(m_ppu, FUNC(cgb_ppu_device::oam_r), FUNC(cgb_ppu_device::oam_w)); @@ -1046,11 +945,7 @@ void gbc_state::gbc_map(address_map &map) void megaduck_state::megaduck_map(address_map &map) { map.unmap_value_high(); - map(0x0000, 0x7fff).rw(m_cartslot, FUNC(gb_cart_slot_device::read_rom), FUNC(gb_cart_slot_device::write_bank)); map(0x8000, 0x9fff).rw(m_ppu, FUNC(dmg_ppu_device::vram_r), FUNC(dmg_ppu_device::vram_w)); - map(0xa000, 0xafff).noprw(); // unused? - map(0xb000, 0xb000).w(m_cartslot, FUNC(gb_cart_slot_device::write_ram)); // used for bank switch - map(0xb001, 0xbfff).noprw(); // unused? map(0xc000, 0xfdff).ram(); // 8k or 16k? RAM map(0xfe00, 0xfeff).rw(m_ppu, FUNC(dmg_ppu_device::oam_r), FUNC(dmg_ppu_device::oam_w)); map(0xff00, 0xff0f).rw(FUNC(megaduck_state::gb_io_r), FUNC(megaduck_state::gb_io_w)); @@ -1173,6 +1068,7 @@ void gb_state::gameboy(machine_config &config) // cartslot GB_CART_SLOT(config, m_cartslot, gameboy_cartridges, nullptr); + m_cartslot->set_space(m_maincpu, AS_PROGRAM); SOFTWARE_LIST(config, "cart_list").set_original("gameboy"); SOFTWARE_LIST(config, "gbc_list").set_compatible("gbcolor"); @@ -1211,6 +1107,7 @@ void sgb_state::supergb(machine_config &config) // cartslot GB_CART_SLOT(config, m_cartslot, gameboy_cartridges, nullptr); + m_cartslot->set_space(m_maincpu, AS_PROGRAM); SOFTWARE_LIST(config, "cart_list").set_original("gameboy"); SOFTWARE_LIST(config, "gbc_list").set_compatible("gbcolor"); @@ -1273,6 +1170,7 @@ void gbc_state::gbcolor(machine_config &config) // cartslot GB_CART_SLOT(config, m_cartslot, gameboy_cartridges, nullptr); + m_cartslot->set_space(m_maincpu, AS_PROGRAM); SOFTWARE_LIST(config, "cart_list").set_original("gbcolor"); SOFTWARE_LIST(config, "gb_list").set_compatible("gameboy"); @@ -1309,6 +1207,8 @@ void megaduck_state::megaduck(machine_config &config) // cartslot MEGADUCK_CART_SLOT(config, m_cartslot, megaduck_cartridges, nullptr); + m_cartslot->set_space(m_maincpu, AS_PROGRAM); + SOFTWARE_LIST(config, "cart_list").set_original("megaduck"); } -- cgit v1.2.3