/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 2002 Xodnizel * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* None of this code should use any of the iNES bank switching wrappers. */ #include "mapinc.h" static void (*sfun)(int P); static void (*psfun)(void); void MMC5RunSound(int Count); void MMC5RunSoundHQ(void); static INLINE void MMC5SPRVROM_BANK1(uint32 A,uint32 V) { if(CHRptr[0]) { V&=CHRmask1[0]; MMC5SPRVPage[(A)>>10]=&CHRptr[0][(V)<<10]-(A); } } static INLINE void MMC5BGVROM_BANK1(uint32 A,uint32 V) {if(CHRptr[0]){V&=CHRmask1[0];MMC5BGVPage[(A)>>10]=&CHRptr[0][(V)<<10]-(A);}} static INLINE void MMC5SPRVROM_BANK2(uint32 A,uint32 V) {if(CHRptr[0]){V&=CHRmask2[0];MMC5SPRVPage[(A)>>10]=MMC5SPRVPage[((A)>>10)+1]=&CHRptr[0][(V)<<11]-(A);}} static INLINE void MMC5BGVROM_BANK2(uint32 A,uint32 V) {if(CHRptr[0]){V&=CHRmask2[0];MMC5BGVPage[(A)>>10]=MMC5BGVPage[((A)>>10)+1]=&CHRptr[0][(V)<<11]-(A);}} static INLINE void MMC5SPRVROM_BANK4(uint32 A,uint32 V) {if(CHRptr[0]){V&=CHRmask4[0];MMC5SPRVPage[(A)>>10]=MMC5SPRVPage[((A)>>10)+1]= MMC5SPRVPage[((A)>>10)+2]=MMC5SPRVPage[((A)>>10)+3]=&CHRptr[0][(V)<<12]-(A);}} static INLINE void MMC5BGVROM_BANK4(uint32 A,uint32 V) {if(CHRptr[0]){V&=CHRmask4[0];MMC5BGVPage[(A)>>10]=MMC5BGVPage[((A)>>10)+1]=MMC5BGVPage[((A)>>10)+2]=MMC5BGVPage[((A)>>10)+3]=&CHRptr[0][(V)<<12]-(A);}} static INLINE void MMC5SPRVROM_BANK8(uint32 V) {if(CHRptr[0]){V&=CHRmask8[0];MMC5SPRVPage[0]=MMC5SPRVPage[1]=MMC5SPRVPage[2]=MMC5SPRVPage[3]=MMC5SPRVPage[4]=MMC5SPRVPage[5]=MMC5SPRVPage[6]=MMC5SPRVPage[7]=&CHRptr[0][(V)<<13];}} static INLINE void MMC5BGVROM_BANK8(uint32 V) {if(CHRptr[0]){V&=CHRmask8[0];MMC5BGVPage[0]=MMC5BGVPage[1]=MMC5BGVPage[2]=MMC5BGVPage[3]=MMC5BGVPage[4]=MMC5BGVPage[5]=MMC5BGVPage[6]=MMC5BGVPage[7]=&CHRptr[0][(V)<<13];}} static uint8 PRGBanks[4]; static uint8 WRAMPage; static uint16 CHRBanksA[8], CHRBanksB[4]; static uint8 WRAMMaskEnable[2]; uint8 mmc5ABMode; /* A=0, B=1 */ static uint8 IRQScanline,IRQEnable; static uint8 CHRMode, NTAMirroring, NTFill, ATFill; static uint8 MMC5IRQR; static uint8 MMC5LineCounter; static uint8 mmc5psize, mmc5vsize; static uint8 mul[2]; static uint8 *WRAM=NULL; static uint8 *MMC5fill=NULL; static uint8 *ExRAM=NULL; static uint8 MMC5WRAMsize; static uint8 MMC5WRAMIndex[8]; static uint8 MMC5ROMWrProtect[4]; static uint8 MMC5MemIn[5]; static void MMC5CHRA(void); static void MMC5CHRB(void); typedef struct __cartdata { uint32 crc32; uint8 size; } cartdata; #define Sprite16 (PPU[0]&0x20) //Sprites 8x16/8x8 //#define MMC5SPRVRAMADR(V) &MMC5SPRVPage[(V)>>10][(V)] static inline uint8 * MMC5BGVRAMADR(uint32 A) { if(!Sprite16) { if(mmc5ABMode==0) return &MMC5SPRVPage[(A)>>10][(A)]; else return &MMC5BGVPage[(A)>>10][(A)]; } else return &MMC5BGVPage[(A)>>10][(A)]; } static void mmc5_PPUWrite(uint32 A, uint8 V) { uint32 tmp = A; extern uint8 PALRAM[0x20]; if(tmp>=0x3F00) { // hmmm.... if(!(tmp&0xf)) PALRAM[0x00]=PALRAM[0x04]=PALRAM[0x08]=PALRAM[0x0C]=V&0x3F; else if(tmp&3) PALRAM[(tmp&0x1f)]=V&0x3f; } else if(tmp<0x2000) { if(PPUCHRRAM&(1<<(tmp>>10))) VPage[tmp>>10][tmp]=V; } else { if(PPUNTARAM&(1<<((tmp&0xF00)>>10))) vnapage[((tmp&0xF00)>>10)][tmp&0x3FF]=V; } } uint8 FASTCALL mmc5_PPURead(uint32 A) { if(A<0x2000) { if(ppuphase == PPUPHASE_BG) return *MMC5BGVRAMADR(A); else return MMC5SPRVPage[(A)>>10][(A)]; } else { return vnapage[(A>>10)&0x3][A&0x3FF]; } } // ELROM seems to have 8KB of RAM // ETROM seems to have 16KB of WRAM // EWROM seems to have 32KB of WRAM cartdata MMC5CartList[]= { {0x9c18762b,2}, /* L'Empereur */ {0x26533405,2}, {0x6396b988,2}, {0xaca15643,2}, /* Uncharted Waters */ {0xfe3488d1,2}, /* Dai Koukai Jidai */ {0x15fe6d0f,2}, /* BKAC */ {0x39f2ce4b,2}, /* Suikoden */ {0x8ce478db,2}, /* Nobunaga's Ambition 2 */ {0xeee9a682,2}, {0xf9b4240f,2}, {0x1ced086f,2}, /* Ishin no Arashi */ {0xf540677b,4}, /* Nobunaga...Bushou Fuuun Roku */ {0x6f4e4312,4}, /* Aoki Ookami..Genchou */ {0xf011e490,4}, /* Romance of the 3 Kingdoms 2 */ {0x184c2124,4}, /* Sangokushi 2 */ {0xee8e6553,4}, }; #define MMC5_NOCARTS (sizeof(MMC5CartList)/sizeof(MMC5CartList[0])) int DetectMMC5WRAMSize(uint32 crc32) { int x; for(x=0;x8KB external WRAM present. Use UNIF if you hack the ROM image.\n"); return(MMC5CartList[x].size*8); } } //mbg 04-aug-08 - previously, this was returning 8KB //but I changed it to return 64 because unlisted carts are probably homebrews, and they should probably use 64 (why not use it all?) //ch4 10-dec-08 - then f***ng for what all this shit above? let's give em all this 64k shit! Damn // homebrew must use it's own emulators or standart features. //adelikat 20-dec-08 - reverting back to return 64, sounds like it was changed back to 8 simply on principle. FCEUX is all encompassing, and that include //rom-hacking. We want it to be the best emulator for such purposes. So unless return 64 harms compatibility with anything else, I see now reason not to have it //mbg 29-mar-09 - I should note that mmc5 is in principle capable of 64KB, even if no real carts ever supported it. //This does not in principle break any games which share this mapper, and it should be OK for homebrew. //if there are games which need 8KB instead of 64KB default then lets add them to the list return 64; } static void BuildWRAMSizeTable(void) { int x; for(x=0;x<8;x++) { switch(MMC5WRAMsize) { case 0: MMC5WRAMIndex[x]=255; break; //X,X,X,X,X,X,X,X case 1: MMC5WRAMIndex[x]=(x>3)?255:0; break; //0,0,0,0,X,X,X,X case 2: MMC5WRAMIndex[x]=(x&4)>>2; break; //0,0,0,0,1,1,1,1 case 4: MMC5WRAMIndex[x]=(x>3)?255:(x&3); break; //0,1,2,3,X,X,X,X case 8: MMC5WRAMIndex[x]=x; break; //0,1,2,3,4,5,6,7,8 //mbg 8/6/08 - i added this to support 64KB of wram //now, I have at least one example (laser invasion) which actually uses size 1 but isnt in the crc list //so, whereas before my change on 8/4/08 we would have selected size 1, now we select size 8 //this means that we could have just introduced an emulation bug, in case those games happened to //address, say, page 3. with size 1 that would resolve to [0] but in size 8 it resolves to [3]. //so, you know what to do if there are problems. } } } static void MMC5CHRA(void) { int x; switch(mmc5vsize&3) { case 0: setchr8(CHRBanksA[7]); MMC5SPRVROM_BANK8(CHRBanksA[7]); break; case 1: setchr4(0x0000,CHRBanksA[3]); setchr4(0x1000,CHRBanksA[7]); MMC5SPRVROM_BANK4(0x0000,CHRBanksA[3]); MMC5SPRVROM_BANK4(0x1000,CHRBanksA[7]); break; case 2: setchr2(0x0000,CHRBanksA[1]); setchr2(0x0800,CHRBanksA[3]); setchr2(0x1000,CHRBanksA[5]); setchr2(0x1800,CHRBanksA[7]); MMC5SPRVROM_BANK2(0x0000,CHRBanksA[1]); MMC5SPRVROM_BANK2(0x0800,CHRBanksA[3]); MMC5SPRVROM_BANK2(0x1000,CHRBanksA[5]); MMC5SPRVROM_BANK2(0x1800,CHRBanksA[7]); break; case 3: for(x=0;x<8;x++) { setchr1(x<<10,CHRBanksA[x]); MMC5SPRVROM_BANK1(x<<10,CHRBanksA[x]); } break; } } static void MMC5CHRB(void) { int x; switch(mmc5vsize&3) { case 0: setchr8(CHRBanksB[3]); MMC5BGVROM_BANK8(CHRBanksB[3]); break; case 1: setchr4(0x0000,CHRBanksB[3]); setchr4(0x1000,CHRBanksB[3]); MMC5BGVROM_BANK4(0x0000,CHRBanksB[3]); MMC5BGVROM_BANK4(0x1000,CHRBanksB[3]); break; case 2: setchr2(0x0000,CHRBanksB[1]); setchr2(0x0800,CHRBanksB[3]); setchr2(0x1000,CHRBanksB[1]); setchr2(0x1800,CHRBanksB[3]); MMC5BGVROM_BANK2(0x0000,CHRBanksB[1]); MMC5BGVROM_BANK2(0x0800,CHRBanksB[3]); MMC5BGVROM_BANK2(0x1000,CHRBanksB[1]); MMC5BGVROM_BANK2(0x1800,CHRBanksB[3]); break; case 3: for(x=0;x<8;x++) { setchr1(x<<10,CHRBanksB[x&3]); MMC5BGVROM_BANK1(x<<10,CHRBanksB[x&3]); } break; } } static void MMC5WRAM(uint32 A, uint32 V) { //printf("%02x\n",V); V=MMC5WRAMIndex[V&7]; if(V!=255) { setprg8r(0x10,A,V); MMC5MemIn[(A-0x6000)>>13]=1; } else MMC5MemIn[(A-0x6000)>>13]=0; } static void MMC5PRG(void) { int x; switch(mmc5psize&3) { case 0: MMC5ROMWrProtect[0]=MMC5ROMWrProtect[1]= MMC5ROMWrProtect[2]=MMC5ROMWrProtect[3]=1; setprg32(0x8000,((PRGBanks[1]&0x7F)>>2)); for(x=0;x<4;x++) MMC5MemIn[1+x]=1; break; case 1: if(PRGBanks[1]&0x80) { MMC5ROMWrProtect[0]=MMC5ROMWrProtect[1]=1; setprg16(0x8000,(PRGBanks[1]>>1)); MMC5MemIn[1]=MMC5MemIn[2]=1; } else { MMC5ROMWrProtect[0]=MMC5ROMWrProtect[1]=0; MMC5WRAM(0x8000,PRGBanks[1]&7&0xFE); MMC5WRAM(0xA000,(PRGBanks[1]&7&0xFE)+1); } MMC5MemIn[3]=MMC5MemIn[4]=1; MMC5ROMWrProtect[2]=MMC5ROMWrProtect[3]=1; setprg16(0xC000,(PRGBanks[3]&0x7F)>>1); break; case 2: if(PRGBanks[1]&0x80) { MMC5MemIn[1]=MMC5MemIn[2]=1; MMC5ROMWrProtect[0]=MMC5ROMWrProtect[1]=1; setprg16(0x8000,(PRGBanks[1]&0x7F)>>1); } else { MMC5ROMWrProtect[0]=MMC5ROMWrProtect[1]=0; MMC5WRAM(0x8000,PRGBanks[1]&7&0xFE); MMC5WRAM(0xA000,(PRGBanks[1]&7&0xFE)+1); } if(PRGBanks[2]&0x80) { MMC5ROMWrProtect[2]=1; MMC5MemIn[3]=1; setprg8(0xC000,PRGBanks[2]&0x7F); } else { MMC5ROMWrProtect[2]=0; MMC5WRAM(0xC000,PRGBanks[2]&7); } MMC5MemIn[4]=1; MMC5ROMWrProtect[3]=1; setprg8(0xE000,PRGBanks[3]&0x7F); break; case 3: for(x=0;x<3;x++) if(PRGBanks[x]&0x80) { MMC5ROMWrProtect[x]=1; setprg8(0x8000+(x<<13),PRGBanks[x]&0x7F); MMC5MemIn[1+x]=1; } else { MMC5ROMWrProtect[x]=0; MMC5WRAM(0x8000+(x<<13),PRGBanks[x]&7); } MMC5MemIn[4]=1; MMC5ROMWrProtect[3]=1; setprg8(0xE000,PRGBanks[3]&0x7F); break; } } static DECLFW(Mapper5_write) { if(A>=0x5120&&A<=0x5127) { mmc5ABMode = 0; CHRBanksA[A&7]=V | ((MMC50x5130&0x3)<<8); //if we had a test case for this then we could test this, but it hasnt been verified //CHRBanksA[A&7]=V; MMC5CHRA(); } else switch(A) { case 0x5105: { int x; for(x=0;x<4;x++) { switch((V>>(x<<1))&3) { case 0: PPUNTARAM|=1<>3)&0x1F;break; case 0x5202: MMC5HackSPPage=V&0x3F;break; case 0x5203: X6502_IRQEnd(FCEU_IQEXT);IRQScanline=V;break; case 0x5204: X6502_IRQEnd(FCEU_IQEXT);IRQEnable=V&0x80;break; case 0x5205: mul[0]=V;break; case 0x5206: mul[1]=V;break; } } static DECLFR(MMC5_ReadROMRAM) { if(MMC5MemIn[(A-0x6000)>>13]) return Page[A>>11][A]; else return X.DB; } static DECLFW(MMC5_WriteROMRAM) { if(A>=0x8000) if(MMC5ROMWrProtect[(A-0x8000)>>13]) return; if(MMC5MemIn[(A-0x6000)>>13]) if(((WRAMMaskEnable[0]&3)|((WRAMMaskEnable[1]&3)<<2)) == 6) Page[A>>11][A]=V; } static DECLFW(MMC5_ExRAMWr) { if(MMC5HackCHRMode!=3) ExRAM[A&0x3ff]=V; } static DECLFR(MMC5_ExRAMRd) { /* Not sure if this is correct, so I'll comment it out for now. */ //if(MMC5HackCHRMode>=2) return ExRAM[A&0x3ff]; //else // return(X.DB); } static DECLFR(MMC5_read) { switch(A) { case 0x5204: X6502_IRQEnd(FCEU_IQEXT); { uint8 x; x=MMC5IRQR; if(!fceuindbg) MMC5IRQR&=0x40; return x; } case 0x5205: return (mul[0]*mul[1]); case 0x5206: return ((mul[0]*mul[1])>>8); } return(X.DB); } void MMC5Synco(void) { int x; MMC5PRG(); for(x=0;x<4;x++) { switch((NTAMirroring>>(x<<1))&3) { case 0:PPUNTARAM|=1<>4]+=MMC5Sound.raw<<1; } static void Do5PCMHQ() { uint32 V; //mbg merge 7/17/06 made uint32 if(!(MMC5Sound.rawcontrol&0x40) && MMC5Sound.raw) for(V=MMC5Sound.BC[2];V>2); MMC5Sound.env[A>>2]=V; break; case 0x2: case 0x6: if(sfun) sfun(A>>2); MMC5Sound.wl[A>>2]&=~0x00FF; MMC5Sound.wl[A>>2]|=V&0xFF; break; case 0x3: case 0x7://printf("%04x:$%02x\n",A,V>>3); MMC5Sound.wl[A>>2]&=~0x0700; MMC5Sound.wl[A>>2]|=(V&0x07)<<8; MMC5Sound.running|=1<<(A>>2); break; case 0x15:if(sfun) { sfun(0); sfun(1); } MMC5Sound.running&=V; MMC5Sound.enable=V; //printf("%02x\n",V); break; } } static void Do5SQ(int P) { static int tal[4]={1,2,4,6}; int32 V,amp,rthresh,wl; int32 start,end; start=MMC5Sound.BC[P]; end=(SOUNDTS<<16)/soundtsinc; if(end<=start) return; MMC5Sound.BC[P]=end; wl=MMC5Sound.wl[P]+1; amp=(MMC5Sound.env[P]&0xF)<<4; rthresh=tal[(MMC5Sound.env[P]&0xC0)>>6]; if(wl>=8 && (MMC5Sound.running&(P+1))) { int dc,vc; wl<<=18; dc=MMC5Sound.dcount[P]; vc=MMC5Sound.vcount[P]; for(V=start;V>4]+=amp; vc-=nesincsize; while(vc<=0) { vc+=wl; dc=(dc+1)&7; } } MMC5Sound.dcount[P]=dc; MMC5Sound.vcount[P]=vc; } } static void Do5SQHQ(int P) { static int tal[4]={1,2,4,6}; uint32 V; //mbg merge 7/17/06 made uint32 int32 amp,rthresh,wl; wl=MMC5Sound.wl[P]+1; amp=((MMC5Sound.env[P]&0xF)<<8); rthresh=tal[(MMC5Sound.env[P]&0xC0)>>6]; if(wl>=8 && (MMC5Sound.running&(P+1))) { int dc,vc; wl<<=1; dc=MMC5Sound.dcount[P]; vc=MMC5Sound.vcount[P]; for(V=MMC5Sound.BC[P];V=1) { sfun=Do5SQHQ; psfun=Do5PCMHQ; } else { sfun=Do5SQ; psfun=Do5PCM; } } else { sfun=0; psfun=0; } memset(MMC5Sound.BC,0,sizeof(MMC5Sound.BC)); memset(MMC5Sound.vcount,0,sizeof(MMC5Sound.vcount)); GameExpSound.HiSync=MMC5HiSync; } void NSFMMC5_Init(void) { memset(&MMC5Sound,0,sizeof(MMC5Sound)); mul[0]=mul[1]=0; ExRAM=(uint8*)FCEU_gmalloc(1024); Mapper5_ESI(); SetWriteHandler(0x5c00,0x5fef,MMC5_ExRAMWr); SetReadHandler(0x5c00,0x5fef,MMC5_ExRAMRd); MMC5HackCHRMode=2; SetWriteHandler(0x5000,0x5015,Mapper5_SW); SetWriteHandler(0x5205,0x5206,Mapper5_write); SetReadHandler(0x5205,0x5206,MMC5_read); } void NSFMMC5_Close(void) { FCEU_gfree(ExRAM); ExRAM=0; } static void GenMMC5Reset(void) { int x; for(x=0;x<4;x++) PRGBanks[x]=~0; for(x=0;x<8;x++) CHRBanksA[x]=~0; for(x=0;x<4;x++) CHRBanksB[x]=~0; WRAMMaskEnable[0]=WRAMMaskEnable[1]=~0; mmc5psize=mmc5vsize=3; CHRMode=0; NTAMirroring=NTFill=ATFill=0xFF; MMC5Synco(); SetWriteHandler(0x4020,0x5bff,Mapper5_write); SetReadHandler(0x4020,0x5bff,MMC5_read); SetWriteHandler(0x5c00,0x5fff,MMC5_ExRAMWr); SetReadHandler(0x5c00,0x5fff,MMC5_ExRAMRd); SetWriteHandler(0x6000,0xFFFF,MMC5_WriteROMRAM); SetReadHandler(0x6000,0xFFFF,MMC5_ReadROMRAM); SetWriteHandler(0x5000,0x5015,Mapper5_SW); SetWriteHandler(0x5205,0x5206,Mapper5_write); SetReadHandler(0x5205,0x5206,MMC5_read); //GameHBIRQHook=MMC5_hb; FCEU_CheatAddRAM(8,0x6000,WRAM); FCEU_CheatAddRAM(1,0x5c00,ExRAM); } static SFORMAT MMC5_StateRegs[]={ { PRGBanks, 4, "PRGB"}, { CHRBanksA, 16, "CHRA"}, { CHRBanksB, 8, "CHRB"}, { &WRAMPage, 1, "WRMP"}, { WRAMMaskEnable, 2, "WRME"}, { &mmc5ABMode, 1, "ABMD"}, { &IRQScanline, 1, "IRQS"}, { &IRQEnable, 1, "IRQE"}, { &CHRMode, 1, "CHRM"}, { &NTAMirroring, 1, "NTAM"}, { &NTFill, 1, "NTFL"}, { &ATFill, 1, "ATFL"}, { &MMC5Sound.wl[0], 2|FCEUSTATE_RLSB, "SDW0"}, { &MMC5Sound.wl[1], 2|FCEUSTATE_RLSB, "SDW1"}, { MMC5Sound.env, 2, "SDEV"}, { &MMC5Sound.enable, 1, "SDEN"}, { &MMC5Sound.running, 1, "SDRU"}, { &MMC5Sound.raw, 1, "SDRW"}, { &MMC5Sound.rawcontrol, 1, "SDRC"}, {0} }; static void GenMMC5_Init(CartInfo *info, int wsize, int battery) { if(wsize) { WRAM=(uint8*)FCEU_gmalloc(wsize*1024); SetupCartPRGMapping(0x10,WRAM,wsize*1024,1); AddExState(WRAM, wsize*1024, 0, "WRAM"); } MMC5fill=(uint8*)FCEU_gmalloc(1024); ExRAM=(uint8*)FCEU_gmalloc(1024); AddExState(MMC5_StateRegs, ~0, 0, 0); AddExState(WRAM, wsize*1024, 0, "WRAM"); AddExState(ExRAM, 1024, 0, "ERAM"); AddExState(&MMC5HackSPMode, 1, 0, "SPLM"); AddExState(&MMC5HackSPScroll, 1, 0, "SPLS"); AddExState(&MMC5HackSPPage, 1, 0, "SPLP"); AddExState(&MMC50x5130, 1, 0, "5130"); MMC5WRAMsize=wsize/8; BuildWRAMSizeTable(); GameStateRestore=MMC5_StateRestore; info->Power=GenMMC5Reset; if(battery) { info->SaveGame[0]=WRAM; if(wsize<=16) info->SaveGameLen[0]=8192; else info->SaveGameLen[0]=32768; } MMC5HackVROMMask=CHRmask4[0]; MMC5HackExNTARAMPtr=ExRAM; MMC5Hack=1; MMC5HackVROMPTR=CHRptr[0]; MMC5HackCHRMode=0; MMC5HackSPMode=MMC5HackSPScroll=MMC5HackSPPage=0; Mapper5_ESI(); FFCEUX_PPURead = mmc5_PPURead; FFCEUX_PPUWrite = mmc5_PPUWrite; } void Mapper5_Init(CartInfo *info) { GenMMC5_Init(info, DetectMMC5WRAMSize(info->CRC32), info->battery); } // ELROM seems to have 0KB of WRAM // EKROM seems to have 8KB of WRAM // ETROM seems to have 16KB of WRAM // EWROM seems to have 32KB of WRAM // ETROM and EWROM are battery-backed, EKROM isn't. void ETROM_Init(CartInfo *info) { GenMMC5_Init(info, 16,info->battery); } void ELROM_Init(CartInfo *info) { GenMMC5_Init(info,0,0); } void EWROM_Init(CartInfo *info) { GenMMC5_Init(info,32,info->battery); } void EKROM_Init(CartInfo *info) { GenMMC5_Init(info,8,info->battery); }