rakata_formats/lip/
reader.rs

1//! LIP binary reader.
2
3use std::io::Read;
4
5use crate::binary;
6
7use super::{
8    Lip, LipBinaryError, LipKeyframe, LipShapeCode, FILE_HEADER_SIZE, KEYFRAME_ENTRY_SIZE,
9    LIP_MAGIC, LIP_VERSION_V10,
10};
11
12/// Reads a LIP file from a reader.
13///
14/// The stream is consumed from its current position.
15#[cfg_attr(
16    feature = "tracing",
17    tracing::instrument(level = "debug", skip(reader))
18)]
19pub fn read_lip<R: Read>(reader: &mut R) -> Result<Lip, LipBinaryError> {
20    let mut bytes = Vec::new();
21    reader.read_to_end(&mut bytes)?;
22    read_lip_from_bytes(&bytes)
23}
24
25/// Reads a LIP file directly from bytes.
26#[cfg_attr(
27    feature = "tracing",
28    tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
29)]
30pub fn read_lip_from_bytes(bytes: &[u8]) -> Result<Lip, LipBinaryError> {
31    if bytes.len() < FILE_HEADER_SIZE {
32        return Err(LipBinaryError::InvalidHeader(
33            "file smaller than LIP header".into(),
34        ));
35    }
36
37    let magic = binary::read_fourcc(bytes, 0)?;
38    if magic != LIP_MAGIC {
39        return Err(LipBinaryError::InvalidMagic(magic));
40    }
41
42    let version = binary::read_fourcc(bytes, 4)?;
43    if version != LIP_VERSION_V10 {
44        return Err(LipBinaryError::InvalidVersion(version));
45    }
46
47    let length = binary::read_f32(bytes, 8)?;
48    let entry_count = binary::checked_to_usize(binary::read_u32(bytes, 12)?, "entry_count")?;
49    let keyframes_table_size = entry_count
50        .checked_mul(KEYFRAME_ENTRY_SIZE)
51        .ok_or_else(|| LipBinaryError::InvalidHeader("keyframe table size overflow".into()))?;
52    binary::check_slice_in_bounds(
53        bytes,
54        FILE_HEADER_SIZE,
55        keyframes_table_size,
56        "keyframe table",
57    )?;
58
59    let mut keyframes = Vec::with_capacity(entry_count);
60    for entry_index in 0..entry_count {
61        let base = FILE_HEADER_SIZE + entry_index * KEYFRAME_ENTRY_SIZE;
62        let time = binary::read_f32(bytes, base)?;
63        let shape = *bytes
64            .get(base + 4)
65            .ok_or_else(|| LipBinaryError::InvalidData("shape byte missing".into()))?;
66
67        keyframes.push(LipKeyframe {
68            time,
69            shape: LipShapeCode::from_raw_id(shape),
70        });
71    }
72
73    Ok(Lip { length, keyframes })
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::lip::{write_lip_to_vec, LipShape};
80
81    const TEST_LIP: &[u8] = include_bytes!(concat!(
82        env!("CARGO_MANIFEST_DIR"),
83        "/../../fixtures/test.lip"
84    ));
85    const CORRUPTED_LIP: &[u8] = include_bytes!(concat!(
86        env!("CARGO_MANIFEST_DIR"),
87        "/../../fixtures/test_corrupted.lip"
88    ));
89
90    #[test]
91    fn parses_lip_fixture() {
92        let lip = read_lip_from_bytes(TEST_LIP).expect("fixture should parse");
93        assert!((lip.length - 1.5).abs() < f32::EPSILON);
94        assert_eq!(lip.keyframes.len(), 3);
95
96        assert_eq!(lip.keyframes[0].shape.raw_id(), 0);
97        assert_eq!(lip.keyframes[1].shape.raw_id(), 5);
98        assert_eq!(lip.keyframes[2].shape.raw_id(), 10);
99
100        assert!((lip.keyframes[0].time - 0.0).abs() < 0.0001);
101        assert!((lip.keyframes[1].time - 0.7777).abs() < 0.0001);
102        assert!((lip.keyframes[2].time - 1.25).abs() < 0.0001);
103    }
104
105    #[test]
106    fn roundtrip_synthetic_lip() {
107        let mut lip = Lip::new();
108        lip.length = 2.75;
109        lip.push_keyframe_with_shape(0.0, LipShapeCode::from(LipShape::Neutral));
110        lip.push_keyframe_with_shape(1.0, LipShapeCode::from(LipShape::Th));
111        lip.push_keyframe(2.5, 200);
112
113        let bytes = write_lip_to_vec(&lip).expect("write should succeed");
114        let parsed = read_lip_from_bytes(&bytes).expect("read should succeed");
115        assert_eq!(parsed, lip);
116        assert!(!parsed.keyframes[2].shape.is_known());
117    }
118
119    #[test]
120    fn writer_is_deterministic_for_synthetic_lip() {
121        let mut lip = Lip::new();
122        lip.length = 1.25;
123        lip.push_keyframe(0.0, 0);
124        lip.push_keyframe(0.5, 7);
125        lip.push_keyframe(1.0, 15);
126
127        let first = write_lip_to_vec(&lip).expect("first write should succeed");
128        let second = write_lip_to_vec(&lip).expect("second write should succeed");
129        assert_eq!(first, second);
130    }
131
132    #[test]
133    fn read_write_roundtrip_preserves_fixture_semantics() {
134        let parsed = read_lip_from_bytes(TEST_LIP).expect("fixture should parse");
135        let bytes = write_lip_to_vec(&parsed).expect("write should succeed");
136        let reparsed = read_lip_from_bytes(&bytes).expect("re-read should succeed");
137        assert_eq!(reparsed, parsed);
138    }
139
140    #[test]
141    fn rejects_invalid_magic() {
142        let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
143        bytes[0..4].copy_from_slice(b"NOPE");
144        bytes[4..8].copy_from_slice(&LIP_VERSION_V10);
145
146        let err = read_lip_from_bytes(&bytes).expect_err("must fail");
147        assert!(matches!(err, LipBinaryError::InvalidMagic(_)));
148    }
149
150    #[test]
151    fn rejects_invalid_version() {
152        let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
153        bytes[0..4].copy_from_slice(&LIP_MAGIC);
154        bytes[4..8].copy_from_slice(b"V9.9");
155
156        let err = read_lip_from_bytes(&bytes).expect_err("must fail");
157        assert!(matches!(err, LipBinaryError::InvalidVersion(_)));
158    }
159
160    #[test]
161    fn rejects_truncated_header() {
162        let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
163        let err = read_lip_from_bytes(&bytes).expect_err("must fail");
164        assert!(matches!(err, LipBinaryError::InvalidHeader(_)));
165    }
166
167    #[test]
168    fn rejects_truncated_or_corrupted_fixture() {
169        let err = read_lip_from_bytes(CORRUPTED_LIP).expect_err("must fail");
170        assert!(matches!(
171            err,
172            LipBinaryError::InvalidHeader(_) | LipBinaryError::InvalidData(_)
173        ));
174    }
175
176    #[test]
177    fn shape_wrapper_maps_known_and_unknown_values() {
178        assert_eq!(
179            LipShapeCode::from_raw_id(0).known_shape(),
180            Some(LipShape::Neutral)
181        );
182        assert_eq!(
183            LipShapeCode::from_raw_id(15).known_shape(),
184            Some(LipShape::Kg)
185        );
186        assert_eq!(LipShapeCode::from_raw_id(16).known_shape(), None);
187    }
188}