jsnes

bfirshApache-2.0活跃维护

用 JavaScript 实现的 NES 模拟器,能在浏览器或 Node.js 里直接跑红白机游戏,适合做前端怀旧游戏或模拟器原理学习。

A JavaScript NES emulator.

v2.1.0发布于 2026-04-11 23:55 UTC6,435star861fork同步于 2026-11-10 11:34 UTC

怎么上手

由 README 提炼,执行前请确认
npm install jsnes
硬件要求按 README 说明

README 未说明;纯 JavaScript 实现,浏览器与 Node.js 均可运行,无 GPU 或显存要求

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

纯 JavaScript 实现的 NES 模拟器库,浏览器和 Node.js 都能跑,省去原生依赖

用 JavaScript 实现的 NES 模拟器,能在浏览器或 Node.js 里直接跑红白机游戏,适合做前端怀旧游戏或模拟器原理学习。

核心能力

  • 用 NES 类逐帧驱动,自行控制渲染与音频输出
  • Browser 类自动处理 canvas、音频、键盘和手柄
  • 提供 toJSON/fromJSON 保存与恢复模拟器状态
  • 支持 Zapper 光枪坐标与扳机输入
  • 内置键盘映射,可运行时改键并持久化到 localStorage

容易踩的坑

需自备 .nes ROM 文件,项目不附带游戏;直接使用 NES 类时需自行按 60fps 调用 frame() 并处理音频;示例页面需在仓库根目录运行 npx serve . 后访问

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

