zipatch-rs 1.0.0

Parser for FFXIV ZiPatch patch files
Documentation
use binrw::{BinRead, BinResult, Endian};

/// Which apply-time flag a `APLY` chunk toggles.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApplyOptionKind {
    /// Set [`crate::ApplyContext::ignore_missing`].
    IgnoreMissing,
    /// Set [`crate::ApplyContext::ignore_old_mismatch`].
    IgnoreOldMismatch,
}

fn read_apply_option_kind<R: std::io::Read + std::io::Seek>(
    reader: &mut R,
    endian: Endian,
    (): (),
) -> BinResult<ApplyOptionKind> {
    let raw = <u32 as BinRead>::read_options(reader, endian, ())?;
    match raw {
        1 => Ok(ApplyOptionKind::IgnoreMissing),
        2 => Ok(ApplyOptionKind::IgnoreOldMismatch),
        _ => Err(binrw::Error::Custom {
            pos: 0,
            err: Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "unknown ApplyOption kind",
            )),
        }),
    }
}

/// `APLY` chunk: toggles a boolean flag on the [`crate::ApplyContext`].
#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
#[br(big)]
pub struct ApplyOption {
    /// Which flag this chunk targets.
    #[br(parse_with = read_apply_option_kind)]
    pub kind: ApplyOptionKind,
    /// New value of the targeted flag.
    #[br(pad_before = 4, map = |x: u32| x != 0)]
    pub value: bool,
}

pub(crate) fn parse(body: &[u8]) -> crate::Result<ApplyOption> {
    super::util::parse_be(body)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_body(kind: u32, value: u32) -> Vec<u8> {
        let mut body = Vec::new();
        body.extend_from_slice(&kind.to_be_bytes());
        body.extend_from_slice(&[0u8; 4]); // padding
        body.extend_from_slice(&value.to_be_bytes());
        body
    }

    #[test]
    fn parses_apply_option_ignore_missing() {
        let cmd = parse(&make_body(1, 0)).unwrap();
        assert_eq!(cmd.kind, ApplyOptionKind::IgnoreMissing);
        assert!(!cmd.value);
    }

    #[test]
    fn parses_apply_option_ignore_old_mismatch() {
        let cmd = parse(&make_body(2, 1)).unwrap();
        assert_eq!(cmd.kind, ApplyOptionKind::IgnoreOldMismatch);
        assert!(cmd.value);
    }

    #[test]
    fn rejects_invalid_apply_option_kind() {
        assert!(parse(&make_body(0, 0)).is_err());
        assert!(parse(&make_body(3, 0)).is_err());
    }
}