-
Notifications
You must be signed in to change notification settings - Fork 21
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.
Retro games are off until you enable them in the Moonbase admin panel. Every game endpoint returns nothing until the feature is on.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
| 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 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 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.
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.
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:
- A custom URL you set in the admin panel
- Self-hosted files installed on your server
- 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.
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.
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.settingsfor the web player, andmoonfin-native-<core>.settingsfor 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 (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 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.
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.