Skip to content

Retro Games

Axl Nunez edited this page Jul 23, 2026 · 4 revisions

Retro Games

Moonbase can turn a folder of ROMs into a browsable game library that plays right in the browser, using a bundled EmulatorJS runtime. Games play with zero setup by default, because the emulator files load from a public CDN. Self-hosting those files is optional. The Moonfin apps on iOS, Android, and desktop play the same libraries with native emulator cores instead, covered under Platform support.

Because ROM files are not a media type Jellyfin or Emby understand, the plugin does not index games in the normal library database. Instead it reads your game folders directly off disk each time.

Turning it on

Retro games are off until you enable them in the Moonbase admin panel. Every game endpoint returns nothing until the feature is on.

Folder layout

Pick your game libraries in the Moonbase admin panel. If you have not picked any, the plugin falls back to auto-detecting libraries whose name contains "game", "rom", or "emulator". Inside a game library, use one folder per system, and one folder per game:

Games/
  SNES/                 one folder per system
    bios files here     loose BIOS files for that system
    Super Mario World/  one folder per game
      Super Mario World.sfc
    Chrono Trigger.sfc  loose ROMs also work
  Genesis/
    Sonic.md

Each top-level folder under the library is treated as a system. A game can be a folder containing a ROM, or a loose ROM file sitting directly in the system folder. The game title comes from the game folder name, or the file name for loose ROMs.

Naming your system folders

The system folder name is what tells the plugin which console a game is for, and getting it right is what lets the game databases find the correct box art and details. Name each folder using any of the accepted names below.

Names are matched loosely: capital letters, spaces, and dashes are ignored. So Super Nintendo, super-nintendo, and SuperNintendo all work the same.

Console Name the folder any of these
NES / Famicom NES, Famicom
Super Nintendo SNES, Super Nintendo, Super Famicom
Game Boy / Game Boy Color GB, Game Boy, GBC, Game Boy Color
Game Boy Advance GBA, Game Boy Advance
Sega Genesis / Mega Drive Genesis, Mega Drive, Sega Genesis
Sega Master System Master System, SMS
Sega Game Gear Game Gear, GG
Nintendo 64 N64, Nintendo 64
Nintendo DS NDS, Nintendo DS
Virtual Boy Virtual Boy
Atari 2600 Atari 2600
Atari 7800 Atari 7800
Atari Lynx Lynx
WonderSwan WonderSwan
Neo Geo Pocket Neo Geo Pocket
PC Engine / TurboGrafx-16 PC Engine, TurboGrafx 16
PlayStation PSX, PS1, PSOne, PlayStation
PSP PSP, PlayStation Portable

If a folder name is not recognized, the plugin falls back to guessing the console from the ROM file extension, and if that also fails it assumes NES. Naming the folder from the list above avoids the guessing.

Supported systems

Systems are matched by the system folder name first, then by the ROM file extension.

System Apple TV Moonfin-Core webOS / Tizen
NES / Famicom Yes Yes Yes
SNES / Super Famicom Yes Yes Yes
Game Boy / Game Boy Color Yes Yes Yes
Game Boy Advance Yes Yes Yes
Sega Genesis / Mega Drive Yes Yes Yes
Sega Master System Yes Yes Yes
Sega Game Gear Yes Yes Yes
PlayStation Yes Yes Yes
Nintendo 64 No Yes Yes
Nintendo DS No Yes Yes
PSP No Yes No
Virtual Boy No Yes Yes
Atari 2600 No Yes Yes
Atari 7800 No Yes Yes
Atari Lynx No Yes Yes
WonderSwan No Yes Yes
Neo Geo Pocket No Yes Yes
PC Engine / TurboGrafx-16 No Yes Yes

The Moonfin-Core column covers the web, Android, desktop, and Android TV clients, which all play the same set. iOS plays the same systems except PSP, using bundled native cores for the common ones and the web player for the rest (see Platform support). webOS / Tizen needs a supported TV version, covered below.

Platform support

