var utils = require("./utils"); var Mappers = {}; Mappers[0] = function(nes) { this.nes = nes; }; Mappers[0].prototype = { reset: function() { this.joy1StrobeState = 0; this.joy2StrobeState = 0; this.joypadLastWrite = 0; this.zapperFired = false; this.zapperX = null; this.zapperY = 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 persistent RAM this.nes.opts.onBatteryRamWrite(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 // https://wiki.nesdev.com/w/index.php/Zapper var w; if ( this.zapperX !== null && this.zapperY !== null && this.nes.ppu.isPixelWhite(this.zapperX, this.zapperY) ) { w = 0; } else { w = 0x1 << 3; } if (this.zapperFired) { w |= 0x1 << 4; } return (this.joy2Read() | w) & 0xffff; } break; } return 0; }, regWrite: function(address, value) { switch (address) { case 0x2000: // PPU Control register 1 this.nes.cpu.mem[address] = value; this.nes.ppu.updateControlReg1(value); break; case 0x2001: // PPU Control register 2 this.nes.cpu.mem[address] = value; this.nes.ppu.updateControlReg2(value); break; case 0x2003: // Set Sprite RAM address: this.nes.ppu.writeSRAMAddress(value); break; case 0x2004: // Write to Sprite RAM: this.nes.ppu.sramWrite(value); break; case 0x2005: // Screen Scroll offsets: this.nes.ppu.scrollWrite(value); break; case 0x2006: // Set VRAM address: this.nes.ppu.writeVRAMAddress(value); break; case 0x2007: // Write to VRAM: this.nes.ppu.vramWrite(value); break; case 0x4014: // Sprite Memory DMA Access this.nes.ppu.sramDMA(value); break; case 0x4015: // Sound Channel Switch, DMC Status this.nes.papu.writeReg(address, value); break; case 0x4016: // Joystick 1 + Strobe if ((value & 1) === 0 && (this.joypadLastWrite & 1) === 1) { this.joy1StrobeState = 0; this.joy2StrobeState = 0; } this.joypadLastWrite = value; break; case 0x4017: // Sound channel frame sequencer: this.nes.papu.writeReg(address, value); break; default: // Sound registers // console.log("write to sound reg"); if (address >= 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.controllers[1].state[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.controllers[2].state[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) { throw new Error("NoMapper: Invalid ROM! Unable to load."); } // 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() { // console.log("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: 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); 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(); 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]; 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; utils.copyArrayElements( this.nes.rom.vrom[bank4k], bankoffset, this.nes.ppu.vramMem, address, 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; 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); 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. }, // eslint-disable-next-line no-unused-vars 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; } }; Mappers[1] = function(nes) { this.nes = nes; }; Mappers[1].prototype = new Mappers[0](); Mappers[1].prototype.reset = function() { 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; }; Mappers[1].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { 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: 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; } }; Mappers[1].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("MMC1: Invalid ROM! Unable to load."); } // 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); }; // eslint-disable-next-line no-unused-vars Mappers[1].prototype.switchLowHighPrgRom = function(oldSetting) { // not yet. }; Mappers[1].prototype.switch16to32 = function() { // not yet. }; Mappers[1].prototype.switch32to16 = function() { // not yet. }; Mappers[1].prototype.toJSON = function() { var s = 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; }; Mappers[1].prototype.fromJSON = function(s) { Mappers[0].prototype.fromJSON.apply(this, arguments); 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; }; Mappers[2] = function(nes) { this.nes = nes; }; Mappers[2].prototype = new Mappers[0](); Mappers[2].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { 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); } }; Mappers[2].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("UNROM: Invalid ROM! Unable to load."); } // 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); }; /** * Mapper 003 (CNROM) * * @constructor * @example Solomon's Key, Arkanoid, Arkista's Ring, Bump 'n' Jump, Cybernoid * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_003 */ Mappers[3] = function(nes) { this.nes = nes; }; Mappers[3].prototype = new Mappers[0](); Mappers[3].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { 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 is a VROM bank select command. // Swap in the given VROM bank at 0x0000: var bank = (value % (this.nes.rom.vromCount / 2)) * 2; this.loadVromBank(bank, 0x0000); this.loadVromBank(bank + 1, 0x1000); this.load8kVromBank(value * 2, 0x0000); } }; 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; }; Mappers[4].prototype = new Mappers[0](); Mappers[4].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { 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. } }; 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; } } }; Mappers[4].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("MMC3: Invalid ROM! Unable to load."); } // 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); }; 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; } } }; Mappers[4].prototype.toJSON = function() { var s = 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; }; Mappers[4].prototype.fromJSON = function(s) { Mappers[0].prototype.fromJSON.apply(this, arguments); 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; }; /** * Mapper005 (MMC5,ExROM) * * @example Castlevania 3, Just Breed, Uncharted Waters, Romance of the 3 Kingdoms 2, Laser Invasion, Metal Slader Glory, Uchuu Keibitai SDF, Shin 4 Nin Uchi Mahjong - Yakuman Tengoku * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_005 * @constructor */ Mappers[5] = function(nes) { this.nes = nes; }; Mappers[5].prototype = new Mappers[0](); Mappers[5].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { Mappers[0].prototype.write.apply(this, arguments); } else { this.load8kVromBank(value, 0x0000); } }; Mappers[5].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x5000) { Mappers[0].prototype.write.apply(this, arguments); return; } switch (address) { case 0x5100: this.prg_size = value & 3; break; case 0x5101: this.chr_size = value & 3; break; case 0x5102: this.sram_we_a = value & 3; break; case 0x5103: this.sram_we_b = value & 3; break; case 0x5104: this.graphic_mode = value & 3; break; case 0x5105: this.nametable_mode = value; this.nametable_type[0] = value & 3; this.load1kVromBank(value & 3, 0x2000); value >>= 2; this.nametable_type[1] = value & 3; this.load1kVromBank(value & 3, 0x2400); value >>= 2; this.nametable_type[2] = value & 3; this.load1kVromBank(value & 3, 0x2800); value >>= 2; this.nametable_type[3] = value & 3; this.load1kVromBank(value & 3, 0x2c00); break; case 0x5106: this.fill_chr = value; break; case 0x5107: this.fill_pal = value & 3; break; case 0x5113: this.SetBank_SRAM(3, value & 3); break; case 0x5114: case 0x5115: case 0x5116: case 0x5117: this.SetBank_CPU(address, value); break; case 0x5120: case 0x5121: case 0x5122: case 0x5123: case 0x5124: case 0x5125: case 0x5126: case 0x5127: this.chr_mode = 0; this.chr_page[0][address & 7] = value; this.SetBank_PPU(); break; case 0x5128: case 0x5129: case 0x512a: case 0x512b: this.chr_mode = 1; this.chr_page[1][(address & 3) + 0] = value; this.chr_page[1][(address & 3) + 4] = value; this.SetBank_PPU(); break; case 0x5200: this.split_control = value; break; case 0x5201: this.split_scroll = value; break; case 0x5202: this.split_page = value & 0x3f; break; case 0x5203: this.irq_line = value; this.nes.cpu.ClearIRQ(); break; case 0x5204: this.irq_enable = value; this.nes.cpu.ClearIRQ(); break; case 0x5205: this.mult_a = value; break; case 0x5206: this.mult_b = value; break; default: if (address >= 0x5000 && address <= 0x5015) { this.nes.papu.exWrite(address, value); } else if (address >= 0x5c00 && address <= 0x5fff) { if (this.graphic_mode === 2) { // ExRAM // vram write } else if (this.graphic_mode !== 3) { // Split,ExGraphic if (this.irq_status & 0x40) { // vram write } else { // vram write } } } else if (address >= 0x6000 && address <= 0x7fff) { if (this.sram_we_a === 2 && this.sram_we_b === 1) { // additional ram write } } break; } }; Mappers[5].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("UNROM: Invalid ROM! Unable to load."); } // Load PRG-ROM: this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0x8000); this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xa000); this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xc000); this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xe000); // Load CHR-ROM: this.loadCHRROM(); // Do Reset-Interrupt: this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET); }; /** * Mapper007 (AxROM) * @example Battletoads, Time Lord, Marble Madness * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_007 * @constructor */ Mappers[7] = function(nes) { this.nes = nes; }; Mappers[7].prototype = new Mappers[0](); Mappers[7].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { Mappers[0].prototype.write.apply(this, arguments); } else { this.load32kRomBank(value & 0x7, 0x8000); if (value & 0x10) { this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING2); } else { this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING); } } }; Mappers[7].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("AOROM: Invalid ROM! Unable to load."); } // Load PRG-ROM: this.loadPRGROM(); // Load CHR-ROM: this.loadCHRROM(); // Do Reset-Interrupt: this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET); }; /** * Mapper 011 (Color Dreams) * * @description http://wiki.nesdev.com/w/index.php/Color_Dreams * @example Crystal Mines, Metal Fighter * @constructor */ Mappers[11] = function(nes) { this.nes = nes; }; Mappers[11].prototype = new Mappers[0](); Mappers[11].prototype.write = function(address, value) { if (address < 0x8000) { Mappers[0].prototype.write.apply(this, arguments); return; } else { // Swap in the given PRG-ROM bank: var prgbank1 = ((value & 0xf) * 2) % this.nes.rom.romCount; var prgbank2 = ((value & 0xf) * 2 + 1) % this.nes.rom.romCount; this.loadRomBank(prgbank1, 0x8000); this.loadRomBank(prgbank2, 0xc000); if (this.nes.rom.vromCount > 0) { // Swap in the given VROM bank at 0x0000: var bank = ((value >> 4) * 2) % this.nes.rom.vromCount; this.loadVromBank(bank, 0x0000); this.loadVromBank(bank + 1, 0x1000); } } }; /** * Mapper 034 (BNROM, NINA-01) * * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_034 * @example Darkseed, Mashou, Mission Impossible 2 * @constructor */ Mappers[34] = function(nes) { this.nes = nes; }; Mappers[34].prototype = new Mappers[0](); Mappers[34].prototype.write = function(address, value) { if (address < 0x8000) { Mappers[0].prototype.write.apply(this, arguments); return; } else { this.load32kRomBank(value, 0x8000); } }; /** * Mapper 038 * * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_038 * @example Crime Busters * @constructor */ Mappers[38] = function(nes) { this.nes = nes; }; Mappers[38].prototype = new Mappers[0](); Mappers[38].prototype.write = function(address, value) { if (address < 0x7000 || address > 0x7fff) { Mappers[0].prototype.write.apply(this, arguments); return; } else { // Swap in the given PRG-ROM bank at 0x8000: this.load32kRomBank(value & 3, 0x8000); // Swap in the given VROM bank at 0x0000: this.load8kVromBank(((value >> 2) & 3) * 2, 0x0000); } }; /** * Mapper 066 (GxROM) * * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_066 * @example Doraemon, Dragon Power, Gumshoe, Thunder & Lightning, * Super Mario Bros. + Duck Hunt * @constructor */ Mappers[66] = function(nes) { this.nes = nes; }; Mappers[66].prototype = new Mappers[0](); Mappers[66].prototype.write = function(address, value) { if (address < 0x8000) { Mappers[0].prototype.write.apply(this, arguments); return; } else { // Swap in the given PRG-ROM bank at 0x8000: this.load32kRomBank((value >> 4) & 3, 0x8000); // Swap in the given VROM bank at 0x0000: this.load8kVromBank((value & 3) * 2, 0x0000); } }; /** * Mapper 094 (UN1ROM) * * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_094 * @example Senjou no Ookami * @constructor */ Mappers[94] = function(nes) { this.nes = nes; }; Mappers[94].prototype = new Mappers[0](); Mappers[94].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { 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 >> 2, 0x8000); } }; Mappers[94].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("UN1ROM: Invalid ROM! Unable to load."); } // 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); }; /** * Mapper 140 * * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_140 * @example Bio Senshi Dan - Increaser Tono Tatakai * @constructor */ Mappers[140] = function(nes) { this.nes = nes; }; Mappers[140].prototype = new Mappers[0](); Mappers[140].prototype.write = function(address, value) { if (address < 0x6000 || address > 0x7fff) { Mappers[0].prototype.write.apply(this, arguments); return; } else { // Swap in the given PRG-ROM bank at 0x8000: this.load32kRomBank((value >> 4) & 3, 0x8000); // Swap in the given VROM bank at 0x0000: this.load8kVromBank((value & 0xf) * 2, 0x0000); } }; /** * Mapper 180 * * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_180 * @example Crazy Climber * @constructor */ Mappers[180] = function(nes) { this.nes = nes; }; Mappers[180].prototype = new Mappers[0](); Mappers[180].prototype.write = function(address, value) { // Writes to addresses other than MMC registers are handled by NoMapper. if (address < 0x8000) { Mappers[0].prototype.write.apply(this, arguments); return; } else { // This is a ROM bank select command. // Swap in the given ROM bank at 0xc000: this.loadRomBank(value, 0xc000); } }; Mappers[180].prototype.loadROM = function() { if (!this.nes.rom.valid) { throw new Error("Mapper 180: Invalid ROM! Unable to load."); } // 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); }; module.exports = Mappers;