Releases: rdmrocha/xtafkit
v1.3.0
Adds read-extraction for Xbox 360 STFS container packages — Arcade (XBLA), XBLIG, Title Updates, Marketplace DLC. Covers both the CLI (xtafkit extract-stfs) and the TUI upload-sniff path (u on a local STFS file → e(X)tract / (R)aw / Esc). New library surface under fatxlib::stfs::extract with a StfsSink trait abstraction shared between host-filesystem and FATX-volume destinations.
STFS extraction (CLI)
- Added
xtafkit extract-stfs <PACKAGE>for streaming Xbox 360 STFS containers (CON / LIVE / PIRS) to a local directory.--to <DIR>overrides the destination;--dry-runlists the entries with sizes and totals;--jsonemits machine-readable output for both dry-run and post-extract modes. - Default destination (no
--to) is./<DisplayName>/taken straight from the STFS header — no catalog lookup, no[TitleID]suffix. The per-packagedisplay_nameis consistently more specific than the title-id catalog mapping (notably for XBLIG, where every indie game shares the same system title-id and the catalog can only return a generic bucket name). - Read-only / type-1 STFS only. Type-0 (read-write, used by save games / on-drive system files / CON packages) surfaces an explicit
"STFS type 0 (read-write) not supported yet"error rather than producing wrong output. - Block-index → byte-offset translator handles all interleaved hash levels: L0 every
0xAAblocks, L1 every0x70E4, L2 every0x4AF768. Inline boundary tests pin offsets at0xA9,0xAA,0x70E3,0x70E4against literal hex values and assert strict monotonicity across boundaries. - File chain follower covers both the consecutive fast path (no hash-block reads) and the fragmented case (next-block pointer threaded through the L0 hash block at offset
(N % 0xAA) * 24 + 0x15). Walk is capped atused_blocksiterations to reject malformed cyclic chains, mirroring the existing FAT cycle rejection involume.rs. - Defensive parent-chain resolution: rejects cyclic
parent_indexreferences and out-of-range parent pointers; refuses to overwrite existing output files.
STFS extraction (TUI)
- The upload prompt (
u) now sniffs every local file. On a CON / LIVE / PIRS package that contains a depth-0default.xex, the prompt becomesDetected STFS '<filename>' (Arcade). e(X)tract / (R)aw / Esc:. Default action is(X)tract. (X)tractstreams the package's inner files directly into a new subfolder under the current FATX directory, named from the STFSdisplay_name(sanitised for FATX). No intermediate host-FS staging.- Gating rule: the (X) option only appears when the package has a depth-0
default.xex. Title Updates (which containdefault.xexponly) and streaming-DLC Marketplace packages (no executable) bypass the sniff and fall through to plain raw upload — they aren't useful as loose files on the drive. - The CLI
extract-stfsremains unrestricted; the TUI gating is a UX guard rail, not a library-level restriction. - Mid-extraction
Esccancels cleanly. The library function honours anOption<&AtomicBool>cancel flag checked at the top of every entry iteration.
Library API additions
- New
fatxlib::stfssubmodules:volume_descriptor,block_translator,file_entry,extract. Existing header parsing (StfsHeader,parse_header,MIN_HEADER_BYTES) moved intofatxlib::stfs::headerand re-exported at the namespace root — no breaking changes for existing callers. fatxlib::stfs::StfsPackage::{open, header, volume, entries, read_block_chain, read_file, has_default_xex}— read API for STFS packages.read_file<W: Write>streams through a writer; no full-file buffering even for multi-hundred-MiB packages.has_default_xexis the depth-0 root-only gating check used by the TUI sniff.fatxlib::stfs::extract::extract_to_host(&mut StfsPackage, &Path, Option<ProgressFn>) -> Result<ExtractReport>— top-level walk + write to a host filesystem, with progress callback shape matching the existing XISO extract ((rel_path, file_size, total_bytes_so_far)).fatxlib::stfs::extract::extract_to_fatx(&mut StfsPackage, &mut FatxVolume, &str, Option<ProgressFn>, Option<&AtomicBool>) -> Result<ExtractReport>— top-level walk + write directly into a FATX volume. Used by the TUI worker; available to library consumers.fatxlib::stfs::extract::ExtractReport—{ files, directories, bytes }returned on success. Unified semantics:directoriescounts STFS-package directory entries only (does not count an implicitly-created destination root).
Internal refactor
fatxlib/src/stfs/extract/split from a single file into a directory module with four parts, mirroring the existingfatxlib/src/iso/god/pattern:core.rs(theStfsSinktrait + sharedrun_extractengine),sink_host.rs(HostSinkimpl +extract_to_hostwrapper),sink_fatx.rs(FatxSinkimpl +extract_to_fatxwrapper + the streamingStfsFileReaderadapter), andmod.rs(StfsPackage+ its tests). Eliminates the ~95% duplication that existed between the twoextract_to_*functions and gives any future extract destination a single trait to implement.
Testing / maintenance
- 32 inline synthetic tests across the STFS submodules: volume-descriptor parsing (type-0/1 detection, wrong-size rejection, truncation), type-1 block translator (boundary indices at every hash level plus monotonicity), file entry parsing (consecutive flag, directory flag, parent-index, non-ASCII tolerance),
has_default_xex(positive, case-insensitive, subfolder-only rejection, TUdefault.xexprejection), and end-to-end synthetic package extraction with a nested directory tree. - New
fatxlib/tests/stfs_extract_to_fatx.rsintegration test exercises a round-trip throughextract_to_fatxagainst a real FATX volume, including the cancel-flag path. - TUI sniff helper (
stfs_extract_target) covered by three new unit tests insrc/tui.rs: non-STFS file rejected, STFS withoutdefault.xexrejected, STFS withdefault.xexreturns the catalog-derived destination name.
What's Changed
- Adds read-extraction for Xbox 360 STFS container packages by @rdmrocha in #9
- stfs in tui by @rdmrocha in #10
Full Changelog: v1.2.1...v1.3.0
v1.2.1
Maintenance and hardening release on top of 1.2.0. No new features; all changes are bug fixes, internal refactors that reduce drift, and test coverage for areas that previously had none.
Correctness / hardening
- Fixed
XBEcertificate parsing reading 4 bytes pastdwVersion— the version field landed in the LAN-key region, so every Original Xbox executable routed throughTitleInfo::from_xbewas returning a wrong version. Seek width corrected from 164 to 160 bytes. - Fixed
ConHeaderBuilder::with_game_titlepanicking on titles ≥ 64 UTF-16 code units; the UTF-16 writer now caps at the field length (0x80 bytes / 64 units, null included) for both title slots in the CON header. - Fixed
looks_like_gamertagrejecting all non-Latin gamertags — Cyrillic, CJK, Greek, etc. now resolve correctly. ASCII-only checks replaced withis_alphabetic/is_alphanumeric. - Fixed
pick_profile_namecaching STFS system-package titles ("Xbox 360 Dashboard") as if they were gamertags; candidates now run throughlooks_like_gamertagbefore being accepted. - Fixed
seek(SeekFrom::End(0))silently returning 0 on macOS raw block devices:partition::detect_xbox_partitions,partition::scan_for_fatx, andFatxVolume::opennow error explicitly when given a zerodevice_size.scan_for_fatxalso gained an explicitdevice_size: u64parameter, with the CLI deep-scan caller updated accordingly. - Fixed FAT16/FAT32 selection drifting from the FATX-specific 65520 (0xFFF0) threshold.
volume.rsandmkimage.rsnow use theFAT16_CLUSTER_THRESHOLDconstant fromfatxlib::types. Volumes with 65520–65524 clusters were previously mislabeled as FAT16. - Fixed
FatxVolume::write_file_in_placenot flushing the FAT cache between chain extension (Phase 1) and payload writes (Phase 2); a mid-operation failure could previously leave the directory entry advertising clusters the on-disk FAT had not yet linked. - Fixed
validate_filenamemeasuring byte length instead of character count when comparing against the 42-char FATX cap. - Fixed
FatxVolume::statspanicking in debug builds (or wrapping in release) when corrupt cached counts producedfree + bad > total; counts are nowsaturating_sub'd. - Fixed host-to-FATX directory copies reading entire files into memory; large host-FS uploads now stream cluster-by-cluster through
create_file_from_reader. - Fixed the guided picker's stale
sudo fatxhint — nowsudo xtafkit. - Tightened
build.rscatalog floor checks (xbox360 > 4000,xbox_og > 800) so a truncated source no longer ships an empty or partial title map. The literal-pairing loop inwrite_mergedwas simplified to a single pass over the merged map, eliminating a parallelvalues()/iter()zip that was correct only by coincidence ofBTreeMapiteration order. - Centralized FATX cluster allocation behind two private helpers (
reserve_free_clusters,link_allocated_clusters); previously three near-identical bitmap-walk + chain-linking copies inallocate_chain,write_file_in_place, andplan_write_in_place_for_entrycould drift on independent fixes.
TUI / quality of life
- Installed a panic hook and
Drop-based terminal guard so a crash inside Ratatui or the background IO worker no longer leaves the shell in raw mode with the alternate screen stuck. - The UI now detects
TryRecvError::Disconnectedon the IO response channel: if the worker thread dies unexpectedly the UI surfaces the failure and quits cleanly instead of freezing onis_busywith no way out. - Flush errors after destructive operations are no longer silently swallowed. A shared
flush_or_errorhelper is wired intoWriteFile,CopyDir,ExtractXiso,ConvertXisoToGod,Mkdir,Delete,Rename,Cleanup, andFlush— a failed FAT commit can no longer be reported as "uploaded successfully." - Single-delete confirmation captures the selection at prompt time via a new
pending_deletefield rather than re-reading the current selection at Enter — closes a race where a backgroundListDirrefresh could re-sort entries mid-confirmation and re-target the delete. - Replaced prompt-string
starts_withdispatch inhandle_input_keywith an exhaustivematchonInputMode, so every input mode has a single, type-checked completion handler.
Testing / maintenance
- Pinned
HashListsemantics with direct fixed-byte tests:readpreserves offsets and zero gaps,writeemits exactly 4 KiB, andHashList::new().digest()matchessha1(b"\0" * 4096)byte-for-byte. - Rewrote the GoD MHT back-chain test to seed via
HashList::read(notadd_hash) and assert against literal SHA-1 byte arrays for a three-part conversion, so byte-order or padding regressions indigest()and propagation bugs in the back-chain surface immediately. - Added a
write_file_in_placeFAT-flush regression test that injects post-flush write failures through a new file-backedFailingWriteFilewrapper, reopens the image, and asserts the chain extension survived the failure. - Added FAT16/FAT32 boundary tests at exactly
FAT16_CLUSTER_THRESHOLDandTHRESHOLD - 1—fat_type_for_cluster_estimateextracted to a private module function so the predicate is unit-testable in isolation. - Added direct
partitiontests covering both the zero-size error path and a planted-magic happy path ondetect_xbox_partitionsandscan_for_fatx. - Added an XBE certificate regression test that builds a synthetic header and verifies
title_idandversionare read from the correct offsets. - Added a CON header truncation test for
with_game_titleat oversized inputs. - Added a Cyrillic gamertag case ("Игрок 7") to the
looks_like_gamertagrules and apick_profile_nametest that rejects the overlong "Xbox 360 Dashboard" candidate. - Moved the volume stats saturation test inline into
volume.rsso theforce_stats_counts_for_testpublic helper could be deleted;fatxlib/tests/stats.rsremoved. - Removed redundant scaffolding tests:
fatxlib/tests/fixture_test.rs(three "didopen()succeed" checks already covered by every other integration test) and fiveoptimization.rstests that either re-tested already-covered invariants (test_bitmap_consistent_after_allocations,test_bitmap_consistent_after_free_chain,test_flush_after_no_changes_is_noop) or labeled themselves alignment tests while only exercising file-backed (non-aligned) I/O (test_default_alignment_works,test_read_write_at_various_offsets). tests/cli_integration::test_scan_imagenow asserts the command actually succeeded instead of discarding the exit code.
What's Changed
Full Changelog: v1.2.0...v1.2.1
v1.2.0
ISO / disc-image support
- Added
xtafkit extractfor streaming Xbox / Xbox 360 XISO contents to a local directory, with$SystemUpdateskipped by default. Supports--keep-systemupdateto override and--dry-runto preview file list + byte totals without writing. - Added
xtafkit godfor XISO → Games-on-Demand conversion. Default trim iscompact;preserve-layoutandnonestay available for debugging and compatibility. Game-title slot in the CON header auto-fills from the bundled catalog; pass--game-title TITLEto override. - TUI upload (
u) now sniffs every local file and, on XISO detection, prompts e(X)tract / (G)oD / (R)aw / Esc. Default flips by cwd context: inside/Content/<XUID>/defaults to GoD (BC playback target); everywhere else defaults to Extract (alt-dashboard target). - Extract destination folder name is resolved from the catalog when the title is known —
disc1.isowith TitleID4D5307E6extracts asHalo 3/rather thandisc1/. Falls back to the file stem on catalog miss. Names are sanitized for FATX (illegal chars replaced with-, runs of whitespace collapsed, truncated to 42 bytes). - Introduced a shared
fatxlib::isonamespace for image reading, manifest planning, compact repacking, and GoD conversion. - Reworked compact GoD conversion to stream a virtual dense XDVDFS layout instead of staging a temporary ISO on disk — peak local disk usage during conversion is zero.
- Centralized ISO filtering and planning so extract, compact trim, and dry-run reporting share the same manifest.
- Removed the old public
fatxlib::xisoandfatxlib::iso2godentry points in favor offatxlib::iso::{image,manifest,compact,god}. - Refactored GoD conversion to share its engine between host-filesystem and FATX-volume targets via an internal
GodSinktrait — onerun_conversionloop, two sink implementations.
Performance
- Fixed a double-I/O bug in
write_part: the upstream implementation read each subpart, hashed it, thenseek_relatived back and re-read it viaio::copyto write the part file. Now writes from the buffer it already has, halving I/O on the hot path (~33 % wall-time reduction on large ISOs). - 1 MiB
BufReaderon the source ISO during the metadata pre-pass cuts syscall tax on multi-GiB inputs. - Streaming variant of GoD conversion to FATX (
convert_iso_to_fatx) builds each part in a reused ~163 MiB buffer and streams straight into the volume — no local staging.
TUI / quality of life
- Mid-conversion
Esccancels GoD conversion cleanly (checked between parts and between MHT-chain steps); no partial silent failures. - Per-part byte-level progress with MiB/s throughput, rate-limited to ~200 ms intervals.
- Upload prompt no longer prefills with the last-used path — always starts blank.
- TUI extract worker skips
$SystemUpdateand surfaces the skip count + bytes in the completion message.
Library API additions
fatxlib::iso::image::XisoImage::title_info()parses the embeddedDefault.xex/default.xbeand returns the title's execution info. Used by catalog name resolution and by the GoD conversion pipeline.fatxlib::executable(top-level module) holdsTitleInfo/TitleExecutionInfoand the XEX/XBE parsers — shared betweeniso::imageandiso::god.fatxlib::volume::FatxVolume::create_file_from_readerstreams a file into FATX cluster-by-cluster from anyReadsource, capping working-set at one cluster regardless of total file size.
Full Changelog: v1.1.0...v1.2.0
v1.1.0
First release under the xtafkit name. Forked from joshuareisbord/fatx-rs; the FATX/XTAF filesystem core traces back to that work. Everything below was added or rewritten in the fork.
Project identity
- Crate + binary renamed to
xtafkit(wasfatx-cli+fatx). Library cratefatxlibretained. - NOTICE rewritten with dual attribution (xtafkit + upstream fatx-rs), both Apache 2.0.
- Cache file paths migrated from
~/.config/fatx-rs/to~/.config/xtafkit/. - Devcontainer name + workdir, githook header, integration-test header brought in line with the new name.
Pre-fork work carried in
- NFS performance: extracted common NFS flushing/cache behavior to reduce redundant disk hits on read paths.
fatx copydirectory semantics +create_filedata integrity: regression tests added first, then the fix; plus atype_complexityclippy cleanup.- macOS metadata cleanup, guided mount/browse, dry-run safety for destructive commands, TUI cleanup, ANSI color compatibility.
Title resolution & folder display
- Compiled-in title catalog of ~5,500 entries, merged at build time from two community sources: AdrianCassar's Xbox 360 gist and jeltaqq's Original Xbox list.
- Smart conflict normalization (whitespace, subtitle markers, etc.). Conflict report written to
target/<…>/title_conflicts.txt(gitignored), not the build log. - Slot-aware folder display in
fatxlib::display:Xuid/TitleId/ContentType/StfsFile/File. Format:<name> [<raw>], with raw case preserved. - Content-type folder labels from the free60 STFS spec.
- All-zeros XUID labeled
Shared. - TUI keybinding cleanup:
mis mkdir,→enters folders (mirroring←for go-up), top-of-file keymap comment regenerated.
On-demand STFS + profile gamertag extraction
- STFS header parser (
CON/LIVE/PIRS) infatxlib::stfs. - On-demand title resolution: when the catalog misses, parse the STFS header of a file inside the unresolved title folder.
- Per-file STFS resolution for Arcade (
0x000D0000), XNA (0x000E0000), Marketplace (0x00000002), and Installer (0x000B0000) content types. - Profile gamertag extraction: decrypts the embedded Account file (ARC4 + HMAC-SHA1) using the public PROD + OTHER keys to recover the real gamertag from profile XUID folders.
- TUI:
Rkeybinding (slot-aware resolve, dispatches to title-resolve / bulk-scan / single-file),?marker on unresolvable entries, sort toggles(by name ⇄ by ID, with bracket-order flip). - Three persistent caches at
~/.config/xtafkit/:user_titles.txt,user_files.txt,user_profiles.txt. Plain text, human-editable, self-healing on load. - Diagnostic helper:
cargo run -p fatxlib --example check_profile -- <file>to inspect gamertag extraction against a raw STFS file.
Scope simplification & TUI-first
- Removed the NFS Finder-mount server entirely, along with the catastrophic stale-mount deadlock that haunted it.
- Dropped 10+ CLI subcommands now subsumed by the TUI:
read,write,mkdir,rm,rmr,rename,copy,info,hexdump,cleanup,mount,shell. - Dropped the interactive numbered-menu shell mode; no-args entry point now lands you in the TUI via guided picker.
- New CLI surface:
browse,ls,scan,mkimage,resolve(5 subcommands, down from 15+). - TTY-aware
lsoutput: text when stdout is a terminal, JSON when piped or redirected. New--text/--jsonflags force either mode. - Auto-pick single disk in the guided no-args flow — if exactly one external disk is detected, skip the picker and use it directly.
- ~3,000 LOC removed, 10 runtime dependencies dropped (including
nfsserve,tokio,async-trait,parking_lot,quick_cache,bytes,ctrlc,core-foundation,core-foundation-sys,io-kit-sys,mach2).
Toolchain & dependencies
- Rust edition bumped from 2021 to 2024.
- Major dependency bumps:
rand0.8 → 0.10,nix0.29 → 0.31,phf0.11 → 0.13,phf_codegen0.11 → 0.13,hmac0.12 → 0.13,sha10.10 → 0.11. Smaller bumps viacargo update.
Infrastructure
- macOS release pipeline: GitHub Actions builds
x86_64-apple-darwinandaarch64-apple-darwinbinaries on tag push, generates a draft release whose notes are sourced from this changelog.
Full Changelog: https://github.com/rdmrocha/xtafkit/commits/v1.1.0