2021-01-31 15:51:09 +01:00
/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file :
* Copyright ( C ) 2020 NewRisingSun
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
2023-05-28 10:40:43 +02:00
/* Multicarts based around the ING003C and TEC9719 enhanced MMC3 ASICs.
ING003C ' s scanline counter is inverted from MMC3 , while TEC9719 is normal .
Both ASICs natively only support 256 KiB of CHR ROM / RAM ; cartridges with more than that re - purpose higher PRG address as CHR address lines . There are two connection variants of this re - purposing .
Mapper 422 : TEC9719 , CHR A18 = PRG A20 , CHR A19 = PRG A21
Mapper 126 : TEC9719 , CHR A18 = PRG A21 , CHR A19 = PRG A20
Mapper 534 : ING003C , CHR A18 = PRG A20 , CHR A19 = PRG A21
Cartridges with more than one PRG ROM chip are sometimes not connected to form one continuous address space . Submappers are used to avoid having to repeat data to accomodate them .
Submapper 0 : Normal connection
Submapper 1 : PRG A21 ( 2 MiB bank ) selects between two 1 MiB chips
Submapper 2 : Register bit 6001.2 ( undocumented in data sheet ) selects between two 1 MiB chips
2025-11-28 12:25:24 +01:00
Submapper 3 : 6000.2 substitutes PRG A14 and CHR A14 with 6000.5
Submapper 4 : LD822 PCB - CHR A20 . . A18 = PRG A20 . . A18
2023-05-28 10:40:43 +02:00
Both ASICs invert the register bit that selects PRG A21 ( 6000.5 ) , hence " EXPREGS[0] ^0x20 " .
2021-01-31 15:51:09 +01:00
*/
# include "mapinc.h"
# include "mmc3.h"
static uint8 reverseCHR_A18_A19 ;
static uint8 invertC000 ;
2023-05-28 10:40:43 +02:00
static uint8 SL0 ;
static uint8 submapper ;
static uint8 getMMC3Bank ( int bank ) {
if ( ~ bank & 1 & & MMC3_cmd & 0x40 ) bank ^ = 2 ;
return bank & 2 ? 0xFE | bank & 1 : DRegBuf [ 6 | bank & 1 ] ;
}
2021-01-31 15:51:09 +01:00
static void wrapPRG ( uint32 A , uint8 V ) {
2021-04-10 02:21:29 +08:00
int prgAND = EXPREGS [ 0 ] & 0x40 ? 0x0F : 0x1F ; /* 128 KiB or 256 KiB inner PRG bank selection */
2023-05-28 10:40:43 +02:00
int prgOR = ( EXPREGS [ 0 ] < < 4 & 0x70 | ( EXPREGS [ 0 ] ^ 0x20 ) < < 3 & 0x180 ) & ~ prgAND ; /* Outer PRG bank */
2025-04-01 20:52:27 +02:00
if ( submapper = = 1 ) prgOR = prgOR & 0x7F | prgOR > > 1 & 0x80 ; /* Submapper 1 uses PRG A21 as a chip select between two 1 MiB chips */
if ( submapper = = 2 ) prgOR = prgOR & 0x7F | EXPREGS [ 1 ] < < 5 & 0x80 ; /* Submapper 2 uses 6001.2 (not documented in datasheet) as a chip select between two 1 MiB chips */
2025-09-17 19:46:40 +02:00
if ( submapper = = 3 & & EXPREGS [ 0 ] & 0x04 ) { /* Submapper 3 replace PRG A14 with PRG A21 */
prgAND & = ~ 0x02 ;
prgOR | = EXPREGS [ 0 ] & 0x20 ? 0x00 : 0x02 ;
}
2023-05-28 10:40:43 +02:00
for ( A = 0 ; A < 4 ; A + + ) {
/* In UNROM-like mode (CT3=1, CT2=1, CT0=1), MMC3 sees A13=0 and A14=CPU A14 during reads, making register 6 apply from $8000-$BFFF, and the fixed bank from $C000-$FFFF.
In NROM - 128 , NROM - 256 , ANROM and UNROM modes ( CT0 = 1 ) , MMC3 sees A13 = 0 and A14 = 0 , making register 6 apply from $ 8000 - $ FFFF . */
V = getMMC3Bank ( A & ( ( EXPREGS [ 3 ] & 0x0D ) = = 0x0D ? 2 : EXPREGS [ 3 ] & 0x01 ? 0 : 3 ) ) ;
/* UNROM and ANROM modes mean that MMC3 register 6 selects 16 and 32 KiB rather than 8 KiB banks. */
if ( EXPREGS [ 3 ] & 0x08 ) switch ( EXPREGS [ 3 ] & 0x03 ) {
case 0 : V = V & 3 | V < < 1 & ~ 3 ; break ; /* PRG A14 appears twice */
case 1 : V = A & 3 | V < < 1 & ~ 1 ; break ; /* 16 KiB mode, bit 0 OR'd with CPU A14 */
case 2 : V = V & 3 | V < < 2 & ~ 3 ; break ; /* PRG A13 and PRG A14 appear twice */
case 3 : V = A & 3 | V < < 2 & ~ 3 ; break ; /* 32 KiB mode */
} else
if ( EXPREGS [ 3 ] & 0x01 ) { /* regular NROM modes */
V = A & 1 | V & ~ 1 ;
if ( EXPREGS [ 3 ] & 0x02 ) V = A & 2 | V & ~ 2 ; /* NROM-256 */
}
setprg8 ( 0x8000 + A * 0x2000 , V & prgAND | prgOR & ~ prgAND ) ;
2021-01-31 15:51:09 +01:00
}
2023-05-28 10:40:43 +02:00
mwrap ( A000B ) ; /* After 8000 write */
2021-01-31 15:51:09 +01:00
}
static void wrapCHR ( uint32 A , uint8 V ) {
2021-04-10 02:21:29 +08:00
int chrAND = EXPREGS [ 0 ] & 0x80 ? 0x7F : 0xFF ; /* 128 KiB or 256 KiB innter CHR bank selection */
int chrOR ; /* outer CHR bank */
if ( reverseCHR_A18_A19 ) /* Mapper 126 swaps CHR A18 and A19 */
2023-05-28 10:40:43 +02:00
chrOR = ( EXPREGS [ 0 ] < < 4 & 0x080 | ( EXPREGS [ 0 ] ^ 0x20 ) < < 3 & 0x100 | EXPREGS [ 0 ] < < 5 & 0x200 ) & ~ chrAND ;
2025-11-28 12:25:24 +01:00
else
if ( submapper = = 4 ) /* LD822 PCB - CHR A20..A18 = PRG A20..A18 */
chrOR = ( EXPREGS [ 0 ] < < 4 & 0x080 | EXPREGS [ 0 ] < < 7 & 0x100 | ~ EXPREGS [ 0 ] < < 4 & 0x200 | EXPREGS [ 0 ] < < 6 & 0x400 ) & ~ chrAND ;
2021-01-31 15:51:09 +01:00
else
2025-04-01 20:52:27 +02:00
chrOR = ( ( EXPREGS [ 0 ] ^ 0x20 ) < < 4 & 0x380 | EXPREGS [ 0 ] < < 8 & 0x400 ) & ~ chrAND ;
2021-01-31 15:51:09 +01:00
2025-09-17 19:46:40 +02:00
if ( submapper = = 3 & & EXPREGS [ 0 ] & 0x04 ) { /* Submapper 3 replace CHR A14 with PRG A21 */
chrAND & = ~ 0x10 ;
chrOR | = EXPREGS [ 0 ] & 0x20 ? 0x00 : 0x10 ;
}
2021-04-10 02:21:29 +08:00
if ( EXPREGS [ 3 ] & 0x10 ) /* CNROM mode: 8 KiB inner CHR bank comes from outer bank register #2 */
2023-05-28 10:40:43 +02:00
setchr8 ( EXPREGS [ 2 ] & ( chrAND > > 3 ) | ( chrOR & ~ chrAND ) > > 3 ) ;
2021-04-10 02:21:29 +08:00
else /* MMC3 CHR mode */
2021-01-31 15:51:09 +01:00
setchr1 ( A , ( V & chrAND ) | chrOR ) ;
}
2023-05-28 10:40:43 +02:00
static void wrapMirroring ( uint8 V ) {
A000B = V ;
if ( EXPREGS [ 3 ] & 0x20 ) { /* ANROM mirroring */
if ( DRegBuf [ 6 ] & 0x10 )
setmirror ( MI_1 ) ;
else
setmirror ( MI_0 ) ;
} else
if ( EXPREGS [ 1 ] & 0x02 ) { /* Extended MMC3 mirroring */
switch ( A000B & 3 ) {
case 0 : setmirror ( MI_V ) ; break ;
case 1 : setmirror ( MI_H ) ; break ;
case 2 : setmirror ( MI_0 ) ; break ;
case 3 : setmirror ( MI_1 ) ; break ;
}
} else /* Normal MMC3 mirroring */
setmirror ( A000B & 1 ? MI_H : MI_V ) ;
}
2021-01-31 15:51:09 +01:00
static DECLFW ( writeWRAM ) {
2023-05-28 10:40:43 +02:00
CartBW ( A , V ) ;
if ( ( A & 3 ) = = 2 ) { /* CNROM Bank (D0-D3), Bank Enable (D4-D6) and Bank Enable Lock (D7) */
2024-05-29 22:40:32 +02:00
int latchMask = 0xFF & ~ ( EXPREGS [ 2 ] & 0x80 ? 0xF0 : 0x00 ) & ~ ( EXPREGS [ 2 ] > > 3 & 0x0E ) ;
2023-05-28 10:40:43 +02:00
EXPREGS [ 2 ] = EXPREGS [ 2 ] & ~ latchMask | V & latchMask ;
FixMMC3CHR ( MMC3_cmd ) ;
} else
2021-01-31 15:51:09 +01:00
if ( ~ EXPREGS [ 3 ] & 0x80 ) {
2021-04-10 02:21:29 +08:00
/* Lock bit clear: Update any outer bank register */
2021-01-31 15:51:09 +01:00
EXPREGS [ A & 3 ] = V ;
FixMMC3PRG ( MMC3_cmd ) ;
FixMMC3CHR ( MMC3_cmd ) ;
2024-05-29 22:40:32 +02:00
mwrap ( A000B ) ; /* After 6001 or 6003 write */
2021-01-31 15:51:09 +01:00
}
}
2024-05-29 22:40:32 +02:00
static DECLFW ( MMC3_CMDWriteA ) { /* In mmc3.c, MMC3_cmd is updated *after* FixMMC3PRG is called, but we need MMC3_cmd in wrapPRG, so work around this problem until the MMC3 core is properly rewritten to actually make sense. */
if ( ( A & 0xE001 ) = = 0x8000 ) {
MMC3_cmd = V ;
FixMMC3PRG ( MMC3_cmd ) ;
FixMMC3CHR ( MMC3_cmd ) ;
} else
MMC3_CMDWrite ( A , V ) ;
}
2023-05-28 10:40:43 +02:00
static DECLFW ( writeCart ) {
if ( ( EXPREGS [ 3 ] & 0x09 ) = = 0x09 ) /* UNROM and ANROM modes treat all writes to $8000-$FFFF as if they were going to $8000-$9FFF */
2024-05-29 22:40:32 +02:00
MMC3_CMDWriteA ( 0x8000 | ( EXPREGS [ 3 ] & 0x08 ? 1 : A ) & 1 , V ) ; /* A0 substitution only looks at bit 3 of register 3 */
2023-05-28 10:40:43 +02:00
else
if ( A > = 0xC000 )
MMC3_IRQWrite ( A , V ^ ( invertC000 ? 0xFF : 0x00 ) ) ; /* Mapper 534 inverts the MMC3 scanline counter reload value */
else
2024-05-29 22:40:32 +02:00
MMC3_CMDWriteA ( A , V ) ;
2021-01-31 15:51:09 +01:00
}
2023-05-28 10:40:43 +02:00
static DECLFR ( readPRG ) {
if ( EXPREGS [ 1 ] & 1 ) A = A & ~ 1 | SL0 & 1 ; /* Replace A0 with SL0 input */
return CartBR ( A ) ;
2021-01-31 15:51:09 +01:00
}
static void reset ( void ) {
2023-05-28 10:40:43 +02:00
SL0 + + ; /* Soft-resetting cycles through SL0 settings */
2021-01-31 15:51:09 +01:00
EXPREGS [ 0 ] = EXPREGS [ 1 ] = EXPREGS [ 2 ] = EXPREGS [ 3 ] = 0 ;
MMC3RegReset ( ) ;
}
static void power ( void ) {
2023-05-28 10:40:43 +02:00
SL0 = 0 ;
2021-01-31 15:51:09 +01:00
EXPREGS [ 0 ] = EXPREGS [ 1 ] = EXPREGS [ 2 ] = EXPREGS [ 3 ] = 0 ;
GenMMC3Power ( ) ;
SetWriteHandler ( 0x6000 , 0x7FFF , writeWRAM ) ;
2023-05-28 10:40:43 +02:00
SetWriteHandler ( 0x8000 , 0xFFFF , writeCart ) ;
SetReadHandler ( 0x8000 , 0xFFFF , readPRG ) ;
2021-01-31 15:51:09 +01:00
}
static void init ( CartInfo * info ) {
GenMMC3_Init ( info , 512 , 256 , 8 , info - > battery ) ;
2023-05-28 10:40:43 +02:00
cwrap = wrapCHR ;
pwrap = wrapPRG ;
mwrap = wrapMirroring ;
submapper = info - > submapper ;
2021-01-31 15:51:09 +01:00
info - > Power = power ;
info - > Reset = reset ;
AddExState ( EXPREGS , 4 , 0 , " EXPR " ) ;
2023-05-28 10:40:43 +02:00
AddExState ( & SL0 , 1 , 0 , " DPSW " ) ;
2021-01-31 15:51:09 +01:00
}
void Mapper126_Init ( CartInfo * info ) {
2023-05-28 10:40:43 +02:00
if ( info - > CRC32 = = 0xEAD80031 | | info - > CRC32 = = 0x6FCBC309 ) { /* Old dumps: Invert CHR A18 */
int i , a , b ;
for ( i = 0 ; i < 0x40000 ; i + + ) {
a = VROM [ 0x00000 + i ] ; b = VROM [ 0x40000 + i ] ;
VROM [ 0x00000 + i ] = b ; VROM [ 0x40000 + i ] = a ;
a = VROM [ 0x80000 + i ] ; b = VROM [ 0xC0000 + i ] ;
VROM [ 0x80000 + i ] = b ; VROM [ 0xC0000 + i ] = a ;
}
}
2021-01-31 15:51:09 +01:00
reverseCHR_A18_A19 = 1 ;
invertC000 = 0 ;
init ( info ) ;
}
void Mapper422_Init ( CartInfo * info ) {
2023-05-28 10:40:43 +02:00
if ( info - > CRC32 = = 0x6D61FE21 | | info - > CRC32 = = 0x3FF46175 | | info - > CRC32 = = 0xA3FF9D9B | | info - > CRC32 = = 0x2BDD0FC2 | | info - > CRC32 = = 0x5789017D | | info - > CRC32 = = 0x46A01871 | | info - > CRC32 = = 0x2466B80A ) {
/* Old dumps: Invert CHR A19 */
int i , a , b ;
for ( i = 0 ; i < 0x80000 ; i + + ) {
a = VROM [ 0x00000 + i ] ; b = VROM [ 0x80000 + i ] ;
VROM [ 0x00000 + i ] = b ; VROM [ 0x80000 + i ] = a ;
}
}
2021-01-31 15:51:09 +01:00
reverseCHR_A18_A19 = 0 ;
invertC000 = 0 ;
init ( info ) ;
}
void Mapper534_Init ( CartInfo * info ) {
2023-05-28 10:40:43 +02:00
if ( info - > CRC32 = = 0x871CFD16 ) {
/* Old dump: Invert PRG A20 */
int i , a , b ;
for ( i = 0 ; i < 0x100000 ; i + + ) {
a = ROM [ 0x000000 + i ] ; b = ROM [ 0x100000 + i ] ;
ROM [ 0x000000 + i ] = b ; ROM [ 0x100000 + i ] = a ;
}
} else
if ( info - > CRC32 = = 0xB2724618 | | info - > CRC32 = = 0x42A9219D ) {
/* Old dumps: Invert PRG A21 */
int i , a , b ;
for ( i = 0 ; i < 0x200000 ; i + + ) {
a = ROM [ 0x000000 + i ] ; b = ROM [ 0x200000 + i ] ;
ROM [ 0x000000 + i ] = b ; ROM [ 0x200000 + i ] = a ;
}
}
2021-01-31 15:51:09 +01:00
reverseCHR_A18_A19 = 0 ;
invertC000 = 1 ;
init ( info ) ;
}