1use std::fmt;
8use std::io::Cursor;
9
10use binrw::meta::{ReadEndian, WriteEndian};
11use binrw::{BinRead, BinWrite, binrw};
12
13#[derive(Clone, Copy, Eq, Hash, PartialEq)]
15pub struct ChunkType(pub u16);
16
17impl ChunkType {
18 pub const NULL: Self = Self(0x0000);
19 pub const STRING_POOL: Self = Self(0x0001);
20 pub const TABLE: Self = Self(0x0002);
21 pub const XML: Self = Self(0x0003);
22 pub const XML_START_NAMESPACE: Self = Self(0x0100);
23 pub const XML_END_NAMESPACE: Self = Self(0x0101);
24 pub const XML_START_ELEMENT: Self = Self(0x0102);
25 pub const XML_END_ELEMENT: Self = Self(0x0103);
26 pub const XML_CDATA: Self = Self(0x0104);
27 pub const XML_RESOURCE_MAP: Self = Self(0x0180);
28 pub const TABLE_PACKAGE: Self = Self(0x0200);
29 pub const TABLE_TYPE: Self = Self(0x0201);
30 pub const TABLE_TYPE_SPEC: Self = Self(0x0202);
31 pub const TABLE_LIBRARY: Self = Self(0x0203);
32
33 fn name(self) -> Option<&'static str> {
34 Some(match self {
35 Self::NULL => "null",
36 Self::STRING_POOL => "string pool",
37 Self::TABLE => "resource table",
38 Self::XML => "XML document",
39 Self::XML_START_NAMESPACE => "XML namespace start",
40 Self::XML_END_NAMESPACE => "XML namespace end",
41 Self::XML_START_ELEMENT => "XML element start",
42 Self::XML_END_ELEMENT => "XML element end",
43 Self::XML_CDATA => "XML text",
44 Self::XML_RESOURCE_MAP => "XML resource map",
45 Self::TABLE_PACKAGE => "package",
46 Self::TABLE_TYPE => "type",
47 Self::TABLE_TYPE_SPEC => "type spec",
48 Self::TABLE_LIBRARY => "library",
49 _ => return None,
50 })
51 }
52}
53
54impl fmt::Display for ChunkType {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self.name() {
57 Some(name) => write!(f, "{name} ({:#06x})", self.0),
58 None => write!(f, "unknown ({:#06x})", self.0),
59 }
60 }
61}
62
63impl fmt::Debug for ChunkType {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "ChunkType({:#06x})", self.0)
66 }
67}
68
69#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
71#[non_exhaustive]
72pub enum ChunkError {
73 #[error("expected a chunk but found no data")]
74 Missing,
75 #[error("chunk header at offset {offset:#x} is truncated")]
76 TruncatedHeader { offset: usize },
77 #[error("{ty} chunk at offset {offset:#x} declares header size {header_size} and total size {size}")]
78 InvalidSizes { ty: ChunkType, offset: usize, header_size: u16, size: u32 },
79 #[error("{ty} chunk at offset {offset:#x} needs {size} bytes but only {available} remain")]
80 Overflow { ty: ChunkType, offset: usize, size: u32, available: usize },
81 #[error("expected a {expected} chunk, found {found}")]
82 UnexpectedType { expected: ChunkType, found: ChunkType },
83 #[error("{len} bytes of trailing data follow the root chunk")]
84 TrailingData { len: usize },
85 #[error("{ty} chunk header is {size} bytes; the format needs at least {min}")]
86 HeaderTooShort { ty: ChunkType, size: usize, min: usize },
87 #[error("chunk exceeds the format's 4 GiB size limit")]
88 TooLarge,
89}
90
91#[binrw]
93#[brw(little)]
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
95pub(crate) struct ChunkHeader {
96 pub ty: u16,
97 pub header_size: u16,
98 pub size: u32,
99}
100
101impl ChunkHeader {
102 pub const SIZE: usize = 8;
103}
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub(crate) struct Chunk<'a> {
108 pub ty: ChunkType,
109 pub bytes: &'a [u8],
111 pub header: &'a [u8],
113 pub body: &'a [u8],
115}
116
117impl<'a> Chunk<'a> {
118 pub fn parse_root(bytes: &'a [u8], expected: ChunkType) -> Result<Self, ChunkError> {
121 let chunk = Chunks::new(bytes).next().ok_or(ChunkError::Missing)??;
122 if chunk.ty != expected {
123 return Err(ChunkError::UnexpectedType { expected, found: chunk.ty });
124 }
125 match bytes.len() - chunk.bytes.len() {
126 0 => Ok(chunk),
127 len => Err(ChunkError::TrailingData { len }),
128 }
129 }
130
131 pub fn header<T>(&self) -> Result<T, ChunkError>
134 where
135 T: Header + BinRead + ReadEndian,
136 for<'b> T::Args<'b>: Default,
137 {
138 if self.header.len() < T::MIN_SIZE {
139 let base = ChunkHeader::SIZE;
140 return Err(ChunkError::HeaderTooShort {
141 ty: self.ty,
142 size: base + self.header.len(),
143 min: base + T::MIN_SIZE,
144 });
145 }
146 Ok(read_padded(self.header))
147 }
148
149 pub fn children(&self) -> Chunks<'a> {
150 Chunks::new(self.body)
151 }
152
153 pub fn expect(&self, expected: ChunkType) -> Result<(), ChunkError> {
155 if self.ty == expected { Ok(()) } else { Err(ChunkError::UnexpectedType { expected, found: self.ty }) }
156 }
157}
158
159pub(crate) trait Header {
161 const MIN_SIZE: usize;
163}
164
165pub(crate) fn read_padded<T>(bytes: &[u8]) -> T
170where
171 T: BinRead + ReadEndian,
172 for<'b> T::Args<'b>: Default,
173{
174 const MAX_RECORD: usize = 512;
175 let mut padded = [0u8; MAX_RECORD];
176 let len = bytes.len().min(MAX_RECORD);
177 padded[..len].copy_from_slice(&bytes[..len]);
178 T::read(&mut Cursor::new(&padded[..])).expect("fixed-size record fits in a zero-extended buffer")
179}
180
181pub(crate) struct Chunks<'a> {
184 data: &'a [u8],
185 offset: usize,
186}
187
188impl<'a> Chunks<'a> {
189 pub fn new(data: &'a [u8]) -> Self {
190 Self { data, offset: 0 }
191 }
192
193 fn next_chunk(&mut self) -> Result<Chunk<'a>, ChunkError> {
194 let offset = self.offset;
195 if self.data.len() < ChunkHeader::SIZE {
196 return Err(ChunkError::TruncatedHeader { offset });
197 }
198 let header: ChunkHeader = read_padded(self.data);
199 let ty = ChunkType(header.ty);
200 let (header_size, size) = (usize::from(header.header_size), header.size as usize);
201 if header_size < ChunkHeader::SIZE || size < header_size {
202 return Err(ChunkError::InvalidSizes { ty, offset, header_size: header.header_size, size: header.size });
203 }
204 if size > self.data.len() {
205 return Err(ChunkError::Overflow { ty, offset, size: header.size, available: self.data.len() });
206 }
207 let bytes = &self.data[..size];
208 self.data = &self.data[size..];
209 self.offset += size;
210 Ok(Chunk { ty, bytes, header: &bytes[ChunkHeader::SIZE..header_size], body: &bytes[header_size..] })
211 }
212}
213
214impl<'a> Iterator for Chunks<'a> {
215 type Item = Result<Chunk<'a>, ChunkError>;
216
217 fn next(&mut self) -> Option<Self::Item> {
218 if self.data.is_empty() {
219 return None;
220 }
221 let chunk = self.next_chunk();
222 if chunk.is_err() {
223 self.data = &[];
224 }
225 Some(chunk)
226 }
227}
228
229pub(crate) trait Put {
231 fn put_u16(&mut self, value: u16);
232 fn put_u32(&mut self, value: u32);
233 fn put<T>(&mut self, value: &T)
235 where
236 T: BinWrite + WriteEndian,
237 for<'b> T::Args<'b>: Default;
238 fn align4(&mut self);
240}
241
242impl Put for Vec<u8> {
243 fn put_u16(&mut self, value: u16) {
244 self.extend_from_slice(&value.to_le_bytes());
245 }
246
247 fn put_u32(&mut self, value: u32) {
248 self.extend_from_slice(&value.to_le_bytes());
249 }
250
251 fn put<T>(&mut self, value: &T)
252 where
253 T: BinWrite + WriteEndian,
254 for<'b> T::Args<'b>: Default,
255 {
256 let mut cursor = Cursor::new(self);
257 cursor.set_position(cursor.get_ref().len() as u64);
258 value.write(&mut cursor).expect("plain records always serialize into a Vec");
259 }
260
261 fn align4(&mut self) {
262 self.resize(self.len().next_multiple_of(4), 0);
263 }
264}
265
266pub(crate) fn write_chunk<H, E>(
269 out: &mut Vec<u8>,
270 ty: ChunkType,
271 header: &H,
272 body: impl FnOnce(&mut Vec<u8>) -> Result<(), E>,
273) -> Result<(), E>
274where
275 H: BinWrite + WriteEndian,
276 for<'b> H::Args<'b>: Default,
277 E: From<ChunkError>,
278{
279 let start = out.len();
280 out.put(&ChunkHeader { ty: ty.0, header_size: 0, size: 0 });
281 out.put(header);
282 let header_size = u16::try_from(out.len() - start).map_err(|_| ChunkError::TooLarge)?;
283 body(out)?;
284 let size = u32::try_from(out.len() - start).map_err(|_| ChunkError::TooLarge)?;
285 out[start + 2..start + 4].copy_from_slice(&header_size.to_le_bytes());
286 out[start + 4..start + 8].copy_from_slice(&size.to_le_bytes());
287 Ok(())
288}
289
290#[binrw]
292#[brw(little)]
293pub(crate) struct NoHeader;
294
295impl Header for NoHeader {
296 const MIN_SIZE: usize = 0;
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn rejects_overlapping_or_truncated_chunks() {
305 let too_small = [0x02, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00];
306 assert_eq!(
307 Chunks::new(&too_small).next(),
308 Some(Err(ChunkError::InvalidSizes { ty: ChunkType::TABLE, offset: 0, header_size: 12, size: 8 }))
309 );
310 let too_big = [0x02, 0x00, 0x08, 0x00, 0xff, 0x00, 0x00, 0x00];
311 assert_eq!(
312 Chunks::new(&too_big).next(),
313 Some(Err(ChunkError::Overflow { ty: ChunkType::TABLE, offset: 0, size: 255, available: 8 }))
314 );
315 assert_eq!(Chunks::new(&[0x02, 0x00]).next(), Some(Err(ChunkError::TruncatedHeader { offset: 0 })));
316 }
317
318 #[test]
319 fn writes_nested_chunks_with_patched_sizes() {
320 let mut out = Vec::new();
321 write_chunk::<_, ChunkError>(&mut out, ChunkType::XML, &NoHeader, |out| {
322 write_chunk(out, ChunkType::XML_RESOURCE_MAP, &NoHeader, |out| {
323 out.put_u32(0x0101_0003);
324 Ok::<_, ChunkError>(())
325 })
326 })
327 .unwrap();
328 let root = Chunk::parse_root(&out, ChunkType::XML).unwrap();
329 let child = root.children().next().unwrap().unwrap();
330 assert_eq!(child.ty, ChunkType::XML_RESOURCE_MAP);
331 assert_eq!(child.body, 0x0101_0003u32.to_le_bytes());
332 assert_eq!(
333 Chunk::parse_root(&out, ChunkType::TABLE).unwrap_err(),
334 ChunkError::UnexpectedType { expected: ChunkType::TABLE, found: ChunkType::XML }
335 );
336 }
337}