Skip to main content
Version: Stable

Launchers

A launcher tells Zaparoo Core how to open a game, video, app, or other media file. Each platform has its own launchers for matching a system and file to the right program.

Loading games

Core first adds installed games and media files to its media database. It then uses the matching platform launcher when you scan a token or select a search result. If a game is missing from search, update the media database. If the game appears but does not open, check its platform launcher guide for required software and setup.

Platform launcher guides

Use your platform guide for exact setup, supported systems, and launcher limitations. Built-in integrations include:

PlatformLauncher integrations
MiSTer FPGAMiSTer cores, AO486, ScummVM, video, and scripts
WindowsSteam, LaunchBox/Big Box, RetroBat, Flashpoint, Kodi, executables, and scripts
LinuxSteam, Lutris, Heroic, RetroArch, Kodi, browser URLs, and scripts
SteamOSSteam, native emulators, RetroArch, EmuDeck, RetroDECK, Kodi, and scripts
BazziteSteam, Lutris, Heroic, Kodi, browser URLs, and scripts
BatoceraEmulationStation, Kodi, and scripts

For an emulator or media app without a built-in integration, use a custom launcher.

Launchables

Some things can be launched but don't map cleanly to a file on disk or a normal system, like a ROM-less FPGA core. Launchables are how Core handles these. Core defines them in code and exposes them as virtual versions of the things you already use: a virtual system or a virtual media entry, whichever fits.

You don't deal with launchables as a separate concept. They show up in the Zaparoo App as ordinary systems or media that you can browse, pick, and write to a token like anything else. The only difference is that there is no real file or system behind them.

MiSTer's ROM-less Other cores, like Chess or Flappy Bird, are the first example. They have no game file and no system of their own, so Core exposes each one as a virtual system and indexes the ones you have installed.

The one place this shows through is the token value. A launchable is identified by a compact zaparoo:// URI instead of a file path, so a token that launches one holds a value like zaparoo://gezdgnbvgy3tqojqgezdgnbvgy rather than a normal path. You don't write this by hand; the App fills it in when you save a launchable.

Create a command launchable

A custom launcher can expose a command as a virtual system on any Core platform. Add an entry to a launcher TOML file in Core's launchers directory:

[[launchers.custom]]
id = "Tools"
kind = "virtual_system"
backend = "command"
name = "Tools"
category = "Computer"
execute = "echo tools"

The virtual system appears in browse and search without needing a media file. Selecting it runs execute. Valid categories are Other, Console, Computer, Handheld, and Arcade; Other is used when category is omitted.

Core derives a stable launchable identity from backend and id, so keep those values unchanged if you want App display settings and artwork to stay attached to the entry. Restart Core or refresh the launchers, then update the media database after adding a virtual system.

For MiSTer cores that launch without media, use the mister_core backend instead.

Launcher controls

Launcher controls send actions to the launcher handling the currently active media. Use them for actions like pause, stop, save state, load state, fast forward, rewind, or next and previous track.

Control support depends on the active launcher. If no media is active, or the launcher does not support the requested action, the command returns an error.

Control support varies

Each launcher supports its own set of actions. Check the active media response to see which controls are available.

How control actions work

Zaparoo Core asks the active launcher to run a named control action. Official launchers can implement controls directly. Custom launchers can define controls as ZapScript snippets in their launcher configuration.

When Core can tell that media is still starting, the control command waits before sending the action.

Built-in action names include:

ActionTypical use
toggle_pausePause or unpause active media
pausePause active media
resumeResume active media
save_stateSave emulator state
load_stateLoad emulator state
save_ramSave RAM data
toggle_menuOpen or close an in-game menu
resetReset active media
stopStop active media
fast_forwardFast forward
rewindRewind
nextMove to the next item
previousMove to the previous item

Not every launcher supports every action. Use the media or media.active API response to check the launcherControls available for the current media.

Current support

Built-in launcher support currently includes:

LauncherSupported actions
Kodi launchers: KodiLocalVideo, KodiMovie, KodiTVEpisode, KodiLocalAudio, KodiAlbum, KodiArtist, KodiTVShow, KodiSongtoggle_pause, stop, fast_forward, rewind, next, previous
Built-in RetroArch launchers on Linux and SteamOS, including EmuDecksave_state, load_state, toggle_menu, toggle_pause, reset, fast_forward, rewind, stop
Native audio launcher (Audio system, see Audio Playback)toggle_pause, pause, resume, stop, fast_forward, rewind
Custom launchersWhatever is defined in the launcher's controls table

