zipatch-rs 1.6.0

Parser for FFXIV ZiPatch patch files
Documentation
//! Apply a .patch file to a target install directory.
//!
//! Demonstrates the full apply pipeline:
//! [`ApplyConfig::new`] -> [`ApplyConfig::with_platform`] ->
//! [`ApplyConfig::with_ignore_missing`] -> [`open_patch`] ->
//! `apply_patch`. Initialises `tracing-subscriber` so the spans/events
//! emitted by the library are printed to stderr at INFO level (or
//! whatever `RUST_LOG` requests).
//!
//! Usage:
//!     cargo run --example apply_to_dir -- path/to/file.patch /path/to/install
//!     RUST_LOG=zipatch_rs=debug cargo run --example apply_to_dir -- file.patch /tmp/ffxiv

#![allow(clippy::doc_markdown)]

use std::path::PathBuf;
use std::process::ExitCode;

use tracing_subscriber::EnvFilter;
use zipatch_rs::{ApplyConfig, Platform, open_patch};

fn main() -> ExitCode {
    // Human-readable log output to stderr. The library uses tracing
    // throughout; without a subscriber, those events go nowhere.
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .with_writer(std::io::stderr)
        .init();

    let mut args = std::env::args_os().skip(1);
    let Some(patch_path) = args.next().map(PathBuf::from) else {
        eprintln!("usage: apply_to_dir <patch_file> <install_dir>");
        return ExitCode::from(2);
    };
    let Some(install_dir) = args.next().map(PathBuf::from) else {
        eprintln!("usage: apply_to_dir <patch_file> <install_dir>");
        return ExitCode::from(2);
    };

    if !install_dir.is_dir() {
        eprintln!("install dir does not exist: {}", install_dir.display());
        return ExitCode::FAILURE;
    }

    let ctx = ApplyConfig::new(&install_dir)
        // SqpkTargetInfo chunks in the stream will override this, but
        // setting an explicit default makes intent clear.
        .with_platform(Platform::Win32)
        // Tolerate missing files. Helpful when re-applying a patch that
        // partially succeeded, or against a non-pristine install.
        .with_ignore_missing(true);

    let reader = match open_patch(&patch_path) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("failed to open {}: {e}", patch_path.display());
            return ExitCode::FAILURE;
        }
    };

    match ctx.apply_patch(reader) {
        Ok(()) => {
            println!(
                "applied {} to {}",
                patch_path.display(),
                install_dir.display()
            );
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("apply failed: {e}");
            ExitCode::FAILURE
        }
    }
}