broken-neees
Version:
A really broken NEEES emulator that introduces glitches and random bugs on purpose!
357 lines (330 loc) • 18.2 kB
JavaScript
"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);
};
}
};
};