npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

retroemu

v0.1.1

Published

Terminal retro game emulator using libretro WASM cores

Readme

retroemu

Terminal-based retro game emulator. Play classic console games directly in your terminal using libretro WASM cores.

  • 25+ retro systems — NES, SNES, Game Boy, Genesis, Atari, and more
  • Truecolor ANSI rendering — half-block characters for clean pixel art
  • 2100+ controllers supported — via SDL2 with automatic mapping
  • Low-latency audio — direct SDL2 audio output
  • Save states & battery saves — automatic SRAM persistence
retroemu game.nes

Supported Systems

The emulator auto-detects systems by file extension. All cores are from the libretro project, compiled to WebAssembly.

Nintendo

| System | ROM Extensions | Core | |--------|----------------|------| | NES / Famicom | .nes .fds .unf .unif | fceumm | | Super Nintendo | .sfc .smc | snes9x | | Game Boy | .gb | gambatte | | Game Boy Color | .gbc | gambatte | | Game Boy Advance | .gba | mgba |

Sega

| System | ROM Extensions | Core | |--------|----------------|------| | Genesis / Mega Drive | .md .gen .smd .bin | genesis_plus_gx | | Master System | .sms | genesis_plus_gx | | Game Gear | .gg | genesis_plus_gx | | SG-1000 | .sg | genesis_plus_gx |

Atari

| System | ROM Extensions | Core | |--------|----------------|------| | Atari 2600 | .a26 | stella2014 | | Atari 5200 | .a52 | atari800 | | Atari 7800 | .a78 | prosystem | | Atari 800/XL/XE | .xex .atr .atx .bas .car .xfd | atari800 | | Atari Lynx | .lnx .o | handy |

NEC

| System | ROM Extensions | Core | |--------|----------------|------| | TurboGrafx-16 / PC Engine | .pce .cue .ccd .chd | beetle_pce_fast |

SNK

| System | ROM Extensions | Core | |--------|----------------|------| | Neo Geo Pocket | .ngp | mednafen_ngp | | Neo Geo Pocket Color | .ngc | mednafen_ngp |

Bandai

| System | ROM Extensions | Core | |--------|----------------|------| | WonderSwan | .ws | mednafen_wswan | | WonderSwan Color | .wsc | mednafen_wswan |

Other Consoles

| System | ROM Extensions | Core | |--------|----------------|------| | ColecoVision | .col | gearcoleco | | Vectrex | .vec | vecx |

Home Computers

| System | ROM Extensions | Core | |--------|----------------|------| | ZX Spectrum | .tzx .z80 .sna | fuse | | MSX / MSX2 | .mx1 .mx2 .rom .dsk .cas | fmsx |

Just run retroemu <rom-file> and the correct core loads automatically based on the file extension.

ZIP support: ROMs can be provided inside .zip archives — the emulator will automatically extract and load the first supported ROM file found.

How It Works

The emulator loads libretro cores compiled to WebAssembly via Emscripten. Each frame, the WASM core executes one tick of the emulated CPU, then calls back into JavaScript with:

  • Video: A raw pixel framebuffer (RGB565, XRGB8888, or 0RGB1555) that gets converted to RGBA and rendered to the terminal as truecolor ANSI art via chafa-wasm in a worker thread
  • Audio: Interleaved int16 stereo samples sent directly to SDL2 audio device (via @kmamal/sdl)
  • Input: Polled from physical gamepads through the W3C Gamepad API (via gamepad-node), with keyboard fallback
 retroemu <rom>
   │
   LibretroHost  ── loads WASM core, registers callbacks, drives retro_run() at 60fps
   │
   core._retro_run()
     │
     ├── input_poll    ──► InputManager.poll() ──► navigator.getGamepads()
     ├── input_state   ──► InputManager.getState(port, device, index, id)
     ├── [emulate one frame]
     ├── video_refresh ──► VideoOutput ──► worker thread ──► chafa-wasm ──► terminal
     └── audio_batch   ──► AudioBridge ──► SDL2 ──► speakers

Prerequisites

  • Node.js >= 22.0.0 (for ES modules and worker threads)
  • Emscripten SDK (only needed for building cores from source)
  • A truecolor terminal (iTerm2, Kitty, Alacritty, Windows Terminal, GNOME Terminal, etc.)

Installation

npm install -g retroemu

Building Cores

Cores must be compiled from C/C++ source to WASM using Emscripten.

Build all cores:

npm run build:cores

