diff --git a/hash/megadriv.xml b/hash/megadriv.xml
index 28f26b8f1f917..374e275741919 100644
--- a/hash/megadriv.xml
+++ b/hash/megadriv.xml
@@ -1156,7 +1156,8 @@ The three available regions are PAL, NTSC-U and NTSC-J or a combination such as
1993
Electronic Arts
-
+
+
@@ -1781,13 +1782,10 @@ The three available regions are PAL, NTSC-U and NTSC-J or a combination such as
-
+
College Slam (USA)
1996
Acclaim Entertainment
-
@@ -3657,11 +3655,12 @@ Crashes with NTSC machines after EOA logo (verify)
1990
Sega
-
+
+
@@ -4651,6 +4650,7 @@ Moans about [Sega Mega Modem] not hooked up, punts to a red screen
1992
Electronic Arts
+
@@ -6206,19 +6206,15 @@ Unsupported [Menacer] peripheral
-
+
NFL Quarterback Club (World)
1994
Acclaim Entertainment
-
-
-
+
@@ -8200,7 +8196,7 @@ Unemulated HeartBeat peripheral
-
+
@@ -10650,7 +10646,7 @@ https://tcrf.net/World_Championship_Soccer_(Genesis)
Doesn't accept any input
]]>
-
+
@@ -10719,7 +10715,7 @@ Doesn't accept any input
-
+
@@ -10882,7 +10878,7 @@ but dumps still have to be confirmed.
Shíliù Zhāng Májiàng II (China)
- 199?
+ 1993?
City Man
@@ -10899,11 +10895,12 @@ but dumps still have to be confirmed.
199?
<unknown>
+
-
+
@@ -10913,11 +10910,12 @@ Only Flashback portion is present in the dump, the other two entries and menu ar
199?
<unknown>
+
-
+
@@ -11500,6 +11498,21 @@ Black screen after starting a game, access $be1040-3 for a 0x524e4302 value (?)
+
+ RockHeaven (hack of Alex Kidd Tenkuu Majou)
+ 199?
+ <unlicensed>
+
+
+
+
+
+
+
+
+
Alien³ (Europe, USA, rev. A)
1993
@@ -11633,7 +11646,7 @@ Black screen after starting a game, access $be1040-3 for a 0x524e4302 value (?)
- Ā Q Liánhuán Pào (Taiwan)
+ Ā Q Liánhuán Pào (Taiwan, unprotected)
1995
C&E
@@ -12512,12 +12525,15 @@ No sound (btanb?)
-
+
Barkley Shut Up and Jam! 2 (USA)
1995
Accolade
+
-
+
@@ -12709,6 +12725,7 @@ https://bootleggames.fandom.com/wiki/Barver_Battle_Saga:_Tai_Kong_Zhan_Shi
Creaton Softech
+
@@ -13646,8 +13663,10 @@ No [VDP] sprites
1995
Codemasters
+
+
-
+
@@ -13657,30 +13676,37 @@ No [VDP] sprites
1995
Codemasters
+
-
+
Brian Lara Cricket 96 (Europe, 199604)
1996
Codemasters
+
-
+
-
+
Brian Lara Cricket 96 (Europe, 199603)
1996
Codemasters
+
-
+
@@ -14998,10 +15024,10 @@ No [VDP] sprites
- Chāojí Dà Fùwēng (Taiwan)
+ Chāojí Dà Fùwēng (Taiwan, unprotected)
1994?
- Gametec
+ Gamtec
@@ -15330,7 +15356,7 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
-
+
College Football USA 96 (prototype 19950621)
1995
@@ -16488,7 +16514,7 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
-
+
CutThroat Island (prototype)
1995
@@ -16572,7 +16598,7 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
-
+
Danny Sullivan's Indy Heat (prototype) (pirate)
1992
@@ -16620,6 +16646,7 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
+
Dashin' Desperadoes (USA)
1993
@@ -16866,8 +16893,10 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
-
-
+
+
+
+
@@ -16924,8 +16953,10 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
-
-
+
+
+
+
@@ -16942,8 +16973,10 @@ https://bootleggames.fandom.com/wiki/Chaoji_Dafuweng
-
-
+
+
+
+
@@ -18736,7 +18769,7 @@ No sound in intro opening (verify)
Fēngkuáng Táohuāyuán (Taiwan)
- 199?
+ 1998?
Creaton Softech
- Fēngshén Yīngjié Chuán (Taiwan)
+ Fēngshén Yīngjié Chuán (Taiwan, unprotected)
1996
Chuanpu Technologies
@@ -19165,13 +19199,10 @@ https://segaretro.org/Feng_Shen_Ying_Jie_Chuan
-
+
Frank Thomas Big Hurt Baseball (Europe, USA)
1995
Acclaim Entertainment
-
@@ -19324,6 +19355,7 @@ https://segaretro.org/Feng_Shen_Ying_Jie_Chuan
1995
Sega
+
@@ -19892,6 +19924,23 @@ Unemulated [Sega Mega Modem] features
+
+ Gunfight 3 in 1 (Taiwan)
+ 1998?
+ <unlicensed>
+
+
+
+
+
+
+
+
+
+
+
Gynoug (Japan)
1991
@@ -19982,9 +20031,12 @@ Unemulated [Sega Mega Modem] features
- Hei Tao 2 - Super Big 2 (Taiwan)
+ Hei Tao 2 - Super Big 2 (Taiwan, unprotected)
199?
King Tec
+
@@ -20773,10 +20825,28 @@ Unemulated [Sega Mega Modem] features
-
- Jiu Ji Ma Jiang II - Ye Yan Bian (China)
+
+ Ju Ji Ma Jiang II - Ye Yan Bian (China, protected)
+ 199?
+ Sun Green
+
+
+
+
+
+
+
+
+
+
+
+
+ Jiu Ji Ma Jiang II - Ye Yan Bian (China, unprotected)
199?
<unlicensed>
+
@@ -20814,6 +20884,7 @@ Unemulated [Sega Mega Modem] features
1992
Electronic Arts
+
@@ -22644,6 +22715,7 @@ Needs [megadriv_z80_z80_bank_w] workaround to work (emulator copy protection?)
+
@@ -22878,9 +22950,12 @@ Needs [megadriv_z80_z80_bank_w] workaround to work (emulator copy protection?)
1993
FCI?
+
+
+
@@ -24954,7 +25029,7 @@ Unemulated [Sega Mega Modem] features
-
+
@@ -24966,6 +25041,7 @@ Unemulated [Sega Mega Modem] features
1991
Sega
+
@@ -25072,6 +25148,28 @@ Unemulated [Sega Mega Modem] features
+
+ Nyuushi Chokuzen Check Nanmon Kimon Kiki Kaikai (Japan, Sega Channel)
+ 1995
+ We Net
+
+
+
+
+
+
+
+
+
+
Old Towers (v1.2)
2019
@@ -25756,7 +25854,7 @@ Black screen at intro
1994
Codemasters
-
+
@@ -25768,7 +25866,7 @@ Black screen at intro
1994
Codemasters
-
+
@@ -26429,6 +26527,28 @@ Black screen
+
+ Planet Message Quiz (Japan, Sega Channel)
+ 1995
+ We Net
+
+
+
+
+
+
+
+
+
+
Pocahontas (USA)
1996
@@ -26443,7 +26563,7 @@ Black screen
Pocket Monsters
- 199?
+ 1999?
<unlicensed>
@@ -26455,7 +26575,7 @@ Black screen
Pocket Monsters (alt protection)
- 199?
+ 1999?
<unlicensed>
@@ -28150,13 +28270,14 @@ Red screen
Sānguózhì V (Taiwan)
- 199?
+ 1999?
SKOB
+
@@ -28436,11 +28557,15 @@ Throws "this game is [...] european megadrive system" even with megadriv system
-
- Shane Warne Cricket (Aus)
+
+ Shane Warne Cricket (Australia)
1997
Codemasters
+
+
@@ -28800,7 +28925,7 @@ https://segaretro.org/Shi_Jie_Zhi_Bang_Zheng_Ba_Zhan:_World_Pro_Baseball_94
- Shuǐhǔ - Fēngyún Chuán (Taiwan)
+ Shuǐhǔ - Fēngyún Chuán (Taiwan, unprotected)
1999
Never Ending Soft Team
- Shuǐhǔ Zhuàn (China)
+ Shuǐhǔ Zhuàn (China, unprotected)
1996
Chuanpu Technologies
+
@@ -29263,7 +29391,7 @@ https://bootleggames.fandom.com/wiki/Shui_Hu_Feng_Yun_Zhuan
-
+
@@ -29277,7 +29405,7 @@ https://bootleggames.fandom.com/wiki/Shui_Hu_Feng_Yun_Zhuan
-
+
@@ -29720,7 +29848,7 @@ Original scene release which was altered to include a cracktro and bypass the co
-
+
@@ -29743,7 +29871,7 @@ Original scene release which was altered to include a cracktro and bypass the co
-
+
@@ -29758,7 +29886,7 @@ Original scene release which was altered to include a cracktro and bypass the co
-
+
@@ -30118,13 +30246,10 @@ Original scene release which was altered to include a cracktro and bypass the co
1997
Tec Toy
+
@@ -31395,6 +31520,7 @@ Freezes at the start of any in-game mode
1995
Codemasters
+
@@ -31406,6 +31532,8 @@ Freezes at the start of any in-game mode
1995
Codemasters
+
+
@@ -32213,7 +32341,7 @@ No BGMs
Tekken Special
- 199?
+ 1997?
<unlicensed>
@@ -32223,8 +32351,9 @@ No BGMs
-
- Tekken 3 Special
+
+
+ Tekken 3 Special (unprotected)
199?
<unlicensed>
@@ -33044,6 +33173,7 @@ No BGMs
<unlicensed>
+
@@ -33052,10 +33182,11 @@ No BGMs
Tūnshí Tiāndì III (China)
- 199?
+ 1997?
SKOB
+
@@ -33885,6 +34016,22 @@ Has missing sound samples (i.e. tire screech, "time bonus" on checkpoints)
+
+ RockWorld (hack of Wani Wani World)
+ 199?
+ <unlicensed>
+
+
+
+
+
+
+
+
+
+
Wardner (USA)
1991
@@ -35504,18 +35651,22 @@ Black screen
-
-
+
+
+
-
+
Wùkōng Wàizhuàn (China)
1996
Ming
+
@@ -35878,13 +36029,16 @@ Black screen
-
+
Beggar Prince (rev 1) (Europe, USA)
2006
Super Fighter Team
+
-
+
@@ -35932,10 +36086,14 @@ Black screen
-
+
Yàsè Chuánshuō (China)
1995
Ming
+
@@ -35963,9 +36121,12 @@ Black screen
- Yīmén Yīngliè - Yángjiā Jiāng (China)
+ Yīmén Yīngliè - Yángjiā Jiāng (China, unprotected)
199?
<unlicensed>
+
@@ -36400,14 +36561,31 @@ Jumps to unmapped area at PC=0xffd004e8
-
- 12 in 1 (incomplete dump)
+
+ 12 in 1 (pirate)
199?
<unlicensed>
-
-
+
+
+
+
+
+
+
+
+
+ 1800 in 1 (China, pirate)
+ 199?
+ <unlicensed>
+
+
+
+
+
@@ -36425,7 +36603,7 @@ Jumps to unmapped area at PC=0xffd004e8
- A Bug's Life
+ A Bug's Life (bootleg?)
2000
<unlicensed>
@@ -36448,8 +36626,9 @@ Jumps to unmapped area at PC=0xffd004e8
+
Tenchi wo Kurau III - Sanguo Wai Chuan - Chinese Fighter (China)
- 199?
+ 1996?
<unlicensed>
- Golden 10 in 1 (Incomplete Dump)
+ Golden 10 in 1 (pirate)
199?
<unlicensed>
-
-
+
+
+
+
+
+
+
+
+
+
+ Golden Mega 250 in 1 (pirate)
+ 199?
+ <unlicensed>
+
+
+
+
@@ -36565,8 +36759,9 @@ Crashes during attract or in character select, copy protection
+
Líng Huàn Dàoshi - Super Magician (China)
- 199?
+ 1994?
Ming
@@ -36606,7 +36801,7 @@ Crashes during attract or in character select, copy protection
Jī Májiàng - Zhī Májiàng Qíngrén (China)
- 199?
+ 1995?
<unlicensed>
@@ -36619,7 +36814,7 @@ Crashes during attract or in character select, copy protection
Jī Májiàng - Zhī Májiàng Qíngrén (China, alt)
- 199?
+ 1995?
<unlicensed>
@@ -36630,9 +36825,27 @@ Crashes during attract or in character select, copy protection
+
+ Shísān Zhāng Májiàng - Zhong Guo Měi Nv Pian (Taiwan)
+ 1993
+ <unlicensed>
+
+
+
+
+
+
+
+
+
+
+
Pokemon Stadium
- 199?
+ 2000?
+
<unlicensed>
@@ -36660,7 +36873,7 @@ https://segaretro.org/Rockman_X3_(Mega_Drive)
Soul Blade
- 199?
+ 1997?
<unlicensed>
@@ -36716,8 +36929,9 @@ https://bootleggames.fandom.com/wiki/Squirrel_King
<unknown>
-
-
+
+
+
@@ -36870,19 +37084,19 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
Top Fighter 2000 MK VIII
- 199?
- <unlicensed>
+ 1999
+ X Boy
-
+
-
- Top Fighter 2005
- 199?
+
+ Top Fighter 2005 (China, unprotected)
+ 2005?
<unlicensed>
@@ -36895,6 +37109,11 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
Whac-a-Critter (USA)
1993
Realtec
+
+
@@ -37003,19 +37222,6 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
-
- Ju Ji Ma Jiang II
- 199?
- Sun Green
-
-
-
-
-
-
-
-
-
Truco '96 (Argentina, unprotected)
@@ -37045,8 +37251,8 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
TC 2000 (Argentina, unprotected)
- 199?
- <unlicensed>
+ 1995?
+ Miky
@@ -37056,8 +37262,8 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
TC 2000 (Argentina, protected)
- 199?
- <unlicensed>
+ 1995?
+ Miky
@@ -37123,7 +37329,7 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
Magic Bubble
1993
C&E
-
+
@@ -37135,7 +37341,7 @@ https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
Magic Bubble (alt)
1993
C&E
-
+
@@ -37450,9 +37656,13 @@ Unsupported lightgun or mouse?
- Dragon Ball Final Bout (China?)
+ Dragon Ball Final Bout (Taiwan, unprotected)
1998
- <unknown>
+ DVS Electronic
+
@@ -37862,8 +38072,9 @@ BGM sometimes goes silent (does several [YM2612] 68k writes without bus)
- Mighty Morphin Power Rangers - The Fighting Edition (Russia)
- 199?
+
+ Mighty Morphin Power Rangers - The Fighting Edition (Russia, unprotected)
+ 1997?
<unknown>
@@ -38214,6 +38425,8 @@ https://bootleggames.fandom.com/wiki/SpongeBob_SquarePants
<unknown>
+
+
@@ -38328,6 +38541,7 @@ https://segaretro.org/Action_Replay_(Mega_Drive)
Needs own slot type with mountable carts
]]>
+
@@ -38657,4 +38871,21 @@ Crashes at startup with megadriv (verify)
+
+ SSF Extended test (v2)
+ 2014
+ krikzz
+
+
+
+
+
+
+
+
+
+
diff --git a/hash/supracan.xml b/hash/supracan.xml
index 0791bcfa6ee56..5521786a133b4 100644
--- a/hash/supracan.xml
+++ b/hash/supracan.xml
@@ -97,7 +97,7 @@ Broken [video] during intro, uses bitmap mode with ROZ layer
Super Taiwanese Baseball League ~ Chao Ji Zhong Hua Zhi Bang Lian Meng
1995
- C&E Soft
+ C&E
clock(), megadrive_cart_options, nullptr).set_must_be_loaded(false);
+}
+
+void megadrive_action_replay_device::device_start()
+{
+ megadrive_rom_device::device_start();
+}
+
+void megadrive_action_replay_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ m_ar_view.select(0);
+}
+
+void megadrive_action_replay_device::unlock_cart_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (data == 0xffff)
+ {
+ m_ar_view.disable();
+ }
+}
+
+void megadrive_action_replay_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x7f'ffff).rw(m_lockon_cart, FUNC(megadrive_cart_slot_device::base_r), FUNC(megadrive_cart_slot_device::base_w));
+ map(0x00'0000, 0x01'ffff).view(m_ar_view);
+ m_ar_view[0](0x00'0000, 0x00'7fff).mirror(0x00'8000).bankr(m_rom);
+ m_ar_view[0](0x01'0006, 0x01'0007).w(FUNC(megadrive_action_replay_device::unlock_cart_w));
+}
+
+void megadrive_action_replay_device::time_io_map(address_map &map)
+{
+ map(0x00, 0xff).rw(m_lockon_cart, FUNC(megadrive_cart_slot_device::time_r), FUNC(megadrive_cart_slot_device::time_w));
+}
diff --git a/src/devices/bus/megadrive/cart/action_replay.h b/src/devices/bus/megadrive/cart/action_replay.h
new file mode 100644
index 0000000000000..889d623ff9e3b
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/action_replay.h
@@ -0,0 +1,37 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_ACTION_REPLAY_H
+#define MAME_BUS_MEGADRIVE_CART_ACTION_REPLAY_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_action_replay_device : public megadrive_rom_device
+{
+public:
+ megadrive_action_replay_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+
+protected:
+ megadrive_action_replay_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ 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;
+
+ required_device m_lockon_cart;
+ memory_view m_ar_view;
+
+ void unlock_cart_w(offs_t offset, u16 data, u16 mem_mask);
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_ACTION_REPLAY, megadrive_action_replay_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_ACTION_REPLAY_H
diff --git a/src/devices/bus/megadrive/cart/avartisan.cpp b/src/devices/bus/megadrive/cart/avartisan.cpp
new file mode 100644
index 0000000000000..d585ce74ef755
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/avartisan.cpp
@@ -0,0 +1,74 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+AV Artisan/Realtec cart mappers
+
+Goofy banking scheme. Maps every bank with $7e000 page at startup, remaps in $20000 chunks
+depending on masked values between $404000 and $402000 then locks the mapper.
+
+Practically only funnywld sets these to non-zero values, everything else just writes 0 to both
+regs.
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "avartisan.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_AVARTISAN, megadrive_unl_avartisan_device, "megadrive_unl_avartisan", "Megadrive AV Artisan cart")
+
+megadrive_unl_avartisan_device::megadrive_unl_avartisan_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_UNL_AVARTISAN, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+{
+}
+
+void megadrive_unl_avartisan_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x00'2000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 0x40; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+}
+
+void megadrive_unl_avartisan_device::device_reset()
+{
+ // TODO: default
+ // GPGX uses /VRES to bypass TMSS with a read at $100, is it even possible?
+ for (int i = 0; i < 0x40; i++)
+ m_rom_bank[i]->set_entry(0x3f);
+ m_lock_config = false;
+}
+
+void megadrive_unl_avartisan_device::cart_map(address_map &map)
+{
+ for (int i = 0; i < 0x40; i++)
+ map(0x00'0000 | (i * 0x2000), 0x00'1fff | (i * 0x2000)).mirror(0x380000).bankr(m_rom_bank[i]);
+ map(0x40'2000, 0x40'2000).lw8(NAME([this] (offs_t offset, u8 data) { m_bank_size = (data & 3) << 4; }));
+ map(0x40'4000, 0x40'4000).lw8(NAME([this] (offs_t offset, u8 data) { m_bank_sel = (data & 3) << 4; }));
+ map(0x40'0000, 0x40'0000).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ // 0 -> 1 transitions
+ if (!m_lock_config && BIT(data, 0))
+ {
+ logerror("size %02x sel %02x (%02x)\n", m_bank_size, m_bank_sel, ~m_bank_size);
+ u8 bank_base = m_bank_sel & m_bank_size;
+ for (int i = 0; i < 0x40; i++)
+ {
+ m_rom_bank[i]->set_entry(((i & ~m_bank_size) | bank_base) & 0x3f);
+ }
+
+ m_lock_config = !!BIT(data, 0);
+ }
+ })
+ );
+}
+
diff --git a/src/devices/bus/megadrive/cart/avartisan.h b/src/devices/bus/megadrive/cart/avartisan.h
new file mode 100644
index 0000000000000..00de874f935c0
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/avartisan.h
@@ -0,0 +1,33 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_AVARTISAN_H
+#define MAME_BUS_MEGADRIVE_CART_AVARTISAN_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_avartisan_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_unl_avartisan_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ memory_bank_array_creator<0x40> m_rom_bank;
+ u8 m_bank_size;
+ u8 m_bank_sel;
+ bool m_lock_config;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_AVARTISAN, megadrive_unl_avartisan_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_AVARTISAN_H
diff --git a/src/devices/bus/megadrive/cart/eeprom.cpp b/src/devices/bus/megadrive/cart/eeprom.cpp
new file mode 100644
index 0000000000000..2f23abe875a8d
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/eeprom.cpp
@@ -0,0 +1,261 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Megadrive ROM + I2C EEPROM
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "eeprom.h"
+
+/*
+ * evander Evander Holyfield Boxing
+ * ddanpei Honoo no Toukyuuji - Dodge Danpei
+ * gameto Game Toshokan
+ * ghw/ghwj/ghwu Greatest Heavyweights
+ * ninjab Ninja Burai Densetsu
+ * megaman/rockman1 Megaman The Wily Wars
+ * sporttbb Sports Talk Baseball
+ * wboymw/wboy5 Wonder Boy in Monster World
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM, megadrive_eeprom_device, "megadrive_eeprom", "Megadrive ROM + I2C EEPROM cart")
+
+megadrive_eeprom_device::megadrive_eeprom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, type, tag, owner, clock)
+ , m_i2cmem(*this, "i2cmem")
+{
+}
+
+megadrive_eeprom_device::megadrive_eeprom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_device(mconfig, MEGADRIVE_EEPROM, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_device::device_add_mconfig(machine_config &config)
+{
+ I2C_X24C01(config, m_i2cmem);
+}
+
+void megadrive_eeprom_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x20'0001, 0x20'0001).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_i2cmem->read_sda();
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_i2cmem->write_scl(BIT(data, 1));
+ m_i2cmem->write_sda(BIT(data, 0));
+ })
+ );
+}
+
+/*
+ * nbajam/nbajam1/nbajamj NBA Jam
+ *
+ * Uses a 24C02, otherwise same as Sega handling
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_NBAJAM, megadrive_eeprom_nbajam_device, "megadrive_eeprom_nbajam", "Megadrive NBA Jam cart")
+
+
+megadrive_eeprom_nbajam_device::megadrive_eeprom_nbajam_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_device(mconfig, MEGADRIVE_EEPROM_NBAJAM, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_nbajam_device::device_add_mconfig(machine_config &config)
+{
+ I2C_24C02(config, m_i2cmem);
+}
+
+/*
+ * nbajamte NBA Jam TE (MD and 32x versions)
+ * nflqb NFL Quarterback Club
+ * blockb Blockbuster World Video Game Championship II
+ *
+ * Uses a 24C02, moves SCL to $200000
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_NBAJAMTE, megadrive_eeprom_nbajamte_device, "megadrive_eeprom_nbajamte", "Megadrive NBA Jam TE cart")
+
+megadrive_eeprom_nbajamte_device::megadrive_eeprom_nbajamte_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_device(mconfig, type, tag, owner, clock)
+{
+}
+
+megadrive_eeprom_nbajamte_device::megadrive_eeprom_nbajamte_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_nbajamte_device(mconfig, MEGADRIVE_EEPROM_NBAJAMTE, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_nbajamte_device::device_add_mconfig(machine_config &config)
+{
+ // nflqb uses a 24LC02B
+ I2C_24C02(config, m_i2cmem);
+}
+
+void megadrive_eeprom_nbajamte_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x20'0000, 0x20'0000).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_i2cmem->write_scl(BIT(data, 0));
+ })
+ );
+ map(0x20'0001, 0x20'0001).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_i2cmem->read_sda();
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_i2cmem->write_sda(BIT(data, 0));
+ })
+ );
+}
+
+/*
+ * nflqb96 NFL Quarterback Club 96
+ *
+ * Same as NBA Jam TE with a '16 in place of the '02
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_NFLQB96, megadrive_eeprom_nflqb96_device, "megadrive_eeprom_nflqb96", "Megadrive NFL Quarterback Club 96 cart")
+
+megadrive_eeprom_nflqb96_device::megadrive_eeprom_nflqb96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_nbajamte_device(mconfig, MEGADRIVE_EEPROM_NFLQB96, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_nflqb96_device::device_add_mconfig(machine_config &config)
+{
+ I2C_24C16(config, m_i2cmem);
+}
+
+/*
+ * collslam College Slam
+ * bighurt Frank Thomas Big Hurt Baseball
+ *
+ * Bump of nflqb96 with a '65 in place of the '16
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_COLLSLAM, megadrive_eeprom_collslam_device, "megadrive_eeprom_collslam", "Megadrive College Slam cart")
+
+megadrive_eeprom_collslam_device::megadrive_eeprom_collslam_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_nbajamte_device(mconfig, MEGADRIVE_EEPROM_COLLSLAM, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_collslam_device::device_add_mconfig(machine_config &config)
+{
+ I2C_24C65(config, m_i2cmem);
+}
+
+/*
+ * nhlpa93 NHLPA Hockey 93
+ * ringspow Rings of Power
+ *
+ * '01 at bits 7-6 of $200001
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_NHLPA, megadrive_eeprom_nhlpa_device, "megadrive_eeprom_nhlpa", "Megadrive NHLPA Hockey 93 cart")
+
+megadrive_eeprom_nhlpa_device::megadrive_eeprom_nhlpa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_device(mconfig, MEGADRIVE_EEPROM_NHLPA, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_nhlpa_device::device_add_mconfig(machine_config &config)
+{
+ I2C_24C01(config, m_i2cmem);
+}
+
+void megadrive_eeprom_nhlpa_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x20'0001, 0x20'0001).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_i2cmem->read_sda() << 7;
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_i2cmem->write_sda(BIT(data, 7));
+ m_i2cmem->write_scl(BIT(data, 6));
+ })
+ );
+}
+
+
+
+/*
+ * brianlar Brian Lara Cricket
+ *
+ * Same as Codemasters J-Cart I2C implementation
+ *
+ * Guru I2C hookup notes:
+ * 1,2,3,4,7 GND
+ * 5 to LS244 pin 17
+ * 6 to PAL pin 15
+ * Output of 244 pin 17 is pin 3 which goes to ROM pin 29. It's a 42 pin ROM so pin 29 is D7
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_BLARA95, megadrive_eeprom_blara95_device, "megadrive_eeprom_blara95", "Megadrive Brian Lara Cricket 95 cart")
+
+megadrive_eeprom_blara95_device::megadrive_eeprom_blara95_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_device(mconfig, type, tag, owner, clock)
+{
+}
+
+megadrive_eeprom_blara95_device::megadrive_eeprom_blara95_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_blara95_device(mconfig, MEGADRIVE_EEPROM_BLARA95, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_blara95_device::device_add_mconfig(machine_config &config)
+{
+ I2C_24C08(config, m_i2cmem);
+}
+
+void megadrive_eeprom_blara95_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x30'0000, 0x30'0000).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_i2cmem->write_scl(BIT(data, 1));
+ m_i2cmem->write_sda(BIT(data, 0));
+ })
+ );
+ map(0x38'0001, 0x38'0001).lr8(
+ NAME([this] (offs_t offset) {
+ return m_i2cmem->read_sda() << 7;
+ })
+ );
+}
+
+/*
+ * brianl96 Brian Lara Cricket 96
+ * shanewar Shane Warne Cricket
+ *
+ * Same as Brian Lara 95, with a '65 here
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_EEPROM_BLARA96, megadrive_eeprom_blara96_device, "megadrive_eeprom_blara96", "Megadrive Brian Lara Cricket 96 cart")
+
+megadrive_eeprom_blara96_device::megadrive_eeprom_blara96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_eeprom_blara95_device(mconfig, MEGADRIVE_EEPROM_BLARA96, tag, owner, clock)
+{
+}
+
+void megadrive_eeprom_blara96_device::device_add_mconfig(machine_config &config)
+{
+ I2C_24C65(config, m_i2cmem);
+}
+
diff --git a/src/devices/bus/megadrive/cart/eeprom.h b/src/devices/bus/megadrive/cart/eeprom.h
new file mode 100644
index 0000000000000..dc8af787b9a44
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/eeprom.h
@@ -0,0 +1,113 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_EEPROM_H
+#define MAME_BUS_MEGADRIVE_CART_EEPROM_H
+
+#pragma once
+
+#include "machine/i2cmem.h"
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_eeprom_device : public megadrive_rom_device
+{
+public:
+ megadrive_eeprom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ megadrive_eeprom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ required_device m_i2cmem;
+};
+
+class megadrive_eeprom_nbajam_device : public megadrive_eeprom_device
+{
+public:
+ megadrive_eeprom_nbajam_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+class megadrive_eeprom_nbajamte_device : public megadrive_eeprom_device
+{
+public:
+ megadrive_eeprom_nbajamte_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ megadrive_eeprom_nbajamte_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+class megadrive_eeprom_nflqb96_device : public megadrive_eeprom_nbajamte_device
+{
+public:
+ megadrive_eeprom_nflqb96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+class megadrive_eeprom_collslam_device : public megadrive_eeprom_nbajamte_device
+{
+public:
+ megadrive_eeprom_collslam_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+class megadrive_eeprom_nhlpa_device : public megadrive_eeprom_device
+{
+public:
+ megadrive_eeprom_nhlpa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+
+
+class megadrive_eeprom_blara95_device : public megadrive_eeprom_device
+{
+public:
+ megadrive_eeprom_blara95_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ megadrive_eeprom_blara95_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+class megadrive_eeprom_blara96_device : public megadrive_eeprom_blara95_device
+{
+public:
+ megadrive_eeprom_blara96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM, megadrive_eeprom_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_NBAJAM, megadrive_eeprom_nbajam_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_NBAJAMTE, megadrive_eeprom_nbajamte_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_NFLQB96, megadrive_eeprom_nflqb96_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_COLLSLAM, megadrive_eeprom_collslam_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_NHLPA, megadrive_eeprom_nhlpa_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_BLARA95, megadrive_eeprom_blara95_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_EEPROM_BLARA96, megadrive_eeprom_blara96_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_EEPROM_H
diff --git a/src/devices/bus/megadrive/cart/everdrive.cpp b/src/devices/bus/megadrive/cart/everdrive.cpp
new file mode 100644
index 0000000000000..810333c2d40b6
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/everdrive.cpp
@@ -0,0 +1,235 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Everdrive-MD (first gen)
+
+TODO:
+- avoid phantom cart slot loading (does nothing, we load BIOS from here);
+- ST_M29W640FT core is incomplete (spurious unhandled writes);
+- SPI comms dislikes receiving a SS signal when full speed is selected
+\- goes 0 -> 1 -> 0, throwing a "SEL ERROR 120" if we don't guard against it;
+- Add remaining cfg_w flags;
+- Currently bases on HW spec 1.1 firmware v3, anything below that not yet supported;
+- Overlay for ROM patching (Game Genie based);
+- Reserved OS loading (A+B+C at power on, +UP for earlier carts) does nothing;
+- Understand and implement module options;
+- JTAG interface;
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "everdrive.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_EVERDRIVE, megadrive_hb_everdrive_device, "everdrive_md", "Megadrive Krikzz Everdrive-MD cart")
+
+megadrive_hb_everdrive_device::megadrive_hb_everdrive_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_HB_EVERDRIVE, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_flash(*this, "flash")
+ , m_sdcard(*this, "sdcard")
+ , m_game_mode(*this, "game_mode")
+{
+}
+
+void megadrive_hb_everdrive_device::device_add_mconfig(machine_config &config)
+{
+ // exact type is guessed by photo manipulation
+ ST_M29W640FT(config, m_flash);
+
+ SPI_SDCARD(config, m_sdcard, 0);
+ m_sdcard->set_prefer_sdhc();
+ m_sdcard->spi_miso_callback().set([this](int state) { m_in_bit = state; });
+}
+
+// 0x00000 bootloader
+// 0x10000 OS
+// 0x20000 reserve OS copy
+// 0x30000 settings
+// 0x40000-0xfffff module ROM area
+// NOTE: bootloader stores boot flags (?) at $d0000
+ROM_START( everdrive_md )
+ ROM_REGION16_BE(0x800000, "flash", ROMREGION_ERASE00)
+ ROM_LOAD16_WORD_SWAP("v35.bin", 0x00000, 0x20000, CRC(161b4d2e) SHA1(fe71de7dd1f2117409b158ccd45c68b1d6781a9c) )
+
+// ROM_LOAD16_WORD_SWAP("game.bin", 0x400000, 0x3e0000, CRC(1) SHA1(1) )
+// ROM_LOAD16_WORD_SWAP("mdos_v2.bin", 0x10000, 0x20000, CRC(b777ef96) SHA1(8efe2ec873fa4fcaddf08a2c156e540ba4a05b57) )
+// ROM_LOAD16_WORD_SWAP("os-v36.bin", 0x10000, 0x10000, CRC(ff915066) SHA1(977a8cca44ce9fa0765001f535f19390a7d8ff16) )
+// ROM_LOAD16_WORD_SWAP("os-v22.bin", 0x10000, 0x10000, CRC(22bcd1c7) SHA1(a23916e6bc1e8d8681704b774a3cbc745065d21e) )
+ROM_END
+
+
+
+const tiny_rom_entry *megadrive_hb_everdrive_device::device_rom_region() const
+{
+ return ROM_NAME( everdrive_md );
+}
+
+
+void megadrive_hb_everdrive_device::device_start()
+{
+ m_spi_clock = timer_alloc(FUNC(megadrive_hb_everdrive_device::spi_clock_cb), this);
+
+ save_item(NAME(m_spi_clock_state));
+ save_item(NAME(m_spi_full_speed));
+ save_item(NAME(m_spi_clock_cycles));
+ save_item(NAME(m_in_bit));
+ save_item(NAME(m_in_latch));
+ save_item(NAME(m_out_latch));
+
+ save_item(NAME(m_vblv));
+}
+
+void megadrive_hb_everdrive_device::device_reset()
+{
+ m_vblv = 0;
+ m_rom_map_port = 0;
+ m_spi_clock->adjust(attotime::never);
+ m_spi_clock_cycles = 0;
+ m_spi_clock_state = false;
+ m_sdcard->spi_ss_w(0);
+
+ m_game_mode.select(0);
+}
+
+void megadrive_hb_everdrive_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).view(m_game_mode);
+ m_game_mode[0](0x00'0000, 0x3f'ffff).lrw16(
+ NAME([this] (offs_t offset) {
+ return m_flash->read(offset | m_rom_map_port);
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ m_flash->write(offset | m_rom_map_port, data);
+ })
+ );
+ m_game_mode[1](0x00'0000, 0x3f'ffff).lr16(
+ NAME([this] (offs_t offset) {
+ return m_flash->read(offset | 0x20'0000);
+ })
+ );
+
+ // vblank redirection (for cheating), in OS mode (so at copy time)
+// m_vbl_catch[0](0x00'0078, 0x00'0079).lr16(NAME([] () { return 0xff; }));
+// m_vbl_catch[0](0x00'007a, 0x00'007b).lr16(NAME([this] () { return m_vblv; }));
+}
+
+// TODO: OS range /TIME inaccessible when in game_mode
+void megadrive_hb_everdrive_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).rw(FUNC(megadrive_hb_everdrive_device::spi_data_r), FUNC(megadrive_hb_everdrive_device::spi_data_w));
+ map(0x02, 0x03).rw(FUNC(megadrive_hb_everdrive_device::state_r), FUNC(megadrive_hb_everdrive_device::cfg_w));
+ map(0x04, 0x05).lw16(NAME([this] (offs_t offset, u16 data, u16 mem_mask) { COMBINE_DATA(&m_vblv); }));
+ // 0x06, 0x07 SRAMB, bank select for SRAM saving
+ // VER, unverified, makes an 'M' to appear on bottom right (for software controlled MEGAKEY?)
+ map(0x08, 0x09).lr16(NAME([] () { return 13; }));
+ map(0x0a, 0x0b).lw16(NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ if (ACCESSING_BITS_0_7)
+ m_rom_map_port = BIT(data, 0) ? (0x40'0000 >> 1) : 0;
+ }));
+
+ // TODO: assume cloning 315-5709 for RAM_MODE_1 and SSF2_MODE
+}
+
+u16 megadrive_hb_everdrive_device::spi_data_r(offs_t offset, u16 mem_mask)
+{
+ const u16 mask = m_spi_16 ? 0xffff : 0xff;
+ return m_in_latch & mask;
+}
+
+void megadrive_hb_everdrive_device::spi_data_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ COMBINE_DATA(&m_out_latch);
+ const u16 mask = m_spi_16 ? 0xffff : 0xff;
+ m_out_latch &= mask;
+
+ m_spi_clock_cycles = m_spi_16 ? 16 : 8;
+ m_spi_clock_state = false;
+
+ // Timings reported are per single byte, estimated.
+ // This will score a "time 1/10 sec: 96" / "speed kb/s: 105" in krikzz's benchmark
+ const int ticks = (m_spi_full_speed ? 16 : 128) >> 3;
+ m_spi_clock->adjust(attotime::from_ticks(ticks, this->clock()), 0, attotime::from_ticks(ticks, this->clock()));
+}
+
+
+u16 megadrive_hb_everdrive_device::state_r(offs_t offset, u16 mem_mask)
+{
+ const u8 spi_ready = m_spi_clock_cycles == 0;
+ return (m_sdcard->get_card_present() << 3)
+ // SMS key pressed if '1'
+ | (0 << 2)
+ | (m_flash->is_ready() << 1)
+ | spi_ready;
+}
+
+void megadrive_hb_everdrive_device::cfg_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (ACCESSING_BITS_0_7)
+ {
+ m_spi_full_speed = BIT(data, 1);
+ // assume negated by using #SS nomenclature
+ // HACK: don't change SS line if full speed is selected
+ if (!m_spi_full_speed)
+ m_sdcard->spi_ss_w(BIT(~data, 0));
+ // used on dir listing onward
+ m_spi_16 = BIT(data, 2);
+ //printf("SS %d full_speed %d\n", BIT(data, 0), m_spi_full_speed);
+
+ // guess
+ m_spi_clock->adjust(attotime::never);
+ m_spi_clock_cycles = 0;
+
+ m_game_mode.select(BIT(data, 3));
+
+ // m_sms_mode = BIT(data, 4);
+ // m_hard_reset = BIT(data, 5);
+ // m_ram_mode_1 = BIT(data, 6);
+ // m_ram_on = BIT(data, 7);
+ // m_vbl_catch.select(BIT(data, 8));
+ // m_megakey_on = BIT(data, 9);
+ // m_megakey_region_1 = BIT(data, 10);
+ // m_ssf2_mode = BIT(data, 11);
+ // m_ram_fs = BIT(data, 12);
+ // following is for Mega CD BIOS loading
+ // m_cart = BIT(data, 13);
+ }
+}
+
+TIMER_CALLBACK_MEMBER(megadrive_hb_everdrive_device::spi_clock_cb)
+{
+ if (m_spi_clock_cycles > 0)
+ {
+ //m_sdcard->spi_ss_w(1);
+
+ if (m_spi_clock_state)
+ {
+ m_in_latch <<= 1;
+ m_in_latch &= ~0x01;
+ m_in_latch |= m_in_bit;
+
+ m_sdcard->spi_clock_w(1);
+
+ m_spi_clock_cycles--;
+ }
+ else
+ {
+ m_sdcard->spi_mosi_w(BIT(m_out_latch, m_spi_16 ? 15 : 7));
+ m_sdcard->spi_clock_w(0);
+
+ m_out_latch <<= 1;
+ }
+
+ m_spi_clock_state = !m_spi_clock_state;
+ }
+ else
+ {
+ //m_sdcard->spi_ss_w(0);
+
+ m_spi_clock_state = false;
+ m_spi_clock->adjust(attotime::never);
+ }
+}
+
diff --git a/src/devices/bus/megadrive/cart/everdrive.h b/src/devices/bus/megadrive/cart/everdrive.h
new file mode 100644
index 0000000000000..7dc8f4b19f2aa
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/everdrive.h
@@ -0,0 +1,58 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_EVERDRIVE_H
+#define MAME_BUS_MEGADRIVE_CART_EVERDRIVE_H
+
+#pragma once
+
+#include "machine/intelfsh.h"
+#include "machine/spi_sdcard.h"
+
+#include "slot.h"
+
+class megadrive_hb_everdrive_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_hb_everdrive_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ static constexpr feature_type imperfect_features() { return feature::MEDIA; }
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) 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;
+
+ virtual const tiny_rom_entry *device_rom_region() const override ATTR_COLD;
+private:
+ required_device m_flash;
+ required_device m_sdcard;
+ memory_view m_game_mode;
+
+ TIMER_CALLBACK_MEMBER(spi_clock_cb);
+ u16 spi_data_r(offs_t offset, u16 mem_mask = ~0);
+ void spi_data_w(offs_t offset, u16 data, u16 mem_mask = ~0);
+ u16 state_r(offs_t offset, u16 mem_mask = ~0);
+ void cfg_w(offs_t offset, u16 data, u16 mem_mask = ~0);
+
+ emu_timer *m_spi_clock;
+ bool m_spi_clock_state;
+ bool m_spi_full_speed;
+ int m_spi_clock_cycles;
+ int m_in_bit;
+ u16 m_in_latch;
+ u16 m_out_latch;
+ bool m_spi_16;
+
+ u32 m_rom_map_port;
+ u16 m_vblv;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_EVERDRIVE, megadrive_hb_everdrive_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_DEV_H
diff --git a/src/devices/bus/megadrive/cart/gamtec.cpp b/src/devices/bus/megadrive/cart/gamtec.cpp
new file mode 100644
index 0000000000000..0a00910f1ffac
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/gamtec.cpp
@@ -0,0 +1,475 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Gamtec/Chuanpu/Never Ending Soft cart mappers
+
+Using a fixed data (discrete?) chip at $40'0000 as protection device
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "gamtec.h"
+
+#include "bus/generic/slot.h"
+
+
+/*
+ * 16 Zhang Mahjong II
+ * https://segaretro.org/16_Zhang_Mahjong_II
+ *
+ * TODO:
+ * - unknown purpose for ports $400002 and $400006 (spotted thru DASM code analysis)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_TILESMJ2, megadrive_unl_tilesmj2_device, "megadrive_unl_tilesmj2", "Megadrive 16 Zhang Mahjong II cart")
+
+megadrive_unl_tilesmj2_device::megadrive_unl_tilesmj2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_TILESMJ2, tag, owner, clock)
+{
+}
+
+void megadrive_unl_tilesmj2_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+// map(0x40'0000, 0x40'0000) unused?
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x98; })); // PC=193c,
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xc9; })); // PC=3ec, startup
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0x18; })); // PC=1626, attract
+}
+
+/*
+ * Líng Huàn Dàoshi - Super Magician
+ * https://segaretro.org/Ling_Huan_Daoshi
+ *
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_ELFWOR, megadrive_unl_elfwor_device, "megadrive_unl_elfwor", "Megadrive Ling Huan Daoshi Super Magician cart")
+
+megadrive_unl_elfwor_device::megadrive_unl_elfwor_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_ELFWOR, tag, owner, clock)
+{
+}
+
+void megadrive_unl_elfwor_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x55; }));
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x0f; }));
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xc9; }));
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0x18; }));
+}
+
+/*
+ * Huan Le Tao Qi Shu: Smart Mouse (huanle original set, not the Piko Interactive 2017 re-release)
+ * https://segaretro.org/Huan_Le_Tao_Qi_Shu:_Smart_Mouse
+ *
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SMOUSE, megadrive_unl_smouse_device, "megadrive_unl_smouse", "Megadrive Huan Le Tao Qi Shu Smart Mouse cart")
+
+megadrive_unl_smouse_device::megadrive_unl_smouse_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SMOUSE, tag, owner, clock)
+{
+}
+
+void megadrive_unl_smouse_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x55; }));
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x0f; }));
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xaa; }));
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0xf0; }));
+}
+
+/*
+ * Ya Se Chuan Shuo / Wu Kong Wai Zhuan
+ * https://segaretro.org/Ya_Se_Chuan_Shuo
+ * https://segaretro.org/Wu_Kong_Wai_Zhuan
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_YASECH, megadrive_unl_yasech_device, "megadrive_unl_yasech", "Megadrive Ya Se Chuan Shuo cart")
+
+megadrive_unl_yasech_device::megadrive_unl_yasech_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_YASECH, tag, owner, clock)
+ , m_nvram(*this, "nvram")
+{
+}
+
+void megadrive_unl_yasech_device::device_add_mconfig(machine_config &config)
+{
+ // init doesn't really matter: game specifically looks for a specific header pattern at tail
+ // (0x06, 0x09, 0x01, 0x08) in both banks, flushing to 0 if not found.
+ NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1);
+}
+
+void megadrive_unl_yasech_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ const u32 nvram_size = 0x1000;
+ m_nvram_ptr = std::make_unique(nvram_size);
+ m_nvram->set_base(m_nvram_ptr.get(), nvram_size);
+
+ save_pointer(NAME(m_nvram_ptr), nvram_size);
+}
+
+u16 megadrive_unl_yasech_device::nvram_r(offs_t offset)
+{
+ const u32 nvram_offset = offset & 0xfff;
+ return 0xff00 | m_nvram_ptr[nvram_offset];
+}
+
+void megadrive_unl_yasech_device::nvram_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (ACCESSING_BITS_0_7)
+ {
+ const u32 nvram_offset = offset & 0xfff;
+ m_nvram_ptr[nvram_offset] = data & 0xff;
+ }
+}
+
+
+void megadrive_unl_yasech_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x20'0000, 0x20'1fff).rw(FUNC(megadrive_unl_yasech_device::nvram_r), FUNC(megadrive_unl_yasech_device::nvram_w));
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x63; }));
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x98; }));
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xc9; }));
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0x18; }));
+}
+
+/*
+ * Meng Huan Shui Guo Pan: 777 Casino
+ * https://segaretro.org/Meng_Huan_Shui_Guo_Pan:_777_Casino
+ *
+ * Protection check here is subtle: it's done from Z80 space when new game is selected.
+ * If fails, going in a parlor and talk with a cashier twice will crash the game by Z80 trashing
+ * work RAM.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_777CASINO, megadrive_unl_777casino_device, "megadrive_unl_777casino", "Megadrive 777 Casino cart")
+
+megadrive_unl_777casino_device::megadrive_unl_777casino_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_777CASINO, tag, owner, clock)
+{
+}
+
+void megadrive_unl_777casino_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ // TODO: unconfirmed values (carried over from GPGX)
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x63; }));
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x98; }));
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xc9; }));
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0x18; }));
+}
+
+/*
+ * Soul Blade
+ * https://segaretro.org/Soul_Blade
+ *
+ * Game also implements self-modifying code at PC=28026 .. PC=28066, presumably against copiers
+ *
+ * Shi San Zhang Ma Jiang: Zhong Guo Mei Nv Pian
+ * (assumed, only $40'0004 checked on title to gameplay transition)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SOULBLADE, megadrive_unl_soulblade_device, "megadrive_unl_soulblade", "Megadrive Soul Blade cart")
+
+megadrive_unl_soulblade_device::megadrive_unl_soulblade_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SOULBLADE, tag, owner, clock)
+{
+}
+
+void megadrive_unl_soulblade_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ // unknown, assuming same as other entries
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x63; }));
+ // alt value on fight startup (PC=28B62 #-$68), unused?
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x98; }));
+ // after set amount of blocks/damage during fights (PC=2844C #-$56 or #-$37), causes GFX garbage
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xc9; }));
+ // on fight startup, twice (PC=28B58 #-$10), locks up if unhappy
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0xf0; }));
+}
+
+/*
+ * Super Bubble Bobble MD
+ * https://segaretro.org/Super_Bubble_Bobble_MD
+ * https://bootleggames.fandom.com/wiki/Super_Bubble_Bobble_MD
+ *
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SUPRBUBL, megadrive_unl_suprbubl_device, "megadrive_unl_suprbubl", "Megadrive Super Bubble Bobble cart")
+
+megadrive_unl_suprbubl_device::megadrive_unl_suprbubl_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SUPRBUBL, tag, owner, clock)
+{
+}
+
+void megadrive_unl_suprbubl_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ // just checked once, on startup
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x55; }));
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0x0f; }));
+}
+
+/*
+ * Chao Ji Mahjong Club
+ * https://segaretro.org/Chao_Ji_Mahjong_Club
+ *
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_CJMJCLUB, megadrive_unl_cjmjclub_device, "megadrive_unl_cjmjclub", "Megadrive Chao Ji Mahjong Club cart")
+
+megadrive_unl_cjmjclub_device::megadrive_unl_cjmjclub_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_CJMJCLUB, tag, owner, clock)
+{
+}
+
+void megadrive_unl_cjmjclub_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ // at startup, passed as dword to $ffff08
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x90; }));
+ map(0x40'0002, 0x40'0002).lr8(NAME([] () { return 0xd3; }));
+}
+
+/*
+ * Creaton Softech published games (beyond the one above)
+ *
+ * majianqr https://segaretro.org/Ma_Jiang_Qing_Ren:_Ji_Ma_Jiang_Zhi
+ * fengkuan https://segaretro.org/Feng_Kuang_Tao_Hua_Yuan
+ * mhpoker https://segaretro.org/Du_Shen_Zhi_Meng_Huan_Poker
+ * btlchess https://segaretro.org/Zhan_Qi_Chinese_Battle_Chess
+ *
+ * Uses $400000, $401000 instead of the usual linear mapping (just hooked up differently?)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_MJLOV, megadrive_unl_mjlov_device, "megadrive_unl_mjlov", "Megadrive Creaton Softec cart")
+
+megadrive_unl_mjlov_device::megadrive_unl_mjlov_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_MJLOV, tag, owner, clock)
+{
+}
+
+void megadrive_unl_mjlov_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x90; }));
+ map(0x40'1000, 0x40'1000).lr8(NAME([] () { return 0xd3; }));
+}
+
+/*
+ * San Guo Yan Yi: Huo Shao Chi Bi / The Battle of Red Cliffs
+ * https://segaretro.org/San_Guo_Yan_Yi:_Huo_Shao_Chi_Bi
+ *
+ * Slight ROM encryption
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_REDCLIFF, megadrive_unl_redcliff_device, "megadrive_unl_redcliff", "Megadrive Battle of Red Cliffs cart")
+
+
+megadrive_unl_redcliff_device::megadrive_unl_redcliff_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_UNL_REDCLIFF, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom(*this, "rom")
+{
+}
+
+void megadrive_unl_redcliff_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ m_decrypted_rom.resize(0x40'0000);
+
+ auto enc = &romregion->as_u8();
+ int i;
+
+ std::fill(m_decrypted_rom.begin(), m_decrypted_rom.end(), 0xff ^ 0x40);
+
+ // NOTE: dump is oversized, original in .mdx format?
+ for (i = 4; i < romregion->bytes(); i++)
+ m_decrypted_rom[i - 4] = enc[i] ^ 0x40;
+
+ m_rom->configure_entry(0, &m_decrypted_rom[0]);
+}
+
+void megadrive_unl_redcliff_device::device_reset()
+{
+}
+
+void megadrive_unl_redcliff_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ // NOTE: may not even use the same "chip" described in this file
+ // gameplay
+ map(0x40'0000, 0x40'0000).lr8(NAME([] () { return 0x55; }));
+ // startup
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xaa; }));
+}
+
+/*
+ * Squirrel King
+ * https://segaretro.org/Squirrel_King
+ *
+ * Uses area $400000-$400006 as r/w latch
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SQUIRRELK, megadrive_unl_squirrelk_device, "megadrive_unl_squirrelk", "Megadrive Squirrel King cart")
+
+megadrive_unl_squirrelk_device::megadrive_unl_squirrelk_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SQUIRRELK, tag, owner, clock)
+{
+}
+
+void megadrive_unl_squirrelk_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ save_item(NAME(m_latch));
+}
+
+void megadrive_unl_squirrelk_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ // irrelevant, initialized at startup
+ m_latch = 0xff;
+}
+
+void megadrive_unl_squirrelk_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0000, 0x40'0000).select(6).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_latch;
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_latch = data;
+ })
+ );
+}
+
+/*
+ * The Lion King II
+ * https://segaretro.org/The_Lion_King_II
+ *
+ * Similar to above except two latches instead of one
+ * Game also implements self-modifying code at PC=808DA .. PC=8095A, presumably against copiers
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_LIONKING2, megadrive_unl_lionking2_device, "megadrive_unl_lionking2", "Megadrive The Lion King II cart")
+
+megadrive_unl_lionking2_device::megadrive_unl_lionking2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_LIONKING2, tag, owner, clock)
+{
+}
+
+void megadrive_unl_lionking2_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ save_pointer(NAME(m_latch), 2);
+}
+
+void megadrive_unl_lionking2_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ // irrelevant, initialized at startup
+ m_latch[0] = m_latch[1] = 0xff;
+}
+
+void megadrive_unl_lionking2_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0000, 0x40'0000).select(4).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_latch[offset] = data;
+ })
+ );
+ map(0x40'0002, 0x40'0002).select(4).lr8(
+ NAME([this] (offs_t offset, u8 data) {
+ return m_latch[offset];
+ })
+ );
+}
+
+/*
+ * Tenchi o Kurau III: Sangoku Gaiden / Tun Shi Tian Di 3: San Guo Wai Chuan / Chinese Fighter III
+ * https://segaretro.org/Tenchi_o_Kurau_III:_Sangoku_Gaiden
+ *
+ * Pseudo-banking scheme looks similar to Top Fighter, otherwise using Squirrel King latching
+ * obfuscated by address lanes.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_CHINF3, megadrive_unl_chinf3_device, "megadrive_unl_chinf3", "Megadrive Chinese Fighter III cart")
+
+megadrive_unl_chinf3_device::megadrive_unl_chinf3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_CHINF3, tag, owner, clock)
+ , m_page_rom(*this, "page_rom")
+ , m_page_view(*this, "page_view")
+{
+}
+
+void megadrive_unl_chinf3_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x01'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_page_rom->configure_entry(entry, &base[page * page_size]);
+ });
+
+ save_pointer(NAME(m_prot_latch), 4);
+}
+
+void megadrive_unl_chinf3_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ m_page_view.select(0);
+ std::fill_n(&m_prot_latch[0], 4, 0xff);
+}
+
+void megadrive_unl_chinf3_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x1f'ffff).view(m_page_view);
+ m_page_view[0](0x00'0000, 0x1f'ffff).bankr(m_rom);
+ m_page_view[1](0x00'0000, 0x00'ffff).mirror(0x1f'0000).bankr(m_page_rom);
+ map(0x40'0000, 0x40'0000).select(0xc).mirror(0x0f'fff3).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_prot_latch[offset >> 2];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_prot_latch[offset >> 2] = data;
+ })
+ );
+ map(0x60'0000, 0x60'0000).mirror(0x0f'ffff).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ if (data)
+ {
+ m_page_rom->set_entry(data & 0xf);
+ m_page_view.select(1);
+ }
+ else
+ {
+ m_page_view.select(0);
+ }
+ })
+ );
+}
diff --git a/src/devices/bus/megadrive/cart/gamtec.h b/src/devices/bus/megadrive/cart/gamtec.h
new file mode 100644
index 0000000000000..05219bc868f1d
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/gamtec.h
@@ -0,0 +1,171 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_GAMTEC_H
+#define MAME_BUS_MEGADRIVE_CART_GAMTEC_H
+
+#pragma once
+
+#include "machine/nvram.h"
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_tilesmj2_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_tilesmj2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_elfwor_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_elfwor_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_smouse_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_smouse_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_yasech_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_yasech_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ u16 nvram_r(offs_t offset);
+ void nvram_w(offs_t offset, u16 data, u16 mem_mask);
+
+private:
+ required_device m_nvram;
+ std::unique_ptr m_nvram_ptr;
+
+};
+
+class megadrive_unl_777casino_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_777casino_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_soulblade_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_soulblade_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_suprbubl_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_suprbubl_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_cjmjclub_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_cjmjclub_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_mjlov_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_mjlov_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_redcliff_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_unl_redcliff_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+// bool check_rom(std::string &message) ATTR_COLD;
+ memory_bank_creator m_rom;
+ std::vector m_decrypted_rom;
+};
+
+class megadrive_unl_squirrelk_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_squirrelk_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+private:
+ u8 m_latch;
+};
+
+class megadrive_unl_lionking2_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_lionking2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+private:
+ u8 m_latch[2];
+};
+
+class megadrive_unl_chinf3_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_chinf3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+private:
+ memory_bank_creator m_page_rom;
+ memory_view m_page_view;
+
+ u8 m_prot_latch[4];
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_TILESMJ2, megadrive_unl_tilesmj2_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_ELFWOR, megadrive_unl_elfwor_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SMOUSE, megadrive_unl_smouse_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_YASECH, megadrive_unl_yasech_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_777CASINO, megadrive_unl_777casino_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SOULBLADE, megadrive_unl_soulblade_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SUPRBUBL, megadrive_unl_suprbubl_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_CJMJCLUB, megadrive_unl_cjmjclub_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_MJLOV, megadrive_unl_mjlov_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_REDCLIFF, megadrive_unl_redcliff_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SQUIRRELK, megadrive_unl_squirrelk_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_LIONKING2, megadrive_unl_lionking2_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_CHINF3, megadrive_unl_chinf3_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_GAMTEC_H
diff --git a/src/devices/bus/megadrive/cart/jcart.cpp b/src/devices/bus/megadrive/cart/jcart.cpp
new file mode 100644
index 0000000000000..4a0b42ba30566
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/jcart.cpp
@@ -0,0 +1,177 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese, Fabio Priuli
+/**************************************************************************************************
+
+Codemasters J-Cart
+
+Two extra ports mounted on cart
+
+TODO:
+- Starting at 3 with control ports doesn't work well for Teradrive (will map to P5/P6, before
+ system ports)
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "jcart.h"
+
+#include "bus/sms_ctrl/controllers.h"
+
+/*
+ * Pete Sampras Tennis
+ *
+ * Maps J-Cart to 0x3f'fffe in place of 0x38'fffe of all the others.
+ * sampras set will hang, sampras1/sampras2 won't but still won't give 4p option
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_SAMPRAS, megadrive_rom_jcart_sampras_device, "megadrive_rom_jcart_sampras", "Megadrive J-Cart Pete Sampras Tennis cart")
+
+megadrive_rom_jcart_sampras_device::megadrive_rom_jcart_sampras_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, type, tag, owner, clock)
+ , m_ctrl_ports(*this, "control%u", 3U)
+{
+}
+
+megadrive_rom_jcart_sampras_device::megadrive_rom_jcart_sampras_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_jcart_sampras_device(mconfig, MEGADRIVE_ROM_JCART_SAMPRAS, tag, owner, clock)
+{
+}
+
+void megadrive_rom_jcart_sampras_device::device_add_mconfig(machine_config &config)
+{
+ SMS_CONTROL_PORT(config, m_ctrl_ports[0], sms_control_port_devices, SMS_CTRL_OPTION_MD_PAD);
+ m_ctrl_ports[0]->th_handler().set(FUNC(megadrive_rom_jcart_sampras_device::th_in<0>));
+
+ SMS_CONTROL_PORT(config, m_ctrl_ports[1], sms_control_port_devices, SMS_CTRL_OPTION_MD_PAD);
+ m_ctrl_ports[1]->th_handler().set(FUNC(megadrive_rom_jcart_sampras_device::th_in<1>));
+}
+
+
+void megadrive_rom_jcart_sampras_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ save_item(NAME(m_th_in));
+ save_item(NAME(m_th_out));
+}
+
+void megadrive_rom_jcart_sampras_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ m_th_in[0] = m_th_in[1] = 0x40;
+ m_th_out = 0x40;
+}
+
+template
+void megadrive_rom_jcart_sampras_device::th_in(int state)
+{
+ m_th_in[N] = state ? 0x40 : 0x00;
+}
+
+u16 megadrive_rom_jcart_sampras_device::jcart_r(offs_t offset, u16 mem_mask)
+{
+ u8 const ctrl3 = (m_ctrl_ports[0]->in_r() & 0x3f) | (m_th_in[0] & m_th_out) | 0x80;
+ u8 const ctrl4 = (m_ctrl_ports[1]->in_r() & 0x3f) | (m_th_in[1] & m_th_out) | 0x80;
+ return ctrl3 | (ctrl4 << 8);
+}
+
+void megadrive_rom_jcart_sampras_device::jcart_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ // assume TH only actively driven low
+ m_th_out = BIT(data, 0) << 6;
+ m_ctrl_ports[0]->out_w(m_th_out | 0x3f, ~m_th_out & 0x40);
+ m_ctrl_ports[1]->out_w(m_th_out | 0x3f, ~m_th_out & 0x40);
+}
+
+void megadrive_rom_jcart_sampras_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x3f'fffe, 0x3f'ffff).rw(FUNC(megadrive_rom_jcart_sampras_device::jcart_r), FUNC(megadrive_rom_jcart_sampras_device::jcart_w));
+}
+
+/*
+ * Super Skidmarks / Pete Sampras Tennis '96
+ *
+ * Relocated J-Cart position
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_SSKID, megadrive_rom_jcart_sskid_device, "megadrive_rom_jcart_sskid", "Megadrive J-Cart Super Skidmarks cart")
+
+megadrive_rom_jcart_sskid_device::megadrive_rom_jcart_sskid_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_jcart_sampras_device(mconfig, MEGADRIVE_ROM_JCART_SSKID, tag, owner, clock)
+{
+}
+
+void megadrive_rom_jcart_sskid_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x38'fffe, 0x38'ffff).rw(FUNC(megadrive_rom_jcart_sskid_device::jcart_r), FUNC(megadrive_rom_jcart_sskid_device::jcart_w));
+}
+
+/*
+ * Micro Machines 2 / Micro Machines Military
+ *
+ * Adds an I2C to the bus
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_MICROMAC2, megadrive_rom_jcart_micromac2_device, "megadrive_rom_jcart_micromac2", "Megadrive J-Cart Micro Machines 2 cart")
+
+megadrive_rom_jcart_micromac2_device::megadrive_rom_jcart_micromac2_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_jcart_sampras_device(mconfig, type, tag, owner, clock)
+ , m_i2cmem(*this, "i2cmem")
+{
+}
+
+megadrive_rom_jcart_micromac2_device::megadrive_rom_jcart_micromac2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_jcart_micromac2_device(mconfig, MEGADRIVE_ROM_JCART_MICROMAC2, tag, owner, clock)
+{
+}
+
+
+void megadrive_rom_jcart_micromac2_device::device_add_mconfig(machine_config &config)
+{
+ megadrive_rom_jcart_sampras_device::device_add_mconfig(config);
+
+ I2C_24C08(config, m_i2cmem);
+}
+
+void megadrive_rom_jcart_micromac2_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ map(0x30'0000, 0x30'0000).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_i2cmem->write_scl(BIT(data, 1));
+ m_i2cmem->write_sda(BIT(data, 0));
+ })
+ );
+ map(0x38'0001, 0x38'0001).lr8(
+ NAME([this] (offs_t offset) {
+ return m_i2cmem->read_sda() << 7;
+ })
+ );
+ map(0x38'fffe, 0x38'ffff).rw(FUNC(megadrive_rom_jcart_micromac2_device::jcart_r), FUNC(megadrive_rom_jcart_micromac2_device::jcart_w));
+}
+
+/*
+ * Micro Machines Turbo Tournament 96
+ *
+ * Bumps I2C, otherwise same as 2
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_MICROMAC96, megadrive_rom_jcart_micromac96_device, "megadrive_rom_jcart_micromac96", "Megadrive J-Cart Micro Machines 96 cart")
+
+megadrive_rom_jcart_micromac96_device::megadrive_rom_jcart_micromac96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_jcart_micromac2_device(mconfig, MEGADRIVE_ROM_JCART_MICROMAC96, tag, owner, clock)
+{
+}
+
+void megadrive_rom_jcart_micromac96_device::device_add_mconfig(machine_config &config)
+{
+ megadrive_rom_jcart_micromac2_device::device_add_mconfig(config);
+
+ I2C_24C16(config.replace(), m_i2cmem);
+}
+
diff --git a/src/devices/bus/megadrive/cart/jcart.h b/src/devices/bus/megadrive/cart/jcart.h
new file mode 100644
index 0000000000000..8cc91320a37a9
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/jcart.h
@@ -0,0 +1,79 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese, Fabio Priuli
+
+#ifndef MAME_BUS_MEGADRIVE_CART_JCART_H
+#define MAME_BUS_MEGADRIVE_CART_JCART_H
+
+#pragma once
+
+#include "bus/sms_ctrl/smsctrl.h"
+#include "machine/i2cmem.h"
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_rom_jcart_sampras_device : public megadrive_rom_device
+{
+public:
+ megadrive_rom_jcart_sampras_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ megadrive_rom_jcart_sampras_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ 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;
+
+ u16 jcart_r(offs_t offset, u16 mem_mask = ~0);
+ void jcart_w(offs_t offset, u16 data, u16 mem_mask = ~0);
+
+private:
+ template void th_in(int state);
+
+ required_device_array m_ctrl_ports;
+ uint8_t m_th_in[2];
+ uint8_t m_th_out;
+};
+
+class megadrive_rom_jcart_sskid_device : public megadrive_rom_jcart_sampras_device
+{
+public:
+ megadrive_rom_jcart_sskid_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_rom_jcart_micromac2_device : public megadrive_rom_jcart_sampras_device
+{
+public:
+ megadrive_rom_jcart_micromac2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ megadrive_rom_jcart_micromac2_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ required_device m_i2cmem;
+};
+
+class megadrive_rom_jcart_micromac96_device : public megadrive_rom_jcart_micromac2_device
+{
+public:
+ megadrive_rom_jcart_micromac96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_SAMPRAS, megadrive_rom_jcart_sampras_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_SSKID, megadrive_rom_jcart_sskid_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_MICROMAC2, megadrive_rom_jcart_micromac2_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_JCART_MICROMAC96, megadrive_rom_jcart_micromac96_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_JCART_H
diff --git a/src/devices/bus/megadrive/cart/mcpirate.cpp b/src/devices/bus/megadrive/cart/mcpirate.cpp
new file mode 100644
index 0000000000000..57f902286fa2b
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/mcpirate.cpp
@@ -0,0 +1,225 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+X-in-1 pirate multigame handling (with actual menu vs. /VRES in multigame)
+
+https://segaretro.org/Mega_Drive_unlicensed_multi-carts_(S_series)
+
+TODO:
+- wisdomtc may use a single mapper bank granularity of $80000
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "mcpirate.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_MCPIRATE, megadrive_mcpirate_device, "megadrive_mcpirate", "Megadrive multigame pirate cart")
+
+megadrive_mcpirate_device::megadrive_mcpirate_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_MCPIRATE, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+{
+}
+
+void megadrive_mcpirate_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x02'0000;
+ m_bank_mask = device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+ logerror("Bank mask %02x\n", m_bank_mask);
+}
+
+void megadrive_mcpirate_device::device_reset()
+{
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->set_entry(i);
+}
+
+void megadrive_mcpirate_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x01'ffff).mirror(0x38'0000).bankr(m_rom_bank[0]);
+ map(0x02'0000, 0x03'ffff).mirror(0x38'0000).bankr(m_rom_bank[1]);
+ map(0x04'0000, 0x05'ffff).mirror(0x38'0000).bankr(m_rom_bank[2]);
+ map(0x06'0000, 0x07'ffff).mirror(0x38'0000).bankr(m_rom_bank[3]);
+}
+
+
+void megadrive_mcpirate_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x00).select(0x3e).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ (void)data;
+ const u8 page_sel = offset >> 1;
+ logerror("Bank select %02x\n", page_sel);
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->set_entry((page_sel + i) & m_bank_mask);
+ })
+ );
+}
+
+/*
+ * 1800-in-1 mapper
+ *
+ * https://segaretro.org/Mega_Drive_unlicensed_multi-carts_(unsorted;_9_in_1)#9_in_1
+ *
+ * Changes bank on strobe reads, shuffled.
+ * All Sega references looks either skipped or patched.
+ *
+ * Mapping (from 9-in-1 menu):
+ * | gamename | log | phy |
+ * | 0001 RABO III | 18 | 02 |
+ * | 0002 SPACE INVADERS | 00 | 00 |
+ * | 0003 SUPPER VOLLEY BALL | 1c | 06 |
+ * | 0004 KLAX | 0a | 08 |
+ * | 0005 PACMANIA | 1a | 0a |
+ * | 0006 TECMO WORLD CAP 92 | 0e | 0c |
+ * | 0007 DOUBLE CLUTCH | 04 | 04 |
+ * | 0008 COLUMNS | 1e | 0e |
+ * | 0009 BLOCK OUT | 7e | 0f |
+ *
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_18KIN1, megadrive_18kin1_device, "megadrive_18kin1", "Megadrive 1800-in-1 pirate cart")
+
+megadrive_18kin1_device::megadrive_18kin1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_18KIN1, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+ , m_config(*this, "CONFIG")
+{
+}
+
+void megadrive_18kin1_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x02'0000;
+ m_bank_mask = device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+ logerror("Bank mask %02x\n", m_bank_mask);
+}
+
+void megadrive_18kin1_device::device_reset()
+{
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->set_entry(i);
+}
+
+INPUT_PORTS_START( _18kin1 )
+ // Menu reads /time register contents for number of entries displayed.
+ // This doesn't change the number of actual games.
+ // TODO: dips or jumpers?
+ PORT_START("CONFIG")
+ PORT_CONFNAME(0x03, 0x03, "Menu title")
+ PORT_CONFSETTING( 0x00, "9 in 1")
+ PORT_CONFSETTING( 0x01, "190 in 1")
+ PORT_CONFSETTING( 0x02, "888 in 1")
+ PORT_CONFSETTING( 0x03, "1800 in 1")
+INPUT_PORTS_END
+
+ioport_constructor megadrive_18kin1_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME( _18kin1 );
+}
+
+void megadrive_18kin1_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x01'ffff).mirror(0x38'0000).bankr(m_rom_bank[0]);
+ map(0x02'0000, 0x03'ffff).mirror(0x38'0000).bankr(m_rom_bank[1]);
+ map(0x04'0000, 0x05'ffff).mirror(0x38'0000).bankr(m_rom_bank[2]);
+ map(0x06'0000, 0x07'ffff).mirror(0x38'0000).bankr(m_rom_bank[3]);
+}
+
+void megadrive_18kin1_device::time_io_map(address_map &map)
+{
+ map(0x01, 0x01).select(0x7e).lr8(
+ NAME([this] (offs_t offset) {
+ const u8 page_sel = bitswap<4>(offset, 1, 2, 4, 5);
+ logerror("Bank select log: %02x phy: %02x & %02x\n", offset, page_sel, m_bank_mask);
+ if (!machine().side_effects_disabled())
+ {
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->set_entry((page_sel + i) & m_bank_mask);
+ }
+ return m_config->read() & 3;
+ })
+ );
+}
+
+/*
+ * Golden Mega 250-in-1
+ *
+ * Sonic 2, Alex Kidd, Trampoline Terror and Tecmo Cup Football Game (the Captain Tsubasa prototype)
+ * mixed in 250 variants ...
+ *
+ * | gamename | log | phy |
+ * | 001. SONIC 2 | 11 | 04 |
+ * | 013. TECMO CUP SOCCER IV | a0 | 02 |
+ * | 030. ALEX KIDD | 40 | 01 |
+ * | 063. TRAMPOLINE TERROR | 00 | 00 |
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_GOLDM250, megadrive_goldm250_device, "megadrive_goldm250", "Megadrive Golden Mega 250-in-1 pirate cart")
+
+megadrive_goldm250_device::megadrive_goldm250_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_GOLDM250, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+{
+}
+
+void megadrive_goldm250_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x04'0000;
+ m_bank_mask = device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+ logerror("Bank mask %02x\n", m_bank_mask);
+}
+
+void megadrive_goldm250_device::device_reset()
+{
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->set_entry(i);
+}
+
+void megadrive_goldm250_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x03'ffff).mirror(0x30'0000).bankr(m_rom_bank[0]);
+ map(0x04'0000, 0x07'ffff).mirror(0x30'0000).bankr(m_rom_bank[1]);
+ map(0x08'0000, 0x0b'ffff).mirror(0x30'0000).bankr(m_rom_bank[2]);
+ map(0x0c'0000, 0x0f'ffff).mirror(0x30'0000).bankr(m_rom_bank[3]);
+ map(0x08'9000, 0x08'9001).lw16(NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ // writes in word units
+ if (ACCESSING_BITS_0_7)
+ {
+ const u8 page_sel = bitswap<3>(data, 0, 7, 6);
+ logerror("Bank select log: %02x phy: %02x & %02x\n", data, page_sel, m_bank_mask);
+ for (int i = 0; i < 4; i++)
+ m_rom_bank[i]->set_entry((page_sel + i) & m_bank_mask);
+ }
+ }));
+}
+
+
diff --git a/src/devices/bus/megadrive/cart/mcpirate.h b/src/devices/bus/megadrive/cart/mcpirate.h
new file mode 100644
index 0000000000000..b0766b8f0955c
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/mcpirate.h
@@ -0,0 +1,75 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_MCPIRATE_H
+#define MAME_BUS_MEGADRIVE_CART_MCPIRATE_H
+
+#pragma once
+
+#include "slot.h"
+
+class megadrive_mcpirate_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_mcpirate_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+private:
+ memory_bank_array_creator<4> m_rom_bank;
+
+ u8 m_bank_mask;
+};
+
+class megadrive_18kin1_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_18kin1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual ioport_constructor device_input_ports() const override ATTR_COLD;
+
+private:
+ memory_bank_array_creator<4> m_rom_bank;
+ required_ioport m_config;
+
+ u8 m_bank_mask;
+};
+
+class megadrive_goldm250_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_goldm250_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+private:
+ memory_bank_array_creator<4> m_rom_bank;
+
+ u8 m_bank_mask;
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_MCPIRATE, megadrive_mcpirate_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_18KIN1, megadrive_18kin1_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_GOLDM250, megadrive_goldm250_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_MCPIRATE_H
diff --git a/src/devices/bus/megadrive/cart/miky.cpp b/src/devices/bus/megadrive/cart/miky.cpp
new file mode 100644
index 0000000000000..941a46032f486
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/miky.cpp
@@ -0,0 +1,88 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+https://segaretro.org/TC_2000
+https://segaretro.org/Truco_%2796
+
+Unknown protection chip, simple strobe write then read
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "miky.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_TC2000, megadrive_unl_tc2000_device, "megadrive_unl_tc2000", "Megadrive TC2000 cart")
+
+megadrive_unl_tc2000_device::megadrive_unl_tc2000_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_TC2000, tag, owner, clock)
+{
+}
+
+void megadrive_unl_tc2000_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ save_item(NAME(m_prot_latch));
+}
+
+void megadrive_unl_tc2000_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ // undefined, initialized by game anyway
+ m_prot_latch = 0;
+}
+
+
+void megadrive_unl_tc2000_device::cart_map(address_map &map)
+{
+ // writes often in ROM space, buggy?
+ map(0x00'0000, 0x0f'ffff).bankr(m_rom).nopw();
+ map(0x10'0000, 0x10'0000).mirror(0x0f'fffe).lr8(NAME([this] () { return m_prot_latch; }));
+ // truco96a
+ map(0x10'0000, 0x10'0000).mirror(0x0f'fff0).lw8(NAME([this] (offs_t offset, u8 data) { (void)data; m_prot_latch = 0x00; }));
+ // tc2000
+ map(0x10'0008, 0x10'0008).mirror(0x0f'fff0).lw8(NAME([this] (offs_t offset, u8 data) { (void)data; m_prot_latch = 0x50; }));
+ map(0x10'000c, 0x10'000c).mirror(0x0f'fff0).lw8(NAME([this] (offs_t offset, u8 data) { (void)data; m_prot_latch = 0xa0; }));
+}
+
+/*
+ * Futbol Argentino 96
+ * https://segaretro.org/J.League_Pro_Striker_2/Bootlegs
+ *
+ * Unlike jlps2 saving a league doesn't really work without playing at least one match first.
+ * Is this is based on an undumped earlier rev? (No protection access on that)
+ *
+ * TODO:
+ * - protection not really understood (game does a very small use of it, just expects the read
+ * values to always return fixed values)
+ *
+ */
+
+ DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_FUTBOL_ARG96, megadrive_unl_futbol_arg96_device, "megadrive_unl_futbol_arg96", "Megadrive Futbol Argentino 96 cart")
+
+megadrive_unl_futbol_arg96_device::megadrive_unl_futbol_arg96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, MEGADRIVE_UNL_FUTBOL_ARG96, tag, owner, clock)
+{
+}
+
+u16 megadrive_unl_futbol_arg96_device::get_nvram_length()
+{
+ return 0x2000;
+}
+
+void megadrive_unl_futbol_arg96_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x1f'ffff).mirror(0x20'0000).bankr(m_rom);
+ map(0x20'0000, 0x20'3fff).rw(FUNC(megadrive_unl_futbol_arg96_device::nvram_r), FUNC(megadrive_unl_futbol_arg96_device::nvram_w));
+ // writes (in this order, always 0):
+ // 4c'6000 x1
+ // 4c'6400 x1
+ // 4c'6800 x2
+ // 4c'6c00 x3
+ // 4c'7000 x4
+ map(0x4c'6201, 0x4c'6201).lr8(NAME([] () { return 0xa; }));
+ map(0x4c'6601, 0x4c'6601).lr8(NAME([] () { return 0x9; }));
+ map(0x4c'6a01, 0x4c'6a01).lr8(NAME([] () { return 0x7; }));
+}
+
+
diff --git a/src/devices/bus/megadrive/cart/miky.h b/src/devices/bus/megadrive/cart/miky.h
new file mode 100644
index 0000000000000..600e72d474d23
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/miky.h
@@ -0,0 +1,43 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_MIKY_H
+#define MAME_BUS_MEGADRIVE_CART_MIKY_H
+
+#pragma once
+
+#include "rom.h"
+#include "sram.h"
+#include "slot.h"
+
+class megadrive_unl_tc2000_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_tc2000_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+private:
+ u8 m_prot_latch;
+};
+
+class megadrive_unl_futbol_arg96_device : public megadrive_rom_tplay96_device
+{
+public:
+ megadrive_unl_futbol_arg96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual u16 get_nvram_length() override ATTR_COLD;
+
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_TC2000, megadrive_unl_tc2000_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_FUTBOL_ARG96, megadrive_unl_futbol_arg96_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_TEKKENSP_H
diff --git a/src/devices/bus/megadrive/cart/multigame.cpp b/src/devices/bus/megadrive/cart/multigame.cpp
new file mode 100644
index 0000000000000..50775670b225b
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/multigame.cpp
@@ -0,0 +1,183 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Multigame cart mappers, with CMOS latching on power/reset
+
+TODO:
+- sportg mirroring is unconfirmed (may just rotate starting address instead);
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "multigame.h"
+
+#include "bus/generic/slot.h"
+
+/*
+ * TecToy Sport Games
+ * https://segaretro.org/Sport_Games
+ *
+ * Sega MPR-19945-MX ROM with a 74HC74N and a 74HC00N attached.
+ * Games bankswitch also changes with power switch according to manual,
+ * for simplicity we just use /VRES only.
+ *
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_TECTOY_SPORTS, megadrive_tectoy_sports_device, "megadrive_tectoy_sports", "Megadrive TecToy Sport Games cart")
+
+megadrive_tectoy_sports_device::megadrive_tectoy_sports_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_TECTOY_SPORTS, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom(*this, "rom")
+{
+}
+
+void megadrive_tectoy_sports_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x10'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_rom->configure_entry(entry, &base[page * page_size]);
+ });
+ // m_rom_mirror = 0x30'0000;
+
+ // No info about what is the default game on cold boot, assume Super Volley Ball
+ m_game_sel = 2;
+ save_item(NAME(m_game_sel));
+}
+
+void megadrive_tectoy_sports_device::device_reset()
+{
+ m_game_sel ++;
+ m_game_sel %= 3;
+
+ const std::string game_names[] = { "Super Volley Ball", "World Championship Soccer II", "Super Real Basketball" };
+
+ logerror("Game mounted: %s\n", game_names[m_game_sel]);
+ m_rom->set_entry(m_game_sel);
+}
+
+void megadrive_tectoy_sports_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x0f'ffff).mirror(0x30'0000).bankr(m_rom);
+}
+
+/*
+ * Codemasters 2-in-1
+ * https://segaretro.org/2_Games_on_One_Cart:_Fantastic_Dizzy_and_Cosmic_Spacehead
+ * https://segaretro.org/Double_Hits:_Micro_Machines_/_Psycho_Pinball
+ *
+ * "One of the games will begin: for example Cosmic Spacehead", again suggesting that cold boot
+ * may still have preserved (static) state.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_CM2IN1, megadrive_cm2in1_device, "megadrive_cm2in1", "Megadrive Codemasters 2 games on 1 cart")
+
+megadrive_cm2in1_device::megadrive_cm2in1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_CM2IN1, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom(*this, "rom")
+{
+}
+
+void megadrive_cm2in1_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x20'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_rom->configure_entry(entry, &base[page * page_size]);
+ });
+
+ // start in a predictable manner
+ m_game_sel = 1;
+ save_item(NAME(m_game_sel));
+}
+
+void megadrive_cm2in1_device::device_reset()
+{
+ m_game_sel ++;
+ m_game_sel &= 1;
+
+ m_rom->set_entry(m_game_sel);
+}
+
+void megadrive_cm2in1_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x1f'ffff).mirror(0x20'0000).bankr(m_rom);
+}
+
+/*
+ * 3 in 1 Flashback - World Championship Soccer - Tecmo World Cup 92
+ * 3 in 1 Road Rash - Ms. Pac-Man - Block Out
+ *
+ * Unknown source (pirate but with copyright text on back cover!?)
+ *
+ * Uneven bank scheme, the two soccer games lies at $18'0000 and $1c'0000 so we simplify with a
+ * memory_view. Road Rash cart is half total size/page size, so we need to capture ROM length at
+ * init time.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_3IN1FWT, megadrive_3in1fwt_device, "megadrive_3in1fwt", "Megadrive Flashback 3-in-1 cart")
+
+megadrive_3in1fwt_device::megadrive_3in1fwt_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_3IN1FWT, tag, owner, clock)
+ , m_rom_bank(*this, "rom_bank")
+ , m_rom_view(*this, "rom_view")
+{
+}
+
+void megadrive_3in1fwt_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+
+ m_rom_mask = romregion->bytes() - 1;
+ const u32 page_size = romregion->bytes() / 8;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8(), pg_mult = page_size] (unsigned entry, unsigned page)
+ {
+ m_rom_bank->configure_entry(entry, &base[page * pg_mult]);
+ });
+
+ m_page_mask = page_size - 1;
+
+ // start in a predictable manner
+ m_game_sel = 2;
+ save_item(NAME(m_game_sel));
+}
+
+void megadrive_3in1fwt_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ m_game_sel ++;
+ m_game_sel %= 3;
+
+ m_rom_view.select(m_game_sel);
+ // unknown order
+ if (m_game_sel != 0)
+ {
+ u8 page_sel = 6 + ((m_game_sel & 1) ^ 1);
+ logerror("Page selection %02x\n", page_sel);
+ m_rom_bank->set_entry(page_sel);
+ }
+}
+
+void megadrive_3in1fwt_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).view(m_rom_view);
+ m_rom_view[0](0x00'0000, m_rom_mask ).bankr(m_rom);
+ m_rom_view[1](0x00'0000, m_page_mask).bankr(m_rom_bank);
+ m_rom_view[2](0x00'0000, m_page_mask).bankr(m_rom_bank);
+}
+
diff --git a/src/devices/bus/megadrive/cart/multigame.h b/src/devices/bus/megadrive/cart/multigame.h
new file mode 100644
index 0000000000000..e42f5b0390bce
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/multigame.h
@@ -0,0 +1,69 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_MULTIGAME_H
+#define MAME_BUS_MEGADRIVE_CART_MULTIGAME_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_tectoy_sports_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_tectoy_sports_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ memory_bank_creator m_rom;
+ u8 m_game_sel;
+};
+
+class megadrive_cm2in1_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_cm2in1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+private:
+ memory_bank_creator m_rom;
+ u8 m_game_sel;
+};
+
+class megadrive_3in1fwt_device : public megadrive_rom_device
+{
+public:
+ megadrive_3in1fwt_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ memory_bank_creator m_rom_bank;
+ memory_view m_rom_view;
+ u8 m_game_sel;
+ u32 m_rom_mask;
+ u32 m_page_mask;
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_TECTOY_SPORTS, megadrive_tectoy_sports_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_CM2IN1, megadrive_cm2in1_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_3IN1FWT, megadrive_3in1fwt_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_MULTIGAME_H
diff --git a/src/devices/bus/megadrive/cart/options.cpp b/src/devices/bus/megadrive/cart/options.cpp
new file mode 100644
index 0000000000000..51abff8278ccd
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/options.cpp
@@ -0,0 +1,215 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#include "emu.h"
+#include "options.h"
+
+#include "action_replay.h"
+#include "avartisan.h"
+#include "eeprom.h"
+#include "everdrive.h"
+#include "gamtec.h"
+#include "jcart.h"
+#include "mcpirate.h"
+#include "miky.h"
+#include "multigame.h"
+#include "rockworld.h"
+#include "rom.h"
+#include "seganet.h"
+#include "sfteam.h"
+#include "sram.h"
+#include "smb.h"
+#include "smw64.h"
+#include "ssf.h"
+#include "t5740.h"
+#include "tekkensp.h"
+#include "xboy.h"
+
+
+namespace bus::megadrive::slotoptions {
+
+char const *const MD_STD = "rom";
+char const *const MD_SSF2 = "rom_ssf2";
+char const *const HB_SSF = "rom_titan"; // TODO: rename
+char const *const HB_SSF_SRAM = "ssf_sram";
+char const *const HB_SSF_EX = "ssf_ex";
+char const *const HB_EVERDRIVE = "everdrive";
+char const *const MD_SRAM = "rom_sram";
+char const *const MD_SONIC3 = "rom_fram"; // TODO: change string
+char const *const MD_TPLAY96 = "rom_tplay96";
+char const *const MD_HARDBALL95 = "rom_hardbl95";
+char const *const MD_BARKLEY2 = "barkley2";
+char const *const MD_EEPROM = "rom_eeprom_mode1";
+char const *const MD_EEPROM_NBAJAM = "rom_nbajam_alt";
+char const *const MD_EEPROM_NBAJAMTE = "rom_nbajamte";
+char const *const MD_EEPROM_NFLQB96 = "rom_nflqb96";
+char const *const MD_EEPROM_COLLSLAM = "rom_cslam";
+char const *const MD_EEPROM_NHLPA = "rom_nhlpa";
+char const *const MD_EEPROM_BLARA95 = "rom_blara95";
+char const *const MD_EEPROM_BLARA96 = "rom_blara96";
+
+char const *const MD_CM2IN1 = "rom_cm2in1";
+char const *const MD_JCART_SAMPRAS = "rom_jcart_sampras";
+char const *const MD_JCART_SSKID = "rom_jcart";
+char const *const MD_JCART_MICROMAC2 = "rom_codemast";
+char const *const MD_JCART_MICROMAC96 = "rom_mm96";
+char const *const MD_SEGANET = "seganet";
+char const *const MD_TECTOY_SPORTS = "tectoy_sports";
+char const *const MD_3IN1_FWT = "3in1fwt";
+char const *const MC_PIRATE = "rom_mcpir"; // TODO: rename, what even MC stands for?
+char const *const MC_18KIN1 = "18kin1";
+char const *const MC_GOLDM250 = "goldm250";
+char const *const UNL_XINQIG = "rom_xinqig";
+char const *const HB_BEGGARP = "rom_sf001";
+char const *const HB_BEGGARP1 = "rom_sf001_beggarp1";
+char const *const HB_WUKONG = "rom_sf002";
+char const *const HB_STARODYS = "rom_sf004";
+char const *const UNL_TILESMJ2 = "rom_16mj2";
+char const *const UNL_ELFWOR = "rom_elfwor";
+char const *const UNL_SMOUSE = "rom_smouse";
+char const *const UNL_YASECH = "rom_yasech";
+char const *const UNL_777CASINO = "rom_777casino";
+char const *const UNL_SOULBLADE = "rom_soulb";
+char const *const UNL_SUPRBUBL = "rom_sbubl";
+char const *const UNL_CJMJCLUB = "rom_cjmjclub";
+char const *const UNL_MJLOV = "rom_mjlov";
+char const *const UNL_REDCLIFF = "rom_redcl";
+char const *const UNL_SQUIRRELK = "rom_squir";
+char const *const UNL_LIONKING2 = "rom_lion2";
+char const *const UNL_KOF98 = "rom_kof98";
+char const *const UNL_BUGSLIFE = "rom_bugs";
+char const *const UNL_POKEMONA = "rom_pokea";
+char const *const UNL_KOF99 = "rom_kof99";
+char const *const UNL_SMB = "rom_smb";
+char const *const UNL_SMB2 = "rom_smb2";
+char const *const UNL_ROCKMANX3 = "rom_rx3";
+char const *const UNL_SANGUO5 = "rom_sanguo5";
+char const *const UNL_AVARTISAN = "rom_realtec";
+char const *const UNL_TEKKENSP = "rom_tekkensp";
+char const *const UNL_TC2000 = "rom_tc2000";
+char const *const UNL_FUTBOL_ARG96 = "rom_sram_arg96";
+char const *const UNL_TOPF = "rom_lion3";
+char const *const UNL_POKESTAD = "rom_pokestad"; // TODO: alias of above, probably unneeded
+char const *const UNL_CHINF3 = "rom_chinf3";
+char const *const UNL_SMW64 = "rom_smw64";
+char const *const UNL_ROCKWORLD = "rockworld";
+char const *const UNL_ROCKHEAVEN = "rockheaven";
+char const *const HB_PSOLAR = "rom_stm95"; // TODO: rename
+
+char const *const ACTION_REPLAY = "ar";
+
+} // namespace bus::megadrive::slotoptions
+
+
+void megadrive_cart_options(device_slot_interface &device)
+{
+ using namespace bus::megadrive;
+
+ // normal
+ device.option_add_internal(slotoptions::MD_STD, MEGADRIVE_ROM);
+ device.option_add_internal(slotoptions::MD_SSF2, MEGADRIVE_ROM_SSF2);
+
+ // SRAM
+ device.option_add_internal(slotoptions::MD_SRAM, MEGADRIVE_ROM_SRAM);
+ device.option_add_internal(slotoptions::MD_SONIC3, MEGADRIVE_ROM_SONIC3);
+ device.option_add_internal(slotoptions::MD_TPLAY96, MEGADRIVE_ROM_TPLAY96);
+ device.option_add_internal(slotoptions::MD_HARDBALL95, MEGADRIVE_ROM_HARDBALL95);
+ device.option_add_internal(slotoptions::MD_BARKLEY2, MEGADRIVE_ROM_BARKLEY2);
+ device.option_add_internal(slotoptions::UNL_SANGUO5, MEGADRIVE_UNL_SANGUO5);
+
+ // EEPROM
+ device.option_add_internal(slotoptions::MD_EEPROM, MEGADRIVE_EEPROM);
+ device.option_add_internal(slotoptions::MD_EEPROM_NBAJAM, MEGADRIVE_EEPROM_NBAJAM);
+ device.option_add_internal(slotoptions::MD_EEPROM_NBAJAMTE, MEGADRIVE_EEPROM_NBAJAMTE);
+ device.option_add_internal(slotoptions::MD_EEPROM_NFLQB96, MEGADRIVE_EEPROM_NFLQB96);
+ device.option_add_internal(slotoptions::MD_EEPROM_COLLSLAM, MEGADRIVE_EEPROM_COLLSLAM);
+ device.option_add_internal(slotoptions::MD_EEPROM_NHLPA, MEGADRIVE_EEPROM_NHLPA);
+ device.option_add_internal(slotoptions::MD_EEPROM_BLARA95, MEGADRIVE_EEPROM_BLARA95);
+ device.option_add_internal(slotoptions::MD_EEPROM_BLARA96, MEGADRIVE_EEPROM_BLARA96);
+
+ // J-Cart
+ device.option_add_internal(slotoptions::MD_JCART_SAMPRAS, MEGADRIVE_ROM_JCART_SAMPRAS);
+ device.option_add_internal(slotoptions::MD_JCART_SSKID, MEGADRIVE_ROM_JCART_SSKID);
+ device.option_add_internal(slotoptions::MD_JCART_MICROMAC2, MEGADRIVE_ROM_JCART_MICROMAC2);
+ device.option_add_internal(slotoptions::MD_JCART_MICROMAC96, MEGADRIVE_ROM_JCART_MICROMAC96);
+
+ // reset based multigames
+ device.option_add_internal(slotoptions::MD_CM2IN1, MEGADRIVE_CM2IN1);
+ device.option_add_internal(slotoptions::MD_TECTOY_SPORTS, MEGADRIVE_TECTOY_SPORTS);
+ device.option_add_internal(slotoptions::MD_3IN1_FWT, MEGADRIVE_3IN1FWT);
+
+ // menu based multigames
+ device.option_add_internal(slotoptions::MD_SEGANET, MEGADRIVE_SEGANET);
+ device.option_add_internal(slotoptions::MC_PIRATE, MEGADRIVE_MCPIRATE);
+ device.option_add_internal(slotoptions::MC_18KIN1, MEGADRIVE_18KIN1);
+ device.option_add_internal(slotoptions::MC_GOLDM250, MEGADRIVE_GOLDM250);
+
+ // unlicensed
+ // Gamtec
+ device.option_add_internal(slotoptions::UNL_TILESMJ2, MEGADRIVE_UNL_TILESMJ2);
+ device.option_add_internal(slotoptions::UNL_ELFWOR, MEGADRIVE_UNL_ELFWOR);
+ device.option_add_internal(slotoptions::UNL_SMOUSE, MEGADRIVE_UNL_SMOUSE);
+ device.option_add_internal(slotoptions::UNL_YASECH, MEGADRIVE_UNL_YASECH);
+ device.option_add_internal(slotoptions::UNL_777CASINO, MEGADRIVE_UNL_777CASINO);
+ device.option_add_internal(slotoptions::UNL_SOULBLADE, MEGADRIVE_UNL_SOULBLADE);
+ device.option_add_internal(slotoptions::UNL_SUPRBUBL, MEGADRIVE_UNL_SUPRBUBL);
+ device.option_add_internal(slotoptions::UNL_CJMJCLUB, MEGADRIVE_UNL_CJMJCLUB);
+ device.option_add_internal(slotoptions::UNL_MJLOV, MEGADRIVE_UNL_MJLOV);
+ device.option_add_internal(slotoptions::UNL_REDCLIFF, MEGADRIVE_UNL_REDCLIFF);
+ device.option_add_internal(slotoptions::UNL_SQUIRRELK, MEGADRIVE_UNL_SQUIRRELK);
+ device.option_add_internal(slotoptions::UNL_LIONKING2, MEGADRIVE_UNL_LIONKING2);
+
+ // X Boy
+ device.option_add_internal(slotoptions::UNL_KOF98, MEGADRIVE_UNL_KOF98);
+ device.option_add_internal(slotoptions::UNL_BUGSLIFE, MEGADRIVE_UNL_BUGSLIFE);
+ device.option_add_internal(slotoptions::UNL_POKEMONA, MEGADRIVE_UNL_POKEMONA);
+ device.option_add_internal(slotoptions::UNL_KOF99, MEGADRIVE_UNL_KOF99);
+ device.option_add_internal(slotoptions::UNL_POKESTAD, MEGADRIVE_UNL_TOPF);
+ device.option_add_internal(slotoptions::UNL_TOPF, MEGADRIVE_UNL_TOPF);
+ device.option_add_internal(slotoptions::UNL_CHINF3, MEGADRIVE_UNL_CHINF3);
+
+ // Super Mario Bros
+ device.option_add_internal(slotoptions::UNL_SMB, MEGADRIVE_UNL_SMB);
+ device.option_add_internal(slotoptions::UNL_SMB2, MEGADRIVE_UNL_SMB2);
+ device.option_add_internal(slotoptions::UNL_ROCKMANX3, MEGADRIVE_UNL_ROCKMANX3);
+
+ // Super Mario World 64
+ device.option_add_internal(slotoptions::UNL_SMW64, MEGADRIVE_UNL_SMW64);
+
+ // AV Artisan
+ device.option_add_internal(slotoptions::UNL_AVARTISAN, MEGADRIVE_UNL_AVARTISAN);
+
+ // Taiwanese carts
+ device.option_add_internal(slotoptions::UNL_TEKKENSP, MEGADRIVE_UNL_TEKKENSP);
+
+ // Miky
+ device.option_add_internal(slotoptions::UNL_TC2000, MEGADRIVE_UNL_TC2000);
+ device.option_add_internal(slotoptions::UNL_FUTBOL_ARG96, MEGADRIVE_UNL_FUTBOL_ARG96);
+
+ // Rock Heaven / Rock World
+ device.option_add_internal(slotoptions::UNL_ROCKHEAVEN, MEGADRIVE_UNL_ROCKHEAVEN);
+ device.option_add_internal(slotoptions::UNL_ROCKWORLD, MEGADRIVE_UNL_ROCKWORLD);
+
+ // Action Replay
+ device.option_add_internal(slotoptions::ACTION_REPLAY, MEGADRIVE_ACTION_REPLAY);
+
+ // Homebrew
+ // Super Fighter Team
+ device.option_add_internal(slotoptions::UNL_XINQIG, MEGADRIVE_UNL_XINQIG);
+ device.option_add_internal(slotoptions::HB_BEGGARP, MEGADRIVE_HB_BEGGARP);
+ device.option_add_internal(slotoptions::HB_BEGGARP1, MEGADRIVE_HB_BEGGARP1);
+ device.option_add_internal(slotoptions::HB_WUKONG, MEGADRIVE_HB_WUKONG);
+ device.option_add_internal(slotoptions::HB_STARODYS, MEGADRIVE_HB_STARODYS);
+
+ // krikzz "SEGA SSF"
+ device.option_add_internal(slotoptions::HB_SSF, MEGADRIVE_HB_SSF);
+ device.option_add_internal(slotoptions::HB_SSF_SRAM, MEGADRIVE_HB_SSF_SRAM);
+ device.option_add_internal(slotoptions::HB_SSF_EX, MEGADRIVE_HB_SSF_EX);
+
+ // Everdrive based carts
+ device.option_add(slotoptions::HB_EVERDRIVE, MEGADRIVE_HB_EVERDRIVE);
+
+ // WaterMelon
+ device.option_add_internal(slotoptions::HB_PSOLAR, MEGADRIVE_HB_PSOLAR);
+
+}
diff --git a/src/devices/bus/megadrive/cart/options.h b/src/devices/bus/megadrive/cart/options.h
new file mode 100644
index 0000000000000..a5fffd6fa2cba
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/options.h
@@ -0,0 +1,83 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_OPTIONS_H
+#define MAME_BUS_MEGADRIVE_CART_OPTIONS_H
+
+#pragma once
+
+void megadrive_cart_options(device_slot_interface &device);
+
+namespace bus::megadrive::slotoptions {
+
+ extern char const *const MD_STD;
+ extern char const *const MD_SSF2;
+ extern char const *const MD_SRAM;
+ extern char const *const MD_SONIC3;
+ extern char const *const MD_TPLAY96;
+ extern char const *const MD_HARDBALL95;
+
+ extern char const *const MD_EEPROM;
+ extern char const *const MD_EEPROM_NBAJAM;
+ extern char const *const MD_EEPROM_NBAJAMTE;
+ extern char const *const MD_EEPROM_NFLQB96;
+ extern char const *const MD_EEPROM_COLLSLAM;
+ extern char const *const MD_EEPROM_NHLPA;
+
+ extern char const *const MD_CM2IN1;
+ extern char const *const MD_TECTOY_SPORTS;
+ extern char const *const MD_GAME_KANZUME;
+
+ extern char const *const MD_JCART_SAMPRAS;
+ extern char const *const MD_JCART_SSKID;
+ extern char const *const MD_JCART_MICROMAC2;
+ extern char const *const MD_JCART_MICROMAC96;
+
+ extern char const *const MC_PIRATE;
+ extern char const *const MC_18KIN1;
+ extern char const *const MC_GOLDM250;
+
+ extern char const *const UNL_XINQIG;
+ extern char const *const HB_BEGGARP;
+ extern char const *const HB_BEGGARP1;
+ extern char const *const HB_WUKONG;
+ extern char const *const HB_STARODYS;
+
+ extern char const *const UNL_TILESMJ2;
+ extern char const *const UNL_ELFWOR;
+ extern char const *const UNL_SMOUSE;
+ extern char const *const UNL_YASECH;
+ extern char const *const UNL_777CASINO;
+ extern char const *const UNL_SOULBLADE;
+ extern char const *const UNL_SUPRBUBL;
+ extern char const *const UNL_CJMJCLUB;
+ extern char const *const UNL_MJLOV;
+ extern char const *const UNL_REDCLIFF;
+ extern char const *const UNL_LIONKING2;
+ extern char const *const UNL_KOF98;
+ extern char const *const UNL_BUGSLIFE;
+ extern char const *const UNL_POKEMONA;
+ extern char const *const UNL_KOF99;
+ extern char const *const UNL_SMB;
+ extern char const *const UNL_SMB2;
+ extern char const *const UNL_ROCKMANX3;
+ extern char const *const UNL_TEKKENSP;
+ extern char const *const UNL_TC2000;
+
+ extern char const *const UNL_POKESTAD;
+ extern char const *const UNL_TOPF;
+ extern char const *const UNL_CHINF3;
+ extern char const *const UNL_SMW64;
+ extern char const *const UNL_ROCKWORLD;
+ extern char const *const UNL_ROCKHEAVEN;
+
+ extern char const *const UNL_SANGUO5;
+
+ extern char const *const ACTION_REPLAY;
+
+ extern char const *const HB_SSF;
+
+} // namespace bus::megadrive::slotoptions
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_OPTIONS_H
diff --git a/src/devices/bus/megadrive/cart/rockworld.cpp b/src/devices/bus/megadrive/cart/rockworld.cpp
new file mode 100644
index 0000000000000..bbeab560511ce
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/rockworld.cpp
@@ -0,0 +1,40 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+RockHeaven / RockWorld mapper
+
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "rockworld.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_ROCKHEAVEN, megadrive_unl_rockheaven_device, "megadrive_unl_rockheaven", "Megadrive Rock Heaven cart")
+
+megadrive_unl_rockheaven_device::megadrive_unl_rockheaven_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_ROCKHEAVEN, tag, owner, clock)
+{
+}
+
+void megadrive_unl_rockheaven_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x50'0008, 0x50'0009).lr16(NAME([] () { return 0x5082; }));
+}
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_ROCKWORLD, megadrive_unl_rockworld_device, "megadrive_unl_rockworld", "Megadrive Rock World cart")
+
+megadrive_unl_rockworld_device::megadrive_unl_rockworld_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_ROCKWORLD, tag, owner, clock)
+{
+}
+
+void megadrive_unl_rockworld_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x50'0008, 0x50'0009).lr16(NAME([] () { return 0x4000; }));
+ map(0x50'0208, 0x50'0209).lr16(NAME([] () { return 0xa000; }));
+}
+
+
diff --git a/src/devices/bus/megadrive/cart/rockworld.h b/src/devices/bus/megadrive/cart/rockworld.h
new file mode 100644
index 0000000000000..f1008e1625cd1
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/rockworld.h
@@ -0,0 +1,31 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_ROCKWORLD_H
+#define MAME_BUS_MEGADRIVE_CART_ROCKWORLD_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_rockheaven_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_rockheaven_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_rockworld_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_rockworld_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_ROCKHEAVEN, megadrive_unl_rockheaven_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_ROCKWORLD, megadrive_unl_rockworld_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_ROCKWORLD_H
diff --git a/src/devices/bus/megadrive/cart/rom.cpp b/src/devices/bus/megadrive/cart/rom.cpp
new file mode 100644
index 0000000000000..a2f84a2dd9233
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/rom.cpp
@@ -0,0 +1,127 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Megadrive generic ROM cart emulation
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "rom.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM, megadrive_rom_device, "megadrive_rom", "Megadrive ROM cart")
+
+megadrive_rom_device::megadrive_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_megadrive_cart_interface( mconfig, *this )
+ , m_rom(*this, "rom")
+{
+}
+
+megadrive_rom_device::megadrive_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_ROM, tag, owner, clock)
+{
+}
+
+void megadrive_rom_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ m_rom_mask = device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes()),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_rom->configure_entry(0, &base[0]);
+ });
+ m_rom_mirror = 0x3f'ffff ^ m_rom_mask;
+ logerror("Map linear rom with mask: %08x mirror: %08x\n", m_rom_mask, m_rom_mirror);
+}
+
+void megadrive_rom_device::device_reset()
+{
+}
+
+void megadrive_rom_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+}
+
+/*
+ * Super Street Fighter II
+ *
+ * Sega 315-5709 or 315-5779 mapper
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_SSF2, megadrive_rom_ssf2_device, "megadrive_rom_ssf2", "Megadrive SSF2 ROM cart")
+
+megadrive_rom_ssf2_device::megadrive_rom_ssf2_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_megadrive_cart_interface( mconfig, *this )
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+ , m_sram_view(*this, "sram_view")
+{
+}
+
+megadrive_rom_ssf2_device::megadrive_rom_ssf2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_ssf2_device(mconfig, MEGADRIVE_ROM_SSF2, tag, owner, clock)
+{
+}
+
+void megadrive_rom_ssf2_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x08'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 8; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+}
+
+void megadrive_rom_ssf2_device::device_reset()
+{
+ for (int i = 0; i < 8; i++)
+ m_rom_bank[i]->set_entry(i);
+ // Starts in SRAM disabled mode, thru /VRES connected
+ // (game initializes $f1 after checking memory banks at $30'0000)
+ m_sram_view.disable();
+}
+
+// need this for subclassing ("A memory_view can be present in only one address map")
+void megadrive_rom_ssf2_device::cart_bank_map(address_map &map)
+{
+ for (int i = 0; i < 8; i++)
+ {
+ const u32 page_size = 0x08'0000;
+
+ map(0x00'0000 | (page_size * i), 0x07'ffff | (page_size * i)).bankr(m_rom_bank[i]);
+ }
+}
+
+void megadrive_rom_ssf2_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).m(*this, FUNC(megadrive_rom_ssf2_device::cart_bank_map));
+ map(0x20'0000, 0x3f'ffff).view(m_sram_view);
+ m_sram_view[0](0x20'0000, 0x3f'ffff).unmaprw();
+}
+
+void megadrive_rom_ssf2_device::time_io_map(address_map &map)
+{
+ map(0xf1, 0xf1).lw8(NAME([this] (offs_t offset, u8 data) {
+ if (BIT(data, 1))
+ m_sram_view.disable();
+ else
+ m_sram_view.select(0);
+ }));
+ map(0xf3, 0xf3).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[1]->set_entry(data & 0xf); }));
+ map(0xf5, 0xf5).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[2]->set_entry(data & 0xf); }));
+ map(0xf7, 0xf7).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[3]->set_entry(data & 0xf); }));
+ map(0xf9, 0xf9).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[4]->set_entry(data & 0xf); }));
+ map(0xfb, 0xfb).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[5]->set_entry(data & 0xf); }));
+ map(0xfd, 0xfd).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[6]->set_entry(data & 0xf); }));
+ map(0xff, 0xff).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[7]->set_entry(data & 0xf); }));
+}
diff --git a/src/devices/bus/megadrive/cart/rom.h b/src/devices/bus/megadrive/cart/rom.h
new file mode 100644
index 0000000000000..c632dd33f137c
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/rom.h
@@ -0,0 +1,58 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_ROM_H
+#define MAME_BUS_MEGADRIVE_CART_ROM_H
+
+#pragma once
+
+#include "slot.h"
+//#include "machine/nvram.h"
+
+class megadrive_rom_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ megadrive_rom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+// bool check_rom(std::string &message) ATTR_COLD;
+ memory_bank_creator m_rom;
+
+ u32 m_rom_mask;
+ u32 m_rom_mirror;
+
+// void install_rom() ATTR_COLD;
+};
+
+class megadrive_rom_ssf2_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_rom_ssf2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+
+ void cart_bank_map(address_map &map);
+protected:
+ megadrive_rom_ssf2_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ memory_bank_array_creator<8> m_rom_bank;
+ memory_view m_sram_view;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM, megadrive_rom_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_SSF2, megadrive_rom_ssf2_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_ROM_H
diff --git a/src/devices/bus/megadrive/cart/seganet.cpp b/src/devices/bus/megadrive/cart/seganet.cpp
new file mode 100644
index 0000000000000..9a8dffe1ba612
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/seganet.cpp
@@ -0,0 +1,70 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Sega Channel Game no Kanzume "digest" RAM cart, developed by CRI
+
+https://segaretro.org/Game_no_Kanzume_Otokuyou
+
+TODO:
+- some unknowns, needs PCB picture
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "seganet.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_SEGANET, megadrive_seganet_device, "megadrive_seganet", "Megadrive Seganet Game no Kanzume RAM cart")
+
+
+megadrive_seganet_device::megadrive_seganet_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_SEGANET, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom(*this, "rom")
+ , m_ram_view(*this, "ram_view")
+{
+}
+
+void megadrive_seganet_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes()),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_rom->configure_entry(0, &base[0]);
+ });
+}
+
+void megadrive_seganet_device::device_reset()
+{
+ m_ram_view.disable();
+}
+
+void megadrive_seganet_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x2f'ffff).bankr(m_rom);
+ // NOTE: menu system writes extra couple writes at $40000,
+ // programming mistake?
+ map(0x00'0000, 0x03'ffff).view(m_ram_view);
+ m_ram_view[0](0x00'0000, 0x03'ffff).ram();
+}
+
+void megadrive_seganet_device::time_io_map(address_map &map)
+{
+// map(0x01, 0x01) unknown, used in tandem with 0xf1 writes, ram bank select?
+ map(0xf1, 0xf1).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ // assumed, bclr #$1 at PC=5e282
+ if (BIT(data, 1))
+ m_ram_view.disable();
+ else
+ m_ram_view.select(0);
+ // bit 0: write protect as per SSF2 mapper?
+ if (data & 0xfd)
+ popmessage("megadrive_seganet: unhandled $f1 write %02x", data);
+ })
+ );
+}
diff --git a/src/devices/bus/megadrive/cart/seganet.h b/src/devices/bus/megadrive/cart/seganet.h
new file mode 100644
index 0000000000000..ae43e121d0f99
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/seganet.h
@@ -0,0 +1,31 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SEGANET_H
+#define MAME_BUS_MEGADRIVE_CART_SEGANET_H
+
+#pragma once
+
+#include "slot.h"
+
+class megadrive_seganet_device : public device_t,
+ public device_megadrive_cart_interface
+{
+public:
+ megadrive_seganet_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ memory_bank_creator m_rom;
+ memory_view m_ram_view;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_SEGANET, megadrive_seganet_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_SEGANET_H
diff --git a/src/devices/bus/megadrive/cart/sfteam.cpp b/src/devices/bus/megadrive/cart/sfteam.cpp
new file mode 100644
index 0000000000000..62f7e31d21f0b
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/sfteam.cpp
@@ -0,0 +1,353 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Super Fighter Team protected mappers
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "sfteam.h"
+
+#include "bus/generic/slot.h"
+
+
+/*
+ * Xin Qi Gai Wang Zi
+ *
+ * NVRAM at $40'0000
+ * Some writes in ROM space at startup, PC=B4BEA (at least) tests that $100-$102
+ * returns SW vectors 0x46fc2700 and resets if unhappy, presumably against copiers.
+ *
+ * NOTES:
+ * - xinqig doesn't work on Teradrive TMSS, no SEGA at $100. Cursory testing was performed.
+ * - To quickly check saving: start a new game, skip intro with start button then go to the
+ * bottom-right house, enter into the aura circle and press start to bring inventory, select the
+ * left diary with pen icon (normally disabled).
+ * - beggarp (below) reuses aforementioned copier check, expects the original bank to live at $0.
+ * To trigger: after intro, go all the way down then first house on the left (player PoV).
+ * Character will stumble on key shaped tile, go forward a bit then exit the house
+ * (bp b4bea to be sure it's triggering)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_XINQIG, megadrive_unl_xinqig_device, "megadrive_unl_xinqig", "Megadrive Xin Qi Gai Wang Zi cart")
+
+megadrive_unl_xinqig_device::megadrive_unl_xinqig_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, type, tag, owner, clock)
+{
+}
+
+megadrive_unl_xinqig_device::megadrive_unl_xinqig_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_unl_xinqig_device(mconfig, MEGADRIVE_UNL_XINQIG, tag, owner, clock)
+{
+}
+
+u16 megadrive_unl_xinqig_device::get_nvram_length()
+{
+ return 0x4000;
+}
+
+void megadrive_unl_xinqig_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0000, 0x40'7fff).rw(FUNC(megadrive_unl_xinqig_device::nvram_r), FUNC(megadrive_unl_xinqig_device::nvram_w));
+}
+
+/*
+ * beggarp Beggar Prince
+ *
+ * Super Fighter Team rerelease of xinqig
+ *
+ * Reuses the original C&E mapper with an extra $e00 write handler.
+ *
+ * $3c'0030 contains a lengthy message explaining why this is protected to begin with.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_BEGGARP, megadrive_hb_beggarp_device, "megadrive_hb_beggarp", "Megadrive Beggar Prince cart")
+
+megadrive_hb_beggarp_device::megadrive_hb_beggarp_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_unl_xinqig_device(mconfig, type, tag, owner, clock)
+ , m_sf_rom_bank(*this, "sf_rom_bank")
+ , m_sf_rom_view(*this, "sf_rom_view")
+ , m_sram_view(*this, "sram_view")
+{
+}
+
+megadrive_hb_beggarp_device::megadrive_hb_beggarp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_hb_beggarp_device(mconfig, MEGADRIVE_HB_BEGGARP, tag, owner, clock)
+{
+}
+
+void megadrive_hb_beggarp_device::device_start()
+{
+ megadrive_unl_xinqig_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x04'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_sf_rom_bank->configure_entry(entry, &base[page * page_size]);
+ });
+}
+
+
+void megadrive_hb_beggarp_device::device_reset()
+{
+ megadrive_unl_xinqig_device::device_reset();
+ m_sram_view.disable();
+ m_sf_rom_view.disable();
+ // remaps 0x38'0000 to 0
+ m_sf_rom_bank->set_entry(0x0e);
+ m_bank_write_lock = false;
+}
+
+void megadrive_hb_beggarp_device::bank_write_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ // Writes 0xa0a0 after the Super Fighter Team logo, that unlocks the NVRAM
+ logerror("bank_write_w: %04x & %04x (%d)\n", data, mem_mask, m_bank_write_lock);
+ if (ACCESSING_BITS_0_7)
+ {
+ if (m_bank_write_lock)
+ return;
+ if (BIT(data, 7))
+ {
+ m_sram_view.select(0);
+ m_sf_rom_view.select(0);
+ }
+ else
+ {
+ m_sram_view.disable();
+ m_sf_rom_view.disable();
+ }
+
+ // TODO: is the game actually using this at all?
+ if (BIT(data, 6))
+ popmessage("megadrive_hb_beggarp_device: cart locked! %04x & %04x", data, mem_mask);
+
+ // once enabled this lock needs an hard reset to lift
+ m_bank_write_lock = !!(BIT(data, 5));
+ }
+}
+
+void megadrive_hb_beggarp_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x00'0000, 0x03'ffff).view(m_sf_rom_view);
+ m_sf_rom_view[0](0x00'0000, 0x03'ffff).bankr(m_sf_rom_bank);
+ map(0x00'0e00, 0x00'0e01).w(FUNC(megadrive_hb_beggarp_device::bank_write_w));
+ map(0x40'0000, 0x40'7fff).view(m_sram_view);
+ m_sram_view[0](0x40'0000, 0x40'7fff).rw(FUNC(megadrive_hb_beggarp_device::nvram_r), FUNC(megadrive_hb_beggarp_device::nvram_w));
+}
+
+
+
+/*
+ * beggarp1 Beggar Prince rev 1
+ *
+ * Relocates NVRAM, probably to make game compatible with an expansion attached.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_BEGGARP1, megadrive_hb_beggarp1_device, "megadrive_hb_beggarp1", "Megadrive Beggar Prince rev 1 cart")
+
+megadrive_hb_beggarp1_device::megadrive_hb_beggarp1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_hb_beggarp_device(mconfig, MEGADRIVE_HB_BEGGARP1, tag, owner, clock)
+{
+}
+
+void megadrive_hb_beggarp1_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x00'0000, 0x03'ffff).view(m_sf_rom_view);
+ m_sf_rom_view[0](0x00'0000, 0x03'ffff).bankr(m_sf_rom_bank);
+ map(0x00'0e00, 0x00'0e01).w(FUNC(megadrive_hb_beggarp1_device::bank_write_w));
+ map(0x3c'0000, 0x3f'ffff).view(m_sram_view);
+ m_sram_view[0](0x3c'0000, 0x3c'7fff).mirror(0x03'8000).rw(FUNC(megadrive_hb_beggarp1_device::nvram_r), FUNC(megadrive_hb_beggarp1_device::nvram_w));
+}
+
+/*
+ * wukong Legend of Wukong
+ *
+ * More $e00 style writes. Swaps the two halves around.
+ *
+ * TODO:
+ * - technically should derive from megadrive_unl_yasech_device
+ *
+ */
+
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_WUKONG, megadrive_hb_wukong_device, "megadrive_hb_wukong", "Megadrive Legend of Wukong cart")
+
+megadrive_hb_wukong_device::megadrive_hb_wukong_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, MEGADRIVE_HB_WUKONG, tag, owner, clock)
+ , m_sf_rom_bank(*this, "sf_rom_bank")
+ , m_sram_view(*this, "sram_view")
+{
+}
+
+void megadrive_hb_wukong_device::device_start()
+{
+ megadrive_rom_tplay96_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x20'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_sf_rom_bank->configure_entry(entry, &base[page * page_size]);
+ });
+
+}
+
+void megadrive_hb_wukong_device::device_reset()
+{
+ megadrive_rom_tplay96_device::device_reset();
+ m_sram_view.disable();
+ m_sf_rom_bank->set_entry(1);
+}
+
+u16 megadrive_hb_wukong_device::get_nvram_length()
+{
+ // unverified, game just use up to $3c'095f (0x800)
+ return 0x2000;
+}
+
+void megadrive_hb_wukong_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x1f'ffff).bankr(m_rom);
+ map(0x20'0000, 0x3f'ffff).bankr(m_sf_rom_bank);
+ map(0x00'0e00, 0x00'0e01).lw16(
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ // 0x00 on Super Fighter Team logo
+ // 0x81 afterwards (where it checks that SRAM works)
+ logerror("bank_write_w: %04x & %04x\n", data, mem_mask);
+ if (ACCESSING_BITS_0_7)
+ {
+ m_sf_rom_bank->set_entry(!BIT(data, 7));
+ if (BIT(data, 0))
+ m_sram_view.select(0);
+ else
+ m_sram_view.disable();
+ }
+ })
+ );
+ map(0x3c'0000, 0x3f'ffff).view(m_sram_view);
+ m_sram_view[0](0x3c'0000, 0x3c'3fff).mirror(0x03'c000).rw(FUNC(megadrive_hb_wukong_device::nvram_r), FUNC(megadrive_hb_wukong_device::nvram_w));
+}
+
+/*
+ * starodys Star Odyssey
+ * https://segaretro.org/Star_Odyssey_(Super_Fighter_Team)
+ *
+ *
+ *
+ */
+
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_STARODYS, megadrive_hb_starodys_device, "megadrive_hb_starodys", "Megadrive Star Odyssey cart")
+
+megadrive_hb_starodys_device::megadrive_hb_starodys_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, MEGADRIVE_HB_STARODYS, tag, owner, clock)
+ , m_sf_rom_bank(*this, "sf_rom_bank_%u", 0U)
+ , m_sf_rom_view(*this, "sf_rom_view")
+ , m_sram_view(*this, "sram_view")
+{
+}
+
+void megadrive_hb_starodys_device::device_start()
+{
+ megadrive_rom_tplay96_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x04'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 5; i++)
+ m_sf_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+}
+
+void megadrive_hb_starodys_device::device_reset()
+{
+ megadrive_rom_tplay96_device::device_reset();
+ m_sram_view.disable();
+ for (int i = 0; i < 5; i++)
+ m_sf_rom_bank[i]->set_entry(i);
+ m_sf_rom_view.select(0);
+ m_bank_num = 0;
+ m_bank_write_lock = false;
+}
+
+u16 megadrive_hb_starodys_device::get_nvram_length()
+{
+ // checks up to $20'3fff
+ return 0x8000;
+}
+
+void megadrive_hb_starodys_device::cart_map(address_map &map)
+{
+ //map(0x00'0000, 0x1f'ffff).mirror(0x20'0000).bankr(m_rom);
+ map(0x00'0000, 0x1f'ffff).view(m_sf_rom_view);
+ m_sf_rom_view[0](0x00'0000, 0x03'ffff).mirror(0x1c'0000).bankr(m_sf_rom_bank[0]);
+ m_sf_rom_view[1](0x00'0000, 0x03'ffff).bankr(m_sf_rom_bank[0]);
+ m_sf_rom_view[1](0x04'0000, 0x07'ffff).bankr(m_sf_rom_bank[1]);
+ m_sf_rom_view[1](0x08'0000, 0x0b'ffff).bankr(m_sf_rom_bank[2]);
+ m_sf_rom_view[1](0x0c'0000, 0x0f'ffff).bankr(m_sf_rom_bank[3]);
+ m_sf_rom_view[1](0x10'0000, 0x13'ffff).bankr(m_sf_rom_bank[4]);
+ map(0x00'0d00, 0x00'0d01).lw16(
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask)
+ {
+ logerror("$d00: %04x & %04x (%d)\n", data, mem_mask, m_bank_write_lock);
+ if (m_bank_write_lock)
+ return;
+
+ if (BIT(data, 7))
+ m_sram_view.select(0);
+ else
+ m_sram_view.disable();
+ })
+ );
+ map(0x00'0e00, 0x00'0e01).lw16(
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask)
+ {
+ logerror("$e00: %04x & %04x (%d)\n", data, mem_mask, m_bank_write_lock);
+ if (m_bank_write_lock)
+ return;
+
+ m_bank_write_lock = !!(BIT(~data, 7));
+ m_sf_rom_view.select(BIT(data, 6));
+ // TODO: is the game actually using this at all?
+ if (BIT(data, 5))
+ popmessage("megadrive_hb_starodys_device: cart locked! %04x & %04x", data, mem_mask);
+ })
+ );
+ map(0x00'0f00, 0x00'0f01).lw16(
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask)
+ {
+ logerror("$e00: %04x & %04x (%d)\n", data, mem_mask, m_bank_write_lock);
+ if (m_bank_write_lock)
+ return;
+
+ m_bank_num = (data >> 4) & 7;
+ for (int i = 0; i < 5; i++)
+ m_sf_rom_bank[i]->set_entry((m_bank_num + i) & 7);
+ })
+ );
+ map(0x20'0000, 0x2f'ffff).view(m_sram_view);
+ m_sram_view[0](0x20'0000, 0x2f'ffff).rw(FUNC(megadrive_hb_starodys_device::nvram_r), FUNC(megadrive_hb_starodys_device::nvram_w));
+}
+
+void megadrive_hb_starodys_device::time_io_map(address_map &map)
+{
+ // $a13'001, $a13'005, $a13'009 checked at startup
+ // $a13'00f after loading a game
+ map(0x01, 0x01).mirror(0xfe).lr8(
+ NAME([this] (offs_t offset) {
+ return m_bank_num << 4;
+ })
+ );
+}
diff --git a/src/devices/bus/megadrive/cart/sfteam.h b/src/devices/bus/megadrive/cart/sfteam.h
new file mode 100644
index 0000000000000..bd4c48fe9aa06
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/sfteam.h
@@ -0,0 +1,102 @@
+
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SFTEAM_H
+#define MAME_BUS_MEGADRIVE_CART_SFTEAM_H
+
+#pragma once
+
+#include "machine/nvram.h"
+
+#include "rom.h"
+#include "sram.h"
+#include "slot.h"
+
+
+class megadrive_unl_xinqig_device : public megadrive_rom_tplay96_device
+{
+public:
+ megadrive_unl_xinqig_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ megadrive_unl_xinqig_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+ virtual u16 get_nvram_length() override ATTR_COLD;
+};
+
+class megadrive_hb_beggarp_device : public megadrive_unl_xinqig_device
+{
+public:
+ megadrive_hb_beggarp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ megadrive_hb_beggarp_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ memory_bank_creator m_sf_rom_bank;
+ memory_view m_sf_rom_view;
+ memory_view m_sram_view;
+
+ void bank_write_w(offs_t offset, u16 data, u16 mem_mask);
+private:
+ bool m_bank_write_lock;
+};
+
+class megadrive_hb_beggarp1_device : public megadrive_hb_beggarp_device
+{
+public:
+ megadrive_hb_beggarp1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_hb_wukong_device : public megadrive_rom_tplay96_device
+{
+public:
+ megadrive_hb_wukong_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ virtual u16 get_nvram_length() override ATTR_COLD;
+private:
+ memory_bank_creator m_sf_rom_bank;
+ memory_view m_sram_view;
+};
+
+class megadrive_hb_starodys_device : public megadrive_rom_tplay96_device
+{
+public:
+ megadrive_hb_starodys_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ virtual u16 get_nvram_length() override ATTR_COLD;
+private:
+ memory_bank_array_creator<5> m_sf_rom_bank;
+ memory_view m_sf_rom_view;
+ memory_view m_sram_view;
+
+ u8 m_bank_num;
+ bool m_bank_write_lock;
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_XINQIG, megadrive_unl_xinqig_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_BEGGARP, megadrive_hb_beggarp_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_BEGGARP1, megadrive_hb_beggarp1_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_WUKONG, megadrive_hb_wukong_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_STARODYS, megadrive_hb_starodys_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_SFTEAM_H
+
diff --git a/src/devices/bus/megadrive/cart/slot.cpp b/src/devices/bus/megadrive/cart/slot.cpp
new file mode 100644
index 0000000000000..71fa2d8b83b58
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/slot.cpp
@@ -0,0 +1,342 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#include "emu.h"
+#include "slot.h"
+
+#include "options.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_CART_SLOT, megadrive_cart_slot_device, "megadrive_cart_slot", "MegaDrive Cartridge Slot")
+
+device_megadrive_cart_interface::device_megadrive_cart_interface(const machine_config &mconfig, device_t &device)
+ : device_interface(device, "megadrive_cart")
+ , m_slot(dynamic_cast(device.owner()))
+{
+}
+
+device_megadrive_cart_interface::~device_megadrive_cart_interface()
+{
+}
+
+void device_megadrive_cart_interface::interface_pre_start()
+{
+ if (!m_slot->started())
+ throw device_missing_dependencies();
+}
+
+void device_megadrive_cart_interface::interface_post_start()
+{
+ m_slot->m_space_mem->install_device(0x000000, 0xdfffff, *this, &device_megadrive_cart_interface::cart_map);
+ m_slot->m_space_io->install_device(0x00, 0xff, *this, &device_megadrive_cart_interface::time_io_map);
+}
+
+void device_megadrive_cart_interface::cart_map(address_map &map)
+{
+ // NOTE: is host responsibility to handle open bus access
+// map(0x000000, 0x3fffff).unmaprw();
+}
+
+// $a13000 base
+void device_megadrive_cart_interface::time_io_map(address_map &map)
+{
+// map(0x00, 0xff).unmaprw();
+}
+
+
+//void device_megadrive_cart_interface::rom_alloc(size_t size)
+//{
+// if (m_rom == nullptr)
+// {
+// m_rom = (uint16_t *)device().machine().memory().region_alloc(device().subtag("^cart:rom"), size, 2, ENDIANNESS_BIG)->base();
+// m_rom_size = size;
+// m_rom_mask = size - 1;
+// }
+//}
+
+
+megadrive_cart_slot_device::megadrive_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_CART_SLOT, tag, owner, clock)
+ , device_memory_interface(mconfig, *this)
+ , device_cartrom_image_interface(mconfig, *this)
+ , device_single_card_slot_interface(mconfig, *this)
+ , m_cart(nullptr)
+ , m_space_mem_config("cart_mem", ENDIANNESS_BIG, 16, 24, 0, address_map_constructor())
+ , m_space_io_config("time_io", ENDIANNESS_BIG, 16, 8, 0, address_map_constructor())
+{
+}
+
+megadrive_cart_slot_device::~megadrive_cart_slot_device()
+{
+}
+
+device_memory_interface::space_config_vector megadrive_cart_slot_device::memory_space_config() const
+{
+ return space_config_vector{
+ std::make_pair(AS_PROGRAM, &m_space_mem_config),
+ std::make_pair(AS_IO, &m_space_io_config)
+ };
+}
+
+void megadrive_cart_slot_device::device_start()
+{
+ m_cart = get_card_device();
+ m_space_mem = &space(AS_PROGRAM);
+ m_space_io = &space(AS_IO);
+}
+
+const char *megadrive_cart_slot_device::get_cart_type(const uint8_t *ROM, uint32_t len)
+{
+ using namespace bus::megadrive;
+
+// int type = slotoptions::MD_STD;
+ // TODO: not caring about loose detection for now
+ return slotoptions::MD_STD;
+}
+
+//static int md_get_pcb_id(const char *slot)
+//{
+// for (auto & elem : slot_list)
+// {
+// if (!strcmp(elem.slot_option, slot))
+// return elem.pcb_id;
+// }
+//
+// return MD_STD;
+//}
+
+//static const char *md_get_slot(int type)
+//{
+// for (auto & elem : slot_list)
+// {
+// if (elem.pcb_id == type)
+// return elem.slot_option;
+// }
+//
+// return "rom";
+//}
+
+std::pair megadrive_cart_slot_device::call_load()
+{
+ if (m_cart)
+ {
+ std::error_condition err;
+
+ if (loaded_through_softlist())
+ {
+ err = load_swlist();
+ }
+ else
+ {
+ err = load_loose();
+ }
+
+ if (err)
+ return std::make_pair(err ? err : std::errc::io_error, "Error loading detection");
+ }
+
+ return std::make_pair(std::error_condition(), std::string());
+}
+
+std::error_condition megadrive_cart_slot_device::load_swlist()
+{
+ using namespace bus::megadrive;
+
+// uint16_t *ROM;
+// uint32_t length = get_software_region_length("rom");
+ const char *slot_name;
+//
+// // if cart size is not (2^n * 64K), the system will see anyway that size so we need to alloc a bit more space
+// length = m_cart->get_padded_size(length);
+//
+// memory_region *const romregion = machine().memory().region_alloc(subtag("rom"), length, 2, ENDIANNESS_BIG);
+// auto const [err, actual] = read_at(file, offset, romregion->base(), len);
+// if (err || (len != actual))
+// return std::make_pair(err ? err : std::errc::io_error, "Error reading ROM data from cartridge file");
+//
+//// m_cart->rom_alloc(length);
+//// ROM = m_cart->get_rom_base();
+// memcpy(romregion, get_software_region("rom"), get_software_region_length("rom"));
+//
+// // if we allocated a ROM larger that the file (e.g. due to uneven cart size), set remaining space to 0xff
+// if (length > get_software_region_length("rom"))
+// memset(romregion + get_software_region_length("rom")/2, 0xffff, (length - get_software_region_length("rom"))/2);
+//
+ slot_name = get_feature("slot");
+ if (slot_name == nullptr)
+ slot_name = slotoptions::MD_STD;
+
+// printf("%s\n", slot_name);
+ return std::error_condition(m_cart->load());
+}
+
+std::error_condition megadrive_cart_slot_device::load_loose()
+{
+ return std::error_condition();
+
+// unsigned char *ROM;
+// bool is_smd, is_md;
+// uint32_t tmplen = length(), offset, len;
+// std::vector tmpROM(tmplen);
+//
+// // STEP 1: store a (possibly headered) copy of the file and determine its type (SMD? MD? BIN?)
+// fread(&tmpROM[0], tmplen);
+// is_smd = false; //genesis_is_SMD(&tmpROM[0x200], tmplen - 0x200);
+// is_md = (tmpROM[0x80] == 'E') && (tmpROM[0x81] == 'A') && (tmpROM[0x82] == 'M' || tmpROM[0x82] == 'G');
+//
+// // take header into account, if any
+// offset = is_smd ? 0x200 : 0;
+//
+// // STEP 2: allocate space for the real copy of the game
+// // if cart size is not (2^n * 64K), the system will see anyway that size so we need to alloc a bit more space
+// len = m_cart->get_padded_size(tmplen - offset);
+//
+// // this contains an hack for SSF2: its current bankswitch code needs larger ROM space to work
+// // m_cart->rom_alloc((len == 0x500000) ? 0x900000 : len);
+//
+// // STEP 3: copy the game data in the appropriate way
+// ROM = (unsigned char *)m_cart->get_rom_base();
+//
+// if (is_smd)
+// {
+// osd_printf_debug("SMD!\n");
+//
+// for (int ptr = 0; ptr < (tmplen - 0x200) / 0x2000; ptr += 2)
+// {
+// for (int x = 0; x < 0x2000; x++)
+// {
+// ROM[ptr * 0x2000 + x * 2 + 0] = tmpROM[0x200 + ((ptr + 1) * 0x2000) + x];
+// ROM[ptr * 0x2000 + x * 2 + 1] = tmpROM[0x200 + ((ptr + 0) * 0x2000) + x];
+// }
+// }
+// }
+// else if (is_md)
+// {
+// osd_printf_debug("MD!\n");
+//
+// for (int ptr = 0; ptr < tmplen; ptr += 2)
+// {
+// ROM[ptr] = tmpROM[(tmplen >> 1) + (ptr >> 1)];
+// ROM[ptr + 1] = tmpROM[(ptr >> 1)];
+// }
+// }
+// else
+// {
+// osd_printf_debug("BIN!\n");
+//
+// fseek(0, SEEK_SET);
+// fread(ROM, len);
+// }
+//
+// // if we allocated a ROM larger that the file (e.g. due to uneven cart size), set remaining space to 0xff
+// //if (len > (tmplen - offset))
+// // memset(m_cart->get_rom_base() + (tmplen - offset)/2, 0xffff, (len - tmplen + offset)/2);
+//
+// // STEP 4: determine the cart type (to deal with sram/eeprom & pirate mappers)
+// // m_type = get_cart_type(ROM, tmplen - offset);
+//
+//// CPU needs to access ROM as a ROM_REGION16_BE, so we need to compensate on LE machines
+//#ifdef LSB_FIRST
+// unsigned char fliptemp;
+// for (int ptr = 0; ptr < len; ptr += 2)
+// {
+// fliptemp = ROM[ptr];
+// ROM[ptr] = ROM[ptr+1];
+// ROM[ptr+1] = fliptemp;
+// }
+//#endif
+//
+// return std::error_condition();
+}
+
+
+std::string megadrive_cart_slot_device::get_default_card_software(get_default_card_software_hook &hook) const
+{
+ using namespace bus::megadrive;
+
+ if (hook.image_file())
+ {
+ uint64_t len;
+
+ if (hook.image_file()->length(len))
+ {
+ osd_printf_warning("[%s] Error getting cartridge ROM length - defaulting to linear ROM type\n", tag());
+ return slotoptions::MD_STD;
+ }
+
+ std::vector rom(len);
+
+ // FIXME: check error return or read returning short
+ util::read(*hook.image_file(), &rom[0], len);
+
+ uint32_t const offset = 0; // genesis_is_SMD(&rom[0x200], len - 0x200) ? 0x200 : 0;
+
+// int const type = get_cart_type(&rom[offset], len - offset);
+ char const *const slot_string = get_cart_type(&rom[offset], len - offset);
+
+ return std::string(slot_string);
+ }
+ else
+ {
+ return software_get_default_slot(slotoptions::MD_STD);
+ }
+}
+
+
+uint32_t device_megadrive_cart_interface::get_padded_size(uint32_t size)
+{
+ uint32_t pad_size = 0x10000;
+ while (size > pad_size)
+ pad_size <<= 1;
+
+ if (pad_size < 0x800000 && size < pad_size)
+ return pad_size;
+ else
+ return size;
+}
+
+
+void megadrive_cart_slot_device::call_unload()
+{
+ if (m_cart)
+ m_cart->unload();
+// if (m_cart && m_cart->get_nvram_base() && m_cart->get_nvram_size())
+// battery_save(m_cart->get_nvram_base(), m_cart->get_nvram_size());
+}
+
+// 0x000000-0x3fffff
+// shift << 1 here and not in memory constructor for two reasons:
+// 1. cart has no A0 connected
+// 2. memory_views would get confused by the shift with
+// "A memory_view must be installed at its configuration address."
+u16 megadrive_cart_slot_device::base_r(offs_t offset, u16 mem_mask)
+{
+ return m_space_mem->read_word(offset << 1, mem_mask);
+}
+
+void megadrive_cart_slot_device::base_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ m_space_mem->write_word(offset << 1, data, mem_mask);
+}
+
+u16 megadrive_cart_slot_device::time_r(offs_t offset, u16 mem_mask)
+{
+ return m_space_io->read_word(offset << 1, mem_mask);
+}
+
+void megadrive_cart_slot_device::time_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ m_space_io->write_word(offset << 1, data, mem_mask);
+}
+
diff --git a/src/devices/bus/megadrive/cart/slot.h b/src/devices/bus/megadrive/cart/slot.h
new file mode 100644
index 0000000000000..8a7da94fc086c
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/slot.h
@@ -0,0 +1,106 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SLOT_H
+#define MAME_BUS_MEGADRIVE_CART_SLOT_H
+
+#pragma once
+
+#include "imagedev/cartrom.h"
+
+class device_megadrive_cart_interface;
+
+class megadrive_cart_slot_device : public device_t,
+ public device_memory_interface,
+ public device_cartrom_image_interface,
+ public device_single_card_slot_interface
+{
+public:
+ // construction/destruction
+ template
+ megadrive_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock, T &&opts, char const *dflt)
+ : megadrive_cart_slot_device(mconfig, tag, owner, clock)
+ {
+ option_reset();
+ opts(*this);
+ set_default_option(dflt);
+ set_fixed(false);
+ }
+ megadrive_cart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+ virtual ~megadrive_cart_slot_device();
+
+ // TODO: should be false for megapc
+ virtual bool is_reset_on_load() const noexcept override { return true; }
+ virtual const char *image_interface() const noexcept override { return "megadriv_cart"; }
+ virtual const char *file_extensions() const noexcept override { return "smd,bin,md,gen"; }
+
+ u16 base_r(offs_t offset, u16 mem_mask);
+ void base_w(offs_t offset, u16 data, u16 mem_mask);
+
+ u16 time_r(offs_t offset, u16 mem_mask);
+ void time_w(offs_t offset, u16 data, u16 mem_mask);
+
+ virtual std::pair call_load() override;
+ virtual void call_unload() override;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual space_config_vector memory_space_config() const override;
+
+ virtual std::string get_default_card_software(get_default_card_software_hook &hook) const override;
+ static const char *get_cart_type(const uint8_t *ROM, uint32_t len);
+private:
+ device_megadrive_cart_interface *m_cart;
+
+ friend class device_megadrive_cart_interface;
+
+ address_space_config m_space_mem_config;
+ address_space_config m_space_io_config;
+
+ address_space *m_space_mem;
+ address_space *m_space_io;
+
+ std::error_condition load_swlist();
+ std::error_condition load_loose();
+};
+
+class device_megadrive_cart_interface : public device_interface
+{
+public:
+ // construction/destruction
+ virtual ~device_megadrive_cart_interface();
+
+ uint32_t get_padded_size(uint32_t size);
+
+ 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_sram_region() { return m_slot ? m_slot->memregion("sram") : 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); }
+
+ virtual std::error_condition load() ATTR_COLD { return std::error_condition(); };
+ virtual void unload() ATTR_COLD { };
+
+protected:
+ device_megadrive_cart_interface(const machine_config &mconfig, device_t &device);
+
+ virtual void interface_pre_start() override;
+ virtual void interface_post_start() override;
+ megadrive_cart_slot_device *const m_slot;
+
+ virtual void cart_map(address_map &map) ATTR_COLD;
+ virtual void time_io_map(address_map &map) ATTR_COLD;
+
+ // device_start, /MRES B2
+ // device_reset, /VRES B27
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_CART_SLOT, megadrive_cart_slot_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_SLOT_H
diff --git a/src/devices/bus/megadrive/cart/smb.cpp b/src/devices/bus/megadrive/cart/smb.cpp
new file mode 100644
index 0000000000000..b14c661fa97e3
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/smb.cpp
@@ -0,0 +1,79 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Super Mario Bros cart mappers
+
+"decode error" printed if protection fails
+
+TODO:
+- are these really hacked versions? Rockman X3 does a few Gamtec style checks, ignored by the code
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "smb.h"
+
+/*
+ * Super Mario World / Super Mario Bros.
+ * https://segaretro.org/Super_Mario_World
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SMB, megadrive_unl_smb_device, "megadrive_unl_smb", "Megadrive Super Mario World cart")
+
+megadrive_unl_smb_device::megadrive_unl_smb_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SMB, tag, owner, clock)
+{
+}
+
+void megadrive_unl_smb_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).lr16(NAME([] () { return 0x001c; }));
+}
+
+/*
+ * Super Mario Bros. 2
+ * https://segaretro.org/Super_Mario_2_1998
+ *
+ */
+
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SMB2, megadrive_unl_smb2_device, "megadrive_unl_smb2", "Megadrive Super Mario Bros 2 cart")
+
+megadrive_unl_smb2_device::megadrive_unl_smb2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SMB2, tag, owner, clock)
+{
+}
+
+void megadrive_unl_smb2_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).lr16(NAME([] () { return 0x000a; }));
+}
+
+/*
+ * Rockman X3
+ * https://segaretro.org/Rockman_X3_(Mega_Drive)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_ROCKMANX3, megadrive_unl_rockmanx3_device, "megadrive_unl_rockmanx3", "Megadrive Rockman X3 cart")
+
+megadrive_unl_rockmanx3_device::megadrive_unl_rockmanx3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_ROCKMANX3, tag, owner, clock)
+{
+}
+
+void megadrive_unl_rockmanx3_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x40'0004, 0x40'0004).lr8(NAME([] () { return 0xc9; })); // PC=a9d2, startup, anded with 0x9c
+ map(0x40'0006, 0x40'0006).lr8(NAME([] () { return 0xf0; })); // PC=12e0, start of level
+
+}
+
+void megadrive_unl_rockmanx3_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).lr16(NAME([] () { return 0x000c; }));
+}
+
diff --git a/src/devices/bus/megadrive/cart/smb.h b/src/devices/bus/megadrive/cart/smb.h
new file mode 100644
index 0000000000000..581e5c9da2f4c
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/smb.h
@@ -0,0 +1,44 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SMB_H
+#define MAME_BUS_MEGADRIVE_CART_SMB_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_smb_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_smb_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_smb2_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_smb2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_rockmanx3_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_rockmanx3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SMB, megadrive_unl_smb_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SMB2, megadrive_unl_smb2_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_ROCKMANX3, megadrive_unl_rockmanx3_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_SMB_H
diff --git a/src/devices/bus/megadrive/cart/smw64.cpp b/src/devices/bus/megadrive/cart/smw64.cpp
new file mode 100644
index 0000000000000..5e66a2dbe8ded
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/smw64.cpp
@@ -0,0 +1,141 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Super Mario World 64 cart mappers
+
+https://segaretro.org/Super_Mario_World_64
+
+Looks its own thing compared to anything else
+
+TODO:
+- some blanks, sound can crash after a while (which looks the main thing they bothered to protect);
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "smw64.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SMW64, megadrive_unl_smw64_device, "megadrive_unl_smw64", "Megadrive Super Mario World 64 cart")
+
+megadrive_unl_smw64_device::megadrive_unl_smw64_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SMW64, tag, owner, clock)
+ , m_page_rom(*this, "page_rom_%u", 0U)
+{
+}
+
+void megadrive_unl_smw64_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x01'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ m_page_rom[0]->configure_entry(entry, &base[page * page_size]);
+ m_page_rom[1]->configure_entry(entry, &base[page * page_size]);
+ });
+
+ save_pointer(NAME(m_latch), 3);
+ save_pointer(NAME(m_ctrl), 2);
+ save_pointer(NAME(m_data), 2);
+ save_item(NAME(m_page_67));
+}
+
+void megadrive_unl_smw64_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+
+ m_page_rom[0]->set_entry(8);
+ m_page_rom[1]->set_entry(8);
+}
+
+void megadrive_unl_smw64_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x07'ffff).mirror(0x08'0000).bankr(m_rom);
+ map(0x60'0000, 0x60'ffff).mirror(0x08'0000).bankr(m_page_rom[0]);
+ map(0x61'0000, 0x61'ffff).mirror(0x08'0000).bankr(m_page_rom[1]);
+ map(0x60'0001, 0x60'0001).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_ctrl[0] = data;
+ })
+ );
+ map(0x60'0003, 0x60'0003).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ switch(m_ctrl[0] & 7)
+ {
+ case 0: m_latch[0] = ((m_latch[0] ^ m_data[0]) ^ data) & 0xfe; break;
+ case 1: m_latch[1] = data & 0xfe; break;
+ // often, unknown purpose
+ case 6: break;
+ case 7: m_page_rom[1]->set_entry(8 + ((data & 0x1c) >> 2)); break;
+ default:
+ logerror("$60'0003 unknown mode %02x\n", m_ctrl[0]);
+ break;
+ }
+ m_data[0] = data;
+ })
+ );
+ map(0x61'0003, 0x61'0003).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_page_61 = data;
+ })
+ );
+ map(0x64'0001, 0x64'0001).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_ctrl[1] = data;
+ })
+ );
+ // 0x64'0003 rmw at startup (& 0xc0 ^ 0x55)
+ map(0x64'0003, 0x64'0003).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_data[1] = data;
+ })
+ );
+ // unknown writes, in tandem with mode 6 in $60'0003
+ map(0x66'0000, 0x66'0001).nopw();
+ map(0x66'0001, 0x66'0001).lr8(NAME([this] () { return m_latch[0]; }));
+ map(0x66'0003, 0x66'0003).lr8(NAME([this] () { return m_latch[0] + 1; }));
+ map(0x66'0005, 0x66'0005).lr8(NAME([this] () { return m_latch[1]; }));
+ map(0x66'0007, 0x66'0007).lr8(NAME([this] () { return m_latch[1] + 1; }));
+ map(0x66'0009, 0x66'0009).lr8(NAME([this] () { return m_latch[2]; }));
+ map(0x66'000b, 0x66'000b).lr8(NAME([this] () { return m_latch[2] + 1; }));
+ map(0x66'000d, 0x66'000d).lr8(NAME([this] () { return m_latch[2] + 2; }));
+ map(0x66'000f, 0x66'000f).lr8(NAME([this] () { return m_latch[2] + 3; }));
+
+ map(0x67'0001, 0x67'0001).select(2).lr8(
+ NAME([this] (offs_t offset) {
+ u8 res = 0;
+ if (BIT(m_page_61, 7))
+ {
+ if (BIT(m_page_67, 6))
+ res = m_ctrl[1] & m_data[1];
+ else
+ res = m_ctrl[1] ^ 0xff;
+ }
+
+ if (BIT(offset, 1))
+ res &= 0x7f;
+ else if (!machine().side_effects_disabled() && BIT(m_page_67, 7))
+ {
+ if (BIT(m_page_67, 5))
+ m_latch[2] = (m_data[1] << 2) & 0xfc;
+ else
+ m_latch[0] = (m_data[0] ^ (m_ctrl[1] << 1)) & 0xfe;
+ }
+
+ return res;
+ })
+ );
+ map(0x67'0001, 0x67'0001).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_page_67 = data;
+ if (BIT(m_page_61, 7))
+ m_page_rom[0]->set_entry(8 + ((data & 0x1c) >> 2));
+ })
+ );
+
+}
diff --git a/src/devices/bus/megadrive/cart/smw64.h b/src/devices/bus/megadrive/cart/smw64.h
new file mode 100644
index 0000000000000..c5e615beeb41e
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/smw64.h
@@ -0,0 +1,37 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SMW64_H
+#define MAME_BUS_MEGADRIVE_CART_SMW64_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_smw64_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_smw64_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ static constexpr feature_type imperfect_features() { return feature::PROTECTION; }
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+private:
+ memory_bank_array_creator<2> m_page_rom;
+
+ u8 m_ctrl[2];
+ u8 m_data[2];
+ u8 m_latch[3];
+ u8 m_page_61, m_page_67;
+};
+
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SMW64, megadrive_unl_smw64_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_SMW64_H
diff --git a/src/devices/bus/megadrive/cart/sram.cpp b/src/devices/bus/megadrive/cart/sram.cpp
new file mode 100644
index 0000000000000..e20e968acc409
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/sram.cpp
@@ -0,0 +1,296 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#include "emu.h"
+#include "sram.h"
+
+/*
+ * Generic ROM + SRAM (Sega style)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_SRAM, megadrive_rom_sram_device, "megadrive_rom_sram", "Megadrive Sega ROM + SRAM cart")
+
+megadrive_rom_sram_device::megadrive_rom_sram_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, type, tag, owner, clock)
+ , m_sram_view(*this, "sram_view")
+{
+}
+
+megadrive_rom_sram_device::megadrive_rom_sram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_sram_device(mconfig, MEGADRIVE_ROM_SRAM, tag, owner, clock)
+{
+}
+
+
+std::error_condition megadrive_rom_sram_device::load()
+{
+ memory_region *const nvramregion(cart_sram_region());
+
+ if (nvramregion)
+ {
+ m_nvram_base = reinterpret_cast(nvramregion->base());
+ m_nvram_size = nvramregion->bytes();
+ m_nvram_mask = m_nvram_size - 1;
+
+ if (m_nvram_size & m_nvram_mask)
+ return image_error::BADSOFTWARE;
+
+ save_pointer(NAME(m_nvram_base), m_nvram_size);
+ battery_load(m_nvram_base, m_nvram_size, nullptr);
+ }
+ else
+ {
+ osd_printf_error("sram region not found\n");
+ return image_error::BADSOFTWARE;
+ }
+
+ return std::error_condition();
+}
+
+
+void megadrive_rom_sram_device::unload()
+{
+ memory_region *const nvramregion(this->cart_sram_region());
+ if (nvramregion && nvramregion->bytes())
+ this->battery_save(nvramregion->base(), nvramregion->bytes());
+}
+
+void megadrive_rom_sram_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ memory_region *const nvramregion(cart_sram_region());
+ // FIXME: initialize this thru load fn
+ if (!nvramregion)
+ throw emu_fatalerror("Missing SRAM region, cannot initialize\n");
+ m_nvram_size = nvramregion->bytes();
+ m_nvram_mask = m_nvram_size - 1;
+ save_item(NAME(m_nvram_write_protect));
+}
+
+void megadrive_rom_sram_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ m_nvram_write_protect = false;
+ m_sram_view.select(0);
+}
+
+
+void megadrive_rom_sram_device::cart_map(address_map &map)
+{
+ megadrive_rom_device::cart_map(map);
+ // TODO: most if not all of them really uses 8-bit interface
+ map(0x20'0000, 0x20'0001 | m_nvram_mask).view(m_sram_view);
+ m_sram_view[0](0x20'0000, 0x20'0001 | m_nvram_mask).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_nvram_base[offset];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ if (!m_nvram_write_protect)
+ m_nvram_base[offset] = data;
+ })
+ );
+}
+
+void megadrive_rom_sram_device::time_io_map(address_map &map)
+{
+ // TODO: does all Sega games have this?
+ // there must be a threshold where this is a thing vs. where is not ...
+ map(0xf1, 0xf1).lw8(NAME([this] (offs_t offset, u8 data) {
+ if (BIT(data, 1))
+ m_sram_view.disable();
+ else
+ m_sram_view.select(0);
+ m_nvram_write_protect = !!BIT(data, 0);
+ }));
+}
+
+/*
+ * Sonic 3
+ * Uses it's own (earlier?) version where bit 0 is the view select, swapped, and no write protect
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_SONIC3, megadrive_rom_sonic3_device, "megadrive_rom_sonic3", "Megadrive Sonic 3 ROM + SRAM cart")
+
+megadrive_rom_sonic3_device::megadrive_rom_sonic3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_sram_device(mconfig, MEGADRIVE_ROM_SONIC3, tag, owner, clock)
+{
+}
+
+void megadrive_rom_sonic3_device::time_io_map(address_map &map)
+{
+ map(0xf1, 0xf1).lw8(NAME([this] (offs_t offset, u8 data) {
+ if (BIT(data, 0))
+ m_sram_view.select(0);
+ else
+ m_sram_view.disable();
+ }));
+}
+
+/*
+ * Triple Play '96
+ *
+ * 8-bit NVRAM with NO write protection control lanes
+ * At title screen hold A+B+C then press start, SFX plays, release all to get prompted for NVRAM
+ * reinitialize.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_TPLAY96, megadrive_rom_tplay96_device, "megadrive_rom_tplay96", "Megadrive Triple Play '96 cart")
+
+megadrive_rom_tplay96_device::megadrive_rom_tplay96_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, type, tag, owner, clock)
+ , m_nvram(*this, "nvram")
+{
+}
+
+megadrive_rom_tplay96_device::megadrive_rom_tplay96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, MEGADRIVE_ROM_TPLAY96, tag, owner, clock)
+{
+}
+
+
+void megadrive_rom_tplay96_device::device_add_mconfig(machine_config &config)
+{
+ NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1);
+}
+
+u16 megadrive_rom_tplay96_device::get_nvram_length()
+{
+ return 0x8000;
+}
+
+void megadrive_rom_tplay96_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ const u32 nvram_size = get_nvram_length();
+ m_nvram_ptr = std::make_unique(nvram_size);
+ m_nvram->set_base(m_nvram_ptr.get(), nvram_size);
+
+ save_pointer(NAME(m_nvram_ptr), nvram_size);
+ m_nvram_mask = nvram_size - 1;
+}
+
+u16 megadrive_rom_tplay96_device::nvram_r(offs_t offset)
+{
+ const u32 nvram_offset = offset & m_nvram_mask;
+ return 0xff00 | m_nvram_ptr[nvram_offset];
+}
+
+void megadrive_rom_tplay96_device::nvram_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (ACCESSING_BITS_0_7)
+ {
+ const u32 nvram_offset = offset & m_nvram_mask;
+ m_nvram_ptr[nvram_offset] = data & 0xff;
+ }
+}
+
+void megadrive_rom_tplay96_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x20'0000, 0x20'ffff).rw(FUNC(megadrive_rom_tplay96_device::nvram_r), FUNC(megadrive_rom_tplay96_device::nvram_w));
+}
+
+/*
+ * Hardball '95
+ * Relocated NVRAM
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_HARDBALL95, megadrive_rom_hardball95_device, "megadrive_rom_hardball95", "Megadrive Hardball '95 cart")
+
+megadrive_rom_hardball95_device::megadrive_rom_hardball95_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, MEGADRIVE_ROM_HARDBALL95, tag, owner, clock)
+{
+}
+
+void megadrive_rom_hardball95_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x30'0000, 0x30'ffff).rw(FUNC(megadrive_rom_hardball95_device::nvram_r), FUNC(megadrive_rom_hardball95_device::nvram_w));
+}
+
+/*
+ * Barkley Shut Up and Jam 2
+ * header indicates $20'0001 - $20'0fff as SRAM but in-game it uses a mirror at $23'xxxx
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_ROM_BARKLEY2, megadrive_rom_barkley2_device, "megadrive_rom_barkley2", "Megadrive Barkley Shut Up and Jam 2 cart")
+
+megadrive_rom_barkley2_device::megadrive_rom_barkley2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_tplay96_device(mconfig, MEGADRIVE_ROM_BARKLEY2, tag, owner, clock)
+{
+}
+
+u16 megadrive_rom_barkley2_device::get_nvram_length()
+{
+ return 0x800;
+}
+
+void megadrive_rom_barkley2_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x1f'ffff).bankr(m_rom);
+ map(0x20'0000, 0x3f'ffff).rw(FUNC(megadrive_rom_barkley2_device::nvram_r), FUNC(megadrive_rom_barkley2_device::nvram_w));
+}
+
+
+
+/*
+ * San Guo Zhi V / Tun Shi Tian Di III
+ *
+ * sanguo5 https://segaretro.org/San_Guo_Zhi_V
+ * tunshi / tunshi1 https://segaretro.org/Tun_Shi_Tian_Di_III
+ *
+ * SKOB published games with invalid header and SRAM at $20'0000
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_SANGUO5, megadrive_unl_sanguo5_device, "megadrive_unl_sanguo5", "Megadrive San Guo Zhi V cart")
+
+
+megadrive_unl_sanguo5_device::megadrive_unl_sanguo5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_SANGUO5, tag, owner, clock)
+ , m_nvram(*this, "nvram")
+{
+}
+
+void megadrive_unl_sanguo5_device::device_add_mconfig(machine_config &config)
+{
+ NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1);
+}
+
+void megadrive_unl_sanguo5_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ const u32 nvram_size = 0x2000;
+ m_nvram_ptr = std::make_unique(nvram_size);
+ m_nvram->set_base(m_nvram_ptr.get(), nvram_size);
+
+ save_pointer(NAME(m_nvram_ptr), nvram_size);
+}
+
+u16 megadrive_unl_sanguo5_device::nvram_r(offs_t offset)
+{
+ const u32 nvram_offset = offset & 0x1fff;
+ return 0xff00 | m_nvram_ptr[nvram_offset];
+}
+
+void megadrive_unl_sanguo5_device::nvram_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (ACCESSING_BITS_0_7)
+ {
+ const u32 nvram_offset = offset & 0x1fff;
+ m_nvram_ptr[nvram_offset] = data & 0xff;
+ }
+}
+
+void megadrive_unl_sanguo5_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ map(0x20'0000, 0x20'3fff).rw(FUNC(megadrive_unl_sanguo5_device::nvram_r), FUNC(megadrive_unl_sanguo5_device::nvram_w));
+}
+
+
diff --git a/src/devices/bus/megadrive/cart/sram.h b/src/devices/bus/megadrive/cart/sram.h
new file mode 100644
index 0000000000000..a9b9b75002de7
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/sram.h
@@ -0,0 +1,115 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SRAM_H
+#define MAME_BUS_MEGADRIVE_CART_SRAM_H
+
+#pragma once
+
+#include "machine/nvram.h"
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_rom_sram_device : public megadrive_rom_device
+{
+public:
+ megadrive_rom_sram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+
+protected:
+ megadrive_rom_sram_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ virtual std::error_condition load() override ATTR_COLD;
+ virtual void unload() override ATTR_COLD;
+
+ memory_view m_sram_view;
+private:
+ u8 *m_nvram_base;
+ u32 m_nvram_size, m_nvram_mask;
+
+ bool m_nvram_write_protect;
+};
+
+class megadrive_rom_sonic3_device : public megadrive_rom_sram_device
+{
+public:
+ megadrive_rom_sonic3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_rom_tplay96_device : public megadrive_rom_device
+{
+public:
+ megadrive_rom_tplay96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ megadrive_rom_tplay96_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ virtual u16 get_nvram_length() ATTR_COLD;
+
+ u16 nvram_r(offs_t offset);
+ void nvram_w(offs_t offset, u16 data, u16 mem_mask);
+
+private:
+ required_device m_nvram;
+ std::unique_ptr m_nvram_ptr;
+ u16 m_nvram_mask;
+};
+
+class megadrive_rom_hardball95_device : public megadrive_rom_tplay96_device
+{
+public:
+ megadrive_rom_hardball95_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_rom_barkley2_device : public megadrive_rom_tplay96_device
+{
+public:
+ megadrive_rom_barkley2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual u16 get_nvram_length() override ATTR_COLD;
+};
+
+class megadrive_unl_sanguo5_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_sanguo5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ u16 nvram_r(offs_t offset);
+ void nvram_w(offs_t offset, u16 data, u16 mem_mask);
+
+private:
+ required_device m_nvram;
+ std::unique_ptr m_nvram_ptr;
+};
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_SRAM, megadrive_rom_sram_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_SONIC3, megadrive_rom_sonic3_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_TPLAY96, megadrive_rom_tplay96_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_HARDBALL95, megadrive_rom_hardball95_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_ROM_BARKLEY2, megadrive_rom_barkley2_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_SANGUO5, megadrive_unl_sanguo5_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_SRAM_H
diff --git a/src/devices/bus/megadrive/cart/ssf.cpp b/src/devices/bus/megadrive/cart/ssf.cpp
new file mode 100644
index 0000000000000..c62ae6c5a83f5
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/ssf.cpp
@@ -0,0 +1,223 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+Krikzz "SEGA SSF" mapper
+
+https://krikzz.com/pub/support/mega-everdrive/v1/dev/extended_ssf-v2.txt
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "ssf.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_SSF, megadrive_hb_ssf_device, "megadrive_hb_ssf", "Megadrive Krikzz Sega SSF cart")
+
+megadrive_hb_ssf_device::megadrive_hb_ssf_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_ssf2_device(mconfig, type, tag, owner, clock)
+{
+}
+
+megadrive_hb_ssf_device::megadrive_hb_ssf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_hb_ssf_device(mconfig, MEGADRIVE_HB_SSF, tag, owner, clock)
+{
+}
+
+// NOTE: same as Sega mapper (Demons of Asteborg wants it this way)
+void megadrive_hb_ssf_device::time_f0_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (BIT(data, 1))
+ m_sram_view.disable();
+ else
+ m_sram_view.select(0);
+ m_nvram_write_protect = !!BIT(data, 0);
+}
+
+
+void megadrive_hb_ssf_device::time_io_map(address_map &map)
+{
+ map(0xf0, 0xf1).w(FUNC(megadrive_hb_ssf_device::time_f0_w));
+ map(0xf3, 0xf3).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[1]->set_entry(data & 0x1f); }));
+ map(0xf5, 0xf5).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[2]->set_entry(data & 0x1f); }));
+ map(0xf7, 0xf7).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[3]->set_entry(data & 0x1f); }));
+ map(0xf9, 0xf9).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[4]->set_entry(data & 0x1f); }));
+ map(0xfb, 0xfb).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[5]->set_entry(data & 0x1f); }));
+ map(0xfd, 0xfd).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[6]->set_entry(data & 0x1f); }));
+ map(0xff, 0xff).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[7]->set_entry(data & 0x1f); }));
+}
+
+/*
+ * Demons of Asteborg
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_SSF_SRAM, megadrive_hb_ssf_sram_device, "megadrive_hb_ssf_sram", "Megadrive Krikzz Sega SSF + SRAM cart")
+
+megadrive_hb_ssf_sram_device::megadrive_hb_ssf_sram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_hb_ssf_device(mconfig, MEGADRIVE_HB_SSF_SRAM, tag, owner, clock)
+ , m_nvram(*this, "nvram")
+{
+}
+
+void megadrive_hb_ssf_sram_device::device_add_mconfig(machine_config &config)
+{
+ NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1);
+}
+
+void megadrive_hb_ssf_sram_device::device_start()
+{
+ megadrive_hb_ssf_device::device_start();
+ const u32 nvram_size = 0x8000;
+ m_nvram_ptr = std::make_unique(nvram_size);
+ m_nvram->set_base(m_nvram_ptr.get(), nvram_size);
+
+ save_pointer(NAME(m_nvram_ptr), nvram_size);
+}
+
+u16 megadrive_hb_ssf_sram_device::nvram_r(offs_t offset)
+{
+ const u32 nvram_offset = offset & 0xffff;
+ return 0xff00 | m_nvram_ptr[nvram_offset];
+}
+
+void megadrive_hb_ssf_sram_device::nvram_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (ACCESSING_BITS_0_7)
+ {
+ const u32 nvram_offset = offset & 0xffff;
+ m_nvram_ptr[nvram_offset] = data & 0xff;
+ }
+}
+
+void megadrive_hb_ssf_sram_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).m(*this, FUNC(megadrive_hb_ssf_sram_device::cart_bank_map));
+ map(0x20'0000, 0x20'ffff).view(m_sram_view);
+ m_sram_view[0](0x20'0000, 0x20'ffff).rw(FUNC(megadrive_hb_ssf_sram_device::nvram_r), FUNC(megadrive_hb_ssf_sram_device::nvram_w));
+}
+
+/*
+ * krikzz SSF actual extended SSF
+ *
+ * TODO:
+ * - SD card;
+ * - USB (2.0?);
+ * - LED;
+ * - is math unit signed or unsigned? Sample ROM doesn't seem to care;
+ * - what "support 16-bit or 32-bit" really mean? Are args reset after use? Sample ROM doesn't care
+ * again;
+ * - division by zero actual values;
+ *
+ */
+
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_SSF_EX, megadrive_hb_ssf_ex_device, "megadrive_hb_ssf_ex", "Megadrive Krikzz Sega SSF Extended cart")
+
+megadrive_hb_ssf_ex_device::megadrive_hb_ssf_ex_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_hb_ssf_device(mconfig, MEGADRIVE_HB_SSF_EX, tag, owner, clock)
+{
+}
+
+void megadrive_hb_ssf_ex_device::device_start()
+{
+ megadrive_hb_ssf_device::device_start();
+ save_pointer(STRUCT_MEMBER(m_math, arg), 2);
+ save_pointer(STRUCT_MEMBER(m_math, mul), 2);
+ save_pointer(STRUCT_MEMBER(m_math, div), 2);
+}
+
+void megadrive_hb_ssf_ex_device::device_reset()
+{
+ megadrive_hb_ssf_device::device_reset();
+ m_sram_view.select(0);
+ // undefined at /VRES
+ for (int i = 0; i < 2; i++)
+ {
+ m_math[i].arg = 0xffff;
+ m_math[i].mul = 0xffff;
+ m_math[i].div = 0xffff;
+ }
+}
+
+/*
+ * needs to be a word write
+ * 1--- ---- ---- ---- unlock writes to this register
+ * -x-- ---- ---- ---- 32X mode
+ * --x- ---- ---- ---- ROM memory write protect
+ * ---x ---- ---- ---- LED
+ * ---- ---- ---x xxxx ROM bank [0]
+ */
+void megadrive_hb_ssf_ex_device::time_f0_w(offs_t offset, u16 data, u16 mem_mask)
+ {
+ logerror("time_f0_w %04x & %04x (%s)\n", data, mem_mask, BIT(data, 15) ? "valid" : "ignored");
+ if (BIT(data, 15))
+ {
+ m_sram_view.select(BIT(data, 13));
+
+ // m_led_output = BIT(data, 12);
+
+ m_rom_bank[0]->set_entry(data & 0x1f);
+ }
+}
+
+void megadrive_hb_ssf_ex_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).view(m_sram_view);
+ m_sram_view[0](0x00'0000, 0x3f'ffff).m(*this, FUNC(megadrive_hb_ssf_ex_device::cart_bank_map));
+ for (int i = 0; i < 8; i++)
+ {
+ const u32 page_size = 0x08'0000;
+
+ m_sram_view[1](0x00'0000 | (page_size * i), 0x07'ffff | (page_size * i)).bankrw(m_rom_bank[i]);
+ }
+}
+
+void megadrive_hb_ssf_ex_device::time_io_map(address_map &map)
+{
+ megadrive_hb_ssf_device::time_io_map(map);
+ map(0xd0, 0xd3).lrw16(
+ NAME([this] (offs_t offset) {
+ return m_math[offset].arg;
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ COMBINE_DATA(&m_math[offset].arg);
+ })
+ );
+ map(0xd4, 0xd7).lw16(
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ COMBINE_DATA(&m_math[offset].mul);
+ if (offset)
+ {
+ const u32 arg = (m_math[0].arg << 16) | m_math[1].arg;
+ const u32 mul = (m_math[0].mul << 16) | m_math[1].mul;
+ const u32 res = arg * mul;
+ m_math[0].arg = res >> 16;
+ m_math[1].arg = res & 0xffff;
+ logerror("mul unit: %08x * %08x = %08x\n", arg, mul, res);
+ }
+ })
+ );
+ map(0xd8, 0xdb).lw16(
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ COMBINE_DATA(&m_math[offset].div);
+ if (offset)
+ {
+ const u32 arg = (m_math[0].arg << 16) | m_math[1].arg;
+ const u32 div = (m_math[0].div << 16) | m_math[1].div;
+ const u32 res = div ? arg / div : 0xffff'ffff;
+ m_math[0].arg = res >> 16;
+ m_math[1].arg = res & 0xffff;
+ logerror("div unit: %08x / %08x = %08x\n", arg, div, res);
+ }
+ })
+ );
+// map(0xe0, 0xe1).rw SD card data
+// map(0xe2, 0xe3).rw USB data (8-bit)
+// map(0xe4, 0xe5).r (14) SPI controller ready
+// (2) USB FIFO write ready
+// (1) USB FIFO read ready
+// (0) SPI controller ready
+// map(0xe6, 0xe7).w (2) SPI auto read (16-bits only)
+// (1) 16bit SPI mode
+// (0) SD card chip select
+}
diff --git a/src/devices/bus/megadrive/cart/ssf.h b/src/devices/bus/megadrive/cart/ssf.h
new file mode 100644
index 0000000000000..acb85748c2279
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/ssf.h
@@ -0,0 +1,79 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SSF_H
+#define MAME_BUS_MEGADRIVE_CART_SSF_H
+
+#pragma once
+
+#include "machine/nvram.h"
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_hb_ssf_device : public megadrive_rom_ssf2_device
+{
+public:
+ megadrive_hb_ssf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+protected:
+ megadrive_hb_ssf_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_f0_w(offs_t offset, u16 data, u16 mem_mask);
+
+ bool m_nvram_write_protect;
+};
+
+class megadrive_hb_ssf_sram_device : public megadrive_hb_ssf_device
+{
+public:
+ megadrive_hb_ssf_sram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ virtual void device_start() override ATTR_COLD;
+
+ u16 nvram_r(offs_t offset);
+ void nvram_w(offs_t offset, u16 data, u16 mem_mask);
+private:
+ required_device m_nvram;
+ std::unique_ptr m_nvram_ptr;
+};
+
+class megadrive_hb_ssf_ex_device : public megadrive_hb_ssf_device
+{
+public:
+ megadrive_hb_ssf_ex_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ static constexpr feature_type unemulated_features() { return feature::MEDIA | feature::DISK; }
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) 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;
+
+ virtual void time_f0_w(offs_t offset, u16 data, u16 mem_mask) override;
+private:
+ struct math_unit_t {
+ u16 arg;
+ u16 mul;
+ u16 div;
+ };
+
+ math_unit_t m_math[2];
+};
+
+
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_SSF, megadrive_hb_ssf_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_SSF_SRAM, megadrive_hb_ssf_sram_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_SSF_EX, megadrive_hb_ssf_ex_device)
+
+#endif // MAME_BUS_MEGADRIVE_CART_SSF_H
diff --git a/src/devices/bus/megadrive/cart/t5740.cpp b/src/devices/bus/megadrive/cart/t5740.cpp
new file mode 100644
index 0000000000000..33819a34a7683
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/t5740.cpp
@@ -0,0 +1,102 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+WaterMelon T-5740
+
+https://segaretro.org/Pier_Solar_and_the_Great_Architects
+
+TODO:
+- several unknowns in TIME io range;
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "t5740.h"
+
+#include "bus/generic/slot.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_HB_PSOLAR, megadrive_hb_psolar_device, "megadrive_hb_psolar", "Megadrive Pier Solar T-5740 cart")
+
+megadrive_hb_psolar_device::megadrive_hb_psolar_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, MEGADRIVE_HB_PSOLAR, tag, owner, clock)
+ , device_megadrive_cart_interface( mconfig, *this )
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+ , m_spi_eeprom(*this, "spi_eeprom")
+{
+}
+
+void megadrive_hb_psolar_device::device_add_mconfig(machine_config &config)
+{
+ M95320_EEPROM(config, m_spi_eeprom, 0);
+}
+
+void megadrive_hb_psolar_device::device_start()
+{
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x08'0000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 8; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+}
+
+void megadrive_hb_psolar_device::device_reset()
+{
+ for (int i = 0; i < 8; i++)
+ m_rom_bank[i]->set_entry(i);
+}
+
+void megadrive_hb_psolar_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x07'ffff).bankr(m_rom_bank[0]);
+ // HACK: protection workaround
+ // At startup each bank is 32K mirrored, and an unspecified condition unlocks the
+ // full bank. PC=15e6 reads it 3 times, with byte accesses, expecting to read the cart header
+ // otherwise will crash during the "not endorsed by Sega" screen.
+ map(0x01'8100, 0x01'81ff).lr16(NAME([this] (offs_t offset, u16 mem_mask) {
+ const auto base = reinterpret_cast(m_rom_bank[0]->base());
+ const u32 rom_address = 0x1'8100 + (offset << 1);
+ if (mem_mask != 0xffff)
+ {
+ return base[(rom_address & 0x7fff) >> 1];
+ }
+ return base[rom_address >> 1];
+ }));
+ map(0x08'0000, 0x0f'ffff).bankr(m_rom_bank[1]);
+ map(0x10'0000, 0x17'ffff).bankr(m_rom_bank[2]);
+ map(0x18'0000, 0x1f'ffff).bankr(m_rom_bank[3]);
+ map(0x20'0000, 0x27'ffff).bankr(m_rom_bank[4]);
+ map(0x28'0000, 0x2f'ffff).bankr(m_rom_bank[5]);
+ map(0x30'0000, 0x37'ffff).bankr(m_rom_bank[6]);
+ map(0x38'0000, 0x3f'ffff).bankr(m_rom_bank[7]);
+}
+
+void megadrive_hb_psolar_device::time_io_map(address_map &map)
+{
+ map(0x01, 0x01).lw8(NAME([] (offs_t offset, u8 data) {
+ //logerror("Mode %02x\n", data);
+ // x--- enable SPI bus?
+ // --x- enable bankswitch addresses?
+ // ---x always on
+ }));
+ map(0x03, 0x03).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[5]->set_entry(data & 0xf); }));
+ map(0x05, 0x05).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[6]->set_entry(data & 0xf); }));
+ map(0x07, 0x07).lw8(NAME([this] (offs_t offset, u8 data) { m_rom_bank[7]->set_entry(data & 0xf); }));
+ map(0x09, 0x09).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ m_spi_eeprom->set_si_line(BIT(data, 0));
+ m_spi_eeprom->set_sck_line(BIT(data, 1));
+ m_spi_eeprom->set_halt_line(BIT(data, 2));
+ m_spi_eeprom->set_cs_line(BIT(data, 3));
+ })
+ );
+ map(0x0b, 0x0b).lr8(
+ NAME([this] (offs_t offset) {
+ return m_spi_eeprom->get_so_line() & 1;
+ })
+ );
+}
diff --git a/src/devices/bus/megadrive/cart/t5740.h b/src/devices/bus/megadrive/cart/t5740.h
new file mode 100644
index 0000000000000..0259e3fa49b66
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/t5740.h
@@ -0,0 +1,34 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_T5740_H
+#define MAME_BUS_MEGADRIVE_CART_T5740_H
+
+#pragma once
+
+#include "machine/m95320.h"
+
+#include "slot.h"
+
+class megadrive_hb_psolar_device : public device_t
+ , public device_megadrive_cart_interface
+{
+public:
+ megadrive_hb_psolar_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+ virtual void time_io_map(address_map &map) 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:
+ memory_bank_array_creator<8> m_rom_bank;
+ required_device m_spi_eeprom;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_HB_PSOLAR, megadrive_hb_psolar_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_T5740_H
diff --git a/src/devices/bus/megadrive/cart/tekkensp.cpp b/src/devices/bus/megadrive/cart/tekkensp.cpp
new file mode 100644
index 0000000000000..6a1697df336aa
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/tekkensp.cpp
@@ -0,0 +1,64 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+https://segaretro.org/Tekken_Special
+
+Unidentified protection chip, developer not known
+
+TODO:
+- identify and verify on HW all the unused combinations;
+- chip mirror, if any;
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "tekkensp.h"
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_TEKKENSP, megadrive_unl_tekkensp_device, "megadrive_unl_tekkensp", "Megadrive Tekken Special cart")
+
+megadrive_unl_tekkensp_device::megadrive_unl_tekkensp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_TEKKENSP, tag, owner, clock)
+{
+}
+
+void megadrive_unl_tekkensp_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ save_item(NAME(m_prot_latch));
+}
+
+void megadrive_unl_tekkensp_device::device_reset()
+{
+ megadrive_rom_device::device_reset();
+ // undefined, initialized by game anyway
+ m_prot_latch = 0;
+}
+
+template void megadrive_unl_tekkensp_device::prot_shift_w(u8 data)
+{
+ if (BIT(data, 0))
+ {
+ m_prot_latch |= (1 << N);
+ }
+}
+
+void megadrive_unl_tekkensp_device::cart_map(address_map &map)
+{
+ map(0x00'0000, m_rom_mask).mirror(m_rom_mirror).bankr(m_rom);
+ // reset?
+ map(0x40'0000, 0x40'0000).lw8(NAME([this] (offs_t offset, u8 data) { (void)data; m_prot_latch = 0; }));
+ // -1 is likely coming from the mode
+ map(0x40'0002, 0x40'0002).lr8(NAME([this] (offs_t offset) { return m_prot_latch - 1; }));
+ map(0x40'0004, 0x40'0004).w(FUNC(megadrive_unl_tekkensp_device::prot_shift_w<0>));
+ map(0x40'0006, 0x40'0006).w(FUNC(megadrive_unl_tekkensp_device::prot_shift_w<1>));
+ map(0x40'0008, 0x40'0008).w(FUNC(megadrive_unl_tekkensp_device::prot_shift_w<2>));
+ map(0x40'000a, 0x40'000a).w(FUNC(megadrive_unl_tekkensp_device::prot_shift_w<3>));
+ map(0x40'000c, 0x40'000c).lw8(NAME([this] (offs_t offset, u8 data) {
+ if (!BIT(data, 0))
+ popmessage("tekkensp.cpp: data output mode bit 0 == 0");
+ }));
+ map(0x40'000e, 0x40'000e).lw8(NAME([this] (offs_t offset, u8 data) {
+ popmessage("tekkensp.cpp: data output mode bit 1 == %d", BIT(data, 0));
+ }));
+}
diff --git a/src/devices/bus/megadrive/cart/tekkensp.h b/src/devices/bus/megadrive/cart/tekkensp.h
new file mode 100644
index 0000000000000..4dd80310cdfd0
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/tekkensp.h
@@ -0,0 +1,29 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_TEKKENSP_H
+#define MAME_BUS_MEGADRIVE_CART_TEKKENSP_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_tekkensp_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_tekkensp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+private:
+ template void prot_shift_w(u8 data);
+ u8 m_prot_latch;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_TEKKENSP, megadrive_unl_tekkensp_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_TEKKENSP_H
diff --git a/src/devices/bus/megadrive/cart/xboy.cpp b/src/devices/bus/megadrive/cart/xboy.cpp
new file mode 100644
index 0000000000000..389eeb848f9f2
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/xboy.cpp
@@ -0,0 +1,216 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+/**************************************************************************************************
+
+X Boy published/developed cart mappers
+
+**************************************************************************************************/
+
+
+#include "emu.h"
+#include "xboy.h"
+
+#include "bus/generic/slot.h"
+
+
+/*
+ * The King of Fighters '99
+ * https://segaretro.org/King_of_Fighters_98%27
+ *
+ * Earlier protection variant
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_KOF98, megadrive_unl_kof98_device, "megadrive_unl_kof98", "Megadrive The King of Fighters '98 cart")
+
+megadrive_unl_kof98_device::megadrive_unl_kof98_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_KOF98, tag, owner, clock)
+{
+}
+
+void megadrive_unl_kof98_device::cart_map(address_map &map)
+{
+ map(0x00'0000, 0x3f'ffff).bankr(m_rom);
+ // everytime, trashes stack otherwise
+ map(0x48'0000, 0x4b'ffff).lr16(NAME([] () { return 0xaa00; }));
+ // when selecting Arcade or VS., pointer to reach above
+ map(0x4c'0000, 0x4f'ffff).lr16(NAME([] () { return 0xf000; }));
+}
+
+
+/*
+ * A Bug's Life
+ * https://segaretro.org/A_Bug%27s_Life_(Mega_Drive)
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_BUGSLIFE, megadrive_unl_bugslife_device, "megadrive_unl_bugslife", "Megadrive A Bug's Life cart")
+
+megadrive_unl_bugslife_device::megadrive_unl_bugslife_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_BUGSLIFE, tag, owner, clock)
+{
+}
+
+void megadrive_unl_bugslife_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).lr16(NAME([] () { return 0x28; }));
+ // those two are patched by SW
+ map(0x02, 0x03).lr16(NAME([] () { return 0x01; }));
+ map(0x3e, 0x3f).lr16(NAME([] () { return 0x1f; }));
+}
+
+/*
+ * Pokemon Monster
+ * https://segaretro.org/Pocket_Monster
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_POKEMONA, megadrive_unl_pokemona_device, "megadrive_unl_pokemona", "Megadrive Pokemon Monster alt cart")
+
+megadrive_unl_pokemona_device::megadrive_unl_pokemona_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_POKEMONA, tag, owner, clock)
+{
+}
+
+void megadrive_unl_pokemona_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).lr16(NAME([] () { return 0x14; }));
+ // those two are patched by SW
+ map(0x02, 0x03).lr16(NAME([] () { return 0x01; }));
+ map(0x3e, 0x3f).lr16(NAME([] () { return 0x1f; }));
+}
+
+/*
+ * The King of Fighters '99
+ * https://segaretro.org/The_King_of_Fighters_%2799_(Mega_Drive)
+ *
+ * Writes "Secondary memory access failure" if $a13000 returns & 0xf != 0
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_KOF99, megadrive_unl_kof99_device, "megadrive_unl_kof99", "Megadrive The King of Fighters '99 cart")
+
+megadrive_unl_kof99_device::megadrive_unl_kof99_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_KOF99, tag, owner, clock)
+{
+}
+
+void megadrive_unl_kof99_device::time_io_map(address_map &map)
+{
+ map(0x00, 0x01).lr16(NAME([] () { return 0x00; }));
+ map(0x02, 0x03).lr16(NAME([] () { return 0x01; }));
+ map(0x3e, 0x3f).lr16(NAME([] () { return 0x1f; }));
+}
+
+/*
+ * pokestad Pokemon Stadium
+ * https://segaretro.org/Pokemon_Stadium
+ *
+ * lionkin3 Lion King 3
+ * mulan Hua Mu Lan - Mulan
+ * pokemon2 Pocket Monsters 2
+ * souledge Soul Edge vs Samurai Spirits
+ * sdkong99/skkong99 Super Donkey Kong 99
+ * topf Top Fighter 2000 MK VIII
+ * https://segaretro.org/Top_Fighter_2000_MK_VIII
+ *
+ * gunfight Gunfight 3 in 1
+ * https://segaretro.org/Gunfight_3_in_1
+ *
+ * Obfuscated bankswitch mechanism + a bitswap based protection device.
+ *
+ * TODO:
+ * - pokestad is the only game that doesn't access protection, verify on HW if it has it anyway.
+ *
+ */
+
+DEFINE_DEVICE_TYPE(MEGADRIVE_UNL_TOPF, megadrive_unl_topf_device, "megadrive_unl_topf", "Megadrive Top Fighter cart")
+
+megadrive_unl_topf_device::megadrive_unl_topf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : megadrive_rom_device(mconfig, MEGADRIVE_UNL_TOPF, tag, owner, clock)
+ , m_rom_bank(*this, "rom_bank_%u", 0U)
+ , m_rom_view(*this, "rom_view")
+{
+}
+
+void megadrive_unl_topf_device::device_start()
+{
+ megadrive_rom_device::device_start();
+ memory_region *const romregion(cart_rom_region());
+ const u32 page_size = 0x00'8000;
+ device_generic_cart_interface::map_non_power_of_two(
+ unsigned(romregion->bytes() / page_size),
+ [this, base = &romregion->as_u8()] (unsigned entry, unsigned page)
+ {
+ for (int i = 0; i < 32; i++)
+ m_rom_bank[i]->configure_entry(entry, &base[page * page_size]);
+ });
+
+
+ save_item(NAME(m_prot_latch));
+ save_item(NAME(m_prot_mode));
+}
+
+void megadrive_unl_topf_device::device_reset()
+{
+ m_prot_latch = 0;
+ m_prot_mode = 0;
+ m_rom_view.select(0);
+}
+
+void megadrive_unl_topf_device::prot_latch_w(u8 data)
+{
+ m_prot_latch = data;
+}
+
+void megadrive_unl_topf_device::prot_mode_w(u8 data)
+{
+ m_prot_mode = data & 3;
+}
+
+u8 megadrive_unl_topf_device::prot_data_r()
+{
+ u8 res = 0;
+ switch(m_prot_mode)
+ {
+ case 0: res = (m_prot_latch << 1); break;
+ case 1: res = (m_prot_latch >> 1); break;
+ case 2: res = bitswap<8>(m_prot_latch, 3, 2, 1, 0, 7, 6, 5, 4); break;
+ case 3: res = bitswap<8>(m_prot_latch, 0, 1, 2, 3, 4, 5, 6, 7); break;
+ }
+
+ return res;
+}
+
+void megadrive_unl_topf_device::cart_map(address_map &map)
+{
+ // TODO: just 0x0f'ffff ?
+ map(0x00'0000, 0x1f'ffff).view(m_rom_view);
+ m_rom_view[0](0x00'0000, 0x1f'ffff).bankr(m_rom);
+ for (int bank = 0; bank < 16; bank++)
+ {
+ u32 bank_base = bank * 0x1'0000;
+ m_rom_view[1](0x00'0000 | bank_base, 0x00'7fff | bank_base).bankr(m_rom_bank[bank * 2 + 0]);
+ m_rom_view[1](0x00'8000 | bank_base, 0x00'ffff | bank_base).bankr(m_rom_bank[bank * 2 + 1]);
+ }
+
+ map(0x60'0001, 0x60'0001).mirror(0x0f'fff8).w(FUNC(megadrive_unl_topf_device::prot_latch_w));
+ map(0x60'0003, 0x60'0003).mirror(0x0f'fff8).w(FUNC(megadrive_unl_topf_device::prot_mode_w));
+ map(0x60'0005, 0x60'0005).mirror(0x0f'fff8).r(FUNC(megadrive_unl_topf_device::prot_data_r));
+
+ map(0x70'0000, 0x70'0000).mirror(0x0f'ffff).lw8(NAME([this] (u8 data) {
+ if (data)
+ {
+ m_rom_view.select(1);
+ for (int i = 0; i < 16; i++)
+ {
+ m_rom_bank[i * 2 + 0]->set_entry((i * 2 | data) & 0x3f);
+ m_rom_bank[i * 2 + 1]->set_entry((i * 2 | (data | 1)) & 0x3f);
+ }
+ }
+ else
+ {
+ m_rom_view.select(0);
+ }
+ }));
+}
diff --git a/src/devices/bus/megadrive/cart/xboy.h b/src/devices/bus/megadrive/cart/xboy.h
new file mode 100644
index 0000000000000..7788ad757c1c4
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/xboy.h
@@ -0,0 +1,73 @@
+// license: BSD-3-Clause
+// copyright-holders: Angelo Salese
+
+#ifndef MAME_BUS_MEGADRIVE_CART_XBOY_H
+#define MAME_BUS_MEGADRIVE_CART_XBOY_H
+
+#pragma once
+
+#include "rom.h"
+#include "slot.h"
+
+class megadrive_unl_kof98_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_kof98_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+};
+
+
+class megadrive_unl_bugslife_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_bugslife_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_pokemona_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_pokemona_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_kof99_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_kof99_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void time_io_map(address_map &map) override ATTR_COLD;
+};
+
+class megadrive_unl_topf_device : public megadrive_rom_device
+{
+public:
+ megadrive_unl_topf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ virtual void cart_map(address_map &map) override ATTR_COLD;
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+ void prot_latch_w(u8 data);
+ void prot_mode_w(u8 data);
+ u8 prot_data_r();
+private:
+ memory_bank_array_creator<32> m_rom_bank;
+ memory_view m_rom_view;
+
+ u8 m_prot_latch;
+ u8 m_prot_mode;
+};
+
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_KOF98, megadrive_unl_kof98_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_BUGSLIFE, megadrive_unl_bugslife_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_POKEMONA, megadrive_unl_pokemona_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_KOF99, megadrive_unl_kof99_device)
+DECLARE_DEVICE_TYPE(MEGADRIVE_UNL_TOPF, megadrive_unl_topf_device)
+
+
+#endif // MAME_BUS_MEGADRIVE_CART_XBOY_H
diff --git a/src/devices/bus/megadrive/eeprom.cpp b/src/devices/bus/megadrive/eeprom.cpp
index 23005b8999828..0af843ea4d378 100644
--- a/src/devices/bus/megadrive/eeprom.cpp
+++ b/src/devices/bus/megadrive/eeprom.cpp
@@ -32,7 +32,7 @@
Micro Machines Military | 0x380001-7 | 0x300000-0*| 0x300000-1*| 0x03ff (24C08) | 0x0f | Yes |
Micro Machines 96 | 0x380001-7 | 0x300000-0*| 0x300000-1*| 0x07ff (24C16) | 0x0f | Yes |
Brian Lara Cricket 96 | 0x380001-7 | 0x300000-0*| 0x300000-1*| 0x1fff (24C64) | 0x??* | |
- Shame Warne Cricket | 0x380001-7 | 0x300000-0*| 0x300000-1*| 0x1fff (24C64) | 0x??* | |
+ Shane Warne Cricket | 0x380001-7 | 0x300000-0*| 0x300000-1*| 0x1fff (24C64) | 0x??* | |
----------------------------------|------------|------------|------------|----------------|-----------|-------|
* Not specified in Eke-Eke's document
@@ -132,7 +132,7 @@ void md_eeprom_nbajamte_device::device_add_mconfig(machine_config &config)
void md_eeprom_cslam_device::device_add_mconfig(machine_config &config)
{
- I2C_24C64(config, m_i2cmem);
+ I2C_24C65(config, m_i2cmem);
}
void md_eeprom_nflqb96_device::device_add_mconfig(machine_config &config)
@@ -147,7 +147,7 @@ void md_eeprom_nhlpa_device::device_add_mconfig(machine_config &config)
void md_eeprom_blara_device::device_add_mconfig(machine_config &config)
{
- I2C_24C64(config, m_i2cmem);
+ I2C_24C65(config, m_i2cmem);
}
void md_eeprom_mode1_device::device_add_mconfig(machine_config &config)
diff --git a/src/devices/bus/megadrive/jcart.cpp b/src/devices/bus/megadrive/jcart.cpp
index d8cad677e5278..115cf4eb8f7a8 100644
--- a/src/devices/bus/megadrive/jcart.cpp
+++ b/src/devices/bus/megadrive/jcart.cpp
@@ -34,7 +34,7 @@
DEFINE_DEVICE_TYPE(MD_JCART, md_jcart_device, "md_jcart", "MD J-Cart games")
DEFINE_DEVICE_TYPE(MD_SEPROM_CODEMAST, md_seprom_codemast_device, "md_seprom_codemast", "MD J-Cart games + SEPROM")
-DEFINE_DEVICE_TYPE(MD_SEPROM_MM96, md_seprom_mm96_device, "md_seprom_mm96", "MD Micro Machine 96")
+DEFINE_DEVICE_TYPE(MD_SEPROM_MM96, md_seprom_mm96_device, "md_seprom_mm96", "MD Micro Machines 96")
// Sampras, Super Skidmarks?
md_jcart_device::md_jcart_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
diff --git a/src/devices/bus/megadrive/md_carts.cpp b/src/devices/bus/megadrive/md_carts.cpp
index f63112fdfab0f..f6f9e1a6bb3cb 100644
--- a/src/devices/bus/megadrive/md_carts.cpp
+++ b/src/devices/bus/megadrive/md_carts.cpp
@@ -41,11 +41,13 @@ void md_cart(device_slot_interface &device)
device.option_add_internal("rom_nflqb96", MD_EEPROM_NFLQB96);
device.option_add_internal("rom_cslam", MD_EEPROM_CSLAM);
device.option_add_internal("rom_nhlpa", MD_EEPROM_NHLPA);
- device.option_add_internal("rom_blara", MD_EEPROM_BLARA);
+ device.option_add_internal("rom_blara95", MD_EEPROM_BLARA);
+ device.option_add_internal("rom_blara96", MD_EEPROM_BLARA);
device.option_add_internal("rom_eeprom_mode1", MD_EEPROM_MODE1);
// J-Cart controller (Sampras Tennis)
+ device.option_add_internal("rom_jcart_sampras", MD_JCART);
device.option_add_internal("rom_jcart", MD_JCART);
-// J-Cart controller + EEPROM handling (not supported fully yet)
+ // J-Cart controller + EEPROM handling (not supported fully yet)
device.option_add_internal("rom_codemast", MD_SEPROM_CODEMAST);
device.option_add_internal("rom_mm96", MD_SEPROM_MM96);
// STM95 EEPROM
@@ -56,7 +58,6 @@ void md_cart(device_slot_interface &device)
device.option_add_internal("rom_ggenie", MD_ROM_GAMEGENIE);
// unique bankswitch
device.option_add_internal("rom_ssf2", MD_ROM_SSF2);
- device.option_add_internal("rom_radica", MD_ROM_RADICA);
// pirate mappers (protection and/or bankswitch)
device.option_add_internal("rom_16mj2", MD_ROM_16MJ2);
device.option_add_internal("rom_bugs", MD_ROM_BUGSLIFE);
@@ -85,7 +86,6 @@ void md_cart(device_slot_interface &device)
device.option_add_internal("rom_sram_arg96", MD_ROM_SRAM_ARG96);
device.option_add_internal("rom_tc2000", MD_ROM_TC2000);
device.option_add_internal("rom_tekkensp", MD_ROM_TEKKENSP);
- device.option_add_internal("rom_topf", MD_ROM_TOPF);
device.option_add_internal("rom_titan", MD_ROM_TITAN);
diff --git a/src/devices/bus/megadrive/md_slot.cpp b/src/devices/bus/megadrive/md_slot.cpp
index 856b48759bf93..c06c988584e5e 100644
--- a/src/devices/bus/megadrive/md_slot.cpp
+++ b/src/devices/bus/megadrive/md_slot.cpp
@@ -242,7 +242,6 @@ static const md_slot slot_list[] =
{ SSF2, "rom_ssf2" },
{ CM_2IN1, "rom_cm2in1" },
- { RADICA, "rom_radica" },
// { GAME_KANDUME, "rom_gkand" }, // what's needed by this?
{ TILESMJ2, "rom_16mj2" },
@@ -625,7 +624,7 @@ void base_md_cart_slot_device::setup_nvram()
break;
case SEGA_FRAM:
m_cart->m_nvram_start = 0x200000;
- m_cart->m_nvram_end = m_cart->m_nvram_start + get_software_region_length("fram") - 1;
+ m_cart->m_nvram_end = m_cart->m_nvram_start + get_software_region_length("sram") - 1;
m_cart->nvram_alloc(m_cart->m_nvram_end - m_cart->m_nvram_start + 1);
m_cart->m_nvram_active = 1;
m_cart->m_nvram_handlers_installed = 1;
@@ -695,7 +694,7 @@ int base_md_cart_slot_device::get_cart_type(const uint8_t *ROM, uint32_t len)
kof98_sig[] = { 0x9b, 0xfc, 0x00, 0x00, 0x4a, 0x00 },
s15in1_sig[] = { 0x22, 0x3c, 0x00, 0xa1, 0x30, 0x00 },
kof99_sig[] = { 0x20, 0x3c, 0x30, 0x00, 0x00, 0xa1 }, // move.l #$300000A1,d0
- radica_sig[] = { 0x4e, 0xd0, 0x30, 0x39, 0x00, 0xa1 }, // jmp (a0) move.w ($a130xx),d0
+// radica_sig[] = { 0x4e, 0xd0, 0x30, 0x39, 0x00, 0xa1 }, // jmp (a0) move.w ($a130xx),d0
soulb_sig[] = { 0x33, 0xfc, 0x00, 0x0c, 0x00, 0xff }, // move.w #$C,($FF020A).l (what happens if check fails)
s19in1_sig[] = { 0x13, 0xc0, 0x00, 0xa1, 0x30, 0x38 },
rockman_sig[] = { 0xea, 0x80 };
@@ -834,9 +833,9 @@ int base_md_cart_slot_device::get_cart_type(const uint8_t *ROM, uint32_t len)
break;
case 0x400000:
- if (!memcmp(&ROM[0x3c031c], radica_sig, sizeof(radica_sig)) ||
- !memcmp(&ROM[0x3f031c], radica_sig, sizeof(radica_sig))) // ssf+gng + radica vol1
- type = RADICA;
+ //if (!memcmp(&ROM[0x3c031c], radica_sig, sizeof(radica_sig)) ||
+ // !memcmp(&ROM[0x3f031c], radica_sig, sizeof(radica_sig))) // ssf+gng + radica vol1
+ // type = RADICA;
if (!memcmp(&ROM[0x028460], soulb_sig, sizeof(soulb_sig)))
type = SOULBLAD;
diff --git a/src/devices/bus/megadrive/md_slot.h b/src/devices/bus/megadrive/md_slot.h
index dc75d97b6d6a1..71cf4d6774b87 100644
--- a/src/devices/bus/megadrive/md_slot.h
+++ b/src/devices/bus/megadrive/md_slot.h
@@ -52,7 +52,7 @@ enum
SSF2, /* Super Street Fighter 2 */
CM_2IN1, /* CodeMasters 2in1 : Psycho Pinball + Micro Machines */
GAME_KANDUME, /* Game no Kandume Otokuyou */
- RADICA, /* Radica TV games.. these probably should be a separate driver since they are a separate 'console' */
+// RADICA, /* Radica TV games, handled in sega/megadriv_rad.cpp */
TILESMJ2, /* 16 Mahjong Tiles II */
BUGSLIFE, /* A Bug's Life */
diff --git a/src/devices/bus/megadrive/rom.cpp b/src/devices/bus/megadrive/rom.cpp
index e87128d0017a5..5a09214526faa 100644
--- a/src/devices/bus/megadrive/rom.cpp
+++ b/src/devices/bus/megadrive/rom.cpp
@@ -59,8 +59,6 @@ DEFINE_DEVICE_TYPE(MD_ROM_REDCL, md_rom_redcl_device, "md_rom_redcl", "
DEFINE_DEVICE_TYPE(MD_ROM_SQUIR, md_rom_squir_device, "md_rom_squir", "MD Squirrel King")
DEFINE_DEVICE_TYPE(MD_ROM_TC2000, md_rom_tc2000_device, "md_rom_tc2000", "MD TC2000")
DEFINE_DEVICE_TYPE(MD_ROM_TEKKENSP, md_rom_tekkensp_device, "md_rom_tekkensp", "MD Tekken Special")
-DEFINE_DEVICE_TYPE(MD_ROM_TOPF, md_rom_topf_device, "md_rom_topf", "MD Top Fighter")
-DEFINE_DEVICE_TYPE(MD_ROM_RADICA, md_rom_radica_device, "md_rom_radica", "MD Radica TV games")
DEFINE_DEVICE_TYPE(MD_ROM_BEGGARP, md_rom_beggarp_device, "md_rom_beggarp", "MD Beggar Prince")
DEFINE_DEVICE_TYPE(MD_ROM_WUKONG, md_rom_wukong_device, "md_rom_wukong", "MD Legend of Wukong")
DEFINE_DEVICE_TYPE(MD_ROM_STARODYS, md_rom_starodys_device, "md_rom_starodys", "MD Star Odyssey")
@@ -239,16 +237,6 @@ md_rom_tekkensp_device::md_rom_tekkensp_device(const machine_config &mconfig, co
{
}
-md_rom_topf_device::md_rom_topf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : md_std_rom_device(mconfig, MD_ROM_TOPF, tag, owner, clock), m_latch(0)
-{
-}
-
-md_rom_radica_device::md_rom_radica_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : md_std_rom_device(mconfig, MD_ROM_RADICA, tag, owner, clock), m_bank(0)
-{
-}
-
md_rom_beggarp_device::md_rom_beggarp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: md_std_rom_device(mconfig, MD_ROM_BEGGARP, tag, owner, clock), m_mode(0), m_lock(0)
{
@@ -401,28 +389,6 @@ void md_rom_tekkensp_device::device_reset()
m_reg = 0;
}
-void md_rom_topf_device::device_start()
-{
- save_item(NAME(m_latch));
- save_item(NAME(m_bank));
-}
-
-void md_rom_topf_device::device_reset()
-{
- m_latch = 0;
- m_bank[0] = m_bank[1] = m_bank[2] = 0;
-}
-
-void md_rom_radica_device::device_start()
-{
- save_item(NAME(m_bank));
-}
-
-void md_rom_radica_device::device_reset()
-{
- m_bank = 0;
-}
-
void md_rom_beggarp_device::device_start()
{
save_item(NAME(m_mode));
@@ -527,7 +493,7 @@ uint16_t md_rom_fram_device::read(offs_t offset)
void md_rom_fram_device::write(offs_t offset, uint16_t data, uint16_t mem_mask)
{
if (offset >= m_nvram_start/2 && offset <= m_nvram_end/2 && m_nvram_active)
- m_nvram[offset - m_nvram_start/2] = data;
+ m_nvram[offset - m_nvram_start/2] = data;
}
void md_rom_fram_device::write_a13(offs_t offset, uint16_t data)
@@ -1344,102 +1310,6 @@ void md_rom_tekkensp_device::write(offs_t offset, uint16_t data, uint16_t mem_ma
}
}
-/*-------------------------------------------------
- TOP FIGHTER
- -------------------------------------------------*/
-
-uint16_t md_rom_topf_device::read(offs_t offset)
-{
- //cpu #0 (PC=0004CBAE): unmapped program memory word read from 006A35D4 & 00FF -- wants regD7
- if (offset == 0x645b44/2)
- {
- //cpu #0 (PC=0004DE00): unmapped program memory word write to 00689B80 = 004A & 00FF
- //cpu #0 (PC=0004DE08): unmapped program memory word write to 00 = 00B5 & 00FF
- //cpu #0 (PC=0004DE0C): unmapped program memory word read from 00645B44 & 00FF
-
- return 0x9f;//0x25;
- }
- if (offset == 0x6bd294/2)
- {
- /*
- cpu #0 (PC=00177192): unmapped program memory word write to 006BD240 = 00A8 & 00FF
- cpu #0 (PC=0017719A): unmapped program memory word write to 006BD2D2 = 0098 & 00FF
- cpu #0 (PC=001771A2): unmapped program memory word read from 006BD294 & 00FF
- */
-
- if (machine().device("maincpu")->pc()==0x1771a2) return 0x50;
- else
- {
- m_latch++;
- logerror("%06x topfig_6BD294_r %04x\n",machine().device("maincpu")->pc(), m_latch);
- return m_latch;
- }
- }
- if (offset == 0x6f5344/2)
- {
- if (machine().device("maincpu")->pc()==0x4C94E)
- return machine().device("maincpu")->state_int((M68K_D0)) & 0xff;
- else
- {
- m_latch++;
- logerror("%06x topfig_6F5344_r %04x\n", machine().device("maincpu")->pc(), m_latch);
- return m_latch;
- }
- }
-
- if (offset >= 0x20000/2 && offset < 0x28000/2)
- return m_rom[offset + (m_bank[0] * 0x188000)/2];
-
- if (offset >= 0x58000/2 && offset < 0x60000/2)
- return m_rom[offset + (m_bank[1] * 0x20000)/2];
-
- if (offset >= 0x60000/2 && offset < 0x68000/2)
- return m_rom[offset + (m_bank[2] * 0x110000)/2];
-
- // non-protection accesses
- if (offset < 0x400000/2)
- return m_rom[MD_ADDR(offset)];
- else
- return 0xffff;
-}
-
-void md_rom_topf_device::write(offs_t offset, uint16_t data, uint16_t mem_mask)
-{
- if (offset >= 0x700000/2 && offset < 0x800000/2)
- {
- if (data == 0x002a)
- m_bank[2] = 1; // == 0x2e*0x8000?!
- else if (data==0x0035) // characters ingame
- m_bank[0] = 1; // == 0x35*0x8000
- else if (data==0x000f) // special moves
- m_bank[1] = 1; // == 0xf*0x8000
- else if (data==0x0000)
- {
- m_bank[0] = 0;
- m_bank[1] = 0;
- m_bank[2] = 0;
- }
- else
- logerror("%06x offset %06x, data %04x\n", machine().device("maincpu")->pc(), offset, data);
- }
-}
-
-/*-------------------------------------------------
- RADICA TV GAMES [to be split...]
- -------------------------------------------------*/
-
-uint16_t md_rom_radica_device::read(offs_t offset)
-{
- return m_rom[(((m_bank * 0x10000) + (offset << 1)) & (m_rom_size - 1))/2];
-}
-
-uint16_t md_rom_radica_device::read_a13(offs_t offset)
-{
- if (offset < 0x80)
- m_bank = offset & 0x3f;
- return 0;
-}
-
/*-------------------------------------------------
BEGGAR PRINCE
This game uses cart which is the same as SEGA_SRAM
diff --git a/src/devices/bus/megadrive/rom.h b/src/devices/bus/megadrive/rom.h
index b98469d3155e3..e9151b9f235ce 100644
--- a/src/devices/bus/megadrive/rom.h
+++ b/src/devices/bus/megadrive/rom.h
@@ -526,49 +526,6 @@ class md_rom_tekkensp_device : public md_std_rom_device
uint16_t m_reg;
};
-// ======================> md_rom_topf_device
-
-class md_rom_topf_device : public md_std_rom_device
-{
-public:
- // construction/destruction
- md_rom_topf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
-
- // reading and writing
- virtual uint16_t read(offs_t offset) override;
- virtual void write(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override;
-
-protected:
- // device-level overrides
- virtual void device_start() override ATTR_COLD;
- virtual void device_reset() override ATTR_COLD;
-
-private:
- uint16_t m_latch;
- uint8_t m_bank[3];
-};
-
-// ======================> md_rom_radica_device
-
-class md_rom_radica_device : public md_std_rom_device
-{
-public:
- // construction/destruction
- md_rom_radica_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
-
- // reading and writing
- virtual uint16_t read(offs_t offset) override;
- virtual uint16_t read_a13(offs_t offset) override;
-
-protected:
- // device-level overrides
- virtual void device_start() override ATTR_COLD;
- virtual void device_reset() override ATTR_COLD;
-
-private:
- uint8_t m_bank;
-};
-
// ======================> md_rom_beggarp_device
class md_rom_beggarp_device : public md_std_rom_device
@@ -671,8 +628,6 @@ DECLARE_DEVICE_TYPE(MD_ROM_SQUIR, md_rom_squir_device)
DECLARE_DEVICE_TYPE(MD_ROM_SRAM_ARG96, md_rom_sram_arg96_device)
DECLARE_DEVICE_TYPE(MD_ROM_TC2000, md_rom_tc2000_device)
DECLARE_DEVICE_TYPE(MD_ROM_TEKKENSP, md_rom_tekkensp_device)
-DECLARE_DEVICE_TYPE(MD_ROM_TOPF, md_rom_topf_device)
-DECLARE_DEVICE_TYPE(MD_ROM_RADICA, md_rom_radica_device)
DECLARE_DEVICE_TYPE(MD_ROM_BEGGARP, md_rom_beggarp_device)
DECLARE_DEVICE_TYPE(MD_ROM_WUKONG, md_rom_wukong_device)
DECLARE_DEVICE_TYPE(MD_ROM_STARODYS, md_rom_starodys_device)
diff --git a/src/devices/machine/i2cmem.cpp b/src/devices/machine/i2cmem.cpp
index 12e8cb88d803b..b856cd862a1fc 100644
--- a/src/devices/machine/i2cmem.cpp
+++ b/src/devices/machine/i2cmem.cpp
@@ -65,6 +65,7 @@ DEFINE_DEVICE_TYPE(I2C_X2404P, i2c_x2404p_device, "x2404p", "X2404P I2C Memor
DEFINE_DEVICE_TYPE(I2C_24C08, i2c_24c08_device, "24c08", "24C08 I2C Memory")
DEFINE_DEVICE_TYPE(I2C_24C16, i2c_24c16_device, "24c16", "24C16 I2C Memory")
DEFINE_DEVICE_TYPE(I2C_24C64, i2c_24c64_device, "24c64", "24C64 I2C Memory")
+DEFINE_DEVICE_TYPE(I2C_24C65, i2c_24c65_device, "24c65", "24C65 I2C Memory")
DEFINE_DEVICE_TYPE(I2C_24C128, i2c_24c128_device, "24c128", "24C128 I2C Memory")
DEFINE_DEVICE_TYPE(I2C_24C256, i2c_24c256_device, "24c256", "24C256 I2C Memory")
DEFINE_DEVICE_TYPE(I2C_24C512, i2c_24c512_device, "24c512", "24C512 I2C Memory")
@@ -175,6 +176,12 @@ i2c_24c64_device::i2c_24c64_device(const machine_config &mconfig, const char *ta
{
}
+// TODO: write protection differs too compared to '64
+i2c_24c65_device::i2c_24c65_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ i2cmem_device(mconfig, I2C_24C65, tag, owner, clock, 0, 64, 0x2000)
+{
+}
+
i2c_24c128_device::i2c_24c128_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
i2cmem_device(mconfig, I2C_24C128, tag, owner, clock, 0, 64, 0x4000)
{
diff --git a/src/devices/machine/i2cmem.h b/src/devices/machine/i2cmem.h
index dc4bb5f7f4fb4..9d9b6648f6ed5 100644
--- a/src/devices/machine/i2cmem.h
+++ b/src/devices/machine/i2cmem.h
@@ -115,6 +115,7 @@ DECLARE_I2C_DEVICE(x2404p);
DECLARE_I2C_DEVICE(24c08);
DECLARE_I2C_DEVICE(24c16);
DECLARE_I2C_DEVICE(24c64);
+DECLARE_I2C_DEVICE(24c65);
DECLARE_I2C_DEVICE(24c128);
DECLARE_I2C_DEVICE(24c256);
DECLARE_I2C_DEVICE(24c512);
@@ -132,6 +133,7 @@ DECLARE_DEVICE_TYPE(I2C_X2404P, i2c_x2404p_device)
DECLARE_DEVICE_TYPE(I2C_24C08, i2c_24c08_device)
DECLARE_DEVICE_TYPE(I2C_24C16, i2c_24c16_device)
DECLARE_DEVICE_TYPE(I2C_24C64, i2c_24c64_device)
+DECLARE_DEVICE_TYPE(I2C_24C65, i2c_24c65_device)
DECLARE_DEVICE_TYPE(I2C_24C128, i2c_24c128_device)
DECLARE_DEVICE_TYPE(I2C_24C256, i2c_24c256_device)
DECLARE_DEVICE_TYPE(I2C_24C512, i2c_24c512_device)
diff --git a/src/devices/machine/intelfsh.cpp b/src/devices/machine/intelfsh.cpp
index e303dd4533d8c..630469944a74c 100644
--- a/src/devices/machine/intelfsh.cpp
+++ b/src/devices/machine/intelfsh.cpp
@@ -109,6 +109,7 @@ DEFINE_DEVICE_TYPE(MACRONIX_29F1610MC_16BIT, macronix_29f1610mc_16bit_device, "m
DEFINE_DEVICE_TYPE(MACRONIX_29L001MC, macronix_29l001mc_device, "macronix_29l001mc", "Macronix 29L001MC Flash")
DEFINE_DEVICE_TYPE(MACRONIX_29LV160TMC, macronix_29lv160tmc_device, "macronix_29lv160tmc", "Macronix 29LV160TMC Flash")
DEFINE_DEVICE_TYPE(ST_M29W640GB, st_m29w640gb_device, "st_m29w640gb", "ST M29W640GB Flash")
+DEFINE_DEVICE_TYPE(ST_M29W640FT, st_m29w640ft_device, "st_m29w640ft", "ST M29W640FT Flash")
DEFINE_DEVICE_TYPE(TMS_29F040, tms_29f040_device, "tms_29f040", "Texas Instruments 29F040 Flash")
DEFINE_DEVICE_TYPE(PANASONIC_MN63F805MNP, panasonic_mn63f805mnp_device, "panasonic_mn63f805mnp", "Panasonic MN63F805MNP Flash")
@@ -264,6 +265,9 @@ macronix_29lv160tmc_device::macronix_29lv160tmc_device(const machine_config &mco
st_m29w640gb_device::st_m29w640gb_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: intelfsh8_device(mconfig, ST_M29W640GB, tag, owner, clock, 0x800000, MFG_ST, 0x227e) { m_bot_boot_sector = true; m_device_id2 = 0x2210; m_device_id3 = 0x2200; }
+st_m29w640ft_device::st_m29w640ft_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : intelfsh16_device(mconfig, ST_M29W640FT, tag, owner, clock, 0x800000, MFG_ST, 0x22ed) { m_bot_boot_sector = true; m_device_id2 = 0x2210; m_device_id3 = 0x2200; }
+
panasonic_mn63f805mnp_device::panasonic_mn63f805mnp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: intelfsh8_device(mconfig, PANASONIC_MN63F805MNP, tag, owner, clock, 0x10000, MFG_PANASONIC, 0x1b) { m_sector_is_4k = true; }
@@ -603,7 +607,8 @@ void intelfsh_device::write_full(uint32_t address, uint32_t data)
m_flash_mode = FM_NORMAL;
break;
case 0x90:
- if ( m_fast_mode && m_maker_id == MFG_FUJITSU ) // reset from fast mode (when fast mode is enabled)
+ // TODO: W640GB also needs this path
+ if ( m_fast_mode && (m_maker_id == MFG_FUJITSU || (m_maker_id == MFG_ST && (m_device_id == 0x22ed || m_device_id == 0x227e))) ) // reset from fast mode (when fast mode is enabled)
m_flash_mode = FM_FAST_RESET;
else // read ID
m_flash_mode = FM_READID;
@@ -626,8 +631,16 @@ void intelfsh_device::write_full(uint32_t address, uint32_t data)
case 0x20: // block erase
if (m_maker_id == MFG_SST && m_device_id == 0x61)
logerror("Unknown flash mode byte %x\n", data & 0xff);
+ else if (m_maker_id == MFG_ST && (m_device_id == 0x22ed || m_device_id == 0x227e))
+ {
+ // unlock bypass
+ m_flash_mode = FM_NORMAL;
+ m_fast_mode = true;
+ }
else
+ {
m_flash_mode = FM_CLEARPART1;
+ }
break;
case 0x60: // set master lock
m_flash_mode = FM_SETMASTER;
@@ -636,8 +649,10 @@ void intelfsh_device::write_full(uint32_t address, uint32_t data)
m_flash_mode = FM_READSTATUS;
break;
case 0xa0: // fast program (fast mode must be enabled)
- if ( m_fast_mode && m_maker_id == MFG_FUJITSU )
+ if ( m_fast_mode && (m_maker_id == MFG_FUJITSU || (m_maker_id == MFG_ST && (m_device_id == 0x22ed || m_device_id == 0x227e))) )
+ {
m_flash_mode = FM_BYTEPROGRAM;
+ }
else
logerror( "%s: Unknown flash mode byte %x\n", machine().describe_context(), data & 0xff );
break;
@@ -958,7 +973,9 @@ void intelfsh_device::write_full(uint32_t address, uint32_t data)
m_data[address] &= data;
}
else
+ {
m_data[address] = data;
+ }
break;
case 16: // senbbs test mode requires this, note, flash type is guessed there based on manufacturer + device ident as markings were erased
m_data[address*2] = data >> 8;
diff --git a/src/devices/machine/intelfsh.h b/src/devices/machine/intelfsh.h
index 7f81f25a15d04..c2da4d31644e2 100644
--- a/src/devices/machine/intelfsh.h
+++ b/src/devices/machine/intelfsh.h
@@ -223,12 +223,19 @@ class macronix_29lv160tmc_device : public intelfsh8_device
macronix_29lv160tmc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
};
+// TODO: both m29w640 can be either 8 or 16 bit interface
class st_m29w640gb_device : public intelfsh8_device
{
public:
st_m29w640gb_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
};
+class st_m29w640ft_device : public intelfsh16_device
+{
+public:
+ st_m29w640ft_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+};
+
class panasonic_mn63f805mnp_device : public intelfsh8_device
{
public:
@@ -425,6 +432,7 @@ DECLARE_DEVICE_TYPE(MACRONIX_29F1610MC_16BIT,macronix_29f1610mc_16bit_device)
DECLARE_DEVICE_TYPE(MACRONIX_29L001MC, macronix_29l001mc_device)
DECLARE_DEVICE_TYPE(MACRONIX_29LV160TMC, macronix_29lv160tmc_device)
DECLARE_DEVICE_TYPE(ST_M29W640GB, st_m29w640gb_device)
+DECLARE_DEVICE_TYPE(ST_M29W640FT, st_m29w640ft_device)
DECLARE_DEVICE_TYPE(TMS_29F040, tms_29f040_device)
DECLARE_DEVICE_TYPE(PANASONIC_MN63F805MNP, panasonic_mn63f805mnp_device)
diff --git a/src/devices/machine/m95320.cpp b/src/devices/machine/m95320.cpp
new file mode 100644
index 0000000000000..97dfc32c13cc7
--- /dev/null
+++ b/src/devices/machine/m95320.cpp
@@ -0,0 +1,202 @@
+// license: BSD-3-Clause
+// copyright-holders: Fabio Priuli, MetalliC
+/**************************************************************************************************
+
+M95320-W M95320-R M95320-DF
+32-Kbit serial SPI bus EEPROM with high-speed clock
+
+TODO:
+- direct converted from original stm95 for megadriv:psolar;
+- Actual SPI bus connection;
+- M95320-D variant (has extra instructions);
+- M95640 (same base instruction set, double size);
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "m95320.h"
+
+DEFINE_DEVICE_TYPE(M95320_EEPROM, m95320_eeprom_device, "m95320_eeprom", "STM95320 SPI bus EEPROM")
+
+m95320_eeprom_device::m95320_eeprom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, type, tag, owner, clock)
+ , device_nvram_interface(mconfig, *this)
+{
+}
+
+m95320_eeprom_device::m95320_eeprom_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : m95320_eeprom_device(mconfig, M95320_EEPROM, tag, owner, clock)
+{
+}
+
+
+void m95320_eeprom_device::device_start()
+{
+ m_eeprom_data = std::make_unique(m_size);
+
+ save_pointer(NAME(m_eeprom_data), m_size);
+ save_item(NAME(m_latch));
+ save_item(NAME(m_reset_line));
+ save_item(NAME(m_sck_line));
+ save_item(NAME(m_wel));
+ save_item(NAME(m_stream_pos));
+ save_item(NAME(m_stream_data));
+ save_item(NAME(m_eeprom_addr));
+}
+
+void m95320_eeprom_device::nvram_default()
+{
+}
+
+
+bool m95320_eeprom_device::nvram_read(util::read_stream &file)
+{
+ auto const [err, actual] = util::read(file, m_eeprom_data.get(), m_size);
+ return !err && (actual == m_size);
+}
+
+bool m95320_eeprom_device::nvram_write(util::write_stream &file)
+{
+ auto const [err, actual] = util::write(file, m_eeprom_data.get(), m_size);
+ return !err;
+}
+
+
+void m95320_eeprom_device::set_cs_line(int state)
+{
+ m_reset_line = state;
+ if (m_reset_line != CLEAR_LINE)
+ {
+ m_stream_pos = 0;
+ m_internal_state = IDLE;
+ }
+}
+
+void m95320_eeprom_device::set_si_line(int state)
+{
+ m_latch = state;
+}
+
+int m95320_eeprom_device::get_so_line(void)
+{
+ if (m_internal_state == READING || m_internal_state == CMD_RDSR)
+ return (m_stream_data >> 8) & 1;
+ else
+ return 0;
+}
+
+void m95320_eeprom_device::set_sck_line(int state)
+{
+ if (m_reset_line == CLEAR_LINE)
+ {
+ if (state == ASSERT_LINE && m_sck_line == CLEAR_LINE)
+ {
+ switch (m_internal_state)
+ {
+ case IDLE:
+ m_stream_data = (m_stream_data << 1) | (m_latch ? 1 : 0);
+ m_stream_pos++;
+ if (m_stream_pos == 8)
+ {
+ m_stream_pos = 0;
+ //printf("STM95 EEPROM: got cmd %02X\n", m_stream_data&0xff);
+ switch(m_stream_data & 0xff)
+ {
+ case 0x01: // write status register
+ if (m_wel != 0)
+ m_internal_state = CMD_WRSR;
+ m_wel = 0;
+ break;
+ case 0x02: // write
+ if (m_wel != 0)
+ m_internal_state = CMD_WRITE;
+ m_stream_data = 0;
+ m_wel = 0;
+ break;
+ case 0x03: // read
+ m_internal_state = CMD_READ;
+ m_stream_data = 0;
+ break;
+ case 0x04: // write disable
+ m_wel = 0;
+ break;
+ case 0x05: // read status register
+ m_internal_state = CMD_RDSR;
+ // TODO: SRWD / BP1 / BP0 and WIP bits
+ m_stream_data = m_wel << 1;
+ break;
+ case 0x06: // write enable
+ m_wel = 1;
+ break;
+ default:
+ logerror("unknown cmd %02X\n", m_stream_data&0xff);
+ }
+ }
+ break;
+ case CMD_WRSR:
+ m_stream_pos++; // just skip, don't care block protection
+ if (m_stream_pos == 8)
+ {
+ m_internal_state = IDLE;
+ m_stream_pos = 0;
+ }
+ break;
+ case CMD_RDSR:
+ m_stream_data = m_stream_data << 1;
+ m_stream_pos++;
+ if (m_stream_pos == 8)
+ {
+ m_internal_state = IDLE;
+ m_stream_pos = 0;
+ }
+ break;
+ case CMD_READ:
+ m_stream_data = (m_stream_data << 1) | (m_latch ? 1 : 0);
+ m_stream_pos++;
+ if (m_stream_pos == 16)
+ {
+ m_eeprom_addr = m_stream_data & (m_size - 1);
+ m_stream_data = m_eeprom_data[m_eeprom_addr];
+ m_internal_state = READING;
+ m_stream_pos = 0;
+ }
+ break;
+ case READING:
+ m_stream_data = m_stream_data<<1;
+ m_stream_pos++;
+ if (m_stream_pos == 8)
+ {
+ if (++m_eeprom_addr == m_size)
+ m_eeprom_addr = 0;
+ m_stream_data |= m_eeprom_data[m_eeprom_addr];
+ m_stream_pos = 0;
+ }
+ break;
+ case CMD_WRITE:
+ m_stream_data = (m_stream_data << 1) | (m_latch ? 1 : 0);
+ m_stream_pos++;
+ if (m_stream_pos == 16)
+ {
+ m_eeprom_addr = m_stream_data & (m_size - 1);
+ m_internal_state = WRITING;
+ m_stream_pos = 0;
+ }
+ break;
+ case WRITING:
+ m_stream_data = (m_stream_data << 1) | (m_latch ? 1 : 0);
+ m_stream_pos++;
+ if (m_stream_pos == 8)
+ {
+ m_eeprom_data[m_eeprom_addr] = m_stream_data;
+ if (++m_eeprom_addr == m_size)
+ m_eeprom_addr = 0;
+ m_stream_pos = 0;
+ }
+ break;
+ }
+ }
+ }
+ m_sck_line = state;
+}
+
+
diff --git a/src/devices/machine/m95320.h b/src/devices/machine/m95320.h
new file mode 100644
index 0000000000000..18505f490deb8
--- /dev/null
+++ b/src/devices/machine/m95320.h
@@ -0,0 +1,59 @@
+// license: BSD-3-Clause
+// copyright-holders: Fabio Priuli, MetalliC
+
+#ifndef MAME_MACHINE_M95320_H
+#define MAME_MACHINE_M95320_H
+
+#pragma once
+
+#include "machine/eeprom.h"
+
+class m95320_eeprom_device : public device_t, public device_nvram_interface
+{
+public:
+ m95320_eeprom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ void set_cs_line(int);
+ void set_halt_line(int state) {}; // TODO: not implemented
+ void set_si_line(int);
+ void set_sck_line(int state);
+ int get_so_line(void);
+
+protected:
+ m95320_eeprom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ virtual void device_start() override ATTR_COLD;
+
+ virtual void nvram_default() override;
+ virtual bool nvram_read(util::read_stream &file) override;
+ virtual bool nvram_write(util::write_stream &file) override;
+private:
+ enum stmstate_t
+ {
+ IDLE = 0,
+ CMD_WRSR,
+ CMD_RDSR,
+ CMD_READ,
+ CMD_WRITE,
+ READING,
+ WRITING
+ };
+ static constexpr unsigned m_size = 0x1000;
+
+ int m_latch;
+ int m_reset_line;
+ int m_sck_line;
+ int m_wel;
+
+ stmstate_t m_internal_state;
+ int m_stream_pos;
+ int m_stream_data;
+ int m_eeprom_addr;
+ std::unique_ptr m_eeprom_data;
+
+};
+
+DECLARE_DEVICE_TYPE(M95320_EEPROM, m95320_eeprom_device)
+
+
+#endif // MAME_MACHINE_M95320_H
diff --git a/src/devices/machine/w83977tf.cpp b/src/devices/machine/w83977tf.cpp
index 2a5131210e53a..8585d20a7a003 100644
--- a/src/devices/machine/w83977tf.cpp
+++ b/src/devices/machine/w83977tf.cpp
@@ -11,6 +11,7 @@ Winbond W83977TF
status only three times (and port $61 is claimed by PIIX4 for PCI SERR# read only)
- DRQ (savquest enables DRQ3 for LPT)
- Hookup LPT modes;
+- subclass for megadrive magistr16 system (uses w83977f/w83977af)
**************************************************************************************************/
diff --git a/src/devices/video/ym7101.cpp b/src/devices/video/ym7101.cpp
index c1d79248cff20..e1e908b9eaba0 100644
--- a/src/devices/video/ym7101.cpp
+++ b/src/devices/video/ym7101.cpp
@@ -124,6 +124,8 @@ void ym7101_device::device_start()
save_item(NAME(m_hscroll_address));
save_item(NAME(m_hsz));
save_item(NAME(m_vsz));
+ save_item(NAME(m_hpage));
+ save_item(NAME(m_vpage));
save_item(NAME(m_auto_increment));
save_item(NAME(m_plane_a_name_table));
save_item(NAME(m_window_name_table));
@@ -483,6 +485,53 @@ void ym7101_device::vsram_map(address_map &map)
static const char *const size_names[] = { "256 pixels/32 cells", "512 pixels/64 cells", "", "1024 pixels/128 cells" };
+void ym7101_device::calculate_plane_sizes()
+{
+ const u16 page_masks[] = { 32, 64, 1, 128 };
+
+ m_hpage = page_masks[m_hsz];
+ m_vpage = page_masks[m_vsz];
+
+ // https://gendev.spritesmind.net/forum/viewtopic.php?p=31307#p31307
+ // Triggering 1x settings that aren't 11x00 or 00x11 trigger various forms of overrides ...
+ if (m_hsz & 2 || m_vsz & 2)
+ {
+ if (m_hsz == 2)
+ {
+ // forces 32x1 regardless of VSZ
+ m_hpage = 32;
+ m_vpage = 1;
+ LOG("Prohibited plane setting HSZ 2 VSZ %d (forced to 32x1)\n", m_vsz);
+ }
+ else if (m_hsz == 3)
+ {
+ // forces 128x32 regardless of VSZ
+ m_vpage = 32;
+ if (m_vsz)
+ LOG("Prohibited plane setting HSZ 3 VSZ %d (forced to 128x32)\n", m_vsz);
+ }
+ else if (m_vsz == 2)
+ {
+ if (m_hsz == 0)
+ popmessage("ym7101.cpp: prohibited plane setting HSZ 0 VSZ 2 (mirroring?)");
+ else
+ {
+ // forces NNx32, H untouched
+ // hulk uses HSZ=1 VSZ=2 on title screen
+ m_vpage = 32;
+ LOG("Prohibited plane setting HSZ %d VSZ 2 (forced to V32)\n", m_hsz);
+ }
+ }
+ else if (m_vsz == 3 && m_hsz == 1)
+ {
+ // forces 64x64
+ m_hpage = 64;
+ m_vpage = 64;
+ LOG("Prohibited plane setting HSZ 1 VSZ 3 (forced to 64x64)\n");
+ }
+ }
+}
+
// https://plutiedev.com/vdp-registers
// https://segaretro.org/Sega_Mega_Drive/VDP_registers
// NOTE: in decimal units, classic Yamaha
@@ -640,8 +689,8 @@ void ym7101_device::regs_map(address_map &map)
, m_vsz
, size_names[m_vsz]
);
- if (m_hsz == 2 || m_vsz == 2 || (m_vsz == 3 && m_hsz != 0) || (m_hsz == 3 && m_vsz != 0))
- popmessage("ym7101.cpp: illegal plane size set %d %d", m_hsz, m_vsz);
+
+ calculate_plane_sizes();
}));
map(17, 17).lw8(NAME([this] (u8 data) {
m_rigt = !!BIT(data, 7);
@@ -853,10 +902,8 @@ void ym7101_device::prepare_tile_line(int scanline)
const u16 tile_mask = 0x7ff;
//const u16 page_mask[] = { 0x7ff, 0x1fff, 0x1fff, 0x1fff };
- const u16 page_masks[] = { 32, 64, 1, 128 };
-
- const u16 h_page = page_masks[m_hsz];
- const u16 v_page = page_masks[m_vsz];
+// const u16 m_hpage = page_masks[m_hsz];
+// const u16 m_vpage = page_masks[m_vsz];
// AV Artisan games will set plane B base with 0x18000
const u32 plane_a_name_base = (m_plane_a_name_table & m_vram_mask) >> 1;
@@ -928,8 +975,8 @@ void ym7101_device::prepare_tile_line(int scanline)
{
const u16 scrollx_a = m_vram[(m_hscroll_address >> 1) + scroll_x_base];
const u16 scrolly_a = m_vsram[x & scroll_y_mask];
- const u16 vcolumn_a = (scrolly_a + scanline) & ((v_page * 8) - 1);
- const u32 tile_offset_a = ((x - (scrollx_a >> 3)) & ((h_page * 1) - 1)) + ((vcolumn_a >> 3) * (h_page >> 0));
+ const u16 vcolumn_a = (scrolly_a + scanline) & ((m_vpage * 8) - 1);
+ const u32 tile_offset_a = ((x - (scrollx_a >> 3)) & ((m_hpage * 1) - 1)) + ((vcolumn_a >> 3) * (m_hpage >> 0));
scrolly_a_frac = scrolly_a & 7;
scrollx_a_frac = scrollx_a & 7;
@@ -945,8 +992,8 @@ void ym7101_device::prepare_tile_line(int scanline)
const u16 scrollx_b_frac = scrollx_b & 7;
const u16 scrolly_b = m_vsram[(x & scroll_y_mask) + 1];
const u16 scrolly_b_frac = scrolly_b & 7;
- const u16 vcolumn_b = (scrolly_b + scanline) & ((v_page * 8) - 1);
- const u32 tile_offset_b = ((x - (scrollx_b >> 3)) & ((h_page * 1) - 1)) + ((vcolumn_b >> 3) * (h_page >> 0));
+ const u16 vcolumn_b = (scrolly_b + scanline) & ((m_vpage * 8) - 1);
+ const u32 tile_offset_b = ((x - (scrollx_b >> 3)) & ((m_hpage * 1) - 1)) + ((vcolumn_b >> 3) * (m_hpage >> 0));
const u16 id_flags_b = m_vram[(plane_b_name_base + tile_offset_b) & m_vram_mask];
const u16 tile_b = id_flags_b & tile_mask;
const u8 flipx_b = BIT(id_flags_b, 11) ? 4 : 3;
diff --git a/src/devices/video/ym7101.h b/src/devices/video/ym7101.h
index 736e690cd1d72..61ce49cd839bb 100644
--- a/src/devices/video/ym7101.h
+++ b/src/devices/video/ym7101.h
@@ -183,6 +183,8 @@ class ym7101_device : public device_t,
u16 m_hvcounter_latch;
u32 m_vram_mask;
bool m_sprite_collision, m_sprite_overflow;
+ u16 m_hpage, m_vpage;
+ void calculate_plane_sizes();
bitmap_rgb32 m_bitmap;
bool render_line(int scanline);
diff --git a/src/mame/pc/teradrive.cpp b/src/mame/pc/teradrive.cpp
index 02ae194fd5684..4663d63eb5dcc 100644
--- a/src/mame/pc/teradrive.cpp
+++ b/src/mame/pc/teradrive.cpp
@@ -1,6 +1,6 @@
// license:BSD-3-Clause
// copyright-holders: Angelo Salese
-// thanks-to: Mask of Destiny, Nemesis, Sik
+// thanks-to: Mask of Destiny, Nemesis, Sik, ICEknight
/**************************************************************************************************
Sega Teradrive
@@ -20,6 +20,7 @@ References (generic MD):
- https://segaretro.org/Sega_Mega_Drive/VDP_general_usage
- https://segaretro.org/Sega_Mega_Drive/Technical_specifications
- https://gendev.spritesmind.net/forum/viewtopic.php?p=37011#p37011
+- https://github.com/jsgroth/jgenesis/wiki/Tricky%E2%80%90to%E2%80%90Emulate-Games#genesis
NOTES (PC side):
- F1 at POST will bring a setup menu;
@@ -33,6 +34,7 @@ NOTES (MD side):
- has discrete YM3438 in place of YM2612
- Mega CD expansion port working with DIY extension cable, 32x needs at least a passive cart adapter
- focus 3 in debugger is the current default for MD side
+- bp ff0122,1,{fill 0xff8100,4,"SEGA";g} to bypass TMSS loading for games without a valid header
- MAME inability of handling differing refresh rates causes visible tearing in MD screen
(cfr. koteteik intro). A partial workaround is to use Video mode = composite, so that
VGA will downclock to ~60 Hz instead.
@@ -42,21 +44,29 @@ NOTES (MD side):
- Quadtel EMM driver fails recognizing WD76C10 chipset with drv4;
- Cannot HDD format with floppy insthdd.bat, cannot boot from HDD (needs floppy first).
Attached disk is a WDL-330PS with no geometry info available;
-- MD side cart slot, expansion bay and VDP rewrites (WIP);
- TMSS unlock and respective x86<->MD bus grants are sketchy;
- SEGA TERADRIVE テラドライブ ユーザーズマニュアル known to exist (not scanned yet)
- "TIMER FAIL" when exiting from F1 setup menu (keyboard? reset from chipset?);
+- dual boot not yet handled;
+
+TODO (MD side):
+- some games (orunnersj, timekillu, rhythmld and late SGDK games) fails on Z80 bus request stuff;
+- dashdes: is a flickerfest during gameplay;
+- sonic2/combatca: no interlace support in 2-players mode;
+- dheadj: scrolling issues in stage 4-1 (blocks overflowing with );
+- shangon/skitchin: one line off during gameplay;
+- caesar: no sound;
+- gynougj: stray tile on top-left of title screen;
**************************************************************************************************/
#include "emu.h"
-#include "bus/generic/carts.h"
-#include "bus/generic/slot.h"
-
#include "bus/isa/isa.h"
#include "bus/isa/isa_cards.h"
#include "bus/isa/svga_paradise.h"
+#include "bus/megadrive/cart/options.h"
+#include "bus/megadrive/cart/slot.h"
#include "bus/pc_kbd/keyboards.h"
#include "bus/pc_kbd/pc_kbdc.h"
#include "bus/sms_ctrl/controllers.h"
@@ -465,7 +475,7 @@ class teradrive_state : public driver_device
required_device m_mdscreen;
required_memory_bank m_tmss_bank;
memory_view m_tmss_view;
- required_device m_md_cart;
+ required_device m_md_cart;
required_device m_md_vdp;
required_device m_opn;
memory_view m_md_68k_sound_view;
@@ -536,7 +546,8 @@ void teradrive_state::md_68k_map(address_map &map)
// m_cart_view[1](0x400000, 0x400fff).view(m_tmss_view);
// m_tmss_view[0](0x400000, 0x400fff).rom().region("tmss", 0);
- map(0x000000, 0x3fffff).r(m_md_cart, FUNC(generic_slot_device::read_rom));
+ // TODO: implement bus conflict for 0x40'0000,0x7f'ffff area (if expansion port attached)
+ map(0x000000, 0x7fffff).rw(m_md_cart, FUNC(megadrive_cart_slot_device::base_r), FUNC(megadrive_cart_slot_device::base_w));
map(0x000000, 0x000fff).view(m_tmss_view);
m_tmss_view[0](0x000000, 0x000fff).bankr(m_tmss_bank);
@@ -598,7 +609,7 @@ void teradrive_state::md_68k_map(address_map &map)
// map(0xa11400, 0xa1dfff) (no DTACK generation, freezes machine without additional HW)
// map(0xa12000, 0xa120ff).m(m_exp, FUNC(...::fdc_map));
-// map(0xa13000, 0xa130ff).m(m_cart, FUNC(...::time_map));
+ map(0xa13000, 0xa130ff).rw(m_md_cart, FUNC(megadrive_cart_slot_device::time_r), FUNC(megadrive_cart_slot_device::time_w));
// map(0xa14000, 0xa14003) TMSS lock
// map(0xa15100, 0xa153ff) 32X registers if present, otherwise
// map(0xae0000, 0xae0003) Teradrive bus switch registers
@@ -1026,11 +1037,7 @@ void teradrive_state::teradrive(machine_config &config)
m_md_ioports[N]->set_out_handler(m_md_ctrl_ports[N], FUNC(sms_control_port_device::out_w));
}
- // TODO: vestigial
- GENERIC_CARTSLOT(config, m_md_cart, generic_plain_slot, "megadriv_cart");
- m_md_cart->set_width(GENERIC_ROM16_WIDTH);
- // TODO: generic_cartslot has issues with softlisted endianness (use loose for now)
- m_md_cart->set_endian(ENDIANNESS_BIG);
+ MEGADRIVE_CART_SLOT(config, m_md_cart, md_master_xtal / 7, megadrive_cart_options, nullptr).set_must_be_loaded(false);
SPEAKER(config, "md_speaker", 2).front();
diff --git a/src/mame/sega/megadriv.cpp b/src/mame/sega/megadriv.cpp
index 2af1094e64c35..36b9a4e3d8697 100644
--- a/src/mame/sega/megadriv.cpp
+++ b/src/mame/sega/megadriv.cpp
@@ -45,12 +45,6 @@ Known Non-Issues (confirmed on Real Genesis)
void md_base_state::megadriv_z80_bank_w(uint16_t data)
{
- // TODO: menghu crashes here
- // Tries to setup a bank of 0xff0000 from z80 side (PC=1131) after you talk with the cashier twice.
- // Without a guard over it game will trash 68k memory causing a crash, works on real HW with everdrive
- // so not coming from a cart copy protection.
- // Update: it breaks cfodder BGM on character select at least, therefore we current don't guard against it
- // Apparently reading 68k RAM from z80 is not recommended by Sega, so *writing* isn't possible lacking bus grant?
m_genz80.z80_bank_addr = ((m_genz80.z80_bank_addr >> 1) | (data << 23)) & 0xff8000;
}