Skip to main content

zipatch_rs/chunk/
afsp.rs

1use binrw::BinRead;
2use std::io::Cursor;
3
4/// `APFS` chunk: free-space book-keeping; ignored at apply time.
5///
6/// This chunk was emitted by older versions of SE's patcher to signal how much
7/// free disk space the patch required. Modern patcher tooling (and all observed
8/// XIVARR+ patch files) never emit it. The two fields are preserved in the
9/// parsed struct for completeness, but the crate does not act on them.
10///
11/// # Wire format
12///
13/// ```text
14/// [unknown_a: i64 BE] [unknown_b: i64 BE]
15/// ```
16///
17/// Both fields are stored as signed 64-bit big-endian integers. No samples of
18/// this chunk in the wild have been found, so the exact semantics of the two
19/// values remain undocumented. The reference calls them `UnknownFieldA` and
20/// `UnknownFieldB`.
21///
22/// # Errors
23///
24/// Parsing fails with [`crate::ParseError::Decode`] if the body is
25/// shorter than 16 bytes (the two `i64` fields).
26#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
27#[br(big)]
28pub struct ApplyFreeSpace {
29    /// First 8-byte signed integer field; purpose unknown.
30    ///
31    /// Read as a `u64` big-endian on the wire and reinterpreted as `i64` via
32    /// a bitcast (`as i64`), matching the C# `ReadInt64BE` call in the
33    /// reference.
34    #[br(map = |x: u64| x as i64)]
35    pub unknown_a: i64,
36    /// Second 8-byte signed integer field; purpose unknown.
37    ///
38    /// Same encoding as [`ApplyFreeSpace::unknown_a`].
39    #[br(map = |x: u64| x as i64)]
40    pub unknown_b: i64,
41}
42
43pub(crate) fn parse(body: &[u8]) -> crate::ParseResult<ApplyFreeSpace> {
44    Ok(ApplyFreeSpace::read_be(&mut Cursor::new(body))?)
45}
46
47#[cfg(test)]
48mod tests {
49    use super::parse;
50
51    #[test]
52    fn parses_apply_free_space() {
53        let mut body = Vec::new();
54        body.extend_from_slice(&0x8000_0000_0000_0000_u64.to_be_bytes()); // high bit → i64::MIN
55        body.extend_from_slice(&1u64.to_be_bytes());
56        let cmd = parse(&body).unwrap();
57        assert_eq!(cmd.unknown_a, i64::MIN);
58        assert_eq!(cmd.unknown_b, 1);
59    }
60
61    #[test]
62    fn rejects_truncated_body() {
63        // 16-byte body needed (two u64); supply only 8 to trigger the `?` error arm.
64        assert!(parse(&[0u8; 8]).is_err());
65    }
66}