Expand description
Parser and applier for FFXIV ZiPatch (.patch) binary files.
zipatch-rs decodes the binary patch format that Square Enix ships for
Final Fantasy XIV and writes the decoded changes to a local game installation.
The library never touches the network — it operates entirely on byte streams
you supply.
§Architecture
The crate is split into three layers that share types but are otherwise independent:
§Layer 1 — I/O primitives (reader)
reader::ReadExt is a crate-internal extension trait that adds typed
big- and little-endian reads on top of std::io::Read. It is not part
of the public API; the parsing layer uses it exclusively.
§Layer 2 — Parsing (chunk)
ZiPatchReader is an Iterator over Chunk values. Construct it
from any std::io::Read source (a std::fs::File, a
std::io::Cursor<Vec<u8>>, a network stream, …). It validates the
12-byte file magic on construction, then yields one Chunk per
Iterator::next call until it sees the EOF_ terminator or hits an
error.
Nothing in the parsing layer allocates file handles, stats paths, or
performs I/O against the install tree. Parse-only users can consume
ZiPatchReader without ever importing apply.
§Layer 3 — Applying (apply)
The Apply trait bridges parsing and application: every Chunk
variant implements it, and each implementation writes the patch change to
disk via an ApplyContext. ApplyContext holds the install root, the
target Platform, behavioural flags, and an internal file-handle cache
that avoids re-opening the same .dat file for every chunk.
§Quick start
The most common usage: open a patch file, build a context, apply every chunk in stream order.
use std::fs::File;
use zipatch_rs::{ApplyContext, ZiPatchReader};
let patch_file = File::open("H2017.07.11.0000.0000a.patch").unwrap();
let mut ctx = ApplyContext::new("/opt/ffxiv/game");
ZiPatchReader::new(patch_file)
.unwrap()
.apply_to(&mut ctx)
.unwrap();§Inspecting a patch without applying it
Iterate the reader directly to inspect chunks without touching the filesystem:
use zipatch_rs::{Chunk, ZiPatchReader};
use std::fs::File;
let reader = ZiPatchReader::new(File::open("patch.patch").unwrap()).unwrap();
for chunk in reader {
match chunk.unwrap() {
Chunk::FileHeader(h) => println!("patch version: {:?}", h),
Chunk::AddDirectory(d) => println!("mkdir {}", d.name),
Chunk::Sqpk(cmd) => println!("sqpk: {cmd:?}"),
_ => {}
}
}§In-memory doctest
The following example builds a minimal well-formed patch in memory — magic
header, one ADIR chunk (which creates a directory), and an EOF_
terminator — then applies it to a temporary directory. This mirrors the
technique used in the crate’s own unit tests.
use std::io::Cursor;
use zipatch_rs::{ApplyContext, Chunk, ZiPatchReader};
// ZiPatch file magic: \x91ZIPATCH\r\n\x1a\n
const MAGIC: [u8; 12] = [
0x91, 0x5A, 0x49, 0x50, 0x41, 0x54, 0x43, 0x48,
0x0D, 0x0A, 0x1A, 0x0A,
];
/// Wrap `tag + body` into a length-prefixed, CRC32-verified chunk frame.
fn make_chunk(tag: &[u8; 4], body: &[u8]) -> Vec<u8> {
// CRC is computed over tag ++ body (NOT including the leading body_len).
let mut crc_input = Vec::new();
crc_input.extend_from_slice(tag);
crc_input.extend_from_slice(body);
let crc = crc32fast::hash(&crc_input);
let mut out = Vec::new();
out.extend_from_slice(&(body.len() as u32).to_be_bytes()); // body_len: u32 BE
out.extend_from_slice(tag); // tag: 4 bytes
out.extend_from_slice(body); // body: body_len bytes
out.extend_from_slice(&crc.to_be_bytes()); // crc32: u32 BE
out
}
// ADIR body: big-endian u32 name length followed by the name bytes.
let mut adir_body = Vec::new();
adir_body.extend_from_slice(&7u32.to_be_bytes()); // name_len
adir_body.extend_from_slice(b"created"); // name
// Assemble the full patch stream.
let mut patch = Vec::new();
patch.extend_from_slice(&MAGIC);
patch.extend_from_slice(&make_chunk(b"ADIR", &adir_body));
patch.extend_from_slice(&make_chunk(b"EOF_", &[]));
// Apply to a temporary directory.
let tmp = tempfile::tempdir().unwrap();
let mut ctx = ApplyContext::new(tmp.path());
ZiPatchReader::new(Cursor::new(patch))
.unwrap()
.apply_to(&mut ctx)
.unwrap();
assert!(tmp.path().join("created").is_dir());§Error handling
Every fallible operation returns Result<T>, which is an alias for
std::result::Result<T, ZiPatchError>. Parse errors and apply
errors share the same type so callers need only one error arm.
§Progress and cancellation
ApplyContext::with_observer installs an ApplyObserver that is
called after each chunk applies (with the chunk index, tag, and running
byte count from ZiPatchReader::bytes_read) and polled inside long-
running chunks for cancellation. Returning
std::ops::ControlFlow::Break from a per-chunk callback, or true
from ApplyObserver::should_cancel, aborts the apply call with
ZiPatchError::Cancelled. Parsing-only consumers and existing
apply_to callers that never install an
observer pay nothing — the default is a no-op.
§Tracing
The library emits structured tracing events and spans across the
parse, plan-build, apply, and verify entry points. Levels follow a
“one event per logical operation at info!, per-target/per-fs-op at
debug!, per-region/byte-level work at trace!” cadence. The top-level
spans (apply_patch, apply_plan, build_plan_patch, compute_crc32,
verify_plan) are emitted at the info level so a subscriber configured
at the default level can scope output via span filtering, while per-target
sub-spans (apply_target) emit at debug. Recoverable anomalies — stale
manifest entries, unknown platform IDs, missing-but-ignored files — fire
warn!; errors are returned via ZiPatchError rather than logged. No
subscriber is configured here — that is the consumer’s responsibility.
Re-exports§
pub use apply::Apply;pub use apply::ApplyContext;pub use apply::ApplyObserver;pub use apply::Checkpoint;pub use apply::CheckpointPolicy;pub use apply::CheckpointSink;pub use apply::ChunkEvent;pub use apply::InFlightAddFile;pub use apply::IndexedCheckpoint;pub use apply::NoopCheckpointSink;pub use apply::NoopObserver;pub use apply::SequentialCheckpoint;pub use chunk::Chunk;pub use chunk::ZiPatchReader;pub use error::ZiPatchError;pub use index::IndexApplier;pub use index::Plan;pub use index::PlanBuilder;pub use index::Verifier;
Modules§
- apply
- Filesystem application of parsed chunks (
Apply,ApplyContext). Filesystem application of parsedZiPatchchunks. - chunk
- Wire-format chunk types and the
ZiPatchReaderiterator. Wire-format chunk types and theZiPatchReaderiterator. - error
- Error type returned by parsing and applying (
ZiPatchError). - index
- Indexed-apply plan model and single-patch builder
(
Plan,PlanBuilder). Pure-data indexed-apply plan model, single-patch builder, applier, and verifier.
Enums§
- Platform
- Target platform for
SqPackfile path resolution.
Type Aliases§
- Result
- Crate-wide
Resultalias parameterised overZiPatchError.