How a game plays depends on the client platform. The web and Smart-TV clients run games in a WebView through EmulatorJS and can play whatever the server serves. Android and desktop clients play through native emulator cores by default. iOS and Apple TV bundle their cores into the app, and iOS falls back to the web player for anything it does not bundle.

Native emulation on Android and desktop

Moonfin-Core plays games with native libretro cores on Android, Windows, macOS, and Linux. Native is the default because it runs far faster than a WebView, especially on Android TV. On Android, Windows, and macOS you can switch back to the EmulatorJS web player with the Native Emulation toggle in Settings > Playback. Linux has no WebView, so it always plays native.

Native cores are downloaded in the app, not bundled. Pick the systems you play in Settings > Playback > Emulator Cores on Android, Windows, and Linux. macOS ships every core inside the app, so there is nothing to download there.

Saves are kept separately per emulator. If you flip the toggle, each side keeps its own save states.

iOS

The App Store does not allow downloading emulator cores at runtime, so iOS bundles a fixed set into the app: NES, SNES, Game Boy and Game Boy Color, Game Boy Advance, Sega Genesis, Master System, Game Gear, and PlayStation. Those play through the native player, the same as the other apps.

Any other system falls back to the EmulatorJS web player automatically, one game at a time, so the rest of the library still plays. The exception is PSP, which plays nowhere on iOS: the native player cannot run it without a JIT, and the web player cannot give it the threading it needs. Turning off Native Emulation in Settings > Playback sends every game to the web player instead.

iOS bundles its cores like macOS and Apple TV, so it has no core download screen.

Apple TV

Apple TV plays only the eight systems marked Yes in its column. It has no WebView (well it DOES but Apple does not allow any third party apps to use, sory), so it plays games through a native bridge with the emulator cores built into the app, and App Store rules don't allow downloading cores at runtime. The set that ships with the app is the set you get. Picking a game from any other system shows a "not supported" message instead of playing.

Smart-TV (webOS and Tizen)

webOS and Tizen CAN play games, but only on webOS 5 or newer and Tizen 5 or newer. Emulator cores are WebAssembly, and the browsers on older TVs (webOS 4 and below, Tizen 4 and below) have no WebAssembly at all, so games cant run there.

Smart TVs also run the emulator without threading, so PSP will not play there even on a supported TV version. Every other system in the table works.

Not supported anywhere

Multi-file disc sets (.cue with .bin, or .m3u multi-disc) don't work, and neither do heavier systems like Saturn, Sega CD, 32X, and arcade/MAME. Disc systems work with single-file images only (for example .chd or .pbp for PlayStation, .cso or .iso for PSP). PlayStation needs a BIOS placed in its system folder.

Controls

These controls apply to the native player on iOS, Android, and desktop. The EmulatorJS web player has its own on-screen controls and key bindings.

In-game menu

The menu pauses the game and offers save state, load state, press Start or Select, restart, fast forward, emulator settings, and exit. Exiting through the menu saves your state first, and the back button or key opens the menu instead of quitting the game, so progress is not lost by accident.

Open it with:

Input Gesture
Controller Hold Start for about 1.5 seconds. A quick press acts as a normal Start. On Xbox pads Start is the menu button, on PlayStation pads it is Options.
Keyboard Esc or Backspace
Touch The menu icon in the top right
Mouse The menu button in the top right
TV remote The Back button
Apple TV The Siri remote Back button

In the menu, the d-pad moves, the bottom face button confirms, and the right face button goes back. On touch and mouse, tap or click the rows directly. Opening Emulator settings and choosing an option brings up its list of values to pick from, and a back arrow returns to the previous menu. On phones and tablets a game plays full screen in landscape.

Keyboard mapping (desktop)

Key Button
Arrow keys D-pad
Z B (bottom face)
X A (right face)
A Y (left face)
S X (top face)
Q / W L / R shoulders
Enter Start
Right Shift Select
Esc / Backspace Open or close the menu

These are RetroPad positions, so what each key does depends on the system. On NES, Z is B and X is A. Game Boy uses Z and X only. SNES uses all four face buttons.

