1use std::io::Read;
4
5use rakata_core::ResourceId;
6
7use super::layout::{self, BifHeader, BifTableEntry};
8use super::{binary, Bif, BifBinaryError, BifContainer, BifReadOptions, BifResource};
9
10#[cfg_attr(
14 feature = "tracing",
15 tracing::instrument(level = "debug", skip(reader))
16)]
17pub fn read_bif<R: Read>(reader: &mut R) -> Result<Bif, BifBinaryError> {
18 read_bif_with_options(reader, BifReadOptions::default())
19}
20
21#[cfg_attr(
23 feature = "tracing",
24 tracing::instrument(level = "debug", skip(reader))
25)]
26pub fn read_bif_with_options<R: Read>(
27 reader: &mut R,
28 options: BifReadOptions,
29) -> Result<Bif, BifBinaryError> {
30 let mut bytes = Vec::new();
31 reader.read_to_end(&mut bytes)?;
32 crate::trace_debug!(bytes_len = bytes.len(), "read bif/bzf bytes from reader");
33 read_bif_from_bytes_with_options(&bytes, options)
34}
35
36#[cfg_attr(
38 feature = "tracing",
39 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
40)]
41pub fn read_bif_from_bytes(bytes: &[u8]) -> Result<Bif, BifBinaryError> {
42 read_bif_from_bytes_with_options(bytes, BifReadOptions::default())
43}
44
45#[cfg_attr(
47 feature = "tracing",
48 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
49)]
50pub fn read_bif_from_bytes_with_options(
51 bytes: &[u8],
52 options: BifReadOptions,
53) -> Result<Bif, BifBinaryError> {
54 let header = BifHeader::parse(bytes, options)?;
55 let variable_table_size = header.variable_table_size()?;
56 binary::check_slice_in_bounds(
57 bytes,
58 header.variable_table_offset,
59 variable_table_size,
60 "variable resource table",
61 )?;
62 let variable_table = bytes
63 .get(header.variable_table_offset..header.variable_table_offset + variable_table_size)
64 .ok_or_else(|| BifBinaryError::InvalidData("variable resource table is missing".into()))?;
65
66 let total_entry_count = if header.parse_fixed_entries {
67 header
68 .variable_count
69 .checked_add(header.fixed_count)
70 .ok_or_else(|| BifBinaryError::InvalidHeader("resource count overflow".into()))?
71 } else {
72 header.variable_count
73 };
74 let mut resource_entries = Vec::with_capacity(total_entry_count);
75 for resource_index in 0..header.variable_count {
76 resource_entries.push(layout::parse_variable_entry(
77 variable_table,
78 resource_index,
79 )?);
80 }
81
82 if header.parse_fixed_entries {
83 let fixed_table_size = header.fixed_table_size()?;
84 binary::check_slice_in_bounds(
85 bytes,
86 header.fixed_table_offset,
87 fixed_table_size,
88 "fixed resource table",
89 )?;
90 let fixed_table = bytes
91 .get(header.fixed_table_offset..header.fixed_table_offset + fixed_table_size)
92 .ok_or_else(|| BifBinaryError::InvalidData("fixed resource table is missing".into()))?;
93 for fixed_index in 0..header.fixed_count {
94 resource_entries.push(layout::parse_fixed_entry(fixed_table, fixed_index)?);
95 }
96 }
97
98 let mut resources = Vec::with_capacity(resource_entries.len());
99 for (resource_index, entry) in resource_entries.into_iter().enumerate() {
100 let BifTableEntry {
101 resource_id,
102 resource_type,
103 data_offset,
104 data_size,
105 storage,
106 source_data_offset,
107 } = entry;
108 check_data_slice_in_bounds(
109 bytes,
110 data_offset,
111 data_size,
112 &format!("resource data[{resource_index}]"),
113 )?;
114 let data = bytes
115 .get(data_offset..data_offset + data_size)
116 .ok_or_else(|| {
117 BifBinaryError::InvalidData(format!(
118 "resource data slice missing for index {resource_index}"
119 ))
120 })?
121 .to_vec();
122
123 resources.push(BifResource {
124 resource_id: ResourceId::from_raw(resource_id),
125 resource_type,
126 storage,
127 data,
128 source_data_offset: Some(source_data_offset),
129 });
130 }
131
132 let bif = Bif {
133 container: BifContainer::Biff,
134 resources,
135 };
136 crate::trace_debug!(
137 container = ?bif.container,
138 resource_count = bif.resources.len(),
139 "parsed bif/bzf from bytes"
140 );
141 Ok(bif)
142}
143
144fn check_data_slice_in_bounds(
145 bytes: &[u8],
146 offset: usize,
147 size: usize,
148 label: &str,
149) -> Result<(), BifBinaryError> {
150 binary::check_slice_in_bounds(bytes, offset, size, label)
151 .map_err(|error| BifBinaryError::InvalidData(error.to_string()))
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::bif::{
158 write_bif_to_vec, BifReadMode, BifResourceStorage, BIF_MAGIC, BIF_VERSION_V10,
159 BIF_VERSION_V11, FILE_HEADER_SIZE, FIXED_ENTRY_SIZE,
160 };
161 #[cfg(not(feature = "bzf"))]
164 use crate::bif::VARIABLE_ENTRY_SIZE;
165 use rakata_core::{ResourceId, ResourceTypeCode};
166
167 const K1_PLAYER_BIF: &[u8] = include_bytes!(concat!(
168 env!("CARGO_MANIFEST_DIR"),
169 "/../../fixtures/k1_player.bif"
170 ));
171
172 #[test]
173 fn parses_bif_fixture() {
174 let bif = read_bif_from_bytes(K1_PLAYER_BIF).expect("fixture should parse");
175 assert_eq!(bif.resources.len(), 3);
176
177 let first = &bif.resources[0];
178 assert_eq!(first.resource_id, ResourceId::from_raw(0x0140_0000));
179 assert_eq!(first.resource_type.raw_id(), 2002);
180
181 let second = &bif.resources[1];
182 assert_eq!(second.resource_id, ResourceId::from_raw(0x0140_0001));
183 assert_eq!(second.resource_type.raw_id(), 3008);
184 }
185
186 #[test]
187 fn roundtrip_synthetic_bif() {
188 let mut bif = Bif::new();
189 bif.push_resource(
190 ResourceId::from_raw(0x0010_0000),
191 ResourceTypeCode::from_raw_id(2017),
192 b"abc".to_vec(),
193 );
194 bif.push_resource(
195 ResourceId::from_raw(0x0010_0001),
196 ResourceTypeCode::from_raw_id(2018),
197 b"defghi".to_vec(),
198 );
199
200 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
201 let parsed = read_bif_from_bytes(&bytes).expect("read should succeed");
202
203 assert_eq!(parsed, bif);
204 }
205
206 #[test]
207 fn writer_is_deterministic_for_synthetic_bif() {
208 let mut bif = Bif::new();
209 bif.push_resource(
210 ResourceId::from_raw(0x0010_0000),
211 ResourceTypeCode::from_raw_id(2017),
212 b"abc".to_vec(),
213 );
214 bif.push_resource(
215 ResourceId::from_raw(0x0010_0001),
216 ResourceTypeCode::from_raw_id(2018),
217 b"defghi".to_vec(),
218 );
219 let first = write_bif_to_vec(&bif).expect("first write should succeed");
220 let second = write_bif_to_vec(&bif).expect("second write should succeed");
221 assert_eq!(first, second, "canonical BIF writer output drifted");
222 }
223
224 #[test]
225 fn writer_aligns_payload_offsets_to_four_bytes() {
226 let mut bif = Bif::new();
227 bif.push_resource(
228 ResourceId::from_raw(1),
229 ResourceTypeCode::from_raw_id(10),
230 vec![1, 2, 3],
231 );
232 bif.push_resource(
233 ResourceId::from_raw(2),
234 ResourceTypeCode::from_raw_id(10),
235 vec![4, 5],
236 );
237
238 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
239
240 let first_offset = u32::from_le_bytes(bytes[24..28].try_into().expect("slice"));
241 let second_offset = u32::from_le_bytes(bytes[40..44].try_into().expect("slice"));
242 assert_eq!(first_offset, 52);
243 assert_eq!(second_offset, 56);
244 }
245
246 #[test]
247 fn roundtrip_preserves_unknown_resource_type_ids() {
248 let mut bif = Bif::new();
249 bif.push_resource(
250 ResourceId::from_raw(7),
251 ResourceTypeCode::from_raw_id(42424),
252 vec![1, 2, 3, 4],
253 );
254
255 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
256 let parsed = read_bif_from_bytes(&bytes).expect("read should succeed");
257
258 assert_eq!(parsed.resources.len(), 1);
259 assert_eq!(parsed.resources[0].resource_type.raw_id(), 42424);
260 assert_eq!(parsed.resources[0].resource_type.known_type(), None);
261 }
262
263 #[test]
264 fn roundtrip_includes_fixed_resource_entries() {
265 let mut bif = Bif::new();
266 bif.push_resource(
267 ResourceId::from_raw(0x0010_0000),
268 ResourceTypeCode::from_raw_id(2017),
269 b"abc".to_vec(),
270 );
271 bif.push_fixed_resource(
272 ResourceId::from_raw(0x0010_4000),
273 ResourceTypeCode::from_raw_id(2027),
274 3,
275 b"fixed".to_vec(),
276 );
277
278 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
279 assert_eq!(
280 u32::from_le_bytes(bytes[8..12].try_into().expect("slice")),
281 1
282 );
283 assert_eq!(
284 u32::from_le_bytes(bytes[12..16].try_into().expect("slice")),
285 1
286 );
287
288 let parsed = read_bif_from_bytes_with_options(
289 &bytes,
290 BifReadOptions {
291 input: BifReadMode::CompatibilityAurora,
292 },
293 )
294 .expect("read should succeed");
295 assert_eq!(parsed, bif);
296 assert_eq!(
297 parsed.resources[1].storage,
298 BifResourceStorage::Fixed { part_count: 3 }
299 );
300 }
301
302 #[test]
303 fn parses_fixed_resource_table_entries() {
304 let mut bytes = vec![0_u8; FILE_HEADER_SIZE + FIXED_ENTRY_SIZE + 4];
305 bytes[0..4].copy_from_slice(&BIF_MAGIC);
306 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
307 bytes[12..16].copy_from_slice(&1_u32.to_le_bytes()); bytes[16..20].copy_from_slice(
309 &u32::try_from(FILE_HEADER_SIZE)
310 .expect("FILE_HEADER_SIZE fits in u32")
311 .to_le_bytes(),
312 );
313
314 let fixed_base = FILE_HEADER_SIZE;
315 bytes[fixed_base..fixed_base + 4].copy_from_slice(&0x0010_4000_u32.to_le_bytes()); bytes[fixed_base + 4..fixed_base + 8].copy_from_slice(
317 &u32::try_from(FILE_HEADER_SIZE + FIXED_ENTRY_SIZE)
318 .expect("header + entry size fits in u32")
319 .to_le_bytes(),
320 ); bytes[fixed_base + 8..fixed_base + 12].copy_from_slice(&2_u32.to_le_bytes()); bytes[fixed_base + 12..fixed_base + 16].copy_from_slice(&4_u32.to_le_bytes()); bytes[fixed_base + 16..fixed_base + 20].copy_from_slice(&2027_u32.to_le_bytes()); bytes[FILE_HEADER_SIZE + FIXED_ENTRY_SIZE..FILE_HEADER_SIZE + FIXED_ENTRY_SIZE + 4]
325 .copy_from_slice(&[1, 2, 3, 4]);
326
327 let bif = read_bif_from_bytes_with_options(
328 &bytes,
329 BifReadOptions {
330 input: BifReadMode::CompatibilityAurora,
331 },
332 )
333 .expect("fixed entries should parse");
334 assert_eq!(bif.resources.len(), 1);
335 assert_eq!(
336 bif.resources[0].storage,
337 BifResourceStorage::Fixed { part_count: 2 }
338 );
339 assert_eq!(bif.resources[0].data, vec![1, 2, 3, 4]);
340 assert_eq!(
341 bif.resource_by_id(rakata_core::ResourceId::from_raw(0x0010_4000_u32)),
342 Some([1, 2, 3, 4].as_slice())
343 );
344 }
345
346 #[test]
347 fn canonical_reader_accepts_fixed_resource_table_entries() {
348 let mut bytes = vec![0_u8; FILE_HEADER_SIZE + FIXED_ENTRY_SIZE];
349 bytes[0..4].copy_from_slice(&BIF_MAGIC);
350 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
351 bytes[12..16].copy_from_slice(&1_u32.to_le_bytes()); bytes[16..20].copy_from_slice(
353 &u32::try_from(FILE_HEADER_SIZE)
354 .expect("FILE_HEADER_SIZE fits in u32")
355 .to_le_bytes(),
356 );
357
358 let bif = read_bif_from_bytes(&bytes).expect("canonical mode should parse");
359 assert_eq!(bif.resources.len(), 0);
360 }
361
362 #[test]
363 fn canonical_reader_ignores_fixed_table_bounds_when_not_loading_fixed_entries() {
364 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
365 bytes[0..4].copy_from_slice(&BIF_MAGIC);
366 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
367 bytes[12..16].copy_from_slice(&1_u32.to_le_bytes()); bytes[16..20].copy_from_slice(
369 &u32::try_from(FILE_HEADER_SIZE)
370 .expect("FILE_HEADER_SIZE fits in u32")
371 .to_le_bytes(),
372 );
373
374 let bif = read_bif_from_bytes(&bytes).expect("canonical mode should ignore fixed table");
375 assert_eq!(bif.resources.len(), 0);
376
377 let err = read_bif_from_bytes_with_options(
378 &bytes,
379 BifReadOptions {
380 input: BifReadMode::CompatibilityAurora,
381 },
382 )
383 .expect_err("compat mode validates fixed table bounds");
384 assert!(matches!(err, BifBinaryError::InvalidHeader(_)));
385 }
386
387 #[test]
388 fn compatibility_reader_accepts_v11_bif_version() {
389 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
390 bytes[0..4].copy_from_slice(&BIF_MAGIC);
391 bytes[4..8].copy_from_slice(&BIF_VERSION_V11);
392 bytes[16..20].copy_from_slice(
393 &u32::try_from(FILE_HEADER_SIZE)
394 .expect("FILE_HEADER_SIZE fits in u32")
395 .to_le_bytes(),
396 );
397
398 let bif = read_bif_from_bytes_with_options(
399 &bytes,
400 BifReadOptions {
401 input: BifReadMode::CompatibilityAurora,
402 },
403 )
404 .expect("compat mode should accept v1.1");
405 assert_eq!(bif.resources.len(), 0);
406 }
407
408 #[test]
409 fn rejects_invalid_magic() {
410 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
411 bytes[0..4].copy_from_slice(b"NOPE");
412 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
413 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
414 assert!(matches!(err, BifBinaryError::InvalidMagic(_)));
415 }
416
417 #[test]
418 fn rejects_invalid_version() {
419 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
420 bytes[0..4].copy_from_slice(&BIF_MAGIC);
421 bytes[4..8].copy_from_slice(b"V9.9");
422 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
423 assert!(matches!(err, BifBinaryError::InvalidVersion(_)));
424 }
425
426 #[test]
427 fn rejects_truncated_header() {
428 let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
429 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
430 assert!(matches!(err, BifBinaryError::InvalidHeader(_)));
431 }
432
433 #[cfg(not(feature = "bzf"))]
434 #[test]
435 fn rejects_out_of_bounds_resource_data() {
436 let mut bytes = vec![0_u8; FILE_HEADER_SIZE + VARIABLE_ENTRY_SIZE];
437 bytes[0..4].copy_from_slice(&BIF_MAGIC);
438 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
439 bytes[8..12].copy_from_slice(&1_u32.to_le_bytes());
440 bytes[16..20].copy_from_slice(
441 &u32::try_from(FILE_HEADER_SIZE)
442 .expect("FILE_HEADER_SIZE fits in u32")
443 .to_le_bytes(),
444 );
445
446 let base = FILE_HEADER_SIZE;
447 bytes[base..base + 4].copy_from_slice(&1_u32.to_le_bytes());
448 bytes[base + 4..base + 8].copy_from_slice(&999_u32.to_le_bytes());
449 bytes[base + 8..base + 12].copy_from_slice(&10_u32.to_le_bytes());
450 bytes[base + 12..base + 16].copy_from_slice(&2017_u32.to_le_bytes());
451
452 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
453 assert!(matches!(err, BifBinaryError::InvalidData(_)));
454 }
455
456 #[test]
457 fn resource_lookup_by_id_accepts_raw_and_typed_values() {
458 let mut bif = Bif::new();
459 let resource_id = ResourceId::from_raw(0x0010_0000);
460 bif.push_resource(
461 resource_id,
462 ResourceTypeCode::from_raw_id(2017),
463 b"abc".to_vec(),
464 );
465
466 assert_eq!(bif.resource_by_id(resource_id), Some(b"abc".as_slice()));
467 assert_eq!(
468 bif.resource_by_id(rakata_core::ResourceId::from_raw(resource_id.raw())),
469 Some(b"abc".as_slice())
470 );
471 }
472}