Files
punktfunk-plugin-rom-manager/README.md
T
enricobuehler 3a6c80558d
CI / build (push) Has been cancelled
CI / publish (push) Has been cancelled
feat: initial ROM & emulator manager plugin (M0–M5)
A punktfunk-plugin-* package that scans ROM directories, maps them to
emulators, fetches box art, and reconciles them into the host game library
under provider id `rom-manager` — with a console-hosted web UI.

Engine (pure, unit-tested core):
- Table-driven platform registry (~25 consoles) + emulator registry with
  best-effort per-OS detection (PATH / Flatpak / known paths) and RetroArch
  core discovery.
- Scanner with disc folding (m3u/cue/gdi), archive gating, excludes.
- No-Intro title parsing + optional per-platform region dedupe.
- Security-critical quoting seam: POSIX single-quote + Windows double-quote
  with hostile-name refusal; ROM filenames never reach a shell un-quoted.
- Pure desired-state reconcile (stable external_ids, scale guard, fingerprint
  skip) → full-replace PUT /library/provider/rom-manager.

Box art (like Steam ROM Manager): SteamGridDB primary (portrait/hero/logo/
header, fuzzy match, operator API key) behind a provider seam, with keyless
libretro-thumbnails as the zero-setup fallback (`auto` default).

UI: console-hosted via the SDK `servePluginUi` (zero plugin-side auth) with a
plugin-local REST/SSE API and a self-contained React SPA (Setup / Emulators /
Games / Sync). Standalone password-gated fallback for host-only installs.

CLI: scan / detect / preview / sync / uninstall / set-password.

48 engine tests, typecheck + biome clean, SPA builds. Verified end-to-end:
scan → detect → reconcile PUT, fingerprint idempotence, and the standalone
UI serving SPA + REST.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 10:09:18 +02:00

