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.
§Tracing
The library emits structured tracing events at trace!, debug!, and
warn! levels. No subscriber is configured here — configure output in your
application binary (or in gaveloc’s launcher binary).
Re-exports§
pub use apply::Apply;pub use apply::ApplyContext;pub use chunk::Chunk;pub use chunk::ZiPatchReader;pub use error::ZiPatchError;
Modules§
- apply
- Filesystem application of parsed chunks (
Apply,ApplyContext). Filesystem application of parsedZiPatchchunks. - chunk
- Wire-format chunk types and the
ZiPatchReaderiterator. - error
- Error type returned by parsing and applying (
ZiPatchError).
Enums§
- Platform
- Target platform for
SqPackfile path resolution.
Type Aliases§
- Result
- Crate-wide
Resultalias parameterised overZiPatchError.