1use std::io::Read;
4
5use super::{
6 binary, checked_mul, Bwm, BwmAabbNode, BwmAdjacency, BwmBinaryError, BwmEdge, BwmFace,
7 BwmTypeCode, BwmVec3, AABB_ENTRY_SIZE, ADJACENCY_ENTRY_SIZE, BWM_MAGIC, BWM_VERSION_V10,
8 DISTANCE_ENTRY_SIZE, EDGE_ENTRY_SIZE, FACE_INDEX_ENTRY_SIZE, FILE_HEADER_SIZE,
9 MATERIAL_ENTRY_SIZE, NORMAL_ENTRY_SIZE, PERIMETER_ENTRY_SIZE, VERTEX_ENTRY_SIZE,
10};
11
12#[cfg_attr(
19 feature = "tracing",
20 tracing::instrument(level = "debug", skip(reader))
21)]
22pub fn read_bwm<R: Read>(reader: &mut R) -> Result<Bwm, BwmBinaryError> {
23 let mut bytes = Vec::new();
24 reader.read_to_end(&mut bytes)?;
25 read_bwm_from_bytes(&bytes)
26}
27
28#[cfg_attr(
38 feature = "tracing",
39 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
40)]
41pub fn read_bwm_from_bytes(bytes: &[u8]) -> Result<Bwm, BwmBinaryError> {
42 if bytes.len() < FILE_HEADER_SIZE {
43 return Err(BwmBinaryError::InvalidHeader(
44 "file smaller than BWM header".into(),
45 ));
46 }
47
48 let magic = binary::read_fourcc(bytes, 0)?;
49 binary::expect_fourcc(magic, BWM_MAGIC).map_err(BwmBinaryError::InvalidMagic)?;
50 let version = binary::read_fourcc(bytes, 4)?;
51 binary::expect_fourcc(version, BWM_VERSION_V10).map_err(BwmBinaryError::InvalidVersion)?;
52
53 let walkmesh_type = BwmTypeCode::from_raw(binary::read_u32(bytes, 8)?); let relative_hook1 = read_vec3(bytes, 12)?; let relative_hook2 = read_vec3(bytes, 24)?; let absolute_hook1 = read_vec3(bytes, 36)?; let absolute_hook2 = read_vec3(bytes, 48)?; let position = read_vec3(bytes, 60)?; let vertex_count = binary::checked_to_usize(binary::read_u32(bytes, 72)?, "vertex_count")?; let vertex_offset = binary::checked_to_usize(binary::read_u32(bytes, 76)?, "vertex_offset")?; let face_count = binary::checked_to_usize(binary::read_u32(bytes, 80)?, "face_count")?; let face_indices_offset =
66 binary::checked_to_usize(binary::read_u32(bytes, 84)?, "face_indices_offset")?; let materials_offset =
68 binary::checked_to_usize(binary::read_u32(bytes, 88)?, "materials_offset")?; let normals_offset = binary::checked_to_usize(binary::read_u32(bytes, 92)?, "normals_offset")?; let planar_distances_offset =
71 binary::checked_to_usize(binary::read_u32(bytes, 96)?, "planar_distances_offset")?; let aabb_count = binary::checked_to_usize(binary::read_u32(bytes, 100)?, "aabb_count")?; let aabb_offset = binary::checked_to_usize(binary::read_u32(bytes, 104)?, "aabb_offset")?; let unknown = binary::read_u32(bytes, 108)?; let adjacency_count =
76 binary::checked_to_usize(binary::read_u32(bytes, 112)?, "adjacency_count")?; let adjacency_offset =
78 binary::checked_to_usize(binary::read_u32(bytes, 116)?, "adjacency_offset")?; let edge_count = binary::checked_to_usize(binary::read_u32(bytes, 120)?, "edge_count")?; let edges_offset = binary::checked_to_usize(binary::read_u32(bytes, 124)?, "edges_offset")?; let perimeter_count =
82 binary::checked_to_usize(binary::read_u32(bytes, 128)?, "perimeter_count")?; let perimeters_offset =
84 binary::checked_to_usize(binary::read_u32(bytes, 132)?, "perimeters_offset")?; let vertices_size = checked_mul(vertex_count, VERTEX_ENTRY_SIZE, "vertices_size")?;
88 binary::check_slice_in_bounds(bytes, vertex_offset, vertices_size, "vertices")?;
89 let face_indices_size = checked_mul(face_count, FACE_INDEX_ENTRY_SIZE, "face_indices_size")?;
90 binary::check_slice_in_bounds(
91 bytes,
92 face_indices_offset,
93 face_indices_size,
94 "face_indices",
95 )?;
96 let materials_size = checked_mul(face_count, MATERIAL_ENTRY_SIZE, "materials_size")?;
97 binary::check_slice_in_bounds(bytes, materials_offset, materials_size, "materials")?;
98 let normals_size = checked_mul(face_count, NORMAL_ENTRY_SIZE, "normals_size")?;
99 binary::check_slice_in_bounds(bytes, normals_offset, normals_size, "normals")?;
100 let distances_size = checked_mul(face_count, DISTANCE_ENTRY_SIZE, "distances_size")?;
101 binary::check_slice_in_bounds(
102 bytes,
103 planar_distances_offset,
104 distances_size,
105 "planar_distances",
106 )?;
107 let aabb_size = checked_mul(aabb_count, AABB_ENTRY_SIZE, "aabb_size")?;
108 binary::check_slice_in_bounds(bytes, aabb_offset, aabb_size, "aabb_nodes")?;
109 let adjacency_size = checked_mul(adjacency_count, ADJACENCY_ENTRY_SIZE, "adjacency_size")?;
110 binary::check_slice_in_bounds(bytes, adjacency_offset, adjacency_size, "adjacencies")?;
111 let edge_size = checked_mul(edge_count, EDGE_ENTRY_SIZE, "edge_size")?;
112 binary::check_slice_in_bounds(bytes, edges_offset, edge_size, "edges")?;
113 let perimeter_size = checked_mul(perimeter_count, PERIMETER_ENTRY_SIZE, "perimeter_size")?;
114 binary::check_slice_in_bounds(bytes, perimeters_offset, perimeter_size, "perimeters")?;
115
116 let mut vertices = Vec::with_capacity(vertex_count);
118 for index in 0..vertex_count {
119 let base = vertex_offset + index * VERTEX_ENTRY_SIZE;
120 vertices.push(read_vec3(bytes, base)?);
121 }
122
123 let mut faces = Vec::with_capacity(face_count);
125 for index in 0..face_count {
126 let indices_base = face_indices_offset + index * FACE_INDEX_ENTRY_SIZE;
127 let i1 = binary::read_u32(bytes, indices_base)?;
128 let i2 = binary::read_u32(bytes, indices_base + 4)?;
129 let i3 = binary::read_u32(bytes, indices_base + 8)?;
130 for (slot, value) in [i1, i2, i3].iter().enumerate() {
131 let vertex_index = binary::checked_to_usize(*value, "face_vertex_index")?;
132 if vertex_index >= vertices.len() {
133 return Err(BwmBinaryError::InvalidData(format!(
134 "faces[{index}].vertex_indices[{slot}]={value} out of bounds for {} vertices",
135 vertices.len()
136 )));
137 }
138 }
139
140 let material_base = materials_offset + index * MATERIAL_ENTRY_SIZE;
141 let material_id = binary::read_u32(bytes, material_base)?;
142 let normal_base = normals_offset + index * NORMAL_ENTRY_SIZE;
143 let normal = read_vec3(bytes, normal_base)?;
144 let distance_base = planar_distances_offset + index * DISTANCE_ENTRY_SIZE;
145 let planar_distance = binary::read_f32(bytes, distance_base)?;
146
147 faces.push(BwmFace {
148 vertex_indices: [i1, i2, i3],
149 material_id,
150 normal,
151 planar_distance,
152 });
153 }
154
155 let mut aabb_nodes = Vec::with_capacity(aabb_count);
157 for index in 0..aabb_count {
158 let base = aabb_offset + index * AABB_ENTRY_SIZE;
159 aabb_nodes.push(BwmAabbNode {
160 bb_min: read_vec3(bytes, base)?,
161 bb_max: read_vec3(bytes, base + 12)?,
162 face_index: binary::read_u32(bytes, base + 24)?,
163 unknown: binary::read_u32(bytes, base + 28)?,
164 split_axis: binary::read_u32(bytes, base + 32)?,
165 left_child: binary::read_u32(bytes, base + 36)?,
166 right_child: binary::read_u32(bytes, base + 40)?,
167 });
168 }
169
170 let mut adjacencies = Vec::with_capacity(adjacency_count);
172 for index in 0..adjacency_count {
173 let base = adjacency_offset + index * ADJACENCY_ENTRY_SIZE;
174 adjacencies.push(BwmAdjacency {
175 edge_refs: [
176 read_i32(bytes, base)?,
177 read_i32(bytes, base + 4)?,
178 read_i32(bytes, base + 8)?,
179 ],
180 });
181 }
182
183 let mut edges = Vec::with_capacity(edge_count);
184 for index in 0..edge_count {
185 let base = edges_offset + index * EDGE_ENTRY_SIZE;
186 edges.push(BwmEdge {
187 edge_index: read_i32(bytes, base)?,
188 transition: read_i32(bytes, base + 4)?,
189 });
190 }
191
192 let mut perimeters = Vec::with_capacity(perimeter_count);
193 for index in 0..perimeter_count {
194 let base = perimeters_offset + index * PERIMETER_ENTRY_SIZE;
195 perimeters.push(binary::read_u32(bytes, base)?);
196 }
197
198 Ok(Bwm {
199 walkmesh_type,
200 relative_hook1,
201 relative_hook2,
202 absolute_hook1,
203 absolute_hook2,
204 position,
205 unknown,
206 vertices,
207 faces,
208 aabb_nodes,
209 adjacencies,
210 edges,
211 perimeters,
212 })
213}
214
215fn read_i32(bytes: &[u8], offset: usize) -> Result<i32, BwmBinaryError> {
216 let raw = binary::read_u32(bytes, offset)?;
217 Ok(i32::from_le_bytes(raw.to_le_bytes()))
218}
219
220fn read_vec3(bytes: &[u8], offset: usize) -> Result<BwmVec3, BwmBinaryError> {
221 Ok(BwmVec3 {
222 x: binary::read_f32(bytes, offset)?,
223 y: binary::read_f32(bytes, offset + 4)?,
224 z: binary::read_f32(bytes, offset + 8)?,
225 })
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::bwm::{write_bwm_to_vec, BwmType};
232
233 const TEST_WOK: &[u8] = include_bytes!(concat!(
234 env!("CARGO_MANIFEST_DIR"),
235 "/../../fixtures/test.wok"
236 ));
237 const TOOLSET_WOK: &[u8] = include_bytes!(concat!(
238 env!("CARGO_MANIFEST_DIR"),
239 "/../../fixtures/zio006j.wok"
240 ));
241
242 fn synthetic_bwm() -> Bwm {
243 Bwm {
244 walkmesh_type: BwmTypeCode::from(BwmType::AreaModel),
245 relative_hook1: BwmVec3::new(1.0, 2.0, 3.0),
246 relative_hook2: BwmVec3::new(4.0, 5.0, 6.0),
247 absolute_hook1: BwmVec3::new(7.0, 8.0, 9.0),
248 absolute_hook2: BwmVec3::new(10.0, 11.0, 12.0),
249 position: BwmVec3::new(13.0, 14.0, 15.0),
250 unknown: 4,
251 vertices: vec![
252 BwmVec3::new(0.0, 0.0, 0.0),
253 BwmVec3::new(1.0, 0.0, 0.0),
254 BwmVec3::new(0.0, 1.0, 0.0),
255 ],
256 faces: vec![BwmFace {
257 vertex_indices: [0, 1, 2],
258 material_id: 1,
259 normal: BwmVec3::new(0.0, 0.0, 1.0),
260 planar_distance: 0.0,
261 }],
262 aabb_nodes: vec![BwmAabbNode {
263 bb_min: BwmVec3::new(0.0, 0.0, 0.0),
264 bb_max: BwmVec3::new(1.0, 1.0, 0.0),
265 face_index: 0,
266 unknown: 4,
267 split_axis: 0,
268 left_child: 0xFFFF_FFFF,
269 right_child: 0xFFFF_FFFF,
270 }],
271 adjacencies: vec![BwmAdjacency {
272 edge_refs: [-1, -1, -1],
273 }],
274 edges: vec![BwmEdge {
275 edge_index: 0,
276 transition: -1,
277 }],
278 perimeters: vec![1],
279 }
280 }
281
282 #[test]
283 fn roundtrip_synthetic_bwm() {
284 let bwm = synthetic_bwm();
285 let bytes = write_bwm_to_vec(&bwm).expect("write should succeed");
286 let parsed = read_bwm_from_bytes(&bytes).expect("read should succeed");
287 assert_eq!(parsed, bwm);
288 }
289
290 #[test]
291 fn writer_is_deterministic_for_synthetic_bwm() {
292 let bwm = synthetic_bwm();
293 let first = write_bwm_to_vec(&bwm).expect("first write should succeed");
294 let second = write_bwm_to_vec(&bwm).expect("second write should succeed");
295 assert_eq!(first, second);
296 }
297
298 #[test]
299 fn parses_wok_fixture() {
300 let bwm = read_bwm_from_bytes(TEST_WOK).expect("fixture should parse");
301 assert_eq!(bwm.walkmesh_type.known(), Some(BwmType::AreaModel));
302 assert_eq!(bwm.vertices.len(), 6);
303 assert_eq!(bwm.faces.len(), 4);
304 assert!(!bwm.aabb_nodes.is_empty());
305 }
306
307 #[test]
308 fn parses_toolset_wok_fixture() {
309 let bwm = read_bwm_from_bytes(TOOLSET_WOK).expect("fixture should parse");
310 assert_eq!(bwm.walkmesh_type.known(), Some(BwmType::AreaModel));
311 assert_eq!(bwm.vertices.len(), 4);
312 assert_eq!(bwm.faces.len(), 2);
313 }
314
315 #[test]
316 fn read_write_roundtrip_preserves_fixture_semantics() {
317 let parsed = read_bwm_from_bytes(TEST_WOK).expect("fixture should parse");
318 let bytes = write_bwm_to_vec(&parsed).expect("write should succeed");
319 let reparsed = read_bwm_from_bytes(&bytes).expect("re-read should succeed");
320 assert_eq!(reparsed, parsed);
321 }
322
323 #[test]
324 fn rejects_invalid_magic() {
325 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
326 bytes[0..4].copy_from_slice(b"NOPE");
327 bytes[4..8].copy_from_slice(&BWM_VERSION_V10);
328 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
329 assert!(matches!(err, BwmBinaryError::InvalidMagic(_)));
330 }
331
332 #[test]
333 fn rejects_invalid_version() {
334 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
335 bytes[0..4].copy_from_slice(&BWM_MAGIC);
336 bytes[4..8].copy_from_slice(b"V9.9");
337 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
338 assert!(matches!(err, BwmBinaryError::InvalidVersion(_)));
339 }
340
341 #[test]
342 fn rejects_truncated_header() {
343 let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
344 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
345 assert!(matches!(err, BwmBinaryError::InvalidHeader(_)));
346 }
347
348 #[test]
349 fn rejects_out_of_bounds_vertex_table() {
350 let mut bytes = write_bwm_to_vec(&synthetic_bwm()).expect("write");
351 bytes[76..80].copy_from_slice(&0xFFFF_FFFF_u32.to_le_bytes()); let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
353 assert!(matches!(err, BwmBinaryError::InvalidHeader(_)));
354 }
355
356 #[test]
357 fn rejects_face_vertex_index_out_of_bounds() {
358 let mut bytes = write_bwm_to_vec(&synthetic_bwm()).expect("write");
359 let face_indices_offset = u32::from_le_bytes([bytes[84], bytes[85], bytes[86], bytes[87]]);
361 let face_indices_offset = usize::try_from(face_indices_offset).expect("offset fits");
362 bytes[face_indices_offset..face_indices_offset + 4].copy_from_slice(&99_u32.to_le_bytes());
363
364 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
365 assert!(matches!(err, BwmBinaryError::InvalidData(_)));
366 }
367
368 #[test]
369 fn decode_encode_traits_roundtrip() {
370 use crate::binary::{DecodeBinary, EncodeBinary};
371 let bwm = synthetic_bwm();
372 let bytes = bwm.encode_binary().expect("encode");
373 let decoded = Bwm::decode_binary(&bytes).expect("decode");
374 assert_eq!(decoded, bwm);
375 }
376}