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

romdev-core-host

v0.5.0

Published

The romdev libretro host runtime, now ISOMORPHIC: load a romdev-core-* emulator core from disk (Node) or from a factory + wasm bytes (browser/worker), run frames, read the framebuffer/audio/memory regions as typed arrays, drive input, save/load state. The

Readme

romdev-core-host

The romdev libretro host runtime, published standalone: load a romdev-core-* emulator core (WASM), run frames, read the framebuffer / audio / memory, drive input, save and load state. This is the SAME host the romdev MCP server runs (the server consumes this package too), so there is exactly one host implementation in the ecosystem. Bundles NO cores; pass any romdev-core-* package.

Isomorphic: the core surface has no top-level node: imports and no pngjs. Under Node you load cores and ROMs by path; in a browser or worker bundle you pass the Emscripten factory + wasm bytes and ROM bytes, and the same host runs unchanged (see "Browser / bytes-only use" below).

import { LibretroHost } from "romdev-core-host";
import * as core from "romdev-core-fceumm"; // any romdev-core-* package

const host = new LibretroHost();
await host.loadCore(core.core.jsPath, core.core.wasmPath);
await host.loadMedia({ platform: "nes", path: "game.nes" });

host.stepFrames(60);
const shot = host.screenshot();            // { pngBase64, width, height }
host.setInput({ ports: [{ start: true }] });
host.stepFrames(2);

ROMs load from disk (path) or from memory (bytes + an optional name whose extension tells the core what it is):

host.loadMedia({ platform: "nes", bytes: romBytes, name: "game.nes" });

API map

Every optional capability is feature-detected: if the loaded core supports it there is a matching *Supported() method, and unsupported calls throw a descriptive error rather than failing silently.

Lifecycle. new LibretroHost({ systemDir, saveDir, log }), loadCore(jsPath, wasmPath, { hwRender }), loadMedia({ platform, path | bytes, name, mediaKind, systemDir }), unloadMedia(), reset(), hardReset(), pause() / resume(), getStatus() (platform, frameCount, framebuffer size, audioSampleRate).

Frames and video. stepFrames(n), renderOneFrame(), getFramebuffer() (raw pixels + pitch + format), screenshot() (PNG), screenshotRgba(), framebufferHash() for cheap change detection.

Audio. Cores push interleaved signed 16-bit stereo into host.state.audioRing (an array of chunks); drain it each frame and play at host.status.audioSampleRate. romdev-core-runner does exactly this.

Input. setInput({ ports: [{ up, down, left, right, a, b, x, y, l, r, l2, r2, l3, r3, start, select }] }) with one object per controller port, named after RetroPad buttons. Held state persists until the next call.

Save states. saveState(name) / loadState(name) / listStates() / getStateBlob(name) for named in-memory states, or serializeState() / unserializeState(blob) for raw Uint8Array blobs you persist yourself.

Memory. readMemory(region, offset, length), writeMemory(region, offset, bytes), regionSize(region), getCartRom(), and writeMemoryCpuAddr(cpuAddr, bytes) for CPU-address writes. Region names are platform-keyed; MemoryRegionToRetro (exported from the index) is the single source of truth.

Cheats. setCheat(index, code), clearCheats(), listActiveCheats(). The encoder/decoder for the native cheat-device formats lives at romdev-core-host/gamegenie.js (decodeCode, encodeForDevice, nativeDevicesFor, and per-device helpers).

Debug / reverse-engineering tier (all feature-detected per core): write watchpoints, read watches, PC breakpoints, runUntilPC(addr) / runUntilRead(addr), stepInstruction(), getReg(id) / setReg(id, v), callSubroutine(...) with a no-hang watchdog, watchVram(lo, hi, frames), watchDma(frames), and getRegSnapshot().

Platform extras. C64: keyboard-matrix typing (pressC64Key, typeC64Text, setC64HeldKeys) and disk-image import/export (exportDiskImage, importDiskImage, putDiskFile). MSX: the bundled open C-BIOS system dir resolves automatically. The 3D cores (N64, PS1, Dreamcast) hardware-render through a headless GL context: pass { hwRender: true } to loadCore and install the optional native-gles dependency; the software-rendered cores need neither.

Browser / bytes-only use

Everything the index exports is browser-bundleable (enforced by a gate test: no top-level node: imports, no pngjs in the static closure). The browser contract is "the caller does the I/O":

import { LibretroHost } from "romdev-core-host";
import factory from "romdev-core-fceumm/wasm/fceumm_libretro.js"; // your bundle
const wasmBinary = new Uint8Array(await (await fetch(wasmUrl)).arrayBuffer());
const romBytes   = new Uint8Array(await (await fetch(romUrl)).arrayBuffer());

const host = new LibretroHost();
await host.loadCore({ factory, wasmBinary });
await host.loadMedia({ platform: "nes", bytes: romBytes });
host.stepFrames(1);
const { rgba, width, height } = host.screenshotRgba(); // blit to a canvas
  • loadCore({ factory, wasmBinary }) takes the glue's default export + wasm bytes; loadCore(jsPath, wasmPath) stays the Node path form. Passing io: false in the options forces the pure contract even under Node (the path-based branches then refuse with a pointer to their bytes equivalent).
  • loadMedia({ systemFiles: { "Machines/x/y.rom": bytes } }) mounts an in-memory BIOS tree, the browser alternative to a host-disk systemDir.
  • screenshot() (base64 PNG) needs the lazily-preloaded PNG tier; where a bundle omits pngjs it throws a descriptive error and the typed-array surface (getFramebuffer() / screenshotRgba()) is the path.
  • Node-only by nature: NODERAWFS cores (Dreamcast disc streaming), the 3D cores' native GL stack, and terminal (chafa) rendering.

Subpath exports

The index exports LibretroHost, the pure framebuffer codecs (framebufferToRgba / decodePixelsInto), the pure utilities (encodeCString, writeFsTree, extnameOf, isNodeEnv), every retro constant, and the shared type helpers. Everything else is addressable directly, e.g.:

import { decodeCode } from "romdev-core-host/gamegenie.js";
// PNG encode/crop/resample (pngjs-backed, Node or shimmed bundles):
import { framebufferToPng, resamplePng } from "romdev-core-host/framebuffer-png.js";

Per-chip audio/state decoders (nes-apu-state.js, gb-apu-state.js, dsp-state.js, gpgx-state.js, snes9x-state.js, c64-sid-state.js, gba-video-state.js, and friends) follow the same pattern. The Node I/O adapter (io-node.js) is internal: the host lazy-loads it on path-based calls; browser bundles never reach it.

Requirements

Node >= 24, ESM only. For a ready-made SDL window over this host (keyboard

  • hot-plug gamepad input, audio, aspect-correct scaling) see romdev-core-runner. For the full agent tooling surface (MCP server, build toolchains, disassembly, playtest) see romdevtools.