rakata_formats/lip/
reader.rs1use 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#[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#[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}