1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct SmackerHeader {
5 pub width: u32,
6 pub height: u32,
7 pub frames: u32,
8 pub frame_micros: u32,
9 pub double_height: bool,
10 pub has_audio: bool,
12}
13impl SmackerHeader {
14 pub fn parse(bytes: &[u8]) -> Result<Self, String> {
16 if bytes.len() < 104 || !matches!(&bytes[..4], b"SMK2" | b"SMK4") {
17 return Err("SMK: missing or invalid header".into());
18 }
19 let number = |i| u32::from_le_bytes(bytes[i..i + 4].try_into().unwrap());
20 let width = number(4);
21 let height = number(8);
22 let frames = number(12);
23 let timing = number(16) as i32;
24 let frame_micros = if timing < 0 {
25 timing.unsigned_abs().checked_mul(10)
26 } else {
27 (timing as u32).checked_mul(1000)
28 }
29 .filter(|v| *v > 0)
30 .ok_or("SMK: invalid frame timing")?;
31 if width == 0 || height == 0 || width > 4096 || height > 4096 || frames == 0 {
32 return Err("SMK: invalid dimensions/frame count".into());
33 }
34 Ok(Self {
35 width,
36 height,
37 frames,
38 frame_micros,
39 double_height: number(20) & 6 != 0,
40 has_audio: (72..100).step_by(4).any(|i| number(i) & 0x00ff_ffff != 0),
41 })
42 }
43}
44#[cfg(test)]
45mod tests {
46 use super::*;
47 #[test]
48 fn negative_timing_is_hundredths_of_a_millisecond() {
49 let mut header = [0; 104];
50 header[..4].copy_from_slice(b"SMK2");
51 for (offset, value) in [
52 (4, 640u32),
53 (8, 240),
54 (12, 6895),
55 (16, (-6666i32) as u32),
56 (20, 2),
57 ] {
58 header[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
59 }
60 assert_eq!(
61 SmackerHeader::parse(&header).unwrap(),
62 SmackerHeader {
63 width: 640,
64 height: 240,
65 frames: 6895,
66 frame_micros: 66660,
67 double_height: true,
68 has_audio: false,
69 }
70 );
71 assert!(SmackerHeader::parse(&header[..24]).is_err());
72 header[16..20].fill(0);
73 assert!(SmackerHeader::parse(&header).is_err());
74 }
75}