romkit
Identify game ROMs and extract their data and assets as structured files.
romkit is the shared core of two consumers:
- a website where you drop in a ROM, browse what it contains, and download what you need;
- games and tools that read a user-supplied ROM for the resources they need at runtime.
What it reads
| Platform | Identify | Browse files | Extract data |
|---|---|---|---|
| Game Boy / Color | yes | Pokémon Red, Blue, Yellow (USA/Europe), Green (Japan), Gold (USA/Europe), Crystal (USA) | |
| Game Boy Advance | yes | Pokémon Ruby, Sapphire, Emerald, FireRed, LeafGreen (USA) | |
| Nintendo DS | yes | NitroFS, NARC members | Pokémon Diamond, Pearl, Platinum, HeartGold, SoulSilver, Black, White, Black 2, White 2 |
| Nintendo 3DS | yes | ExeFS, RomFS, GARC files | Pokémon X, Y, Omega Ruby, Alpha Sapphire, Sun, Moon, Ultra Sun, Ultra Moon |
The catalog recognizes every mainline Pokémon game from Red to Ultra Moon. Gen 4 games extract
species.json (stats, types, abilities, evolutions, level-up moves, TM/HM compatibility),
moves.json, abilities.json, every text bank as text/<bank>.json, and sprites as PNG:
| Asset | What |
|---|---|
sprites/pokemon/<variant>/<species>.png |
80×80 battle sprites (see below) |
sprites/icons/<species>.png |
32×32 box icons |
sprites/trainers/<class>.png |
80×80 trainer class sprites |
sprites/items/<item>.png |
32×32 item icons |
Battle sprite variants are front, front-shiny, back, and back-shiny, plus front-female,
front-shiny-female, back-female, and back-shiny-female for species whose females look
different.
Gen 5 games extract the same tables with their Gen 5 additions (hidden abilities, forms, tutors,
move effects in detail), items.json, both text archives as text/main/<bank>.json
(names, menus) and text/story/<bank>.json (dialogue), and the same sprites: 96×96 battle
sprites, 32×32 icons (with sprites/icons-female/ where females differ), 80×80 trainer sprites
(the first frame of the animated ones in Black 2 and White 2), and 32×32 item icons. Alternate
forms with sprites of their own are named after species and form number, e.g.
sprites/pokemon/front/0386-1.png for Deoxys's Attack Forme.
Gen 3 games (the first USA release of each) extract species.json (with egg moves, and both the
National Pokédex number and the games' internal index), moves.json, abilities.json,
items.json, 64×64 battle sprites (front, front-shiny, back, back-shiny), 32×32 box
icons, 64×64 trainer sprites, 24×24 item icons (Emerald, FireRed, LeafGreen), and cries as
sound/cries/<species>.wav, all keyed by National Pokédex number. GBA games have no filesystem,
so each dump's Edition records where its tables are.
Gen 1 and Gen 2 games extract species.json (stats, types, evolutions, level-up moves, TM/HM
compatibility; Gen 1 adds the internal index and level-1 moves, Gen 2 egg moves and held items),
moves.json (Gen 2 with descriptions), items.json (Gen 2 with descriptions and attributes),
and Pokémon sprites colored with the palettes in the ROM: Gen 1 front (56×56) and back
(32×32) in their Super Game Boy colors (Yellow: Game Boy Color), Gen 2 front and
front-shiny (56×56), back and back-shiny (48×48). Names keep the games' upper case;
Japanese Green decodes to kana. Releases are told apart by their header checksum, so other
languages and revisions (and Silver, without a dump to verify) are identified but not extracted.
The 3DS games (decrypted dumps) extract species.json (with alternate forms, hidden abilities,
tutors, Z-Crystals), moves.json (battle data in detail, Z-Moves), abilities.json, and every
text bank of every language as text/<language>/main/<bank>.json and
text/<language>/story/<bank>.json; the banks romkit reads are named, e.g.
text/en/main/species_names.json. Omega Ruby, Alpha Sapphire, and Generation VII also extract
their music and jingles as sound/<name>.wav, decoded from BCSTM streams (X and Y use AAC, which
romkit does not decode). Languages are ja-kana, ja-kanji, en, fr, it, de,
es, ko, and in Generation VII zh-hans and zh-hant, whose characters stay in the games'
private-use encoding.
For any DS game, formats::g2d decodes the Nitro 2D graphics formats (NCLR palettes, NCGR
tiles, NCER cells, NSCR screens) to images, and formats::lz decompresses LZ77 data.
Gen 4 games also extract the overworld in 3D as glTF with embedded textures: map terrain
(models/maps/<n>.glb), buildings (models/buildings/<n>.glb), and in HeartGold and
SoulSilver indoor furniture (models/furniture/<n>.glb); and as PNG the texture sets of maps and
buildings and the overworld character sprites (textures/{maps,buildings,overworld}/<n>/<name>.png).
Any DS game's 3D models (NSBMD) and textures (NSBTX) convert through formats::nitro3d.
Gen 4 and Gen 5 games also extract their sound archive (SDAT): music and sound effects as MIDI
(sound/sequences/SEQ_TOWN01_D.mid), samples as WAV (sound/waves/<archive>/<index>.wav),
streams (sound/streams/<name>.wav), and every Pokémon's cry by National Pokédex number
(sound/cries/0025.wav, plus forms such as 0492-sky.wav). The SDAT reader and its asset tree
work for any DS game.
Layout
crates/
romkit/ the library
src/
io/ ByteSource, Window, FileSource; Reader, the bounds-checked cursor
platforms/ identify() and one module per handheld; nds/ has NitroFS, the banner,
and the ARM9 binary; n3ds/ has the ExeFS and the RomFS
formats/ file formats inside ROMs: signature sniffing, NARC and GARC archives,
LZ77, BCSTM audio, g2d/ and nitro3d/ (the Nitro 2D and 3D formats),
sdat/ (DS sound archives)
games/
catalog.rs GameId: every recognized game and its header signature
mod.rs the Game trait, and open() to get a game's extractor
sound.rs any DS sound archive as assets
pokemon/gen1/ the Gen1 handle, the Edition table (bank:address of each table),
species, moves, items, text charmaps, pic decompression, sprites
pokemon/gen2/ the same for Gold and Crystal, plus LZ decompression
pokemon/gen3/ the Gen3 handle, the Edition table (table offsets), species, moves,
items, text, sprites, cries, assets
pokemon/gen4/ the Gen4 handle, the Edition table, species, moves, text, sprites, sound,
3D field, assets
pokemon/gen5/ the same for Black, White, Black 2, and White 2, plus items
pokemon/n3ds/ the N3ds handle and Edition table for the eight 3DS games
rom.rs Rom: any ROM, for hosts that take whatever they are given
tests/roms.rs checks against real dumps
romkit-cli/ the romkit command-line tool
romkit-wasm/ the wasm build, romkit.mjs (a minimal JS host), and site/, the website
The root Cargo.toml holds what the crates share: versions, dependency versions, and the build
profiles.
Each layer knows nothing of the ones above it: platforms do not know games, formats do not know platforms.
Opinions
- Read lazily. Everything reads through
ByteSource, so opening a 128 MB DS ROM reads about 25 KB and a 4 GB 3DS image about 50 KB, a browser reads a dropped file in place, and in-memory ROMs are borrowed, never copied. - Typed first, files second. Each game has a typed handle (
Gen4::species()) for programs. TheGametrait is a uniform view over the same data as asset files (JSON for data), which is what the website and the exporter use. - Lose nothing. Values without a known meaning are kept (
Unknown(raw)enum variants, unknown text codes), and text has a lossless markup form. - Untrusted input. ROMs come from users: malformed data is an error naming the format and the offset, never a panic or an endless loop.
Use
In a program that needs one game's data:
use Gen4;
use FileSource;
let game = open?;
let pikachu = &game.species?;
println!;
For any ROM, use Rom:
let rom = open?;
println!;
for file in rom.files?
if let Some = rom.game
rom.export?;
From the command line, after cargo install romkit-cli (or with a binary from the GitHub
releases):
From a checkout, cargo run --release -p romkit-cli -- stands in for romkit.
In a browser, build the wasm module and load it in a Web Worker with
crates/romkit-wasm/romkit.mjs:
import from "./romkit.mjs";
const reader = ;
const romkit = await ;
const overview = romkit.;
const species = JSON.;
The website in crates/romkit-wasm/site is static files around that worker. To try it locally,
put the module and the host beside it and serve the folder:
Adding a game
- Recognize it: add a row to the catalog in
crates/romkit/src/games/catalog.rs. - Same engine as a supported game: add its
Edition(file paths and text banks) in that family's module, e.g.crates/romkit/src/games/pokemon/gen4/mod.rs. Locate text banks by their content in a real dump. - New engine: add a module under
crates/romkit/src/games/with a typed handle that parses tables on first use and implementsGame, then add a line togames::openandGameId::is_supported. - Check it against a real dump in
crates/romkit/tests/roms.rswith facts you know to be true.
Test
ROMKIT_ROMS=/roms
ROM dumps are never committed; without ROMKIT_ROMS the real-dump tests pass vacuously.
Release
CI (.github/workflows/ci.yml) checks formatting, lints, tests, and builds the CLI and the wasm
module on every push to main and every pull request.
To release, bump version in the root Cargo.toml (both [workspace.package] and the romkit
entry of [workspace.dependencies]), commit, and push a matching tag:
&&
release.ymlbuilds the CLI for Linux (static musl), macOS, and Windows, on x86-64 and ARM64, plus the wasm module withromkit.mjs; publishesromkitandromkit-clito crates.io with theCARGO_REGISTRY_TOKENsecret of thecrates-ioenvironment; then publishes the GitHub release withSHA256SUMS.pages.ymlbuilds the wasm module and deploys the website to GitHub Pages on every push tomain. It can also be run by hand from the Actions tab.