Build a single core (e.g., NES):

bash scripts/cores/fceumm.sh

The build script clones the libretro core repo, compiles it with emmake, and links it into a WASM module with the correct exported functions. Output goes to cores/{name}_libretro.js + .wasm.

Emscripten Setup

If you don't have Emscripten installed:

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

Usage

retroemu [options] <rom-file>

Options:
  --save-dir <dir>     Directory for save files (default: <rom-dir>/saves)
  --frame-skip <n>     Render every Nth frame to terminal (default: 2)
  --contrast <n>       Contrast boost, 1.0=normal, 1.5+=enhanced (default: 1.0)
  --ascii              Use detailed Unicode symbols instead of half-blocks
  --no-gamepad         Disable gamepad input (keyboard only)
  -h, --help           Show help

Examples:

retroemu ~/roms/mario.nes
retroemu ~/roms/zelda.zip              # extracts ROM from ZIP automatically
emu --frame-skip 3 ~/roms/zelda.sfc
emu --save-dir ~/.emu/saves ~/roms/pokemon.gbc
emu --contrast 1.5 ~/roms/space_invaders.a26   # boost contrast for dark games

Rendering

The emulator uses chafa-wasm to convert pixel data to truecolor ANSI sequences.

Default mode (half-blocks): Uses vertical half-block characters (▀▄) which provide 2 vertical pixels per character cell. This matches the blocky pixel art aesthetic of retro games and renders quickly.

ASCII mode (--ascii): Uses a wider variety of Unicode block and border characters for more detail. May look better for some games but can introduce visual artifacts.

Contrast boost (--contrast): Some games (especially Atari) have low contrast that doesn't translate well to terminal rendering. Use --contrast 1.5 or higher to boost visibility.

Controls

Gamepad

Any gamepad recognized by gamepad-node works automatically. Buttons are mapped positionally — the south face button is always B, east is A, etc. — regardless of the controller's printed labels.

| Gamepad Button | Libretro | |---------------|----------| | South face (A/Cross) | B | | East face (B/Circle) | A | | West face (X/Square) | Y | | North face (Y/Triangle) | X | | L1 / LB | L | | R1 / RB | R | | L2 / LT | L2 | | R2 / RT | R2 | | Select / Back | Select | | Start / Options | Start | | D-Pad | D-Pad | | Left Stick | Analog Left | | Right Stick | Analog Right |

Keyboard

Keyboard input is available as a fallback for player 1:

| Key | Action | |-----|--------| | Arrow keys | D-Pad | | Z | B | | X | A | | A | Y | | S | X | | Q | L | | W | R | | Enter | Start | | Shift | Select |

Hotkeys

| Key | Action | |-----|--------| | F1 | Reset | | F5 | Save state (slot 0) | | F7 | Load state (slot 0) | | ESC | Quit | | Ctrl+C | Force quit |

Save System

SRAM (battery-backed saves) is automatically saved when you quit and loaded when you start a ROM. Save files are stored as {rom-name}.srm in the save directory.

Save states capture the full emulation state (CPU registers, memory, video state, etc.) and are stored as {rom-name}.state0. Use F5 to save and F7 to load.

Default save directory: saves/ next to the ROM file, configurable with --save-dir.

Architecture

