zipatch-rs
A Rust library for parsing and applying FFXIV ZiPatch files (.patch).
ZiPatch is the binary patch format Square Enix ships Final Fantasy XIV game updates in. A patch is a chunked binary log of file additions, deletions, SqPack .dat/.index mutations, and binary diffs that an applier walks against an existing game installation to bring it to a new version.
Quick start
[]
= "1.6"
Apply a patch to an existing install:
apply_patch_file?;
For consumers that need progress observers, a non-default platform, custom
checkpoint sinks, or to reuse the same ApplyConfig across many patches,
use the two-step form:
use ;
let reader = open_patch?;
let ctx = new;
ctx.apply_patch?;
See docs.rs for the indexed-apply path (Plan / PlanVerifier / IndexApplier), post-apply hash verification (HashVerifier), observer hooks, and resume entry points.
Sequential vs Indexed apply
The crate ships two apply pipelines. They are not "one is better" — they answer different questions. Sequential is the default; reach for indexed when you need what it offers.
-
Sequential —
ApplyConfig::apply_patchwalks a single patch in chunk order and writes each chunk as it parses it. One forward pass over the patch, bounded memory, minimum latency from "start" to "first byte on disk". Works against anystd::io::Readsource. -
Indexed —
PlanBuilderfolds one or many patches into a deduplicated, target-majorPlan, thenIndexApplier::executereplays it. The plan is pure data: inspect it, persist it (with theserdefeature), populate expected CRC32s, verify the install against it, or run a targeted repair from aRepairManifest.
| Need | Pipeline |
|---|---|
| Apply one patch to an install, in order, now | Sequential |
| Minimum latency to first write; bounded streaming memory | Sequential |
| Patch source is forward-only (no seeks) | Sequential |
| Apply a chain of N patches and dedupe writes superseded mid-chain | Indexed |
| Pre-validate the chain against the install before touching a byte | Indexed |
| Pre-compute expected CRC32s and verify the install against them | Indexed |
Produce a RepairManifest of drifted regions |
Indexed |
| Persist a structured description of pending work for later replay | Indexed |
Costs to weigh: the indexed pipeline does two passes over the patch source
(plan-build, then apply) and holds the plan in memory. The source must
support random access, so the indexed entry points take a PatchSource
(typically FilePatchSource) rather than a plain Read. The sequential
pipeline does a single forward pass and never holds more than one chunk in
flight.
Migration is incremental: starting sequential and growing to indexed later
is a supported path. The ApplyConfig knobs (with_observer,
with_cancel_token, with_checkpoint_sink, with_platform, with_vfs)
all carry across to IndexApplier with the same names and semantics.
// Sequential: one patch, one pass.
apply_patch_file?;
// Indexed: build a plan over a chain, then apply it.
use ;
use FilePatchSource;
let mut builder = new;
builder.add_patch?;
builder.add_patch?;
let plan = builder.finish;
let source = open_chain?;
new.execute?;
Features
- Streaming chunk parser — bounded memory, one chunk in flight at a time.
- Sequential apply — chunk-by-chunk, in-place writes to the install tree.
- Indexed verify-and-repair — build a
Plandescribing every byte of every target, verify an install against it, and re-apply only the regions that drift viaRepairManifest. - Post-apply hash verification —
HashVerifierreads the resulting.index/.datfiles back from disk and compares against caller-supplied SHA1s (whole-file or 50 MiB block-mode). Catches silent on-disk corruption the per-chunk CRC32s can't see. - Parallel verify — both
HashVerifier::executeandindex::PlanVerifier::executefan out across rayon's global pool. SHA1 hashing scales near-linearly with cores. - Multi-patch chains — a single
Planspans the patch chain end-to-end; mid-chainRemoveAll/DeleteFile/AddFile @ 0retroactively drop superseded regions. - Resumable apply — checkpoint emission at every chunk and every DEFLATE block inside
SqpkFile::AddFile; resume entry points fast-forward to the recorded position. - Progress + cancellation —
ApplyObserverfires per chunk;should_cancelis polled mid-block on long DEFLATE streams. - Optional CRC32 verification —
Plan::with_crc32populates expected CRCs so the verifier catches single-byte damage the size-only policy would miss. - Alternate DEFLATE backends — opt into
zlib-rsorzlib-ngfor faster DEFLATE; the default is pure-Rustminiz_oxidewith no system dependencies. - Fuzz harness — eight cargo-fuzz targets cover the full parse + apply surface; weekly cron in CI.
Cargo features
| Feature | Default | Purpose |
|---|---|---|
serde |
off | Serialize / Deserialize on Plan, RepairManifest, the Checkpoint types, and the index data model. The crate does not pick an on-disk format; you bring your own (bincode, rmp-serde, JSON, etc.). |
zlib-rs |
off | Switches the SqpkFile::AddFile decompress path to the pure-Rust zlib-rs port. Mutually exclusive with zlib-ng. |
zlib-ng |
off | Switches the decompress path to the C zlib-ng fork. Requires cmake and a C compiler at build time. |
test-utils |
off | Exposes a zipatch_rs::test_utils module of chunk-framing fixtures used by the test suite and benchmarks. Not part of the stable public API. |
Scope
zipatch-rs is a library, not a launcher. It does no network I/O — patch fetch, authentication, version negotiation, and progress UI all live somewhere else. If all you want to do is apply a .patch file you already have on disk to an install you already have on disk, this crate alone is enough.
Stability
- MSRV: Rust 1.85 (the minimum that supports edition 2024). Bumps land as a major version bump.
- SemVer: public API changes follow semantic versioning. The
test_utilsmodule is explicitly excluded. - License: MIT — see LICENSE.
Contributing
See CONTRIBUTING.md. Security issues go through the private advisory form; see SECURITY.md.
Supported chunks
Chunk types
| Tag | Name | Notes |
|---|---|---|
FHDR |
File Header | V2 and V3 |
APLY |
Apply Option | ignore-missing, ignore-old-mismatch flags |
APFS |
Apply Free Space | |
ADIR |
Add Directory | |
DELD |
Delete Directory | |
SQPK |
SQPK command block | See sub-commands below |
EOF_ |
End of File |
SQPK sub-commands
| Command | Name | Notes |
|---|---|---|
A |
AddData | Writes data into a .dat file at a block offset |
D |
DeleteData | Zeros a block range in a .dat file |
E |
ExpandData | Expands a .dat file with an empty block |
H |
Header | Writes version or index headers into .dat/.index files |
F |
File | AddFile, DeleteFile, RemoveAll, MakeDirTree; supports DEFLATE-compressed blocks |
T |
TargetInfo | Platform (Win32, PS3, PS4), region, debug flag |
I |
Index | Parsed; no-op at apply time |
X |
PatchInfo | Parsed; no-op at apply time |