2022-11-15 10:07:41 +01:00

250 lines
6.0 KiB
JavaScript

mappers[4] = function(nes, rom, header) {
this.name = "MMC3";
this.version = 1;
this.nes = nes;
this.rom = rom;
this.h = header;
this.chrRam = new Uint8Array(0x2000);
this.prgRam = new Uint8Array(0x2000);
this.ppuRam = new Uint8Array(0x800);
this.bankRegs = new Uint8Array(8);
this.reset = function(hard) {
if(hard) {
// clear chr ram
for(let i = 0; i < this.chrRam.length; i++) {
this.chrRam[i] = 0;
}
// clear prg ram, only if not battery backed
if(!this.h.battery) {
for(let i = 0; i < this.prgRam.length; i++) {
this.prgRam[i] = 0;
}
}
// clear ppu ram
for(let i = 0; i < this.ppuRam.length; i++) {
this.ppuRam[i] = 0;
}
}
for(let i = 0; i < this.bankRegs.length; i++) {
this.bankRegs[i] = 0;
}
this.mirroring = 0;
this.prgMode = 1;
this.chrMode = 1;
this.regSelect = 0;
this.reloadIrq = false;
this.irqLatch = 0;
this.irqEnabled = false;
this.irqCounter = 0;
this.lastRead = 0;
}
this.reset(true);
this.saveVars = [
"name", "chrRam", "prgRam", "ppuRam", "bankRegs", "mirroring", "prgMode",
"chrMode", "regSelect", "reloadIrq", "irqLatch", "irqEnabled", "irqCounter",
"lastRead"
];
this.getBattery = function() {
return Array.prototype.slice.call(this.prgRam);
}
this.setBattery = function(data) {
if(data.length !== 0x2000) {
return false;
}
this.prgRam = new Uint8Array(data);
return true;
}
this.getRomAdr = function(adr) {
let final = 0;
if(this.prgMode === 1) {
if(adr < 0xa000) {
final = ((this.h.banks * 2) - 2) * 0x2000 + (adr & 0x1fff);
} else if(adr < 0xc000) {
final = this.bankRegs[7] * 0x2000 + (adr & 0x1fff);
} else if(adr < 0xe000) {
final = this.bankRegs[6] * 0x2000 + (adr & 0x1fff);
} else {
final = ((this.h.banks * 2) - 1) * 0x2000 + (adr & 0x1fff);
}
} else {
if(adr < 0xa000) {
final = this.bankRegs[6] * 0x2000 + (adr & 0x1fff);
} else if(adr < 0xc000) {
final = this.bankRegs[7] * 0x2000 + (adr & 0x1fff);
} else if(adr < 0xe000) {
final = ((this.h.banks * 2) - 2) * 0x2000 + (adr & 0x1fff);
} else {
final = ((this.h.banks * 2) - 1) * 0x2000 + (adr & 0x1fff);
}
}
return final & this.h.prgAnd;
}
this.getMirroringAdr = function(adr) {
if(this.mirroring === 0) {
// vertical
return adr & 0x7ff;
} else {
// horizontal
return (adr & 0x3ff) | ((adr & 0x800) >> 1);
}
}
this.getChrAdr = function(adr) {
if(this.chrMode === 1) {
adr ^= 0x1000;
}
let final = 0;
if(adr < 0x800) {
final = (this.bankRegs[0] >> 1) * 0x800 + (adr & 0x7ff);
} else if(adr < 0x1000) {
final = (this.bankRegs[1] >> 1) * 0x800 + (adr & 0x7ff);
} else if(adr < 0x1400) {
final = this.bankRegs[2] * 0x400 + (adr & 0x3ff);
} else if(adr < 0x1800) {
final = this.bankRegs[3] * 0x400 + (adr & 0x3ff);
} else if(adr < 0x1c00) {
final = this.bankRegs[4] * 0x400 + (adr & 0x3ff);
} else {
final = this.bankRegs[5] * 0x400 + (adr & 0x3ff);
}
return final & this.h.chrAnd;
}
this.clockIrq = function() {
if(this.irqCounter === 0 || this.reloadIrq) {
this.irqCounter = this.irqLatch;
this.reloadIrq = false;
} else {
this.irqCounter--;
this.irqCounter &= 0xff;
}
if(this.irqCounter === 0 && this.irqEnabled) {
this.nes.mapperIrqWanted = true;
}
}
this.peak = function(adr) {
return this.read(adr);
}
this.read = function(adr) {
if(adr < 0x6000) {
return 0; // not readable
}
if(adr < 0x8000) {
return this.prgRam[adr & 0x1fff];
}
return this.rom[this.h.base + this.getRomAdr(adr)];
}
this.write = function(adr, value) {
if(adr < 0x6000) {
return; // no mapper registers
}
if(adr < 0x8000) {
this.prgRam[adr & 0x1fff] = value;
return;
}
switch(adr & 0x6001) {
case 0x0000: {
this.regSelect = value & 0x7;
this.prgMode = (value & 0x40) >> 6;
this.chrMode = (value & 0x80) >> 7;
break;
}
case 0x0001: {
this.bankRegs[this.regSelect] = value;
break;
}
case 0x2000: {
this.mirroring = value & 0x1;
break;
}
case 0x2001: {
// ram protection not implemented
break;
}
case 0x4000: {
this.irqLatch = value;
break;
}
case 0x4001: {
this.reloadIrq = true;
break;
}
case 0x6000: {
this.irqEnabled = false;
this.nes.mapperIrqWanted = false;
break;
}
case 0x6001: {
this.irqEnabled = true;
break;
}
}
}
this.ppuPeak = function(adr) {
if(adr < 0x2000) {
if(this.h.chrBanks === 0) {
return this.chrRam[this.getChrAdr(adr)];
} else {
return this.rom[this.h.chrBase + this.getChrAdr(adr)];
}
} else {
return this.ppuRam[this.getMirroringAdr(adr)];
}
}
// ppu-read
this.ppuRead = function(adr) {
if(adr < 0x2000) {
// A12 should be ignored for 8 cycles after going high
// see https://forums.nesdev.com/viewtopic.php?f=3&t=17290#p217456
// ignore for nametables, for now
if((this.lastRead & 0x1000) === 0 && (adr & 0x1000) > 0) {
// A12 went high, clock irq
this.clockIrq();
}
this.lastRead = adr;
if(this.h.chrBanks === 0) {
return this.chrRam[this.getChrAdr(adr)];
} else {
return this.rom[this.h.chrBase + this.getChrAdr(adr)];
}
} else {
return this.ppuRam[this.getMirroringAdr(adr)];
}
}
// ppu-write
this.ppuWrite = function(adr, value) {
if(adr < 0x2000) {
if(this.h.chrBanks === 0) {
this.chrRam[this.getChrAdr(adr)] = value;
return;
} else {
// not writable
return;
}
} else {
return this.ppuRam[this.getMirroringAdr(adr)] = value;
}
}
}