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}