zipatch-rs 1.7.0

Parser for FFXIV ZiPatch patch files
Documentation
# zipatch-rs

[![Crates.io](https://img.shields.io/crates/v/zipatch-rs.svg)](https://crates.io/crates/zipatch-rs)
[![docs.rs](https://img.shields.io/docsrs/zipatch-rs)](https://docs.rs/zipatch-rs)
[![CI](https://github.com/reh3502/zipatch-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/reh3502/zipatch-rs/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/reh3502/zipatch-rs/graph/badge.svg)](https://codecov.io/gh/reh3502/zipatch-rs)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![MSRV: 1.85](https://img.shields.io/badge/rustc-1.85+-orange.svg)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)

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

```toml
[dependencies]
zipatch-rs = "1.6"
```

Apply a patch to an existing install:

```rust
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:

```rust
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](https://docs.rs/zipatch-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_patch`]https://docs.rs/zipatch-rs/latest/zipatch_rs/apply/struct.ApplyConfig.html#method.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.

- **Indexed**  [`PlanBuilder`]https://docs.rs/zipatch-rs/latest/zipatch_rs/index/struct.PlanBuilder.html
  folds one or many patches into a deduplicated, target-major
  [`Plan`]https://docs.rs/zipatch-rs/latest/zipatch_rs/index/struct.Plan.html,
  then
  [`IndexApplier::execute`]https://docs.rs/zipatch-rs/latest/zipatch_rs/index/struct.IndexApplier.html#method.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`]https://docs.rs/zipatch-rs/latest/zipatch_rs/index/struct.RepairManifest.html.

| 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.

```rust
// Sequential: one patch, one pass.
zipatch_rs::apply_patch_file("patch.patch", "/opt/ffxiv/game")?;
```

```rust
// 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 verification**`HashVerifier` 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 + cancellation**`ApplyObserver` fires per chunk; `should_cancel` is polled mid-block on long DEFLATE streams.
- **Optional CRC32 verification**`Plan::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`]https://github.com/trifectatechfoundation/zlib-rs port. Mutually exclusive with `zlib-ng`. |
| `zlib-ng` | off | Switches the decompress path to the C [`zlib-ng`]https://github.com/zlib-ng/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]LICENSE.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). Security issues go through the [private advisory form](https://github.com/reh3502/zipatch-rs/security/advisories/new); see [SECURITY.md](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 |