UNPKG

broken-neees

Version:

A really broken NEEES emulator that introduces glitches and random bugs on purpose!

357 lines (330 loc) 18.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _CPUMemory = _interopRequireDefault(require("./CPUMemory")); var _CPU = _interopRequireDefault(require("./cpu/CPU")); var _PPU = _interopRequireDefault(require("./ppu/PPU")); var _APU = _interopRequireDefault(require("./apu/APU")); var _Cartridge = _interopRequireDefault(require("./Cartridge")); var _Controller = _interopRequireDefault(require("./controller/Controller")); var _mappers = _interopRequireDefault(require("./mappers/mappers")); var _interrupts = _interopRequireDefault(require("./lib/cpu/interrupts")); var _saveStates = _interopRequireDefault(require("./saveStates")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } const PPU_STEPS_PER_CPU_CYCLE = 3; const APU_STEPS_PER_CPU_CYCLE = 0.5; const TIMEOUT_MS = 3000; const MAX_STEP_MS = 300; const BUTTONS = ["BUTTON_A", "BUTTON_B", "BUTTON_SELECT", "BUTTON_START", "BUTTON_UP", "BUTTON_DOWN", "BUTTON_LEFT", "BUTTON_RIGHT"]; /** The NEEES Emulator. */ var _default = exports.default = function _default() { let customComponents = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return class NEEES { constructor() { let onFrame = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : () => {}; let onSample = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : () => {}; this.customComponents = _objectSpread({}, customComponents); const components = _objectSpread({}, customComponents); if (!components.CPUMemory) components.CPUMemory = _CPUMemory.default; if (!components.CPU) components.CPU = _CPU.default; if (!components.PPU) components.PPU = _PPU.default; if (!components.APU) components.APU = _APU.default; if (!components.Cartridge) components.Cartridge = _Cartridge.default; if (!components.Controller) components.Controller = _Controller.default; if (!components.mappers) components.mappers = _mappers.default; if (!components.omitReset) components.omitReset = false; if (!components.unbroken) components.unbroken = false; this.components = components; this.onFrame = onFrame; this.onSample = (sample, pulse1, pulse2, triangle, noise, dmc) => { this.sampleCount++; onSample(sample, pulse1, pulse2, triangle, noise, dmc); }; let cpuMemory = null; try { cpuMemory = new this.components.CPUMemory(); } catch (error) { throw new Error("🐒 Failure instantiating new CPUMemory(): " + (error === null || error === void 0 ? void 0 : error.message)); } try { this.cpu = new this.components.CPU(cpuMemory); if (components.unbroken) this.cpu.unbroken = true; } catch (error) { throw new Error("🐒 Failure instantiating new CPU(): " + (error === null || error === void 0 ? void 0 : error.message)); } try { this.ppu = new this.components.PPU(this.cpu); } catch (error) { throw new Error("🐒 Failure instantiating new PPU(cpu): " + (error === null || error === void 0 ? void 0 : error.message)); } try { this.apu = new this.components.APU(this.cpu); } catch (error) { throw new Error("🐒 Failure instantiating new APU(cpu): " + (error === null || error === void 0 ? void 0 : error.message)); } this._patchCPUMemory(); this.sampleCount = 0; this.pendingPPUCycles = 0; this.pendingAPUCycles = 0; this.onScanline = null; } /** Loads a `rom` as the current cartridge. */ load(rom) { let saveFileBytes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; const cartridge = new this.components.Cartridge(rom); if (this.components.unbroken) cartridge.unbroken = true; const mapper = this.components.mappers.create(this.cpu, this.ppu, cartridge); const controller1 = new this.components.Controller(1); const controller2 = new this.components.Controller(2); controller1.other = controller2; controller2.other = controller1; const controllers = [controller1, controller2]; controller1.cpu = this.cpu; controller2.cpu = this.cpu; try { this.cpu.memory.onLoad(this.ppu, this.apu, mapper, controllers); } catch (error) { throw new Error("🐒 CPU::memory::onLoad(...) failed: " + (error === null || error === void 0 ? void 0 : error.message)); } try { var _this$ppu$onLoad, _this$ppu; (_this$ppu$onLoad = (_this$ppu = this.ppu).onLoad) === null || _this$ppu$onLoad === void 0 ? void 0 : _this$ppu$onLoad.call(_this$ppu, mapper); } catch (error) { throw new Error("🐒 PPU::onLoad(...) failed: " + (error === null || error === void 0 ? void 0 : error.message)); } try { var _this$ppu$memory, _this$ppu$memory$onLo; (_this$ppu$memory = this.ppu.memory) === null || _this$ppu$memory === void 0 ? void 0 : (_this$ppu$memory$onLo = _this$ppu$memory.onLoad) === null || _this$ppu$memory$onLo === void 0 ? void 0 : _this$ppu$memory$onLo.call(_this$ppu$memory, cartridge, mapper); } catch (error) { throw new Error("🐒 PPU::memory::onLoad(...) failed: " + (error === null || error === void 0 ? void 0 : error.message)); } this.pendingPPUCycles = 0; this.pendingAPUCycles = 0; this.context = { cartridge, mapper, controllers }; try { if (!this.components.omitReset) this.cpu.interrupt(_interrupts.default.RESET); } catch (error) { throw new Error("🐒 RESET interrupt failed: " + (error === null || error === void 0 ? void 0 : error.message)); } this._setSaveFile(saveFileBytes); } /** Runs the emulation for a whole video frame. */ frame() { if (!this.context) return; let lastTime = Date.now(); let activeElapsed = 0; const currentFrame = this.ppu.frame; while (this.ppu.frame === currentFrame) { this.step(); const now = Date.now(); const gap = now - lastTime; lastTime = now; activeElapsed += gap < MAX_STEP_MS ? gap : 0; // long gaps = debugging if (activeElapsed > TIMEOUT_MS) { throw new Error("\uD83D\uDC12 The PPU is taking more than ".concat(TIMEOUT_MS, " ms to generate a single frame. This looks bad!")); } } } /** Runs the emulation until the audio system generates `requestedSamples`. */ samples(requestedSamples) { if (!this.context) return; let lastTime = Date.now(); let activeElapsed = 0; this.sampleCount = 0; while (this.sampleCount < requestedSamples) { this.step(); const now = Date.now(); const gap = now - lastTime; lastTime = now; activeElapsed += gap < MAX_STEP_MS ? gap : 0; // long gaps = debugging if (activeElapsed > TIMEOUT_MS) { throw new Error("\uD83D\uDC12 The APU is taking more than ".concat(TIMEOUT_MS, " ms to generate ").concat(requestedSamples, " samples. This looks bad!")); } } } /** Runs the emulation until the next scanline. */ scanline() { let debug = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!this.context) return; let lastTime = Date.now(); let activeElapsed = 0; const currentScanline = this.ppu.scanline; while (this.ppu.scanline === currentScanline) { this.step(); const now = Date.now(); const gap = now - lastTime; lastTime = now; activeElapsed += gap < MAX_STEP_MS ? gap : 0; // long gaps = debugging if (activeElapsed > TIMEOUT_MS) { throw new Error("\uD83D\uDC12 The PPU is taking more than ".concat(TIMEOUT_MS, " ms to generate a single scanline. This looks bad!")); } } let oldFrameBuffer; if (debug) { oldFrameBuffer = new Uint32Array(this.ppu.frameBuffer.length); for (let i = 0; i < this.ppu.frameBuffer.length; i++) oldFrameBuffer[i] = this.ppu.frameBuffer[i]; for (let i = 0; i < 256; i++) this.ppu.plot(i, this.ppu.scanline, 0xff0000ff); } this.onFrame(this.ppu.frameBuffer); if (debug) { for (let i = 0; i < this.ppu.frameBuffer.length; i++) this.ppu.frameBuffer[i] = oldFrameBuffer[i]; } } /** Executes a step in the emulation (1 CPU instruction). */ step() { let cpuCycles = this.cpu.step(); cpuCycles = this._clockPPU(cpuCycles); this._clockAPU(cpuCycles); } /** Sets the `button` state of `player` to `isPressed`. */ setButton(player, button, isPressed) { if (!this.context) return; if (player !== 1 && player !== 2) throw new Error("Invalid player: ".concat(player, ".")); this.context.controllers[player - 1].update(button, isPressed); } /** Sets all buttons of `player` to a non-pressed state. */ clearButtons(player) { if (!this.context) return; if (player !== 1 && player !== 2) throw new Error("Invalid player: ".concat(player, ".")); for (let button of BUTTONS) this.context.controllers[player - 1].update(button, false); } /** Returns the PRG RAM bytes, or null. */ getSaveFile() { if (!this.context) return; const { prgRam } = this.context.mapper; if (!prgRam) return null; const bytes = []; for (let i = 0; i < prgRam.length; i++) bytes[i] = prgRam[i]; return bytes; } /** Returns a snapshot of the current state. */ getSaveState() { if (!this.context) return; return _saveStates.default.getSaveState(this); } /** Restores state from a snapshot. */ setSaveState(_saveState) { if (!this.context) return; const saveState = JSON.parse(JSON.stringify(_saveState)); // deep copy _saveStates.default.setSaveState(this, saveState); this._setSaveFile(saveState.saveFile); } _clockPPU(cpuCycles) { const scanline = this.ppu.scanline; let unitCycles = this.pendingPPUCycles + cpuCycles * PPU_STEPS_PER_CPU_CYCLE; this.pendingPPUCycles = 0; const onIntr = interrupt => { const newCPUCycles = this.cpu.interrupt(interrupt); cpuCycles += newCPUCycles; this.pendingPPUCycles += newCPUCycles * PPU_STEPS_PER_CPU_CYCLE; }; for (let i = 0; i < unitCycles; i++) { // <optimization> if (this.ppu.cycle > 1 && this.ppu.cycle < 256 || this.ppu.cycle > 260 && this.ppu.cycle < 304 || this.ppu.cycle > 304 && this.ppu.cycle < 340) { this.ppu.cycle++; continue; } // </optimization> this.ppu.step(this.onFrame, onIntr); } if (this.ppu.scanline !== scanline && this.onScanline != null) this.onScanline(); return cpuCycles; } _clockAPU(cpuCycles) { let unitCycles = this.pendingAPUCycles + cpuCycles * APU_STEPS_PER_CPU_CYCLE; const onIntr = interrupt => { const newCPUCycles = this.cpu.interrupt(interrupt); unitCycles += newCPUCycles * APU_STEPS_PER_CPU_CYCLE; this.pendingPPUCycles += newCPUCycles * PPU_STEPS_PER_CPU_CYCLE; }; while (unitCycles >= 1) { this.apu.step(this.onSample, onIntr); unitCycles--; } this.pendingAPUCycles = unitCycles; } _setSaveFile(prgRamBytes) { const prgRam = this.context.mapper.prgRam; if (!prgRam || !prgRamBytes) return; for (let i = 0; i < prgRamBytes.length; i++) prgRam[i] = prgRamBytes[i]; } _patchCPUMemory() { if (!this.cpu.memory) throw new Error("🐒 CPU::memory not found"); this._patchCPUMemoryReads(); this._patchCPUMemoryWrites(); } _patchCPUMemoryReads() { const components = this.customComponents; const memory = this.cpu.memory; const read = memory.read; if (!read) throw new Error("🐒 CPU::memory::read(...) not found"); memory.read = function (address) { // PPU registers if (!components.PPU || !components.CPUMemory) { var _this$ppu$registers$r, _this$ppu$registers, _this$ppu$registers$r2, _this$ppu$registers$r3, _this$ppu$registers2, _this$ppu$registers2$; if (address >= 0x2000 && address <= 0x2007 || address === 0x4014) return (_this$ppu$registers$r = (_this$ppu$registers = this.ppu.registers) === null || _this$ppu$registers === void 0 ? void 0 : (_this$ppu$registers$r2 = _this$ppu$registers.read) === null || _this$ppu$registers$r2 === void 0 ? void 0 : _this$ppu$registers$r2.call(_this$ppu$registers, address)) !== null && _this$ppu$registers$r !== void 0 ? _this$ppu$registers$r : 0;else if (address >= 0x2008 && address <= 0x3fff) return (_this$ppu$registers$r3 = (_this$ppu$registers2 = this.ppu.registers) === null || _this$ppu$registers2 === void 0 ? void 0 : (_this$ppu$registers2$ = _this$ppu$registers2.read) === null || _this$ppu$registers2$ === void 0 ? void 0 : _this$ppu$registers2$.call(_this$ppu$registers2, 0x2000 + (address - 0x2008) % 0x0008)) !== null && _this$ppu$registers$r3 !== void 0 ? _this$ppu$registers$r3 : 0; } // APU registers if (!components.APU || !components.CPUMemory) { var _this$apu$registers$r, _this$apu$registers, _this$apu$registers$r2; if (address >= 0x4000 && address <= 0x4013 || address === 0x4015) return (_this$apu$registers$r = (_this$apu$registers = this.apu.registers) === null || _this$apu$registers === void 0 ? void 0 : (_this$apu$registers$r2 = _this$apu$registers.read) === null || _this$apu$registers$r2 === void 0 ? void 0 : _this$apu$registers$r2.call(_this$apu$registers, address)) !== null && _this$apu$registers$r !== void 0 ? _this$apu$registers$r : 0; } // Controller ports if (!components.Controller || !components.CPUMemory) { if (address === 0x4016) return this.controllers[0].onRead();else if (address === 0x4017) return this.controllers[1].onRead(); } // APU and I/O functionality that is normally disabled if (address >= 0x4018 && address <= 0x401f) return 0; // Cartridge space: PRG ROM, PRG RAM, and mapper registers if (!components.mappers || !components.CPUMemory) { if (address >= 0x4020 && address <= 0xffff) return this.mapper.cpuRead(address); } // Original method call return read.call(this, address); }; } _patchCPUMemoryWrites() { const components = this.customComponents; const memory = this.cpu.memory; const write = memory.write; if (!write) throw new Error("🐒 CPU::memory::write(...) not found"); memory.write = function (address, value) { // PPU registers if (!components.PPU || !components.CPUMemory) { var _this$ppu$registers3, _this$ppu$registers3$, _this$ppu$registers4, _this$ppu$registers4$; if (address >= 0x2000 && address <= 0x2007 || address === 0x4014) return (_this$ppu$registers3 = this.ppu.registers) === null || _this$ppu$registers3 === void 0 ? void 0 : (_this$ppu$registers3$ = _this$ppu$registers3.write) === null || _this$ppu$registers3$ === void 0 ? void 0 : _this$ppu$registers3$.call(_this$ppu$registers3, address, value);else if (address >= 0x2008 && address <= 0x3fff) return (_this$ppu$registers4 = this.ppu.registers) === null || _this$ppu$registers4 === void 0 ? void 0 : (_this$ppu$registers4$ = _this$ppu$registers4.write) === null || _this$ppu$registers4$ === void 0 ? void 0 : _this$ppu$registers4$.call(_this$ppu$registers4, 0x2000 + (address - 0x2008) % 0x0008, value); } // APU registers if (!components.APU || !components.CPUMemory) { var _this$apu$registers2, _this$apu$registers2$; if (address >= 0x4000 && address <= 0x4013 || address === 0x4015 || address === 0x4017) return (_this$apu$registers2 = this.apu.registers) === null || _this$apu$registers2 === void 0 ? void 0 : (_this$apu$registers2$ = _this$apu$registers2.write) === null || _this$apu$registers2$ === void 0 ? void 0 : _this$apu$registers2$.call(_this$apu$registers2, address, value); } // Controller ports if (!components.Controller || !components.CPUMemory) { if (address === 0x4016) return this.controllers[0].onWrite(value); } // APU and I/O functionality that is normally disabled if (address >= 0x4018 && address <= 0x401f) return; // Cartridge space: PRG ROM, PRG RAM, and mapper registers if (!components.mappers || !components.CPUMemory) { if (address >= 0x4020 && address <= 0xffff) return this.mapper.cpuWrite(address, value); } // Original method call return write.call(this, address, value); }; } }; };