zipatch_rs/chunk/sqpk/
header.rs1use binrw::{BinRead, BinResult, Endian};
2use std::io::Cursor;
3
4use super::SqpackFile;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum TargetFileKind {
9 Dat,
11 Index,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum TargetHeaderKind {
18 Version,
20 Index,
22 Data,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum SqpkHeaderTarget {
29 Dat(SqpackFile),
31 Index(SqpackFile),
33}
34
35fn read_file_kind<R: std::io::Read + std::io::Seek>(
36 reader: &mut R,
37 _: Endian,
38 (): (),
39) -> BinResult<TargetFileKind> {
40 let byte = <u8 as BinRead>::read_options(reader, Endian::Big, ())?;
41 match byte {
42 b'D' => Ok(TargetFileKind::Dat),
43 b'I' => Ok(TargetFileKind::Index),
44 _ => Err(binrw::Error::Custom {
45 pos: 0,
46 err: Box::new(std::io::Error::new(
47 std::io::ErrorKind::InvalidData,
48 "unknown SqpkHeader file kind",
49 )),
50 }),
51 }
52}
53
54fn read_header_kind<R: std::io::Read + std::io::Seek>(
55 reader: &mut R,
56 _: Endian,
57 (): (),
58) -> BinResult<TargetHeaderKind> {
59 let byte = <u8 as BinRead>::read_options(reader, Endian::Big, ())?;
60 match byte {
61 b'V' => Ok(TargetHeaderKind::Version),
62 b'I' => Ok(TargetHeaderKind::Index),
63 b'D' => Ok(TargetHeaderKind::Data),
64 _ => Err(binrw::Error::Custom {
65 pos: 0,
66 err: Box::new(std::io::Error::new(
67 std::io::ErrorKind::InvalidData,
68 "unknown SqpkHeader header kind",
69 )),
70 }),
71 }
72}
73
74fn read_header_target<R: std::io::Read + std::io::Seek>(
75 reader: &mut R,
76 endian: Endian,
77 (file_kind,): (&TargetFileKind,),
78) -> BinResult<SqpkHeaderTarget> {
79 let f = SqpackFile::read_options(reader, endian, ())?;
80 match file_kind {
81 TargetFileKind::Dat => Ok(SqpkHeaderTarget::Dat(f)),
82 TargetFileKind::Index => Ok(SqpkHeaderTarget::Index(f)),
83 }
84}
85
86#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
88#[br(big)]
89pub struct SqpkHeader {
90 #[br(parse_with = read_file_kind)]
92 pub file_kind: TargetFileKind,
93 #[br(parse_with = read_header_kind)]
95 pub header_kind: TargetHeaderKind,
96 #[br(pad_before = 1, parse_with = read_header_target, args(&file_kind))]
98 pub target: SqpkHeaderTarget,
99 #[br(count = 1024)]
101 pub header_data: Vec<u8>,
102}
103
104pub(crate) fn parse(body: &[u8]) -> crate::Result<SqpkHeader> {
105 Ok(SqpkHeader::read_be(&mut Cursor::new(body))?)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn parses_header_dat_version() {
114 let mut body = Vec::new();
115 body.push(b'D'); body.push(b'V'); body.push(0u8); body.extend_from_slice(&10u16.to_be_bytes()); body.extend_from_slice(&20u16.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&[0xCCu8; 1024]); let cmd = parse(&body).unwrap();
124 assert!(matches!(cmd.file_kind, TargetFileKind::Dat));
125 assert!(matches!(cmd.header_kind, TargetHeaderKind::Version));
126 match cmd.target {
127 SqpkHeaderTarget::Dat(f) => {
128 assert_eq!(f.main_id, 10);
129 assert_eq!(f.sub_id, 20);
130 }
131 other => panic!("expected SqpkHeaderTarget::Dat, got {other:?}"),
132 }
133 assert_eq!(cmd.header_data.len(), 1024);
134 }
135
136 #[test]
137 fn rejects_unknown_file_kind() {
138 let mut body = Vec::new();
139 body.push(b'Z'); body.push(b'V');
141 body.push(0u8);
142 body.extend_from_slice(&[0u8; 8 + 1024]);
143 assert!(parse(&body).is_err());
144 }
145
146 #[test]
147 fn rejects_unknown_header_kind() {
148 let mut body = Vec::new();
149 body.push(b'D');
150 body.push(b'Z'); body.push(0u8);
152 body.extend_from_slice(&[0u8; 8 + 1024]);
153 assert!(parse(&body).is_err());
154 }
155
156 #[test]
157 fn parses_header_index_file() {
158 let mut body = Vec::new();
159 body.push(b'I'); body.push(b'I'); body.push(0u8);
162 body.extend_from_slice(&7u16.to_be_bytes()); body.extend_from_slice(&8u16.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&[0xBBu8; 1024]);
166
167 let cmd = parse(&body).unwrap();
168 assert!(matches!(cmd.file_kind, TargetFileKind::Index));
169 assert!(matches!(cmd.header_kind, TargetHeaderKind::Index));
170 match cmd.target {
171 SqpkHeaderTarget::Index(f) => {
172 assert_eq!(f.main_id, 7);
173 assert_eq!(f.sub_id, 8);
174 }
175 other => panic!("expected SqpkHeaderTarget::Index, got {other:?}"),
176 }
177 assert_eq!(cmd.header_data.len(), 1024);
178 }
179
180 #[test]
181 fn header_data_truncated() {
182 let mut body = Vec::new();
183 body.push(b'D');
184 body.push(b'V');
185 body.push(0u8);
186 body.extend_from_slice(&[0u8; 8]);
187 body.extend_from_slice(&[0u8; 512]); assert!(parse(&body).is_err());
189 }
190}