Migrate to latest GPGX MD Cart mapper support
This commit is contained in:
parent
84df4141ea
commit
db0f2a65a1
|
@ -2,7 +2,7 @@
|
|||
* Genesis Plus
|
||||
* Mega Drive cartridge hardware support
|
||||
*
|
||||
* Copyright (C) 2007-2013 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2007-2020 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Many cartridge protections were initially documented by Haze
|
||||
* (http://haze.mameworld.info/)
|
||||
|
@ -47,8 +47,6 @@
|
|||
#include "gamepad.h"
|
||||
#include <emulibc.h>
|
||||
|
||||
#define CART_CNT (55)
|
||||
|
||||
/* Cart database entry */
|
||||
typedef struct
|
||||
{
|
||||
|
@ -61,6 +59,7 @@ typedef struct
|
|||
|
||||
/* Function prototypes */
|
||||
static void mapper_sega_w(uint32 data);
|
||||
static void mapper_512k_w(uint32 address, uint32 data);
|
||||
static void mapper_ssf2_w(uint32 address, uint32 data);
|
||||
static void mapper_sf001_w(uint32 address, uint32 data);
|
||||
static void mapper_sf002_w(uint32 address, uint32 data);
|
||||
|
@ -68,6 +67,8 @@ static void mapper_sf004_w(uint32 address, uint32 data);
|
|||
static uint32 mapper_sf004_r(uint32 address);
|
||||
static void mapper_t5740_w(uint32 address, uint32 data);
|
||||
static uint32 mapper_t5740_r(uint32 address);
|
||||
static void mapper_flashkit_w(uint32 address, uint32 data);
|
||||
static uint32 mapper_flashkit_r(uint32 address);
|
||||
static uint32 mapper_smw_64_r(uint32 address);
|
||||
static void mapper_smw_64_w(uint32 address, uint32 data);
|
||||
static void mapper_realtec_w(uint32 address, uint32 data);
|
||||
|
@ -75,7 +76,11 @@ static void mapper_seganet_w(uint32 address, uint32 data);
|
|||
static void mapper_32k_w(uint32 data);
|
||||
static void mapper_64k_w(uint32 data);
|
||||
static void mapper_64k_multi_w(uint32 address);
|
||||
static uint32 mapper_radica_r(uint32 address);
|
||||
static uint32 mapper_128k_multi_r(uint32 address);
|
||||
static void mapper_256k_multi_w(uint32 address, uint32 data);
|
||||
static void mapper_wd1601_w(uint32 address, uint32 data);
|
||||
static uint32 mapper_64k_radica_r(uint32 address);
|
||||
static uint32 mapper_128k_radica_r(uint32 address);
|
||||
static void default_time_w(uint32 address, uint32 data);
|
||||
static void default_regs_w(uint32 address, uint32 data);
|
||||
static uint32 default_regs_r(uint32 address);
|
||||
|
@ -92,7 +97,7 @@ static void tekken_regs_w(uint32 address, uint32 data);
|
|||
- copy protection device
|
||||
- custom ROM banking device
|
||||
*/
|
||||
static const md_entry_t rom_database[CART_CNT] =
|
||||
static const md_entry_t rom_database[] =
|
||||
{
|
||||
/* Funny World & Balloon Boy */
|
||||
{0x0000,0x06ab,0x40,0x40,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},1,0,NULL,NULL,NULL,mapper_realtec_w}},
|
||||
|
@ -100,14 +105,29 @@ static const md_entry_t rom_database[CART_CNT] =
|
|||
{0xffff,0xf863,0x40,0x40,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},1,0,NULL,NULL,NULL,mapper_realtec_w}},
|
||||
/* Earth Defense */
|
||||
{0xffff,0x44fb,0x40,0x40,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},1,0,NULL,NULL,NULL,mapper_realtec_w}},
|
||||
/* Tom Clown */
|
||||
{0x0000,0xc0cd,0x40,0x40,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},1,0,NULL,NULL,NULL,mapper_realtec_w}},
|
||||
|
||||
/* 1800-in-1 */
|
||||
{0x3296,0x2370,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,1,mapper_128k_multi_r,m68k_unused_8_w,NULL,NULL}},
|
||||
|
||||
/* Golden Mega 250-in-1 */
|
||||
{0xe43c,0x886f,0x08,0x08,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,1,NULL,m68k_unused_8_w,NULL,mapper_256k_multi_w}},
|
||||
|
||||
/* RADICA (Volume 1) (bad dump ?) */
|
||||
{0x0000,0x2326,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,1,mapper_radica_r,NULL,NULL,NULL}},
|
||||
{0x0000,0x2326,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,1,mapper_64k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
/* RADICA (Volume 1) */
|
||||
{0x24f4,0xfc84,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_radica_r,NULL,NULL,NULL}},
|
||||
{0x24f4,0xfc84,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_64k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
/* RADICA (Volume 2) */
|
||||
{0x104f,0x32e9,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_radica_r,NULL,NULL,NULL}},
|
||||
{0xd951,0x78d0,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_64k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
/* RADICA (Volume 3 - Super Sonic Gold edition) */
|
||||
{0x0000,0x1f25,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_64k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
/* RADICA (Street Fighter II CE edition) */
|
||||
{0x1add,0xa838,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_64k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
/* RADICA (Street Fighter II CE edition) (PAL) */
|
||||
{0x104f,0x32e9,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_64k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
/* RADICA (Sensible Soccer Plus edition) (PAL) */
|
||||
{0x0000,0x1f7f,0x00,0x00,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,0,mapper_128k_radica_r,m68k_unused_8_w,NULL,NULL}},
|
||||
|
||||
|
||||
/* Tenchi wo Kurau III: Sangokushi Gaiden - Chinese Fighter */
|
||||
|
@ -127,7 +147,7 @@ static const md_entry_t rom_database[CART_CNT] =
|
|||
/* Super King Kong 99 */
|
||||
{0x0000,0x7d6e,0x60,0x7f,{{0x00,0x00,0x00,0x00},{0xf00007,0xf00007,0xf00007,0xffffff},{0x600001,0x600003,0x600005,0x000000},0,1,NULL,NULL,default_regs_r,custom_regs_w}},
|
||||
/* Gunfight 3-in-1 */
|
||||
{0x0000,0x6ff8,0x60,0x7f,{{0x00,0x00,0x00,0x00},{0xf00007,0xf00007,0xf00007,0xffffff},{0x600001,0x600003,0x600005,0x000000},0,1,NULL,NULL,default_regs_r,custom_regs_w}},
|
||||
{0x0000,0x6ff8,0x60,0x7f,{{0x00,0x00,0x00,0x00},{0xf00007,0xf00007,0xf00007,0xffffff},{0x600001,0x600003,0x600005,0x000000},0,1,NULL,NULL,default_regs_r,custom_regs_w}},
|
||||
/* Pokemon Stadium */
|
||||
{0x0000,0x843c,0x70,0x7f,{{0x00,0x00,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x000000,0x000000,0x000000,0x000000},0,1,NULL,NULL,NULL,custom_regs_w}},
|
||||
|
||||
|
@ -181,6 +201,8 @@ static const md_entry_t rom_database[CART_CNT] =
|
|||
{0x0000,0x1585,0x40,0x40,{{0x55,0x0f,0xaa,0xf0},{0xffffff,0xffffff,0xffffff,0xffffff},{0x400000,0x400002,0x400004,0x400006},0,0,NULL,NULL,default_regs_r_16,NULL}},
|
||||
|
||||
|
||||
/* Chaoji Puke - Super Poker (correct ROM dump, original release is an overdump) */
|
||||
{0xffff,0xd7b0,0x40,0x40,{{0x55,0x0f,0xaa,0xf0},{0xffffff,0xffffff,0xffffff,0xffffff},{0x400000,0x400002,0x400004,0x400006},0,0,NULL,NULL,default_regs_r,NULL}},
|
||||
/* Super Bubble Bobble */
|
||||
{0x0000,0x16cd,0x40,0x40,{{0x55,0x0f,0x00,0x00},{0xffffff,0xffffff,0xffffff,0xffffff},{0x400000,0x400002,0x000000,0x000000},0,0,NULL,NULL,default_regs_r,NULL}},
|
||||
/* Tenchi wo Kurau II - The Battle of Red Cliffs (Unl) */
|
||||
|
@ -240,9 +262,6 @@ static const md_entry_t rom_database[CART_CNT] =
|
|||
};
|
||||
|
||||
|
||||
static uint8* mapper_32k_scratch = NULL;
|
||||
|
||||
|
||||
/************************************************************
|
||||
Cart Hardware initialization
|
||||
*************************************************************/
|
||||
|
@ -322,6 +341,9 @@ void md_cart_init(void)
|
|||
/* ROM is mirrored each 2^k bytes */
|
||||
cart.mask = size - 1;
|
||||
|
||||
/* no special external hardware required by default */
|
||||
cart.special = 0;
|
||||
|
||||
/**********************************************
|
||||
DEFAULT CARTRIDGE MAPPING
|
||||
***********************************************/
|
||||
|
@ -373,13 +395,12 @@ void md_cart_init(void)
|
|||
sram_init();
|
||||
eeprom_i2c_init();
|
||||
|
||||
/* external SRAM */
|
||||
/* memory-mapped SRAM */
|
||||
if (sram.on && !sram.custom)
|
||||
{
|
||||
/* disabled on startup if ROM is mapped in same area */
|
||||
if (cart.romsize <= sram.start)
|
||||
/* SRAM is mapped by default unless it overlaps with ROM area (Phantasy Star 4, Beyond Oasis/Legend of Thor, World Series Baseball 9x, Duke Nukem 3D,...) */
|
||||
if (sram.start >= cart.romsize)
|
||||
{
|
||||
/* initialize m68k bus handlers */
|
||||
m68k.memory_map[sram.start >> 16].base = sram.sram;
|
||||
m68k.memory_map[sram.start >> 16].read8 = sram_read_byte;
|
||||
m68k.memory_map[sram.start >> 16].read16 = sram_read_word;
|
||||
|
@ -388,6 +409,34 @@ void md_cart_init(void)
|
|||
zbank_memory_map[sram.start >> 16].read = sram_read_byte;
|
||||
zbank_memory_map[sram.start >> 16].write = sram_write_byte;
|
||||
}
|
||||
|
||||
/* support for Triple Play 96 & Triple Play - Gold Edition mapping */
|
||||
else if ((strstr(rominfo.product,"T-172026") != NULL) || (strstr(rominfo.product,"T-172116") != NULL))
|
||||
{
|
||||
/* $000000-$1fffff: cartridge ROM (lower 2MB) */
|
||||
/* $200000-$2fffff: SRAM (32KB mirrored) */
|
||||
/* NB: existing 4MB ROM dumps include SRAM data at ROM offsets 0x200000-0x2fffff */
|
||||
for (i=0x20; i<0x30; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = sram.sram;
|
||||
m68k.memory_map[i].read8 = sram_read_byte;
|
||||
m68k.memory_map[i].read16 = sram_read_word;
|
||||
m68k.memory_map[i].write8 = sram_write_byte;
|
||||
m68k.memory_map[i].write16 = sram_write_word;
|
||||
zbank_memory_map[i].read = sram_read_byte;
|
||||
zbank_memory_map[i].write = sram_write_byte;
|
||||
}
|
||||
|
||||
/* $300000-$3fffff: cartridge ROM (upper 1MB) */
|
||||
/* NB: only real (3MB) Mask ROM dumps need ROM offsets 0x200000-0x2fffff to be remapped to this area */
|
||||
if (READ_BYTE(cart.rom, 0x200000) != 0xFF)
|
||||
{
|
||||
for (i=0x30; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + ((i - 0x10) << 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
|
@ -399,38 +448,6 @@ void md_cart_init(void)
|
|||
svp_init();
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
J-CART
|
||||
***********************************************/
|
||||
cart.special = 0;
|
||||
if ((strstr(rominfo.product,"00000000") && (rominfo.checksum == 0x168b)) || /* Super Skidmarks, Micro Machines Military */
|
||||
(strstr(rominfo.product,"00000000") && (rominfo.checksum == 0x165e)) || /* Pete Sampras Tennis (1991), Micro Machines 96 */
|
||||
(strstr(rominfo.product,"00000000") && (rominfo.checksum == 0xcee0)) || /* Micro Machines Military (bad) */
|
||||
(strstr(rominfo.product,"00000000") && (rominfo.checksum == 0x2c41)) || /* Micro Machines 96 (bad) */
|
||||
(strstr(rominfo.product,"XXXXXXXX") && (rominfo.checksum == 0xdf39)) || /* Sampras Tennis 96 */
|
||||
(strstr(rominfo.product,"T-123456") && (rominfo.checksum == 0x1eae)) || /* Sampras Tennis 96 */
|
||||
(strstr(rominfo.product,"T-120066") && (rominfo.checksum == 0x16a4)) || /* Pete Sampras Tennis (1994)*/
|
||||
strstr(rominfo.product,"T-120096")) /* Micro Machines 2 */
|
||||
{
|
||||
if (cart.romsize <= 0x380000) /* just to be sure (checksum might not be enough) */
|
||||
{
|
||||
cart.special |= HW_J_CART;
|
||||
|
||||
/* force port 1 setting */
|
||||
if (input.system[1] != SYSTEM_WAYPLAY)
|
||||
{
|
||||
old_system[1] = input.system[1];
|
||||
input.system[1] = SYSTEM_MD_GAMEPAD;
|
||||
}
|
||||
|
||||
/* extra connectors mapped at $38xxxx or $3Fxxxx */
|
||||
m68k.memory_map[0x38].read16 = jcart_read;
|
||||
m68k.memory_map[0x38].write16 = jcart_write;
|
||||
m68k.memory_map[0x3f].read16 = jcart_read;
|
||||
m68k.memory_map[0x3f].write16 = jcart_write;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
LOCK-ON
|
||||
***********************************************/
|
||||
|
@ -456,54 +473,45 @@ void md_cart_init(void)
|
|||
|
||||
case TYPE_SK:
|
||||
{
|
||||
FILE *f;
|
||||
|
||||
/* store S&K ROM above cartridge ROM (and before backup memory) */
|
||||
if (cart.romsize > 0x600000) break;
|
||||
|
||||
/* load Sonic & Knuckles ROM (2 MB) */
|
||||
f = fopen(SK_ROM,"rb");
|
||||
if (!f) break;
|
||||
for (i=0; i<0x200000; i+=0x1000)
|
||||
/* try to load Sonic & Knuckles ROM file (2 MB) */
|
||||
if (load_archive(SK_ROM, cart.rom + 0x600000, 0x200000, NULL) == 0x200000)
|
||||
{
|
||||
fread(cart.rom + 0x600000 + i, 0x1000, 1, f);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
/* load Sonic 2 UPMEM ROM (256 KB) */
|
||||
f = fopen(SK_UPMEM,"rb");
|
||||
if (!f) break;
|
||||
for (i=0; i<0x40000; i+=0x1000)
|
||||
{
|
||||
fread(cart.rom + 0x900000 + i, 0x1000, 1, f);
|
||||
}
|
||||
fclose(f);
|
||||
/* check ROM header */
|
||||
if (!memcmp(cart.rom + 0x600000 + 0x120, "SONIC & KNUCKLES",16))
|
||||
{
|
||||
/* try to load Sonic 2 & Knuckles UPMEM ROM (256 KB) */
|
||||
if (load_archive(SK_UPMEM, cart.rom + 0x900000, 0x40000, NULL) == 0x40000)
|
||||
{
|
||||
/* $000000-$1FFFFF is mapped to S&K ROM */
|
||||
for (i=0x00; i<0x20; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + 0x600000 + (i << 16);
|
||||
}
|
||||
|
||||
#ifdef LSB_FIRST
|
||||
for (i=0; i<0x200000; i+=2)
|
||||
{
|
||||
/* Byteswap ROM */
|
||||
uint8 temp = cart.rom[i + 0x600000];
|
||||
cart.rom[i + 0x600000] = cart.rom[i + 0x600000 + 1];
|
||||
cart.rom[i + 0x600000 + 1] = temp;
|
||||
}
|
||||
for (i=0; i<0x200000; i+=2)
|
||||
{
|
||||
/* Byteswap ROM */
|
||||
uint8 temp = cart.rom[i + 0x600000];
|
||||
cart.rom[i + 0x600000] = cart.rom[i + 0x600000 + 1];
|
||||
cart.rom[i + 0x600000 + 1] = temp;
|
||||
}
|
||||
|
||||
for (i=0; i<0x40000; i+=2)
|
||||
{
|
||||
/* Byteswap ROM */
|
||||
uint8 temp = cart.rom[i + 0x900000];
|
||||
cart.rom[i + 0x900000] = cart.rom[i + 0x900000 + 1];
|
||||
cart.rom[i + 0x900000 + 1] = temp;
|
||||
}
|
||||
for (i=0; i<0x40000; i+=2)
|
||||
{
|
||||
/* Byteswap ROM */
|
||||
uint8 temp = cart.rom[i + 0x900000];
|
||||
cart.rom[i + 0x900000] = cart.rom[i + 0x900000 + 1];
|
||||
cart.rom[i + 0x900000 + 1] = temp;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* $000000-$1FFFFF is mapped to S&K ROM */
|
||||
for (i=0x00; i<0x20; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + 0x600000 + (i << 16);
|
||||
cart.special |= HW_LOCK_ON;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cart.special |= HW_LOCK_ON;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -519,7 +527,7 @@ void md_cart_init(void)
|
|||
memset(&cart.hw, 0, sizeof(cart.hw));
|
||||
|
||||
/* search for game into database */
|
||||
for (i=0; i<CART_CNT; i++)
|
||||
for (i=0; i<(sizeof(rom_database)/sizeof(md_entry_t)); i++)
|
||||
{
|
||||
/* known cart found ! */
|
||||
if ((rominfo.checksum == rom_database[i].chk_1) &&
|
||||
|
@ -548,14 +556,8 @@ void md_cart_init(void)
|
|||
j++;
|
||||
}
|
||||
|
||||
if (rom_database[i].cart_hw.regs_w == custom_regs_w)
|
||||
{
|
||||
// allocate extra memory needed for the bank swapping on these carts
|
||||
mapper_32k_scratch = alloc_plain(1 << 20);
|
||||
}
|
||||
|
||||
/* leave loop */
|
||||
i = CART_CNT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -579,18 +581,18 @@ void md_cart_init(void)
|
|||
if (strstr(rominfo.consoletype,"SEGA SSF"))
|
||||
{
|
||||
/* Everdrive extended SSF mapper */
|
||||
cart.hw.bankshift = 1;
|
||||
cart.hw.time_w = mapper_512k_w;
|
||||
|
||||
/* specific !TIME handler */
|
||||
cart.hw.time_w = mapper_ssf2_w;
|
||||
/* cartridge ROM mapping is reinitialized on /VRES */
|
||||
cart.hw.bankshift = 1;
|
||||
}
|
||||
if (strstr(rominfo.domestic,"SUPER STREET FIGHTER2"))
|
||||
else if (strstr(rominfo.domestic,"SUPER STREET FIGHTER2"))
|
||||
{
|
||||
/* SSF2 mapper */
|
||||
cart.hw.bankshift = 1;
|
||||
|
||||
/* specific !TIME handler */
|
||||
cart.hw.time_w = mapper_ssf2_w;
|
||||
|
||||
/* cartridge ROM mapping is reinitialized on /VRES */
|
||||
cart.hw.bankshift = 1;
|
||||
}
|
||||
else if (strstr(rominfo.product,"T-5740"))
|
||||
{
|
||||
|
@ -615,6 +617,18 @@ void md_cart_init(void)
|
|||
|
||||
/* no !TIME handler */
|
||||
cart.hw.time_w = m68k_unused_8_w;
|
||||
|
||||
/* cartridge ROM is mapped to $3C0000-$3FFFFF on reset */
|
||||
for (i=0x3c; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + (i << 16);
|
||||
m68k.memory_map[i].read8 = NULL;
|
||||
m68k.memory_map[i].read16 = NULL;
|
||||
m68k.memory_map[i].write8 = m68k_unused_8_w;
|
||||
m68k.memory_map[i].write16 = m68k_unused_16_w;
|
||||
zbank_memory_map[i].read = NULL;
|
||||
zbank_memory_map[i].write = m68k_unused_8_w;
|
||||
}
|
||||
}
|
||||
else if (strstr(rominfo.ROMType,"SF") && strstr(rominfo.product,"002"))
|
||||
{
|
||||
|
@ -637,13 +651,13 @@ void md_cart_init(void)
|
|||
cart.hw.time_r = mapper_sf004_r;
|
||||
cart.hw.time_w = m68k_unused_8_w;
|
||||
|
||||
/* first 256K ROM bank is initially mirrored into $000000-$1FFFFF */
|
||||
/* first 256K ROM bank is mirrored into $000000-$1FFFFF on reset */
|
||||
for (i=0x00; i<0x20; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + ((i & 0x03) << 16);
|
||||
}
|
||||
|
||||
/* 32K static RAM is mapped to $200000-$2FFFFF (disabled on startup) */
|
||||
/* 32K static RAM mapped to $200000-$2FFFFF is disabled on reset */
|
||||
for (i=0x20; i<0x30; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = sram.sram;
|
||||
|
@ -666,6 +680,27 @@ void md_cart_init(void)
|
|||
zbank_memory_map[i].write = zbank_unused_w;
|
||||
}
|
||||
}
|
||||
else if (strstr(rominfo.ROMType,"GM") && strstr(rominfo.product,"00000000-42"))
|
||||
{
|
||||
/* Flashkit MD mapper */
|
||||
m68k.memory_map[0x00].write8 = mapper_flashkit_w;
|
||||
m68k.memory_map[0x00].write16 = mapper_flashkit_w;
|
||||
zbank_memory_map[0x00].write = mapper_flashkit_w;
|
||||
}
|
||||
else if ((cart.romsize = 0x400000) &&
|
||||
(READ_BYTE(cart.rom, 0x200150) == 'C') &&
|
||||
(READ_BYTE(cart.rom, 0x200151) == 'A') &&
|
||||
(READ_BYTE(cart.rom, 0x200152) == 'N') &&
|
||||
(READ_BYTE(cart.rom, 0x200153) == 'O') &&
|
||||
(READ_BYTE(cart.rom, 0x200154) == 'N'))
|
||||
{
|
||||
/* Canon - Legend of the new Gods (4MB dump) */
|
||||
cart.hw.time_w = mapper_wd1601_w;
|
||||
cart.hw.bankshift = 1;
|
||||
sram.on = 1;
|
||||
sram.start = 0x200000;
|
||||
sram.end = 0x201fff;
|
||||
}
|
||||
else if ((*(uint16 *)(cart.rom + 0x08) == 0x6000) && (*(uint16 *)(cart.rom + 0x0a) == 0x01f6) && (rominfo.realchecksum == 0xf894))
|
||||
{
|
||||
/* Super Mario World 64 (unlicensed) mapper */
|
||||
|
@ -700,7 +735,7 @@ void md_cart_init(void)
|
|||
}
|
||||
else if (cart.romsize > 0x400000)
|
||||
{
|
||||
/* assume linear ROM mapper without bankswitching (max. 10MB) */
|
||||
/* assume linear ROM mapping by default (max. 10MB) */
|
||||
for (i=0x40; i<0xA0; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + (i<<16);
|
||||
|
@ -835,25 +870,37 @@ static void mapper_sega_w(uint32 data)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Everdrive extended SSF ROM bankswitch
|
||||
documented by Krikzz (http://krikzz.com/pub/support/mega-ed/dev/extended_ssf.txt)
|
||||
*/
|
||||
static void mapper_512k_w(uint32 address, uint32 data)
|
||||
{
|
||||
uint32 i;
|
||||
|
||||
/* 512K ROM paging */
|
||||
uint8 *src = cart.rom + ((data << 19) & cart.mask);
|
||||
|
||||
/* cartridge area ($000000-$3FFFFF) is divided into 8 x 512K banks */
|
||||
address = (address << 2) & 0x38;
|
||||
|
||||
/* remap selected ROM page to selected bank */
|
||||
for (i=0; i<8; i++)
|
||||
{
|
||||
m68k.memory_map[address++].base = src + (i<<16);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Super Street Fighter 2 ROM bankswitch
|
||||
documented by Bart Trzynadlowski (http://www.trzy.org/files/ssf2.txt)
|
||||
documented by Bart Trzynadlowski (http://emu-docs.org/Genesis/ssf2.txt)
|
||||
*/
|
||||
static void mapper_ssf2_w(uint32 address, uint32 data)
|
||||
{
|
||||
/* 8 x 512k banks */
|
||||
address = (address << 2) & 0x38;
|
||||
|
||||
/* bank 0 remains unchanged */
|
||||
if (address)
|
||||
/* only banks 1-7 are remappable, bank 0 remains unchanged */
|
||||
if (address & 0x0E)
|
||||
{
|
||||
uint32 i;
|
||||
uint8 *src = cart.rom + (data << 19);
|
||||
|
||||
for (i=0; i<8; i++)
|
||||
{
|
||||
m68k.memory_map[address++].base = src + (i<<16);
|
||||
}
|
||||
mapper_512k_w(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -958,59 +1005,23 @@ static void mapper_sf001_w(uint32 address, uint32 data)
|
|||
*/
|
||||
static void mapper_sf002_w(uint32 address, uint32 data)
|
||||
{
|
||||
/* 8 x 512k banks */
|
||||
address = (address << 2) & 0x38;
|
||||
|
||||
/* bank 0 remains unchanged */
|
||||
if (address)
|
||||
{
|
||||
uint32 i;
|
||||
uint8 *src = cart.rom + (data << 19);
|
||||
|
||||
for (i = 0; i<8; i++)
|
||||
{
|
||||
m68k.memory_map[address++].base = src + (i << 16);
|
||||
}
|
||||
}
|
||||
else // emulate turning on SRAM
|
||||
{
|
||||
if (data & 1)
|
||||
{
|
||||
if (sram.on)
|
||||
{
|
||||
/* Backup RAM mapped to $200000-$20ffff (normally mirrored up to $3fffff but this breaks Sonic Megamix and no game need it) */
|
||||
cart.hw.bankshift = m68k.memory_map[0x20].base;
|
||||
m68k.memory_map[0x20].base = sram.sram;
|
||||
m68k.memory_map[0x20].read8 = sram_read_byte;
|
||||
m68k.memory_map[0x20].read16 = sram_read_word;
|
||||
zbank_memory_map[0x20].read = sram_read_byte;
|
||||
|
||||
/* Backup RAM write protection */
|
||||
if (data & 2)
|
||||
{
|
||||
m68k.memory_map[0x20].write8 = m68k_unused_8_w;
|
||||
m68k.memory_map[0x20].write16 = m68k_unused_16_w;
|
||||
zbank_memory_map[0x20].write = zbank_unused_w;
|
||||
}
|
||||
else
|
||||
{
|
||||
m68k.memory_map[0x20].write8 = sram_write_byte;
|
||||
m68k.memory_map[0x20].write16 = sram_write_word;
|
||||
zbank_memory_map[0x20].write = sram_write_byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// automatically turn off writing to SRAM if SRAM is not visible
|
||||
m68k.memory_map[0x20].write8 = m68k_unused_8_w;
|
||||
m68k.memory_map[0x20].write16 = m68k_unused_16_w;
|
||||
zbank_memory_map[0x20].write = zbank_unused_w;
|
||||
|
||||
// put the ROM data back in the memory map
|
||||
m68k.memory_map[0x20].base = cart.hw.bankshift;
|
||||
}
|
||||
}
|
||||
int i;
|
||||
if (data & 0x80)
|
||||
{
|
||||
/* $000000-$1BFFFF mapped to $200000-$3BFFFF */
|
||||
for (i=0x20; i<0x3C; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + ((i & 0x1F) << 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* $200000-$3BFFFF mapped to $200000-$3BFFFF */
|
||||
for (i=0x20; i<0x3C; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + (i << 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1236,7 +1247,45 @@ static uint32 mapper_t5740_r(uint32 address)
|
|||
return READ_BYTE(cart.rom, address);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
FlashKit MD mapper (very limited M29W320xx Flash memory support -- enough for unlicensed games using device signature as protection)
|
||||
*/
|
||||
static void mapper_flashkit_w(uint32 address, uint32 data)
|
||||
{
|
||||
/* Increment Bus Write counter */
|
||||
cart.hw.regs[0]++;
|
||||
|
||||
/* Wait for 3 consecutive bus writes */
|
||||
if (cart.hw.regs[0] == 3)
|
||||
{
|
||||
/* assume 'Auto Select' command */
|
||||
m68k.memory_map[0x0].read16 = mapper_flashkit_r;
|
||||
}
|
||||
else if (cart.hw.regs[0] == 4)
|
||||
{
|
||||
/* assume 'Read/Reset' command */
|
||||
m68k.memory_map[0x0].read16 = NULL;
|
||||
|
||||
/* reset Bus Write counter */
|
||||
cart.hw.regs[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32 mapper_flashkit_r(uint32 address)
|
||||
{
|
||||
/* hard-coded device signature */
|
||||
switch (address & 0x06)
|
||||
{
|
||||
case 0x00: /* Manufacturer Code (STMicroelectronics) */
|
||||
return 0x0020;
|
||||
case 0x02: /* Device Code (M29W320EB) */
|
||||
return 0x2257;
|
||||
default: /* not supported */
|
||||
return 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Super Mario World 64 (unlicensed) mapper
|
||||
*/
|
||||
static void mapper_smw_64_w(uint32 address, uint32 data)
|
||||
|
@ -1489,8 +1538,8 @@ static void mapper_32k_w(uint32 data)
|
|||
{
|
||||
for (i=0; i<0x10; i++)
|
||||
{
|
||||
/* Remap to extra scratch area */
|
||||
m68k.memory_map[i].base = &mapper_32k_scratch[i << 16];
|
||||
/* Remap to unused ROM area */
|
||||
m68k.memory_map[i].base = &cart.rom[0x400000 + (i << 16)];
|
||||
|
||||
/* address = address OR (value << 15) */
|
||||
memcpy(m68k.memory_map[i].base, cart.rom + ((i << 16) | (data & 0x3f) << 15), 0x8000);
|
||||
|
@ -1543,27 +1592,208 @@ static void mapper_64k_multi_w(uint32 address)
|
|||
/* 64 x 64k banks */
|
||||
for (i=0; i<64; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[((address++) & 0x3f) << 16];
|
||||
m68k.memory_map[i].base = &cart.rom[((address + i) & 0x3f) << 16];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Custom ROM Bankswitch used in pirate "1800-in-1" cartridge
|
||||
*/
|
||||
static uint32 mapper_128k_multi_r(uint32 address)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* 16 x 128k banks (2MB ROM) */
|
||||
/* Bank index (B3 B2 B1 B0) is encoded in address lower byte = {0 X B0 B1 X B2 B3 0} */
|
||||
/* Note: {0 B0 X B1 X B2 B3 0} also works, see below for the 9 unique values being used for all menu entries
|
||||
read16 00A13000 (0002FBEE) => 0x000000-0x03ffff (2x128KB)
|
||||
read16 00A13018 (00FF2056) => 0x040000-0x07ffff (2x128KB)
|
||||
read16 00A13004 (00FF2120) => 0x080000-0x0bffff (2x128KB)
|
||||
read16 00A1301C (00FF20A6) => 0x0c0000-0x0fffff (2x128KB)
|
||||
read16 00A1300A (00FF20BA) => 0x100000-0x13ffff (2x128KB)
|
||||
read16 00A1301A (00FF20CE) => 0x140000-0x17ffff (2x128KB)
|
||||
read16 00A1300E (00FF20F4) => 0x180000-0x1bffff (2x128KB)
|
||||
read16 00A1301E (00FF2136) => 0x1c0000-0x1dffff (1x128KB)
|
||||
read16 00A1307E (00FF2142) => 0x1e0000-0x1fffff (1x128KB)
|
||||
*/
|
||||
int bank = ((address & 0x02) << 2) | (address & 0x04) | ((address & 0x10) >> 3) | ((address & 0x20) >> 5);
|
||||
|
||||
/* remap cartridge area (64 x 64k banks) */
|
||||
address = bank << 1;
|
||||
for (i=0x00; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[((address + i) & 0x3f) << 16];
|
||||
}
|
||||
|
||||
/* returned value changes the menu title and number of entries in the 'game' list (the number of distinct games does not change though) */
|
||||
/* 0x00 => 9-in-1 */
|
||||
/* 0x01 => 190-in-1 */
|
||||
/* 0x02 => 888-in-1 */
|
||||
/* 0x03 => 1800-in-1 */
|
||||
/* real cartridge board has switches to select between the four different menus but here we force the largest menu selection (each other menus being a subset of the next larger menu) */
|
||||
return 0x03;
|
||||
}
|
||||
|
||||
/*
|
||||
Custom ROM Bankswitch used in pirate "Golden Mega 250-in-1" cartridge
|
||||
*/
|
||||
static void mapper_256k_multi_w(uint32 address, uint32 data)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* 8 x 256k banks (2MB ROM) */
|
||||
/* Bank index (B2 B1 B0) is encoded in data lower byte = {B1 B0 X X 0 0 0 B2} */
|
||||
/* Note: {X B0 B1 B2 0 0 0 X}, {B1 B0 X B2 0 0 0 X} or {X B0 B1 X 0 0 0 B2} also work, see below for the 4 unique values being used for all menu entries
|
||||
write16 00089000 = 0000 (00FF0006) => 0x000000-0x03ffff (1x256KB)
|
||||
write16 00089000 = 0040 (00FF0006) => 0x040000-0x07ffff (1x256KB)
|
||||
write16 00089000 = 00A0 (00FF0006) => 0x080000-0x0fffff (2x256KB)
|
||||
write16 00089000 = 0011 (00FF0006) => 0x100000-0x1fffff (4x256KB)
|
||||
*/
|
||||
int bank = ((data & 0x01) << 2) | ((data & 0xc0) >> 6);
|
||||
|
||||
/* remap cartridge area (64 x 64k banks) */
|
||||
address = bank << 2;
|
||||
for (i=0x00; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[((address + i) & 0x3f) << 16];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Custom ROM Bankswitch used in "Canon - Legend of the New Gods"
|
||||
(uses WD1601 QFPL V1.01 board also used in chinese X-in-1 pirates sold by mindkids)
|
||||
*/
|
||||
static void mapper_wd1601_w(uint32 address, uint32 data)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* !TIME write16 0xA13002 = 0x3002 (00FFFE0C) */
|
||||
/* The board probably allows up to 256MB Flash ROM remapping but this game only has 4MB ROM chip */
|
||||
if ((address & 0xfe) == 0x02)
|
||||
{
|
||||
/* upper 2MB ROM mapped to $000000-$1fffff */
|
||||
for (i=0; i<0x20; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[(0x20 + i) << 16];
|
||||
}
|
||||
|
||||
/* backup RAM (8KB) mapped to $2000000-$3fffff */
|
||||
for (i=0x20; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = sram.sram;
|
||||
m68k.memory_map[i].read8 = sram_read_byte;
|
||||
m68k.memory_map[i].read16 = sram_read_word;
|
||||
m68k.memory_map[i].write8 = sram_write_byte;
|
||||
m68k.memory_map[i].write16 = sram_write_word;
|
||||
zbank_memory_map[i].read = sram_read_byte;
|
||||
zbank_memory_map[i].write = sram_write_byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Custom ROM Bankswitch used in RADICA cartridges
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++
|
||||
Two different boards seem to exist (one with support for 64KB banks mapping and another one supporting 128KB banks + battery-RAM).
|
||||
Radica Volume 1 requires 64KB banks mapping as the menu is located at a 64KB boundary.
|
||||
Sensible Soccer Plus edition requires 128KB banks mapping with only VA6-VA2 being used to select bank index (VA1 is ignored).
|
||||
Sensible Soccer Plus edition also requires 8KB backup RAM to be mapped in higher 2MB range.
|
||||
Other games support both 64KB or 128KB mapping so it's not clear what exact board they are using but none require SRAM so we use 64KB mapper by default.
|
||||
Note that Radica Volume 3 uses similar ROM mapping as Sensible Soccer Plus edition so it might be using same 128KB board, without any SRAM chip connected.
|
||||
*/
|
||||
static uint32 mapper_radica_r(uint32 address)
|
||||
static uint32 mapper_64k_radica_r(uint32 address)
|
||||
{
|
||||
int i = 0;
|
||||
address = (address >> 1);
|
||||
|
||||
/* 64 x 64k banks */
|
||||
for (i = 0; i < 64; i++)
|
||||
/*
|
||||
Volume 1
|
||||
--------
|
||||
000000h-0fffffh: Kid Chameleon : !TIME read16 0xA13000 (00FF103A)
|
||||
100000h-1fffffh: Dr Robotnik's Mean Bean Machine : !TIME read16 0xA13020 (00FF101E)
|
||||
200000h-27ffffh: Sonic The Hedgehog : !TIME read16 0xA13040 (00FF101E)
|
||||
280000h-2fffffh: Golden Axe : !TIME read16 0xA13050 (00FF101E)
|
||||
300000h-37ffffh: Altered Beast : !TIME read16 0xA13060 (00FF101E)
|
||||
380000h-39ffffh: Flicky : !TIME read16 0xA13070 (00FF101E)
|
||||
3a0000h-3effffh: N/A : N/A
|
||||
3f0000h-3fffffh: Radica Menu (64 KB) : !TIME read16 0xA1307E (00FF1006)
|
||||
|
||||
Volume 2
|
||||
--------
|
||||
000000h-0fffffh: Sonic The Hedgehog 2 : !TIME read16 0xA13000 (00FF103A)
|
||||
100000h-1fffffh: The Ooze : !TIME read16 0xA13020 (00FF101E)
|
||||
200000h-2fffffh: Ecco The Dolphin : !TIME read16 0xA13040 (00FF101E)
|
||||
300000h-37ffffh: Gain Ground : !TIME read16 0xA13060 (00FF101E)
|
||||
380000h-3bffffh: Alex Kidd in Enchanted Castle : !TIME read16 0xA13070 (00FF101E)
|
||||
3c0000h-3dffffh: Columns : !TIME read16 0xA13078 (00FF101E)
|
||||
3e0000h-3fffffh: Radica Menu (128 KB) : !TIME read16 0xA1307C (00FF1006)
|
||||
|
||||
Volume 3 - Super Sonic Gold edition
|
||||
-----------------------------------
|
||||
000000h-01ffffh: Radica Menu (128 KB) : N/A
|
||||
020000h-07ffffh: N/A : N/A
|
||||
080000h-0fffffh: Sonic The Hedgehog : !TIME read16 0xA13010 (00FF1012)
|
||||
100000h-1fffffh: Sonic The Hedgehog 2 : !TIME read16 0xA13020 (00FF1012)
|
||||
200000h-2fffffh: Sonic Spinball : !TIME read16 0xA13040 (00FF1012)
|
||||
300000h-3fffffh: Dr Robotnik's Mean Bean Machine : !TIME read16 0xA13060 (00FF1012)
|
||||
|
||||
Street Fighter 2 CE edition
|
||||
---------------------------
|
||||
000000h-2fffffh: Street Fighter 2 CE : !TIME read16 0xA13000 (00FF103A)
|
||||
300000h-3bffffh: Ghouls'n Ghosts : !TIME read16 0xA13060 (00FF101E)
|
||||
3c0000h-3dffffh: Radica Menu (128 KB) : !TIME read16 0xA13078 (00FF1006)
|
||||
3e0000h-3fffffh: N/A : N/A
|
||||
*/
|
||||
int index = (address >> 1) & 0x3F;
|
||||
|
||||
/* $000000-$3fffff area is mapped to selected banks (OR gates between VA21-VA16 and selected index) */
|
||||
for (i = 0x00; i < 0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[((address++)& 0x3f)<< 16];
|
||||
m68k.memory_map[i].base = &cart.rom[(index | i) << 16];
|
||||
}
|
||||
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
static uint32 mapper_128k_radica_r(uint32 address)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
/* 32 x 128k banks */
|
||||
/*
|
||||
Sensible Soccer Plus edition
|
||||
----------------------------
|
||||
000000h-01ffffh: Radica Menu (128 KB) : N/A
|
||||
020000h-07ffffh: N/A : N/A
|
||||
080000h-0fffffh: Sensible Soccer : !TIME read16 0xA13010 (00FF1012)
|
||||
100000h-1fffffh: Mega-Lo-Mania : !TIME read16 0xA13022 (00FF1012)
|
||||
200000h-37ffffh: Cannon Fodder : !TIME read16 0xA13042 (00FF1012)
|
||||
380000h-3fffffh: N/A : N/A
|
||||
|
||||
Note: address bit 1 is ignored for bank selection but might be used to enable/disable SRAM mapping ?
|
||||
*/
|
||||
int index = (address >> 1) & 0x3E;
|
||||
|
||||
/* $000000-$1fffff area is mapped to selected banks (OR gates between VA20-VA17 and selected index) */
|
||||
for (i = 0x00; i < 0x20; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[(index | i) << 16];
|
||||
}
|
||||
|
||||
/* $200000-$3fffff area is mapped to 8KB SRAM (mirrored) */
|
||||
for (i = 0x20; i < 0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = sram.sram;
|
||||
m68k.memory_map[i].read8 = sram_read_byte;
|
||||
m68k.memory_map[i].read16 = sram_read_word;
|
||||
m68k.memory_map[i].write8 = sram_write_byte;
|
||||
m68k.memory_map[i].write16 = sram_write_word;
|
||||
zbank_memory_map[i].read = sram_read_byte;
|
||||
zbank_memory_map[i].write = sram_write_byte;
|
||||
}
|
||||
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
/************************************************************
|
||||
default !TIME signal handler
|
||||
|
@ -1571,15 +1801,22 @@ static uint32 mapper_radica_r(uint32 address)
|
|||
|
||||
static void default_time_w(uint32 address, uint32 data)
|
||||
{
|
||||
if (address < 0xa13040)
|
||||
/* enable multi-game cartridge mapper by default */
|
||||
if (address < 0xa13060)
|
||||
{
|
||||
/* unlicensed cartridges mapper (default) */
|
||||
mapper_64k_multi_w(address);
|
||||
return;
|
||||
}
|
||||
|
||||
/* official cartridges mapper (default) */
|
||||
mapper_sega_w(data);
|
||||
/* enable "official" cartridge mapper by default */
|
||||
if (address > 0xa130f1)
|
||||
{
|
||||
mapper_512k_w(address, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapper_sega_w(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue