/* JSNES, based on Jamie Sanders' vNES Copyright (C) 2010 Ben Firshman 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 3 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, see . */ JSNES.Mappers = {}; JSNES.Mappers[0] = function(nes) { this.nes = nes; }; JSNES.Mappers[0].prototype = { reset: function() { this.joy1StrobeState = 0; this.joy2StrobeState = 0; this.joypadLastWrite = 0; this.mousePressed = false; this.mouseX = null; this.mouseY = null; }, write: function(address, value) { if (address < 0x2000) { // Mirroring of RAM: this.nes.cpu.mem[address & 0x7FF] = value; } else if (address > 0x4017) { this.nes.cpu.mem[address] = value; if (address >= 0x6000 && address < 0x8000) { // Write to SaveRAM. Store in file: // TODO: not yet //if(this.nes.rom!=null) // this.nes.rom.writeBatteryRam(address,value); } } else if (address > 0x2007 && address < 0x4000) { this.regWrite(0x2000 + (address & 0x7), value); } else { this.regWrite(address, value); } }, writelow: function(address, value) { if (address < 0x2000) { // Mirroring of RAM: this.nes.cpu.mem[address & 0x7FF] = value; } else if (address > 0x4017) { this.nes.cpu.mem[address] = value; } else if (address > 0x2007 && address < 0x4000) { this.regWrite(0x2000 + (address & 0x7), value); } else { this.regWrite(address, value); } }, load: function(address) { // Wrap around: address &= 0xFFFF; // Check address range: if (address > 0x4017) { // ROM: return this.nes.cpu.mem[address]; } else if (address >= 0x2000) { // I/O Ports. return this.regLoad(address); } else { // RAM (mirrored) return this.nes.cpu.mem[address & 0x7FF]; } }, regLoad: function(address) { switch (address >> 12) { // use fourth nibble (0xF000) case 0: break; case 1: break; case 2: // Fall through to case 3 case 3: // PPU Registers switch (address & 0x7) { case 0x0: // 0x2000: // PPU Control Register 1. // (the value is stored both // in main memory and in the // PPU as flags): // (not in the real NES) return this.nes.cpu.mem[0x2000]; case 0x1: // 0x2001: // PPU Control Register 2. // (the value is stored both // in main memory and in the // PPU as flags): // (not in the real NES) return this.nes.cpu.mem[0x2001]; case 0x2: // 0x2002: // PPU Status Register. // The value is stored in // main memory in addition // to as flags in the PPU. // (not in the real NES) return this.nes.ppu.readStatusRegister(); case 0x3: return 0; case 0x4: // 0x2004: // Sprite Memory read. return this.nes.ppu.sramLoad(); case 0x5: return 0; case 0x6: return 0; case 0x7: // 0x2007: // VRAM read: return this.nes.ppu.vramLoad(); } break; case 4: // Sound+Joypad registers switch (address - 0x4015) { case 0: // 0x4015: // Sound channel enable, DMC Status return this.nes.papu.readReg(address); case 1: // 0x4016: // Joystick 1 + Strobe return this.joy1Read(); case 2: // 0x4017: // Joystick 2 + Strobe if (this.mousePressed) { // Check for white pixel nearby: var sx = Math.max(0, this.mouseX - 4); var ex = Math.min(256, this.mouseX + 4); var sy = Math.max(0, this.mouseY - 4); var ey = Math.min(240, this.mouseY + 4); var w = 0; for (var y=sy; y= 0x4000 && address <= 0x4017) { this.nes.papu.writeReg(address,value); } } }, joy1Read: function() { var ret; switch (this.joy1StrobeState) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: ret = this.nes.keyboard.state1[this.joy1StrobeState]; break; case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: ret = 0; break; case 19: ret = 1; break; default: ret = 0; } this.joy1StrobeState++; if (this.joy1StrobeState == 24) { this.joy1StrobeState = 0; } return ret; }, joy2Read: function() { var ret; switch (this.joy2StrobeState) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: ret = this.nes.keyboard.state2[this.joy2StrobeState]; break; case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: ret = 0; break; case 19: ret = 1; break; default: ret = 0; } this.joy2StrobeState++; if (this.joy2StrobeState == 24) { this.joy2StrobeState = 0; } return ret; }, loadROM: function() { if (!this.nes.rom.valid || this.nes.rom.romCount < 1) { alert("NoMapper: Invalid ROM! Unable to load."); return; } // Load ROM into memory: this.loadPRGROM(); // Load CHR-ROM: this.loadCHRROM(); // Load Battery RAM (if present): this.loadBatteryRam(); // Reset IRQ: //nes.getCpu().doResetInterrupt(); this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET); }, loadPRGROM: function() { if (this.nes.rom.romCount > 1) { // Load the two first banks into memory. this.loadRomBank(0, 0x8000); this.loadRomBank(1, 0xC000); } else { // Load the one bank into both memory locations: this.loadRomBank(0, 0x8000); this.loadRomBank(0, 0xC000); } }, loadCHRROM: function() { ////System.out.println("Loading CHR ROM.."); if (this.nes.rom.vromCount > 0) { if (this.nes.rom.vromCount == 1) { this.loadVromBank(0,0x0000); this.loadVromBank(0,0x1000); } else { this.loadVromBank(0,0x0000); this.loadVromBank(1,0x1000); } } else { //System.out.println("There aren't any CHR-ROM banks.."); } }, loadBatteryRam: function() { if (this.nes.rom.batteryRam) { var ram = this.nes.rom.batteryRam; if (ram !== null && ram.length == 0x2000) { // Load Battery RAM into memory: JSNES.Utils.copyArrayElements(ram, 0, this.nes.cpu.mem, 0x6000, 0x2000); } } }, loadRomBank: function(bank, address) { // Loads a ROM bank into the specified address. bank %= this.nes.rom.romCount; //var data = this.nes.rom.rom[bank]; //cpuMem.write(address,data,data.length); JSNES.Utils.copyArrayElements(this.nes.rom.rom[bank], 0, this.nes.cpu.mem, address, 16384); }, loadVromBank: function(bank, address) { if (this.nes.rom.vromCount === 0) { return; } this.nes.ppu.triggerRendering(); JSNES.Utils.copyArrayElements(this.nes.rom.vrom[bank % this.nes.rom.vromCount], 0, this.nes.ppu.vramMem, address, 4096); var vromTile = this.nes.rom.vromTile[bank % this.nes.rom.vromCount]; JSNES.Utils.copyArrayElements(vromTile, 0, this.nes.ppu.ptTile,address >> 4, 256); }, load32kRomBank: function(bank, address) { this.loadRomBank((bank*2) % this.nes.rom.romCount, address); this.loadRomBank((bank*2+1) % this.nes.rom.romCount, address+16384); }, load8kVromBank: function(bank4kStart, address) { if (this.nes.rom.vromCount === 0) { return; } this.nes.ppu.triggerRendering(); this.loadVromBank((bank4kStart) % this.nes.rom.vromCount, address); this.loadVromBank((bank4kStart + 1) % this.nes.rom.vromCount, address + 4096); }, load1kVromBank: function(bank1k, address) { if (this.nes.rom.vromCount === 0) { return; } this.nes.ppu.triggerRendering(); var bank4k = Math.floor(bank1k / 4) % this.nes.rom.vromCount; var bankoffset = (bank1k % 4) * 1024; JSNES.Utils.copyArrayElements(this.nes.rom.vrom[bank4k], 0, this.nes.ppu.vramMem, bankoffset, 1024); // Update tiles: var vromTile = this.nes.rom.vromTile[bank4k]; var baseIndex = address >> 4; for (var i = 0; i < 64; i++) { this.nes.ppu.ptTile[baseIndex+i] = vromTile[((bank1k%4) << 6) + i]; } }, load2kVromBank: function(bank2k, address) { if (this.nes.rom.vromCount === 0) { return; } this.nes.ppu.triggerRendering(); var bank4k = Math.floor(bank2k / 2) % this.nes.rom.vromCount; var bankoffset = (bank2k % 2) * 2048; JSNES.Utils.copyArrayElements(this.nes.rom.vrom[bank4k], bankoffset, this.nes.ppu.vramMem, address, 2048); // Update tiles: var vromTile = this.nes.rom.vromTile[bank4k]; var baseIndex = address >> 4; for (var i = 0; i < 128; i++) { this.nes.ppu.ptTile[baseIndex+i] = vromTile[((bank2k%2) << 7) + i]; } }, load8kRomBank: function(bank8k, address) { var bank16k = Math.floor(bank8k / 2) % this.nes.rom.romCount; var offset = (bank8k % 2) * 8192; //this.nes.cpu.mem.write(address,this.nes.rom.rom[bank16k],offset,8192); JSNES.Utils.copyArrayElements(this.nes.rom.rom[bank16k], offset, this.nes.cpu.mem, address, 8192); }, clockIrqCounter: function() { // Does nothing. This is used by the MMC3 mapper. }, latchAccess: function(address) { // Does nothing. This is used by MMC2. }, toJSON: function() { return { 'joy1StrobeState': this.joy1StrobeState, 'joy2StrobeState': this.joy2StrobeState, 'joypadLastWrite': this.joypadLastWrite }; }, fromJSON: function(s) { this.joy1StrobeState = s.joy1StrobeState; this.joy2StrobeState = s.joy2StrobeState; this.joypadLastWrite = s.joypadLastWrite; } }; JSNES.Mappers[1] = function(nes) { this.nes = nes; }; JSNES.Mappers[1].prototype = new JSNES.Mappers[0](); JSNES.Mappers[1].prototype.reset = function() { JSNES.Mappers[0].prototype.reset.apply(this); // 5-bit buffer: this.regBuffer = 0; this.regBufferCounter = 0; // Register 0: this.mirroring = 0; this.oneScreenMirroring = 0; this.prgSwitchingArea = 1; this.prgSwitchingSize = 1; this.vromSwitchingSize = 0; // Register 1: this.romSelectionReg0 = 0; // Register 2: this.romSelectionReg1 = 0; // Register 3: this.romBankSelect = 0; }; JSNES.Mappers[1].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { JSNES.Mappers[0].prototype.write.apply(this, arguments); return; } // See what should be done with the written value: if ((value & 128) !== 0) { // Reset buffering: this.regBufferCounter = 0; this.regBuffer = 0; // Reset register: if (this.getRegNumber(address) === 0) { this.prgSwitchingArea = 1; this.prgSwitchingSize = 1; } } else { // Continue buffering: //regBuffer = (regBuffer & (0xFF-(1<> 2) & 1; // PRG Switching Size: this.prgSwitchingSize = (value >> 3) & 1; // VROM Switching Size: this.vromSwitchingSize = (value >> 4) & 1; break; case 1: // ROM selection: this.romSelectionReg0 = (value >> 4) & 1; // Check whether the cart has VROM: if (this.nes.rom.vromCount > 0) { // Select VROM bank at 0x0000: if (this.vromSwitchingSize === 0) { // Swap 8kB VROM: if (this.romSelectionReg0 === 0) { this.load8kVromBank((value & 0xF), 0x0000); } else { this.load8kVromBank( Math.floor(this.nes.rom.vromCount / 2) + (value & 0xF), 0x0000 ); } } else { // Swap 4kB VROM: if (this.romSelectionReg0 === 0) { this.loadVromBank((value & 0xF), 0x0000); } else { this.loadVromBank( Math.floor(this.nes.rom.vromCount / 2) + (value & 0xF), 0x0000 ); } } } break; case 2: // ROM selection: this.romSelectionReg1 = (value >> 4) & 1; // Check whether the cart has VROM: if (this.nes.rom.vromCount > 0) { // Select VROM bank at 0x1000: if (this.vromSwitchingSize === 1) { // Swap 4kB of VROM: if (this.romSelectionReg1 === 0) { this.loadVromBank((value & 0xF), 0x1000); } else { this.loadVromBank( Math.floor(this.nes.rom.vromCount / 2) + (value & 0xF), 0x1000 ); } } } break; default: // Select ROM bank: // ------------------------- tmp = value & 0xF; var bank; var baseBank = 0; if (this.nes.rom.romCount >= 32) { // 1024 kB cart if (this.vromSwitchingSize === 0) { if (this.romSelectionReg0 === 1) { baseBank = 16; } } else { baseBank = (this.romSelectionReg0 | (this.romSelectionReg1 << 1)) << 3; } } else if (this.nes.rom.romCount >= 16) { // 512 kB cart if (this.romSelectionReg0 === 1) { baseBank = 8; } } if (this.prgSwitchingSize === 0) { // 32kB bank = baseBank + (value & 0xF); this.load32kRomBank(bank, 0x8000); } else { // 16kB bank = baseBank * 2 + (value & 0xF); if (this.prgSwitchingArea === 0) { this.loadRomBank(bank, 0xC000); } else { this.loadRomBank(bank, 0x8000); } } } }; // Returns the register number from the address written to: JSNES.Mappers[1].prototype.getRegNumber = function(address) { if (address >= 0x8000 && address <= 0x9FFF) { return 0; } else if (address >= 0xA000 && address <= 0xBFFF) { return 1; } else if (address >= 0xC000 && address <= 0xDFFF) { return 2; } else { return 3; } }; JSNES.Mappers[1].prototype.loadROM = function(rom) { if (!this.nes.rom.valid) { alert("MMC1: Invalid ROM! Unable to load."); return; } // Load PRG-ROM: this.loadRomBank(0, 0x8000); // First ROM bank.. this.loadRomBank(this.nes.rom.romCount - 1, 0xC000); // ..and last ROM bank. // Load CHR-ROM: this.loadCHRROM(); // Load Battery RAM (if present): this.loadBatteryRam(); // Do Reset-Interrupt: this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET); }; JSNES.Mappers[1].prototype.switchLowHighPrgRom = function(oldSetting) { // not yet. }; JSNES.Mappers[1].prototype.switch16to32 = function() { // not yet. }; JSNES.Mappers[1].prototype.switch32to16 = function() { // not yet. }; JSNES.Mappers[1].prototype.toJSON = function() { var s = JSNES.Mappers[0].prototype.toJSON.apply(this); s.mirroring = this.mirroring; s.oneScreenMirroring = this.oneScreenMirroring; s.prgSwitchingArea = this.prgSwitchingArea; s.prgSwitchingSize = this.prgSwitchingSize; s.vromSwitchingSize = this.vromSwitchingSize; s.romSelectionReg0 = this.romSelectionReg0; s.romSelectionReg1 = this.romSelectionReg1; s.romBankSelect = this.romBankSelect; s.regBuffer = this.regBuffer; s.regBufferCounter = this.regBufferCounter; return s; }; JSNES.Mappers[1].prototype.fromJSON = function(s) { JSNES.Mappers[0].prototype.fromJSON.apply(this, s); this.mirroring = s.mirroring; this.oneScreenMirroring = s.oneScreenMirroring; this.prgSwitchingArea = s.prgSwitchingArea; this.prgSwitchingSize = s.prgSwitchingSize; this.vromSwitchingSize = s.vromSwitchingSize; this.romSelectionReg0 = s.romSelectionReg0; this.romSelectionReg1 = s.romSelectionReg1; this.romBankSelect = s.romBankSelect; this.regBuffer = s.regBuffer; this.regBufferCounter = s.regBufferCounter; }; JSNES.Mappers[2] = function(nes) { this.nes = nes; }; JSNES.Mappers[2].prototype = new JSNES.Mappers[0](); JSNES.Mappers[2].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { JSNES.Mappers[0].prototype.write.apply(this, arguments); return; } else { // This is a ROM bank select command. // Swap in the given ROM bank at 0x8000: this.loadRomBank(value, 0x8000); } }; JSNES.Mappers[2].prototype.loadROM = function(rom) { if (!this.nes.rom.valid) { alert("UNROM: Invalid ROM! Unable to load."); return; } // Load PRG-ROM: this.loadRomBank(0, 0x8000); this.loadRomBank(this.nes.rom.romCount - 1, 0xC000); // Load CHR-ROM: this.loadCHRROM(); // Do Reset-Interrupt: this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET); }; JSNES.Mappers[4] = function(nes) { this.nes = nes; this.CMD_SEL_2_1K_VROM_0000 = 0; this.CMD_SEL_2_1K_VROM_0800 = 1; this.CMD_SEL_1K_VROM_1000 = 2; this.CMD_SEL_1K_VROM_1400 = 3; this.CMD_SEL_1K_VROM_1800 = 4; this.CMD_SEL_1K_VROM_1C00 = 5; this.CMD_SEL_ROM_PAGE1 = 6; this.CMD_SEL_ROM_PAGE2 = 7; this.command = null; this.prgAddressSelect = null; this.chrAddressSelect = null; this.pageNumber = null; this.irqCounter = null; this.irqLatchValue = null; this.irqEnable = null; this.prgAddressChanged = false; }; JSNES.Mappers[4].prototype = new JSNES.Mappers[0](); JSNES.Mappers[4].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { JSNES.Mappers[0].prototype.write.apply(this, arguments); return; } switch (address) { case 0x8000: // Command/Address Select register this.command = value & 7; var tmp = (value >> 6) & 1; if (tmp != this.prgAddressSelect) { this.prgAddressChanged = true; } this.prgAddressSelect = tmp; this.chrAddressSelect = (value >> 7) & 1; break; case 0x8001: // Page number for command this.executeCommand(this.command, value); break; case 0xA000: // Mirroring select if ((value & 1) !== 0) { this.nes.ppu.setMirroring( this.nes.rom.HORIZONTAL_MIRRORING ); } else { this.nes.ppu.setMirroring(this.nes.rom.VERTICAL_MIRRORING); } break; case 0xA001: // SaveRAM Toggle // TODO //nes.getRom().setSaveState((value&1)!=0); break; case 0xC000: // IRQ Counter register this.irqCounter = value; //nes.ppu.mapperIrqCounter = 0; break; case 0xC001: // IRQ Latch register this.irqLatchValue = value; break; case 0xE000: // IRQ Control Reg 0 (disable) //irqCounter = irqLatchValue; this.irqEnable = 0; break; case 0xE001: // IRQ Control Reg 1 (enable) this.irqEnable = 1; break; default: // Not a MMC3 register. // The game has probably crashed, // since it tries to write to ROM.. // IGNORE. } }; JSNES.Mappers[4].prototype.executeCommand = function(cmd, arg) { switch (cmd) { case this.CMD_SEL_2_1K_VROM_0000: // Select 2 1KB VROM pages at 0x0000: if (this.chrAddressSelect === 0) { this.load1kVromBank(arg, 0x0000); this.load1kVromBank(arg + 1, 0x0400); } else { this.load1kVromBank(arg, 0x1000); this.load1kVromBank(arg + 1, 0x1400); } break; case this.CMD_SEL_2_1K_VROM_0800: // Select 2 1KB VROM pages at 0x0800: if (this.chrAddressSelect === 0) { this.load1kVromBank(arg, 0x0800); this.load1kVromBank(arg + 1, 0x0C00); } else { this.load1kVromBank(arg, 0x1800); this.load1kVromBank(arg + 1, 0x1C00); } break; case this.CMD_SEL_1K_VROM_1000: // Select 1K VROM Page at 0x1000: if (this.chrAddressSelect === 0) { this.load1kVromBank(arg, 0x1000); } else { this.load1kVromBank(arg, 0x0000); } break; case this.CMD_SEL_1K_VROM_1400: // Select 1K VROM Page at 0x1400: if (this.chrAddressSelect === 0) { this.load1kVromBank(arg, 0x1400); } else { this.load1kVromBank(arg, 0x0400); } break; case this.CMD_SEL_1K_VROM_1800: // Select 1K VROM Page at 0x1800: if (this.chrAddressSelect === 0) { this.load1kVromBank(arg, 0x1800); } else { this.load1kVromBank(arg, 0x0800); } break; case this.CMD_SEL_1K_VROM_1C00: // Select 1K VROM Page at 0x1C00: if (this.chrAddressSelect === 0) { this.load1kVromBank(arg, 0x1C00); }else { this.load1kVromBank(arg, 0x0C00); } break; case this.CMD_SEL_ROM_PAGE1: if (this.prgAddressChanged) { // Load the two hardwired banks: if (this.prgAddressSelect === 0) { this.load8kRomBank( ((this.nes.rom.romCount - 1) * 2), 0xC000 ); } else { this.load8kRomBank( ((this.nes.rom.romCount - 1) * 2), 0x8000 ); } this.prgAddressChanged = false; } // Select first switchable ROM page: if (this.prgAddressSelect === 0) { this.load8kRomBank(arg, 0x8000); } else { this.load8kRomBank(arg, 0xC000); } break; case this.CMD_SEL_ROM_PAGE2: // Select second switchable ROM page: this.load8kRomBank(arg, 0xA000); // hardwire appropriate bank: if (this.prgAddressChanged) { // Load the two hardwired banks: if (this.prgAddressSelect === 0) { this.load8kRomBank( ((this.nes.rom.romCount - 1) * 2), 0xC000 ); } else { this.load8kRomBank( ((this.nes.rom.romCount - 1) * 2), 0x8000 ); } this.prgAddressChanged = false; } } }; JSNES.Mappers[4].prototype.loadROM = function(rom) { if (!this.nes.rom.valid) { alert("MMC3: Invalid ROM! Unable to load."); return; } // Load hardwired PRG banks (0xC000 and 0xE000): this.load8kRomBank(((this.nes.rom.romCount - 1) * 2), 0xC000); this.load8kRomBank(((this.nes.rom.romCount - 1) * 2) + 1, 0xE000); // Load swappable PRG banks (0x8000 and 0xA000): this.load8kRomBank(0, 0x8000); this.load8kRomBank(1, 0xA000); // Load CHR-ROM: this.loadCHRROM(); // Load Battery RAM (if present): this.loadBatteryRam(); // Do Reset-Interrupt: this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET); }; JSNES.Mappers[4].prototype.clockIrqCounter = function() { if (this.irqEnable == 1) { this.irqCounter--; if (this.irqCounter < 0) { // Trigger IRQ: //nes.getCpu().doIrq(); this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL); this.irqCounter = this.irqLatchValue; } } }; JSNES.Mappers[4].prototype.toJSON = function() { var s = JSNES.Mappers[0].prototype.toJSON.apply(this); s.command = this.command; s.prgAddressSelect = this.prgAddressSelect; s.chrAddressSelect = this.chrAddressSelect; s.pageNumber = this.pageNumber; s.irqCounter = this.irqCounter; s.irqLatchValue = this.irqLatchValue; s.irqEnable = this.irqEnable; s.prgAddressChanged = this.prgAddressChanged; return s; }; JSNES.Mappers[4].prototype.fromJSON = function(s) { JSNES.Mappers[0].prototype.fromJSON.apply(this, s); this.command = s.command; this.prgAddressSelect = s.prgAddressSelect; this.chrAddressSelect = s.chrAddressSelect; this.pageNumber = s.pageNumber; this.irqCounter = s.irqCounter; this.irqLatchValue = s.irqLatchValue; this.irqEnable = s.irqEnable; this.prgAddressChanged = s.prgAddressChanged; };