Usage Guide

From basic drop-in to advanced cloud integration.

Minimal Example

The simplest possible setup. ROM loads from URL, saves download as files.

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


export default function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <GamePlayer
        romId="game-123"
        romUrl="https://example.com/roms/mario.nes"
        system="NES"
        title="Super Mario Bros."
      />
    </div>
  );
}

Required Props

  • romId — Unique identifier for saves
  • romUrl — URL to the ROM file
  • system — Console key (NES, GBA, PS1...)
  • title — Display name

Cloud Save Integration

Connect to your backend by providing async handlers. The player manages the UI and save queue — you just implement the storage.

CloudExample.tsx
import { GamePlayer, SaveSlot } from 'koin.js';

export default function CloudExample() {
  // Fetch list of existing saves
  const handleGetSlots = async (): Promise<SaveSlot[]> => {
    const res = await fetch('/api/saves');
    return res.json();
  };

  // Save state to a slot
  const handleSave = async (slot: number, blob: Blob, screenshot?: string) => {
    const formData = new FormData();
    formData.append('state', blob);
    if (screenshot) formData.append('screenshot', screenshot);
    
    await fetch(`/api/saves/${slot}`, {
      method: 'POST',
      body: formData,
    });
  };

  // Load state from a slot
  const handleLoad = async (slot: number): Promise<Blob | null> => {
    const res = await fetch(`/api/saves/${slot}`);
    if (!res.ok) return null;
    return res.blob();
  };

  // Auto-save (runs every 60s by default)
  const handleAutoSave = async (blob: Blob, screenshot?: string) => {
    await fetch('/api/saves/auto', {
      method: 'POST',
      body: blob,
    });
  };

  return (
    <GamePlayer
      romId="game-123"
      romUrl="/roms/game.nes"
      system="NES"
      title="My Game"
      
      // Save hooks
      onGetSaveSlots={handleGetSlots}
      onSaveState={handleSave}
      onLoadState={handleLoad}
      onAutoSave={handleAutoSave}
      autoSaveInterval={60000} // 60 seconds
      
      // Optional: Limit slots for free tier
      maxSlots={3}
      currentTier="Free"
      onUpgrade={() => window.location.href = '/upgrade'}
    />
  );
}

BIOS Handling

Some systems require BIOS files. The player supports two mounting modes:

System Folder (Default)

BIOS mounts to /system/. Used by most cores.

biosUrl="https://example.com/scph5501.bin"

ROM Folder (Arcade/NeoGeo)

BIOS mounts alongside ROM in /content/.

biosUrl={{
  url: "https://example.com/neogeo.zip",
  name: "neogeo.zip",
  location: "rom_folder"
}}

Systems Requiring BIOS

PlayStationSaturnDreamcastPC Engine CDSega CDNeo Geo *Atari LynxArcade (varies)

* Neo Geo BIOS is often bundled with ROM

RetroAchievements Integration

Enable achievement tracking by providing user credentials and handling login flow.

RAExample.tsx
<GamePlayer
  // ... core props
  
  // RetroAchievements
  retroAchievementsConfig={{
    username: 'player123',
    token: 'user-api-token',  // NOT password!
    hardcore: false,          // Enable for no saves/rewind
  }}
  
  // UI props (from your RA API calls)
  raUser={raCredentials}
  raGame={gameData}
  raAchievements={achievements}
  raUnlockedAchievements={new Set([1, 2, 5])}
  
  // Handlers
  onRALogin={async (user, pass) => {
    // Call RA API, return true on success
    return await loginToRA(user, pass);
  }}
  onRALogout={() => clearRACredentials()}
  onRAHardcoreChange={(enabled) => setHardcore(enabled)}
/>

Hardcore Mode Restrictions

When hardcore: true, the player automatically disables:

  • • Save states (load/save)
  • • Rewind
  • • Cheats
  • • Slow motion (<1x speed)

Vanilla HTML (Web Component)

No React? No problem. Use the bundled Web Component.

index.html
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/koin.js/dist/web-component.global.js"></script>
</head>
<body>
  <retro-game-player 
    rom-url="./game.nes" 
    system="nes" 
    title="My Game"
    rom-id="game-1"
  ></retro-game-player>

  <script>
    const player = document.querySelector('retro-game-player');
    
    // Advanced properties via JS
    player.onSaveState = async (slot, blob) => {
      console.log('Saved to slot', slot, blob);
    };
  </script>
</body>
</html>

> Next Steps_