/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.mappers;
import com.grapeshot.halfnes.*;
import com.grapeshot.halfnes.PPU;
import java.util.Arrays;
import java.util.prefs.Preferences;
import java.util.zip.CRC32;
public abstract class Mapper {
protected ROMLoader loader;
protected int mappertype, submapper, prgsize, prgoff, chroff, chrsize;
public CPU cpu;
public CPURAM cpuram;
public PPU ppu;
protected int[] prg, chr, chr_map, prg_map, prgram = new int[8192];
protected MirrorType scrolltype;
protected boolean haschrram = false, hasprgram = true, savesram = false;
// PPU nametables
protected final int[] pput0 = new int[0x400], pput1 = new int[0x400],
pput2 = new int[0x400], pput3 = new int[0x400];
//99% of games only use 2 of these, but we have to create 4 and use ptrs to them
//for those with extra RAM for 4 screen mirror
protected int[] nt0, nt1, nt2, nt3;
//and these are pointers to the nametables, so for singlescreen when we switch
//and then switch back the data in the other singlescreen NT isn't gone.
long crc;
TVType region;
Preferences prefs = PrefsSingleton.get();
public boolean supportsSaves() {
return savesram;
}
public void destroy() {
cpu = null;
cpuram = null;
ppu = null;
}
public static enum MirrorType {
H_MIRROR, V_MIRROR, SS_MIRROR0, SS_MIRROR1, FOUR_SCREEN_MIRROR
};
public static enum TVType {
NTSC,
PAL,
DENDY;
}
public static long crc32(int[] array) {
CRC32 c = new CRC32();
for (int i : array) {
c.update(i);
}
return c.getValue();
}
public void loadrom() throws BadMapperException {
loader.parseHeader();
prgsize = loader.prgsize;
mappertype = loader.mappertype;
prgoff = loader.prgoff;
chroff = loader.chroff;
chrsize = loader.chrsize;
scrolltype = loader.scrolltype;
savesram = loader.savesram;
prg = loader.load(prgsize, prgoff);
region = loader.tvtype;
submapper = loader.submapper;
crc = crc32(prg);
//System.err.println(utils.hex(crc));
//crc "database" for certain impossible-to-recognize games
if ((crc == 0x41243492L) //low g man (u)
|| (crc == 0x98CCD385L)//low g man (e)
) {
hasprgram = false;
}
chr = loader.load(chrsize, chroff);
if (chrsize == 0) {//chr ram
haschrram = true;
chrsize = 8192;
chr = new int[8192];
}
prg_map = new int[32];
for (int i = 0; i < 32; ++i) {
prg_map[i] = (1024 * i) & (prgsize - 1);
}
chr_map = new int[8];
for (int i = 0; i < 8; ++i) {
chr_map[i] = (1024 * i) & (chrsize - 1);
}
cpuram = new CPURAM(this);
cpu = new CPU(cpuram);
ppu = new PPU(this);
Arrays.fill(pput0, 0xa0);
Arrays.fill(pput1, 0xb0);
Arrays.fill(pput2, 0xc0);
Arrays.fill(pput3, 0xd0);
setmirroring(scrolltype);
}
public void reset() {
//this is empty so that mappers w/o some specific instructions
//on soft reset need not implement this
}
//write into the cartridge's address space
public void cartWrite(final int addr, final int data) {
//default no-mapper operation just writes if in PRG RAM range
if (addr >= 0x6000 && addr < 0x8000) {
prgram[addr & 0x1fff] = data;
}
}
public int cartRead(final int addr) {
// by default has wram at 0x6000 and cartridge at 0x8000-0xfff
// but some mappers have different so override for those
if (addr >= 0x8000) {
return prg[prg_map[((addr & 0x7fff)) >> 10] + (addr & 1023)];
} else if (addr >= 0x6000 && hasprgram) {
return prgram[addr & 0x1fff];
}
return addr >> 8; //open bus
}
public int ppuRead(int addr) {
if (addr < 0x2000) {
return chr[chr_map[addr >> 10] + (addr & 1023)];
} else {
switch (addr & 0xc00) {
case 0:
return nt0[addr & 0x3ff];
case 0x400:
return nt1[addr & 0x3ff];
case 0x800:
return nt2[addr & 0x3ff];
case 0xc00:
default:
if (addr >= 0x3f00) {
addr &= 0x1f;
if (addr >= 0x10 && ((addr & 3) == 0)) {
addr -= 0x10;
}
return ppu.pal[addr];
} else {
return nt3[addr & 0x3ff];
}
}
}
}
public void ppuWrite(int addr, final int data) {
addr &= 0x3fff;
if (addr < 0x2000) {
if (haschrram) {
// Shame on you, Milon's Secret Castle. What possible
// reason could you have to write to your own chr rom?
chr[chr_map[addr >> 10] + (addr & 1023)] = data;
// anyway, only allowing writes when there's actual ram here.
}
} else {
switch (addr & 0xc00) {
case 0x0:
nt0[addr & 0x3ff] = data;
break;
case 0x400:
nt1[addr & 0x3ff] = data;
break;
case 0x800:
nt2[addr & 0x3ff] = data;
break;
case 0xc00:
if (addr >= 0x3f00 && addr <= 0x3fff) {
addr &= 0x1f;
//System.err.println("wrote "+utils.hex(data)+" to palette index " + utils.hex(addr));
if (addr >= 0x10 && ((addr & 3) == 0)) { //0x10,0x14,0x18 etc are mirrors of 0x0, 0x4,0x8 etc
addr -= 0x10;
}
ppu.pal[addr] = (data & 0x3f);
} else {
nt3[addr & 0x3ff] = data;
}
break;
default:
System.err.println("where?");
}
}
}
public void notifyscanline(final int scanline) {
//this is empty so that mappers w/o a scanline counter need not implement
}
public void cpucycle(int cycles) {
//do it right for once
}
public static Mapper getCorrectMapper(final ROMLoader l) throws BadMapperException {
int type = l.mappertype;
boolean haschr = (l.chrsize == 0);
switch (type) {
case -1:
return new NSFMapper();
case 0:
return new NromMapper();
case 1:
return new MMC1Mapper();
case 2:
return new UnromMapper();
case 3:
return new CnromMapper();
case 4:
return new MMC3Mapper();
case 5:
return new MMC5Mapper();
case 7:
return new AnromMapper();
case 9:
return new MMC2Mapper();
case 10:
return new MMC4Mapper();
case 11:
return new ColorDreamsMapper();
case 15:
case 169:
return new Mapper15();
case 19:
return new NamcoMapper();
case 21:
case 23:
case 25:
//VRC4 has three different mapper numbers for six different address line layouts
//some of which really should be VRC2
//but they're all handled in the same file
//there's a proposal for submapper #s in iNES 2.0
return new VRC4Mapper(type);
case 22:
return new VRC2Mapper();
case 24:
case 26:
return new VRC6Mapper(type);
case 31:
return new Mapper31();
case 33:
return new Mapper33();
case 34:
if (haschr) {
return new BnromMapper();
} else {
return new NINA_001_Mapper();
}
case 36:
return new Mapper36();
case 38:
return new CrimeBustersMapper();
case 41:
return new CaltronMapper();
case 47:
return new Mapper47();
case 48:
return new Mapper48();
case 58:
return new Mapper58();
case 60:
return new Mapper60();
case 61:
return new Mapper61();
case 62:
return new Mapper62();
case 64:
return new TengenRamboMapper();
case 65:
return new IremH3001Mapper();
case 66:
return new GnromMapper();
case 67:
return new Sunsoft03Mapper();
case 68:
return new AfterburnerMapper();
case 69:
return new FME7Mapper();
case 70:
return new Mapper70();
case 71:
return new CodemastersMapper();
case 72:
return new Mapper72();
case 73:
return new VRC3Mapper();
case 75:
return new VRC1Mapper();
case 76:
return new Mapper76();
case 78:
return new Mapper78();
case 79:
case 113:
return new NINA_003_006_Mapper(type);
case 85:
return new VRC7Mapper();
case 86:
return new Mapper86();
case 87:
return new Mapper87();
case 88:
case 154:
return new Namcot34x3Mapper(type);
case 89:
case 93:
return new Sunsoft02Mapper(type);
case 92:
return new Mapper92();
case 94:
return new Mapper94();
case 97:
return new Mapper97();
case 107:
return new Mapper107();
case 112:
return new Mapper112();
case 119:
return new Mapper119();
case 140:
return new Mapper140();
case 152:
return new Mapper152();
case 180:
return new CrazyClimberMapper();
case 182:
return new Mapper182();
case 184:
return new Sunsoft01Mapper();
case 185:
return new Mapper185();
case 200:
return new Mapper200();
case 201:
return new Mapper201();
case 203:
return new Mapper203();
case 206:
return new MIMICMapper();
case 212:
return new Mapper212();
case 213:
return new Mapper213();
case 214:
return new Mapper214();
case 225:
return new Mapper225();
case 226:
return new Mapper226();
case 228:
return new Action52Mapper();
case 229:
return new Mapper229();
case 231:
return new Mapper231();
case 240:
return new Mapper240();
case 241:
return new Mapper241();
case 242:
return new Mapper242();
case 244:
return new Mapper244();
case 246:
return new Mapper246();
case 255:
return new Mapper255();
default:
System.err.println("unsupported mapper # " + type);
throw new BadMapperException("Unsupported mapper: " + type);
}
}
public String getrominfo() {
return ("ROM INFO: \n"
+ "Filename: " + loader.name + "\n"
+ "Mapper: " + mappertype + "\n"
+ "PRG Size: " + prgsize / 1024 + " K\n"
+ "CHR Size: " + (haschrram ? 0 : chrsize / 1024) + " K\n"
+ "Mirroring: " + scrolltype.toString() + "\n"
+ "Battery Save: " + ((savesram) ? "Yes" : "No")) + "\n"
+ "CRC: " + utils.hex(this.crc);
}
public boolean hasSRAM() {
return savesram;
}
public void setLoader(final ROMLoader l) {
loader = l;
}
public CPURAM getCPURAM() {
return cpuram;
}
public void checkA12(int addr) {
//needed for mmc3 irq counter
}
public void setPRGRAM(final int[] newprgram) {
prgram = newprgram.clone();
}
public int[] getPRGRam() {
return prgram.clone();
}
public final void setmirroring(final Mapper.MirrorType type) {
switch (type) {
case H_MIRROR:
nt0 = pput0;
nt1 = pput0;
nt2 = pput1;
nt3 = pput1;
break;
case V_MIRROR:
nt0 = pput0;
nt1 = pput1;
nt2 = pput0;
nt3 = pput1;
break;
case SS_MIRROR0:
nt0 = pput0;
nt1 = pput0;
nt2 = pput0;
nt3 = pput0;
break;
case SS_MIRROR1:
nt0 = pput1;
nt1 = pput1;
nt2 = pput1;
nt3 = pput1;
break;
case FOUR_SCREEN_MIRROR:
default:
nt0 = pput0;
nt1 = pput1;
nt2 = pput2;
nt3 = pput3;
break;
}
}
public TVType getTVType() {
int prefsregion = prefs.getInt("region", 0);
switch (prefsregion) {
case 0:
default://auto detect
return region;
case 1: //ntsc
return TVType.NTSC;
case 2: //pal
return TVType.PAL;
case 3: //dendy
return TVType.DENDY;
}
}
public void init() {
} //needed for NSF initialization
}