Skip to main content

zipatch_rs/chunk/
aply.rs

1use binrw::{BinRead, BinResult, Endian};
2
3/// Which apply-time flag an `APLY` chunk toggles.
4///
5/// The discriminant values (1 and 2) are fixed by the wire format.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ApplyOptionKind {
8    /// Wire value `1`. When set to `true`, missing files and directories
9    /// encountered during apply are silently skipped rather than causing an
10    /// error. Corresponds to [`crate::ApplySession::ignore_missing`].
11    IgnoreMissing,
12    /// Wire value `2`. When set to `true`, mismatches between the patch's
13    /// expected "old" file content and the actual on-disk content are
14    /// silently ignored. Corresponds to
15    /// [`crate::ApplySession::ignore_old_mismatch`].
16    IgnoreOldMismatch,
17}
18
19/// `binrw` parse helper: read a `u32 BE` and map it to an [`ApplyOptionKind`].
20///
21/// Returns a `binrw::Error::Custom` for any value other than `1` or `2`.
22fn read_apply_option_kind<R: std::io::Read + std::io::Seek>(
23    reader: &mut R,
24    endian: Endian,
25    (): (),
26) -> BinResult<ApplyOptionKind> {
27    let raw = <u32 as BinRead>::read_options(reader, endian, ())?;
28    match raw {
29        1 => Ok(ApplyOptionKind::IgnoreMissing),
30        2 => Ok(ApplyOptionKind::IgnoreOldMismatch),
31        _ => Err(binrw::Error::Custom {
32            pos: 0,
33            err: Box::new(std::io::Error::new(
34                std::io::ErrorKind::InvalidData,
35                "unknown ApplyOption kind",
36            )),
37        }),
38    }
39}
40
41/// `APLY` chunk: sets or clears a boolean flag on the [`crate::ApplyConfig`].
42///
43/// Each `APLY` chunk carries one flag selector ([`ApplyOptionKind`]) and one
44/// boolean value. When applied, the corresponding field on [`crate::ApplyConfig`]
45/// is updated. In all observed FFXIV patch files both flags are set to `false`
46/// — the chunk exists to allow SE's tooling to override the defaults without
47/// hard-coding them.
48///
49/// # Wire format
50///
51/// ```text
52/// [kind: u32 BE] [padding: 4 bytes] [value: u32 BE, non-zero = true]
53/// ```
54///
55/// The 4-byte padding between `kind` and `value` is always `0x00000004` in
56/// observed files, but the reference implementation discards it without
57/// checking.
58///
59/// # Errors
60///
61/// Parsing fails with [`crate::ParseError::Decode`] if:
62/// - the `kind` field is not `1` or `2` (unrecognised option), or
63/// - the body is too short to contain all three 4-byte fields.
64#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
65#[br(big)]
66pub struct ApplyOption {
67    /// Which [`crate::ApplyConfig`] flag this chunk targets.
68    ///
69    /// Encoded as a `u32` big-endian on the wire; mapped to [`ApplyOptionKind`]
70    /// during parsing.
71    #[br(parse_with = read_apply_option_kind)]
72    pub kind: ApplyOptionKind,
73    /// New value for the targeted flag.
74    ///
75    /// On the wire: a `u32 BE` preceded by 4 bytes of padding (`pad_before = 4`).
76    /// Any non-zero value is treated as `true`.
77    #[br(pad_before = 4, map = |x: u32| x != 0)]
78    pub value: bool,
79}
80
81pub(crate) fn parse(body: &[u8]) -> crate::ParseResult<ApplyOption> {
82    super::util::parse_be(body)
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    fn make_body(kind: u32, value: u32) -> Vec<u8> {
90        let mut body = Vec::new();
91        body.extend_from_slice(&kind.to_be_bytes());
92        body.extend_from_slice(&[0u8; 4]); // padding
93        body.extend_from_slice(&value.to_be_bytes());
94        body
95    }
96
97    #[test]
98    fn parses_apply_option_ignore_missing() {
99        let cmd = parse(&make_body(1, 0)).unwrap();
100        assert_eq!(cmd.kind, ApplyOptionKind::IgnoreMissing);
101        assert!(!cmd.value);
102    }
103
104    #[test]
105    fn parses_apply_option_ignore_old_mismatch() {
106        let cmd = parse(&make_body(2, 1)).unwrap();
107        assert_eq!(cmd.kind, ApplyOptionKind::IgnoreOldMismatch);
108        assert!(cmd.value);
109    }
110
111    #[test]
112    fn rejects_invalid_apply_option_kind() {
113        assert!(parse(&make_body(0, 0)).is_err());
114        assert!(parse(&make_body(3, 0)).is_err());
115    }
116}