{ "version": 3, "sources": ["../jsnes/src/controller.js", "../jsnes/src/utils.js", "../jsnes/src/cpu.js", "../jsnes/src/tile.js", "../jsnes/src/ppu.js", "../jsnes/src/papu.js", "../jsnes/src/mappers.js", "../jsnes/src/rom.js", "../jsnes/src/nes.js", "../jsnes/src/index.js", "../src/platform/nes.ts"], "sourcesContent": ["var Controller = function() {\n this.state = new Array(8);\n for (var i = 0; i < this.state.length; i++) {\n this.state[i] = 0x40;\n }\n};\n\nController.BUTTON_A = 0;\nController.BUTTON_B = 1;\nController.BUTTON_SELECT = 2;\nController.BUTTON_START = 3;\nController.BUTTON_UP = 4;\nController.BUTTON_DOWN = 5;\nController.BUTTON_LEFT = 6;\nController.BUTTON_RIGHT = 7;\n\nController.prototype = {\n buttonDown: function(key) {\n this.state[key] = 0x41;\n },\n\n buttonUp: function(key) {\n this.state[key] = 0x40;\n }\n};\n\nmodule.exports = Controller;\n", "module.exports = {\n copyArrayElements: function(src, srcPos, dest, destPos, length) {\n for (var i = 0; i < length; ++i) {\n dest[destPos + i] = src[srcPos + i];\n }\n },\n\n copyArray: function(src) {\n return src.slice(0);\n },\n\n fromJSON: function(obj, state) {\n for (var i = 0; i < obj.JSON_PROPERTIES.length; i++) {\n var key = obj.JSON_PROPERTIES[i];\n if (obj[key] !== null && obj[key].buffer && obj[key].set)\n obj[key].set(state[key]); // set array elements\n else\n obj[key] = state[key]; // replace\n }\n },\n\n toJSON: function(obj) {\n var state = {};\n for (var i = 0; i < obj.JSON_PROPERTIES.length; i++) {\n var key = obj.JSON_PROPERTIES[i];\n state[key] = obj[key];\n }\n return state;\n }\n};\n", "var utils = require(\"./utils\");\n\nvar CPU = function(nes) {\n this.nes = nes;\n\n // Keep Chrome happy\n this.mem = null;\n this.REG_ACC = null;\n this.REG_X = null;\n this.REG_Y = null;\n this.REG_SP = null;\n this.REG_PC = null;\n this.REG_PC_NEW = null;\n this.REG_STATUS = null;\n this.F_CARRY = null;\n this.F_DECIMAL = null;\n this.F_INTERRUPT = null;\n this.F_INTERRUPT_NEW = null;\n this.F_OVERFLOW = null;\n this.F_SIGN = null;\n this.F_ZERO = null;\n this.F_NOTUSED = null;\n this.F_NOTUSED_NEW = null;\n this.F_BRK = null;\n this.F_BRK_NEW = null;\n this.opdata = null;\n this.cyclesToHalt = null;\n this.crash = null;\n this.irqRequested = null;\n this.irqType = null;\n\n this.reset();\n};\n\nCPU.prototype = {\n // IRQ Types\n IRQ_NORMAL: 0,\n IRQ_NMI: 1,\n IRQ_RESET: 2,\n\n reset: function() {\n // Main memory\n this.mem = new Uint8Array(0x10000);\n\n for (var i = 0; i < 0x2000; i++) {\n this.mem[i] = 0xff;\n }\n for (var p = 0; p < 4; p++) {\n var j = p * 0x800;\n this.mem[j + 0x008] = 0xf7;\n this.mem[j + 0x009] = 0xef;\n this.mem[j + 0x00a] = 0xdf;\n this.mem[j + 0x00f] = 0xbf;\n }\n for (var k = 0x2001; k < this.mem.length; k++) {\n this.mem[k] = 0;\n }\n\n // CPU Registers:\n this.REG_ACC = 0;\n this.REG_X = 0;\n this.REG_Y = 0;\n // Reset Stack pointer:\n this.REG_SP = 0x01ff;\n // Reset Program counter:\n this.REG_PC = 0x8000 - 1;\n this.REG_PC_NEW = 0x8000 - 1;\n // Reset Status register:\n this.REG_STATUS = 0x28;\n\n this.setStatus(0x28);\n\n // Set flags:\n this.F_CARRY = 0;\n this.F_DECIMAL = 0;\n this.F_INTERRUPT = 1;\n this.F_INTERRUPT_NEW = 1;\n this.F_OVERFLOW = 0;\n this.F_SIGN = 0;\n this.F_ZERO = 1;\n\n this.F_NOTUSED = 1;\n this.F_NOTUSED_NEW = 1;\n this.F_BRK = 1;\n this.F_BRK_NEW = 1;\n\n this.opdata = new OpData().opdata;\n this.cyclesToHalt = 0;\n\n // Reset crash flag:\n this.crash = false;\n\n // Interrupt notification:\n this.irqRequested = false;\n this.irqType = null;\n },\n\n // Emulates a single CPU instruction, returns the number of cycles\n emulate: function() {\n var temp;\n var add;\n\n // Check interrupts:\n if (this.irqRequested) {\n temp =\n this.F_CARRY |\n ((this.F_ZERO === 0 ? 1 : 0) << 1) |\n (this.F_INTERRUPT << 2) |\n (this.F_DECIMAL << 3) |\n (this.F_BRK << 4) |\n (this.F_NOTUSED << 5) |\n (this.F_OVERFLOW << 6) |\n (this.F_SIGN << 7);\n\n this.REG_PC_NEW = this.REG_PC;\n this.F_INTERRUPT_NEW = this.F_INTERRUPT;\n switch (this.irqType) {\n case 0: {\n // Normal IRQ:\n if (this.F_INTERRUPT !== 0) {\n // console.log(\"Interrupt was masked.\");\n break;\n }\n this.doIrq(temp);\n // console.log(\"Did normal IRQ. I=\"+this.F_INTERRUPT);\n break;\n }\n case 1: {\n // NMI:\n this.doNonMaskableInterrupt(temp);\n break;\n }\n case 2: {\n // Reset:\n this.doResetInterrupt();\n break;\n }\n }\n\n this.REG_PC = this.REG_PC_NEW;\n this.F_INTERRUPT = this.F_INTERRUPT_NEW;\n this.F_BRK = this.F_BRK_NEW;\n this.irqRequested = false;\n }\n\n var opinf = this.opdata[this.nes.mmap.load(this.REG_PC + 1)];\n var cycleCount = opinf >> 24;\n var cycleAdd = 0;\n\n // Find address mode:\n var addrMode = (opinf >> 8) & 0xff;\n\n // Increment PC by number of op bytes:\n var opaddr = this.REG_PC;\n this.REG_PC += (opinf >> 16) & 0xff;\n\n var addr = 0;\n switch (addrMode) {\n case 0: {\n // Zero Page mode. Use the address given after the opcode,\n // but without high byte.\n addr = this.load(opaddr + 2);\n break;\n }\n case 1: {\n // Relative mode.\n addr = this.load(opaddr + 2);\n if (addr < 0x80) {\n addr += this.REG_PC;\n } else {\n addr += this.REG_PC - 256;\n }\n break;\n }\n case 2: {\n // Ignore. Address is implied in instruction.\n break;\n }\n case 3: {\n // Absolute mode. Use the two bytes following the opcode as\n // an address.\n addr = this.load16bit(opaddr + 2);\n break;\n }\n case 4: {\n // Accumulator mode. The address is in the accumulator\n // register.\n addr = this.REG_ACC;\n break;\n }\n case 5: {\n // Immediate mode. The value is given after the opcode.\n addr = this.REG_PC;\n break;\n }\n case 6: {\n // Zero Page Indexed mode, X as index. Use the address given\n // after the opcode, then add the\n // X register to it to get the final address.\n addr = (this.load(opaddr + 2) + this.REG_X) & 0xff;\n break;\n }\n case 7: {\n // Zero Page Indexed mode, Y as index. Use the address given\n // after the opcode, then add the\n // Y register to it to get the final address.\n addr = (this.load(opaddr + 2) + this.REG_Y) & 0xff;\n break;\n }\n case 8: {\n // Absolute Indexed Mode, X as index. Same as zero page\n // indexed, but with the high byte.\n addr = this.load16bit(opaddr + 2);\n if ((addr & 0xff00) !== ((addr + this.REG_X) & 0xff00)) {\n cycleAdd = 1;\n }\n addr += this.REG_X;\n break;\n }\n case 9: {\n // Absolute Indexed Mode, Y as index. Same as zero page\n // indexed, but with the high byte.\n addr = this.load16bit(opaddr + 2);\n if ((addr & 0xff00) !== ((addr + this.REG_Y) & 0xff00)) {\n cycleAdd = 1;\n }\n addr += this.REG_Y;\n break;\n }\n case 10: {\n // Pre-indexed Indirect mode. Find the 16-bit address\n // starting at the given location plus\n // the current X register. The value is the contents of that\n // address.\n addr = this.load(opaddr + 2);\n if ((addr & 0xff00) !== ((addr + this.REG_X) & 0xff00)) {\n cycleAdd = 1;\n }\n addr += this.REG_X;\n addr &= 0xff;\n addr = this.load16bit(addr);\n break;\n }\n case 11: {\n // Post-indexed Indirect mode. Find the 16-bit address\n // contained in the given location\n // (and the one following). Add to that address the contents\n // of the Y register. Fetch the value\n // stored at that adress.\n addr = this.load16bit(this.load(opaddr + 2));\n if ((addr & 0xff00) !== ((addr + this.REG_Y) & 0xff00)) {\n cycleAdd = 1;\n }\n addr += this.REG_Y;\n break;\n }\n case 12: {\n // Indirect Absolute mode. Find the 16-bit address contained\n // at the given location.\n addr = this.load16bit(opaddr + 2); // Find op\n if (addr < 0x1fff) {\n addr =\n this.mem[addr] +\n (this.mem[(addr & 0xff00) | (((addr & 0xff) + 1) & 0xff)] << 8); // Read from address given in op\n } else {\n addr =\n this.nes.mmap.load(addr) +\n (this.nes.mmap.load(\n (addr & 0xff00) | (((addr & 0xff) + 1) & 0xff)\n ) <<\n 8);\n }\n break;\n }\n }\n // Wrap around for addresses above 0xFFFF:\n addr &= 0xffff;\n\n // ----------------------------------------------------------------------------------------------------\n // Decode & execute instruction:\n // ----------------------------------------------------------------------------------------------------\n\n // This should be compiled to a jump table.\n switch (opinf & 0xff) {\n case 0: {\n // *******\n // * ADC *\n // *******\n\n // Add with carry.\n temp = this.REG_ACC + this.load(addr) + this.F_CARRY;\n\n if (\n ((this.REG_ACC ^ this.load(addr)) & 0x80) === 0 &&\n ((this.REG_ACC ^ temp) & 0x80) !== 0\n ) {\n this.F_OVERFLOW = 1;\n } else {\n this.F_OVERFLOW = 0;\n }\n this.F_CARRY = temp > 255 ? 1 : 0;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n this.REG_ACC = temp & 255;\n cycleCount += cycleAdd;\n break;\n }\n case 1: {\n // *******\n // * AND *\n // *******\n\n // AND memory with accumulator.\n this.REG_ACC = this.REG_ACC & this.load(addr);\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 2: {\n // *******\n // * ASL *\n // *******\n\n // Shift left one bit\n if (addrMode === 4) {\n // ADDR_ACC = 4\n\n this.F_CARRY = (this.REG_ACC >> 7) & 1;\n this.REG_ACC = (this.REG_ACC << 1) & 255;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n } else {\n temp = this.load(addr);\n this.F_CARRY = (temp >> 7) & 1;\n temp = (temp << 1) & 255;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp;\n this.write(addr, temp);\n }\n break;\n }\n case 3: {\n // *******\n // * BCC *\n // *******\n\n // Branch on carry clear\n if (this.F_CARRY === 0) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 4: {\n // *******\n // * BCS *\n // *******\n\n // Branch on carry set\n if (this.F_CARRY === 1) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 5: {\n // *******\n // * BEQ *\n // *******\n\n // Branch on zero\n if (this.F_ZERO === 0) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 6: {\n // *******\n // * BIT *\n // *******\n\n temp = this.load(addr);\n this.F_SIGN = (temp >> 7) & 1;\n this.F_OVERFLOW = (temp >> 6) & 1;\n temp &= this.REG_ACC;\n this.F_ZERO = temp;\n break;\n }\n case 7: {\n // *******\n // * BMI *\n // *******\n\n // Branch on negative result\n if (this.F_SIGN === 1) {\n cycleCount++;\n this.REG_PC = addr;\n }\n break;\n }\n case 8: {\n // *******\n // * BNE *\n // *******\n\n // Branch on not zero\n if (this.F_ZERO !== 0) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 9: {\n // *******\n // * BPL *\n // *******\n\n // Branch on positive result\n if (this.F_SIGN === 0) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 10: {\n // *******\n // * BRK *\n // *******\n\n this.REG_PC += 2;\n this.push((this.REG_PC >> 8) & 255);\n this.push(this.REG_PC & 255);\n this.F_BRK = 1;\n\n this.push(\n this.F_CARRY |\n ((this.F_ZERO === 0 ? 1 : 0) << 1) |\n (this.F_INTERRUPT << 2) |\n (this.F_DECIMAL << 3) |\n (this.F_BRK << 4) |\n (this.F_NOTUSED << 5) |\n (this.F_OVERFLOW << 6) |\n (this.F_SIGN << 7)\n );\n\n this.F_INTERRUPT = 1;\n //this.REG_PC = load(0xFFFE) | (load(0xFFFF) << 8);\n this.REG_PC = this.load16bit(0xfffe);\n this.REG_PC--;\n break;\n }\n case 11: {\n // *******\n // * BVC *\n // *******\n\n // Branch on overflow clear\n if (this.F_OVERFLOW === 0) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 12: {\n // *******\n // * BVS *\n // *******\n\n // Branch on overflow set\n if (this.F_OVERFLOW === 1) {\n cycleCount += (opaddr & 0xff00) !== (addr & 0xff00) ? 2 : 1;\n this.REG_PC = addr;\n }\n break;\n }\n case 13: {\n // *******\n // * CLC *\n // *******\n\n // Clear carry flag\n this.F_CARRY = 0;\n break;\n }\n case 14: {\n // *******\n // * CLD *\n // *******\n\n // Clear decimal flag\n this.F_DECIMAL = 0;\n break;\n }\n case 15: {\n // *******\n // * CLI *\n // *******\n\n // Clear interrupt flag\n this.F_INTERRUPT = 0;\n break;\n }\n case 16: {\n // *******\n // * CLV *\n // *******\n\n // Clear overflow flag\n this.F_OVERFLOW = 0;\n break;\n }\n case 17: {\n // *******\n // * CMP *\n // *******\n\n // Compare memory and accumulator:\n temp = this.REG_ACC - this.load(addr);\n this.F_CARRY = temp >= 0 ? 1 : 0;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n cycleCount += cycleAdd;\n break;\n }\n case 18: {\n // *******\n // * CPX *\n // *******\n\n // Compare memory and index X:\n temp = this.REG_X - this.load(addr);\n this.F_CARRY = temp >= 0 ? 1 : 0;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n break;\n }\n case 19: {\n // *******\n // * CPY *\n // *******\n\n // Compare memory and index Y:\n temp = this.REG_Y - this.load(addr);\n this.F_CARRY = temp >= 0 ? 1 : 0;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n break;\n }\n case 20: {\n // *******\n // * DEC *\n // *******\n\n // Decrement memory by one:\n temp = (this.load(addr) - 1) & 0xff;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp;\n this.write(addr, temp);\n break;\n }\n case 21: {\n // *******\n // * DEX *\n // *******\n\n // Decrement index X by one:\n this.REG_X = (this.REG_X - 1) & 0xff;\n this.F_SIGN = (this.REG_X >> 7) & 1;\n this.F_ZERO = this.REG_X;\n break;\n }\n case 22: {\n // *******\n // * DEY *\n // *******\n\n // Decrement index Y by one:\n this.REG_Y = (this.REG_Y - 1) & 0xff;\n this.F_SIGN = (this.REG_Y >> 7) & 1;\n this.F_ZERO = this.REG_Y;\n break;\n }\n case 23: {\n // *******\n // * EOR *\n // *******\n\n // XOR Memory with accumulator, store in accumulator:\n this.REG_ACC = (this.load(addr) ^ this.REG_ACC) & 0xff;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n cycleCount += cycleAdd;\n break;\n }\n case 24: {\n // *******\n // * INC *\n // *******\n\n // Increment memory by one:\n temp = (this.load(addr) + 1) & 0xff;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp;\n this.write(addr, temp & 0xff);\n break;\n }\n case 25: {\n // *******\n // * INX *\n // *******\n\n // Increment index X by one:\n this.REG_X = (this.REG_X + 1) & 0xff;\n this.F_SIGN = (this.REG_X >> 7) & 1;\n this.F_ZERO = this.REG_X;\n break;\n }\n case 26: {\n // *******\n // * INY *\n // *******\n\n // Increment index Y by one:\n this.REG_Y++;\n this.REG_Y &= 0xff;\n this.F_SIGN = (this.REG_Y >> 7) & 1;\n this.F_ZERO = this.REG_Y;\n break;\n }\n case 27: {\n // *******\n // * JMP *\n // *******\n\n // Jump to new location:\n this.REG_PC = addr - 1;\n break;\n }\n case 28: {\n // *******\n // * JSR *\n // *******\n\n // Jump to new location, saving return address.\n // Push return address on stack:\n this.push((this.REG_PC >> 8) & 255);\n this.push(this.REG_PC & 255);\n this.REG_PC = addr - 1;\n break;\n }\n case 29: {\n // *******\n // * LDA *\n // *******\n\n // Load accumulator with memory:\n this.REG_ACC = this.load(addr);\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n cycleCount += cycleAdd;\n break;\n }\n case 30: {\n // *******\n // * LDX *\n // *******\n\n // Load index X with memory:\n this.REG_X = this.load(addr);\n this.F_SIGN = (this.REG_X >> 7) & 1;\n this.F_ZERO = this.REG_X;\n cycleCount += cycleAdd;\n break;\n }\n case 31: {\n // *******\n // * LDY *\n // *******\n\n // Load index Y with memory:\n this.REG_Y = this.load(addr);\n this.F_SIGN = (this.REG_Y >> 7) & 1;\n this.F_ZERO = this.REG_Y;\n cycleCount += cycleAdd;\n break;\n }\n case 32: {\n // *******\n // * LSR *\n // *******\n\n // Shift right one bit:\n if (addrMode === 4) {\n // ADDR_ACC\n\n temp = this.REG_ACC & 0xff;\n this.F_CARRY = temp & 1;\n temp >>= 1;\n this.REG_ACC = temp;\n } else {\n temp = this.load(addr) & 0xff;\n this.F_CARRY = temp & 1;\n temp >>= 1;\n this.write(addr, temp);\n }\n this.F_SIGN = 0;\n this.F_ZERO = temp;\n break;\n }\n case 33: {\n // *******\n // * NOP *\n // *******\n\n // No OPeration.\n // Ignore.\n break;\n }\n case 34: {\n // *******\n // * ORA *\n // *******\n\n // OR memory with accumulator, store in accumulator.\n temp = (this.load(addr) | this.REG_ACC) & 255;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp;\n this.REG_ACC = temp;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 35: {\n // *******\n // * PHA *\n // *******\n\n // Push accumulator on stack\n this.push(this.REG_ACC);\n break;\n }\n case 36: {\n // *******\n // * PHP *\n // *******\n\n // Push processor status on stack\n this.F_BRK = 1;\n this.push(\n this.F_CARRY |\n ((this.F_ZERO === 0 ? 1 : 0) << 1) |\n (this.F_INTERRUPT << 2) |\n (this.F_DECIMAL << 3) |\n (this.F_BRK << 4) |\n (this.F_NOTUSED << 5) |\n (this.F_OVERFLOW << 6) |\n (this.F_SIGN << 7)\n );\n break;\n }\n case 37: {\n // *******\n // * PLA *\n // *******\n\n // Pull accumulator from stack\n this.REG_ACC = this.pull();\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n break;\n }\n case 38: {\n // *******\n // * PLP *\n // *******\n\n // Pull processor status from stack\n temp = this.pull();\n this.F_CARRY = temp & 1;\n this.F_ZERO = ((temp >> 1) & 1) === 1 ? 0 : 1;\n this.F_INTERRUPT = (temp >> 2) & 1;\n this.F_DECIMAL = (temp >> 3) & 1;\n this.F_BRK = (temp >> 4) & 1;\n this.F_NOTUSED = (temp >> 5) & 1;\n this.F_OVERFLOW = (temp >> 6) & 1;\n this.F_SIGN = (temp >> 7) & 1;\n\n this.F_NOTUSED = 1;\n break;\n }\n case 39: {\n // *******\n // * ROL *\n // *******\n\n // Rotate one bit left\n if (addrMode === 4) {\n // ADDR_ACC = 4\n\n temp = this.REG_ACC;\n add = this.F_CARRY;\n this.F_CARRY = (temp >> 7) & 1;\n temp = ((temp << 1) & 0xff) + add;\n this.REG_ACC = temp;\n } else {\n temp = this.load(addr);\n add = this.F_CARRY;\n this.F_CARRY = (temp >> 7) & 1;\n temp = ((temp << 1) & 0xff) + add;\n this.write(addr, temp);\n }\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp;\n break;\n }\n case 40: {\n // *******\n // * ROR *\n // *******\n\n // Rotate one bit right\n if (addrMode === 4) {\n // ADDR_ACC = 4\n\n add = this.F_CARRY << 7;\n this.F_CARRY = this.REG_ACC & 1;\n temp = (this.REG_ACC >> 1) + add;\n this.REG_ACC = temp;\n } else {\n temp = this.load(addr);\n add = this.F_CARRY << 7;\n this.F_CARRY = temp & 1;\n temp = (temp >> 1) + add;\n this.write(addr, temp);\n }\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp;\n break;\n }\n case 41: {\n // *******\n // * RTI *\n // *******\n\n // Return from interrupt. Pull status and PC from stack.\n\n temp = this.pull();\n this.F_CARRY = temp & 1;\n this.F_ZERO = ((temp >> 1) & 1) === 0 ? 1 : 0;\n this.F_INTERRUPT = (temp >> 2) & 1;\n this.F_DECIMAL = (temp >> 3) & 1;\n this.F_BRK = (temp >> 4) & 1;\n this.F_NOTUSED = (temp >> 5) & 1;\n this.F_OVERFLOW = (temp >> 6) & 1;\n this.F_SIGN = (temp >> 7) & 1;\n\n this.REG_PC = this.pull();\n this.REG_PC += this.pull() << 8;\n if (this.REG_PC === 0xffff) {\n return;\n }\n this.REG_PC--;\n this.F_NOTUSED = 1;\n break;\n }\n case 42: {\n // *******\n // * RTS *\n // *******\n\n // Return from subroutine. Pull PC from stack.\n\n this.REG_PC = this.pull();\n this.REG_PC += this.pull() << 8;\n\n if (this.REG_PC === 0xffff) {\n return; // return from NSF play routine:\n }\n break;\n }\n case 43: {\n // *******\n // * SBC *\n // *******\n\n temp = this.REG_ACC - this.load(addr) - (1 - this.F_CARRY);\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n if (\n ((this.REG_ACC ^ temp) & 0x80) !== 0 &&\n ((this.REG_ACC ^ this.load(addr)) & 0x80) !== 0\n ) {\n this.F_OVERFLOW = 1;\n } else {\n this.F_OVERFLOW = 0;\n }\n this.F_CARRY = temp < 0 ? 0 : 1;\n this.REG_ACC = temp & 0xff;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 44: {\n // *******\n // * SEC *\n // *******\n\n // Set carry flag\n this.F_CARRY = 1;\n break;\n }\n case 45: {\n // *******\n // * SED *\n // *******\n\n // Set decimal mode\n this.F_DECIMAL = 1;\n break;\n }\n case 46: {\n // *******\n // * SEI *\n // *******\n\n // Set interrupt disable status\n this.F_INTERRUPT = 1;\n break;\n }\n case 47: {\n // *******\n // * STA *\n // *******\n\n // Store accumulator in memory\n this.write(addr, this.REG_ACC);\n break;\n }\n case 48: {\n // *******\n // * STX *\n // *******\n\n // Store index X in memory\n this.write(addr, this.REG_X);\n break;\n }\n case 49: {\n // *******\n // * STY *\n // *******\n\n // Store index Y in memory:\n this.write(addr, this.REG_Y);\n break;\n }\n case 50: {\n // *******\n // * TAX *\n // *******\n\n // Transfer accumulator to index X:\n this.REG_X = this.REG_ACC;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n break;\n }\n case 51: {\n // *******\n // * TAY *\n // *******\n\n // Transfer accumulator to index Y:\n this.REG_Y = this.REG_ACC;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n break;\n }\n case 52: {\n // *******\n // * TSX *\n // *******\n\n // Transfer stack pointer to index X:\n this.REG_X = this.REG_SP - 0x0100;\n this.F_SIGN = (this.REG_SP >> 7) & 1;\n this.F_ZERO = this.REG_X;\n break;\n }\n case 53: {\n // *******\n // * TXA *\n // *******\n\n // Transfer index X to accumulator:\n this.REG_ACC = this.REG_X;\n this.F_SIGN = (this.REG_X >> 7) & 1;\n this.F_ZERO = this.REG_X;\n break;\n }\n case 54: {\n // *******\n // * TXS *\n // *******\n\n // Transfer index X to stack pointer:\n this.REG_SP = this.REG_X + 0x0100;\n this.stackWrap();\n break;\n }\n case 55: {\n // *******\n // * TYA *\n // *******\n\n // Transfer index Y to accumulator:\n this.REG_ACC = this.REG_Y;\n this.F_SIGN = (this.REG_Y >> 7) & 1;\n this.F_ZERO = this.REG_Y;\n break;\n }\n case 56: {\n // *******\n // * ALR *\n // *******\n\n // Shift right one bit after ANDing:\n temp = this.REG_ACC & this.load(addr);\n this.F_CARRY = temp & 1;\n this.REG_ACC = this.F_ZERO = temp >> 1;\n this.F_SIGN = 0;\n break;\n }\n case 57: {\n // *******\n // * ANC *\n // *******\n\n // AND accumulator, setting carry to bit 7 result.\n this.REG_ACC = this.F_ZERO = this.REG_ACC & this.load(addr);\n this.F_CARRY = this.F_SIGN = (this.REG_ACC >> 7) & 1;\n break;\n }\n case 58: {\n // *******\n // * ARR *\n // *******\n\n // Rotate right one bit after ANDing:\n temp = this.REG_ACC & this.load(addr);\n this.REG_ACC = this.F_ZERO = (temp >> 1) + (this.F_CARRY << 7);\n this.F_SIGN = this.F_CARRY;\n this.F_CARRY = (temp >> 7) & 1;\n this.F_OVERFLOW = ((temp >> 7) ^ (temp >> 6)) & 1;\n break;\n }\n case 59: {\n // *******\n // * AXS *\n // *******\n\n // Set X to (X AND A) - value.\n temp = (this.REG_X & this.REG_ACC) - this.load(addr);\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n if (\n ((this.REG_X ^ temp) & 0x80) !== 0 &&\n ((this.REG_X ^ this.load(addr)) & 0x80) !== 0\n ) {\n this.F_OVERFLOW = 1;\n } else {\n this.F_OVERFLOW = 0;\n }\n this.F_CARRY = temp < 0 ? 0 : 1;\n this.REG_X = temp & 0xff;\n break;\n }\n case 60: {\n // *******\n // * LAX *\n // *******\n\n // Load A and X with memory:\n this.REG_ACC = this.REG_X = this.F_ZERO = this.load(addr);\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n cycleCount += cycleAdd;\n break;\n }\n case 61: {\n // *******\n // * SAX *\n // *******\n\n // Store A AND X in memory:\n this.write(addr, this.REG_ACC & this.REG_X);\n break;\n }\n case 62: {\n // *******\n // * DCP *\n // *******\n\n // Decrement memory by one:\n temp = (this.load(addr) - 1) & 0xff;\n this.write(addr, temp);\n\n // Then compare with the accumulator:\n temp = this.REG_ACC - temp;\n this.F_CARRY = temp >= 0 ? 1 : 0;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 63: {\n // *******\n // * ISC *\n // *******\n\n // Increment memory by one:\n temp = (this.load(addr) + 1) & 0xff;\n this.write(addr, temp);\n\n // Then subtract from the accumulator:\n temp = this.REG_ACC - temp - (1 - this.F_CARRY);\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n if (\n ((this.REG_ACC ^ temp) & 0x80) !== 0 &&\n ((this.REG_ACC ^ this.load(addr)) & 0x80) !== 0\n ) {\n this.F_OVERFLOW = 1;\n } else {\n this.F_OVERFLOW = 0;\n }\n this.F_CARRY = temp < 0 ? 0 : 1;\n this.REG_ACC = temp & 0xff;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 64: {\n // *******\n // * RLA *\n // *******\n\n // Rotate one bit left\n temp = this.load(addr);\n add = this.F_CARRY;\n this.F_CARRY = (temp >> 7) & 1;\n temp = ((temp << 1) & 0xff) + add;\n this.write(addr, temp);\n\n // Then AND with the accumulator.\n this.REG_ACC = this.REG_ACC & temp;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 65: {\n // *******\n // * RRA *\n // *******\n\n // Rotate one bit right\n temp = this.load(addr);\n add = this.F_CARRY << 7;\n this.F_CARRY = temp & 1;\n temp = (temp >> 1) + add;\n this.write(addr, temp);\n\n // Then add to the accumulator\n temp = this.REG_ACC + this.load(addr) + this.F_CARRY;\n\n if (\n ((this.REG_ACC ^ this.load(addr)) & 0x80) === 0 &&\n ((this.REG_ACC ^ temp) & 0x80) !== 0\n ) {\n this.F_OVERFLOW = 1;\n } else {\n this.F_OVERFLOW = 0;\n }\n this.F_CARRY = temp > 255 ? 1 : 0;\n this.F_SIGN = (temp >> 7) & 1;\n this.F_ZERO = temp & 0xff;\n this.REG_ACC = temp & 255;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 66: {\n // *******\n // * SLO *\n // *******\n\n // Shift one bit left\n temp = this.load(addr);\n this.F_CARRY = (temp >> 7) & 1;\n temp = (temp << 1) & 255;\n this.write(addr, temp);\n\n // Then OR with the accumulator.\n this.REG_ACC = this.REG_ACC | temp;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 67: {\n // *******\n // * SRE *\n // *******\n\n // Shift one bit right\n temp = this.load(addr) & 0xff;\n this.F_CARRY = temp & 1;\n temp >>= 1;\n this.write(addr, temp);\n\n // Then XOR with the accumulator.\n this.REG_ACC = this.REG_ACC ^ temp;\n this.F_SIGN = (this.REG_ACC >> 7) & 1;\n this.F_ZERO = this.REG_ACC;\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n case 68: {\n // *******\n // * SKB *\n // *******\n\n // Do nothing\n break;\n }\n case 69: {\n // *******\n // * IGN *\n // *******\n\n // Do nothing but load.\n // TODO: Properly implement the double-reads.\n this.load(addr);\n if (addrMode !== 11) cycleCount += cycleAdd; // PostIdxInd = 11\n break;\n }\n\n default: {\n // *******\n // * ??? *\n // *******\n\n this.nes.stop();\n this.nes.crashMessage =\n \"Game crashed, invalid opcode at address $\" + opaddr.toString(16);\n break;\n }\n } // end of switch\n\n return cycleCount;\n },\n\n load: function(addr) {\n if (addr < 0x2000) {\n return this.mem[addr & 0x7ff];\n } else {\n return this.nes.mmap.load(addr);\n }\n },\n\n load16bit: function(addr) {\n if (addr < 0x1fff) {\n return this.mem[addr & 0x7ff] | (this.mem[(addr + 1) & 0x7ff] << 8);\n } else {\n return this.nes.mmap.load(addr) | (this.nes.mmap.load(addr + 1) << 8);\n }\n },\n\n write: function(addr, val) {\n if (addr < 0x2000) {\n this.mem[addr & 0x7ff] = val;\n } else {\n this.nes.mmap.write(addr, val);\n }\n },\n\n requestIrq: function(type) {\n if (this.irqRequested) {\n if (type === this.IRQ_NORMAL) {\n return;\n }\n // console.log(\"too fast irqs. type=\"+type);\n }\n this.irqRequested = true;\n this.irqType = type;\n },\n\n push: function(value) {\n this.nes.mmap.write(this.REG_SP, value);\n this.REG_SP--;\n this.REG_SP = 0x0100 | (this.REG_SP & 0xff);\n },\n\n stackWrap: function() {\n this.REG_SP = 0x0100 | (this.REG_SP & 0xff);\n },\n\n pull: function() {\n this.REG_SP++;\n this.REG_SP = 0x0100 | (this.REG_SP & 0xff);\n return this.nes.mmap.load(this.REG_SP);\n },\n\n pageCrossed: function(addr1, addr2) {\n return (addr1 & 0xff00) !== (addr2 & 0xff00);\n },\n\n haltCycles: function(cycles) {\n this.cyclesToHalt += cycles;\n },\n\n doNonMaskableInterrupt: function(status) {\n if ((this.nes.mmap.load(0x2000) & 128) !== 0) {\n // Check whether VBlank Interrupts are enabled\n\n this.REG_PC_NEW++;\n this.push((this.REG_PC_NEW >> 8) & 0xff);\n this.push(this.REG_PC_NEW & 0xff);\n //this.F_INTERRUPT_NEW = 1;\n this.push(status);\n\n this.REG_PC_NEW =\n this.nes.mmap.load(0xfffa) | (this.nes.mmap.load(0xfffb) << 8);\n this.REG_PC_NEW--;\n }\n },\n\n doResetInterrupt: function() {\n this.REG_PC_NEW =\n this.nes.mmap.load(0xfffc) | (this.nes.mmap.load(0xfffd) << 8);\n this.REG_PC_NEW--;\n },\n\n doIrq: function(status) {\n this.REG_PC_NEW++;\n this.push((this.REG_PC_NEW >> 8) & 0xff);\n this.push(this.REG_PC_NEW & 0xff);\n this.push(status);\n this.F_INTERRUPT_NEW = 1;\n this.F_BRK_NEW = 0;\n\n this.REG_PC_NEW =\n this.nes.mmap.load(0xfffe) | (this.nes.mmap.load(0xffff) << 8);\n this.REG_PC_NEW--;\n },\n\n getStatus: function() {\n return (\n this.F_CARRY |\n (this.F_ZERO << 1) |\n (this.F_INTERRUPT << 2) |\n (this.F_DECIMAL << 3) |\n (this.F_BRK << 4) |\n (this.F_NOTUSED << 5) |\n (this.F_OVERFLOW << 6) |\n (this.F_SIGN << 7)\n );\n },\n\n setStatus: function(st) {\n this.F_CARRY = st & 1;\n this.F_ZERO = (st >> 1) & 1;\n this.F_INTERRUPT = (st >> 2) & 1;\n this.F_DECIMAL = (st >> 3) & 1;\n this.F_BRK = (st >> 4) & 1;\n this.F_NOTUSED = (st >> 5) & 1;\n this.F_OVERFLOW = (st >> 6) & 1;\n this.F_SIGN = (st >> 7) & 1;\n },\n\n JSON_PROPERTIES: [\n \"mem\",\n \"cyclesToHalt\",\n \"irqRequested\",\n \"irqType\",\n // Registers\n \"REG_ACC\",\n \"REG_X\",\n \"REG_Y\",\n \"REG_SP\",\n \"REG_PC\",\n \"REG_PC_NEW\",\n \"REG_STATUS\",\n // Status\n \"F_CARRY\",\n \"F_DECIMAL\",\n \"F_INTERRUPT\",\n \"F_INTERRUPT_NEW\",\n \"F_OVERFLOW\",\n \"F_SIGN\",\n \"F_ZERO\",\n \"F_NOTUSED\",\n \"F_NOTUSED_NEW\",\n \"F_BRK\",\n \"F_BRK_NEW\"\n ],\n\n toJSON: function() {\n return utils.toJSON(this);\n },\n\n fromJSON: function(s) {\n utils.fromJSON(this, s);\n }\n};\n\n// Generates and provides an array of details about instructions\nvar OpData = function() {\n this.opdata = new Array(256);\n\n // Set all to invalid instruction (to detect crashes):\n for (var i = 0; i < 256; i++) this.opdata[i] = 0xff;\n\n // Now fill in all valid opcodes:\n\n // ADC:\n this.setOp(this.INS_ADC, 0x69, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_ADC, 0x65, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_ADC, 0x75, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_ADC, 0x6d, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_ADC, 0x7d, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_ADC, 0x79, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_ADC, 0x61, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_ADC, 0x71, this.ADDR_POSTIDXIND, 2, 5);\n\n // AND:\n this.setOp(this.INS_AND, 0x29, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_AND, 0x25, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_AND, 0x35, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_AND, 0x2d, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_AND, 0x3d, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_AND, 0x39, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_AND, 0x21, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_AND, 0x31, this.ADDR_POSTIDXIND, 2, 5);\n\n // ASL:\n this.setOp(this.INS_ASL, 0x0a, this.ADDR_ACC, 1, 2);\n this.setOp(this.INS_ASL, 0x06, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_ASL, 0x16, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_ASL, 0x0e, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_ASL, 0x1e, this.ADDR_ABSX, 3, 7);\n\n // BCC:\n this.setOp(this.INS_BCC, 0x90, this.ADDR_REL, 2, 2);\n\n // BCS:\n this.setOp(this.INS_BCS, 0xb0, this.ADDR_REL, 2, 2);\n\n // BEQ:\n this.setOp(this.INS_BEQ, 0xf0, this.ADDR_REL, 2, 2);\n\n // BIT:\n this.setOp(this.INS_BIT, 0x24, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_BIT, 0x2c, this.ADDR_ABS, 3, 4);\n\n // BMI:\n this.setOp(this.INS_BMI, 0x30, this.ADDR_REL, 2, 2);\n\n // BNE:\n this.setOp(this.INS_BNE, 0xd0, this.ADDR_REL, 2, 2);\n\n // BPL:\n this.setOp(this.INS_BPL, 0x10, this.ADDR_REL, 2, 2);\n\n // BRK:\n this.setOp(this.INS_BRK, 0x00, this.ADDR_IMP, 1, 7);\n\n // BVC:\n this.setOp(this.INS_BVC, 0x50, this.ADDR_REL, 2, 2);\n\n // BVS:\n this.setOp(this.INS_BVS, 0x70, this.ADDR_REL, 2, 2);\n\n // CLC:\n this.setOp(this.INS_CLC, 0x18, this.ADDR_IMP, 1, 2);\n\n // CLD:\n this.setOp(this.INS_CLD, 0xd8, this.ADDR_IMP, 1, 2);\n\n // CLI:\n this.setOp(this.INS_CLI, 0x58, this.ADDR_IMP, 1, 2);\n\n // CLV:\n this.setOp(this.INS_CLV, 0xb8, this.ADDR_IMP, 1, 2);\n\n // CMP:\n this.setOp(this.INS_CMP, 0xc9, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_CMP, 0xc5, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_CMP, 0xd5, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_CMP, 0xcd, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_CMP, 0xdd, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_CMP, 0xd9, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_CMP, 0xc1, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_CMP, 0xd1, this.ADDR_POSTIDXIND, 2, 5);\n\n // CPX:\n this.setOp(this.INS_CPX, 0xe0, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_CPX, 0xe4, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_CPX, 0xec, this.ADDR_ABS, 3, 4);\n\n // CPY:\n this.setOp(this.INS_CPY, 0xc0, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_CPY, 0xc4, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_CPY, 0xcc, this.ADDR_ABS, 3, 4);\n\n // DEC:\n this.setOp(this.INS_DEC, 0xc6, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_DEC, 0xd6, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_DEC, 0xce, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_DEC, 0xde, this.ADDR_ABSX, 3, 7);\n\n // DEX:\n this.setOp(this.INS_DEX, 0xca, this.ADDR_IMP, 1, 2);\n\n // DEY:\n this.setOp(this.INS_DEY, 0x88, this.ADDR_IMP, 1, 2);\n\n // EOR:\n this.setOp(this.INS_EOR, 0x49, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_EOR, 0x45, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_EOR, 0x55, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_EOR, 0x4d, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_EOR, 0x5d, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_EOR, 0x59, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_EOR, 0x41, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_EOR, 0x51, this.ADDR_POSTIDXIND, 2, 5);\n\n // INC:\n this.setOp(this.INS_INC, 0xe6, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_INC, 0xf6, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_INC, 0xee, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_INC, 0xfe, this.ADDR_ABSX, 3, 7);\n\n // INX:\n this.setOp(this.INS_INX, 0xe8, this.ADDR_IMP, 1, 2);\n\n // INY:\n this.setOp(this.INS_INY, 0xc8, this.ADDR_IMP, 1, 2);\n\n // JMP:\n this.setOp(this.INS_JMP, 0x4c, this.ADDR_ABS, 3, 3);\n this.setOp(this.INS_JMP, 0x6c, this.ADDR_INDABS, 3, 5);\n\n // JSR:\n this.setOp(this.INS_JSR, 0x20, this.ADDR_ABS, 3, 6);\n\n // LDA:\n this.setOp(this.INS_LDA, 0xa9, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_LDA, 0xa5, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_LDA, 0xb5, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_LDA, 0xad, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_LDA, 0xbd, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_LDA, 0xb9, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_LDA, 0xa1, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_LDA, 0xb1, this.ADDR_POSTIDXIND, 2, 5);\n\n // LDX:\n this.setOp(this.INS_LDX, 0xa2, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_LDX, 0xa6, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_LDX, 0xb6, this.ADDR_ZPY, 2, 4);\n this.setOp(this.INS_LDX, 0xae, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_LDX, 0xbe, this.ADDR_ABSY, 3, 4);\n\n // LDY:\n this.setOp(this.INS_LDY, 0xa0, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_LDY, 0xa4, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_LDY, 0xb4, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_LDY, 0xac, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_LDY, 0xbc, this.ADDR_ABSX, 3, 4);\n\n // LSR:\n this.setOp(this.INS_LSR, 0x4a, this.ADDR_ACC, 1, 2);\n this.setOp(this.INS_LSR, 0x46, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_LSR, 0x56, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_LSR, 0x4e, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_LSR, 0x5e, this.ADDR_ABSX, 3, 7);\n\n // NOP:\n this.setOp(this.INS_NOP, 0x1a, this.ADDR_IMP, 1, 2);\n this.setOp(this.INS_NOP, 0x3a, this.ADDR_IMP, 1, 2);\n this.setOp(this.INS_NOP, 0x5a, this.ADDR_IMP, 1, 2);\n this.setOp(this.INS_NOP, 0x7a, this.ADDR_IMP, 1, 2);\n this.setOp(this.INS_NOP, 0xda, this.ADDR_IMP, 1, 2);\n this.setOp(this.INS_NOP, 0xea, this.ADDR_IMP, 1, 2);\n this.setOp(this.INS_NOP, 0xfa, this.ADDR_IMP, 1, 2);\n\n // ORA:\n this.setOp(this.INS_ORA, 0x09, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_ORA, 0x05, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_ORA, 0x15, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_ORA, 0x0d, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_ORA, 0x1d, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_ORA, 0x19, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_ORA, 0x01, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_ORA, 0x11, this.ADDR_POSTIDXIND, 2, 5);\n\n // PHA:\n this.setOp(this.INS_PHA, 0x48, this.ADDR_IMP, 1, 3);\n\n // PHP:\n this.setOp(this.INS_PHP, 0x08, this.ADDR_IMP, 1, 3);\n\n // PLA:\n this.setOp(this.INS_PLA, 0x68, this.ADDR_IMP, 1, 4);\n\n // PLP:\n this.setOp(this.INS_PLP, 0x28, this.ADDR_IMP, 1, 4);\n\n // ROL:\n this.setOp(this.INS_ROL, 0x2a, this.ADDR_ACC, 1, 2);\n this.setOp(this.INS_ROL, 0x26, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_ROL, 0x36, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_ROL, 0x2e, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_ROL, 0x3e, this.ADDR_ABSX, 3, 7);\n\n // ROR:\n this.setOp(this.INS_ROR, 0x6a, this.ADDR_ACC, 1, 2);\n this.setOp(this.INS_ROR, 0x66, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_ROR, 0x76, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_ROR, 0x6e, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_ROR, 0x7e, this.ADDR_ABSX, 3, 7);\n\n // RTI:\n this.setOp(this.INS_RTI, 0x40, this.ADDR_IMP, 1, 6);\n\n // RTS:\n this.setOp(this.INS_RTS, 0x60, this.ADDR_IMP, 1, 6);\n\n // SBC:\n this.setOp(this.INS_SBC, 0xe9, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_SBC, 0xe5, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_SBC, 0xf5, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_SBC, 0xed, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_SBC, 0xfd, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_SBC, 0xf9, this.ADDR_ABSY, 3, 4);\n this.setOp(this.INS_SBC, 0xe1, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_SBC, 0xf1, this.ADDR_POSTIDXIND, 2, 5);\n\n // SEC:\n this.setOp(this.INS_SEC, 0x38, this.ADDR_IMP, 1, 2);\n\n // SED:\n this.setOp(this.INS_SED, 0xf8, this.ADDR_IMP, 1, 2);\n\n // SEI:\n this.setOp(this.INS_SEI, 0x78, this.ADDR_IMP, 1, 2);\n\n // STA:\n this.setOp(this.INS_STA, 0x85, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_STA, 0x95, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_STA, 0x8d, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_STA, 0x9d, this.ADDR_ABSX, 3, 5);\n this.setOp(this.INS_STA, 0x99, this.ADDR_ABSY, 3, 5);\n this.setOp(this.INS_STA, 0x81, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_STA, 0x91, this.ADDR_POSTIDXIND, 2, 6);\n\n // STX:\n this.setOp(this.INS_STX, 0x86, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_STX, 0x96, this.ADDR_ZPY, 2, 4);\n this.setOp(this.INS_STX, 0x8e, this.ADDR_ABS, 3, 4);\n\n // STY:\n this.setOp(this.INS_STY, 0x84, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_STY, 0x94, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_STY, 0x8c, this.ADDR_ABS, 3, 4);\n\n // TAX:\n this.setOp(this.INS_TAX, 0xaa, this.ADDR_IMP, 1, 2);\n\n // TAY:\n this.setOp(this.INS_TAY, 0xa8, this.ADDR_IMP, 1, 2);\n\n // TSX:\n this.setOp(this.INS_TSX, 0xba, this.ADDR_IMP, 1, 2);\n\n // TXA:\n this.setOp(this.INS_TXA, 0x8a, this.ADDR_IMP, 1, 2);\n\n // TXS:\n this.setOp(this.INS_TXS, 0x9a, this.ADDR_IMP, 1, 2);\n\n // TYA:\n this.setOp(this.INS_TYA, 0x98, this.ADDR_IMP, 1, 2);\n\n // ALR:\n this.setOp(this.INS_ALR, 0x4b, this.ADDR_IMM, 2, 2);\n\n // ANC:\n this.setOp(this.INS_ANC, 0x0b, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_ANC, 0x2b, this.ADDR_IMM, 2, 2);\n\n // ARR:\n this.setOp(this.INS_ARR, 0x6b, this.ADDR_IMM, 2, 2);\n\n // AXS:\n this.setOp(this.INS_AXS, 0xcb, this.ADDR_IMM, 2, 2);\n\n // LAX:\n this.setOp(this.INS_LAX, 0xa3, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_LAX, 0xa7, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_LAX, 0xaf, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_LAX, 0xb3, this.ADDR_POSTIDXIND, 2, 5);\n this.setOp(this.INS_LAX, 0xb7, this.ADDR_ZPY, 2, 4);\n this.setOp(this.INS_LAX, 0xbf, this.ADDR_ABSY, 3, 4);\n\n // SAX:\n this.setOp(this.INS_SAX, 0x83, this.ADDR_PREIDXIND, 2, 6);\n this.setOp(this.INS_SAX, 0x87, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_SAX, 0x8f, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_SAX, 0x97, this.ADDR_ZPY, 2, 4);\n\n // DCP:\n this.setOp(this.INS_DCP, 0xc3, this.ADDR_PREIDXIND, 2, 8);\n this.setOp(this.INS_DCP, 0xc7, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_DCP, 0xcf, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_DCP, 0xd3, this.ADDR_POSTIDXIND, 2, 8);\n this.setOp(this.INS_DCP, 0xd7, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_DCP, 0xdb, this.ADDR_ABSY, 3, 7);\n this.setOp(this.INS_DCP, 0xdf, this.ADDR_ABSX, 3, 7);\n\n // ISC:\n this.setOp(this.INS_ISC, 0xe3, this.ADDR_PREIDXIND, 2, 8);\n this.setOp(this.INS_ISC, 0xe7, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_ISC, 0xef, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_ISC, 0xf3, this.ADDR_POSTIDXIND, 2, 8);\n this.setOp(this.INS_ISC, 0xf7, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_ISC, 0xfb, this.ADDR_ABSY, 3, 7);\n this.setOp(this.INS_ISC, 0xff, this.ADDR_ABSX, 3, 7);\n\n // RLA:\n this.setOp(this.INS_RLA, 0x23, this.ADDR_PREIDXIND, 2, 8);\n this.setOp(this.INS_RLA, 0x27, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_RLA, 0x2f, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_RLA, 0x33, this.ADDR_POSTIDXIND, 2, 8);\n this.setOp(this.INS_RLA, 0x37, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_RLA, 0x3b, this.ADDR_ABSY, 3, 7);\n this.setOp(this.INS_RLA, 0x3f, this.ADDR_ABSX, 3, 7);\n\n // RRA:\n this.setOp(this.INS_RRA, 0x63, this.ADDR_PREIDXIND, 2, 8);\n this.setOp(this.INS_RRA, 0x67, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_RRA, 0x6f, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_RRA, 0x73, this.ADDR_POSTIDXIND, 2, 8);\n this.setOp(this.INS_RRA, 0x77, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_RRA, 0x7b, this.ADDR_ABSY, 3, 7);\n this.setOp(this.INS_RRA, 0x7f, this.ADDR_ABSX, 3, 7);\n\n // SLO:\n this.setOp(this.INS_SLO, 0x03, this.ADDR_PREIDXIND, 2, 8);\n this.setOp(this.INS_SLO, 0x07, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_SLO, 0x0f, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_SLO, 0x13, this.ADDR_POSTIDXIND, 2, 8);\n this.setOp(this.INS_SLO, 0x17, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_SLO, 0x1b, this.ADDR_ABSY, 3, 7);\n this.setOp(this.INS_SLO, 0x1f, this.ADDR_ABSX, 3, 7);\n\n // SRE:\n this.setOp(this.INS_SRE, 0x43, this.ADDR_PREIDXIND, 2, 8);\n this.setOp(this.INS_SRE, 0x47, this.ADDR_ZP, 2, 5);\n this.setOp(this.INS_SRE, 0x4f, this.ADDR_ABS, 3, 6);\n this.setOp(this.INS_SRE, 0x53, this.ADDR_POSTIDXIND, 2, 8);\n this.setOp(this.INS_SRE, 0x57, this.ADDR_ZPX, 2, 6);\n this.setOp(this.INS_SRE, 0x5b, this.ADDR_ABSY, 3, 7);\n this.setOp(this.INS_SRE, 0x5f, this.ADDR_ABSX, 3, 7);\n\n // SKB:\n this.setOp(this.INS_SKB, 0x80, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_SKB, 0x82, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_SKB, 0x89, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_SKB, 0xc2, this.ADDR_IMM, 2, 2);\n this.setOp(this.INS_SKB, 0xe2, this.ADDR_IMM, 2, 2);\n\n // SKB:\n this.setOp(this.INS_IGN, 0x0c, this.ADDR_ABS, 3, 4);\n this.setOp(this.INS_IGN, 0x1c, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_IGN, 0x3c, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_IGN, 0x5c, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_IGN, 0x7c, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_IGN, 0xdc, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_IGN, 0xfc, this.ADDR_ABSX, 3, 4);\n this.setOp(this.INS_IGN, 0x04, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_IGN, 0x44, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_IGN, 0x64, this.ADDR_ZP, 2, 3);\n this.setOp(this.INS_IGN, 0x14, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_IGN, 0x34, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_IGN, 0x54, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_IGN, 0x74, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_IGN, 0xd4, this.ADDR_ZPX, 2, 4);\n this.setOp(this.INS_IGN, 0xf4, this.ADDR_ZPX, 2, 4);\n\n // prettier-ignore\n this.cycTable = new Array(\n /*0x00*/ 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,\n /*0x10*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,\n /*0x20*/ 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,\n /*0x30*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,\n /*0x40*/ 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,\n /*0x50*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,\n /*0x60*/ 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,\n /*0x70*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,\n /*0x80*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,\n /*0x90*/ 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,\n /*0xA0*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,\n /*0xB0*/ 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,\n /*0xC0*/ 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,\n /*0xD0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,\n /*0xE0*/ 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6,\n /*0xF0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7\n );\n\n this.instname = new Array(70);\n\n // Instruction Names:\n this.instname[0] = \"ADC\";\n this.instname[1] = \"AND\";\n this.instname[2] = \"ASL\";\n this.instname[3] = \"BCC\";\n this.instname[4] = \"BCS\";\n this.instname[5] = \"BEQ\";\n this.instname[6] = \"BIT\";\n this.instname[7] = \"BMI\";\n this.instname[8] = \"BNE\";\n this.instname[9] = \"BPL\";\n this.instname[10] = \"BRK\";\n this.instname[11] = \"BVC\";\n this.instname[12] = \"BVS\";\n this.instname[13] = \"CLC\";\n this.instname[14] = \"CLD\";\n this.instname[15] = \"CLI\";\n this.instname[16] = \"CLV\";\n this.instname[17] = \"CMP\";\n this.instname[18] = \"CPX\";\n this.instname[19] = \"CPY\";\n this.instname[20] = \"DEC\";\n this.instname[21] = \"DEX\";\n this.instname[22] = \"DEY\";\n this.instname[23] = \"EOR\";\n this.instname[24] = \"INC\";\n this.instname[25] = \"INX\";\n this.instname[26] = \"INY\";\n this.instname[27] = \"JMP\";\n this.instname[28] = \"JSR\";\n this.instname[29] = \"LDA\";\n this.instname[30] = \"LDX\";\n this.instname[31] = \"LDY\";\n this.instname[32] = \"LSR\";\n this.instname[33] = \"NOP\";\n this.instname[34] = \"ORA\";\n this.instname[35] = \"PHA\";\n this.instname[36] = \"PHP\";\n this.instname[37] = \"PLA\";\n this.instname[38] = \"PLP\";\n this.instname[39] = \"ROL\";\n this.instname[40] = \"ROR\";\n this.instname[41] = \"RTI\";\n this.instname[42] = \"RTS\";\n this.instname[43] = \"SBC\";\n this.instname[44] = \"SEC\";\n this.instname[45] = \"SED\";\n this.instname[46] = \"SEI\";\n this.instname[47] = \"STA\";\n this.instname[48] = \"STX\";\n this.instname[49] = \"STY\";\n this.instname[50] = \"TAX\";\n this.instname[51] = \"TAY\";\n this.instname[52] = \"TSX\";\n this.instname[53] = \"TXA\";\n this.instname[54] = \"TXS\";\n this.instname[55] = \"TYA\";\n this.instname[56] = \"ALR\";\n this.instname[57] = \"ANC\";\n this.instname[58] = \"ARR\";\n this.instname[59] = \"AXS\";\n this.instname[60] = \"LAX\";\n this.instname[61] = \"SAX\";\n this.instname[62] = \"DCP\";\n this.instname[63] = \"ISC\";\n this.instname[64] = \"RLA\";\n this.instname[65] = \"RRA\";\n this.instname[66] = \"SLO\";\n this.instname[67] = \"SRE\";\n this.instname[68] = \"SKB\";\n this.instname[69] = \"IGN\";\n\n this.addrDesc = new Array(\n \"Zero Page \",\n \"Relative \",\n \"Implied \",\n \"Absolute \",\n \"Accumulator \",\n \"Immediate \",\n \"Zero Page,X \",\n \"Zero Page,Y \",\n \"Absolute,X \",\n \"Absolute,Y \",\n \"Preindexed Indirect \",\n \"Postindexed Indirect\",\n \"Indirect Absolute \"\n );\n};\n\nOpData.prototype = {\n INS_ADC: 0,\n INS_AND: 1,\n INS_ASL: 2,\n\n INS_BCC: 3,\n INS_BCS: 4,\n INS_BEQ: 5,\n INS_BIT: 6,\n INS_BMI: 7,\n INS_BNE: 8,\n INS_BPL: 9,\n INS_BRK: 10,\n INS_BVC: 11,\n INS_BVS: 12,\n\n INS_CLC: 13,\n INS_CLD: 14,\n INS_CLI: 15,\n INS_CLV: 16,\n INS_CMP: 17,\n INS_CPX: 18,\n INS_CPY: 19,\n\n INS_DEC: 20,\n INS_DEX: 21,\n INS_DEY: 22,\n\n INS_EOR: 23,\n\n INS_INC: 24,\n INS_INX: 25,\n INS_INY: 26,\n\n INS_JMP: 27,\n INS_JSR: 28,\n\n INS_LDA: 29,\n INS_LDX: 30,\n INS_LDY: 31,\n INS_LSR: 32,\n\n INS_NOP: 33,\n\n INS_ORA: 34,\n\n INS_PHA: 35,\n INS_PHP: 36,\n INS_PLA: 37,\n INS_PLP: 38,\n\n INS_ROL: 39,\n INS_ROR: 40,\n INS_RTI: 41,\n INS_RTS: 42,\n\n INS_SBC: 43,\n INS_SEC: 44,\n INS_SED: 45,\n INS_SEI: 46,\n INS_STA: 47,\n INS_STX: 48,\n INS_STY: 49,\n\n INS_TAX: 50,\n INS_TAY: 51,\n INS_TSX: 52,\n INS_TXA: 53,\n INS_TXS: 54,\n INS_TYA: 55,\n\n INS_ALR: 56,\n INS_ANC: 57,\n INS_ARR: 58,\n INS_AXS: 59,\n INS_LAX: 60,\n INS_SAX: 61,\n INS_DCP: 62,\n INS_ISC: 63,\n INS_RLA: 64,\n INS_RRA: 65,\n INS_SLO: 66,\n INS_SRE: 67,\n INS_SKB: 68,\n INS_IGN: 69,\n\n INS_DUMMY: 70, // dummy instruction used for 'halting' the processor some cycles\n\n // -------------------------------- //\n\n // Addressing modes:\n ADDR_ZP: 0,\n ADDR_REL: 1,\n ADDR_IMP: 2,\n ADDR_ABS: 3,\n ADDR_ACC: 4,\n ADDR_IMM: 5,\n ADDR_ZPX: 6,\n ADDR_ZPY: 7,\n ADDR_ABSX: 8,\n ADDR_ABSY: 9,\n ADDR_PREIDXIND: 10,\n ADDR_POSTIDXIND: 11,\n ADDR_INDABS: 12,\n\n setOp: function(inst, op, addr, size, cycles) {\n this.opdata[op] =\n (inst & 0xff) |\n ((addr & 0xff) << 8) |\n ((size & 0xff) << 16) |\n ((cycles & 0xff) << 24);\n }\n};\n\nmodule.exports = CPU;\n", "var Tile = function() {\n // Tile data:\n this.pix = new Uint8Array(64);\n\n this.fbIndex = null;\n this.tIndex = null;\n this.x = null;\n this.y = null;\n this.w = null;\n this.h = null;\n this.incX = null;\n this.incY = null;\n this.palIndex = null;\n this.tpri = null;\n this.c = null;\n this.initialized = false;\n this.opaque = new Array(8);\n};\n\nTile.prototype = {\n setBuffer: function(scanline) {\n for (this.y = 0; this.y < 8; this.y++) {\n this.setScanline(this.y, scanline[this.y], scanline[this.y + 8]);\n }\n },\n\n setScanline: function(sline, b1, b2) {\n this.initialized = true;\n this.tIndex = sline << 3;\n for (this.x = 0; this.x < 8; this.x++) {\n this.pix[this.tIndex + this.x] =\n ((b1 >> (7 - this.x)) & 1) + (((b2 >> (7 - this.x)) & 1) << 1);\n if (this.pix[this.tIndex + this.x] === 0) {\n this.opaque[sline] = false;\n }\n }\n },\n\n render: function(\n buffer,\n srcx1,\n srcy1,\n srcx2,\n srcy2,\n dx,\n dy,\n palAdd,\n palette,\n flipHorizontal,\n flipVertical,\n pri,\n priTable\n ) {\n if (dx < -7 || dx >= 256 || dy < -7 || dy >= 240) {\n return;\n }\n\n this.w = srcx2 - srcx1;\n this.h = srcy2 - srcy1;\n\n if (dx < 0) {\n srcx1 -= dx;\n }\n if (dx + srcx2 >= 256) {\n srcx2 = 256 - dx;\n }\n\n if (dy < 0) {\n srcy1 -= dy;\n }\n if (dy + srcy2 >= 240) {\n srcy2 = 240 - dy;\n }\n\n if (!flipHorizontal && !flipVertical) {\n this.fbIndex = (dy << 8) + dx;\n this.tIndex = 0;\n for (this.y = 0; this.y < 8; this.y++) {\n for (this.x = 0; this.x < 8; this.x++) {\n if (\n this.x >= srcx1 &&\n this.x < srcx2 &&\n this.y >= srcy1 &&\n this.y < srcy2\n ) {\n this.palIndex = this.pix[this.tIndex];\n this.tpri = priTable[this.fbIndex];\n if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {\n //console.log(\"Rendering upright tile to buffer\");\n buffer[this.fbIndex] = palette[this.palIndex + palAdd];\n this.tpri = (this.tpri & 0xf00) | pri;\n priTable[this.fbIndex] = this.tpri;\n }\n }\n this.fbIndex++;\n this.tIndex++;\n }\n this.fbIndex -= 8;\n this.fbIndex += 256;\n }\n } else if (flipHorizontal && !flipVertical) {\n this.fbIndex = (dy << 8) + dx;\n this.tIndex = 7;\n for (this.y = 0; this.y < 8; this.y++) {\n for (this.x = 0; this.x < 8; this.x++) {\n if (\n this.x >= srcx1 &&\n this.x < srcx2 &&\n this.y >= srcy1 &&\n this.y < srcy2\n ) {\n this.palIndex = this.pix[this.tIndex];\n this.tpri = priTable[this.fbIndex];\n if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {\n buffer[this.fbIndex] = palette[this.palIndex + palAdd];\n this.tpri = (this.tpri & 0xf00) | pri;\n priTable[this.fbIndex] = this.tpri;\n }\n }\n this.fbIndex++;\n this.tIndex--;\n }\n this.fbIndex -= 8;\n this.fbIndex += 256;\n this.tIndex += 16;\n }\n } else if (flipVertical && !flipHorizontal) {\n this.fbIndex = (dy << 8) + dx;\n this.tIndex = 56;\n for (this.y = 0; this.y < 8; this.y++) {\n for (this.x = 0; this.x < 8; this.x++) {\n if (\n this.x >= srcx1 &&\n this.x < srcx2 &&\n this.y >= srcy1 &&\n this.y < srcy2\n ) {\n this.palIndex = this.pix[this.tIndex];\n this.tpri = priTable[this.fbIndex];\n if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {\n buffer[this.fbIndex] = palette[this.palIndex + palAdd];\n this.tpri = (this.tpri & 0xf00) | pri;\n priTable[this.fbIndex] = this.tpri;\n }\n }\n this.fbIndex++;\n this.tIndex++;\n }\n this.fbIndex -= 8;\n this.fbIndex += 256;\n this.tIndex -= 16;\n }\n } else {\n this.fbIndex = (dy << 8) + dx;\n this.tIndex = 63;\n for (this.y = 0; this.y < 8; this.y++) {\n for (this.x = 0; this.x < 8; this.x++) {\n if (\n this.x >= srcx1 &&\n this.x < srcx2 &&\n this.y >= srcy1 &&\n this.y < srcy2\n ) {\n this.palIndex = this.pix[this.tIndex];\n this.tpri = priTable[this.fbIndex];\n if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {\n buffer[this.fbIndex] = palette[this.palIndex + palAdd];\n this.tpri = (this.tpri & 0xf00) | pri;\n priTable[this.fbIndex] = this.tpri;\n }\n }\n this.fbIndex++;\n this.tIndex--;\n }\n this.fbIndex -= 8;\n this.fbIndex += 256;\n }\n }\n },\n\n isTransparent: function(x, y) {\n return this.pix[(y << 3) + x] === 0;\n },\n\n toJSON: function() {\n return {\n opaque: this.opaque,\n pix: this.pix\n };\n },\n\n fromJSON: function(s) {\n this.opaque = s.opaque;\n this.pix = s.pix;\n }\n};\n\nmodule.exports = Tile;\n", "var Tile = require(\"./tile\");\nvar utils = require(\"./utils\");\n\nvar PPU = function(nes) {\n this.nes = nes;\n\n // Keep Chrome happy\n this.vramMem = null;\n this.spriteMem = null;\n this.vramAddress = null;\n this.vramTmpAddress = null;\n this.vramBufferedReadValue = null;\n this.firstWrite = null;\n this.sramAddress = null;\n this.currentMirroring = null;\n this.requestEndFrame = null;\n this.nmiOk = null;\n this.dummyCycleToggle = null;\n this.validTileData = null;\n this.nmiCounter = null;\n this.scanlineAlreadyRendered = null;\n this.f_nmiOnVblank = null;\n this.f_spriteSize = null;\n this.f_bgPatternTable = null;\n this.f_spPatternTable = null;\n this.f_addrInc = null;\n this.f_nTblAddress = null;\n this.f_color = null;\n this.f_spVisibility = null;\n this.f_bgVisibility = null;\n this.f_spClipping = null;\n this.f_bgClipping = null;\n this.f_dispType = null;\n this.cntFV = null;\n this.cntV = null;\n this.cntH = null;\n this.cntVT = null;\n this.cntHT = null;\n this.regFV = null;\n this.regV = null;\n this.regH = null;\n this.regVT = null;\n this.regHT = null;\n this.regFH = null;\n this.regS = null;\n this.curNt = null;\n this.attrib = null;\n this.buffer = null;\n this.bgbuffer = null;\n this.pixrendered = null;\n\n this.validTileData = null;\n this.scantile = null;\n this.scanline = null;\n this.lastRenderedScanline = null;\n this.curX = null;\n this.sprX = null;\n this.sprY = null;\n this.sprTile = null;\n this.sprCol = null;\n this.vertFlip = null;\n this.horiFlip = null;\n this.bgPriority = null;\n this.spr0HitX = null;\n this.spr0HitY = null;\n this.hitSpr0 = null;\n this.sprPalette = null;\n this.imgPalette = null;\n this.ptTile = null;\n this.ntable1 = null;\n this.currentMirroring = null;\n this.nameTable = null;\n this.vramMirrorTable = null;\n this.palTable = null;\n\n // Rendering Options:\n this.showSpr0Hit = false;\n this.clipToTvSize = true;\n\n this.reset();\n};\n\nPPU.prototype = {\n // Status flags:\n STATUS_VRAMWRITE: 4,\n STATUS_SLSPRITECOUNT: 5,\n STATUS_SPRITE0HIT: 6,\n STATUS_VBLANK: 7,\n\n reset: function() {\n var i;\n\n // Memory\n this.vramMem = new Uint8Array(0x8000);\n this.spriteMem = new Uint8Array(0x100);\n for (i = 0; i < this.vramMem.length; i++) {\n this.vramMem[i] = 0;\n }\n for (i = 0; i < this.spriteMem.length; i++) {\n this.spriteMem[i] = 0;\n }\n\n // VRAM I/O:\n this.vramAddress = null;\n this.vramTmpAddress = null;\n this.vramBufferedReadValue = 0;\n this.firstWrite = true; // VRAM/Scroll Hi/Lo latch\n\n // SPR-RAM I/O:\n this.sramAddress = 0; // 8-bit only.\n\n this.currentMirroring = -1;\n this.requestEndFrame = false;\n this.nmiOk = false;\n this.dummyCycleToggle = false;\n this.validTileData = false;\n this.nmiCounter = 0;\n this.scanlineAlreadyRendered = null;\n\n // Control Flags Register 1:\n this.f_nmiOnVblank = 0; // NMI on VBlank. 0=disable, 1=enable\n this.f_spriteSize = 0; // Sprite size. 0=8x8, 1=8x16\n this.f_bgPatternTable = 0; // Background Pattern Table address. 0=0x0000,1=0x1000\n this.f_spPatternTable = 0; // Sprite Pattern Table address. 0=0x0000,1=0x1000\n this.f_addrInc = 0; // PPU Address Increment. 0=1,1=32\n this.f_nTblAddress = 0; // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00\n\n // Control Flags Register 2:\n this.f_color = 0; // Background color. 0=black, 1=blue, 2=green, 4=red\n this.f_spVisibility = 0; // Sprite visibility. 0=not displayed,1=displayed\n this.f_bgVisibility = 0; // Background visibility. 0=Not Displayed,1=displayed\n this.f_spClipping = 0; // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping\n this.f_bgClipping = 0; // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping\n this.f_dispType = 0; // Display type. 0=color, 1=monochrome\n\n // Counters:\n this.cntFV = 0;\n this.cntV = 0;\n this.cntH = 0;\n this.cntVT = 0;\n this.cntHT = 0;\n\n // Registers:\n this.regFV = 0;\n this.regV = 0;\n this.regH = 0;\n this.regVT = 0;\n this.regHT = 0;\n this.regFH = 0;\n this.regS = 0;\n\n // These are temporary variables used in rendering and sound procedures.\n // Their states outside of those procedures can be ignored.\n // TODO: the use of this is a bit weird, investigate\n this.curNt = null;\n\n // Variables used when rendering:\n this.attrib = new Array(32);\n this.buffer = new Uint32Array(256 * 240);\n this.bgbuffer = new Uint32Array(256 * 240);\n this.pixrendered = new Uint32Array(256 * 240);\n\n this.validTileData = null;\n\n this.scantile = new Array(32);\n\n // Initialize misc vars:\n this.scanline = 0;\n this.lastRenderedScanline = -1;\n this.curX = 0;\n\n // Sprite data:\n this.sprX = new Array(64); // X coordinate\n this.sprY = new Array(64); // Y coordinate\n this.sprTile = new Array(64); // Tile Index (into pattern table)\n this.sprCol = new Array(64); // Upper two bits of color\n this.vertFlip = new Array(64); // Vertical Flip\n this.horiFlip = new Array(64); // Horizontal Flip\n this.bgPriority = new Array(64); // Background priority\n this.spr0HitX = 0; // Sprite #0 hit X coordinate\n this.spr0HitY = 0; // Sprite #0 hit Y coordinate\n this.hitSpr0 = false;\n\n // Palette data:\n this.sprPalette = new Uint32Array(16);\n this.imgPalette = new Uint32Array(16);\n\n // Create pattern table tile buffers:\n this.ptTile = new Array(512);\n for (i = 0; i < 512; i++) {\n this.ptTile[i] = new Tile();\n }\n\n // Create nametable buffers:\n // Name table data:\n this.ntable1 = new Array(4);\n this.currentMirroring = -1;\n this.nameTable = new Array(4);\n for (i = 0; i < 4; i++) {\n this.nameTable[i] = new NameTable(32, 32, \"Nt\" + i);\n }\n\n // Initialize mirroring lookup table:\n this.vramMirrorTable = new Uint16Array(0x8000);\n for (i = 0; i < 0x8000; i++) {\n this.vramMirrorTable[i] = i;\n }\n\n this.palTable = new PaletteTable();\n this.palTable.loadNTSCPalette();\n //this.palTable.loadDefaultPalette();\n\n this.updateControlReg1(0);\n this.updateControlReg2(0);\n },\n\n // Sets Nametable mirroring.\n setMirroring: function(mirroring) {\n if (mirroring === this.currentMirroring) {\n return;\n }\n\n this.currentMirroring = mirroring;\n this.triggerRendering();\n\n // Remove mirroring:\n if (this.vramMirrorTable === null) {\n this.vramMirrorTable = new Uint16Array(0x8000);\n }\n for (var i = 0; i < 0x8000; i++) {\n this.vramMirrorTable[i] = i;\n }\n\n // Palette mirroring:\n this.defineMirrorRegion(0x3f20, 0x3f00, 0x20);\n this.defineMirrorRegion(0x3f40, 0x3f00, 0x20);\n this.defineMirrorRegion(0x3f80, 0x3f00, 0x20);\n this.defineMirrorRegion(0x3fc0, 0x3f00, 0x20);\n\n // Additional mirroring:\n this.defineMirrorRegion(0x3000, 0x2000, 0xf00);\n this.defineMirrorRegion(0x4000, 0x0000, 0x4000);\n\n if (mirroring === this.nes.rom.HORIZONTAL_MIRRORING) {\n // Horizontal mirroring.\n\n this.ntable1[0] = 0;\n this.ntable1[1] = 0;\n this.ntable1[2] = 1;\n this.ntable1[3] = 1;\n\n this.defineMirrorRegion(0x2400, 0x2000, 0x400);\n this.defineMirrorRegion(0x2c00, 0x2800, 0x400);\n } else if (mirroring === this.nes.rom.VERTICAL_MIRRORING) {\n // Vertical mirroring.\n\n this.ntable1[0] = 0;\n this.ntable1[1] = 1;\n this.ntable1[2] = 0;\n this.ntable1[3] = 1;\n\n this.defineMirrorRegion(0x2800, 0x2000, 0x400);\n this.defineMirrorRegion(0x2c00, 0x2400, 0x400);\n } else if (mirroring === this.nes.rom.SINGLESCREEN_MIRRORING) {\n // Single Screen mirroring\n\n this.ntable1[0] = 0;\n this.ntable1[1] = 0;\n this.ntable1[2] = 0;\n this.ntable1[3] = 0;\n\n this.defineMirrorRegion(0x2400, 0x2000, 0x400);\n this.defineMirrorRegion(0x2800, 0x2000, 0x400);\n this.defineMirrorRegion(0x2c00, 0x2000, 0x400);\n } else if (mirroring === this.nes.rom.SINGLESCREEN_MIRRORING2) {\n this.ntable1[0] = 1;\n this.ntable1[1] = 1;\n this.ntable1[2] = 1;\n this.ntable1[3] = 1;\n\n this.defineMirrorRegion(0x2400, 0x2400, 0x400);\n this.defineMirrorRegion(0x2800, 0x2400, 0x400);\n this.defineMirrorRegion(0x2c00, 0x2400, 0x400);\n } else {\n // Assume Four-screen mirroring.\n\n this.ntable1[0] = 0;\n this.ntable1[1] = 1;\n this.ntable1[2] = 2;\n this.ntable1[3] = 3;\n }\n },\n\n // Define a mirrored area in the address lookup table.\n // Assumes the regions don't overlap.\n // The 'to' region is the region that is physically in memory.\n defineMirrorRegion: function(fromStart, toStart, size) {\n for (var i = 0; i < size; i++) {\n this.vramMirrorTable[fromStart + i] = toStart + i;\n }\n },\n\n startVBlank: function() {\n // Do NMI:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI);\n\n // Make sure everything is rendered:\n if (this.lastRenderedScanline < 239) {\n this.renderFramePartially(\n this.lastRenderedScanline + 1,\n 240 - this.lastRenderedScanline\n );\n }\n\n // End frame:\n this.endFrame();\n\n // Reset scanline counter:\n this.lastRenderedScanline = -1;\n },\n\n endScanline: function() {\n switch (this.scanline) {\n case 19:\n // Dummy scanline.\n // May be variable length:\n if (this.dummyCycleToggle) {\n // Remove dead cycle at end of scanline,\n // for next scanline:\n this.curX = 1;\n this.dummyCycleToggle = !this.dummyCycleToggle;\n }\n break;\n\n case 20:\n // Clear VBlank flag:\n this.setStatusFlag(this.STATUS_VBLANK, false);\n\n // Clear Sprite #0 hit flag:\n this.setStatusFlag(this.STATUS_SPRITE0HIT, false);\n this.hitSpr0 = false;\n this.spr0HitX = -1;\n this.spr0HitY = -1;\n\n if (this.f_bgVisibility === 1 || this.f_spVisibility === 1) {\n // Update counters:\n this.cntFV = this.regFV;\n this.cntV = this.regV;\n this.cntH = this.regH;\n this.cntVT = this.regVT;\n this.cntHT = this.regHT;\n\n if (this.f_bgVisibility === 1) {\n // Render dummy scanline:\n this.renderBgScanline(false, 0);\n }\n }\n\n if (this.f_bgVisibility === 1 && this.f_spVisibility === 1) {\n // Check sprite 0 hit for first scanline:\n this.checkSprite0(0);\n }\n\n if (this.f_bgVisibility === 1 || this.f_spVisibility === 1) {\n // Clock mapper IRQ Counter:\n this.nes.mmap.clockIrqCounter();\n }\n break;\n\n case 261:\n // Dead scanline, no rendering.\n // Set VINT:\n this.setStatusFlag(this.STATUS_VBLANK, true);\n this.requestEndFrame = true;\n this.nmiCounter = 9;\n\n // Wrap around:\n this.scanline = -1; // will be incremented to 0\n\n break;\n\n default:\n if (this.scanline >= 21 && this.scanline <= 260) {\n // Render normally:\n if (this.f_bgVisibility === 1) {\n if (!this.scanlineAlreadyRendered) {\n // update scroll:\n this.cntHT = this.regHT;\n this.cntH = this.regH;\n this.renderBgScanline(true, this.scanline + 1 - 21);\n }\n this.scanlineAlreadyRendered = false;\n\n // Check for sprite 0 (next scanline):\n if (!this.hitSpr0 && this.f_spVisibility === 1) {\n if (\n this.sprX[0] >= -7 &&\n this.sprX[0] < 256 &&\n this.sprY[0] + 1 <= this.scanline - 20 &&\n this.sprY[0] + 1 + (this.f_spriteSize === 0 ? 8 : 16) >=\n this.scanline - 20\n ) {\n if (this.checkSprite0(this.scanline - 20)) {\n this.hitSpr0 = true;\n }\n }\n }\n }\n\n if (this.f_bgVisibility === 1 || this.f_spVisibility === 1) {\n // Clock mapper IRQ Counter:\n this.nes.mmap.clockIrqCounter();\n }\n }\n }\n\n this.scanline++;\n this.regsToAddress();\n this.cntsToAddress();\n },\n\n startFrame: function() {\n // Set background color:\n var bgColor = 0;\n\n if (this.f_dispType === 0) {\n // Color display.\n // f_color determines color emphasis.\n // Use first entry of image palette as BG color.\n bgColor = this.imgPalette[0];\n } else {\n // Monochrome display.\n // f_color determines the bg color.\n switch (this.f_color) {\n case 0:\n // Black\n bgColor = 0x00000;\n break;\n case 1:\n // Green\n bgColor = 0x00ff00;\n break;\n case 2:\n // Blue\n bgColor = 0xff0000;\n break;\n case 3:\n // Invalid. Use black.\n bgColor = 0x000000;\n break;\n case 4:\n // Red\n bgColor = 0x0000ff;\n break;\n default:\n // Invalid. Use black.\n bgColor = 0x0;\n }\n }\n\n var buffer = this.buffer;\n var i;\n for (i = 0; i < 256 * 240; i++) {\n buffer[i] = bgColor;\n }\n var pixrendered = this.pixrendered;\n for (i = 0; i < pixrendered.length; i++) {\n pixrendered[i] = 65;\n }\n },\n\n endFrame: function() {\n var i, x, y;\n var buffer = this.buffer;\n\n // Draw spr#0 hit coordinates:\n if (this.showSpr0Hit) {\n // Spr 0 position:\n if (\n this.sprX[0] >= 0 &&\n this.sprX[0] < 256 &&\n this.sprY[0] >= 0 &&\n this.sprY[0] < 240\n ) {\n for (i = 0; i < 256; i++) {\n buffer[(this.sprY[0] << 8) + i] = 0xff5555;\n }\n for (i = 0; i < 240; i++) {\n buffer[(i << 8) + this.sprX[0]] = 0xff5555;\n }\n }\n // Hit position:\n if (\n this.spr0HitX >= 0 &&\n this.spr0HitX < 256 &&\n this.spr0HitY >= 0 &&\n this.spr0HitY < 240\n ) {\n for (i = 0; i < 256; i++) {\n buffer[(this.spr0HitY << 8) + i] = 0x55ff55;\n }\n for (i = 0; i < 240; i++) {\n buffer[(i << 8) + this.spr0HitX] = 0x55ff55;\n }\n }\n }\n\n // This is a bit lazy..\n // if either the sprites or the background should be clipped,\n // both are clipped after rendering is finished.\n if (\n this.clipToTvSize ||\n this.f_bgClipping === 0 ||\n this.f_spClipping === 0\n ) {\n // Clip left 8-pixels column:\n for (y = 0; y < 240; y++) {\n for (x = 0; x < 8; x++) {\n buffer[(y << 8) + x] = 0;\n }\n }\n }\n\n if (this.clipToTvSize) {\n // Clip right 8-pixels column too:\n for (y = 0; y < 240; y++) {\n for (x = 0; x < 8; x++) {\n buffer[(y << 8) + 255 - x] = 0;\n }\n }\n }\n\n // Clip top and bottom 8 pixels:\n if (this.clipToTvSize) {\n for (y = 0; y < 8; y++) {\n for (x = 0; x < 256; x++) {\n buffer[(y << 8) + x] = 0;\n buffer[((239 - y) << 8) + x] = 0;\n }\n }\n }\n\n this.nes.ui.writeFrame(buffer);\n },\n\n updateControlReg1: function(value) {\n this.triggerRendering();\n\n this.f_nmiOnVblank = (value >> 7) & 1;\n this.f_spriteSize = (value >> 5) & 1;\n this.f_bgPatternTable = (value >> 4) & 1;\n this.f_spPatternTable = (value >> 3) & 1;\n this.f_addrInc = (value >> 2) & 1;\n this.f_nTblAddress = value & 3;\n\n this.regV = (value >> 1) & 1;\n this.regH = value & 1;\n this.regS = (value >> 4) & 1;\n },\n\n updateControlReg2: function(value) {\n this.triggerRendering();\n\n this.f_color = (value >> 5) & 7;\n this.f_spVisibility = (value >> 4) & 1;\n this.f_bgVisibility = (value >> 3) & 1;\n this.f_spClipping = (value >> 2) & 1;\n this.f_bgClipping = (value >> 1) & 1;\n this.f_dispType = value & 1;\n\n if (this.f_dispType === 0) {\n this.palTable.setEmphasis(this.f_color);\n }\n this.updatePalettes();\n },\n\n setStatusFlag: function(flag, value) {\n var n = 1 << flag;\n this.nes.cpu.mem[0x2002] =\n (this.nes.cpu.mem[0x2002] & (255 - n)) | (value ? n : 0);\n },\n\n // CPU Register $2002:\n // Read the Status Register.\n readStatusRegister: function() {\n var tmp = this.nes.cpu.mem[0x2002];\n\n // Reset scroll & VRAM Address toggle:\n this.firstWrite = true;\n\n // Clear VBlank flag:\n this.setStatusFlag(this.STATUS_VBLANK, false);\n\n // Fetch status data:\n return tmp;\n },\n\n // CPU Register $2003:\n // Write the SPR-RAM address that is used for sramWrite (Register 0x2004 in CPU memory map)\n writeSRAMAddress: function(address) {\n this.sramAddress = address;\n },\n\n // CPU Register $2004 (R):\n // Read from SPR-RAM (Sprite RAM).\n // The address should be set first.\n sramLoad: function() {\n /*short tmp = sprMem.load(sramAddress);\n sramAddress++; // Increment address\n sramAddress%=0x100;\n return tmp;*/\n return this.spriteMem[this.sramAddress];\n },\n\n // CPU Register $2004 (W):\n // Write to SPR-RAM (Sprite RAM).\n // The address should be set first.\n sramWrite: function(value) {\n this.spriteMem[this.sramAddress] = value;\n this.spriteRamWriteUpdate(this.sramAddress, value);\n this.sramAddress++; // Increment address\n this.sramAddress %= 0x100;\n },\n\n // CPU Register $2005:\n // Write to scroll registers.\n // The first write is the vertical offset, the second is the\n // horizontal offset:\n scrollWrite: function(value) {\n this.triggerRendering();\n\n if (this.firstWrite) {\n // First write, horizontal scroll:\n this.regHT = (value >> 3) & 31;\n this.regFH = value & 7;\n } else {\n // Second write, vertical scroll:\n this.regFV = value & 7;\n this.regVT = (value >> 3) & 31;\n }\n this.firstWrite = !this.firstWrite;\n },\n\n // CPU Register $2006:\n // Sets the adress used when reading/writing from/to VRAM.\n // The first write sets the high byte, the second the low byte.\n writeVRAMAddress: function(address) {\n if (this.firstWrite) {\n this.regFV = (address >> 4) & 3;\n this.regV = (address >> 3) & 1;\n this.regH = (address >> 2) & 1;\n this.regVT = (this.regVT & 7) | ((address & 3) << 3);\n } else {\n this.triggerRendering();\n\n this.regVT = (this.regVT & 24) | ((address >> 5) & 7);\n this.regHT = address & 31;\n\n this.cntFV = this.regFV;\n this.cntV = this.regV;\n this.cntH = this.regH;\n this.cntVT = this.regVT;\n this.cntHT = this.regHT;\n\n this.checkSprite0(this.scanline - 20);\n }\n\n this.firstWrite = !this.firstWrite;\n\n // Invoke mapper latch:\n this.cntsToAddress();\n if (this.vramAddress < 0x2000) {\n this.nes.mmap.latchAccess(this.vramAddress);\n }\n },\n\n // CPU Register $2007(R):\n // Read from PPU memory. The address should be set first.\n vramLoad: function() {\n var tmp;\n\n this.cntsToAddress();\n this.regsToAddress();\n\n // If address is in range 0x0000-0x3EFF, return buffered values:\n if (this.vramAddress <= 0x3eff) {\n tmp = this.vramBufferedReadValue;\n\n // Update buffered value:\n if (this.vramAddress < 0x2000) {\n this.vramBufferedReadValue = this.vramMem[this.vramAddress];\n } else {\n this.vramBufferedReadValue = this.mirroredLoad(this.vramAddress);\n }\n\n // Mapper latch access:\n if (this.vramAddress < 0x2000) {\n this.nes.mmap.latchAccess(this.vramAddress);\n }\n\n // Increment by either 1 or 32, depending on d2 of Control Register 1:\n this.vramAddress += this.f_addrInc === 1 ? 32 : 1;\n\n this.cntsFromAddress();\n this.regsFromAddress();\n\n return tmp; // Return the previous buffered value.\n }\n\n // No buffering in this mem range. Read normally.\n tmp = this.mirroredLoad(this.vramAddress);\n\n // Increment by either 1 or 32, depending on d2 of Control Register 1:\n this.vramAddress += this.f_addrInc === 1 ? 32 : 1;\n\n this.cntsFromAddress();\n this.regsFromAddress();\n\n return tmp;\n },\n\n // CPU Register $2007(W):\n // Write to PPU memory. The address should be set first.\n vramWrite: function(value) {\n this.triggerRendering();\n this.cntsToAddress();\n this.regsToAddress();\n\n if (this.vramAddress >= 0x2000) {\n // Mirroring is used.\n this.mirroredWrite(this.vramAddress, value);\n } else {\n // Write normally.\n this.writeMem(this.vramAddress, value);\n\n // Invoke mapper latch:\n this.nes.mmap.latchAccess(this.vramAddress);\n }\n\n // Increment by either 1 or 32, depending on d2 of Control Register 1:\n this.vramAddress += this.f_addrInc === 1 ? 32 : 1;\n this.regsFromAddress();\n this.cntsFromAddress();\n },\n\n // CPU Register $4014:\n // Write 256 bytes of main memory\n // into Sprite RAM.\n sramDMA: function(value) {\n var baseAddress = value * 0x100;\n var data;\n for (var i = this.sramAddress; i < 256; i++) {\n data = this.nes.cpu.mem[baseAddress + i];\n this.spriteMem[i] = data;\n this.spriteRamWriteUpdate(i, data);\n }\n\n this.nes.cpu.haltCycles(513);\n },\n\n // Updates the scroll registers from a new VRAM address.\n regsFromAddress: function() {\n var address = (this.vramTmpAddress >> 8) & 0xff;\n this.regFV = (address >> 4) & 7;\n this.regV = (address >> 3) & 1;\n this.regH = (address >> 2) & 1;\n this.regVT = (this.regVT & 7) | ((address & 3) << 3);\n\n address = this.vramTmpAddress & 0xff;\n this.regVT = (this.regVT & 24) | ((address >> 5) & 7);\n this.regHT = address & 31;\n },\n\n // Updates the scroll registers from a new VRAM address.\n cntsFromAddress: function() {\n var address = (this.vramAddress >> 8) & 0xff;\n this.cntFV = (address >> 4) & 3;\n this.cntV = (address >> 3) & 1;\n this.cntH = (address >> 2) & 1;\n this.cntVT = (this.cntVT & 7) | ((address & 3) << 3);\n\n address = this.vramAddress & 0xff;\n this.cntVT = (this.cntVT & 24) | ((address >> 5) & 7);\n this.cntHT = address & 31;\n },\n\n regsToAddress: function() {\n var b1 = (this.regFV & 7) << 4;\n b1 |= (this.regV & 1) << 3;\n b1 |= (this.regH & 1) << 2;\n b1 |= (this.regVT >> 3) & 3;\n\n var b2 = (this.regVT & 7) << 5;\n b2 |= this.regHT & 31;\n\n this.vramTmpAddress = ((b1 << 8) | b2) & 0x7fff;\n },\n\n cntsToAddress: function() {\n var b1 = (this.cntFV & 7) << 4;\n b1 |= (this.cntV & 1) << 3;\n b1 |= (this.cntH & 1) << 2;\n b1 |= (this.cntVT >> 3) & 3;\n\n var b2 = (this.cntVT & 7) << 5;\n b2 |= this.cntHT & 31;\n\n this.vramAddress = ((b1 << 8) | b2) & 0x7fff;\n },\n\n incTileCounter: function(count) {\n for (var i = count; i !== 0; i--) {\n this.cntHT++;\n if (this.cntHT === 32) {\n this.cntHT = 0;\n this.cntVT++;\n if (this.cntVT >= 30) {\n this.cntH++;\n if (this.cntH === 2) {\n this.cntH = 0;\n this.cntV++;\n if (this.cntV === 2) {\n this.cntV = 0;\n this.cntFV++;\n this.cntFV &= 0x7;\n }\n }\n }\n }\n }\n },\n\n // Reads from memory, taking into account\n // mirroring/mapping of address ranges.\n mirroredLoad: function(address) {\n return this.vramMem[this.vramMirrorTable[address]];\n },\n\n // Writes to memory, taking into account\n // mirroring/mapping of address ranges.\n mirroredWrite: function(address, value) {\n if (address >= 0x3f00 && address < 0x3f20) {\n // Palette write mirroring.\n if (address === 0x3f00 || address === 0x3f10) {\n this.writeMem(0x3f00, value);\n this.writeMem(0x3f10, value);\n } else if (address === 0x3f04 || address === 0x3f14) {\n this.writeMem(0x3f04, value);\n this.writeMem(0x3f14, value);\n } else if (address === 0x3f08 || address === 0x3f18) {\n this.writeMem(0x3f08, value);\n this.writeMem(0x3f18, value);\n } else if (address === 0x3f0c || address === 0x3f1c) {\n this.writeMem(0x3f0c, value);\n this.writeMem(0x3f1c, value);\n } else {\n this.writeMem(address, value);\n }\n } else {\n // Use lookup table for mirrored address:\n if (address < this.vramMirrorTable.length) {\n this.writeMem(this.vramMirrorTable[address], value);\n } else {\n throw new Error(\"Invalid VRAM address: \" + address.toString(16));\n }\n }\n },\n\n triggerRendering: function() {\n if (this.scanline >= 21 && this.scanline <= 260) {\n // Render sprites, and combine:\n this.renderFramePartially(\n this.lastRenderedScanline + 1,\n this.scanline - 21 - this.lastRenderedScanline\n );\n\n // Set last rendered scanline:\n this.lastRenderedScanline = this.scanline - 21;\n }\n },\n\n renderFramePartially: function(startScan, scanCount) {\n if (this.f_spVisibility === 1) {\n this.renderSpritesPartially(startScan, scanCount, true);\n }\n\n if (this.f_bgVisibility === 1) {\n var si = startScan << 8;\n var ei = (startScan + scanCount) << 8;\n if (ei > 0xf000) {\n ei = 0xf000;\n }\n var buffer = this.buffer;\n var bgbuffer = this.bgbuffer;\n var pixrendered = this.pixrendered;\n for (var destIndex = si; destIndex < ei; destIndex++) {\n if (pixrendered[destIndex] > 0xff) {\n buffer[destIndex] = bgbuffer[destIndex];\n }\n }\n }\n\n if (this.f_spVisibility === 1) {\n this.renderSpritesPartially(startScan, scanCount, false);\n }\n\n this.validTileData = false;\n },\n\n renderBgScanline: function(bgbuffer, scan) {\n var baseTile = this.regS === 0 ? 0 : 256;\n var destIndex = (scan << 8) - this.regFH;\n\n this.curNt = this.ntable1[this.cntV + this.cntV + this.cntH];\n\n this.cntHT = this.regHT;\n this.cntH = this.regH;\n this.curNt = this.ntable1[this.cntV + this.cntV + this.cntH];\n\n if (scan < 240 && scan - this.cntFV >= 0) {\n var tscanoffset = this.cntFV << 3;\n var scantile = this.scantile;\n var attrib = this.attrib;\n var ptTile = this.ptTile;\n var nameTable = this.nameTable;\n var imgPalette = this.imgPalette;\n var pixrendered = this.pixrendered;\n var targetBuffer = bgbuffer ? this.bgbuffer : this.buffer;\n\n var t, tpix, att, col;\n\n for (var tile = 0; tile < 32; tile++) {\n if (scan >= 0) {\n // Fetch tile & attrib data:\n if (this.validTileData) {\n // Get data from array:\n t = scantile[tile];\n if (typeof t === \"undefined\") {\n continue;\n }\n tpix = t.pix;\n att = attrib[tile];\n } else {\n // Fetch data:\n t =\n ptTile[\n baseTile +\n nameTable[this.curNt].getTileIndex(this.cntHT, this.cntVT)\n ];\n if (typeof t === \"undefined\") {\n continue;\n }\n tpix = t.pix;\n att = nameTable[this.curNt].getAttrib(this.cntHT, this.cntVT);\n scantile[tile] = t;\n attrib[tile] = att;\n }\n\n // Render tile scanline:\n var sx = 0;\n var x = (tile << 3) - this.regFH;\n\n if (x > -8) {\n if (x < 0) {\n destIndex -= x;\n sx = -x;\n }\n if (t.opaque[this.cntFV]) {\n for (; sx < 8; sx++) {\n targetBuffer[destIndex] =\n imgPalette[tpix[tscanoffset + sx] + att];\n pixrendered[destIndex] |= 256;\n destIndex++;\n }\n } else {\n for (; sx < 8; sx++) {\n col = tpix[tscanoffset + sx];\n if (col !== 0) {\n targetBuffer[destIndex] = imgPalette[col + att];\n pixrendered[destIndex] |= 256;\n }\n destIndex++;\n }\n }\n }\n }\n\n // Increase Horizontal Tile Counter:\n if (++this.cntHT === 32) {\n this.cntHT = 0;\n this.cntH++;\n this.cntH %= 2;\n this.curNt = this.ntable1[(this.cntV << 1) + this.cntH];\n }\n }\n\n // Tile data for one row should now have been fetched,\n // so the data in the array is valid.\n this.validTileData = true;\n }\n\n // update vertical scroll:\n this.cntFV++;\n if (this.cntFV === 8) {\n this.cntFV = 0;\n this.cntVT++;\n if (this.cntVT === 30) {\n this.cntVT = 0;\n this.cntV++;\n this.cntV %= 2;\n this.curNt = this.ntable1[(this.cntV << 1) + this.cntH];\n } else if (this.cntVT === 32) {\n this.cntVT = 0;\n }\n\n // Invalidate fetched data:\n this.validTileData = false;\n }\n },\n\n renderSpritesPartially: function(startscan, scancount, bgPri) {\n if (this.f_spVisibility === 1) {\n for (var i = 0; i < 64; i++) {\n if (\n this.bgPriority[i] === bgPri &&\n this.sprX[i] >= 0 &&\n this.sprX[i] < 256 &&\n this.sprY[i] + 8 >= startscan &&\n this.sprY[i] < startscan + scancount\n ) {\n // Show sprite.\n if (this.f_spriteSize === 0) {\n // 8x8 sprites\n\n this.srcy1 = 0;\n this.srcy2 = 8;\n\n if (this.sprY[i] < startscan) {\n this.srcy1 = startscan - this.sprY[i] - 1;\n }\n\n if (this.sprY[i] + 8 > startscan + scancount) {\n this.srcy2 = startscan + scancount - this.sprY[i] + 1;\n }\n\n if (this.f_spPatternTable === 0) {\n this.ptTile[this.sprTile[i]].render(\n this.buffer,\n 0,\n this.srcy1,\n 8,\n this.srcy2,\n this.sprX[i],\n this.sprY[i] + 1,\n this.sprCol[i],\n this.sprPalette,\n this.horiFlip[i],\n this.vertFlip[i],\n i,\n this.pixrendered\n );\n } else {\n this.ptTile[this.sprTile[i] + 256].render(\n this.buffer,\n 0,\n this.srcy1,\n 8,\n this.srcy2,\n this.sprX[i],\n this.sprY[i] + 1,\n this.sprCol[i],\n this.sprPalette,\n this.horiFlip[i],\n this.vertFlip[i],\n i,\n this.pixrendered\n );\n }\n } else {\n // 8x16 sprites\n var top = this.sprTile[i];\n if ((top & 1) !== 0) {\n top = this.sprTile[i] - 1 + 256;\n }\n\n var srcy1 = 0;\n var srcy2 = 8;\n\n if (this.sprY[i] < startscan) {\n srcy1 = startscan - this.sprY[i] - 1;\n }\n\n if (this.sprY[i] + 8 > startscan + scancount) {\n srcy2 = startscan + scancount - this.sprY[i];\n }\n\n this.ptTile[top + (this.vertFlip[i] ? 1 : 0)].render(\n this.buffer,\n 0,\n srcy1,\n 8,\n srcy2,\n this.sprX[i],\n this.sprY[i] + 1,\n this.sprCol[i],\n this.sprPalette,\n this.horiFlip[i],\n this.vertFlip[i],\n i,\n this.pixrendered\n );\n\n srcy1 = 0;\n srcy2 = 8;\n\n if (this.sprY[i] + 8 < startscan) {\n srcy1 = startscan - (this.sprY[i] + 8 + 1);\n }\n\n if (this.sprY[i] + 16 > startscan + scancount) {\n srcy2 = startscan + scancount - (this.sprY[i] + 8);\n }\n\n this.ptTile[top + (this.vertFlip[i] ? 0 : 1)].render(\n this.buffer,\n 0,\n srcy1,\n 8,\n srcy2,\n this.sprX[i],\n this.sprY[i] + 1 + 8,\n this.sprCol[i],\n this.sprPalette,\n this.horiFlip[i],\n this.vertFlip[i],\n i,\n this.pixrendered\n );\n }\n }\n }\n }\n },\n\n checkSprite0: function(scan) {\n this.spr0HitX = -1;\n this.spr0HitY = -1;\n\n var toffset;\n var tIndexAdd = this.f_spPatternTable === 0 ? 0 : 256;\n var x, y, t, i;\n var bufferIndex;\n\n x = this.sprX[0];\n y = this.sprY[0] + 1;\n\n if (this.f_spriteSize === 0) {\n // 8x8 sprites.\n\n // Check range:\n if (y <= scan && y + 8 > scan && x >= -7 && x < 256) {\n // Sprite is in range.\n // Draw scanline:\n t = this.ptTile[this.sprTile[0] + tIndexAdd];\n\n if (this.vertFlip[0]) {\n toffset = 7 - (scan - y);\n } else {\n toffset = scan - y;\n }\n toffset *= 8;\n\n bufferIndex = scan * 256 + x;\n if (this.horiFlip[0]) {\n for (i = 7; i >= 0; i--) {\n if (x >= 0 && x < 256) {\n if (\n bufferIndex >= 0 &&\n bufferIndex < 61440 &&\n this.pixrendered[bufferIndex] !== 0\n ) {\n if (t.pix[toffset + i] !== 0) {\n this.spr0HitX = bufferIndex % 256;\n this.spr0HitY = scan;\n return true;\n }\n }\n }\n x++;\n bufferIndex++;\n }\n } else {\n for (i = 0; i < 8; i++) {\n if (x >= 0 && x < 256) {\n if (\n bufferIndex >= 0 &&\n bufferIndex < 61440 &&\n this.pixrendered[bufferIndex] !== 0\n ) {\n if (t.pix[toffset + i] !== 0) {\n this.spr0HitX = bufferIndex % 256;\n this.spr0HitY = scan;\n return true;\n }\n }\n }\n x++;\n bufferIndex++;\n }\n }\n }\n } else {\n // 8x16 sprites:\n\n // Check range:\n if (y <= scan && y + 16 > scan && x >= -7 && x < 256) {\n // Sprite is in range.\n // Draw scanline:\n\n if (this.vertFlip[0]) {\n toffset = 15 - (scan - y);\n } else {\n toffset = scan - y;\n }\n\n if (toffset < 8) {\n // first half of sprite.\n t = this.ptTile[\n this.sprTile[0] +\n (this.vertFlip[0] ? 1 : 0) +\n ((this.sprTile[0] & 1) !== 0 ? 255 : 0)\n ];\n } else {\n // second half of sprite.\n t = this.ptTile[\n this.sprTile[0] +\n (this.vertFlip[0] ? 0 : 1) +\n ((this.sprTile[0] & 1) !== 0 ? 255 : 0)\n ];\n if (this.vertFlip[0]) {\n toffset = 15 - toffset;\n } else {\n toffset -= 8;\n }\n }\n toffset *= 8;\n\n bufferIndex = scan * 256 + x;\n if (this.horiFlip[0]) {\n for (i = 7; i >= 0; i--) {\n if (x >= 0 && x < 256) {\n if (\n bufferIndex >= 0 &&\n bufferIndex < 61440 &&\n this.pixrendered[bufferIndex] !== 0\n ) {\n if (t.pix[toffset + i] !== 0) {\n this.spr0HitX = bufferIndex % 256;\n this.spr0HitY = scan;\n return true;\n }\n }\n }\n x++;\n bufferIndex++;\n }\n } else {\n for (i = 0; i < 8; i++) {\n if (x >= 0 && x < 256) {\n if (\n bufferIndex >= 0 &&\n bufferIndex < 61440 &&\n this.pixrendered[bufferIndex] !== 0\n ) {\n if (t.pix[toffset + i] !== 0) {\n this.spr0HitX = bufferIndex % 256;\n this.spr0HitY = scan;\n return true;\n }\n }\n }\n x++;\n bufferIndex++;\n }\n }\n }\n }\n\n return false;\n },\n\n // This will write to PPU memory, and\n // update internally buffered data\n // appropriately.\n writeMem: function(address, value) {\n this.vramMem[address] = value;\n\n // Update internally buffered data:\n if (address < 0x2000) {\n this.vramMem[address] = value;\n this.patternWrite(address, value);\n } else if (address >= 0x2000 && address < 0x23c0) {\n this.nameTableWrite(this.ntable1[0], address - 0x2000, value);\n } else if (address >= 0x23c0 && address < 0x2400) {\n this.attribTableWrite(this.ntable1[0], address - 0x23c0, value);\n } else if (address >= 0x2400 && address < 0x27c0) {\n this.nameTableWrite(this.ntable1[1], address - 0x2400, value);\n } else if (address >= 0x27c0 && address < 0x2800) {\n this.attribTableWrite(this.ntable1[1], address - 0x27c0, value);\n } else if (address >= 0x2800 && address < 0x2bc0) {\n this.nameTableWrite(this.ntable1[2], address - 0x2800, value);\n } else if (address >= 0x2bc0 && address < 0x2c00) {\n this.attribTableWrite(this.ntable1[2], address - 0x2bc0, value);\n } else if (address >= 0x2c00 && address < 0x2fc0) {\n this.nameTableWrite(this.ntable1[3], address - 0x2c00, value);\n } else if (address >= 0x2fc0 && address < 0x3000) {\n this.attribTableWrite(this.ntable1[3], address - 0x2fc0, value);\n } else if (address >= 0x3f00 && address < 0x3f20) {\n this.updatePalettes();\n }\n },\n\n // Reads data from $3f00 to $f20\n // into the two buffered palettes.\n updatePalettes: function() {\n var i;\n\n for (i = 0; i < 16; i++) {\n if (this.f_dispType === 0) {\n this.imgPalette[i] = this.palTable.getEntry(\n this.vramMem[0x3f00 + i] & 63\n );\n } else {\n this.imgPalette[i] = this.palTable.getEntry(\n this.vramMem[0x3f00 + i] & 32\n );\n }\n }\n for (i = 0; i < 16; i++) {\n if (this.f_dispType === 0) {\n this.sprPalette[i] = this.palTable.getEntry(\n this.vramMem[0x3f10 + i] & 63\n );\n } else {\n this.sprPalette[i] = this.palTable.getEntry(\n this.vramMem[0x3f10 + i] & 32\n );\n }\n }\n },\n\n // Updates the internal pattern\n // table buffers with this new byte.\n // In vNES, there is a version of this with 4 arguments which isn't used.\n patternWrite: function(address, value) {\n var tileIndex = Math.floor(address / 16);\n var leftOver = address % 16;\n if (leftOver < 8) {\n this.ptTile[tileIndex].setScanline(\n leftOver,\n value,\n this.vramMem[address + 8]\n );\n } else {\n this.ptTile[tileIndex].setScanline(\n leftOver - 8,\n this.vramMem[address - 8],\n value\n );\n }\n },\n\n // Updates the internal name table buffers\n // with this new byte.\n nameTableWrite: function(index, address, value) {\n this.nameTable[index].tile[address] = value;\n\n // Update Sprite #0 hit:\n //updateSpr0Hit();\n this.checkSprite0(this.scanline - 20);\n },\n\n // Updates the internal pattern\n // table buffers with this new attribute\n // table byte.\n attribTableWrite: function(index, address, value) {\n this.nameTable[index].writeAttrib(address, value);\n },\n\n // Updates the internally buffered sprite\n // data with this new byte of info.\n spriteRamWriteUpdate: function(address, value) {\n var tIndex = Math.floor(address / 4);\n\n if (tIndex === 0) {\n //updateSpr0Hit();\n this.checkSprite0(this.scanline - 20);\n }\n\n if (address % 4 === 0) {\n // Y coordinate\n this.sprY[tIndex] = value;\n } else if (address % 4 === 1) {\n // Tile index\n this.sprTile[tIndex] = value;\n } else if (address % 4 === 2) {\n // Attributes\n this.vertFlip[tIndex] = (value & 0x80) !== 0;\n this.horiFlip[tIndex] = (value & 0x40) !== 0;\n this.bgPriority[tIndex] = (value & 0x20) !== 0;\n this.sprCol[tIndex] = (value & 3) << 2;\n } else if (address % 4 === 3) {\n // X coordinate\n this.sprX[tIndex] = value;\n }\n },\n\n doNMI: function() {\n // Set VBlank flag:\n this.setStatusFlag(this.STATUS_VBLANK, true);\n //nes.getCpu().doNonMaskableInterrupt();\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI);\n },\n\n isPixelWhite: function(x, y) {\n this.triggerRendering();\n return this.nes.ppu.buffer[(y << 8) + x] === 0xffffff;\n },\n\n JSON_PROPERTIES: [\n // Memory\n \"vramMem\",\n \"spriteMem\",\n // Counters\n \"cntFV\",\n \"cntV\",\n \"cntH\",\n \"cntVT\",\n \"cntHT\",\n // Registers\n \"regFV\",\n \"regV\",\n \"regH\",\n \"regVT\",\n \"regHT\",\n \"regFH\",\n \"regS\",\n // VRAM addr\n \"vramAddress\",\n \"vramTmpAddress\",\n // Control/Status registers\n \"f_nmiOnVblank\",\n \"f_spriteSize\",\n \"f_bgPatternTable\",\n \"f_spPatternTable\",\n \"f_addrInc\",\n \"f_nTblAddress\",\n \"f_color\",\n \"f_spVisibility\",\n \"f_bgVisibility\",\n \"f_spClipping\",\n \"f_bgClipping\",\n \"f_dispType\",\n // VRAM I/O\n \"vramBufferedReadValue\",\n \"firstWrite\",\n // Mirroring\n \"currentMirroring\",\n \"vramMirrorTable\",\n \"ntable1\",\n // SPR-RAM I/O\n \"sramAddress\",\n // Sprites. Most sprite data is rebuilt from spriteMem\n \"hitSpr0\",\n // Palettes\n \"sprPalette\",\n \"imgPalette\",\n // Rendering progression\n \"curX\",\n \"scanline\",\n \"lastRenderedScanline\",\n \"curNt\",\n \"scantile\",\n // Used during rendering\n \"attrib\",\n \"buffer\",\n \"bgbuffer\",\n \"pixrendered\",\n // Misc\n \"requestEndFrame\",\n \"nmiOk\",\n \"dummyCycleToggle\",\n \"nmiCounter\",\n \"validTileData\",\n \"scanlineAlreadyRendered\"\n ],\n\n toJSON: function() {\n var i;\n var state = utils.toJSON(this);\n\n state.nameTable = [];\n for (i = 0; i < this.nameTable.length; i++) {\n state.nameTable[i] = this.nameTable[i].toJSON();\n }\n\n state.ptTile = [];\n for (i = 0; i < this.ptTile.length; i++) {\n state.ptTile[i] = this.ptTile[i].toJSON();\n }\n\n return state;\n },\n\n fromJSON: function(state) {\n var i;\n\n utils.fromJSON(this, state);\n\n for (i = 0; i < this.nameTable.length; i++) {\n this.nameTable[i].fromJSON(state.nameTable[i]);\n }\n\n for (i = 0; i < this.ptTile.length; i++) {\n this.ptTile[i].fromJSON(state.ptTile[i]);\n }\n\n // Sprite data:\n for (i = 0; i < this.spriteMem.length; i++) {\n this.spriteRamWriteUpdate(i, this.spriteMem[i]);\n }\n }\n};\n\nvar NameTable = function(width, height, name) {\n this.width = width;\n this.height = height;\n this.name = name;\n\n this.tile = new Uint8Array(width * height);\n this.attrib = new Uint8Array(width * height);\n for (var i = 0; i < width * height; i++) {\n this.tile[i] = 0;\n this.attrib[i] = 0;\n }\n};\n\nNameTable.prototype = {\n getTileIndex: function(x, y) {\n return this.tile[y * this.width + x];\n },\n\n getAttrib: function(x, y) {\n return this.attrib[y * this.width + x];\n },\n\n writeAttrib: function(index, value) {\n var basex = (index % 8) * 4;\n var basey = Math.floor(index / 8) * 4;\n var add;\n var tx, ty;\n var attindex;\n\n for (var sqy = 0; sqy < 2; sqy++) {\n for (var sqx = 0; sqx < 2; sqx++) {\n add = (value >> (2 * (sqy * 2 + sqx))) & 3;\n for (var y = 0; y < 2; y++) {\n for (var x = 0; x < 2; x++) {\n tx = basex + sqx * 2 + x;\n ty = basey + sqy * 2 + y;\n attindex = ty * this.width + tx;\n this.attrib[attindex] = (add << 2) & 12;\n }\n }\n }\n }\n },\n\n toJSON: function() {\n return {\n tile: this.tile,\n attrib: this.attrib\n };\n },\n\n fromJSON: function(s) {\n this.tile = s.tile;\n this.attrib = s.attrib;\n }\n};\n\nvar PaletteTable = function() {\n this.curTable = new Uint32Array(64);\n this.emphTable = new Array(8);\n this.currentEmph = -1;\n};\n\nPaletteTable.prototype = {\n reset: function() {\n this.setEmphasis(0);\n },\n\n loadNTSCPalette: function() {\n // prettier-ignore\n this.curTable = new Uint32Array([0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000]);\n this.makeTables();\n this.setEmphasis(0);\n },\n\n loadPALPalette: function() {\n // prettier-ignore\n this.curTable = new Uint32Array([0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000]);\n this.makeTables();\n this.setEmphasis(0);\n },\n\n makeTables: function() {\n var r, g, b, col, i, rFactor, gFactor, bFactor;\n\n // Calculate a table for each possible emphasis setting:\n for (var emph = 0; emph < 8; emph++) {\n // Determine color component factors:\n rFactor = 1.0;\n gFactor = 1.0;\n bFactor = 1.0;\n\n if ((emph & 1) !== 0) {\n rFactor = 0.75;\n bFactor = 0.75;\n }\n if ((emph & 2) !== 0) {\n rFactor = 0.75;\n gFactor = 0.75;\n }\n if ((emph & 4) !== 0) {\n gFactor = 0.75;\n bFactor = 0.75;\n }\n\n this.emphTable[emph] = new Uint32Array(64);\n\n // Calculate table:\n for (i = 0; i < 64; i++) {\n col = this.curTable[i];\n r = Math.floor(this.getRed(col) * rFactor);\n g = Math.floor(this.getGreen(col) * gFactor);\n b = Math.floor(this.getBlue(col) * bFactor);\n this.emphTable[emph][i] = this.getRgb(r, g, b);\n }\n }\n },\n\n setEmphasis: function(emph) {\n if (emph !== this.currentEmph) {\n this.currentEmph = emph;\n for (var i = 0; i < 64; i++) {\n this.curTable[i] = this.emphTable[emph][i];\n }\n }\n },\n\n getEntry: function(yiq) {\n return this.curTable[yiq];\n },\n\n getRed: function(rgb) {\n return (rgb >> 16) & 0xff;\n },\n\n getGreen: function(rgb) {\n return (rgb >> 8) & 0xff;\n },\n\n getBlue: function(rgb) {\n return rgb & 0xff;\n },\n\n getRgb: function(r, g, b) {\n return (r << 16) | (g << 8) | b;\n },\n\n loadDefaultPalette: function() {\n this.curTable[0] = this.getRgb(117, 117, 117);\n this.curTable[1] = this.getRgb(39, 27, 143);\n this.curTable[2] = this.getRgb(0, 0, 171);\n this.curTable[3] = this.getRgb(71, 0, 159);\n this.curTable[4] = this.getRgb(143, 0, 119);\n this.curTable[5] = this.getRgb(171, 0, 19);\n this.curTable[6] = this.getRgb(167, 0, 0);\n this.curTable[7] = this.getRgb(127, 11, 0);\n this.curTable[8] = this.getRgb(67, 47, 0);\n this.curTable[9] = this.getRgb(0, 71, 0);\n this.curTable[10] = this.getRgb(0, 81, 0);\n this.curTable[11] = this.getRgb(0, 63, 23);\n this.curTable[12] = this.getRgb(27, 63, 95);\n this.curTable[13] = this.getRgb(0, 0, 0);\n this.curTable[14] = this.getRgb(0, 0, 0);\n this.curTable[15] = this.getRgb(0, 0, 0);\n this.curTable[16] = this.getRgb(188, 188, 188);\n this.curTable[17] = this.getRgb(0, 115, 239);\n this.curTable[18] = this.getRgb(35, 59, 239);\n this.curTable[19] = this.getRgb(131, 0, 243);\n this.curTable[20] = this.getRgb(191, 0, 191);\n this.curTable[21] = this.getRgb(231, 0, 91);\n this.curTable[22] = this.getRgb(219, 43, 0);\n this.curTable[23] = this.getRgb(203, 79, 15);\n this.curTable[24] = this.getRgb(139, 115, 0);\n this.curTable[25] = this.getRgb(0, 151, 0);\n this.curTable[26] = this.getRgb(0, 171, 0);\n this.curTable[27] = this.getRgb(0, 147, 59);\n this.curTable[28] = this.getRgb(0, 131, 139);\n this.curTable[29] = this.getRgb(0, 0, 0);\n this.curTable[30] = this.getRgb(0, 0, 0);\n this.curTable[31] = this.getRgb(0, 0, 0);\n this.curTable[32] = this.getRgb(255, 255, 255);\n this.curTable[33] = this.getRgb(63, 191, 255);\n this.curTable[34] = this.getRgb(95, 151, 255);\n this.curTable[35] = this.getRgb(167, 139, 253);\n this.curTable[36] = this.getRgb(247, 123, 255);\n this.curTable[37] = this.getRgb(255, 119, 183);\n this.curTable[38] = this.getRgb(255, 119, 99);\n this.curTable[39] = this.getRgb(255, 155, 59);\n this.curTable[40] = this.getRgb(243, 191, 63);\n this.curTable[41] = this.getRgb(131, 211, 19);\n this.curTable[42] = this.getRgb(79, 223, 75);\n this.curTable[43] = this.getRgb(88, 248, 152);\n this.curTable[44] = this.getRgb(0, 235, 219);\n this.curTable[45] = this.getRgb(0, 0, 0);\n this.curTable[46] = this.getRgb(0, 0, 0);\n this.curTable[47] = this.getRgb(0, 0, 0);\n this.curTable[48] = this.getRgb(255, 255, 255);\n this.curTable[49] = this.getRgb(171, 231, 255);\n this.curTable[50] = this.getRgb(199, 215, 255);\n this.curTable[51] = this.getRgb(215, 203, 255);\n this.curTable[52] = this.getRgb(255, 199, 255);\n this.curTable[53] = this.getRgb(255, 199, 219);\n this.curTable[54] = this.getRgb(255, 191, 179);\n this.curTable[55] = this.getRgb(255, 219, 171);\n this.curTable[56] = this.getRgb(255, 231, 163);\n this.curTable[57] = this.getRgb(227, 255, 163);\n this.curTable[58] = this.getRgb(171, 243, 191);\n this.curTable[59] = this.getRgb(179, 255, 207);\n this.curTable[60] = this.getRgb(159, 255, 243);\n this.curTable[61] = this.getRgb(0, 0, 0);\n this.curTable[62] = this.getRgb(0, 0, 0);\n this.curTable[63] = this.getRgb(0, 0, 0);\n\n this.makeTables();\n this.setEmphasis(0);\n }\n};\n\nmodule.exports = PPU;\n", "var CPU_FREQ_NTSC = 1789772.5; //1789772.72727272d;\n// var CPU_FREQ_PAL = 1773447.4;\n\nvar PAPU = function(nes) {\n this.nes = nes;\n\n this.square1 = new ChannelSquare(this, true);\n this.square2 = new ChannelSquare(this, false);\n this.triangle = new ChannelTriangle(this);\n this.noise = new ChannelNoise(this);\n this.dmc = new ChannelDM(this);\n\n this.frameIrqCounter = null;\n this.frameIrqCounterMax = 4;\n this.initCounter = 2048;\n this.channelEnableValue = null;\n\n this.sampleRate = 44100;\n\n this.lengthLookup = null;\n this.dmcFreqLookup = null;\n this.noiseWavelengthLookup = null;\n this.square_table = null;\n this.tnd_table = null;\n\n this.frameIrqEnabled = false;\n this.frameIrqActive = null;\n this.frameClockNow = null;\n this.startedPlaying = false;\n this.recordOutput = false;\n this.initingHardware = false;\n\n this.masterFrameCounter = null;\n this.derivedFrameCounter = null;\n this.countSequence = null;\n this.sampleTimer = null;\n this.frameTime = null;\n this.sampleTimerMax = null;\n this.sampleCount = null;\n this.triValue = 0;\n\n this.smpSquare1 = null;\n this.smpSquare2 = null;\n this.smpTriangle = null;\n this.smpDmc = null;\n this.accCount = null;\n\n // DC removal vars:\n this.prevSampleL = 0;\n this.prevSampleR = 0;\n this.smpAccumL = 0;\n this.smpAccumR = 0;\n\n // DAC range:\n this.dacRange = 0;\n this.dcValue = 0;\n\n // Master volume:\n this.masterVolume = 256;\n\n // Stereo positioning:\n this.stereoPosLSquare1 = null;\n this.stereoPosLSquare2 = null;\n this.stereoPosLTriangle = null;\n this.stereoPosLNoise = null;\n this.stereoPosLDMC = null;\n this.stereoPosRSquare1 = null;\n this.stereoPosRSquare2 = null;\n this.stereoPosRTriangle = null;\n this.stereoPosRNoise = null;\n this.stereoPosRDMC = null;\n\n this.extraCycles = null;\n\n this.maxSample = null;\n this.minSample = null;\n\n // Panning:\n this.panning = [80, 170, 100, 150, 128];\n this.setPanning(this.panning);\n\n // Initialize lookup tables:\n this.initLengthLookup();\n this.initDmcFrequencyLookup();\n this.initNoiseWavelengthLookup();\n this.initDACtables();\n\n // Init sound registers:\n for (var i = 0; i < 0x14; i++) {\n if (i === 0x10) {\n this.writeReg(0x4010, 0x10);\n } else {\n this.writeReg(0x4000 + i, 0);\n }\n }\n\n this.reset();\n};\n\nPAPU.prototype = {\n reset: function() {\n this.sampleRate = this.nes.opts.sampleRate;\n this.sampleTimerMax = Math.floor(\n (1024.0 * CPU_FREQ_NTSC * this.nes.opts.preferredFrameRate) /\n (this.sampleRate * 60.0)\n );\n\n this.frameTime = Math.floor(\n (14915.0 * this.nes.opts.preferredFrameRate) / 60.0\n );\n\n this.sampleTimer = 0;\n\n this.updateChannelEnable(0);\n this.masterFrameCounter = 0;\n this.derivedFrameCounter = 0;\n this.countSequence = 0;\n this.sampleCount = 0;\n this.initCounter = 2048;\n this.frameIrqEnabled = false;\n this.initingHardware = false;\n\n this.resetCounter();\n\n this.square1.reset();\n this.square2.reset();\n this.triangle.reset();\n this.noise.reset();\n this.dmc.reset();\n\n this.accCount = 0;\n this.smpSquare1 = 0;\n this.smpSquare2 = 0;\n this.smpTriangle = 0;\n this.smpDmc = 0;\n\n this.frameIrqEnabled = false;\n this.frameIrqCounterMax = 4;\n\n this.channelEnableValue = 0xff;\n this.startedPlaying = false;\n this.prevSampleL = 0;\n this.prevSampleR = 0;\n this.smpAccumL = 0;\n this.smpAccumR = 0;\n\n this.maxSample = -500000;\n this.minSample = 500000;\n },\n\n // eslint-disable-next-line no-unused-vars\n readReg: function(address) {\n // Read 0x4015:\n var tmp = 0;\n tmp |= this.square1.getLengthStatus();\n tmp |= this.square2.getLengthStatus() << 1;\n tmp |= this.triangle.getLengthStatus() << 2;\n tmp |= this.noise.getLengthStatus() << 3;\n tmp |= this.dmc.getLengthStatus() << 4;\n tmp |= (this.frameIrqActive && this.frameIrqEnabled ? 1 : 0) << 6;\n tmp |= this.dmc.getIrqStatus() << 7;\n\n this.frameIrqActive = false;\n this.dmc.irqGenerated = false;\n\n return tmp & 0xffff;\n },\n\n writeReg: function(address, value) {\n if (address >= 0x4000 && address < 0x4004) {\n // Square Wave 1 Control\n this.square1.writeReg(address, value);\n // console.log(\"Square Write\");\n } else if (address >= 0x4004 && address < 0x4008) {\n // Square 2 Control\n this.square2.writeReg(address, value);\n } else if (address >= 0x4008 && address < 0x400c) {\n // Triangle Control\n this.triangle.writeReg(address, value);\n } else if (address >= 0x400c && address <= 0x400f) {\n // Noise Control\n this.noise.writeReg(address, value);\n } else if (address === 0x4010) {\n // DMC Play mode & DMA frequency\n this.dmc.writeReg(address, value);\n } else if (address === 0x4011) {\n // DMC Delta Counter\n this.dmc.writeReg(address, value);\n } else if (address === 0x4012) {\n // DMC Play code starting address\n this.dmc.writeReg(address, value);\n } else if (address === 0x4013) {\n // DMC Play code length\n this.dmc.writeReg(address, value);\n } else if (address === 0x4015) {\n // Channel enable\n this.updateChannelEnable(value);\n\n if (value !== 0 && this.initCounter > 0) {\n // Start hardware initialization\n this.initingHardware = true;\n }\n\n // DMC/IRQ Status\n this.dmc.writeReg(address, value);\n } else if (address === 0x4017) {\n // Frame counter control\n this.countSequence = (value >> 7) & 1;\n this.masterFrameCounter = 0;\n this.frameIrqActive = false;\n\n if (((value >> 6) & 0x1) === 0) {\n this.frameIrqEnabled = true;\n } else {\n this.frameIrqEnabled = false;\n }\n\n if (this.countSequence === 0) {\n // NTSC:\n this.frameIrqCounterMax = 4;\n this.derivedFrameCounter = 4;\n } else {\n // PAL:\n this.frameIrqCounterMax = 5;\n this.derivedFrameCounter = 0;\n this.frameCounterTick();\n }\n }\n },\n\n resetCounter: function() {\n if (this.countSequence === 0) {\n this.derivedFrameCounter = 4;\n } else {\n this.derivedFrameCounter = 0;\n }\n },\n\n // Updates channel enable status.\n // This is done on writes to the\n // channel enable register (0x4015),\n // and when the user enables/disables channels\n // in the GUI.\n updateChannelEnable: function(value) {\n this.channelEnableValue = value & 0xffff;\n this.square1.setEnabled((value & 1) !== 0);\n this.square2.setEnabled((value & 2) !== 0);\n this.triangle.setEnabled((value & 4) !== 0);\n this.noise.setEnabled((value & 8) !== 0);\n this.dmc.setEnabled((value & 16) !== 0);\n },\n\n // Clocks the frame counter. It should be clocked at\n // twice the cpu speed, so the cycles will be\n // divided by 2 for those counters that are\n // clocked at cpu speed.\n clockFrameCounter: function(nCycles) {\n if (this.initCounter > 0) {\n if (this.initingHardware) {\n this.initCounter -= nCycles;\n if (this.initCounter <= 0) {\n this.initingHardware = false;\n }\n return;\n }\n }\n\n // Don't process ticks beyond next sampling:\n nCycles += this.extraCycles;\n var maxCycles = this.sampleTimerMax - this.sampleTimer;\n if (nCycles << 10 > maxCycles) {\n this.extraCycles = ((nCycles << 10) - maxCycles) >> 10;\n nCycles -= this.extraCycles;\n } else {\n this.extraCycles = 0;\n }\n\n var dmc = this.dmc;\n var triangle = this.triangle;\n var square1 = this.square1;\n var square2 = this.square2;\n var noise = this.noise;\n\n // Clock DMC:\n if (dmc.isEnabled) {\n dmc.shiftCounter -= nCycles << 3;\n while (dmc.shiftCounter <= 0 && dmc.dmaFrequency > 0) {\n dmc.shiftCounter += dmc.dmaFrequency;\n dmc.clockDmc();\n }\n }\n\n // Clock Triangle channel Prog timer:\n if (triangle.progTimerMax > 0) {\n triangle.progTimerCount -= nCycles;\n while (triangle.progTimerCount <= 0) {\n triangle.progTimerCount += triangle.progTimerMax + 1;\n if (triangle.linearCounter > 0 && triangle.lengthCounter > 0) {\n triangle.triangleCounter++;\n triangle.triangleCounter &= 0x1f;\n\n if (triangle.isEnabled) {\n if (triangle.triangleCounter >= 0x10) {\n // Normal value.\n triangle.sampleValue = triangle.triangleCounter & 0xf;\n } else {\n // Inverted value.\n triangle.sampleValue = 0xf - (triangle.triangleCounter & 0xf);\n }\n triangle.sampleValue <<= 4;\n }\n }\n }\n }\n\n // Clock Square channel 1 Prog timer:\n square1.progTimerCount -= nCycles;\n if (square1.progTimerCount <= 0) {\n square1.progTimerCount += (square1.progTimerMax + 1) << 1;\n\n square1.squareCounter++;\n square1.squareCounter &= 0x7;\n square1.updateSampleValue();\n }\n\n // Clock Square channel 2 Prog timer:\n square2.progTimerCount -= nCycles;\n if (square2.progTimerCount <= 0) {\n square2.progTimerCount += (square2.progTimerMax + 1) << 1;\n\n square2.squareCounter++;\n square2.squareCounter &= 0x7;\n square2.updateSampleValue();\n }\n\n // Clock noise channel Prog timer:\n var acc_c = nCycles;\n if (noise.progTimerCount - acc_c > 0) {\n // Do all cycles at once:\n noise.progTimerCount -= acc_c;\n noise.accCount += acc_c;\n noise.accValue += acc_c * noise.sampleValue;\n } else {\n // Slow-step:\n while (acc_c-- > 0) {\n if (--noise.progTimerCount <= 0 && noise.progTimerMax > 0) {\n // Update noise shift register:\n noise.shiftReg <<= 1;\n noise.tmp =\n ((noise.shiftReg << (noise.randomMode === 0 ? 1 : 6)) ^\n noise.shiftReg) &\n 0x8000;\n if (noise.tmp !== 0) {\n // Sample value must be 0.\n noise.shiftReg |= 0x01;\n noise.randomBit = 0;\n noise.sampleValue = 0;\n } else {\n // Find sample value:\n noise.randomBit = 1;\n if (noise.isEnabled && noise.lengthCounter > 0) {\n noise.sampleValue = noise.masterVolume;\n } else {\n noise.sampleValue = 0;\n }\n }\n\n noise.progTimerCount += noise.progTimerMax;\n }\n\n noise.accValue += noise.sampleValue;\n noise.accCount++;\n }\n }\n\n // Frame IRQ handling:\n if (this.frameIrqEnabled && this.frameIrqActive) {\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL);\n }\n\n // Clock frame counter at double CPU speed:\n this.masterFrameCounter += nCycles << 1;\n if (this.masterFrameCounter >= this.frameTime) {\n // 240Hz tick:\n this.masterFrameCounter -= this.frameTime;\n this.frameCounterTick();\n }\n\n // Accumulate sample value:\n this.accSample(nCycles);\n\n // Clock sample timer:\n this.sampleTimer += nCycles << 10;\n if (this.sampleTimer >= this.sampleTimerMax) {\n // Sample channels:\n this.sample();\n this.sampleTimer -= this.sampleTimerMax;\n }\n },\n\n accSample: function(cycles) {\n // Special treatment for triangle channel - need to interpolate.\n if (this.triangle.sampleCondition) {\n this.triValue = Math.floor(\n (this.triangle.progTimerCount << 4) / (this.triangle.progTimerMax + 1)\n );\n if (this.triValue > 16) {\n this.triValue = 16;\n }\n if (this.triangle.triangleCounter >= 16) {\n this.triValue = 16 - this.triValue;\n }\n\n // Add non-interpolated sample value:\n this.triValue += this.triangle.sampleValue;\n }\n\n // Now sample normally:\n if (cycles === 2) {\n this.smpTriangle += this.triValue << 1;\n this.smpDmc += this.dmc.sample << 1;\n this.smpSquare1 += this.square1.sampleValue << 1;\n this.smpSquare2 += this.square2.sampleValue << 1;\n this.accCount += 2;\n } else if (cycles === 4) {\n this.smpTriangle += this.triValue << 2;\n this.smpDmc += this.dmc.sample << 2;\n this.smpSquare1 += this.square1.sampleValue << 2;\n this.smpSquare2 += this.square2.sampleValue << 2;\n this.accCount += 4;\n } else {\n this.smpTriangle += cycles * this.triValue;\n this.smpDmc += cycles * this.dmc.sample;\n this.smpSquare1 += cycles * this.square1.sampleValue;\n this.smpSquare2 += cycles * this.square2.sampleValue;\n this.accCount += cycles;\n }\n },\n\n frameCounterTick: function() {\n this.derivedFrameCounter++;\n if (this.derivedFrameCounter >= this.frameIrqCounterMax) {\n this.derivedFrameCounter = 0;\n }\n\n if (this.derivedFrameCounter === 1 || this.derivedFrameCounter === 3) {\n // Clock length & sweep:\n this.triangle.clockLengthCounter();\n this.square1.clockLengthCounter();\n this.square2.clockLengthCounter();\n this.noise.clockLengthCounter();\n this.square1.clockSweep();\n this.square2.clockSweep();\n }\n\n if (this.derivedFrameCounter >= 0 && this.derivedFrameCounter < 4) {\n // Clock linear & decay:\n this.square1.clockEnvDecay();\n this.square2.clockEnvDecay();\n this.noise.clockEnvDecay();\n this.triangle.clockLinearCounter();\n }\n\n if (this.derivedFrameCounter === 3 && this.countSequence === 0) {\n // Enable IRQ:\n this.frameIrqActive = true;\n }\n\n // End of 240Hz tick\n },\n\n // Samples the channels, mixes the output together, then writes to buffer.\n sample: function() {\n var sq_index, tnd_index;\n\n if (this.accCount > 0) {\n this.smpSquare1 <<= 4;\n this.smpSquare1 = Math.floor(this.smpSquare1 / this.accCount);\n\n this.smpSquare2 <<= 4;\n this.smpSquare2 = Math.floor(this.smpSquare2 / this.accCount);\n\n this.smpTriangle = Math.floor(this.smpTriangle / this.accCount);\n\n this.smpDmc <<= 4;\n this.smpDmc = Math.floor(this.smpDmc / this.accCount);\n\n this.accCount = 0;\n } else {\n this.smpSquare1 = this.square1.sampleValue << 4;\n this.smpSquare2 = this.square2.sampleValue << 4;\n this.smpTriangle = this.triangle.sampleValue;\n this.smpDmc = this.dmc.sample << 4;\n }\n\n var smpNoise = Math.floor((this.noise.accValue << 4) / this.noise.accCount);\n this.noise.accValue = smpNoise >> 4;\n this.noise.accCount = 1;\n\n // Stereo sound.\n\n // Left channel:\n sq_index =\n (this.smpSquare1 * this.stereoPosLSquare1 +\n this.smpSquare2 * this.stereoPosLSquare2) >>\n 8;\n tnd_index =\n (3 * this.smpTriangle * this.stereoPosLTriangle +\n (smpNoise << 1) * this.stereoPosLNoise +\n this.smpDmc * this.stereoPosLDMC) >>\n 8;\n if (sq_index >= this.square_table.length) {\n sq_index = this.square_table.length - 1;\n }\n if (tnd_index >= this.tnd_table.length) {\n tnd_index = this.tnd_table.length - 1;\n }\n var sampleValueL =\n this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue;\n\n // Right channel:\n sq_index =\n (this.smpSquare1 * this.stereoPosRSquare1 +\n this.smpSquare2 * this.stereoPosRSquare2) >>\n 8;\n tnd_index =\n (3 * this.smpTriangle * this.stereoPosRTriangle +\n (smpNoise << 1) * this.stereoPosRNoise +\n this.smpDmc * this.stereoPosRDMC) >>\n 8;\n if (sq_index >= this.square_table.length) {\n sq_index = this.square_table.length - 1;\n }\n if (tnd_index >= this.tnd_table.length) {\n tnd_index = this.tnd_table.length - 1;\n }\n var sampleValueR =\n this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue;\n\n // Remove DC from left channel:\n var smpDiffL = sampleValueL - this.prevSampleL;\n this.prevSampleL += smpDiffL;\n this.smpAccumL += smpDiffL - (this.smpAccumL >> 10);\n sampleValueL = this.smpAccumL;\n\n // Remove DC from right channel:\n var smpDiffR = sampleValueR - this.prevSampleR;\n this.prevSampleR += smpDiffR;\n this.smpAccumR += smpDiffR - (this.smpAccumR >> 10);\n sampleValueR = this.smpAccumR;\n\n // Write:\n if (sampleValueL > this.maxSample) {\n this.maxSample = sampleValueL;\n }\n if (sampleValueL < this.minSample) {\n this.minSample = sampleValueL;\n }\n\n if (this.nes.opts.onAudioSample) {\n this.nes.opts.onAudioSample(sampleValueL / 32768, sampleValueR / 32768);\n }\n\n // Reset sampled values:\n this.smpSquare1 = 0;\n this.smpSquare2 = 0;\n this.smpTriangle = 0;\n this.smpDmc = 0;\n },\n\n getLengthMax: function(value) {\n return this.lengthLookup[value >> 3];\n },\n\n getDmcFrequency: function(value) {\n if (value >= 0 && value < 0x10) {\n return this.dmcFreqLookup[value];\n }\n return 0;\n },\n\n getNoiseWaveLength: function(value) {\n if (value >= 0 && value < 0x10) {\n return this.noiseWavelengthLookup[value];\n }\n return 0;\n },\n\n setPanning: function(pos) {\n for (var i = 0; i < 5; i++) {\n this.panning[i] = pos[i];\n }\n this.updateStereoPos();\n },\n\n setMasterVolume: function(value) {\n if (value < 0) {\n value = 0;\n }\n if (value > 256) {\n value = 256;\n }\n this.masterVolume = value;\n this.updateStereoPos();\n },\n\n updateStereoPos: function() {\n this.stereoPosLSquare1 = (this.panning[0] * this.masterVolume) >> 8;\n this.stereoPosLSquare2 = (this.panning[1] * this.masterVolume) >> 8;\n this.stereoPosLTriangle = (this.panning[2] * this.masterVolume) >> 8;\n this.stereoPosLNoise = (this.panning[3] * this.masterVolume) >> 8;\n this.stereoPosLDMC = (this.panning[4] * this.masterVolume) >> 8;\n\n this.stereoPosRSquare1 = this.masterVolume - this.stereoPosLSquare1;\n this.stereoPosRSquare2 = this.masterVolume - this.stereoPosLSquare2;\n this.stereoPosRTriangle = this.masterVolume - this.stereoPosLTriangle;\n this.stereoPosRNoise = this.masterVolume - this.stereoPosLNoise;\n this.stereoPosRDMC = this.masterVolume - this.stereoPosLDMC;\n },\n\n initLengthLookup: function() {\n // prettier-ignore\n this.lengthLookup = [\n 0x0A, 0xFE,\n 0x14, 0x02,\n 0x28, 0x04,\n 0x50, 0x06,\n 0xA0, 0x08,\n 0x3C, 0x0A,\n 0x0E, 0x0C,\n 0x1A, 0x0E,\n 0x0C, 0x10,\n 0x18, 0x12,\n 0x30, 0x14,\n 0x60, 0x16,\n 0xC0, 0x18,\n 0x48, 0x1A,\n 0x10, 0x1C,\n 0x20, 0x1E\n ];\n },\n\n initDmcFrequencyLookup: function() {\n this.dmcFreqLookup = new Array(16);\n\n this.dmcFreqLookup[0x0] = 0xd60;\n this.dmcFreqLookup[0x1] = 0xbe0;\n this.dmcFreqLookup[0x2] = 0xaa0;\n this.dmcFreqLookup[0x3] = 0xa00;\n this.dmcFreqLookup[0x4] = 0x8f0;\n this.dmcFreqLookup[0x5] = 0x7f0;\n this.dmcFreqLookup[0x6] = 0x710;\n this.dmcFreqLookup[0x7] = 0x6b0;\n this.dmcFreqLookup[0x8] = 0x5f0;\n this.dmcFreqLookup[0x9] = 0x500;\n this.dmcFreqLookup[0xa] = 0x470;\n this.dmcFreqLookup[0xb] = 0x400;\n this.dmcFreqLookup[0xc] = 0x350;\n this.dmcFreqLookup[0xd] = 0x2a0;\n this.dmcFreqLookup[0xe] = 0x240;\n this.dmcFreqLookup[0xf] = 0x1b0;\n //for(int i=0;i<16;i++)dmcFreqLookup[i]/=8;\n },\n\n initNoiseWavelengthLookup: function() {\n this.noiseWavelengthLookup = new Array(16);\n\n this.noiseWavelengthLookup[0x0] = 0x004;\n this.noiseWavelengthLookup[0x1] = 0x008;\n this.noiseWavelengthLookup[0x2] = 0x010;\n this.noiseWavelengthLookup[0x3] = 0x020;\n this.noiseWavelengthLookup[0x4] = 0x040;\n this.noiseWavelengthLookup[0x5] = 0x060;\n this.noiseWavelengthLookup[0x6] = 0x080;\n this.noiseWavelengthLookup[0x7] = 0x0a0;\n this.noiseWavelengthLookup[0x8] = 0x0ca;\n this.noiseWavelengthLookup[0x9] = 0x0fe;\n this.noiseWavelengthLookup[0xa] = 0x17c;\n this.noiseWavelengthLookup[0xb] = 0x1fc;\n this.noiseWavelengthLookup[0xc] = 0x2fa;\n this.noiseWavelengthLookup[0xd] = 0x3f8;\n this.noiseWavelengthLookup[0xe] = 0x7f2;\n this.noiseWavelengthLookup[0xf] = 0xfe4;\n },\n\n initDACtables: function() {\n var value, ival, i;\n var max_sqr = 0;\n var max_tnd = 0;\n\n this.square_table = new Float32Array(32 * 16);\n this.tnd_table = new Float32Array(204 * 16);\n\n for (i = 0; i < 32 * 16; i++) {\n value = 95.52 / (8128.0 / (i / 16.0) + 100.0);\n value *= 0.98411;\n value *= 50000.0;\n ival = Math.floor(value);\n\n this.square_table[i] = ival;\n if (ival > max_sqr) {\n max_sqr = ival;\n }\n }\n\n for (i = 0; i < 204 * 16; i++) {\n value = 163.67 / (24329.0 / (i / 16.0) + 100.0);\n value *= 0.98411;\n value *= 50000.0;\n ival = Math.floor(value);\n\n this.tnd_table[i] = ival;\n if (ival > max_tnd) {\n max_tnd = ival;\n }\n }\n\n this.dacRange = max_sqr + max_tnd;\n this.dcValue = this.dacRange / 2;\n }\n};\n\nvar ChannelDM = function(papu) {\n this.papu = papu;\n\n this.MODE_NORMAL = 0;\n this.MODE_LOOP = 1;\n this.MODE_IRQ = 2;\n\n this.isEnabled = null;\n this.hasSample = null;\n this.irqGenerated = false;\n\n this.playMode = null;\n this.dmaFrequency = null;\n this.dmaCounter = null;\n this.deltaCounter = null;\n this.playStartAddress = null;\n this.playAddress = null;\n this.playLength = null;\n this.playLengthCounter = null;\n this.shiftCounter = null;\n this.reg4012 = null;\n this.reg4013 = null;\n this.sample = null;\n this.dacLsb = null;\n this.data = null;\n\n this.reset();\n};\n\nChannelDM.prototype = {\n clockDmc: function() {\n // Only alter DAC value if the sample buffer has data:\n if (this.hasSample) {\n if ((this.data & 1) === 0) {\n // Decrement delta:\n if (this.deltaCounter > 0) {\n this.deltaCounter--;\n }\n } else {\n // Increment delta:\n if (this.deltaCounter < 63) {\n this.deltaCounter++;\n }\n }\n\n // Update sample value:\n this.sample = this.isEnabled ? (this.deltaCounter << 1) + this.dacLsb : 0;\n\n // Update shift register:\n this.data >>= 1;\n }\n\n this.dmaCounter--;\n if (this.dmaCounter <= 0) {\n // No more sample bits.\n this.hasSample = false;\n this.endOfSample();\n this.dmaCounter = 8;\n }\n\n if (this.irqGenerated) {\n this.papu.nes.cpu.requestIrq(this.papu.nes.cpu.IRQ_NORMAL);\n }\n },\n\n endOfSample: function() {\n if (this.playLengthCounter === 0 && this.playMode === this.MODE_LOOP) {\n // Start from beginning of sample:\n this.playAddress = this.playStartAddress;\n this.playLengthCounter = this.playLength;\n }\n\n if (this.playLengthCounter > 0) {\n // Fetch next sample:\n this.nextSample();\n\n if (this.playLengthCounter === 0) {\n // Last byte of sample fetched, generate IRQ:\n if (this.playMode === this.MODE_IRQ) {\n // Generate IRQ:\n this.irqGenerated = true;\n }\n }\n }\n },\n\n nextSample: function() {\n // Fetch byte:\n this.data = this.papu.nes.mmap.load(this.playAddress);\n this.papu.nes.cpu.haltCycles(4);\n\n this.playLengthCounter--;\n this.playAddress++;\n if (this.playAddress > 0xffff) {\n this.playAddress = 0x8000;\n }\n\n this.hasSample = true;\n },\n\n writeReg: function(address, value) {\n if (address === 0x4010) {\n // Play mode, DMA Frequency\n if (value >> 6 === 0) {\n this.playMode = this.MODE_NORMAL;\n } else if (((value >> 6) & 1) === 1) {\n this.playMode = this.MODE_LOOP;\n } else if (value >> 6 === 2) {\n this.playMode = this.MODE_IRQ;\n }\n\n if ((value & 0x80) === 0) {\n this.irqGenerated = false;\n }\n\n this.dmaFrequency = this.papu.getDmcFrequency(value & 0xf);\n } else if (address === 0x4011) {\n // Delta counter load register:\n this.deltaCounter = (value >> 1) & 63;\n this.dacLsb = value & 1;\n this.sample = (this.deltaCounter << 1) + this.dacLsb; // update sample value\n } else if (address === 0x4012) {\n // DMA address load register\n this.playStartAddress = (value << 6) | 0x0c000;\n this.playAddress = this.playStartAddress;\n this.reg4012 = value;\n } else if (address === 0x4013) {\n // Length of play code\n this.playLength = (value << 4) + 1;\n this.playLengthCounter = this.playLength;\n this.reg4013 = value;\n } else if (address === 0x4015) {\n // DMC/IRQ Status\n if (((value >> 4) & 1) === 0) {\n // Disable:\n this.playLengthCounter = 0;\n } else {\n // Restart:\n this.playAddress = this.playStartAddress;\n this.playLengthCounter = this.playLength;\n }\n this.irqGenerated = false;\n }\n },\n\n setEnabled: function(value) {\n if (!this.isEnabled && value) {\n this.playLengthCounter = this.playLength;\n }\n this.isEnabled = value;\n },\n\n getLengthStatus: function() {\n return this.playLengthCounter === 0 || !this.isEnabled ? 0 : 1;\n },\n\n getIrqStatus: function() {\n return this.irqGenerated ? 1 : 0;\n },\n\n reset: function() {\n this.isEnabled = false;\n this.irqGenerated = false;\n this.playMode = this.MODE_NORMAL;\n this.dmaFrequency = 0;\n this.dmaCounter = 0;\n this.deltaCounter = 0;\n this.playStartAddress = 0;\n this.playAddress = 0;\n this.playLength = 0;\n this.playLengthCounter = 0;\n this.sample = 0;\n this.dacLsb = 0;\n this.shiftCounter = 0;\n this.reg4012 = 0;\n this.reg4013 = 0;\n this.data = 0;\n }\n};\n\nvar ChannelNoise = function(papu) {\n this.papu = papu;\n\n this.isEnabled = null;\n this.envDecayDisable = null;\n this.envDecayLoopEnable = null;\n this.lengthCounterEnable = null;\n this.envReset = null;\n this.shiftNow = null;\n\n this.lengthCounter = null;\n this.progTimerCount = null;\n this.progTimerMax = null;\n this.envDecayRate = null;\n this.envDecayCounter = null;\n this.envVolume = null;\n this.masterVolume = null;\n this.shiftReg = 1 << 14;\n this.randomBit = null;\n this.randomMode = null;\n this.sampleValue = null;\n this.accValue = 0;\n this.accCount = 1;\n this.tmp = null;\n\n this.reset();\n};\n\nChannelNoise.prototype = {\n reset: function() {\n this.progTimerCount = 0;\n this.progTimerMax = 0;\n this.isEnabled = false;\n this.lengthCounter = 0;\n this.lengthCounterEnable = false;\n this.envDecayDisable = false;\n this.envDecayLoopEnable = false;\n this.shiftNow = false;\n this.envDecayRate = 0;\n this.envDecayCounter = 0;\n this.envVolume = 0;\n this.masterVolume = 0;\n this.shiftReg = 1;\n this.randomBit = 0;\n this.randomMode = 0;\n this.sampleValue = 0;\n this.tmp = 0;\n },\n\n clockLengthCounter: function() {\n if (this.lengthCounterEnable && this.lengthCounter > 0) {\n this.lengthCounter--;\n if (this.lengthCounter === 0) {\n this.updateSampleValue();\n }\n }\n },\n\n clockEnvDecay: function() {\n if (this.envReset) {\n // Reset envelope:\n this.envReset = false;\n this.envDecayCounter = this.envDecayRate + 1;\n this.envVolume = 0xf;\n } else if (--this.envDecayCounter <= 0) {\n // Normal handling:\n this.envDecayCounter = this.envDecayRate + 1;\n if (this.envVolume > 0) {\n this.envVolume--;\n } else {\n this.envVolume = this.envDecayLoopEnable ? 0xf : 0;\n }\n }\n if (this.envDecayDisable) {\n this.masterVolume = this.envDecayRate;\n } else {\n this.masterVolume = this.envVolume;\n }\n this.updateSampleValue();\n },\n\n updateSampleValue: function() {\n if (this.isEnabled && this.lengthCounter > 0) {\n this.sampleValue = this.randomBit * this.masterVolume;\n }\n },\n\n writeReg: function(address, value) {\n if (address === 0x400c) {\n // Volume/Envelope decay:\n this.envDecayDisable = (value & 0x10) !== 0;\n this.envDecayRate = value & 0xf;\n this.envDecayLoopEnable = (value & 0x20) !== 0;\n this.lengthCounterEnable = (value & 0x20) === 0;\n if (this.envDecayDisable) {\n this.masterVolume = this.envDecayRate;\n } else {\n this.masterVolume = this.envVolume;\n }\n } else if (address === 0x400e) {\n // Programmable timer:\n this.progTimerMax = this.papu.getNoiseWaveLength(value & 0xf);\n this.randomMode = value >> 7;\n } else if (address === 0x400f) {\n // Length counter\n this.lengthCounter = this.papu.getLengthMax(value & 248);\n this.envReset = true;\n }\n // Update:\n //updateSampleValue();\n },\n\n setEnabled: function(value) {\n this.isEnabled = value;\n if (!value) {\n this.lengthCounter = 0;\n }\n this.updateSampleValue();\n },\n\n getLengthStatus: function() {\n return this.lengthCounter === 0 || !this.isEnabled ? 0 : 1;\n }\n};\n\nvar ChannelSquare = function(papu, square1) {\n this.papu = papu;\n\n // prettier-ignore\n this.dutyLookup = [\n 0, 1, 0, 0, 0, 0, 0, 0,\n 0, 1, 1, 0, 0, 0, 0, 0,\n 0, 1, 1, 1, 1, 0, 0, 0,\n 1, 0, 0, 1, 1, 1, 1, 1\n ];\n // prettier-ignore\n this.impLookup = [\n 1,-1, 0, 0, 0, 0, 0, 0,\n 1, 0,-1, 0, 0, 0, 0, 0,\n 1, 0, 0, 0,-1, 0, 0, 0,\n -1, 0, 1, 0, 0, 0, 0, 0\n ];\n\n this.sqr1 = square1;\n this.isEnabled = null;\n this.lengthCounterEnable = null;\n this.sweepActive = null;\n this.envDecayDisable = null;\n this.envDecayLoopEnable = null;\n this.envReset = null;\n this.sweepCarry = null;\n this.updateSweepPeriod = null;\n\n this.progTimerCount = null;\n this.progTimerMax = null;\n this.lengthCounter = null;\n this.squareCounter = null;\n this.sweepCounter = null;\n this.sweepCounterMax = null;\n this.sweepMode = null;\n this.sweepShiftAmount = null;\n this.envDecayRate = null;\n this.envDecayCounter = null;\n this.envVolume = null;\n this.masterVolume = null;\n this.dutyMode = null;\n this.sweepResult = null;\n this.sampleValue = null;\n this.vol = null;\n\n this.reset();\n};\n\nChannelSquare.prototype = {\n reset: function() {\n this.progTimerCount = 0;\n this.progTimerMax = 0;\n this.lengthCounter = 0;\n this.squareCounter = 0;\n this.sweepCounter = 0;\n this.sweepCounterMax = 0;\n this.sweepMode = 0;\n this.sweepShiftAmount = 0;\n this.envDecayRate = 0;\n this.envDecayCounter = 0;\n this.envVolume = 0;\n this.masterVolume = 0;\n this.dutyMode = 0;\n this.vol = 0;\n\n this.isEnabled = false;\n this.lengthCounterEnable = false;\n this.sweepActive = false;\n this.sweepCarry = false;\n this.envDecayDisable = false;\n this.envDecayLoopEnable = false;\n },\n\n clockLengthCounter: function() {\n if (this.lengthCounterEnable && this.lengthCounter > 0) {\n this.lengthCounter--;\n if (this.lengthCounter === 0) {\n this.updateSampleValue();\n }\n }\n },\n\n clockEnvDecay: function() {\n if (this.envReset) {\n // Reset envelope:\n this.envReset = false;\n this.envDecayCounter = this.envDecayRate + 1;\n this.envVolume = 0xf;\n } else if (--this.envDecayCounter <= 0) {\n // Normal handling:\n this.envDecayCounter = this.envDecayRate + 1;\n if (this.envVolume > 0) {\n this.envVolume--;\n } else {\n this.envVolume = this.envDecayLoopEnable ? 0xf : 0;\n }\n }\n\n if (this.envDecayDisable) {\n this.masterVolume = this.envDecayRate;\n } else {\n this.masterVolume = this.envVolume;\n }\n this.updateSampleValue();\n },\n\n clockSweep: function() {\n if (--this.sweepCounter <= 0) {\n this.sweepCounter = this.sweepCounterMax + 1;\n if (\n this.sweepActive &&\n this.sweepShiftAmount > 0 &&\n this.progTimerMax > 7\n ) {\n // Calculate result from shifter:\n this.sweepCarry = false;\n if (this.sweepMode === 0) {\n this.progTimerMax += this.progTimerMax >> this.sweepShiftAmount;\n if (this.progTimerMax > 4095) {\n this.progTimerMax = 4095;\n this.sweepCarry = true;\n }\n } else {\n this.progTimerMax =\n this.progTimerMax -\n ((this.progTimerMax >> this.sweepShiftAmount) -\n (this.sqr1 ? 1 : 0));\n }\n }\n }\n\n if (this.updateSweepPeriod) {\n this.updateSweepPeriod = false;\n this.sweepCounter = this.sweepCounterMax + 1;\n }\n },\n\n updateSampleValue: function() {\n if (this.isEnabled && this.lengthCounter > 0 && this.progTimerMax > 7) {\n if (\n this.sweepMode === 0 &&\n this.progTimerMax + (this.progTimerMax >> this.sweepShiftAmount) > 4095\n ) {\n //if (this.sweepCarry) {\n this.sampleValue = 0;\n } else {\n this.sampleValue =\n this.masterVolume *\n this.dutyLookup[(this.dutyMode << 3) + this.squareCounter];\n }\n } else {\n this.sampleValue = 0;\n }\n },\n\n writeReg: function(address, value) {\n var addrAdd = this.sqr1 ? 0 : 4;\n if (address === 0x4000 + addrAdd) {\n // Volume/Envelope decay:\n this.envDecayDisable = (value & 0x10) !== 0;\n this.envDecayRate = value & 0xf;\n this.envDecayLoopEnable = (value & 0x20) !== 0;\n this.dutyMode = (value >> 6) & 0x3;\n this.lengthCounterEnable = (value & 0x20) === 0;\n if (this.envDecayDisable) {\n this.masterVolume = this.envDecayRate;\n } else {\n this.masterVolume = this.envVolume;\n }\n this.updateSampleValue();\n } else if (address === 0x4001 + addrAdd) {\n // Sweep:\n this.sweepActive = (value & 0x80) !== 0;\n this.sweepCounterMax = (value >> 4) & 7;\n this.sweepMode = (value >> 3) & 1;\n this.sweepShiftAmount = value & 7;\n this.updateSweepPeriod = true;\n } else if (address === 0x4002 + addrAdd) {\n // Programmable timer:\n this.progTimerMax &= 0x700;\n this.progTimerMax |= value;\n } else if (address === 0x4003 + addrAdd) {\n // Programmable timer, length counter\n this.progTimerMax &= 0xff;\n this.progTimerMax |= (value & 0x7) << 8;\n\n if (this.isEnabled) {\n this.lengthCounter = this.papu.getLengthMax(value & 0xf8);\n }\n\n this.envReset = true;\n }\n },\n\n setEnabled: function(value) {\n this.isEnabled = value;\n if (!value) {\n this.lengthCounter = 0;\n }\n this.updateSampleValue();\n },\n\n getLengthStatus: function() {\n return this.lengthCounter === 0 || !this.isEnabled ? 0 : 1;\n }\n};\n\nvar ChannelTriangle = function(papu) {\n this.papu = papu;\n\n this.isEnabled = null;\n this.sampleCondition = null;\n this.lengthCounterEnable = null;\n this.lcHalt = null;\n this.lcControl = null;\n\n this.progTimerCount = null;\n this.progTimerMax = null;\n this.triangleCounter = null;\n this.lengthCounter = null;\n this.linearCounter = null;\n this.lcLoadValue = null;\n this.sampleValue = null;\n this.tmp = null;\n\n this.reset();\n};\n\nChannelTriangle.prototype = {\n reset: function() {\n this.progTimerCount = 0;\n this.progTimerMax = 0;\n this.triangleCounter = 0;\n this.isEnabled = false;\n this.sampleCondition = false;\n this.lengthCounter = 0;\n this.lengthCounterEnable = false;\n this.linearCounter = 0;\n this.lcLoadValue = 0;\n this.lcHalt = true;\n this.lcControl = false;\n this.tmp = 0;\n this.sampleValue = 0xf;\n },\n\n clockLengthCounter: function() {\n if (this.lengthCounterEnable && this.lengthCounter > 0) {\n this.lengthCounter--;\n if (this.lengthCounter === 0) {\n this.updateSampleCondition();\n }\n }\n },\n\n clockLinearCounter: function() {\n if (this.lcHalt) {\n // Load:\n this.linearCounter = this.lcLoadValue;\n this.updateSampleCondition();\n } else if (this.linearCounter > 0) {\n // Decrement:\n this.linearCounter--;\n this.updateSampleCondition();\n }\n if (!this.lcControl) {\n // Clear halt flag:\n this.lcHalt = false;\n }\n },\n\n getLengthStatus: function() {\n return this.lengthCounter === 0 || !this.isEnabled ? 0 : 1;\n },\n\n // eslint-disable-next-line no-unused-vars\n readReg: function(address) {\n return 0;\n },\n\n writeReg: function(address, value) {\n if (address === 0x4008) {\n // New values for linear counter:\n this.lcControl = (value & 0x80) !== 0;\n this.lcLoadValue = value & 0x7f;\n\n // Length counter enable:\n this.lengthCounterEnable = !this.lcControl;\n } else if (address === 0x400a) {\n // Programmable timer:\n this.progTimerMax &= 0x700;\n this.progTimerMax |= value;\n } else if (address === 0x400b) {\n // Programmable timer, length counter\n this.progTimerMax &= 0xff;\n this.progTimerMax |= (value & 0x07) << 8;\n this.lengthCounter = this.papu.getLengthMax(value & 0xf8);\n this.lcHalt = true;\n }\n\n this.updateSampleCondition();\n },\n\n clockProgrammableTimer: function(nCycles) {\n if (this.progTimerMax > 0) {\n this.progTimerCount += nCycles;\n while (\n this.progTimerMax > 0 &&\n this.progTimerCount >= this.progTimerMax\n ) {\n this.progTimerCount -= this.progTimerMax;\n if (\n this.isEnabled &&\n this.lengthCounter > 0 &&\n this.linearCounter > 0\n ) {\n this.clockTriangleGenerator();\n }\n }\n }\n },\n\n clockTriangleGenerator: function() {\n this.triangleCounter++;\n this.triangleCounter &= 0x1f;\n },\n\n setEnabled: function(value) {\n this.isEnabled = value;\n if (!value) {\n this.lengthCounter = 0;\n }\n this.updateSampleCondition();\n },\n\n updateSampleCondition: function() {\n this.sampleCondition =\n this.isEnabled &&\n this.progTimerMax > 7 &&\n this.linearCounter > 0 &&\n this.lengthCounter > 0;\n }\n};\n\nmodule.exports = PAPU;\n", "var utils = require(\"./utils\");\n\nvar Mappers = {};\n\nMappers[0] = function(nes) {\n this.nes = nes;\n};\n\nMappers[0].prototype = {\n reset: function() {\n this.joy1StrobeState = 0;\n this.joy2StrobeState = 0;\n this.joypadLastWrite = 0;\n\n this.zapperFired = false;\n this.zapperX = null;\n this.zapperY = null;\n },\n\n write: function(address, value) {\n if (address < 0x2000) {\n // Mirroring of RAM:\n this.nes.cpu.mem[address & 0x7ff] = value;\n } else if (address > 0x4017) {\n this.nes.cpu.mem[address] = value;\n if (address >= 0x6000 && address < 0x8000) {\n // Write to persistent RAM\n this.nes.opts.onBatteryRamWrite(address, value);\n }\n } else if (address > 0x2007 && address < 0x4000) {\n this.regWrite(0x2000 + (address & 0x7), value);\n } else {\n this.regWrite(address, value);\n }\n },\n\n writelow: function(address, value) {\n if (address < 0x2000) {\n // Mirroring of RAM:\n this.nes.cpu.mem[address & 0x7ff] = value;\n } else if (address > 0x4017) {\n this.nes.cpu.mem[address] = value;\n } else if (address > 0x2007 && address < 0x4000) {\n this.regWrite(0x2000 + (address & 0x7), value);\n } else {\n this.regWrite(address, value);\n }\n },\n\n load: function(address) {\n // Wrap around:\n address &= 0xffff;\n\n // Check address range:\n if (address > 0x4017) {\n // ROM:\n return this.nes.cpu.mem[address];\n } else if (address >= 0x2000) {\n // I/O Ports.\n return this.regLoad(address);\n } else {\n // RAM (mirrored)\n return this.nes.cpu.mem[address & 0x7ff];\n }\n },\n\n regLoad: function(address) {\n switch (\n address >> 12 // use fourth nibble (0xF000)\n ) {\n case 0:\n break;\n\n case 1:\n break;\n\n case 2:\n // Fall through to case 3\n case 3:\n // PPU Registers\n switch (address & 0x7) {\n case 0x0:\n // 0x2000:\n // PPU Control Register 1.\n // (the value is stored both\n // in main memory and in the\n // PPU as flags):\n // (not in the real NES)\n return this.nes.cpu.mem[0x2000];\n\n case 0x1:\n // 0x2001:\n // PPU Control Register 2.\n // (the value is stored both\n // in main memory and in the\n // PPU as flags):\n // (not in the real NES)\n return this.nes.cpu.mem[0x2001];\n\n case 0x2:\n // 0x2002:\n // PPU Status Register.\n // The value is stored in\n // main memory in addition\n // to as flags in the PPU.\n // (not in the real NES)\n return this.nes.ppu.readStatusRegister();\n\n case 0x3:\n return 0;\n\n case 0x4:\n // 0x2004:\n // Sprite Memory read.\n return this.nes.ppu.sramLoad();\n case 0x5:\n return 0;\n\n case 0x6:\n return 0;\n\n case 0x7:\n // 0x2007:\n // VRAM read:\n return this.nes.ppu.vramLoad();\n }\n break;\n case 4:\n // Sound+Joypad registers\n switch (address - 0x4015) {\n case 0:\n // 0x4015:\n // Sound channel enable, DMC Status\n return this.nes.papu.readReg(address);\n\n case 1:\n // 0x4016:\n // Joystick 1 + Strobe\n return this.joy1Read();\n\n case 2:\n // 0x4017:\n // Joystick 2 + Strobe\n // https://wiki.nesdev.com/w/index.php/Zapper\n var w;\n\n if (\n this.zapperX !== null &&\n this.zapperY !== null &&\n this.nes.ppu.isPixelWhite(this.zapperX, this.zapperY)\n ) {\n w = 0;\n } else {\n w = 0x1 << 3;\n }\n\n if (this.zapperFired) {\n w |= 0x1 << 4;\n }\n return (this.joy2Read() | w) & 0xffff;\n }\n break;\n }\n return 0;\n },\n\n regWrite: function(address, value) {\n switch (address) {\n case 0x2000:\n // PPU Control register 1\n this.nes.cpu.mem[address] = value;\n this.nes.ppu.updateControlReg1(value);\n break;\n\n case 0x2001:\n // PPU Control register 2\n this.nes.cpu.mem[address] = value;\n this.nes.ppu.updateControlReg2(value);\n break;\n\n case 0x2003:\n // Set Sprite RAM address:\n this.nes.ppu.writeSRAMAddress(value);\n break;\n\n case 0x2004:\n // Write to Sprite RAM:\n this.nes.ppu.sramWrite(value);\n break;\n\n case 0x2005:\n // Screen Scroll offsets:\n this.nes.ppu.scrollWrite(value);\n break;\n\n case 0x2006:\n // Set VRAM address:\n this.nes.ppu.writeVRAMAddress(value);\n break;\n\n case 0x2007:\n // Write to VRAM:\n this.nes.ppu.vramWrite(value);\n break;\n\n case 0x4014:\n // Sprite Memory DMA Access\n this.nes.ppu.sramDMA(value);\n break;\n\n case 0x4015:\n // Sound Channel Switch, DMC Status\n this.nes.papu.writeReg(address, value);\n break;\n\n case 0x4016:\n // Joystick 1 + Strobe\n if ((value & 1) === 0 && (this.joypadLastWrite & 1) === 1) {\n this.joy1StrobeState = 0;\n this.joy2StrobeState = 0;\n }\n this.joypadLastWrite = value;\n break;\n\n case 0x4017:\n // Sound channel frame sequencer:\n this.nes.papu.writeReg(address, value);\n break;\n\n default:\n // Sound registers\n // console.log(\"write to sound reg\");\n if (address >= 0x4000 && address <= 0x4017) {\n this.nes.papu.writeReg(address, value);\n }\n }\n },\n\n joy1Read: function() {\n var ret;\n\n switch (this.joy1StrobeState) {\n case 0:\n case 1:\n case 2:\n case 3:\n case 4:\n case 5:\n case 6:\n case 7:\n ret = this.nes.controllers[1].state[this.joy1StrobeState];\n break;\n case 8:\n case 9:\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n ret = 0;\n break;\n case 19:\n ret = 1;\n break;\n default:\n ret = 0;\n }\n\n this.joy1StrobeState++;\n if (this.joy1StrobeState === 24) {\n this.joy1StrobeState = 0;\n }\n\n return ret;\n },\n\n joy2Read: function() {\n var ret;\n\n switch (this.joy2StrobeState) {\n case 0:\n case 1:\n case 2:\n case 3:\n case 4:\n case 5:\n case 6:\n case 7:\n ret = this.nes.controllers[2].state[this.joy2StrobeState];\n break;\n case 8:\n case 9:\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n ret = 0;\n break;\n case 19:\n ret = 1;\n break;\n default:\n ret = 0;\n }\n\n this.joy2StrobeState++;\n if (this.joy2StrobeState === 24) {\n this.joy2StrobeState = 0;\n }\n\n return ret;\n },\n\n loadROM: function() {\n if (!this.nes.rom.valid || this.nes.rom.romCount < 1) {\n throw new Error(\"NoMapper: Invalid ROM! Unable to load.\");\n }\n\n // Load ROM into memory:\n this.loadPRGROM();\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Load Battery RAM (if present):\n this.loadBatteryRam();\n\n // Reset IRQ:\n //nes.getCpu().doResetInterrupt();\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n },\n\n loadPRGROM: function() {\n if (this.nes.rom.romCount > 1) {\n // Load the two first banks into memory.\n this.loadRomBank(0, 0x8000);\n this.loadRomBank(1, 0xc000);\n } else {\n // Load the one bank into both memory locations:\n this.loadRomBank(0, 0x8000);\n this.loadRomBank(0, 0xc000);\n }\n },\n\n loadCHRROM: function() {\n // console.log(\"Loading CHR ROM..\");\n if (this.nes.rom.vromCount > 0) {\n if (this.nes.rom.vromCount === 1) {\n this.loadVromBank(0, 0x0000);\n this.loadVromBank(0, 0x1000);\n } else {\n this.loadVromBank(0, 0x0000);\n this.loadVromBank(1, 0x1000);\n }\n } else {\n //System.out.println(\"There aren't any CHR-ROM banks..\");\n }\n },\n\n loadBatteryRam: function() {\n if (this.nes.rom.batteryRam) {\n var ram = this.nes.rom.batteryRam;\n if (ram !== null && ram.length === 0x2000) {\n // Load Battery RAM into memory:\n utils.copyArrayElements(ram, 0, this.nes.cpu.mem, 0x6000, 0x2000);\n }\n }\n },\n\n loadRomBank: function(bank, address) {\n // Loads a ROM bank into the specified address.\n bank %= this.nes.rom.romCount;\n //var data = this.nes.rom.rom[bank];\n //cpuMem.write(address,data,data.length);\n utils.copyArrayElements(\n this.nes.rom.rom[bank],\n 0,\n this.nes.cpu.mem,\n address,\n 16384\n );\n },\n\n loadVromBank: function(bank, address) {\n if (this.nes.rom.vromCount === 0) {\n return;\n }\n this.nes.ppu.triggerRendering();\n\n utils.copyArrayElements(\n this.nes.rom.vrom[bank % this.nes.rom.vromCount],\n 0,\n this.nes.ppu.vramMem,\n address,\n 4096\n );\n\n var vromTile = this.nes.rom.vromTile[bank % this.nes.rom.vromCount];\n utils.copyArrayElements(\n vromTile,\n 0,\n this.nes.ppu.ptTile,\n address >> 4,\n 256\n );\n },\n\n load32kRomBank: function(bank, address) {\n this.loadRomBank((bank * 2) % this.nes.rom.romCount, address);\n this.loadRomBank((bank * 2 + 1) % this.nes.rom.romCount, address + 16384);\n },\n\n load8kVromBank: function(bank4kStart, address) {\n if (this.nes.rom.vromCount === 0) {\n return;\n }\n this.nes.ppu.triggerRendering();\n\n this.loadVromBank(bank4kStart % this.nes.rom.vromCount, address);\n this.loadVromBank(\n (bank4kStart + 1) % this.nes.rom.vromCount,\n address + 4096\n );\n },\n\n load1kVromBank: function(bank1k, address) {\n if (this.nes.rom.vromCount === 0) {\n return;\n }\n this.nes.ppu.triggerRendering();\n\n var bank4k = Math.floor(bank1k / 4) % this.nes.rom.vromCount;\n var bankoffset = (bank1k % 4) * 1024;\n utils.copyArrayElements(\n this.nes.rom.vrom[bank4k],\n bankoffset,\n this.nes.ppu.vramMem,\n address,\n 1024\n );\n\n // Update tiles:\n var vromTile = this.nes.rom.vromTile[bank4k];\n var baseIndex = address >> 4;\n for (var i = 0; i < 64; i++) {\n this.nes.ppu.ptTile[baseIndex + i] = vromTile[(bank1k % 4 << 6) + i];\n }\n },\n\n load2kVromBank: function(bank2k, address) {\n if (this.nes.rom.vromCount === 0) {\n return;\n }\n this.nes.ppu.triggerRendering();\n\n var bank4k = Math.floor(bank2k / 2) % this.nes.rom.vromCount;\n var bankoffset = (bank2k % 2) * 2048;\n utils.copyArrayElements(\n this.nes.rom.vrom[bank4k],\n bankoffset,\n this.nes.ppu.vramMem,\n address,\n 2048\n );\n\n // Update tiles:\n var vromTile = this.nes.rom.vromTile[bank4k];\n var baseIndex = address >> 4;\n for (var i = 0; i < 128; i++) {\n this.nes.ppu.ptTile[baseIndex + i] = vromTile[(bank2k % 2 << 7) + i];\n }\n },\n\n load8kRomBank: function(bank8k, address) {\n var bank16k = Math.floor(bank8k / 2) % this.nes.rom.romCount;\n var offset = (bank8k % 2) * 8192;\n\n //this.nes.cpu.mem.write(address,this.nes.rom.rom[bank16k],offset,8192);\n utils.copyArrayElements(\n this.nes.rom.rom[bank16k],\n offset,\n this.nes.cpu.mem,\n address,\n 8192\n );\n },\n\n clockIrqCounter: function() {\n // Does nothing. This is used by the MMC3 mapper.\n },\n\n // eslint-disable-next-line no-unused-vars\n latchAccess: function(address) {\n // Does nothing. This is used by MMC2.\n },\n\n toJSON: function() {\n return {\n joy1StrobeState: this.joy1StrobeState,\n joy2StrobeState: this.joy2StrobeState,\n joypadLastWrite: this.joypadLastWrite\n };\n },\n\n fromJSON: function(s) {\n this.joy1StrobeState = s.joy1StrobeState;\n this.joy2StrobeState = s.joy2StrobeState;\n this.joypadLastWrite = s.joypadLastWrite;\n }\n};\n\nMappers[1] = function(nes) {\n this.nes = nes;\n};\n\nMappers[1].prototype = new Mappers[0]();\n\nMappers[1].prototype.reset = function() {\n Mappers[0].prototype.reset.apply(this);\n\n // 5-bit buffer:\n this.regBuffer = 0;\n this.regBufferCounter = 0;\n\n // Register 0:\n this.mirroring = 0;\n this.oneScreenMirroring = 0;\n this.prgSwitchingArea = 1;\n this.prgSwitchingSize = 1;\n this.vromSwitchingSize = 0;\n\n // Register 1:\n this.romSelectionReg0 = 0;\n\n // Register 2:\n this.romSelectionReg1 = 0;\n\n // Register 3:\n this.romBankSelect = 0;\n};\n\nMappers[1].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n }\n\n // See what should be done with the written value:\n if ((value & 128) !== 0) {\n // Reset buffering:\n this.regBufferCounter = 0;\n this.regBuffer = 0;\n\n // Reset register:\n if (this.getRegNumber(address) === 0) {\n this.prgSwitchingArea = 1;\n this.prgSwitchingSize = 1;\n }\n } else {\n // Continue buffering:\n //regBuffer = (regBuffer & (0xFF-(1<> 2) & 1;\n\n // PRG Switching Size:\n this.prgSwitchingSize = (value >> 3) & 1;\n\n // VROM Switching Size:\n this.vromSwitchingSize = (value >> 4) & 1;\n\n break;\n\n case 1:\n // ROM selection:\n this.romSelectionReg0 = (value >> 4) & 1;\n\n // Check whether the cart has VROM:\n if (this.nes.rom.vromCount > 0) {\n // Select VROM bank at 0x0000:\n if (this.vromSwitchingSize === 0) {\n // Swap 8kB VROM:\n if (this.romSelectionReg0 === 0) {\n this.load8kVromBank(value & 0xf, 0x0000);\n } else {\n this.load8kVromBank(\n Math.floor(this.nes.rom.vromCount / 2) + (value & 0xf),\n 0x0000\n );\n }\n } else {\n // Swap 4kB VROM:\n if (this.romSelectionReg0 === 0) {\n this.loadVromBank(value & 0xf, 0x0000);\n } else {\n this.loadVromBank(\n Math.floor(this.nes.rom.vromCount / 2) + (value & 0xf),\n 0x0000\n );\n }\n }\n }\n\n break;\n\n case 2:\n // ROM selection:\n this.romSelectionReg1 = (value >> 4) & 1;\n\n // Check whether the cart has VROM:\n if (this.nes.rom.vromCount > 0) {\n // Select VROM bank at 0x1000:\n if (this.vromSwitchingSize === 1) {\n // Swap 4kB of VROM:\n if (this.romSelectionReg1 === 0) {\n this.loadVromBank(value & 0xf, 0x1000);\n } else {\n this.loadVromBank(\n Math.floor(this.nes.rom.vromCount / 2) + (value & 0xf),\n 0x1000\n );\n }\n }\n }\n break;\n\n default:\n // Select ROM bank:\n // -------------------------\n tmp = value & 0xf;\n var bank;\n var baseBank = 0;\n\n if (this.nes.rom.romCount >= 32) {\n // 1024 kB cart\n if (this.vromSwitchingSize === 0) {\n if (this.romSelectionReg0 === 1) {\n baseBank = 16;\n }\n } else {\n baseBank =\n (this.romSelectionReg0 | (this.romSelectionReg1 << 1)) << 3;\n }\n } else if (this.nes.rom.romCount >= 16) {\n // 512 kB cart\n if (this.romSelectionReg0 === 1) {\n baseBank = 8;\n }\n }\n\n if (this.prgSwitchingSize === 0) {\n // 32kB\n bank = baseBank + (value & 0xf);\n this.load32kRomBank(bank, 0x8000);\n } else {\n // 16kB\n bank = baseBank * 2 + (value & 0xf);\n if (this.prgSwitchingArea === 0) {\n this.loadRomBank(bank, 0xc000);\n } else {\n this.loadRomBank(bank, 0x8000);\n }\n }\n }\n};\n\n// Returns the register number from the address written to:\nMappers[1].prototype.getRegNumber = function(address) {\n if (address >= 0x8000 && address <= 0x9fff) {\n return 0;\n } else if (address >= 0xa000 && address <= 0xbfff) {\n return 1;\n } else if (address >= 0xc000 && address <= 0xdfff) {\n return 2;\n } else {\n return 3;\n }\n};\n\nMappers[1].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"MMC1: Invalid ROM! Unable to load.\");\n }\n\n // Load PRG-ROM:\n this.loadRomBank(0, 0x8000); // First ROM bank..\n this.loadRomBank(this.nes.rom.romCount - 1, 0xc000); // ..and last ROM bank.\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Load Battery RAM (if present):\n this.loadBatteryRam();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\n// eslint-disable-next-line no-unused-vars\nMappers[1].prototype.switchLowHighPrgRom = function(oldSetting) {\n // not yet.\n};\n\nMappers[1].prototype.switch16to32 = function() {\n // not yet.\n};\n\nMappers[1].prototype.switch32to16 = function() {\n // not yet.\n};\n\nMappers[1].prototype.toJSON = function() {\n var s = Mappers[0].prototype.toJSON.apply(this);\n s.mirroring = this.mirroring;\n s.oneScreenMirroring = this.oneScreenMirroring;\n s.prgSwitchingArea = this.prgSwitchingArea;\n s.prgSwitchingSize = this.prgSwitchingSize;\n s.vromSwitchingSize = this.vromSwitchingSize;\n s.romSelectionReg0 = this.romSelectionReg0;\n s.romSelectionReg1 = this.romSelectionReg1;\n s.romBankSelect = this.romBankSelect;\n s.regBuffer = this.regBuffer;\n s.regBufferCounter = this.regBufferCounter;\n return s;\n};\n\nMappers[1].prototype.fromJSON = function(s) {\n Mappers[0].prototype.fromJSON.apply(this, arguments);\n this.mirroring = s.mirroring;\n this.oneScreenMirroring = s.oneScreenMirroring;\n this.prgSwitchingArea = s.prgSwitchingArea;\n this.prgSwitchingSize = s.prgSwitchingSize;\n this.vromSwitchingSize = s.vromSwitchingSize;\n this.romSelectionReg0 = s.romSelectionReg0;\n this.romSelectionReg1 = s.romSelectionReg1;\n this.romBankSelect = s.romBankSelect;\n this.regBuffer = s.regBuffer;\n this.regBufferCounter = s.regBufferCounter;\n};\n\nMappers[2] = function(nes) {\n this.nes = nes;\n};\n\nMappers[2].prototype = new Mappers[0]();\n\nMappers[2].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // This is a ROM bank select command.\n // Swap in the given ROM bank at 0x8000:\n this.loadRomBank(value, 0x8000);\n }\n};\n\nMappers[2].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"UNROM: Invalid ROM! Unable to load.\");\n }\n\n // Load PRG-ROM:\n this.loadRomBank(0, 0x8000);\n this.loadRomBank(this.nes.rom.romCount - 1, 0xc000);\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\n/**\n * Mapper 003 (CNROM)\n *\n * @constructor\n * @example Solomon's Key, Arkanoid, Arkista's Ring, Bump 'n' Jump, Cybernoid\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_003\n */\nMappers[3] = function(nes) {\n this.nes = nes;\n};\n\nMappers[3].prototype = new Mappers[0]();\n\nMappers[3].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // This is a ROM bank select command.\n // Swap in the given ROM bank at 0x8000:\n // This is a VROM bank select command.\n // Swap in the given VROM bank at 0x0000:\n var bank = (value % (this.nes.rom.vromCount / 2)) * 2;\n this.loadVromBank(bank, 0x0000);\n this.loadVromBank(bank + 1, 0x1000);\n this.load8kVromBank(value * 2, 0x0000);\n }\n};\n\nMappers[4] = function(nes) {\n this.nes = nes;\n\n this.CMD_SEL_2_1K_VROM_0000 = 0;\n this.CMD_SEL_2_1K_VROM_0800 = 1;\n this.CMD_SEL_1K_VROM_1000 = 2;\n this.CMD_SEL_1K_VROM_1400 = 3;\n this.CMD_SEL_1K_VROM_1800 = 4;\n this.CMD_SEL_1K_VROM_1C00 = 5;\n this.CMD_SEL_ROM_PAGE1 = 6;\n this.CMD_SEL_ROM_PAGE2 = 7;\n\n this.command = null;\n this.prgAddressSelect = null;\n this.chrAddressSelect = null;\n this.irqCounter = 0xff;\n this.irqLatchValue = 0xff;\n this.irqReload = 0;\n this.irqEnable = 0;\n this.prgAddressChanged = false;\n};\n\nMappers[4].prototype = new Mappers[0]();\n\nMappers[4].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n }\n\n switch (address & 0xe001) {\n case 0x8000:\n // Command/Address Select register\n this.command = value & 7;\n var tmp = (value >> 6) & 1;\n if (tmp !== this.prgAddressSelect) {\n this.prgAddressChanged = true;\n }\n this.prgAddressSelect = tmp;\n this.chrAddressSelect = (value >> 7) & 1;\n break;\n\n case 0x8001:\n // Page number for command\n this.executeCommand(this.command, value);\n break;\n\n case 0xa000:\n // Mirroring select\n if ((value & 1) !== 0) {\n this.nes.ppu.setMirroring(this.nes.rom.HORIZONTAL_MIRRORING);\n } else {\n this.nes.ppu.setMirroring(this.nes.rom.VERTICAL_MIRRORING);\n }\n break;\n\n case 0xa001:\n // SaveRAM Toggle\n // TODO\n //nes.getRom().setSaveState((value&1)!=0);\n break;\n\n case 0xc000:\n // IRQ Latch register\n this.irqLatchValue = value;\n break;\n\n case 0xc001:\n // IRQ Reload register (TODO: copy at next rising A12)\n this.irqReload = 1;\n break;\n\n case 0xe000:\n // IRQ Control Reg 0 (disable, ack)\n this.irqEnable = 0;\n break;\n\n case 0xe001:\n // IRQ Control Reg 1 (enable)\n this.irqEnable = 1;\n break;\n\n default:\n // Not a MMC3 register.\n // The game has probably crashed,\n // since it tries to write to ROM..\n // IGNORE.\n }\n};\n\nMappers[4].prototype.executeCommand = function(cmd, arg) {\n switch (cmd) {\n case this.CMD_SEL_2_1K_VROM_0000:\n // Select 2 1KB VROM pages at 0x0000:\n if (this.chrAddressSelect === 0) {\n this.load1kVromBank(arg, 0x0000);\n this.load1kVromBank(arg + 1, 0x0400);\n } else {\n this.load1kVromBank(arg, 0x1000);\n this.load1kVromBank(arg + 1, 0x1400);\n }\n break;\n\n case this.CMD_SEL_2_1K_VROM_0800:\n // Select 2 1KB VROM pages at 0x0800:\n if (this.chrAddressSelect === 0) {\n this.load1kVromBank(arg, 0x0800);\n this.load1kVromBank(arg + 1, 0x0c00);\n } else {\n this.load1kVromBank(arg, 0x1800);\n this.load1kVromBank(arg + 1, 0x1c00);\n }\n break;\n\n case this.CMD_SEL_1K_VROM_1000:\n // Select 1K VROM Page at 0x1000:\n if (this.chrAddressSelect === 0) {\n this.load1kVromBank(arg, 0x1000);\n } else {\n this.load1kVromBank(arg, 0x0000);\n }\n break;\n\n case this.CMD_SEL_1K_VROM_1400:\n // Select 1K VROM Page at 0x1400:\n if (this.chrAddressSelect === 0) {\n this.load1kVromBank(arg, 0x1400);\n } else {\n this.load1kVromBank(arg, 0x0400);\n }\n break;\n\n case this.CMD_SEL_1K_VROM_1800:\n // Select 1K VROM Page at 0x1800:\n if (this.chrAddressSelect === 0) {\n this.load1kVromBank(arg, 0x1800);\n } else {\n this.load1kVromBank(arg, 0x0800);\n }\n break;\n\n case this.CMD_SEL_1K_VROM_1C00:\n // Select 1K VROM Page at 0x1C00:\n if (this.chrAddressSelect === 0) {\n this.load1kVromBank(arg, 0x1c00);\n } else {\n this.load1kVromBank(arg, 0x0c00);\n }\n break;\n\n case this.CMD_SEL_ROM_PAGE1:\n if (this.prgAddressChanged) {\n // Load the two hardwired banks:\n if (this.prgAddressSelect === 0) {\n this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0xc000);\n } else {\n this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0x8000);\n }\n this.prgAddressChanged = false;\n }\n\n // Select first switchable ROM page:\n if (this.prgAddressSelect === 0) {\n this.load8kRomBank(arg, 0x8000);\n } else {\n this.load8kRomBank(arg, 0xc000);\n }\n break;\n\n case this.CMD_SEL_ROM_PAGE2:\n // Select second switchable ROM page:\n this.load8kRomBank(arg, 0xa000);\n\n // hardwire appropriate bank:\n if (this.prgAddressChanged) {\n // Load the two hardwired banks:\n if (this.prgAddressSelect === 0) {\n this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0xc000);\n } else {\n this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0x8000);\n }\n this.prgAddressChanged = false;\n }\n }\n};\n\nMappers[4].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"MMC3: Invalid ROM! Unable to load.\");\n }\n\n // Load hardwired PRG banks (0xC000 and 0xE000):\n this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0xc000);\n this.load8kRomBank((this.nes.rom.romCount - 1) * 2 + 1, 0xe000);\n\n // Load swappable PRG banks (0x8000 and 0xA000):\n this.load8kRomBank(0, 0x8000);\n this.load8kRomBank(1, 0xa000);\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Load Battery RAM (if present):\n this.loadBatteryRam();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\nMappers[4].prototype.clockIrqCounter = function() {\n if (this.irqReload === 1) {\n this.irqCounter = this.irqLatchValue;\n this.irqReload = 0;\n }\n this.irqCounter--;\n if (this.irqCounter < 0) {\n if (this.irqEnable === 1) {\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL);\n }\n this.irqCounter = this.irqLatchValue;\n }\n};\n\nMappers[4].prototype.toJSON = function() {\n var s = Mappers[0].prototype.toJSON.apply(this);\n s.command = this.command;\n s.prgAddressSelect = this.prgAddressSelect;\n s.chrAddressSelect = this.chrAddressSelect;\n s.irqReload = this.irqReload;\n s.irqCounter = this.irqCounter;\n s.irqLatchValue = this.irqLatchValue;\n s.irqEnable = this.irqEnable;\n s.prgAddressChanged = this.prgAddressChanged;\n return s;\n};\n\nMappers[4].prototype.fromJSON = function(s) {\n Mappers[0].prototype.fromJSON.apply(this, arguments);\n this.command = s.command;\n this.prgAddressSelect = s.prgAddressSelect;\n this.chrAddressSelect = s.chrAddressSelect;\n this.irqReload = s.irqReload;\n this.irqCounter = s.irqCounter;\n this.irqLatchValue = s.irqLatchValue;\n this.irqEnable = s.irqEnable;\n this.prgAddressChanged = s.prgAddressChanged;\n};\n\n/**\n * Mapper005 (MMC5,ExROM)\n *\n * @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\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_005\n * @constructor\n */\nMappers[5] = function(nes) {\n this.nes = nes;\n};\n\nMappers[5].prototype = new Mappers[0]();\n\nMappers[5].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n } else {\n this.load8kVromBank(value, 0x0000);\n }\n};\n\nMappers[5].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x5000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n }\n\n switch (address) {\n case 0x5100:\n this.prg_size = value & 3;\n break;\n case 0x5101:\n this.chr_size = value & 3;\n break;\n case 0x5102:\n this.sram_we_a = value & 3;\n break;\n case 0x5103:\n this.sram_we_b = value & 3;\n break;\n case 0x5104:\n this.graphic_mode = value & 3;\n break;\n case 0x5105:\n this.nametable_mode = value;\n this.nametable_type[0] = value & 3;\n this.load1kVromBank(value & 3, 0x2000);\n value >>= 2;\n this.nametable_type[1] = value & 3;\n this.load1kVromBank(value & 3, 0x2400);\n value >>= 2;\n this.nametable_type[2] = value & 3;\n this.load1kVromBank(value & 3, 0x2800);\n value >>= 2;\n this.nametable_type[3] = value & 3;\n this.load1kVromBank(value & 3, 0x2c00);\n break;\n case 0x5106:\n this.fill_chr = value;\n break;\n case 0x5107:\n this.fill_pal = value & 3;\n break;\n case 0x5113:\n this.SetBank_SRAM(3, value & 3);\n break;\n case 0x5114:\n case 0x5115:\n case 0x5116:\n case 0x5117:\n this.SetBank_CPU(address, value);\n break;\n case 0x5120:\n case 0x5121:\n case 0x5122:\n case 0x5123:\n case 0x5124:\n case 0x5125:\n case 0x5126:\n case 0x5127:\n this.chr_mode = 0;\n this.chr_page[0][address & 7] = value;\n this.SetBank_PPU();\n break;\n case 0x5128:\n case 0x5129:\n case 0x512a:\n case 0x512b:\n this.chr_mode = 1;\n this.chr_page[1][(address & 3) + 0] = value;\n this.chr_page[1][(address & 3) + 4] = value;\n this.SetBank_PPU();\n break;\n case 0x5200:\n this.split_control = value;\n break;\n case 0x5201:\n this.split_scroll = value;\n break;\n case 0x5202:\n this.split_page = value & 0x3f;\n break;\n case 0x5203:\n this.irq_line = value;\n this.nes.cpu.ClearIRQ();\n break;\n case 0x5204:\n this.irq_enable = value;\n this.nes.cpu.ClearIRQ();\n break;\n case 0x5205:\n this.mult_a = value;\n break;\n case 0x5206:\n this.mult_b = value;\n break;\n default:\n if (address >= 0x5000 && address <= 0x5015) {\n this.nes.papu.exWrite(address, value);\n } else if (address >= 0x5c00 && address <= 0x5fff) {\n if (this.graphic_mode === 2) {\n // ExRAM\n // vram write\n } else if (this.graphic_mode !== 3) {\n // Split,ExGraphic\n if (this.irq_status & 0x40) {\n // vram write\n } else {\n // vram write\n }\n }\n } else if (address >= 0x6000 && address <= 0x7fff) {\n if (this.sram_we_a === 2 && this.sram_we_b === 1) {\n // additional ram write\n }\n }\n break;\n }\n};\n\nMappers[5].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"UNROM: Invalid ROM! Unable to load.\");\n }\n\n // Load PRG-ROM:\n this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0x8000);\n this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xa000);\n this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xc000);\n this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xe000);\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\n/**\n * Mapper007 (AxROM)\n * @example Battletoads, Time Lord, Marble Madness\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_007\n * @constructor\n */\nMappers[7] = function(nes) {\n this.nes = nes;\n};\n\nMappers[7].prototype = new Mappers[0]();\n\nMappers[7].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n } else {\n this.load32kRomBank(value & 0x7, 0x8000);\n if (value & 0x10) {\n this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING2);\n } else {\n this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING);\n }\n }\n};\n\nMappers[7].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"AOROM: Invalid ROM! Unable to load.\");\n }\n\n // Load PRG-ROM:\n this.loadPRGROM();\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\n/**\n * Mapper 011 (Color Dreams)\n *\n * @description http://wiki.nesdev.com/w/index.php/Color_Dreams\n * @example Crystal Mines, Metal Fighter\n * @constructor\n */\nMappers[11] = function(nes) {\n this.nes = nes;\n};\n\nMappers[11].prototype = new Mappers[0]();\n\nMappers[11].prototype.write = function(address, value) {\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // Swap in the given PRG-ROM bank:\n var prgbank1 = ((value & 0xf) * 2) % this.nes.rom.romCount;\n var prgbank2 = ((value & 0xf) * 2 + 1) % this.nes.rom.romCount;\n\n this.loadRomBank(prgbank1, 0x8000);\n this.loadRomBank(prgbank2, 0xc000);\n\n if (this.nes.rom.vromCount > 0) {\n // Swap in the given VROM bank at 0x0000:\n var bank = ((value >> 4) * 2) % this.nes.rom.vromCount;\n this.loadVromBank(bank, 0x0000);\n this.loadVromBank(bank + 1, 0x1000);\n }\n }\n};\n\n/**\n * Mapper 034 (BNROM, NINA-01)\n *\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_034\n * @example Darkseed, Mashou, Mission Impossible 2\n * @constructor\n */\nMappers[34] = function(nes) {\n this.nes = nes;\n};\n\nMappers[34].prototype = new Mappers[0]();\n\nMappers[34].prototype.write = function(address, value) {\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n this.load32kRomBank(value, 0x8000);\n }\n};\n\n/**\n * Mapper 038\n *\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_038\n * @example Crime Busters\n * @constructor\n */\nMappers[38] = function(nes) {\n this.nes = nes;\n};\n\nMappers[38].prototype = new Mappers[0]();\n\nMappers[38].prototype.write = function(address, value) {\n if (address < 0x7000 || address > 0x7fff) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // Swap in the given PRG-ROM bank at 0x8000:\n this.load32kRomBank(value & 3, 0x8000);\n\n // Swap in the given VROM bank at 0x0000:\n this.load8kVromBank(((value >> 2) & 3) * 2, 0x0000);\n }\n};\n\n/**\n * Mapper 066 (GxROM)\n *\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_066\n * @example Doraemon, Dragon Power, Gumshoe, Thunder & Lightning,\n * Super Mario Bros. + Duck Hunt\n * @constructor\n */\nMappers[66] = function(nes) {\n this.nes = nes;\n};\n\nMappers[66].prototype = new Mappers[0]();\n\nMappers[66].prototype.write = function(address, value) {\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // Swap in the given PRG-ROM bank at 0x8000:\n this.load32kRomBank((value >> 4) & 3, 0x8000);\n\n // Swap in the given VROM bank at 0x0000:\n this.load8kVromBank((value & 3) * 2, 0x0000);\n }\n};\n\n/**\n * Mapper 094 (UN1ROM)\n *\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_094\n * @example Senjou no Ookami\n * @constructor\n */\nMappers[94] = function(nes) {\n this.nes = nes;\n};\n\nMappers[94].prototype = new Mappers[0]();\n\nMappers[94].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // This is a ROM bank select command.\n // Swap in the given ROM bank at 0x8000:\n this.loadRomBank(value >> 2, 0x8000);\n }\n};\n\nMappers[94].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"UN1ROM: Invalid ROM! Unable to load.\");\n }\n\n // Load PRG-ROM:\n this.loadRomBank(0, 0x8000);\n this.loadRomBank(this.nes.rom.romCount - 1, 0xc000);\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\n/**\n * Mapper 140\n *\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_140\n * @example Bio Senshi Dan - Increaser Tono Tatakai\n * @constructor\n */\nMappers[140] = function(nes) {\n this.nes = nes;\n};\n\nMappers[140].prototype = new Mappers[0]();\n\nMappers[140].prototype.write = function(address, value) {\n if (address < 0x6000 || address > 0x7fff) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // Swap in the given PRG-ROM bank at 0x8000:\n this.load32kRomBank((value >> 4) & 3, 0x8000);\n\n // Swap in the given VROM bank at 0x0000:\n this.load8kVromBank((value & 0xf) * 2, 0x0000);\n }\n};\n\n/**\n * Mapper 180\n *\n * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_180\n * @example Crazy Climber\n * @constructor\n */\nMappers[180] = function(nes) {\n this.nes = nes;\n};\n\nMappers[180].prototype = new Mappers[0]();\n\nMappers[180].prototype.write = function(address, value) {\n // Writes to addresses other than MMC registers are handled by NoMapper.\n if (address < 0x8000) {\n Mappers[0].prototype.write.apply(this, arguments);\n return;\n } else {\n // This is a ROM bank select command.\n // Swap in the given ROM bank at 0xc000:\n this.loadRomBank(value, 0xc000);\n }\n};\n\nMappers[180].prototype.loadROM = function() {\n if (!this.nes.rom.valid) {\n throw new Error(\"Mapper 180: Invalid ROM! Unable to load.\");\n }\n\n // Load PRG-ROM:\n this.loadRomBank(0, 0x8000);\n this.loadRomBank(this.nes.rom.romCount - 1, 0xc000);\n\n // Load CHR-ROM:\n this.loadCHRROM();\n\n // Do Reset-Interrupt:\n this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);\n};\n\nmodule.exports = Mappers;\n", "var Mappers = require(\"./mappers\");\nvar Tile = require(\"./tile\");\n\nvar ROM = function(nes) {\n this.nes = nes;\n\n this.mapperName = new Array(92);\n\n for (var i = 0; i < 92; i++) {\n this.mapperName[i] = \"Unknown Mapper\";\n }\n this.mapperName[0] = \"Direct Access\";\n this.mapperName[1] = \"Nintendo MMC1\";\n this.mapperName[2] = \"UNROM\";\n this.mapperName[3] = \"CNROM\";\n this.mapperName[4] = \"Nintendo MMC3\";\n this.mapperName[5] = \"Nintendo MMC5\";\n this.mapperName[6] = \"FFE F4xxx\";\n this.mapperName[7] = \"AOROM\";\n this.mapperName[8] = \"FFE F3xxx\";\n this.mapperName[9] = \"Nintendo MMC2\";\n this.mapperName[10] = \"Nintendo MMC4\";\n this.mapperName[11] = \"Color Dreams Chip\";\n this.mapperName[12] = \"FFE F6xxx\";\n this.mapperName[15] = \"100-in-1 switch\";\n this.mapperName[16] = \"Bandai chip\";\n this.mapperName[17] = \"FFE F8xxx\";\n this.mapperName[18] = \"Jaleco SS8806 chip\";\n this.mapperName[19] = \"Namcot 106 chip\";\n this.mapperName[20] = \"Famicom Disk System\";\n this.mapperName[21] = \"Konami VRC4a\";\n this.mapperName[22] = \"Konami VRC2a\";\n this.mapperName[23] = \"Konami VRC2a\";\n this.mapperName[24] = \"Konami VRC6\";\n this.mapperName[25] = \"Konami VRC4b\";\n this.mapperName[32] = \"Irem G-101 chip\";\n this.mapperName[33] = \"Taito TC0190/TC0350\";\n this.mapperName[34] = \"32kB ROM switch\";\n\n this.mapperName[64] = \"Tengen RAMBO-1 chip\";\n this.mapperName[65] = \"Irem H-3001 chip\";\n this.mapperName[66] = \"GNROM switch\";\n this.mapperName[67] = \"SunSoft3 chip\";\n this.mapperName[68] = \"SunSoft4 chip\";\n this.mapperName[69] = \"SunSoft5 FME-7 chip\";\n this.mapperName[71] = \"Camerica chip\";\n this.mapperName[78] = \"Irem 74HC161/32-based\";\n this.mapperName[91] = \"Pirate HK-SF3 chip\";\n};\n\nROM.prototype = {\n // Mirroring types:\n VERTICAL_MIRRORING: 0,\n HORIZONTAL_MIRRORING: 1,\n FOURSCREEN_MIRRORING: 2,\n SINGLESCREEN_MIRRORING: 3,\n SINGLESCREEN_MIRRORING2: 4,\n SINGLESCREEN_MIRRORING3: 5,\n SINGLESCREEN_MIRRORING4: 6,\n CHRROM_MIRRORING: 7,\n\n header: null,\n rom: null,\n vrom: null,\n vromTile: null,\n\n romCount: null,\n vromCount: null,\n mirroring: null,\n batteryRam: null,\n trainer: null,\n fourScreen: null,\n mapperType: null,\n valid: false,\n\n load: function(data) {\n var i, j, v;\n\n if (data.indexOf(\"NES\\x1a\") === -1) {\n throw new Error(\"Not a valid NES ROM.\");\n }\n this.header = new Array(16);\n for (i = 0; i < 16; i++) {\n this.header[i] = data.charCodeAt(i) & 0xff;\n }\n this.romCount = this.header[4];\n this.vromCount = this.header[5] * 2; // Get the number of 4kB banks, not 8kB\n this.mirroring = (this.header[6] & 1) !== 0 ? 1 : 0;\n this.batteryRam = (this.header[6] & 2) !== 0;\n this.trainer = (this.header[6] & 4) !== 0;\n this.fourScreen = (this.header[6] & 8) !== 0;\n this.mapperType = (this.header[6] >> 4) | (this.header[7] & 0xf0);\n /* TODO\n if (this.batteryRam)\n this.loadBatteryRam();*/\n // Check whether byte 8-15 are zero's:\n var foundError = false;\n for (i = 8; i < 16; i++) {\n if (this.header[i] !== 0) {\n foundError = true;\n break;\n }\n }\n if (foundError) {\n this.mapperType &= 0xf; // Ignore byte 7\n }\n // Load PRG-ROM banks:\n this.rom = new Array(this.romCount);\n var offset = 16;\n for (i = 0; i < this.romCount; i++) {\n this.rom[i] = new Uint8Array(16384);\n for (j = 0; j < 16384; j++) {\n if (offset + j >= data.length) {\n break;\n }\n this.rom[i][j] = data.charCodeAt(offset + j) & 0xff;\n }\n offset += 16384;\n }\n // Load CHR-ROM banks:\n this.vrom = new Array(this.vromCount);\n for (i = 0; i < this.vromCount; i++) {\n this.vrom[i] = new Uint8Array(4096);\n for (j = 0; j < 4096; j++) {\n if (offset + j >= data.length) {\n break;\n }\n this.vrom[i][j] = data.charCodeAt(offset + j) & 0xff;\n }\n offset += 4096;\n }\n\n // Create VROM tiles:\n this.vromTile = new Array(this.vromCount);\n for (i = 0; i < this.vromCount; i++) {\n this.vromTile[i] = new Array(256);\n for (j = 0; j < 256; j++) {\n this.vromTile[i][j] = new Tile();\n }\n }\n\n // Convert CHR-ROM banks to tiles:\n var tileIndex;\n var leftOver;\n for (v = 0; v < this.vromCount; v++) {\n for (i = 0; i < 4096; i++) {\n tileIndex = i >> 4;\n leftOver = i % 16;\n if (leftOver < 8) {\n this.vromTile[v][tileIndex].setScanline(\n leftOver,\n this.vrom[v][i],\n this.vrom[v][i + 8]\n );\n } else {\n this.vromTile[v][tileIndex].setScanline(\n leftOver - 8,\n this.vrom[v][i - 8],\n this.vrom[v][i]\n );\n }\n }\n }\n\n this.valid = true;\n },\n\n getMirroringType: function() {\n if (this.fourScreen) {\n return this.FOURSCREEN_MIRRORING;\n }\n if (this.mirroring === 0) {\n return this.HORIZONTAL_MIRRORING;\n }\n return this.VERTICAL_MIRRORING;\n },\n\n getMapperName: function() {\n if (this.mapperType >= 0 && this.mapperType < this.mapperName.length) {\n return this.mapperName[this.mapperType];\n }\n return \"Unknown Mapper, \" + this.mapperType;\n },\n\n mapperSupported: function() {\n return typeof Mappers[this.mapperType] !== \"undefined\";\n },\n\n createMapper: function() {\n if (this.mapperSupported()) {\n return new Mappers[this.mapperType](this.nes);\n } else {\n throw new Error(\n \"This ROM uses a mapper not supported by JSNES: \" +\n this.getMapperName() +\n \"(\" +\n this.mapperType +\n \")\"\n );\n }\n }\n};\n\nmodule.exports = ROM;\n", "var CPU = require(\"./cpu\");\nvar Controller = require(\"./controller\");\nvar PPU = require(\"./ppu\");\nvar PAPU = require(\"./papu\");\nvar ROM = require(\"./rom\");\n\nvar NES = function(opts) {\n this.opts = {\n onFrame: function() {},\n onAudioSample: null,\n onStatusUpdate: function() {},\n onBatteryRamWrite: function() {},\n\n // FIXME: not actually used except for in PAPU\n preferredFrameRate: 60,\n\n emulateSound: true,\n sampleRate: 44100 // Sound sample rate in hz\n };\n if (typeof opts !== \"undefined\") {\n var key;\n for (key in this.opts) {\n if (typeof opts[key] !== \"undefined\") {\n this.opts[key] = opts[key];\n }\n }\n }\n\n this.frameTime = 1000 / this.opts.preferredFrameRate;\n\n this.ui = {\n writeFrame: this.opts.onFrame,\n updateStatus: this.opts.onStatusUpdate\n };\n this.cpu = new CPU(this);\n this.ppu = new PPU(this);\n this.papu = new PAPU(this);\n this.mmap = null; // set in loadROM()\n this.controllers = {\n 1: new Controller(),\n 2: new Controller()\n };\n\n this.ui.updateStatus(\"Ready to load a ROM.\");\n\n this.frame = this.frame.bind(this);\n this.buttonDown = this.buttonDown.bind(this);\n this.buttonUp = this.buttonUp.bind(this);\n this.zapperMove = this.zapperMove.bind(this);\n this.zapperFireDown = this.zapperFireDown.bind(this);\n this.zapperFireUp = this.zapperFireUp.bind(this);\n};\n\nNES.prototype = {\n fpsFrameCount: 0,\n romData: null,\n\n // Resets the system\n reset: function() {\n if (this.mmap !== null) {\n this.mmap.reset();\n }\n\n this.cpu.reset();\n this.ppu.reset();\n this.papu.reset();\n\n this.lastFpsTime = null;\n this.fpsFrameCount = 0;\n },\n\n frame: function() {\n this.ppu.startFrame();\n var cycles = 0;\n var emulateSound = this.opts.emulateSound;\n var cpu = this.cpu;\n var ppu = this.ppu;\n var papu = this.papu;\n FRAMELOOP: for (;;) {\n if (cpu.cyclesToHalt === 0) {\n // Execute a CPU instruction\n cycles = cpu.emulate();\n if (emulateSound) {\n papu.clockFrameCounter(cycles);\n }\n cycles *= 3;\n } else {\n if (cpu.cyclesToHalt > 8) {\n cycles = 24;\n if (emulateSound) {\n papu.clockFrameCounter(8);\n }\n cpu.cyclesToHalt -= 8;\n } else {\n cycles = cpu.cyclesToHalt * 3;\n if (emulateSound) {\n papu.clockFrameCounter(cpu.cyclesToHalt);\n }\n cpu.cyclesToHalt = 0;\n }\n }\n\n for (; cycles > 0; cycles--) {\n if (\n ppu.curX === ppu.spr0HitX &&\n ppu.f_spVisibility === 1 &&\n ppu.scanline - 21 === ppu.spr0HitY\n ) {\n // Set sprite 0 hit flag:\n ppu.setStatusFlag(ppu.STATUS_SPRITE0HIT, true);\n }\n\n if (ppu.requestEndFrame) {\n ppu.nmiCounter--;\n if (ppu.nmiCounter === 0) {\n ppu.requestEndFrame = false;\n ppu.startVBlank();\n break FRAMELOOP;\n }\n }\n\n ppu.curX++;\n if (ppu.curX === 341) {\n ppu.curX = 0;\n ppu.endScanline();\n }\n }\n }\n this.fpsFrameCount++;\n },\n\n buttonDown: function(controller, button) {\n this.controllers[controller].buttonDown(button);\n },\n\n buttonUp: function(controller, button) {\n this.controllers[controller].buttonUp(button);\n },\n\n zapperMove: function(x, y) {\n if (!this.mmap) return;\n this.mmap.zapperX = x;\n this.mmap.zapperY = y;\n },\n\n zapperFireDown: function() {\n if (!this.mmap) return;\n this.mmap.zapperFired = true;\n },\n\n zapperFireUp: function() {\n if (!this.mmap) return;\n this.mmap.zapperFired = false;\n },\n\n getFPS: function() {\n var now = +new Date();\n var fps = null;\n if (this.lastFpsTime) {\n fps = this.fpsFrameCount / ((now - this.lastFpsTime) / 1000);\n }\n this.fpsFrameCount = 0;\n this.lastFpsTime = now;\n return fps;\n },\n\n reloadROM: function() {\n if (this.romData !== null) {\n this.loadROM(this.romData);\n }\n },\n\n // Loads a ROM file into the CPU and PPU.\n // The ROM file is validated first.\n loadROM: function(data) {\n // Load ROM file:\n this.rom = new ROM(this);\n this.rom.load(data);\n\n this.reset();\n this.mmap = this.rom.createMapper();\n this.mmap.loadROM();\n this.ppu.setMirroring(this.rom.getMirroringType());\n this.romData = data;\n },\n\n setFramerate: function(rate) {\n this.opts.preferredFrameRate = rate;\n this.frameTime = 1000 / rate;\n this.papu.setSampleRate(this.opts.sampleRate, false);\n },\n\n toJSON: function() {\n return {\n romData: this.romData,\n cpu: this.cpu.toJSON(),\n mmap: this.mmap.toJSON(),\n ppu: this.ppu.toJSON()\n };\n },\n\n fromJSON: function(s) {\n this.loadROM(s.romData);\n this.cpu.fromJSON(s.cpu);\n this.mmap.fromJSON(s.mmap);\n this.ppu.fromJSON(s.ppu);\n }\n};\n\nmodule.exports = NES;\n", "module.exports = {\n Controller: require(\"./controller\"),\n NES: require(\"./nes\")\n};\n", "\nimport { Platform, Base6502Platform, getOpcodeMetadata_6502, getToolForFilename_6502 } from \"../common/baseplatform\";\nimport { PLATFORMS, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, KeyFlags, EmuHalt, ControllerPoller } from \"../common/emu\";\nimport { hex, byteArrayToString } from \"../common/util\";\nimport { CodeAnalyzer_nes } from \"../common/analysis\";\nimport { SampleAudio } from \"../common/audio\";\nimport { ProbeRecorder } from \"../common/probe\";\nimport { NullProbe, Probeable, ProbeAll } from \"../common/devices\";\nimport Mousetrap = require('mousetrap');\nimport jsnes = require('../../jsnes');\nimport { BaseMAME6502Platform } from \"../common/mameplatform\";\n\nconst JSNES_PRESETS = [\n {id:'hello.c', name:'Hello World'},\n {id:'attributes.c', name:'Attribute Table'},\n {id:'scroll.c', name:'Scrolling'},\n {id:'sprites.c', name:'Sprites'},\n {id:'metasprites.c', name:'Metasprites'},\n {id:'flicker.c', name:'Flickering Sprites'},\n {id:'metacursor.c', name:'Controllers'},\n {id:'vrambuffer.c', name:'VRAM Buffer'},\n {id:'statusbar.c', name:'Split Status Bar'},\n {id:'siegegame.c', name:'Siege Game'},\n {id:'tint.c', name:'Color Emphasis'},\n {id:'rletitle.c', name:'Title Screen RLE'},\n {id:'aputest.c', name:'Sound Tester'},\n {id:'music.c', name:'Music Player'},\n {id:'horizscroll.c', name:'Offscreen Scrolling'},\n {id:'monobitmap.c', name:'Monochrome Bitmap'},\n {id:'fami.c', name:'Famitone Demo'},\n {id:'shoot2.c', name:'Solarian Game'},\n {id:'climber.c', name:'Climber Game'},\n {id:'bankswitch.c', name:'Bank Switching'},\n {id:'irq.c', name:'IRQ Scanline Counter'},\n {id:'ex0.dasm', name:'Initialization (ASM)'},\n {id:'ex1.dasm', name:'Hello World (ASM)'},\n {id:'ex2.dasm', name:'Scrolling Demo (ASM)'},\n {id:'ex3.dasm', name:'Sprite Demo (ASM)'},\n {id:'ex4.dasm', name:'Controller Demo (ASM)'},\n {id:'musicdemo.dasm', name:'Famitone Demo (ASM)'},\n {id:'xyscroll.dasm', name:'XY Split Scrolling (ASM)'},\n// {id:'scrollrt.dasm', name:'Line-by-line Scrolling (ASM)'},\n {id:'road.dasm', name:'3-D Road Demo (ASM)'},\n {id:'chase/game.c', name:'Shiru\\'s Chase Game'},\n {id:'hello.wiz', name:'Hello (Wiz)'},\n];\n\n/// JSNES\n\nconst JSNES_KEYCODE_MAP = makeKeycodeMap([\n [Keys.A, 0, 0],\n [Keys.B, 0, 1],\n [Keys.SELECT, 0, 2],\n [Keys.START, 0, 3],\n [Keys.UP, 0, 4],\n [Keys.DOWN, 0, 5],\n [Keys.LEFT, 0, 6],\n [Keys.RIGHT, 0, 7],\n \n [Keys.P2_A, 1, 0],\n [Keys.P2_B, 1, 1],\n [Keys.P2_SELECT, 1, 2],\n [Keys.P2_START, 1, 3],\n [Keys.P2_UP, 1, 4],\n [Keys.P2_DOWN, 1, 5],\n [Keys.P2_LEFT, 1, 6],\n [Keys.P2_RIGHT, 1, 7],\n]);\n\nclass JSNESPlatform extends Base6502Platform implements Platform, Probeable {\n\n mainElement;\n nes;\n video;\n audio;\n timer;\n poller : ControllerPoller;\n audioFrequency = 44030; //44100\n frameindex = 0;\n ntvideo;\n ntlastbuf;\n \n machine = { cpuCyclesPerLine: 114 }; // TODO: hack for width of probe scope\n \n constructor(mainElement) {\n super();\n this.mainElement = mainElement;\n }\n\n getPresets() { return JSNES_PRESETS; }\n\n start() {\n this.debugPCDelta = 1;\n var debugbar = $(\"
\").appendTo(this.mainElement);\n this.audio = new SampleAudio(this.audioFrequency);\n this.video = new RasterVideo(this.mainElement,256,224,{overscan:true});\n this.video.create();\n // debugging view\n this.ntvideo = new RasterVideo(this.mainElement,512,480,{overscan:false});\n this.ntvideo.create();\n $(this.ntvideo.canvas).hide();\n this.ntlastbuf = new Uint32Array(0x1000);\n if (Mousetrap.bind) Mousetrap.bind('ctrl+shift+alt+n', () => {\n $(this.video.canvas).toggle()\n $(this.ntvideo.canvas).toggle()\n });\n // toggle buttons (TODO)\n /*\n $('