Skip to main content
The EmulatorManager class handles emulator discovery, selection, and lifecycle management. It supports both RetroArch (external process) and native libretro core loading modes.

Constructor

const emulatorManager = new EmulatorManager();
Automatically discovers available emulators on the system and initializes the core downloader.

Core management

getCoreDownloader()

Returns the CoreDownloader instance for managing libretro cores.
const coreDownloader = emulatorManager.getCoreDownloader();
Returns: CoreDownloader

getCoresForSystem()

Returns information about all known cores for a system, including display name, description, and installation status.
const cores = emulatorManager.getCoresForSystem('nes');
systemId
string
required
System identifier (e.g., 'nes', 'snes', 'genesis')
Returns: CoreInfo[] Example response:
[
  {
    name: 'fceumm_libretro',
    displayName: 'FCEUmm',
    description: 'Fast NES emulator',
    installed: true,
    path: '/path/to/fceumm_libretro.dylib'
  }
]

getCorePathForSystem()

Get the core path for a specific system, checking the app-managed cores directory first, then falling back to RetroArch’s directory.
const corePath = emulatorManager.getCorePathForSystem('nes');
systemId
string
required
System identifier
Returns: string | null - Core file path, or null if no core is installed

Emulator management

getAvailableEmulators()

Get list of available emulators discovered on the system.
const emulators = emulatorManager.getAvailableEmulators();
Returns: EmulatorInfo[]
EmulatorInfo
object
id
string
Unique emulator identifier
name
string
Display name
type
string
Emulator type: 'retroarch' or 'libretro-native'
path
string
Installation path
supportedSystems
string[]
Array of system IDs this emulator supports
features
object
Feature support flags (saveStates, screenshots, pauseResume, etc.)

getEmulator()

Get a specific emulator by ID.
const emulator = emulatorManager.getEmulator('libretro-native');
emulatorId
string
required
Emulator identifier
Returns: EmulatorInfo | undefined

isEmulatorRunning()

Check if an emulator is currently running.
if (emulatorManager.isEmulatorRunning()) {
  console.log('Game is running');
}
Returns: boolean

getCurrentEmulatorPid()

Get the currently running emulator’s process ID (for external RetroArch processes).
const pid = emulatorManager.getCurrentEmulatorPid();
Returns: number | undefined

isNativeMode()

Check if the current emulator is using native libretro core loading.
if (emulatorManager.isNativeMode()) {
  // Single window mode - game renders inside BrowserWindow
}
Returns: boolean

Game launching

launchGame()

Launch a game with the specified emulator and system. Automatically downloads missing cores.
await emulatorManager.launchGame(
  '/path/to/game.nes',
  'nes',
  'libretro-native',
  undefined,
  'fceumm_libretro'
);
romPath
string
required
Full path to the ROM file
systemId
string
required
System identifier (e.g., 'nes', 'snes')
emulatorId
string
Emulator ID to use. If omitted, selects the best available emulator for the system.
extraArgs
string[]
Additional command-line arguments for external emulators
coreName
string
Specific core to use (e.g., 'fceumm_libretro'). If omitted, uses the preferred core for the system.
Returns: Promise<void> Throws: Error if no emulator is found for the system or core download fails Emits:
  • gameLaunched - When game starts successfully
  • core:downloadProgress - During core download (if needed)
If the requested core is not installed, launchGame will automatically download it before launching.

stopEmulator()

Stop the currently running emulator.
await emulatorManager.stopEmulator();
Returns: Promise<void>

Emulation control

pause()

Pause the current emulator.
await emulatorManager.pause();
Returns: Promise<void> Throws: Error if no emulator is running Emits: emulator:paused

resume()

Resume the current emulator.
await emulatorManager.resume();
Returns: Promise<void> Throws: Error if no emulator is running Emits: emulator:resumed

reset()

Reset the current emulator (soft reset the game).
await emulatorManager.reset();
Returns: Promise<void> Throws: Error if no emulator is running Emits: emulator:reset

setSpeed()

Set emulation speed multiplier.
// Fast-forward at 2x speed
emulatorManager.setSpeed(2);

// Reset to normal speed
emulatorManager.setSpeed(1);
multiplier
number
required
Speed multiplier (1 = normal, 2 = 2x, 0.5 = half speed)
Returns: void
Speed control only works in native mode (libretro-native). External RetroArch processes do not support this API.