134 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# punktfunk-plugin-rom-manager
A [punktfunk](https://git.unom.io/unom/punktfunk) plugin that scans your ROM folders, maps them to
emulators, fetches box art, and reconciles everything into the host game library — so your retro games
appear automatically in the punktfunk web console, native clients, and Moonlight `/applist`, each
launchable and covered in art. It ships a web UI that lives **inside the punktfunk console** (no second
password, no second port).
Think of it as [Steam ROM Manager](https://github.com/SteamGridDB/steam-rom-manager) for punktfunk: it
even uses the same art source (SteamGridDB) when you give it an API key.
---
## Requirements
- A punktfunk host (Linux or Windows) with the **scripting runner** (`punktfunk-scripting`) enabled.
- [Bun](https://bun.sh) (the runner is Bun).
- `@punktfunk/host` **with the plugin-UI surface** (`servePluginUi`). This is what powers the
console-hosted UI; the standalone fallback works without it.
## Install
```sh
cd "$(punktfunk config-dir)/plugins" # e.g. ~/.config/punktfunk/plugins
bun add punktfunk-plugin-rom-manager --registry https://git.unom.io/api/packages/unom/npm/
# then enable the runner (opt-in):
systemctl --user enable --now punktfunk-scripting # Linux
Enable-ScheduledTask PunktfunkScripting # Windows
```
Open the punktfunk console — a **ROM Manager** entry appears in the nav. That's it.
> **Headless / host-only boxes** without the console can use the [standalone UI](#standalone-ui) or
> just drop a `config.json` (below) — the sync engine is fully functional from the file alone.
## Quick start (config file)
The plugin owns `<config_dir>/rom-manager/config.json`. A minimal example:
```jsonc
{
"roots": [
{ "dir": "/mnt/roms/snes", "platform": "snes" },
{ "dir": "/mnt/roms/ps1", "platform": "ps1", "excludes": ["*.sav", "bios/**"] }
],
"art": { "provider": "auto", "steamGridDbKey": "" }
}
```
See [`config.example.json`](./config.example.json) for every option. Then, from the plugin dir:
```sh
bunx punktfunk-plugin-rom-manager scan # what the scanner finds
bunx punktfunk-plugin-rom-manager detect # which emulators are installed
bunx punktfunk-plugin-rom-manager preview # the desired library (no host write)
bunx punktfunk-plugin-rom-manager sync # reconcile into the live library
```
## Box art
Two sources, selected by `art.provider`:
| Provider | Key? | Art types | Notes |
|---------------|-------|------------------------------------|-------|
| `steamgriddb` | yes | portrait + hero + logo + header | What Steam ROM Manager uses; best coverage + fuzzy title match. Free key from [steamgriddb.com profile preferences](https://www.steamgriddb.com/profile/preferences). |
| `libretro` | no | box-art portrait only | Keyless [libretro-thumbnails](https://thumbnails.libretro.com); works out of the box. |
| `auto` (default) | — | SteamGridDB if a key is set, else libretro | |
Art URLs are handed to the host as-is; native clients/console fetch them directly (so **clients need
internet for covers**), and Moonlight covers are proxied host-side. Verdicts are cached in
`cache.json` and never re-probed unless you change provider/key.
## Platforms & emulators
~25 built-in platforms (NES, SNES, N64, GameCube, Wii, Switch, GB/GBC/GBA, NDS, 3DS, PS1/2/PSP,
Genesis, Master System, Game Gear, Saturn, Dreamcast, PC Engine, Neo Geo Pocket, WonderSwan, Lynx,
Atari 2600/7800, Xbox). Arcade (MAME/FBNeo romset versioning) is intentionally out of scope.
Emulators are detected best-effort (PATH → Flatpak → known install paths): RetroArch (+ cores),
Dolphin, PCSX2, DuckStation, PPSSPP, mGBA, melonDS, Flycast, xemu, plus Azahar/Ryujinx. Any platform's
emulator, core, and args are overridable per-platform and per-game (config or UI). Missing an
emulator? Add a `custom-*` template.
Disc games are folded so a set is **one entry**: `.m3u` playlists absorb their discs, `.cue`/`.gdi`
sheets hide their tracks, `.chd` stands alone.
## The UI
### Console-hosted (default)
`servePluginUi` serves the SPA on a loopback ephemeral port behind a per-boot secret and registers it
with the host; the console reverse-proxies it and gates it with your existing console sign-in. Four
pages: **Setup** (ROM roots), **Emulators** (detected + per-platform overrides), **Games** (scan
preview with covers + per-title include toggles), **Sync** (status, art settings, sync options).
### Standalone UI
For host-only installs, set `ui.standalone: true` (config). It serves the same SPA on a fixed port:
```jsonc
"ui": { "standalone": true, "port": 47993, "bind": "127.0.0.1" }
```
A **non-loopback bind requires a password** (fail-closed — the UI can edit launch commands, i.e.
host-user code):
```sh
bunx punktfunk-plugin-rom-manager set-password 'your-password'
```
## Security
- **ROM filenames are untrusted input** that ends up in a `sh -c` / `cmd.exe /c` string run as the host
user. Every path goes through strict per-OS quoting (`src/quote.ts`); on Windows, names containing
`cmd.exe` metacharacters (`" % ! ^ & | < >`) are refused rather than risk injection.
- `config.json` lives in the hardened `<config_dir>`; the plugin refuses a group/world-writable one.
- Outbound network in v1: SteamGridDB (if keyed) and libretro-thumbnails only. No telemetry.
## Development
```sh
bun install # resolves @punktfunk/host from the Gitea npm registry
bun test # engine unit tests (pure core: quoting, scanner, reconcile, art)
bun run typecheck
bun run build:all # backend → dist/, SPA → dist/ui/
cd ui && bun run dev # SPA dev server (point it at a running standalone instance)
```
This plugin requires `@punktfunk/host` ≥ 0.1.0 (the version that introduced `servePluginUi`).
## License
MIT OR Apache-2.0.