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