GitHub - muditjuneja/koin: The high-performance, preservation-focused emulation engine powering Koin Deck. Built for those who remember blowing into cartridges, but demand the convenience of the cloud.

4 min read Original article ↗

koin.js

Browser Retro Game Emulation for React

24 systems. Cloud saves. Multi-language. Zero backend required.

Try the Demo NPM Version License

The drop-in React component for browser-based retro game emulation. Built on Nostalgist.js, adding production-ready features like cloud saves, touch controls, gameplay recording, and RetroAchievements.

koin.js

Features

🎮 Core Emulation

  • 25 Consoles — NES to PlayStation, Game Boy to Saturn
  • Automatic Core Selection — Best emulator core per system
  • BIOS Management — Multi-file BIOS support with UI selection
  • Performance Optimized — SharedArrayBuffer for maximum speed

â˜ī¸ Save System

  • Slot-Based Saves — Multiple save states with screenshots
  • Auto-Save — Periodic background saves (configurable interval)
  • Emergency Saves — Automatic save on tab hide/close
  • Cloud-Ready API — Bring your own backend with async handlers

🎨 Display & Effects

  • 10 CRT Shaders — Lottes, Geom, Easymode, Hyllian, zFast, and more
  • Runtime Shader Switching — Change filters without restart
  • System Theming — Per-console accent colors
  • Screenshot Capture — PNG snapshots with hotkey support

đŸ•šī¸ Controls

  • Keyboard Remapping — Per-console custom key bindings
  • Gamepad Support — Auto-detect Xbox, PlayStation, Nintendo controllers
  • Touch Controls — GPU-accelerated virtual D-pad and buttons for mobile
  • Control Persistence — Saves user preferences across sessions

âĒ Special Features

  • Rewind — Time-travel gameplay (auto-enabled for 8/16-bit)
  • Speed Control — 0.25x to 4x with hotkey toggle
  • Fast-Forward — Turbo mode for grinding

📹 Recording & Overlays

  • Gameplay Recording — VP9/VP8 WebM capture at 30fps
  • Performance Overlay — FPS, frame time, memory stats
  • Input Display — Virtual controller overlay for streaming
  • Toast Notifications — Non-intrusive save/load feedback

🏆 RetroAchievements

  • Official RA Integration — Track unlocks across sessions
  • Hardcore Mode — Disable saves/cheats for leaderboard eligibility
  • Achievement Browser — Filter by locked/unlocked status
  • Progress Tracking — Points remaining per game

🌍 Internationalization

  • 3 Built-in Languages — English, Spanish, French
  • Type-Safe Translations — Full TypeScript support
  • Partial Overrides — Customize specific strings
  • Custom Languages — Implement your own translation set

đŸŽ¯ Developer Experience

  • TypeScript First — Complete type definitions
  • Zero Config — Works out of the box
  • Customizable UI — Accent colors, shaders, controls
  • Web Component — Use without React

Installation

npm install koin.js
# or
yarn add koin.js
# or
pnpm add koin.js

Quick Start

import { GamePlayer } from 'koin.js';
import 'koin.js/styles.css';

export default function App() {
  return (
    <GamePlayer
      romId="game-123"
      romUrl="/roms/mario.nes"
      system="NES"
      title="Super Mario Bros."
    />
  );
}

Cloud Integration

import { GamePlayer } from 'koin.js';

<GamePlayer
  romId="game-123"
  romUrl="/roms/game.nes"
  system="NES"
  title="My Game"
  
  // Cloud save handlers
  onSaveState={async (slot, blob, screenshot) => {
    await fetch(`/api/saves/${slot}`, {
      method: 'POST',
      body: blob,
    });
  }}
  onLoadState={async (slot) => {
    const res = await fetch(`/api/saves/${slot}`);
    return res.ok ? await res.blob() : null;
  }}
  onAutoSave={async (blob, screenshot) => {
    await fetch('/api/autosave', { method: 'POST', body: blob });
  }}
  
  // Customization
  systemColor="#FF3333"
  shader="crt/crt-lottes"
  initialLanguage="es"
/>

Internationalization

<GamePlayer
  initialLanguage="es"  // Spanish UI
/>

// Or provide custom translations
import { en } from 'koin.js';

<GamePlayer
  translations={{
    controls: {
      ...en.controls,
      play: 'START GAME',
    }
  }}
/>

Web Component

<script src="https://unpkg.com/koin.js/dist/web-component.global.js"></script>

<retro-game-player
  rom-url="./game.nes"
  system="nes"
  title="My Game"
  rom-id="game-1"
></retro-game-player>

Supported Systems

System Key Core Source
NES / Famicom NES fceumm Nostalgist
Super Nintendo SNES snes9x Nostalgist
Nintendo 64 N64 mupen64plus_next BinBashBanana
Game Boy / Color GB, GBC gambatte Nostalgist
Game Boy Advance GBA mgba Nostalgist
Nintendo DS NDS melonds BinBashBanana
PlayStation PS1 pcsx_rearmed Nostalgist
Sega Genesis / Mega Drive GENESIS genesis_plus_gx Nostalgist
Sega Master System MASTER_SYSTEM gearsystem Nostalgist
Game Gear GAME_GEAR gearsystem Nostalgist
Sega Saturn SATURN yabause BinBashBanana
Neo Geo NEOGEO fbalpha2012_neogeo Nostalgist
Arcade (FBNeo) ARCADE fbneo Nostalgist
Atari 2600 ATARI_2600 stella2014 BinBashBanana
Atari 5200 ATARI_5200 a5200 BinBashBanana
Atari 7800 ATARI_7800 prosystem BinBashBanana
Atari Lynx LYNX handy Nostalgist
PC Engine / TurboGrafx-16 PC_ENGINE mednafen_pce_fast Nostalgist
WonderSwan / Color WONDERSWAN, WONDERSWAN_COLOR mednafen_wswan Nostalgist
Virtual Boy VIRTUAL_BOY mednafen_vb Nostalgist
Neo Geo Pocket / Color NEOGEO_POCKET, NEOGEO_POCKET_COLOR mednafen_ngp Nostalgist
Commodore 64 C64 vice_x64 Nostalgist

Note: Systems marked BinBashBanana use cores from BinBashBanana/webretro via jsDelivr. Dreamcast and PSP are currently unavailable due to lack of compatible WASM cores.

Full system details →

Requirements

COOP/COEP Headers Required for SharedArrayBuffer:

// next.config.js
async headers() {
  return [{
    source: '/:path*',
    headers: [
      { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
      { key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
    ],
  }];
}

Documentation

License

MIT Š Mudit Juneja