53 Commits
Author SHA1 Message Date
jujuforce 7100ec4418 Merge pull request 'chore: release version 0.1.9' (#12) from jujuforce/fsdecrypt:chore/bump-0.1.9 into trunk v0.1.9 2026-07-06 21:34:59 +00:00
JujuforceandClaude Opus 4.8 521736aa58 chore: release version 0.1.9
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:34:43 +02:00
jujuforce b1832872d3 Merge pull request 'chore: repoint exfat-fs patch to v0.1.3-tag-based commit' (#11) from jujuforce/fsdecrypt:fix/exfat-empty-file-rev-clean into trunk 2026-07-06 20:13:54 +00:00
JujuforceandClaude Opus 4.8 f4f1f33f8b chore: repoint exfat-fs patch to the v0.1.3-tag-based fix commit
The exfat-fs empty-file fix branch was rebuilt on top of the upstream v0.1.3
release tag (instead of an orphan commit) so it has proper shared history and
reads as a clean "1 commit ahead of v0.1.3". Update the pinned rev accordingly;
tree and behavior are identical (v0.1.3 + the one-function fix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:13:24 +02:00
jujuforce 18c2a66032 Merge pull request 'fix: extract exFAT OPTION containers that contain empty files' (#10) from jujuforce/fsdecrypt:fix/exfat-empty-file-extraction into trunk 2026-07-06 20:08:07 +00:00
JujuforceandClaude Opus 4.8 622aa537ee fix: extract exFAT OPTION containers that contain empty files
exfat-fs 0.1.3's StreamExtensionEntry::valid() rejects any entry with
FirstCluster < 2, but a zero-length file (or empty dir) legitimately has
FirstCluster = 0 / DataLength = 0 per the exFAT spec. The parser returned
InvalidStreamExtension and aborted the *entire* directory tree the moment it
hit one empty file, so real OPTION (.opt) containers failed to extract with:

    WARNING: Failed to extract exfat contents: InvalidFileEntry(InvalidStreamExtension)

even though unsega/7-Zip read them fine.

0.1.3 is the latest published release and its `main` has an incompatible
public API, so override the dependency via [patch.crates-io] with a fork
branch that is the 0.1.3 release plus only the one-function fix (gate the
cluster check on DataLength: 0 -> FirstCluster must be 0, else >= 2).

Regenerate the synthetic OPTION fixture with a zero-byte file so tests/e2e.rs
reproduces the bug: without the patch it fails with InvalidStreamExtension;
with it, extraction succeeds. Verified against a real 191 MB OPTION container
(1602 files / 1217 dirs, empty file written as 0 bytes) and the full suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:06:38 +02:00
jujuforce 47ca360f2e Merge pull request 'chore: gitignore AGENTS.md' (#9) from jujuforce/fsdecrypt:chore/gitignore-agents-md into trunk 2026-06-28 13:06:50 +00:00
JujuforceandClaude Opus 4.8 fd7890e6ba chore: gitignore AGENTS.md
Agent-instructions file (like the already-ignored CLAUDE.md); keep it out of the repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:06:31 +02:00
jujuforce bc837174ed Merge pull request 'feat: add Windows right-click "Unpack with fsdecrypt" context menu' (#8) from jujuforce/fsdecrypt:feat/windows-context-menu into trunk 2026-06-28 12:57:16 +00:00
JujuforceandClaude Opus 4.8 0057ede592 feat: add Windows right-click "Unpack with fsdecrypt" context menu
install-context-menu.bat / uninstall-context-menu.bat add a per-user (HKCU)
Explorer entry for .app and .opt files, with an embedded unpack icon
(assets/fsdecrypt.ico). No admin needed; the normal double-click association is
untouched. The registered command keeps the console open after extraction.
Documented in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:56:45 +02:00
jujuforce a3f1ed22ff Merge pull request 'fix: skip NTFS DOS (8.3) short-name entries when extracting' (#7) from jujuforce/fsdecrypt:fix/ntfs-dos-short-name-extraction into trunk 2026-06-28 12:25:40 +00:00
JujuforceandClaude Opus 4.8 cd02c09ad0 fix: skip NTFS DOS (8.3) short-name entries when extracting
When a file or directory has a separate 8.3 short name, it appears in the NTFS
directory index twice: once under its Win32 long name and once under the Dos
short name. The extractor iterated all index entries, so every such item was
written a second time under its mangled NAME~1.EXT alias — and short-named
directories had their entire subtree re-extracted.

Skip index entries in the Dos namespace (the file is still extracted via its
Win32 entry). Add unit tests for the skip decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:03:30 +02:00
jujuforce 8e3d7a6d90 Merge pull request 'test: add unit + CLI + e2e tests with synthetic fixtures, and a local Docker runner' (#6) from jujuforce/fsdecrypt:test/add-unit-tests-and-ci into trunk 2026-06-28 11:53:27 +00:00
JujuforceandClaude Opus 4.8 d79ded6ee5 test: add synthetic, committable e2e fixtures (base/delta app + opt)
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>
2026-06-28 13:37:37 +02:00
JujuforceandClaude Opus 4.8 a73f71e3de test: add e2e fixture decryption test
Decrypts real .app/.opt containers dropped into a git-ignored fixtures/ dir
(copied to a temp dir first so extraction never touches the fixtures), asserting
each produces a non-empty extraction. No-op when fixtures/ is empty, so the suite
stays green without the proprietary sample data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:28:00 +02:00
JujuforceandClaude Opus 4.8 0a7b633310 test: add a CLI --help integration test
Runs the built binary with --help and asserts it exits 0 and prints the usage, options, and about text (visible via cargo test -- --nocapture).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:01:46 +02:00
JujuforceandClaude Opus 4.8 d92e4b5ba9 test: add unit tests and a local Docker test runner
- Unit tests for calculate_page_iv (incl. 8-byte wrap), calculate_file_iv round-trip, and game-key lookup
- Extract exfat_utc_offset_seconds and nt_timestamp_to_system_time as pure helpers and test them (covers the recent exFAT UtcOffset fix)
- scripts/test.sh runs cargo test + a smoke test natively, then repeats for the x86_64-musl target in a clux/muslrust container (no hosted CI needed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:44:35 +02:00
jujuforce d48433510e Merge pull request 'chore: bump version to 0.1.8' (#4) from release/0.1.8 into trunk v0.1.8 2026-06-28 09:18:27 +00:00
JujuforceandClaude Opus 4.8 9c2821ddab chore: bump version to 0.1.8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:18:27 +02:00
jujuforce 10014ac0e6 feat: auto-extract and merge delta .app updates in pure Rust
Reviewed-on: https://gitea.tendokyu.moe/beerpsi/fsdecrypt/pulls/3
Reviewed-by: beerpsi <beerpsi@noreply.gitea.tendokyu.moe>
2026-06-28 08:36:03 +00:00
Jujuforce 93526c5b6d Merge branch 'feat/auto-merge-delta-vhd' of https://gitea.tendokyu.moe/jujuforce/fsdecrypt into feat/auto-merge-delta-vhd 2026-04-21 12:41:31 +02:00
Jujuforce 01e1437a1e chore: edited gitignore 2026-04-21 12:41:09 +02:00
jujuforce 2ef6c992f9 fix: resolve VHD chains via parent/own GUIDs 2026-04-20 20:44:56 +02:00
jujuforce d719125ea3 fix: set file times after writing to preserve source mtime 2026-04-20 14:28:12 +02:00
jujuforce 558c0ecec0 fix: parse exFAT UtcOffset per spec, tolerate bad timestamps 2026-04-20 11:37:53 +02:00
JujuforceandClaude Opus 4.6 b0e97f8b00 docs: simplify README, reflect pure Rust extraction
Remove technical implementation details, keep it user-friendly.
Document delta update auto-detection, output folder naming,
and cross-platform support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 068badad7e feat: name output folder after the input .app file
Output directory is now derived from the input .app path (without
extension) instead of the intermediate VHD filename.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 2121b43699 feat: pure Rust VHD merge, remove PowerShell dependency
Replace Set-VHD + Merge-VHD PowerShell cmdlets with a pure Rust
MergedVhdReader that overlays delta blocks on the base VHD using
the sector bitmap to decide which sectors come from which disk.
No actual merge to disk needed — reads directly from both files.

- No admin privileges or UAC prompts required
- No Hyper-V or PowerShell dependency
- Cross-platform (works on Windows and Unix)
- Remove #[cfg(windows)] from merge/extract path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 65b070adbd refactor: clean up code, use constants, separate concerns
- Move VHD parsing and NTFS extraction to src/vhd.rs
- Replace all hardcoded magic values with named constants
- Extract shared progress bar style to constant
- Simplify main.rs merge/resolve logic with helper functions
- Condense formatting without changing behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 f612c44f19 feat: pure Rust VHD extraction, no admin/Hyper-V needed
Replace PowerShell-based VHD mounting (Set-VHD/Mount-VHD/Copy-Item)
with a pure Rust implementation. New src/vhd.rs module:
- VhdReader<R> parses fixed and dynamic VHD formats
- Handles MBR partition tables and NTFS offset detection
- Dynamic VHD support with BAT (Block Allocation Table) parsing
- Recursive NTFS directory extraction with timestamp preservation
- No elevation, no Hyper-V, works cross-platform

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 96bb605a17 feat: extract VHD contents to folder and delete VHD
After decryption and merging, VHDs are now mounted via Hyper-V,
their contents copied to a same-named folder (excluding system
dirs), then dismounted and deleted. Handles partitions without
drive letters by assigning one temporarily. Uses a temp .ps1
script to avoid nested PowerShell quoting issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 6b06bc4e92 feat: auto-extract base .app when only delta is provided
When no base VHD is found, fsdecrypt now also searches for the base
.app file ({game_id}_*_0.app) in the same directory, extracts its
VHD, and then merges the delta into it automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 371abf4e32 feat: search for existing base VHD in same directory
When only a delta .app is provided without the base, fsdecrypt now
scans the same directory for an existing base VHD matching the
game ID pattern ({game_id}_*_0.vhd) before giving up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 c896f362ba feat: auto-merge delta VHD updates for APP containers
When multiple APP files are provided (base seq=0 + delta seq>0),
fsdecrypt now automatically links and merges the differencing VHD
into the parent using Set-VHD + Merge-VHD (Windows/Hyper-V only).
Triggers a single UAC prompt for elevation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:40:18 +01:00
JujuforceandClaude Opus 4.6 4461e5169e chore: add .gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:39:52 +01:00
JujuforceandClaude Opus 4.6 d4c4ba8d8d docs: add README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:39:52 +01:00
jujuforce 49e24c0ee4 Merge branch 'trunk' of git.tendokyu.moe:beerpsi/fsdecrypt into trunk 2026-01-21 13:36:15 +01:00
beerpsi 4c3ebd9771 fix IV derivation v0.1.7 2026-01-21 09:26:00 +07:00
beerpsi 362fb91ff7 feat: also extract exfat without saving intermediary exfat image v0.1.6 2026-01-21 09:05:10 +07:00
beerpsi 023627426e Release 0.1.5 v0.1.5 2026-01-20 18:02:42 +07:00
beerpsi 0c4a521bc2 feat: directly extract VHD for apps and packs 2026-01-20 18:00:19 +07:00
beerpsi 8db2d1e71b port buffering to exfat extraction v0.1.4 2025-11-04 13:05:10 +07:00
莯凛 8c5998a85c perf-buf-size (#2)
Increased IO throughput when laying out VHD file

`std::io::copy` will only setup an 8KiB stack buffer, which results sub optimal performance when passing `File` directly.

Reviewed-on: https://gitea.tendokyu.moe/beerpsi/fsdecrypt/pulls/2
Co-authored-by: 莯凛 <mokurin000@gmail.com>
Co-committed-by: 莯凛 <mokurin000@gmail.com>
2025-11-04 05:53:17 +00:00
beerpsi ece90ebb5b ntfs extraction + add spinner for extracting images v0.1.3 2025-07-17 20:54:53 +07:00
beerpsi 8b34b87732 feat: NTFS support 2025-07-17 19:57:03 +07:00
beerpsi 7bcd2efb06 Release 0.1.2 2025-07-16 23:45:13 +07:00
beerpsi 1d79e96509 feat: set access/modified time from the exfat 2025-07-16 23:36:42 +07:00
5d1e0ca908 feat: exfat content extraction (#1)
Co-authored-by: jujuforce <jujuforce@gmail.com>
Co-authored-by: beerpsi <beerpsi@noreply.gitea.tendokyu.moe>
Reviewed-on: https://gitea.tendokyu.moe/beerpsi/fsdecrypt/pulls/1
Co-authored-by: jujuforce <jujuforce@noreply.gitea.tendokyu.moe>
Co-committed-by: jujuforce <jujuforce@noreply.gitea.tendokyu.moe>
v0.1.2
2025-07-16 15:49:11 +00:00
beerpsi 6cdf6a8e52 use more idiomatic rust 2025-07-16 15:47:53 +00:00
jujuforce 53c389ba05 feat: exfat content extraction 2025-07-16 15:12:32 +02:00