retroemu/
  bin/cli.js                    CLI entry point
  index.js                      Library exports
  src/
    core/
      LibretroHost.js           Main engine: WASM loading, callback registration, frame loop
      CoreLoader.js             Dynamic import of Emscripten WASM modules
      SystemDetector.js         ROM extension -> system/core mapping
      SaveManager.js            SRAM and save state persistence
    video/
      VideoOutput.js            Pixel format conversion, aspect ratio, contrast
      videoWorker.js            Worker thread for chafa-wasm rendering
    audio/
      AudioBridge.js            Direct SDL2 audio output
    input/
      InputManager.js           Gamepad polling + keyboard fallback
      InputMap.js               W3C <-> libretro button mapping tables
    constants/
      libretro.js               Libretro C API constants
  cores/                        Pre-built .wasm + .js glue files
  scripts/
    build-core.sh               Emscripten build for any libretro core
    build-all-cores.sh          Batch build
    cores/*.sh                  Per-core build configs

Key Modules

LibretroHost (src/core/LibretroHost.js) is the central orchestrator. It:

  1. Loads a WASM core via CoreLoader
  2. Registers 6 JavaScript callbacks as WASM function pointers using Emscripten's addFunction():
    • retro_environment — handles 20+ environment commands from the core (pixel format negotiation, directory queries, variable configuration, capability reporting)
    • retro_video_refresh — receives framebuffer data each frame
    • retro_audio_sample_batch — receives batched stereo audio samples
    • retro_audio_sample — receives individual stereo samples (legacy fallback)
    • retro_input_poll — triggers gamepad state refresh
    • retro_input_state — returns button/axis state for a given port, device, and button ID
  3. Allocates the ROM and retro_game_info struct in WASM memory
  4. Reads retro_system_av_info to get screen dimensions, FPS, and audio sample rate
  5. Runs the frame loop at the correct FPS using setTimeout/setImmediate hybrid timing

VideoOutput (src/video/VideoOutput.js) converts pixel data from the WASM heap into terminal art:

  • Supports three pixel formats: RGB565, XRGB8888, 0RGB1555
  • Uses pre-computed lookup tables (32-entry and 64-entry Uint8Arrays) for 16-bit to 8-bit color conversion — no division in the hot loop
  • Default half-block mode (▀▄) doubles vertical resolution and matches pixel art aesthetic
  • Optional detailed mode (--ascii) uses block/border Unicode symbols for more variety
  • Contrast boost option for low-contrast games (Atari, etc.)
  • Renders every Nth frame (configurable, default 2) to avoid overwhelming terminal I/O
  • Runs chafa conversion in a worker thread to keep the main loop responsive

AudioBridge (src/audio/AudioBridge.js) sends audio directly to SDL2:

  • Opens an SDL2 audio device in S16 stereo format (matches libretro exactly)
  • Zero-copy path: passes WASM memory buffer directly to SDL2's queue
  • No sample format conversion needed — libretro and SDL2 both use int16

InputManager (src/input/InputManager.js) multiplexes gamepad and keyboard input:

  • Calls navigator.getGamepads() (provided by gamepad-node) each frame
  • Maps W3C standard button indices to libretro joypad IDs via InputMap
  • Supports input bitmasks for modern cores (mGBA, etc.) that query all buttons at once
  • Supports analog sticks (W3C float axes converted to libretro int16 range)
  • Falls back to keyboard for player 1 using stdin raw mode with frame-based key hold timing

Build System

scripts/build-core.sh compiles any libretro core to WASM:

  1. Clones the core repo (git clone --depth 1)
  2. Builds with emmake make -f Makefile.libretro platform=emscripten
  3. Links the output with emcc using flags:
    • -O3 — full optimization
    • -s MODULARIZE=1 -s EXPORT_ES6=1 — ES module factory
    • -s ENVIRONMENT=node — Node.js target
    • -s ALLOW_MEMORY_GROWTH=1 — dynamic memory (32MB initial, 256MB max)
    • -s ALLOW_TABLE_GROWTH=1 — required for addFunction() callback registration
    • -s FILESYSTEM=0 — no Emscripten FS (host handles I/O)
  4. Exports 23 libretro API functions + Emscripten runtime helpers (addFunction, HEAPU8, setValue, etc.)

Programmatic API

import { LibretroHost, VideoOutput, AudioBridge, InputManager, SaveManager } from 'retroemu';

const video = new VideoOutput();
await video.init();

const audio = new AudioBridge();
const input = new InputManager();
const saves = new SaveManager('./saves');

const host = new LibretroHost({
  videoOutput: video,
  audioBridge: audio,
  inputManager: input,
  saveManager: saves,
});

await host.loadAndStart('./game.nes');

// Later:
await host.saveState(0);
await host.loadState(0);
host.reset();
await host.shutdown();

Dependencies

| Package | Purpose | |---------|---------| | gamepad-node | W3C Gamepad API for Node.js via SDL2 — 2100+ controllers with standard mapping | | @kmamal/sdl | Native SDL2 bindings for Node.js — audio output and gamepad input | | chafa-wasm | Image-to-ANSI conversion — auto-detects Sixel, Kitty, or Unicode block art |

Acknowledgments

This project is built on top of the amazing work by the libretro team and the RetroArch community. All emulator cores are libretro cores compiled to WebAssembly:

  • libretro provides a standardized API that allows emulator cores to be written once and run on many frontends
  • RetroArch is the reference frontend implementation and home to most libretro core development
  • Individual core authors and maintainers who have created and continue to improve these emulators

Without the libretro ecosystem and the open-source emulation community, this project would not be possible.

License

MIT