EmuDeck standalone emulator launchers and RetroDECK launchers do not currently define built-in launcher controls.

To control background audio instead of the active game, add ?slot=background to the control action.

ZapScript control

Use the control command to send a control action from a token:

**control:toggle_pause

Save state for the active media, if the launcher supports it:

**control:save_state

See the ZapScript utility command reference for exact syntax and examples.

API control

Apps and integrations can use the media.control API method to send launcher control actions. The active media response includes launcherControls when controls are available.

Launcher availability

Core checks whether each launcher's runtime dependencies are present. Unavailable launchers are excluded from automatic selection, but remain in the launchers API response with available: false and an availabilityReason explaining what is missing.

After installing a missing dependency, restart Core or use Settings > Advanced > Reload Core in the terminal UI. You can also run -reload. Reloading asks supported platforms to rediscover launcher data such as MiSTer RBF files and Batocera's EmulationStation system configuration.

Custom launchers

Custom launchers are user-defined launchers configured with TOML files, similar to mapping files. Use them when your platform does not include a launcher for an emulator or media app you want to use.

Custom launchers provide fewer options than built-in launchers. Use one when the target app can open media from a file path or URL.

Media tracking limitations

Custom launcher tracking depends on how the launcher starts the app:

  • With the default blocking lifecycle, Core tracks the direct process started by the launcher
  • With background, Core starts the command and does not track the process afterward
  • If the command hands off to another app and exits, Core may lose track of the media even with blocking

When Core cannot track the launched process, features that depend on active media state, such as hold mode exit handling or API stop commands, may not work for that launcher.

Creating a custom launcher

To start, open the launchers directory in the Core data folder. Check the page for your platform if you're not sure where that folder is.

Create a new file ending in .toml. This example uses OpenEmuGB.toml. The filename is not important, but launcher IDs are, so use a unique ID unless you have a specific reason not to.

Open the file and add the launcher definition:

[[launchers.custom]]
id = "OpenEmuGB"
system = "Gameboy"
media_dirs = ["/Volumes/games/Gameboy"]
file_exts = [".gb"]
execute = "osascript -e 'tell application \"OpenEmu\" to open POSIX file \"[[media_path]]\"'"

The first line, [[launchers.custom]], tells Core this is a custom launcher definition. It's required. Make sure to include the double square brackets.

The id line defines the internal ID of the launcher. Generally this won't matter, but you can reference it with the ?launcher=<launcher id> advanced argument in ZapScript.

The system line specifies which system this launcher will belong to.

The media_dirs line specifies a list of directories where Core should search for media. Relative paths resolve from the Core executable directory. This definition is also used to match a file to the launcher during scans.

The file_exts line is a list of file extensions which will match to this launcher. Extensions are automatically normalized - you can write ".gb" or "gb" and both will work (a . prefix is added if missing and extensions are converted to lowercase).

The execute line is the command which will be run when a token is scanned which matches to this launcher. Expression variables are replaced by their values, then the resulting string is split into a program and arguments (respecting double and single quoted strings) and executed directly without a shell interpreter. If you need shell features like pipes or redirection, create a wrapper script and reference it here instead.

Avoid nesting another shell

Core runs your command directly, without a shell. If your execute command starts PowerShell or cmd, that shell parses your arguments again and can split quoted paths on their spaces. Run the target program directly when you can. See Windows custom launchers for a worked example.

Available expression variables

You can use the following variables in your execute command:

VariableDescription
[[media_path]]Resolved absolute path to the media file
[[platform]]Platform ID (e.g., "linux", "windows", "mister")
[[version]]Zaparoo Core version
[[device.hostname]]Device hostname
[[device.os]]Operating system ("linux", "windows", "darwin")
[[device.arch]]System architecture ("amd64", "arm64", etc.)
[[action]]Launch action from launcher defaults or ZapScript advanced args
[[install_dir]]Install directory from launcher defaults
[[server_url]]Server URL from launcher defaults
[[system_id]]System ID the launcher belongs to
[[launcher_id]]The launcher's ID

Environment variable

Custom launcher commands also receive a ZAPAROO_ENVIRONMENT environment variable containing a JSON object with the same data as the expression variables. This allows shell scripts to access the full context.

Save the file, restart Zaparoo Core, and run a media database update. Core should detect matching media for the launcher and make it launchable from scans.

Advanced options

groups

Associate the launcher with one or more groups. Groups let you set defaults for multiple launchers at once using [[launchers.default]] in config.toml.

[[launchers.custom]]
id = "MyKodiVideo"
system = "Video"
groups = ["Kodi"]
# ...
config.toml
[[launchers.default]]
launcher = "Kodi"
server_url = "http://kodi:8080"

