UNPKG

nes-emu

Version:

A NES emulator

194 lines (182 loc) 6.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _cpu = _interopRequireDefault(require("./cpu")); var _ppu = _interopRequireDefault(require("./ppu")); var _apu = _interopRequireDefault(require("./apu")); var _Bus = require("./memory/Bus"); var _cartridge = _interopRequireDefault(require("./cartridge")); var _controller = _interopRequireDefault(require("./controller")); var _constants = _interopRequireDefault(require("./constants")); var _helpers = require("./helpers"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** The NES Emulator. */ class NES { constructor() { let onFrame = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : () => {}; let onSample = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : () => {}; let logger = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; _helpers.WithContext.apply(this); this.onFrame = onFrame; this.onSample = sample => { this.sampleCount++; onSample(sample); }; this.logger = logger; this.cpu = new _cpu.default(); this.ppu = new _ppu.default(); this.apu = new _apu.default(); this.sampleCount = 0; this.pendingPPUCycles = 0; this.pendingAPUCycles = 0; } /** Loads a `rom` as the current cartridge. */ load(rom) { let saveFileBytes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; const cartridge = new _cartridge.default(rom); const mapper = cartridge.createMapper(); const controllerPorts = _controller.default.createPorts(); this.loadContext({ nes: this, logger: this.logger, cpu: this.cpu, ppu: this.ppu, apu: this.apu, memoryBus: { cpu: new _Bus.CPUBus(mapper) }, cartridge, mapper, controllers: [new _controller.default(controllerPorts.primary), new _controller.default(controllerPorts.secondary)], inDebugMode(action) { try { this.isDebugging = true; return action(); } finally { this.isDebugging = false; } } }); this._setSaveFile(saveFileBytes); } /** Runs the emulation for a whole video frame. */ frame() { this.requireContext(); const currentFrame = this.ppu.frame; while (this.ppu.frame === currentFrame) this.step(); } /** Runs the emulation until the audio system generates `requestedSamples`. */ samples(requestedSamples) { this.requireContext(); this.sampleCount = 0; while (this.sampleCount < requestedSamples) this.step(); } /** Runs the emulation until the next scanline. */ scanline() { let debug = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; this.requireContext(); const currentScanline = this.ppu.scanline; while (this.ppu.scanline === currentScanline) this.step(); 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 < _constants.default.SCREEN_WIDTH; 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) { this.requireContext(); 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) { this.requireContext(); 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. */ getSaveFile() { this.requireContext(); const { prgRam } = this.context.mapper; if (!prgRam) return null; const bytes = []; for (let i = 0; i < prgRam.memorySize; i++) bytes[i] = prgRam.readAt(i); return bytes; } /** Returns a snapshot of the current state. */ getSaveState() { this.requireContext(); return { cpu: this.cpu.getSaveState(), ppu: this.ppu.getSaveState(), apu: this.apu.getSaveState(), mapper: this.context.mapper.getSaveState(), saveFile: this.getSaveFile() }; } /** Restores state from a snapshot. */ setSaveState(saveState) { this.requireContext(); this.cpu.setSaveState(saveState.cpu); this.ppu.setSaveState(saveState.ppu); this.apu.setSaveState(saveState.apu); this.context.mapper.setSaveState(saveState.mapper); this._setSaveFile(saveState.saveFile); } /** When a context is loaded. */ onLoad(context) { this.pendingPPUCycles = 0; this.pendingAPUCycles = 0; context.mapper.loadContext(context); this.ppu.loadContext(context); this.apu.loadContext(context); this.cpu.loadContext(context); } _clockPPU(cpuCycles) { let unitCycles = this.pendingPPUCycles + cpuCycles * _constants.default.PPU_STEPS_PER_CPU_CYCLE; this.pendingPPUCycles = 0; this.ppu.step(unitCycles, this.onFrame, interrupt => { const newCPUCycles = this.cpu.interrupt(interrupt); cpuCycles += newCPUCycles; this.pendingPPUCycles += newCPUCycles * _constants.default.PPU_STEPS_PER_CPU_CYCLE; }); return cpuCycles; } _clockAPU(cpuCycles) { let unitCycles = this.pendingAPUCycles + cpuCycles * _constants.default.APU_STEPS_PER_CPU_CYCLE; while (unitCycles >= 1) { const interrupt = this.apu.step(this.onSample); unitCycles--; if (interrupt != null) { const newCPUCycles = this.cpu.interrupt(interrupt); unitCycles += newCPUCycles * _constants.default.APU_STEPS_PER_CPU_CYCLE; this.pendingPPUCycles += newCPUCycles * _constants.default.PPU_STEPS_PER_CPU_CYCLE; } } this.pendingAPUCycles = unitCycles; } _setSaveFile(prgRamBytes) { const { prgRam } = this.context.mapper; if (!prgRam) return; for (let i = 0; i < prgRamBytes.length; i++) prgRam.writeAt(i, prgRamBytes[i]); } } exports.default = NES;