Controllers

Controllers work in-game on every platform with the native player. Face buttons map by position rather than by label, so the bottom face button is always B on Nintendo-style layouts.

ROMs and archives

ROMs stream to the player with range support, so large files can seek and resume. Compressed ROMs are extracted on the server on the fly, so the original archive is left untouched. Only .zip and .7z archives are supported, and only single-file archives (one ROM inside). The .7z support uses the bundled SharpCompress library that ships next to the plugin.

BIOS files

A BIOS is any non-ROM file at the top of a system folder, with an extension like .bin, .bios, .rom, .img, .sys, or .bs. When a game needs a BIOS, the plugin lists it on the game and streams it to the player alongside the ROM.

Emulator cores

This section covers the web player's cores, used wherever games play through EmulatorJS. The native cores the Android and desktop clients use are separate and are managed in the app under Settings > Playback > Emulator Cores.

"Cores" are the EmulatorJS runtime and the per-system emulator files. The player looks for them in this order:

  1. A custom URL you set in the admin panel
  2. Self-hosted files installed on your server
  3. The public EmulatorJS CDN (the default, so games work out of the box)

Self-hosting is worth it if you want games to work without reaching out to the CDN. You can install the cores two ways from the admin panel:

  • Install downloads the cores bundle in the background (it is large, around 290 MB). The download starts and the panel shows progress.
  • Upload installs the cores from a zip you provide.

The cores status shows whether they are installed or still downloading.

Saved games

Save states sync per user. When you save in the player, or exit through the in-game menu, the blob uploads to the server keyed to your account and the specific game, so your progress follows you to another device. Saves are capped at 32 MB each.

Where saves are stored

Saves live on the server, inside the Moonfin plugin's own data folder:

<plugin data folder>/
  saves/
    <user id>/
      <game id>.state       save state
      <game id>.settings    emulator settings

The plugin data folder is the Moonfin folder under your server's plugins directory, so on a typical setup it sits alongside your server config (for Docker, inside the volume you mapped to config). This is the same for Jellyfin and Emby.

A few things worth knowing about the file names:

  • The user id is the account's id with the dashes removed, so each account gets its own folder and nobody can read anyone else's saves.
  • The game id is the ROM's full path encoded as base64url. That means moving or renaming a ROM orphans its saves, because the new path encodes to a different id. Settle on a folder layout before people start playing.
  • Native cores and the EmulatorJS web player write separate save files. Native states carry an lr- prefix on the game id, so flipping the Native Emulation toggle hides the other side's saves rather than loading something the emulator cant read. The two formats are not interchangeable.
  • Emulator settings are stored per emulator rather than per game: moonfin-global.settings for the web player, and moonfin-native-<core>.settings for each native core.

To back up saves, copy the saves folder. To move a user's progress to another account, copy their folder and rename it to the other account's id.

Battery saves

Battery saves (the in-game saves cartridges keep, .srm files) are not synced. The native player writes them next to its working files on the device, and the web player keeps them in browser storage. Only save states travel between devices, so use Save state in the in-game menu, or exit through that menu, when you want progress to follow you.

On mobile and desktop the native player keeps its ROM cache and battery saves in the app's cache directory, which the operating system is allowed to clear when storage runs low. Treat battery saves as device-local and disposable.

Game details

Game info (overview, genre, developer, publisher, year, players, region) is filled in automatically from keyless public databases (the LaunchBox Games Database and libretro's databases). Box art and screenshots come from the libretro thumbnail library, downloaded and cached on the server the first time a game is shown. This all happens entirely on the server, and the client never handles the ROM file itself. Also there is no way to update the metadata, the best way to make sure it matches is to ensure the file name is something that the databases recognize.

Troubleshooting

If a library is not detected, the admin-only GET /Moonfin/Games/Debug endpoint dumps what the plugin sees: whether games are enabled, which library ids are configured, every library folder and whether it exists on disk, and the libraries it resolved as game libraries.

See the API Reference for the full endpoint list.