Save states

saveState()

Save state to a specific slot.
// Save to slot 0
await emulatorManager.saveState(0);

// Slot 99 is reserved for autosave
await emulatorManager.saveState(99);
slot
number
required
Save slot number (0-99). Slot 99 is used for autosave on game close.
Returns: Promise<void> Throws: Error if no emulator is running or save fails Emits: emulator:stateSaved with { slot: number }

loadState()

Load state from a specific slot.
// Load from slot 0
await emulatorManager.loadState(0);

// Load autosave
await emulatorManager.loadState(99);
slot
number
required
Save slot number to load from
Returns: Promise<void> Throws: Error if no emulator is running or load fails Emits: emulator:stateLoaded with { slot: number }

Screenshots

screenshot()

Take a screenshot of the current game.
// Auto-generate filename
const path = await emulatorManager.screenshot();

// Custom output path
const path = await emulatorManager.screenshot('/path/to/screenshot.png');
outputPath
string
Optional output file path. If omitted, generates a timestamped filename in the screenshots directory.
Returns: Promise<string> - Path to the saved screenshot Throws: Error if no emulator is running or screenshot fails Emits: emulator:screenshotTaken with { path: string }

Worker client (native mode)

setWorkerClient()

Set the worker client for native mode emulation. Called internally by IPCHandlers after spawning the utility process.
emulatorManager.setWorkerClient(workerClient);
client
EmulationWorkerClient | null
required
Worker client instance, or null to clear
Returns: void

getWorkerClient()

Get the current worker client (if in native mode).
const workerClient = emulatorManager.getWorkerClient();
if (workerClient) {
  console.log('Running in native mode');
}
Returns: EmulationWorkerClient | null

getCurrentEmulator()

Get the current emulator instance (for native core access).
const emulator = emulatorManager.getCurrentEmulator();
Returns: EmulatorCore | null

prepareForQuit()

Synchronously mark the worker as shutting down so that a process exit during the async shutdown sequence doesn’t emit an error.
emulatorManager.prepareForQuit();
Returns: void
This must be called before the app quits to prevent error events during worker shutdown.

Events

EmulatorManager extends EventEmitter and emits the following events:
gameLaunched
object
Emitted when a game starts successfully
romPath
string
Path to the ROM file
systemId
string
System identifier
emulatorId
string
Emulator used
emulator:launched
object
Emulated by the underlying EmulatorCore
emulator:exited
object
Emulator process exited
code
number
Exit code
emulator:error
object
Emulator error occurred
message
string
Error message
fatal
boolean
Whether the error is fatal
emulator:paused
void
Emulation paused
emulator:resumed
void
Emulation resumed
emulator:reset
void
Emulation reset
emulator:speedChanged
object
Emulation speed changed
multiplier
number
New speed multiplier
emulator:stateSaved
object
Save state created
slot
number
Slot number
emulator:stateLoaded
object
Save state loaded
slot
number
Slot number
emulator:screenshotTaken
object
Screenshot saved
path
string
File path
emulator:terminated
void
Emulator terminated
core:downloadProgress
object
Core download progress
coreName
string
Core being downloaded
progress
number
Download progress (0-100)

Usage example

import { EmulatorManager } from './EmulatorManager';

const emulatorManager = new EmulatorManager();

// Listen for events
emulatorManager.on('gameLaunched', (data) => {
  console.log(`Game launched: ${data.romPath}`);
});

emulatorManager.on('emulator:error', (error) => {
  console.error('Emulator error:', error.message);
});

// Launch a game with automatic core selection
await emulatorManager.launchGame(
  '/path/to/super-mario-bros.nes',
  'nes'
);

// Pause the game
await emulatorManager.pause();

// Save state
await emulatorManager.saveState(0);

// Resume
await emulatorManager.resume();

// Stop when done
await emulatorManager.stopEmulator();

Error handling

All async methods throw errors that should be caught:
try {
  await emulatorManager.launchGame(romPath, systemId);
} catch (error) {
  if (error.message.includes('No emulator found')) {
    console.error('System not supported');
  } else {
    console.error('Launch failed:', error.message);
  }
}

Source reference

  • Implementation: apps/desktop/src/main/emulator/EmulatorManager.ts:16
  • Usage example: apps/desktop/src/main/ipc/handlers.ts:21

Build docs developers (and LLMs) love