2 个版本v2.1.0
  1. v2.1.02.1.0最新正式版2026-04-11 · 2026-04-11 23:55 UTC

    Highlights

    MMC5 mapper support (Castlevania III and others) plus a batch of PPU, APU, and mapper accuracy fixes.

    New mappers

    • Mapper 5 (MMC5) — Castlevania III, Uncharted Waters, Laser Invasion, and other late-era titles. Dual CHR bank sets, ExRAM modes, split-screen nametable, direct-CHR sprite 0 hit.

    Hardware accuracy

    PPU

    • Sprite evaluation rewritten for hardware-accurate OAM scan.
    • Sprite 0 hit pre-computed for the next scanline for dot-accurate timing.
    • Color emphasis bits (NTSC) mapped correctly.
    • Grayscale mask fixed (0x20 → 0x30).
    • General rendering pipeline and register accuracy improvements.

    APU

    • Frame counter IRQ timing fixed for hardware accuracy.
    • Square sweep negate and overflow mute threshold corrected.

    CPU

    • NMI handler now sets the I flag.

    Mappers

    • MMC3: register writes decoded via mirrored address bits.
    • Mapper 180: initial PRG bank at $C000 corrected.

    Controller

    • Strobe now models APU-clock-gated OUT0 updates.

    Save states

    CPU dataBus and controller state are now persisted. Save states from 2.0.x still load, but dataBus will be zero until the next bus operation.

    Packaging

    • main/require exports now point to the UMD bundle, fixing Node/CommonJS consumers.
    • Game Genie and controller JSON serialization added to type declarations.
  2. v2.0.02.0.02026-03-01 · 2026-03-01 17:53 UTC
    查看更新说明

    Highlights

    JSNES 2.0 is a major release focused on hardware accuracy, ease of use, and performance. The emulator now passes significantly more tests from the AccuracyCoin and nestest test suites, supports more games through six new mappers, adds Game Genie cheat code support, and ships a batteries-included Browser class for embedding in web pages with a single function call.

    New: jsnes.Browser class

    A new high-level Browser class makes it easy to embed JSNES in a web page without wiring up canvas rendering, audio, or input handling yourself:

    <div id="nes" style="width: 512px; height: 480px"></div>
    <script src="https://unpkg.com/jsnes/dist/jsnes.min.js"></script>
    <script>
      var browser = new jsnes.Browser({
        container: document.getElementById("nes"),
      });
      jsnes.Browser.loadROMFromURL("my-rom.nes", function (err, data) {
        browser.loadROM(data);
      });
    </script>
    

    The Browser class handles:

    • Canvas rendering and scaling to fit its container
    • Audio output via AudioWorklet (see below)
    • Keyboard input with configurable key bindings (persisted to localStorage)
    • Gamepad input with configurable button mapping
    • Zapper (light gun) support via mouse
    • Frame timing at 60 fps
    • Error handling via onError callback
    • Screenshots via browser.screenshot()
    • Full cleanup via browser.destroy()

    It also works well with React and other frameworks — see the README for examples.

    Game Genie support

    New nes.addGameGenieCode(code) and nes.removeGameGenieCode(code) methods let users apply Game Genie cheat codes at runtime. Standard 6-letter (compare-free) and 8-letter (compare) codes are both supported.

    New mappers

    Six new cartridge mappers expand game compatibility:

    • Mapper 9 (MMC2) — Mike Tyson's Punch-Out!!, Punch-Out!! (PAL). Tile-triggered CHR bank switching via latches.
    • Mapper 71 (Camerica/Codemasters) — Fire Hawk, Micro Machines, Bee 52, MiG 29. UxROM variant with optional single-screen mirroring.
    • Mapper 79 (NINA-03/NINA-06) — Tiles of Fate, Krazy Kreatures, Impossible Mission II. GxROM-like with register in expansion area.
    • Mapper 118 (TxSROM) — Armadillo, Pro Sport Hockey, Goal! Two. MMC3 variant with CHR-controlled nametable mirroring.
    • Mapper 119 (TQROM) — Pin-Bot, High Speed. MMC3 variant supporting simultaneous CHR ROM and CHR RAM.
    • Mapper 240 — Sheng Huo Lie Zhuan, Jing Ke Xin Zhuan. Simple PRG/CHR banking via writes to $4020-$40FF.

    Turbo buttons

    New turbo button support simulates the auto-fire feature of the NES Advantage and similar controllers. Controller.BUTTON_TURBO_A and Controller.BUTTON_TURBO_B auto-fire at ~30 Hz while held. The Browser class maps these to the S and A keys by default.

    Hardware accuracy improvements

    Numerous fixes to match real NES hardware behavior, verified by the AccuracyCoin and nestest test suites:

    NMI timing

    • Bus-cycle-aware NMI edge detection matching real 6502 behavior.
    • NMI promotion timing at VBlank clear for φ2 accuracy — the PPU can suppress or promote NMI depending on the exact dot within an instruction.
    • Proper handling of $2000 writes enabling NMI while VBlank is already active.

    PPU catch-up

    • The PPU is now advanced inline after every CPU bus operation (load, write, push, pull), so mid-instruction PPU register reads see cycle-accurate state. Previously, the PPU only advanced between instructions.

    Dummy reads and writes

    • Branch instructions: Taken branches now perform a dummy read from the old PC location, and page-crossing branches perform an additional dummy read from the partially-computed address.
    • Implied/accumulator modes: Second cycle reads from PC, matching real 6502 behavior.
    • Absolute indexed: Dummy read from address with uncorrected high byte on page crossing.
    • Zero page indexed: Dummy read from unindexed zero-page address while adding X/Y.
    • *Pre-indexed indirect

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    JSNES

    A JavaScript NES emulator.

    It's a library that works in both the browser and Node.js.

    Installation

    For Node.js or Webpack:

    $ npm install jsnes
    

    In the browser, you can use unpkg:

    <script type="text/javascript" src="https://unpkg.com/jsnes@2/dist/jsnes.min.js"></script>
    

    Usage

    Browser

    The easiest way to use JSNES in a web page is with jsnes.Browser. It handles canvas rendering, audio, keyboard input, gamepad input, and frame timing automatically.

    <div id="nes" style="width: 512px; height: 480px"></div>
    <script src="https://unpkg.com/jsnes@2/dist/jsnes.min.js"></script>
    <script>
      var browser = new jsnes.Browser({
        container: document.getElementById("nes"),
        onError: function (e) {
          console.error(e);
        },
      });
      jsnes.Browser.loadROMFromURL("my-rom.nes", function (err, data) {
        if (err) {
          console.error(err);
          return;
        }
        browser.loadROM(data);
      });
    </script>
    

    If you already have ROM data as a string or byte array, you can pass it directly:

    var browser = new jsnes.Browser({
      container: document.getElementById("nes"),
      romData: romData,
    });
    

    See Keyboard Controls below for default key bindings. Gamepads are also supported automatically.

    A complete embedding example is in the example/ directory. You can try it by running npx serve . in the repository root and opening http://localhost:3000/example/nes-embed.

    React

    import { Browser } from "jsnes";
    
    function Emulator({ romData }) {
      const containerRef = useRef(null);
      const browserRef = useRef(null);
    
      useEffect(() => {
        browserRef.current = new Browser({
          container: containerRef.current,
          romData: romData,
        });
        return () => browserRef.current.destroy();
      }, [romData]);
    
      return <div ref={containerRef} />;
    }
    

    A full-featured React frontend is available in the web/ directory of this repository.

    Node.js / custom integration

    If you need lower-level control (custom rendering, audio handling, or running in Node.js), use the NES class directly:

    // Initialize and set up outputs
    var nes = new jsnes.NES({
      onFrame: function(frameBuffer) {
        // ... write frameBuffer to screen
      },
      onAudioSample: function(left, right) {
        // ... play audio sample
      }
    });
    
    // Read ROM data from disk (using Node.js APIs, for the sake of this example)
    const fs = require('fs');
    var romData = fs.readFileSync('path/to/rom.nes', {encoding: 'binary'});
    
    // Load ROM data as a string or byte array
    nes.loadROM(romData);
    
    // Run frames at 60 fps, or as fast as you can.
    // You are responsible for reliable timing as best you can on your platform.
    nes.frame();
    nes.frame();
    // ...
    
    // Hook up whatever input device you have to the controller.
    nes.buttonDown(1, jsnes.Controller.BUTTON_A);
    nes.frame();
    nes.buttonUp(1, jsnes.Controller.BUTTON_A);
    nes.frame();
    // ...
    

    API Reference

    NES

    var nes = new jsnes.NES(options);
    

    Options:

    OptionTypeDescription
    onFramefunction(frameBuffer)Called at the end of each frame with a 256×240 pixel buffer (Int32Array of ARGB values).
    onAudioSamplefunction(left, right)Called for each audio sample with left/right channel values (-1.0 to 1.0).
    onStatusUpdatefunction(status)Called with status messages (e.g. "Ready to load a ROM.").
    onBatteryRamWritefunction(address, value)Called when battery-backed SRAM is written. Use this to persist save data.
    emulateSoundbooleanEnable/disable audio emulation. Default: true.
    sampleRatenumberAudio sample rate in Hz. Default: 48000.

    Methods:

    MethodDescription
    nes.loadROM(data)Load a ROM from a string, Uint8Array, or ArrayBuffer.
    nes.frame()Execute one frame of emulation. Call this at 60 fps.
    nes.buttonDown(player, button)Press a button. player is 1 or 2.
    nes.buttonUp(player, button)Release a button.
    nes.reset()Reset the emulator (like pressing the reset button on the NES).
    nes.reloadROM()Reload the current ROM from scratch.
    nes.getFPS()Get the current frames-per-second count.
    nes.setFramerate(rate)Change the target frame rate.
    nes.toJSON()Serialize emulator state (for save states).
    nes.fromJSON(data)Restore emulator state from a previous toJSON() call.
    nes.zapperMove(x, y)Move the Zapper light gun to the given pixel coordinates.
    nes.zapperFireDown()Pull the Zapper trigger.
    nes.zapperFireUp()Release the Zapper trigger.

    Button constants (on jsnes.Controller):

    BUTTON_A, BUTTON_B, BUTTON_SELECT, BUTTON_START, BUTTON_UP, BUTTON_DOWN, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_TURBO_A, BUTTON_TURBO_B

    Browser

    var browser = new jsnes.Browser(options);
    

    Options:

    OptionTypeDescription
    containerHTMLElementRequired. The DOM element to render into.
    romDatastringROM data to load immediately. If omitted, call loadROM() later.
    onErrorfunction(error)Called when the emulator encounters an error during frame execution.
    onBatteryRamWritefunction(address, value)Called when battery-backed SRAM is written.

    Methods:

    MethodDescription
    browser.start()Start emulation. Called automatically if romData was provided.
    browser.stop()Pause emulation.
    browser.loadROM(data)Load a new ROM and start emulation.
    browser.fitInParent()Re-layout the canvas to fill its container.
    browser.screenshot()Returns an HTMLImageElement of the current frame.
    browser.destroy()Full cleanup: stop emulation, remove listeners, remove canvas.
    Browser.loadROMFromURL(url, callback)Static method. Fetch ROM data from a URL. Callback is (error, data).

    Properties:

    PropertyDescription
    browser.nesThe underlying NES instance.
    browser.keyboardThe KeyboardController for remapping keys.
    browser.gamepadThe GamepadController for remapping gamepad buttons.

    Keyboard Controls

    When using jsnes.Browser, the following keyboard bindings are set up by default:

    ButtonPlayer 1Player 2
    Up / Down / Left / RightArrow keysNumpad 8 / 2 / 4 / 6
    AXNumpad 7
    BZ (or Y)Numpad 9
    Turbo AS—
    Turbo BA—
    StartEnterNumpad 1
    SelectRight CtrlNumpad 3

    Turbo A and Turbo B behave like A and B but auto-fire repeatedly while the key is held.

    Key bindings can be customized at runtime via the browser.keyboard property. Use setKeys() to provide a custom mapping (persisted to localStorage) and loadKeys() to reload it:

    // Get the current key map
    var keys = browser.keyboard.keys;
    
    // Remap Player 1 A button to the J key (keyCode 74)
    keys[74] = [1, jsnes.Controller.BUTTON_A, "J"];
    browser.keyboard.setKeys(keys);
    

    Build

    To build a distribution:

    $ npm run build
    

    This will create dist/jsnes.min.js.

    Running tests

    $ npm test
    

    Formatting code

    All code must conform to Prettier formatting. The test suite won't pass unless it does.

    To automatically format all your code, run:

    $ npm run format
    

    Related projects

    • NEStation - Multiplayer NES over the internet!

    Thanks

    JSNES is based on James Sanders' vNES, and owes an awful lot to it. It also wouldn't have happened without Matt Wescott's JSSpeccy, which sparked the original idea. (Ben, circa 2008: "Hmm, I wonder what else could run in a browser?!")