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 savesromUrl— URL to the ROM filesystem— 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>