Skip to main content

zipatch_rs/chunk/sqpk/
add_data.rs

1use binrw::BinRead;
2use std::io::Cursor;
3
4use super::SqpackFile;
5
6// SQPK 'A' body layout: 3 pad + 2 main_id + 2 sub_id + 4 file_id
7//                      + 4 block_offset_raw + 4 data_bytes_raw + 4 block_delete_number_raw = 23
8const SQPK_ADDDATA_HEADER_SIZE: u64 = 23;
9
10/// SQPK `A` command body: write `data` into a `.dat` file at `block_offset`,
11/// then zero `block_delete_number` bytes after it.
12#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
13#[br(big)]
14pub struct SqpkAddData {
15    /// `SqPack` file the write targets.
16    #[br(pad_before = 3)]
17    pub target_file: SqpackFile,
18    /// Destination offset within the `.dat` file (raw `u32` shifted left 7 bits).
19    #[br(map = |raw: u32| (raw as u64) << 7)]
20    pub block_offset: u64,
21    /// Length of the inline `data` payload (raw `u32` shifted left 7 bits).
22    #[br(map = |raw: u32| (raw as u64) << 7)]
23    pub data_bytes: u64,
24    /// Number of bytes to zero immediately after the payload write.
25    #[br(map = |raw: u32| (raw as u64) << 7)]
26    pub block_delete_number: u64,
27    /// Inline payload of length `data_bytes`.
28    #[br(count = data_bytes)]
29    pub data: Vec<u8>,
30}
31
32impl SqpkAddData {
33    /// Byte offset of the `data` field within the SQPK command body slice.
34    ///
35    /// Add the chunk's absolute file position to get the patch-file offset
36    /// needed for `IndexedZiPatch` random-access reads.
37    pub const DATA_SOURCE_OFFSET: u64 = SQPK_ADDDATA_HEADER_SIZE;
38}
39
40pub(crate) fn parse(body: &[u8]) -> crate::Result<SqpkAddData> {
41    Ok(SqpkAddData::read_be(&mut Cursor::new(body))?)
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn parses_add_data() {
50        let mut body = Vec::new();
51        body.extend_from_slice(&[0u8; 3]); // alignment
52        body.extend_from_slice(&1u16.to_be_bytes()); // main_id
53        body.extend_from_slice(&2u16.to_be_bytes()); // sub_id
54        body.extend_from_slice(&3u32.to_be_bytes()); // file_id
55        body.extend_from_slice(&1u32.to_be_bytes()); // block_offset raw → 1 << 7 = 128
56        body.extend_from_slice(&1u32.to_be_bytes()); // data_bytes raw → 1 << 7 = 128
57        body.extend_from_slice(&0u32.to_be_bytes()); // block_delete_number raw → 0
58        body.extend_from_slice(&[0xABu8; 128]); // data blob
59
60        let cmd = parse(&body).unwrap();
61        assert_eq!(cmd.target_file.main_id, 1);
62        assert_eq!(cmd.target_file.sub_id, 2);
63        assert_eq!(cmd.target_file.file_id, 3);
64        assert_eq!(cmd.block_offset, 128);
65        assert_eq!(cmd.data_bytes, 128);
66        assert_eq!(cmd.block_delete_number, 0);
67        assert_eq!(cmd.data.len(), 128);
68        assert!(cmd.data.iter().all(|&b| b == 0xAB));
69        assert_eq!(SqpkAddData::DATA_SOURCE_OFFSET, 23);
70    }
71}