zipatch_rs/chunk/sqpk/
index.rs1use binrw::{BinRead, BinResult, Endian};
2use std::io::Cursor;
3
4use super::SqpackFile;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum IndexCommand {
9 Add,
11 Delete,
13}
14
15fn read_index_command<R: std::io::Read + std::io::Seek>(
16 reader: &mut R,
17 _: Endian,
18 (): (),
19) -> BinResult<IndexCommand> {
20 let byte = <u8 as BinRead>::read_options(reader, Endian::Big, ())?;
21 match byte {
22 b'A' => Ok(IndexCommand::Add),
23 b'D' => Ok(IndexCommand::Delete),
24 _ => Err(binrw::Error::Custom {
25 pos: 0,
26 err: Box::new(std::io::Error::new(
27 std::io::ErrorKind::InvalidData,
28 "unknown IndexCommand",
29 )),
30 }),
31 }
32}
33
34#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
36#[br(big)]
37pub struct SqpkIndex {
38 #[br(parse_with = read_index_command)]
40 pub command: IndexCommand,
41 #[br(map = |x: u8| x != 0)]
43 pub is_synonym: bool,
44 #[br(pad_before = 1)]
46 pub target_file: SqpackFile,
47 pub file_hash: u64,
49 pub block_offset: u32,
51 pub block_number: u32,
53}
54
55#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
57#[br(big)]
58pub struct SqpkPatchInfo {
59 pub status: u8,
61 pub version: u8,
63 #[br(pad_before = 1)]
65 pub install_size: u64,
66}
67
68pub(crate) fn parse_index(body: &[u8]) -> crate::Result<SqpkIndex> {
69 Ok(SqpkIndex::read_be(&mut Cursor::new(body))?)
70}
71
72pub(crate) fn parse_patch_info(body: &[u8]) -> crate::Result<SqpkPatchInfo> {
73 Ok(SqpkPatchInfo::read_be(&mut Cursor::new(body))?)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn parses_sqpk_index_add() {
82 let mut body = Vec::new();
83 body.push(b'A'); body.push(1u8); body.push(0u8); body.extend_from_slice(&0x0102u16.to_be_bytes()); body.extend_from_slice(&0x0304u16.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&0x0807060504030201u64.to_be_bytes()); body.extend_from_slice(&5u32.to_be_bytes()); body.extend_from_slice(&10u32.to_be_bytes()); let idx = parse_index(&body).unwrap();
94 assert!(matches!(idx.command, IndexCommand::Add));
95 assert!(idx.is_synonym);
96 assert_eq!(idx.target_file.main_id, 0x0102);
97 assert_eq!(idx.file_hash, 0x0807060504030201);
98 assert_eq!(idx.block_offset, 5);
99 assert_eq!(idx.block_number, 10);
100 }
101
102 #[test]
103 fn rejects_unknown_index_command() {
104 let mut body = Vec::new();
105 body.push(b'Z'); body.extend_from_slice(&[0u8; 20]);
107 assert!(parse_index(&body).is_err());
108 }
109
110 #[test]
111 fn parses_sqpk_patch_info() {
112 let mut body = Vec::new();
113 body.push(3u8); body.push(1u8); body.push(0u8); body.extend_from_slice(&0x0102030405060708u64.to_be_bytes()); let info = parse_patch_info(&body).unwrap();
119 assert_eq!(info.status, 3);
120 assert_eq!(info.version, 1);
121 assert_eq!(info.install_size, 0x0102030405060708);
122 }
123}