zipatch-rs 1.6.0

Parser for FFXIV ZiPatch patch files
Documentation

zipatch-rs

Crates.io docs.rs CI codecov License: MIT MSRV: 1.85

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

[dependencies]
zipatch-rs = "1.6"

Apply a patch to an existing install:

zipatch_rs::apply_patch_file(
    "D2024.05.31.0000.0000.patch",
    "/path/to/ffxiv/game",
)?;

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 zipatch_rs::{ApplyConfig, open_patch};

let reader = open_patch("D2024.05.31.0000.0000.patch")?;
let ctx = ApplyConfig::new("/path/to/ffxiv/game");
ctx.apply_patch(reader)?;

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.

  • SequentialApplyConfig::apply_patch walks 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 any std::io::Read source.

  • IndexedPlanBuilder folds one or many patches into a deduplicated, target-major Plan, then IndexApplier::execute replays it. The plan is pure data: inspect it, persist it (with the serde feature), populate expected CRC32s, verify the install against it, or run a targeted repair from a RepairManifest.

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.
zipatch_rs::apply_patch_file("patch.patch", "/opt/ffxiv/game")?;
// Indexed: build a plan over a chain, then apply it.
use zipatch_rs::{IndexApplier, PlanBuilder, open_patch};
use zipatch_rs::index::FilePatchSource;

let mut builder = PlanBuilder::new();
builder.add_patch("p1.patch", open_patch("p1.patch")?)?;
builder.add_patch("p2.patch", open_patch("p2.patch")?)?;
let plan = builder.finish();

let source = FilePatchSource::open_chain(["p1.patch", "p2.patch"])?;
IndexApplier::new(source, "/opt/ffxiv/game").execute(&plan)?;

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 Plan describing every byte of every target, verify an install against it, and re-apply only the regions that drift via RepairManifest.
  • Post-apply hash verificationHashVerifier reads the resulting .index / .dat files 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::execute and index::PlanVerifier::execute fan out across rayon's global pool. SHA1 hashing scales near-linearly with cores.
  • Multi-patch chains — a single Plan spans the patch chain end-to-end; mid-chain RemoveAll / DeleteFile / AddFile @ 0 retroactively 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 + cancellationApplyObserver fires per chunk; should_cancel is polled mid-block on long DEFLATE streams.
  • Optional CRC32 verificationPlan::with_crc32 populates expected CRCs so the verifier catches single-byte damage the size-only policy would miss.
  • Alternate DEFLATE backends — opt into zlib-rs or zlib-ng for faster DEFLATE; the default is pure-Rust miniz_oxide with 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_utils module 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