The defaults lookup checks both the launcher ID and any groups it belongs to.

schemes

Define URL schemes the launcher handles, without the trailing ://. This allows the launcher to be matched when launching URLs with that scheme.

[[launchers.custom]]
id = "MyPlayer"
schemes = ["myplayer"]
# ...

lifecycle

Control how the launcher process is managed:

  • "blocking" (default) - Core waits for the process and tracks it
  • "background" - Fire and forget, Core doesn't track the process
[[launchers.custom]]
id = "BackgroundPlayer"
lifecycle = "background"
# ...

controls

Define control actions that can be triggered on active media via launcher controls. Values are ZapScript strings that run in a restricted control runtime. Media-launching, playlist, and nested control commands are blocked, but utility commands like input.keyboard, execute, delay, and echo are allowed.

The execute command in control scripts still requires a matching allow_execute entry.

[[launchers.custom]]
id = "RetroArchSNES"
system = "SNES"
execute = "retroarch -L snes \"[[media_path]]\""
media_dirs = ["/games/snes"]
file_exts = [".sfc", ".smc"]

[launchers.custom.controls]
save_state = "**input.keyboard:{f2}"
load_state = "**input.keyboard:{f4}"
toggle_menu = "**input.keyboard:{f1}"
toggle_pause = "**input.keyboard:{p}"

Available control actions are reported in the launcherControls field of the active media object. Scripts are validated when the launcher loads; entries with invalid ZapScript syntax are skipped with a warning in the logs.

restricted

When set to true, only files matching the allow_file patterns in config.toml can be launched. Use this for security-sensitive launchers.

[[launchers.custom]]
id = "ScriptRunner"
restricted = true
# ...

More examples

Windows emulator

[[launchers.custom]]
id = "WindowsEmulator"
system = "PS2"
media_dirs = ["C:/Games/PS2"]
file_exts = [".iso", ".chd"]
execute = "C:/Emulators/PCSX2/pcsx2.exe \"[[media_path]]\""

Platform-aware launcher

This example uses expression variables to change the command by platform:

[[launchers.custom]]
id = "PlatformAware"
system = "Multi"
media_dirs = ["games"]
file_exts = [".rom"]
execute = "/opt/launchers/[[platform]]/run.sh \"[[media_path]]\" --host [[device.hostname]]"

Default launchers

Core resolves launcher choices in this order:

  1. Explicit ZapScript ?launcher= argument
  2. Saved per-media launcher override
  3. Explicit [[systems.default]] launcher
  4. First available launchers.preference entry
  5. Normal platform launcher detection

Use a system default when every game in one system should use the same launcher. Core applies it to title/search launches and direct path launches when it can infer the system from the path. System defaults remain authoritative even when their selected launcher is unavailable, so Core reports the missing dependency instead of choosing another launcher.

API clients can save a per-media launcher override through media.meta.update. Use this when one game should always use a different launcher from the rest of its system.

Per-media overrides are stored alongside favorites in Core's user database, separate from the rebuildable media database, so they are kept even if Core has to rebuild the media database after corruption. This user data is included in Core's device backups.

Use launchers.preference when you want an ordered fallback across launcher groups or IDs. Unavailable preference entries are skipped. SteamOS supports the Native, EmuDeck, and RetroDECK groups, for example:

[launchers]
preference = ["Native", "EmuDeck", "RetroDECK"]

Use [[launchers.default]] to set launcher-specific defaults such as action, load_path, render_scale, or render_resolution.

Troubleshooting

Verifying your launcher loaded

Check the Zaparoo Core logs when it starts up. Look for messages about custom launchers, such as:

  • parsed custom launcher from TOML
  • registered custom launcher
  • loaded custom launchers

If your launcher isn't loading, check for TOML syntax or validation errors in the logs. Invalid custom entries are ignored and logged.

Testing commands

Before adding a command to your launcher config, test it manually in your terminal or command prompt. Replace [[media_path]] with an actual file path to verify it works.

Common issues

  • Paths with spaces: Quote the program path and [[media_path]] separately in your execute command, especially on Windows. If you wrap the command in another shell like PowerShell, that shell can split the path on its spaces. Launch the program directly instead when you can. See Windows custom launchers
  • Launcher selection: If several launchers match the same file, Core prefers more specific matches. Duplicate IDs or equally specific matches can be order-dependent
  • File not found: Ensure your media_dirs paths are absolute or correctly relative to the Core executable directory
  • Command not found: Verify the programs you're calling in execute are installed and in your system's PATH