CLI usage
Install the rom-weaver CLI when you want repeatable patching, batch jobs, CI, container work, checksums, trimming, or JSON automation. It uses the same Rust command core as the browser app, but its workflow is designed for terminals and scripts.
Want visible cards and guided tours instead? Start with the browser documentation. This page keeps the terminal workflow, install methods, and flags together.
Install
Prebuilt install
Every method here installs a binary built for the release: macOS arm64 and x86-64; Linux x86-64 GNU plus x86-64, arm64, and i686 musl; and Windows arm64, x86-64, and x86.
Homebrew (macOS arm64/Intel, Linux arm64/x86-64)
brew install rom-weaver/tap/rom-weaver
Scoop (Windows)
scoop bucket add rom-weaver https://github.com/rom-weaver/scoop-bucket
scoop install rom-weaver
Install script (macOS, Linux)
Downloads the latest release to ~/.local/bin and checks its build provenance,
refusing to install a binary this repository did not publish. Set
ROM_WEAVER_INSTALL_DIR to choose another directory, or ROM_WEAVER_VERSION to
install a specific release. See Verifying a download
for what that check does and does not prove.
curl --proto '=https' --tlsv1.2 -LsSf \
https://raw.githubusercontent.com/rom-weaver/rom-weaver/main/install.sh | sh
Install script (Windows)
The PowerShell equivalent, installing to %LOCALAPPDATA%\rom-weaver\bin. It
honors the same environment variables and runs the same checks.
irm https://raw.githubusercontent.com/rom-weaver/rom-weaver/main/install.ps1 | iex
Verifying a download
Every published artifact carries signed build provenance: the platform binaries and the webapp tarball on each release, the npm packages, and the container images. It records which workflow, run, and commit produced the file, so an asset uploaded by anything other than the release workflow - a stolen token, a maintainer's laptop - has none.
Verifying a file you downloaded by hand
Hash the file and ask GitHub whether this repository's release workflow built exactly those bytes. Nothing needs installing - this is the same check both install scripts run:
file=rom-weaver-linux-x64-gnu
# sha256sum on Linux; macOS ships shasum instead. Probed rather than tried and
# fallen back from: `$(missing | cut)` exits 0, so a fallback keyed on the exit
# status never runs and the digest silently comes out empty.
if command -v sha256sum >/dev/null 2>&1; then
digest=$(sha256sum "$file" | cut -d ' ' -f 1)
else
digest=$(shasum -a 256 "$file" | cut -d ' ' -f 1)
fi
if curl -fsS "https://api.github.com/repos/rom-weaver/rom-weaver/attestations/sha256:$digest?predicate_type=https://slsa.dev/provenance/v1" \
| grep -q '"repository_id"'
then
echo "VERIFIED: built by the rom-weaver release workflow"
else
echo "NOT VERIFIED: no build provenance covers this file" >&2
fi
The PowerShell equivalent:
$file = 'rom-weaver-win32-x64-msvc.exe'
$digest = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower()
$uri = "https://api.github.com/repos/rom-weaver/rom-weaver/attestations/sha256:${digest}" +
'?predicate_type=https://slsa.dev/provenance/v1'
# A repository with no attestations at all answers 404, which Invoke-RestMethod
# raises rather than returns - uncaught, it ends the script before the
# not-verified message it was written for. Unlike the install scripts, this does
# not tell that apart from an unreachable API; both report NOT VERIFIED here.
# The property is checked before it is read because `@($null).Count` is 1, so
# reading a missing one blind would count an unrelated 200 as verified.
$count = 0
try {
$response = Invoke-RestMethod -Uri $uri
if ($response.PSObject.Properties['attestations']) {
$count = @($response.attestations).Count
}
} catch { }
if ($count -gt 0) {
Write-Host 'VERIFIED: built by the rom-weaver release workflow'
} else {
Write-Error 'NOT VERIFIED: no build provenance covers this file'
}
predicate_type is not optional. Immutable releases make GitHub attest
every release automatically, and that attestation lists the digest of every
asset in it - so an unfiltered query returns a hit for any file in any release,
and the check passes on files nothing built. That automatic attestation only
says "this was in release X", which is also true of an asset a stolen token
uploaded to the draft. Filtering to SLSA provenance is what makes a hit mean
"the release workflow produced this".
Because it is the release workflow's own attestation, it only exists for releases cut after this was added. Verifying an asset from an earlier release correctly reports NOT VERIFIED - there is no build provenance to find.
Checking the signature
The queries above trust GitHub's API response over TLS, which is the same trust
the download itself already places in GitHub. They do not check the Sigstore
signature. For that - signature, certificate chain, and transparency-log
inclusion - use gh, which must be signed in even for a public repository:
gh attestation verify rom-weaver-linux-x64-gnu --repo rom-weaver/rom-weaver
gh attestation verify oci://ghcr.io/rom-weaver/rom-weaver-cli:latest \
--repo rom-weaver/rom-weaver
npm packages carry their own provenance, verified with:
npm audit signatures
The install scripts deliberately do not reach for gh or cosign. Requiring a
tool most machines lack, in order to install a tool, is not a trade worth making
in a curl | sh - and gh refuses to run unauthenticated, so a fresh machine
could not use it anyway.
What the install scripts do
They run the query above against the file they just downloaded, which is also
why no .sha256 sidecar is published any more: altered bytes hash to something
no attestation covers, so the check refuses them. A sidecar could not have added
anything, having shipped from the same place as the binary.
A definite negative stops the install; an unanswered question does not:
| Outcome | Behavior |
|---|---|
| This repository attested these bytes | installs |
| Nothing attested them - empty response or HTTP 404 | refuses |
| The check could not run - offline, rate-limited, 5xx | warns, installs |
The unauthenticated API allows 60 requests an hour per address, so the last row is reachable by ordinary use rather than only by attack, which is why it does not block. Every refusal prints the way past it:
curl --proto '=https' --tlsv1.2 -LsSf \
https://raw.githubusercontent.com/rom-weaver/rom-weaver/main/install.sh |
ROM_WEAVER_SKIP_ATTESTATION=1 sh
The assignment belongs on sh, not on curl: putting it at the front of the
pipeline sets it for the download and not for the script that reads the
variable, so the install refuses again.
Going the other way, ROM_WEAVER_REQUIRE_ATTESTATION=1 promotes that warning to
a refusal too, so an install that could not be verified fails rather than
proceeding.
npm
The only channel covering every supported target at once. Needs Node.js 22+.
The unscoped rom-weaver package points at the @rom-weaver/cli launcher,
whose binary arrives through a platform-specific optional dependency, so only
your platform's binary is downloaded.
npm install --global rom-weaver
Use the scoped launcher directly for a one-off run, or as a dev dependency for a repository's scripts:
npx @rom-weaver/cli probe --input game.iso
npm install --save-dev @rom-weaver/cli
cargo-binstall
Fetches the released binary instead of compiling the workspace, which
cargo install rom-weaver-cli would otherwise do.
cargo binstall rom-weaver-cli
mise
Manages the CLI per project in mise.toml and verifies the release's GitHub
artifact attestations on install.
Install mise first.
The minimum_release_age=0s option lets
new releases resolve immediately on release day; omit it if you prefer mise's
default release-age delay.
mise use 'github:rom-weaver/rom-weaver[minimum_release_age=0s]'
Source install
Install the current source build. This requires Rust 1.95, CMake, Clang, and a native compiler toolchain.
git clone https://github.com/rom-weaver/rom-weaver.git
cd rom-weaver
cargo install --path crates/rom-weaver-cli --locked
rom-weaver --version
Run in Docker
A Linux CLI image is published for each release. It carries its own runtime, so nothing but Docker is required:
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$PWD:/work" \
ghcr.io/rom-weaver/rom-weaver-cli:latest \
probe --input /work/game.iso
The image's working directory is /work; mount the directory holding your ROMs
there and pass paths under /work. Arguments after the image name go straight to
rom-weaver, so --help and every subcommand work unchanged.
--user "$(id -u):$(id -g)" is what makes the output usable. Bind-mounted files
keep their host ownership, and without it the container runs as the base image's
nonroot user (uid 65532): reading your files may be refused, and anything it
does write ends up owned by a uid that does not exist on the host. rom-weaver
reads no home directory or user config, so an arbitrary uid needs no matching
account inside the image.
The image is distroless - it contains the rom-weaver binary and its C runtime
and nothing else, so there is no shell inside and --entrypoint sh will not get
you a prompt.
Mount read-only sources with :ro and give writes their own destination:
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$HOME/roms:/work/in:ro" \
--volume "$PWD/out:/work/out" \
ghcr.io/rom-weaver/rom-weaver-cli:latest \
weave --input /work/in/game.sfc --patch /work/in/hack.bps --output /work/out/patched.sfc
Tags follow the release: latest, the exact version (X.Y.Z), and the minor
series (X.Y). Stable releases also receive a major-series tag once the
project reaches 1.0. Prereleases publish under beta instead of latest. The
image is built for linux/amd64 and linux/arm64; Docker selects the native
variant on Apple Silicon and other arm64 hosts.
Development checkout
For a development checkout, follow the development guide
and use cargo run -p rom-weaver-cli --bin rom-weaver -- in place of
rom-weaver.
First weave
Run a complete patch with the tiny original homebrew NES ROM the webapp uses. The download holds the ROM, the patch, and the checksum the result should have, so there is nothing else to supply.
curl --fail --location --output first-weave.zip https://rom-weaver.com/first-weave.zip
rom-weaver weave --input first-weave.zip --output modified-rom.nes --no-compress
rom-weaver checksum --input modified-rom.nes --algo sha256
The original ROM displays HELLO WORLD; the first IPS patch changes HELLO
to MODIFIED, and the second changes WORLD to ROM. The final ROM displays
MODIFIED ROM. The final SHA-256 should be
e0db7cbd02cccd5e83931e7974db94aaafe40327b2a33fdd4c83235c9880a90e.
Open the result in any NES emulator to run it.
| Original ROM | After the first patch | After both patches |
|---|---|---|
![]() |
![]() |
![]() |
Practice patch creation and bundles
Use the two loose homebrew ROMs from guided Create. The first is the clean Original. The second is Modified:
curl --fail --location --output hello-world.nes \
https://rom-weaver.com/hello-world.nes
curl --fail --location --output modified-world.nes \
https://rom-weaver.com/modified-world.nes
Create a BPS patch, apply the downloaded artifact to the clean Original, and checksum the rebuilt file:
rom-weaver patch create \
--original hello-world.nes \
--modified modified-world.nes \
--output sample.bps
rom-weaver weave \
--input hello-world.nes \
--patch sample.bps \
--output rebuilt.nes \
--no-compress
rom-weaver checksum --input rebuilt.nes --algo sha256
The final SHA-256 should be
f203a199694d5a67a43857ce7e37a79e14a9fa1e7554ddd316b84f8df508b45e.
That match proves the patch rebuilt Modified byte for byte.
Now package that tested patch as a public-safe bundle. --no-bundle-rom keeps
the Original out of the ZIP while recording its checksums:
rom-weaver bundle create \
--input hello-world.nes \
--patch sample.bps \
--patch-id sample \
--patch-name "HELLO to MODIFIED" \
--expect-out sha256=f203a199694d5a67a43857ce7e37a79e14a9fa1e7554ddd316b84f8df508b45e \
--output rom-weaver-bundle.json \
--bundle sample-bundle.zip \
--no-bundle-rom
Test the finished archive from the same clean Original:
rom-weaver weave \
--input hello-world.nes \
--bundle sample-bundle.zip \
--output bundle-rebuilt.nes \
--no-compress
rom-weaver checksum --input bundle-rebuilt.nes --algo sha256
The result should have the same f203a1…b45e SHA-256. This sequence uses the
same generated assets as the browser tours, so both interfaces start from
identical bytes.
Common workflows
Find out what a file is. Archives are opened for you unless you pass
--no-extract:
rom-weaver probe --input unknown-file.bin
rom-weaver probe --input archive.zip --select '*.sfc'
Apply one patch, or several in order:
rom-weaver weave \
--input original.sfc \
--patch translation.bps \
--output translated.sfc \
--no-compress
rom-weaver weave \
--input original.sfc \
--patch base.ips \
--patch fixes.ups \
--output patched.zip
weave is the short name for patch apply; either spelling works.
The result is compressed by default, into whatever the --output extension
names. Pass --no-compress for a plain ROM, or set --compress-format,
--compress-codec, and --compress-level yourself.
Build a patch from an original ROM and a changed one:
rom-weaver patch create \
--original original.gba \
--modified modified.gba \
--format bps \
--output release.bps
SOLID patches carry their own metadata. --solid-system, --solid-game, and
--solid-hack fill in its three-string header. Adding any of
--solid-version, --solid-author, --solid-contact, or --solid-comment
switches to the seven-string extended header; --solid-extended selects that
header with the extra fields left empty. These flags need SOLID output and
cannot be combined with --plan.
rom-weaver patch create \
--original original.sfc \
--modified translated.sfc \
--output translation.solid \
--solid-system SNES \
--solid-game "Example Game" \
--solid-hack "English Translation" \
--solid-version 1.0 \
--solid-author "Example Team"
Unpack an archive, and hash a ROM:
rom-weaver extract --input collection.7z --output extracted
rom-weaver checksum --input game.gba --algo sha256
Shrink a disc image, or trim a ROM in place and put it back later:
rom-weaver compress --input disc.cue --output disc.chd
rom-weaver trim --input game.nds --in-place --revert-marker
rom-weaver trim --input game.nds --in-place --revert
Nearly every command takes -i/--input and -o/--output; patch create
is the exception, taking --original and --modified instead. The other short
flags are -j threads, -f format, -s select, -a algorithm, -e
extension, -n dry run, -v verbose, and -q quiet. Run
rom-weaver <command> --help for the full list.
probe and checksum accept - as the --input value to read from stdin, so
they fit into a pipeline:
curl -sL https://example.com/game.gba | rom-weaver checksum --input - --algo sha256
xz -dc game.iso.xz | rom-weaver probe --input - --json
Commands
| Command | Purpose |
|---|---|
probe |
Identify a file: its format, its platform, and any header it carries. |
extract |
Unpack an archive or disc image. |
checksum |
Hash a file, a byte range, or a ROM inside an archive. |
ingest |
Sort a file into ROMs and patches, unpacking and hashing as needed. |
compress |
Pack files into an archive or a compressed disc image. |
trim |
Cut the padding off a ROM, or put it back. |
patch apply |
Apply one or more patches to a ROM, in order. |
patch create |
Build a patch from an original ROM and a changed one. |
patch validate |
Check that patches would apply cleanly, without writing anything. |
bundle create |
Write a rom-weaver-bundle.json recipe from local files. |
bundle parse |
Read a bundle recipe and report what it points at. |
bundle schema |
Print the rom-weaver-bundle.json JSON Schema to stdout. |
tools ppf-undo |
Undo a PPF3 patch, using the undo data stored inside it. |
completions |
Print a tab-completion script for your shell. |
-h prints a one-line summary of each option; --help prints the full
explanation, including the extra detail on flags like --patch-header.
Alternate names
Some commands and flags answer to more than one name. They are the same code either way, so pick whichever reads better:
| Canonical | Also accepted |
|---|---|
rom-weaver probe |
rom-weaver inspect |
rom-weaver patch apply |
rom-weaver weave, rom-weaver patch weave |
trim --revert |
trim --untrim, trim --restore |
trim --revert-marker |
trim --reversible |
This guide uses weave for patching, since it is the shortest way to spell the
command people reach for most.
Format names have alternates too, accepted anywhere --format is: 7zip for
7z, 3ds for z3ds, xdelta3 for xdelta, bsdiff for bdf, and more.
The format tables list every one.
Codecs are stricter. Each format accepts only the codec names in its own row of
the codec table, and the only two alternates are CHD's
huffman for huff and avhu for avhuff. Passing --codec zlib to a ZIP,
for instance, is an error rather than a synonym for deflate.
Every command accepts these global flags, listed under Global options in its
help:
--jsonprints one JSON object per line instead of human-readable output.--progressand--no-progressoverride the automatic choice, which is to show progress on a terminal and hide it when output is piped.--log-level off|error|warn|info|debug|tracesets how much rom-weaver logs to stderr. Logging is off unless you ask for it, and it is separate from the normal output.-v,-vv, and-vvvare shorthand for info, debug, and trace.-q/--quietlogs errors only.--dep-traceadds trace output from the bundled libraries, useful in a bug report. On its own it also raises rom-weaver's own logs to warning level.--colorand--no-coloroverride colored output. The flag wins over theNO_COLORenvironment variable, which wins over the terminal-vs-piped default.--colorkeeps color even when piped, though the live progress bar stays terminal-only.
Most commands also accept -j/--threads auto|N. auto uses every core; a
number caps it, and a format may still use fewer when its implementation has a
lower ceiling.
List-valued flags (--algo, --checksum, --filter, --codec, --expect-in,
--expect-out, --assume-in, and the compression codec flags) can be repeated
or comma-separated: --algo crc32,sha1 and --algo crc32 --algo sha1 do the
same thing.
rom-weaver only asks interactive questions when stdin and stderr are both
terminals and --json is off. Otherwise it decides on its own or fails.
Reaching inside archives
probe, extract, checksum, ingest, trim, bundle parse, and the
patching commands all open archives for you, so you can point them at a .zip
and they will work on the ROM inside it. Four flags steer that, and they mean
the same thing everywhere they appear:
-s/--selectpicks which file to use, by exact name, prefix, or glob.--filter romconsiders only files that look like ROMs;--filter patchonly patches. Both judge by extension, and the flag is repeatable and comma-separable (--filter rom,patch).--no-ignorealso considers the files normally skipped: readmes, images, checksum sidecars, and OS clutter such as.DS_Store.--no-extractskips all of this and works on the file itself.
Not every command takes all four. ingest has --select and --no-ignore
only, since it always looks inside and always sorts by kind. extract has no
--no-extract, since unpacking is the whole job. trim spells its filter
--no-filter, because it filters to ROMs by default. rom-weaver <command> --help is authoritative.
extract also unpacks archives found inside the input, up to eight levels
deep; --no-nested-extract stops after the first layer. If any output file
already exists, extraction stops before writing anything, unless --force is
given. While extracting it can hash what it writes (--checksum ALGO, or
--checksum-rom ALGO for the ROMs only) and report each file's format and
platform (--probe).
Patch apply behavior
Repeat --patch to run several patches in order, each on the result of the
last. Leave --patch out entirely and rom-weaver looks for RetroArch-style
patches sitting next to the ROM inside the input archive. A
rom-weaver-bundle.json can supply the ROM, the patch order, the checks, and
the output name instead.
Checking the result:
- Formats that carry their own checksums are verified strictly.
--ignore-checksum-validationapplies the patch anyway, which can produce a broken ROM. --expect-in ALGO=HEXstops unless the ROM about to be patched matches.--expect-out ALGO=HEXfails unless the finished ROM matches.--assume-in ALGO=HEXtakes a checksum on trust rather than reading the ROM to compute it. It is a speed option for scripts and verifies nothing.
Headers and byte order:
--patch-header auto|keep|stripdecides whether each patch applies to the ROM with or without its copier header. Auto works it out per patch from the patch's own source checksum.--output-header auto|keep|stripdecides whether the finished ROM keeps its header. Auto keeps the ones emulators need and drops the ones they do not.--repair-checksumrecomputes the ROM's internal header checksum afterwards, so the console does not reject it.--n64-byte-order auto|keep|big-endian|little-endian|byte-swappedputs an N64 ROM in the interleaving a patch expects. Auto matches the patch's source CRC32, and the output is written back in the order the input arrived in.
Extras:
--codebakes Game Genie or GameShark/Pro Action Replay codes into the ROM, as if they were a patch.--emit-bundle PATHalso writes arom-weaver-bundle.jsonrecording the run: the ROM's checksums, the patches in order, and the result. It runs the same code asbundle create, so the file is byte-identical to the equivalentbundle createcall. It carries no per-patch names or authors; for those usebundle create,bundle create --from, or--tui.--tuiasks for each patch's name, version, author, and optional state plus an output name, then applies and writes the bundle. It needs a terminal, and for now it needs explicit--patchfiles; re-opening a bundle is not supported yet.
DCP patches need a Dreamcast .cue or .gdi input. They rebuild the GD-ROM
data track and reassemble the whole disc, so they cannot be chained with
another patch or combined with the header and checksum options.
Patch validation
patch validate runs the same checks as patch apply but writes nothing: it
parses each patch and verifies every checksum the format carries.
--expect-in adds a check on the ROM itself, and accepts a checksum
(ALGO=HEX), an exact size (size=N), or a minimum size (min-size=N).
--strip-header and --n64-byte-order put the ROM in the form the patches
expect before checking; N64 byte order defaults to matching the patch's source
CRC32.
Patches are checked as a chain by default, each against the output of the one
before it. --independent checks each one against the original ROM instead and
reports a verdict per patch, rather than stopping at the first failure.
Bundles
A rom-weaver-bundle.json bundle describes a distributable patching
workflow: ordered patches, expected input and output checksums, and output
naming. The machine-readable schema is
rom-weaver-bundle-v1.schema.json; its $id
resolves to the public GitHub copy at
https://raw.githubusercontent.com/rom-weaver/rom-weaver/main/docs/rom-weaver-bundle-v1.schema.json.
For an end-to-end release workflow in either the Weave webapp or terminal,
start with Create and share a patch bundle.
Print the current schema to stdout with bundle schema, then redirect it to a
file or point an editor at it:
rom-weaver bundle schema > rom-weaver-bundle-v1.schema.json
Create a bundle from local files; the checks are computed from the real bytes:
rom-weaver bundle create \
--input original.sfc \
--patch translation.bps \
--patch fixes.ips \
--output rom-weaver-bundle.json
-i/--input names the ROM. Use --rom-url and --rom-name when the ROM
ships from somewhere else and the bundle should only point at it.
Every --patch-* flag describes the --patch before it: --patch-id,
--patch-version, --patch-author, --patch-name, --patch-description,
--patch-optional, --patch-label, --patch-source-url, --patch-header,
and --patch-basis. Give each patch an ID that stays the same across releases
and the webapp keeps its settings when you publish a replacement; bump
--patch-version at the same time.
Checksums use the same tokens as patch apply. --expect-out ALGO=HEX pins
the final result, --patch-expect-in and --patch-expect-out pin what a
single patch should see and produce, and --assume-in takes the ROM's
checksum on trust rather than reading the file.
--bundle <archive> also packs the recipe and its files into one shareable
archive. --no-bundle-rom leaves the ROM out and records only its checksums,
which is the usual shape for distributing a patch. --schema-ref <URL> records
a $schema URL for editors; it is left out unless you ask for it, so the
output stays byte-stable.
Rather than pass every flag, hand-author a rom-weaver-bundle.json spec with
local paths and optional or omitted checksums, add a $schema line so your
editor validates it, then let bundle create --from hash the referenced files
and bake the canonical checksummed bundle:
{
"$schema": "https://raw.githubusercontent.com/rom-weaver/rom-weaver/main/docs/rom-weaver-bundle-v1.schema.json",
"version": 1,
"rom": { "path": "original.sfc" },
"patches": [
{ "path": "translation.bps", "name": "English translation" },
{ "path": "fixes.ips", "optional": true }
],
"output": { "name": "translated.sfc" }
}
rom-weaver bundle create --from spec.json --output rom-weaver-bundle.json
--from - reads the spec from stdin, in which case paths resolve against the
current directory; otherwise they resolve against the spec file. Any flag you
also pass overrides what the spec says, and a $schema already in the spec is
kept. --from only accepts entries with a local path; url-only and
checks-only entries are rejected with an explanation.
bundle parse --input <bundle> checks a bundle and reports what it points at.
Add --output <dir> to write out the files packed alongside it. For an archive
bundle it also takes -s/--select, --filter rom|patch, and --no-extract,
which behave as they do elsewhere. A plain JSON bundle references files by
relative path, so there is nothing for those options to unpack.
To actually run a bundle, use rom-weaver weave --bundle <path-or-url>, with
--with and --without to change which optional patches run.
Supported formats
Patch formats
All formats marked Apply also support probe/parse. DCP is a specialized Dreamcast apply workflow rather than a general single-file patch parser.
| Format | Aliases | Extensions | Apply | Create |
|---|---|---|---|---|
| IPS | none | .ips |
yes | yes |
| IPS32 | none | .ips32 |
yes | yes |
| SOLID | solidpatch, solid-patch |
.solid |
yes | yes |
| BPS | none | .bps |
yes | yes |
| UPS | none | .ups |
yes | yes |
| VCDIFF | vcdiff |
.vcdiff |
yes | yes |
| xdelta | xdelta3 |
.xdelta, .delta, .dat |
yes | yes |
| GDIFF | gdiff |
.gdiff, .gdf |
yes | yes |
| HDiffPatch/HPatchZ | hdiffpatch, hdiff, hpatch, hpatchz |
.hdiff, .hpatchz |
yes | no |
| APS (N64) | none | .aps |
yes | yes |
| APSGBA | aps-gba |
.apsgba |
yes | yes |
| RUP | none | .rup |
yes | yes |
| PPF | none | .ppf |
yes | yes |
| PAT | ffp, fireflower |
.pat, .ffp |
yes | yes |
| EBP | none | .ebp |
yes | yes |
| BDF/BSDIFF40 | bdf, bsdiff, bsdiff40 |
.bdf, .bsdiff, .bsdiff40 |
yes | yes |
| BSP | bspatch |
.bsp, .bspatch |
yes | no |
| MOD | pmsr |
.mod, .pmsr |
yes | yes |
| DLDI | none | .dldi |
yes | yes |
| DPS | none | .dps |
yes | yes |
| DCP | none | .dcp |
yes | no |
HDiffPatch directory patches (HDIFF19) are not supported; single-file
.hdiff and .hpatchz patches are supported. NINJA1 headers can be detected
but not applied, and PDS is unsupported.
Container and compression formats
| Format | Aliases | Extensions | Probe | Extract | Create |
|---|---|---|---|---|---|
| ZIP | none | .zip |
yes | yes | yes |
| ZIPX | none | .zipx |
yes | yes | no |
| 7z | 7zip |
.7z |
yes | yes | yes |
| RAR | none | .rar |
yes | yes | no |
| TAR | none | .tar |
yes | yes | no |
| TAR.GZ | tgz |
.tar.gz, .tgz |
yes | yes | no |
| TAR.BZ2 | tbz2 |
.tar.bz2, .tbz2 |
yes | yes | no |
| TAR.XZ | txz |
.tar.xz, .txz |
yes | yes | no |
| Gzip | gzip |
.gz |
yes | yes | no |
| Bzip2 | bzip2 |
.bz2 |
yes | yes | no |
| XZ | lzma, lzma2 |
.xz |
yes | yes | no |
| Zstandard | zstd, zstandard |
.zst |
yes | yes | no |
| CSO | ciso |
.cso, .ciso |
yes | yes | no |
| PBP | none | .pbp |
yes | yes | no |
| CHD | chd-cd, chd-gd, chd-dvd, chd-raw, chd-hd, chd-av, chd-ld |
.chd |
yes | yes | yes |
| GCZ | none | .gcz |
yes | yes | no |
| WIA | none | .wia |
yes | yes | no |
| TGC | none | .tgc |
yes | yes | no |
| NFS | none | .nfs |
yes | yes | no |
| WBFS | none | .wbfs |
yes | yes | no |
| RVZ | none | .rvz |
yes | yes | yes |
| Z3DS | 3ds |
.z3ds, .zcci, .zcxi, .zcia, .z3dsx |
yes | yes | yes |
| XISO | none | .xiso, .xiso.iso |
no | yes | no |
XISO extraction rebuilds the detected XDVDFS filesystem as a normalized ISO;
detailed probe reports and XISO creation are not supported. CHD
parent/differential support exists in the Rust container API but is not exposed
as a native CLI flag. extract --split-bin affects CHD CD extraction only.
Create-time codecs
| Output | Supported --codec values |
|---|---|
| ZIP | store, deflate, zstd |
| 7z | lzma2 |
| RVZ | zstd |
| Z3DS | zstd |
| CHD | store, zlib, zstd, lzma, huff, flac, cdlz, cdzl, cdzs, cdfl, avhuff |
huffman aliases huff; avhu aliases avhuff. CHD accepts repeated codec
options for MAME-style codec lists.
Compression levels use these codec-aware profiles:
minvery-lowlowmediumhighvery-highmax
An explicit codec:level value overrides the global profile.
Checksum support
Supported algorithms are crc32, md5, sha1, sha256, blake3,
crc32c, crc16, and adler32.
Checksums can target source bytes, selected container payloads, or byte
ranges (--start/--length).
Known header and byte-order compatibility transforms appear as
checksum_variants, including raw, headerless, repaired-header, and N64 byte
orders. --no-trim-fix disables automatic trim-boundary variants.
Trim support
trim supports:
- NDS-family ROMs (
.nds,.dsi,.srl) - GBA ROMs (
.gba) - 3DS images (
.3ds) - XISO images (
.xiso,.xiso.iso, and probed XDVDFS.isofiles) - RVZ scrub candidates detected by the format recommendation
--in-place rewrites the source file; --output or --extension write the
trimmed copy elsewhere instead, and -n/--dry-run reports what would change
without writing anything.
--revert pads a trimmed file back out, and works for NDS, GBA, and 3DS. XISO
and RVZ scrub cannot be reverted. It also answers to --untrim and
--restore.
--revert-marker (also --reversible) embeds a small footer so a later revert
reproduces the original padding exactly rather than guessing at it; see the
footer format.
Header detection and repair
Probe, checksum, and patch apply recognize headers for A78, LNX, NES/FDS, SNES copier and SMC variants, PCE copier formats, Game Boy/GBA, Mega Drive, SMS/Game Gear, all N64 byte orders, NDS, Neo Geo Pocket, and MSX.
JSON output
Pass --json to emit one JSON object per line, including progress, status,
warnings, selected inputs, and emitted-file metadata where relevant. JSON mode
disables interactive selection, making it the stable interface for scripts:
rom-weaver --json probe --input game.sfc | jq
Exit codes
rom-weaver returns 0 on success, 1 when an operation fails, 2 for an
unsupported operation or a command-line usage error, and 130 when a run is
cancelled.
File permissions
Inputs are checked for readability before a command does any work. The commands
that write large outputs (extract, compress, trim, patch apply, and
patch create) have their destination checked for writability at the same
point, so a read-only output directory costs you a quick error rather than an
abandoned multi-gigabyte compress. Both checks do the real thing, an open, a
listing, or a create, so ACLs, group membership, and read-only mounts are
honored instead of guessed at from mode bits.
Denials name the path, the operation, and the identities involved:
error: i/o error: cannot open `/roms/game.iso`: Permission denied (os error 13)
(`/roms/game.iso` is mode 0600 owned by 0:0; this process runs as 1000:1000)
Read that as three facts: what was refused, who owns it, and who asked. Only a
genuinely missing path is reported as input path does not exist. A file that
exists but cannot be reached, including one behind a directory you cannot
traverse, is always reported as a denial rather than as a typo.
Common fixes:
- Reading someone else's files.
sudo chownthem, add yourself to the owning group, or copy them somewhere you own. - Writing to a read-only location. Point
--outputat a directory you own. rom-weaver creates missing output directories but never changes permissions on an existing one. - Output files owned by the wrong user. New files inherit your identity and umask; rom-weaver does not copy the source file's mode.
- Inside a container. The message adds a container hint, because the usual
cause is a uid mismatch against a bind mount. Re-run with
--user "$(id -u):$(id -g)"as shown in Run in Docker.
Permission failures exit 1. Under --json they arrive as a terminal event
with "status": "failed", carrying "stage": "validate" when the preflight
caught them.
Shell completions
Print a completion script and save it where your shell looks for one, then start a new shell:
rom-weaver completions bash > /etc/bash_completion.d/rom-weaver
rom-weaver completions zsh > ~/.zfunc/_rom-weaver
rom-weaver completions fish > ~/.config/fish/completions/rom-weaver.fish
bash, zsh, fish, powershell, and elvish are supported.
For format specifications and upstream implementations, see
references.md.
Man pages
The pages under docs/man come from the same Clap definitions as --help, so
they always match it. They are not checked in; run the generator to produce
them (the npm prepack step does this automatically before publishing):
mise run manpages
Use man ./docs/man/rom-weaver.1 from a source checkout when they are not
installed system-wide. Do not edit the generated .1 files manually.


