Forge three small fixtures with no proprietary content so the e2e test runs by default and covers the full decrypt+extract path, including the delta/base VHD merge: - TEST_T001_..._0.opt encrypted exFAT (OPTION key) - TEST_1.00.00_..._0.app base APP: outer NTFS -> internal_0.vhd (fixed) -> inner NTFS - TEST_1.01.00_..._1_1.00.00.app delta APP: differencing internal_1.vhd, auto-merged with base by GUID The APP fixtures use the synthetic game id TEST, decrypted via the committed TEST.bin external key (also exercising that key-file fallback). e2e.rs copies the fixtures + key into a temp dir and runs from there so the delta finds its base and the key. .gitignore whitelists only these synthetic files so real containers dropped in fixtures/ stay private. The generator is kept local, not committed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fsdecrypt
Decryptor and extractor for SEGA arcade filesystem containers (fscrypt format). Works on Windows and Linux, no admin privileges required.
Installation
Requires Rust.
cargo build --release
The binary will be at target/release/fsdecrypt (or fsdecrypt.exe on Windows).
Usage
fsdecrypt <FILES>...
Examples
# Extract a game APP container
fsdecrypt ABCD_1.00.00_20240101120000_0.app
# Extract an OPTION/DLC container
fsdecrypt ABCD_A001_20240101120000_0.opt
# Extract multiple files at once
fsdecrypt game_v1.app game_v2.app extras.opt
# Decrypt only (outputs raw .ntfs/.exfat image, no extraction)
fsdecrypt --no-extract ABCD_1.00.00_20240101120000_0.app
Delta Updates
Games often ship incremental updates as a separate .app file. Just pass the update file — fsdecrypt will automatically find the base in the same folder and merge them:
fsdecrypt ABCD_1.01.00_20240215143000_1_1.00.00.app
You can also pass both explicitly:
fsdecrypt ABCD_1.00.00_20240101120000_0.app ABCD_1.01.00_20240215143000_1_1.00.00.app
The output folder is named after the input file (e.g. ABCD_1.01.00_20240215143000_1_1.00.00/).
External Key Files
For games not in the built-in key database, place a file named {GAME_ID}.bin in the working directory:
- 16 bytes for key only (IV derived automatically)
- 32 bytes for key + IV
Testing
Tests run locally (there is no hosted CI). The unit tests are plain cargo test;
the Linux x86_64-musl release target is exercised via Docker when you need it.
cargo test # unit tests (IV derivation, timestamp decoding, key lookup)
scripts/test.sh # native tests + smoke test, then the same in a musl container
scripts/test.sh --no-docker # skip the Docker/Linux step
scripts/test.sh uses the clux/muslrust
image to build and test the static Linux binary — the same way release artifacts
are produced — so both targets can be validated from any host.
End-to-end fixtures
The tests/e2e.rs test decrypts real containers placed in a fixtures/
directory at the repo root and checks that each extracts successfully. That
folder is git-ignored — the proprietary sample containers are never committed —
and the test is a no-op when it is empty, so just drop a few .app/.opt files
in fixtures/ and run cargo test (or scripts/test.sh) to exercise the full
decrypt-and-extract path.
License
BSD Zero Clause License (0BSD)