const SCREEN_WIDTH = 256; const SCREEN_HEIGHT = 240; const WORKING_MEMORY = 0x800; const SAMPLE_REDUCTION = 4; const jsnes = require('jsnes') const { Subject } = require('rxjs') const chunk = (a, l) => Array(Math.ceil(a.length / l)).fill().map((_, i) => (a.slice ? a.slice(i * l, i * l + l) : a.substr(i * l, i * l + 1))) const sample = (a, n) => Array(Math.floor(a.length / n)).fill().map((_, i) => a[i * n]) class NES { constructor(rom) { this.nes = new jsnes.NES({ onFrame: this.onFrame.bind(this) }) this.nes.loadROM(rom) this.frameCount = 0 this.packetBuffer = new ArrayBuffer(256 * 4 + SCREEN_WIDTH * SCREEN_HEIGHT + WORKING_MEMORY) this.paletteBuffer = new Uint32Array(this.packetBuffer) this.pixelBuffer = new Uint8ClampedArray(this.packetBuffer, 256 * 4) this.copyMemoryBuffer = new Uint8Array(this.packetBuffer, 256 * 4 + SCREEN_WIDTH * SCREEN_HEIGHT, WORKING_MEMORY) this.frameBuffer = new ArrayBuffer(SCREEN_WIDTH * SCREEN_HEIGHT * 4) this.screen32 = new Uint32Array(this.frameBuffer) this.nes.ppu.buffer = this.screen32 this.frameBytes = new Uint8ClampedArray(this.frameBuffer) this.frames = new Subject() } command(data) { if (data.type === 'button') { const player = data.player || 1 this.nes.controllers[player].state[data.button] = data.pressed ? 0x41 : 0x40 } } onFrame(frameBuffer) { //console.log('frames.next' + Date.now()) const colorMap = new Map() for (let i = 0; i < 256; i++) { this.paletteBuffer[i] = this.nes.ppu.palTable.curTable[i] colorMap.set(this.nes.ppu.palTable.curTable[i], i) } for (let i = 0; i < this.screen32.length; i++) { this.pixelBuffer[i] = colorMap.get(this.screen32[i]) } for (let i = 0; i < WORKING_MEMORY; i++) { this.copyMemoryBuffer[i] = this.nes.cpu.mem[i] } this.frames.next(this.packetBuffer) } genie(code) { const map = { A: 0x0, P: 0x1, Z: 0x2, L: 0x3, G: 0x4, I: 0x5, T: 0x6, Y: 0x7, E: 0x8, O: 0x9, X: 0xA, U: 0xB, K: 0xC, S: 0xD, V: 0xE, N: 0xF } const [n0, n1, n2, n3, n4, n5, n6, n7] = code.split('').map(c => map[c]) let address, data, compare address = 0x8000 + ((n3 & 7) << 12) | ((n5 & 7) << 8) | ((n4 & 8) << 8) | ((n2 & 7) << 4) | ((n1 & 8) << 4) | (n4 & 7) | (n3 & 8) if (code.length === 6) { data = ((n1 & 7) << 4) | ((n0 & 8) << 4) | (n0 & 7) | (n5 & 8) compare = null } else { data = ((n1 & 7) << 4) | ((n0 & 8) << 4) | (n0 & 7) | (n7 & 8) compare = ((n7 & 7) << 4) | ((n6 & 8) << 4) | (n6 & 7) | (n5 & 8) } this.hack(address, data, compare) } hack(address, data, compare = null) { let val = this.nes.cpu.mem[address] Object.defineProperty(this.nes.cpu.mem, address, { get: () => compare === null || val === compare ? data : val, set: v => (val = v) }) } start() { if (!this.timer) this.timer = setInterval(() => this.nes.frame(), 1000 / 60) } stop() { if (this.timer) { clearInterval(this.timer) this.timer = null } } } module.exports = NES