1use crate::error::Error;
5
6#[derive(Debug, Clone)]
7pub struct SplashVertex {
8 pub position: [f32; 3],
9 pub normal: [f32; 3],
10 pub uv: [f32; 2],
12}
13
14#[derive(Debug, Clone)]
15pub struct SplashFace {
16 pub vertices: [usize; 3],
17 pub material: u32,
18 pub antialiased_edges: u32,
19}
20
21#[derive(Debug, Clone)]
22pub struct SplashMesh {
23 pub vertices: Vec<SplashVertex>,
24 pub faces: Vec<SplashFace>,
25}
26
27#[derive(Debug, Clone)]
28pub struct SplashTexture {
29 pub width: u32,
30 pub height: u32,
31 pub rgba: Vec<u8>,
33}
34
35#[derive(Debug, Clone)]
36pub struct GlideSplash {
37 pub meshes: [SplashMesh; 3],
39 pub frames: Vec<[[f32; 16]; 3]>,
41 pub textures: [SplashTexture; 3],
43}
44
45fn invalid(message: &str) -> Error {
46 Error::Parse(format!("Glide splash: {message}"))
47}
48
49fn bytes(data: &[u8], start: usize, length: usize) -> Result<&[u8], Error> {
50 data.get(
51 start
52 ..start
53 .checked_add(length)
54 .ok_or_else(|| invalid("range overflow"))?,
55 )
56 .ok_or_else(|| invalid("truncated data"))
57}
58
59fn word(data: &[u8], start: usize) -> Result<u32, Error> {
60 Ok(u32::from_le_bytes(
61 bytes(data, start, 4)?.try_into().unwrap(),
62 ))
63}
64
65fn floats<const N: usize>(data: &[u8], start: usize) -> Result<[f32; N], Error> {
66 let mut result = [0.0; N];
67 for (i, value) in result.iter_mut().enumerate() {
68 *value = f32::from_bits(word(data, start + i * 4)?);
69 if !value.is_finite() {
70 return Err(invalid("non-finite geometry or transform"));
71 }
72 }
73 Ok(result)
74}
75
76fn unique(data: &[u8], words: &[u32]) -> Result<usize, Error> {
77 let pattern: Vec<_> = words.iter().flat_map(|value| value.to_le_bytes()).collect();
78 let mut matches = data
79 .windows(pattern.len())
80 .enumerate()
81 .filter(|(_, bytes)| *bytes == pattern);
82 let first = matches
83 .next()
84 .ok_or_else(|| invalid("unsupported data-table layout"))?
85 .0;
86 if matches.next().is_some() {
87 return Err(invalid("ambiguous data-table layout"));
88 }
89 Ok(first)
90}
91
92pub fn parse_glide_splash(data: &[u8]) -> Result<GlideSplash, Error> {
96 if bytes(data, 0, 2)? != b"MZ" {
97 return Err(invalid("missing DOS header"));
98 }
99 let le = word(data, 60)? as usize;
100 if bytes(data, le, 4)? != b"LE\0\0" {
101 return Err(invalid("unsupported executable header"));
102 }
103 let page_size = word(data, le + 0x28)? as usize;
104 let object_table = le + word(data, le + 0x40)? as usize;
105 let object_count = word(data, le + 0x44)? as usize;
106 let page_table = le + word(data, le + 0x48)? as usize;
107 let file_pages = word(data, le + 0x80)? as usize;
108 if !(1..=64).contains(&object_count) || page_size != 4096 {
109 return Err(invalid("unsupported LE object/page layout"));
110 }
111 let mut candidates = Vec::new();
112 for object in 0..object_count {
113 let entry = object_table + object * 24;
114 let length = word(data, entry)? as usize;
115 let flags = word(data, entry + 8)?;
116 if flags & 2 == 0 {
117 continue;
118 }
119 if length > 8 * 1024 * 1024 {
120 return Err(invalid("data object is too large"));
121 }
122 let first_page = word(data, entry + 12)? as usize;
123 let page_count = word(data, entry + 16)? as usize;
124 if first_page == 0 || page_count > length.div_ceil(page_size) {
125 return Err(invalid("invalid data-object page range"));
126 }
127 let mut image = vec![0; length];
128 for page in 0..page_count {
129 let map = bytes(data, page_table + (first_page - 1 + page) * 4, 4)?;
130 if map[3] != 0 {
131 return Err(invalid("unsupported encoded LE page"));
132 }
133 let number =
134 (usize::from(map[0]) << 16) | (usize::from(map[1]) << 8) | usize::from(map[2]);
135 if number == 0 {
136 return Err(invalid("invalid LE file-page number"));
137 }
138 let size = if number == word(data, le + 0x14)? as usize {
139 word(data, le + 0x2c)? as usize
140 } else {
141 page_size
142 };
143 if size > page_size || page * page_size + size > image.len() {
144 return Err(invalid("invalid LE page size"));
145 }
146 image[page * page_size..page * page_size + size].copy_from_slice(bytes(
147 data,
148 file_pages + (number - 1) * page_size,
149 size,
150 )?);
151 }
152 if let Ok(root) = unique(&image, &[68, 1694, 34]) {
153 candidates.push((image, root));
154 }
155 }
156 if candidates.len() != 1 {
157 return Err(invalid("unsupported or ambiguous splash object"));
158 }
159 let (image, counts) = candidates.pop().unwrap();
160 let root = counts
161 .checked_sub(12)
162 .ok_or_else(|| invalid("invalid mesh-table location"))?;
163 if [
164 word(&image, root + 36)?,
165 word(&image, root + 40)?,
166 word(&image, root + 44)?,
167 word(&image, root + 48)?,
168 ] != [100, 1606, 32, 75]
169 {
170 return Err(invalid("unsupported splash mesh/frame counts"));
171 }
172 let mut meshes = Vec::new();
173 for mesh in 0..3 {
174 let vertex_start = word(&image, root + mesh * 4)? as usize;
175 let vertex_count = word(&image, root + 12 + mesh * 4)? as usize;
176 let face_start = word(&image, root + 24 + mesh * 4)? as usize;
177 let face_count = word(&image, root + 36 + mesh * 4)? as usize;
178 let vertices = (0..vertex_count)
179 .map(|i| {
180 let values = floats::<8>(&image, vertex_start + i * 32)?;
181 Ok(SplashVertex {
182 position: values[..3].try_into().unwrap(),
183 normal: values[3..6].try_into().unwrap(),
184 uv: values[6..].try_into().unwrap(),
185 })
186 })
187 .collect::<Result<Vec<_>, Error>>()?;
188 let faces = (0..face_count)
189 .map(|i| {
190 let start = face_start + i * 20;
191 let vertices = [
192 word(&image, start)? as usize,
193 word(&image, start + 4)? as usize,
194 word(&image, start + 8)? as usize,
195 ];
196 let material = word(&image, start + 12)?;
197 let antialiased_edges = word(&image, start + 16)?;
198 if vertices.iter().any(|index| *index >= vertex_count)
199 || material > 4
200 || antialiased_edges > 7
201 {
202 return Err(invalid("invalid face index, material, or edge flags"));
203 }
204 Ok(SplashFace {
205 vertices,
206 material,
207 antialiased_edges,
208 })
209 })
210 .collect::<Result<Vec<_>, Error>>()?;
211 meshes.push(SplashMesh { vertices, faces });
212 }
213 let frames = (0..75)
214 .map(|frame| {
215 let start = root + 52 + frame * 192;
216 Ok([
217 floats(&image, start)?,
218 floats(&image, start + 64)?,
219 floats(&image, start + 128)?,
220 ])
221 })
222 .collect::<Result<Vec<_>, Error>>()?;
223 let textures = [
224 texture(&image, [64, 64, 8, 2, 3, 1])?,
225 texture(&image, [32, 32, 8, 3, 3, 3])?,
226 texture(&image, [64, 32, 8, 2, 2, 3])?,
227 ];
228 Ok(GlideSplash {
229 meshes: meshes.try_into().unwrap(),
230 frames,
231 textures,
232 })
233}
234
235fn texture(data: &[u8], header: [u32; 6]) -> Result<SplashTexture, Error> {
236 let start = unique(data, &header)?;
237 let [width, height, _, _, _, format] = header;
238 let mip_size = word(data, start + 1052)? as usize;
239 if mip_size < (width * height) as usize || mip_size > 64 * 1024 {
240 return Err(invalid("invalid texture mip-chain size"));
241 }
242 let pixels = bytes(data, start + 1056, mip_size)?;
243 let table = bytes(data, start + 24, 64)?;
244 let mut rgba = Vec::with_capacity((width * height * 4) as usize);
245 for &pixel in &pixels[..(width * height) as usize] {
246 if format == 3 {
247 rgba.extend([pixel, pixel, pixel, 255]);
248 } else {
249 for channel in 0..3 {
250 let i = 16 + (usize::from((pixel >> 2) & 3) * 3 + channel) * 2;
251 let q = 40 + (usize::from(pixel & 3) * 3 + channel) * 2;
252 let component = |offset| {
253 let bits = u16::from_le_bytes(table[offset..offset + 2].try_into().unwrap());
254 i32::from(bits & 255) - i32::from(bits & 256)
255 };
256 let value = i32::from(table[usize::from(pixel >> 4)]) + component(i) + component(q);
257 rgba.push(value.clamp(0, 255) as u8);
258 }
259 rgba.push(255);
260 }
261 }
262 Ok(SplashTexture {
263 width,
264 height,
265 rgba,
266 })
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn yiq_sign_extends_nine_bit_color_components() {
275 let header = [64, 64, 8, 2, 3, 1];
276 let mut data = vec![0; 1056 + 4096];
277 for (i, value) in header.into_iter().enumerate() {
278 data[i * 4..i * 4 + 4].copy_from_slice(&u32::to_le_bytes(value));
279 }
280 data[24 + 8] = 128;
281 for (start, vector) in [
282 (24 + 16 + 6, [502u16, 20, 30]),
283 (24 + 40 + 12, [5, 507, 492]),
284 ] {
285 for (channel, value) in vector.into_iter().enumerate() {
286 data[start + channel * 2..start + channel * 2 + 2]
287 .copy_from_slice(&value.to_le_bytes());
288 }
289 }
290 data[1052..1056].copy_from_slice(&4096u32.to_le_bytes());
291 data[1056] = (8 << 4) | (1 << 2) | 2;
292 assert_eq!(
293 &texture(&data, header).unwrap().rgba[..4],
294 &[123, 143, 138, 255]
295 );
296 }
297
298 #[test]
299 fn intensity_is_opaque_grayscale() {
300 let header = [32, 32, 8, 3, 3, 3];
301 let mut data = vec![0; 1056 + 1024];
302 for (i, value) in header.into_iter().enumerate() {
303 data[i * 4..i * 4 + 4].copy_from_slice(&u32::to_le_bytes(value));
304 }
305 data[1052..1056].copy_from_slice(&1024u32.to_le_bytes());
306 data[1056..1060].copy_from_slice(&[0, 64, 128, 255]);
307 let decoded = texture(&data, header).unwrap();
308 assert_eq!(
309 &decoded.rgba[..16],
310 &[
311 0, 0, 0, 255, 64, 64, 64, 255, 128, 128, 128, 255, 255, 255, 255, 255
312 ]
313 );
314 }
315}