UNPKG

broken-neees

Version:

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

376 lines (352 loc) 20.1 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(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } var PPU_STEPS_PER_CPU_CYCLE = 3; var APU_STEPS_PER_CPU_CYCLE = 0.5; var TIMEOUT_MS = 3000; /** The NEEES Emulator. */ var _default = function _default() { var customComponents = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return /*#__PURE__*/function () { function NEEES() { var _this = this; var onFrame = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {}; var onSample = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; _classCallCheck(this, NEEES); this.customComponents = _objectSpread({}, customComponents); var 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 = function (sample, pulse1, pulse2, triangle, noise, dmc) { _this.sampleCount++; onSample(sample, pulse1, pulse2, triangle, noise, dmc); }; var 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. */ _createClass(NEEES, [{ key: "load", value: function load(rom) { var saveFileBytes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var cartridge = new this.components.Cartridge(rom); if (this.components.unbroken) cartridge.unbroken = true; var mapper = this.components.mappers.create(this.cpu, this.ppu, cartridge); var controller1 = new this.components.Controller(1); var controller2 = new this.components.Controller(2); controller1.setOtherController(controller2); controller2.setOtherController(controller1); var controllers = [controller1, controller2]; 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: cartridge, mapper: mapper, controllers: 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. */ }, { key: "frame", value: function frame() { if (!this.context) return; var startTime = Date.now(); var currentFrame = this.ppu.frame; while (this.ppu.frame === currentFrame) { this.step(); if (Date.now() > startTime + 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`. */ }, { key: "samples", value: function samples(requestedSamples) { if (!this.context) return; var startTime = Date.now(); this.sampleCount = 0; while (this.sampleCount < requestedSamples) { this.step(); if (Date.now() > startTime + 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. */ }, { key: "scanline", value: function scanline() { var debug = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!this.context) return; var startTime = Date.now(); var currentScanline = this.ppu.scanline; while (this.ppu.scanline === currentScanline) { this.step(); if (Date.now() > startTime + 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!")); } } var oldFrameBuffer; if (debug) { oldFrameBuffer = new Uint32Array(this.ppu.frameBuffer.length); for (var i = 0; i < this.ppu.frameBuffer.length; i++) oldFrameBuffer[i] = this.ppu.frameBuffer[i]; for (var _i = 0; _i < 256; _i++) this.ppu.plot(_i, this.ppu.scanline, 0xff0000ff); } this.onFrame(this.ppu.frameBuffer); if (debug) { for (var _i2 = 0; _i2 < this.ppu.frameBuffer.length; _i2++) this.ppu.frameBuffer[_i2] = oldFrameBuffer[_i2]; } } /** Executes a step in the emulation (1 CPU instruction). */ }, { key: "step", value: function step() { var cpuCycles = this.cpu.step(); cpuCycles = this._clockPPU(cpuCycles); this._clockAPU(cpuCycles); } /** Sets the `button` state of `player` to `isPressed`. */ }, { key: "setButton", value: function 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. */ }, { key: "clearButtons", value: function clearButtons(player) { if (!this.context) return; if (player !== 1 && player !== 2) throw new Error("Invalid player: ".concat(player, ".")); this.context.controllers[player - 1].clear(); } /** Returns the PRG RAM bytes, or null. */ }, { key: "getSaveFile", value: function getSaveFile() { if (!this.context) return; var prgRam = this.context.mapper.prgRam; if (!prgRam) return null; var bytes = []; for (var i = 0; i < prgRam.length; i++) bytes[i] = prgRam[i]; return bytes; } /** Returns a snapshot of the current state. */ }, { key: "getSaveState", value: function getSaveState() { if (!this.context) return; return _saveStates.default.getSaveState(this); } /** Restores state from a snapshot. */ }, { key: "setSaveState", value: function setSaveState(_saveState) { if (!this.context) return; var saveState = JSON.parse(JSON.stringify(_saveState)); // deep copy _saveStates.default.setSaveState(this, saveState); this._setSaveFile(saveState.saveFile); } }, { key: "_clockPPU", value: function _clockPPU(cpuCycles) { var _this2 = this; var scanline = this.ppu.scanline; var unitCycles = this.pendingPPUCycles + cpuCycles * PPU_STEPS_PER_CPU_CYCLE; this.pendingPPUCycles = 0; var onIntr = function onIntr(interrupt) { var newCPUCycles = _this2.cpu.interrupt(interrupt); cpuCycles += newCPUCycles; _this2.pendingPPUCycles += newCPUCycles * PPU_STEPS_PER_CPU_CYCLE; }; for (var 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; } }, { key: "_clockAPU", value: function _clockAPU(cpuCycles) { var _this3 = this; var unitCycles = this.pendingAPUCycles + cpuCycles * APU_STEPS_PER_CPU_CYCLE; var onIntr = function onIntr(interrupt) { var newCPUCycles = _this3.cpu.interrupt(interrupt); unitCycles += newCPUCycles * APU_STEPS_PER_CPU_CYCLE; _this3.pendingPPUCycles += newCPUCycles * PPU_STEPS_PER_CPU_CYCLE; }; while (unitCycles >= 1) { this.apu.step(this.onSample, onIntr); unitCycles--; } this.pendingAPUCycles = unitCycles; } }, { key: "_setSaveFile", value: function _setSaveFile(prgRamBytes) { var prgRam = this.context.mapper.prgRam; if (!prgRam || !prgRamBytes) return; for (var i = 0; i < prgRamBytes.length; i++) prgRam[i] = prgRamBytes[i]; } }, { key: "_patchCPUMemory", value: function _patchCPUMemory() { if (!this.cpu.memory) throw new Error("🐒 CPU::memory not found"); this._patchCPUMemoryReads(); this._patchCPUMemoryWrites(); } }, { key: "_patchCPUMemoryReads", value: function _patchCPUMemoryReads() { var components = this.customComponents; var memory = this.cpu.memory; var read = memory.read; if (!read) throw new Error("🐒 CPU::memory::read(...) not found"); memory.read = function (address) { // PPU registers if (!components.PPU) { 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) { 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) { 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) { if (address >= 0x4020 && address <= 0xffff) return this.mapper.cpuRead(address); } // Original method call return read.call(this, address); }; } }, { key: "_patchCPUMemoryWrites", value: function _patchCPUMemoryWrites() { var components = this.customComponents; var memory = this.cpu.memory; var write = memory.write; if (!write) throw new Error("🐒 CPU::memory::write(...) not found"); memory.write = function (address, value) { // PPU registers if (!components.PPU) { 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) { 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) { 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) { if (address >= 0x4020 && address <= 0xffff) return this.mapper.cpuWrite(address, value); } // Original method call return write.call(this, address, value); }; } }]); return NEEES; }(); }; exports.default = _default;