I primarilly referenced yoshi's doc and the nesdev wiki. The rest was just fiddling with things trying to figure out how the game actually uses the mapper.
One key point that the docs just brush upon, but don't really stress is that punch out *requires* that you to actually render the off-screen 33rd tile. It uses that tile to trigger the latch and switch VRAM banks.
Other than that. While your rendering the scan-line, every time it accesses < 0x2000 name tables, just do your latch calculations to possibly switch banks.
Example code:
Code: Select all
Nezulator.Mappers[9].prototype.reset = function() {
Nezulator.Mappers[0].prototype.reset.apply(this);
this.latch_a = 0xFE;
this.latch_b = 0xFE;
this.latchFD0 = 0;
this.latchFE0 = 4;
this.latchFD1 = 0;
this.latchFE1 = 0;
this.numTiles = 33;
};
Nezulator.Mappers[9].prototype.write = function(address, value) {
// Writes to addresses other than MMC registers are handled by NoMapper.
if (address < 0x8000) {
Nezulator.Mappers[0].prototype.write.apply(this, arguments);
return;
}
switch(address >> 12) {
case 0xA:
this.load8kRomBank(value, 0x8000);
break;
case 0xB:
this.latchFD0 = value;
if(this.latch_a == 0xFD) {
this.loadVromBank(this.latchFD0, 0x0000);
}
break;
case 0xC:
this.latchFE0 = value;
if(this.latch_a == 0xFE) {
this.loadVromBank(this.latchFE0, 0x0000);
}
break;
case 0xD:
this.latchFD1 = value;
if(this.latch_b == 0xFD) {
this.loadVromBank(this.latchFD1, 0x1000);
}
break;
case 0xE:
this.latchFE1 = value;
if(this.latch_b == 0xFE) {
this.loadVromBank(this.latchFE1, 0x1000);
}
break;
case 0xF:
if(value & 0x1) {
this.nes.ppu.setMirroring(this.nes.rom.HORIZONTAL_MIRRORING);
} else {
this.nes.ppu.setMirroring(this.nes.rom.VERTICAL_MIRRORING);
}
break;
default:
break;
}
};
Nezulator.Mappers[9].prototype.loadROM = function(rom) {
if (!this.nes.rom.valid) {
alert("MMC2: Invalid ROM! Unable to load.");
return;
}
// Load PRGUROM:
// Swappable
this.load8kRomBank(0, 0x8000);
// Hard-wired
this.load8kRomBank((this.nes.rom.romCount * 2) - 3, 0xA000);
this.load8kRomBank((this.nes.rom.romCount * 2) - 2, 0xC000);
this.load8kRomBank((this.nes.rom.romCount * 2) - 1, 0xE000);
// Load CHR-ROM:
this.loadVromBank(4, 0x0000);
this.loadVromBank(0, 0x1000);
// Do Reset-Interrupt:
this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
};
Nezulator.Mappers[9].prototype.latchAccess = function(address) {
address &= 0x1FF0;
if(address === 0x0FD0 && this.latch_a != 0xFD) {
this.latch_a = 0xFD;
this.loadVromBank(this.latchFD0, 0x0000);
} else if(address == 0x0FE0 && this.latch_a != 0xFE) {
this.latch_a = 0xFE;
this.loadVromBank(this.latchFE0, 0x0000);
} else if(address == 0x1FD0 && this.latch_b != 0xFD) {
this.latch_b = 0xFD;
this.loadVromBank(this.latchFD1, 0x1000);
} else if(address == 0x1FE0 && this.latch_b != 0xFE) {
this.latch_b = 0xFE;
this.loadVromBank(this.latchFE1, 0x1000);
}
};