@wasm-gaming/rom-manager
v0.1.8
Published
Embeddable ROM metadata editor - like mp3tag for video game ROMs
Readme
ROM Manager
A client-side tool (no backend) for managing a retro ROM collection: it groups files by game with their different variants (region, revision, language), and attaches metadata and cover art to them.
Files are accessed through the File System Access API, so nothing is ever uploaded anywhere. Real support only exists in Chromium browsers.
Project Specs
Goal
- The heart of the project is the dataset: a catalog of games with their variants, generated by hash matching against DATs (No-Intro / Redump).
- Target: retro games. The catalog is practically static — retro-system DATs are already mature — so no architecture for full dataset regeneration is needed; it can grow incrementally.
- The manager identifies ROMs by hash, never by file name. A local name is not trustworthy; a CRC32 checked against the DAT is.
How it works
- 24 mainstream MiSTer systems, identified by the exact folder name MiSTer
uses under
/games. Each declares a fixed medium: cartridge or disc. Neo Geo is the one whose games also arrive as a romset — a folder or zip of chip dumps, identified by the checksums of its members rather than by one of its own. See Target systems. - Grouping by base title. Every DAT entry is a candidate variant; the region,
language, revision and flag tags in its name produce a variant key, and
normalizeGameName()produces the group key that names the folder and resolves the cover. See Grouping. - Folder structure depends on the medium: cartridge systems are flat
(
<system>/<Game>.<variant>.<ext>), disc systems get a folder per game and a subfolder per variant. Metadata always lives outside the ROM folders, in.meta/. See Folder structure and canonical organization. - One cover per region (EU, US, JP), sourced from
libretro-thumbnailsand cached locally as you browse. A preference order decides which one is shown. See Cover art. - Curated collections are off limits. The manager only scans and organizes the base folder of each system, never the contents of a collection folder. See Collections.
- The explorer has two modes: flat (the raw filesystem listing) and wizard (recognized files folded into their game, one row per game). See File explorer.
Folder structure at a glance
Nintendo - SNES/Super Mario World.USA.sfc
Nintendo - SNES/Super Mario World.Japan-rev1.sfc
Sony - PlayStation/Final Fantasy VII/game.json
Sony - PlayStation/Final Fantasy VII/USA/Final Fantasy VII (USA) (Disc 1).bin
Sony - PlayStation/Final Fantasy VII/USA/Final Fantasy VII (USA) (Disc 1).cue
.meta/<system>/<Game>.json # editable metadata
.meta/<system>/<Game>.<region>.case.png # cover for one region
.meta/<system>/scan.json # hash cache
.meta/wizard.json # per-folder settingsDesign principles to preserve
- The canonical dataset is the source of truth for grouping; it is queried while browsing, never rewritten on the fly.
- The manager never touches anything inside a collection folder.
- Every per-folder behavior decision (wizard on/off) is explicit and persisted
(
wizard.json), never inferred by heuristic. - Anything that moves the user's files is split in two: a plan, which touches nothing, and its application, which never happens without the plan having been shown first.
Documentation
| Document | Contents |
|---|---|
| docs/systems.md | The closed list of supported systems, their medium and their DAT, and how Neo Geo romsets are identified by their members. |
| docs/grouping.md | Game ↔ variant grouping, variant identity, regions and video standards, name normalization. |
| docs/folder-structure.md | On-disk layout per medium, the .meta/ layer, and the canonical organization operation with its undo log. |
| docs/covers.md | One cover per region: how they are matched, chosen, downloaded and stored, plus hand-added images. |
| docs/collections.md | What a collection is and why the manager stays out of it. |
| docs/explorer.md | Flat mode vs wizard mode, one row per game, region preference order, drag and drop. |
Sessions and specifications live in SESSIONS/; see AGENTS.md for the working conventions.
Development
Requires Node >= 24.
npm install
npm run dev # dev server
npm run build # app build
npm run build:lib # embeddable library build
npm run test # vitest
npm run type-check # tsc --noEmitDataset generation (network access required):
npm run dataset:fetch-dat # download the DATs from libretro-database
npm run dataset:fetch-covers # list the libretro-thumbnails repositories
npm run dataset:to-json # build static/datasets/ from what is downloadedEvery dataset is regenerable from those three commands alone; nothing in
static/datasets/ is hand-made. A generated dataset can then be checked against
a real collection of ROMs, which reads no file name and decompresses nothing:
npm run dataset:verify-romsets -- <folder> --system=NEOGEOTodos
- Editable per-game metadata: today the
.jsonrecord is indexed by the file path, so in the canonical cartridge structure it ends up being per variant (<Game>.<variant>.json) instead of per game, which is what the design calls for. - Neo Geo romsets in the browser: the dataset already carries the members' checksums, but the app does not read them yet — identifying a zip as a set, taking one in whole instead of unpacking it, and naming a Neo Geo file after its romset are still to be written. See docs/systems.md.
