1use std::io::Read;
4
5#[cfg(feature = "bzf")]
6use std::io::Cursor;
7
8#[cfg(feature = "bzf")]
9use lzma_rust2::{LzmaOptions, LzmaReader};
10use rakata_core::{ResourceId, ResourceTypeCode};
11
12use super::{
13 binary, Bif, BifBinaryError, BifContainer, BifReadMode, BifReadOptions, BifResource,
14 BifResourceStorage, BIF_MAGIC, BIF_VERSION_V10, BIF_VERSION_V11, BZF_MAGIC, FILE_HEADER_SIZE,
15 FIXED_ENTRY_SIZE, VARIABLE_ENTRY_SIZE,
16};
17
18#[cfg_attr(
22 feature = "tracing",
23 tracing::instrument(level = "debug", skip(reader))
24)]
25pub fn read_bif<R: Read>(reader: &mut R) -> Result<Bif, BifBinaryError> {
26 read_bif_with_options(reader, BifReadOptions::default())
27}
28
29#[cfg_attr(
31 feature = "tracing",
32 tracing::instrument(level = "debug", skip(reader))
33)]
34pub fn read_bif_with_options<R: Read>(
35 reader: &mut R,
36 options: BifReadOptions,
37) -> Result<Bif, BifBinaryError> {
38 let mut bytes = Vec::new();
39 reader.read_to_end(&mut bytes)?;
40 crate::trace_debug!(bytes_len = bytes.len(), "read bif/bzf bytes from reader");
41 read_bif_from_bytes_with_options(&bytes, options)
42}
43
44#[cfg_attr(
46 feature = "tracing",
47 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
48)]
49pub fn read_bif_from_bytes(bytes: &[u8]) -> Result<Bif, BifBinaryError> {
50 read_bif_from_bytes_with_options(bytes, BifReadOptions::default())
51}
52
53#[cfg_attr(
55 feature = "tracing",
56 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
57)]
58pub fn read_bif_from_bytes_with_options(
59 bytes: &[u8],
60 options: BifReadOptions,
61) -> Result<Bif, BifBinaryError> {
62 if bytes.len() < FILE_HEADER_SIZE {
63 return Err(BifBinaryError::InvalidHeader(
64 "file smaller than BIF header".into(),
65 ));
66 }
67
68 let magic = binary::read_fourcc(bytes, 0)?;
69 let container = if magic == BZF_MAGIC {
70 #[cfg(not(feature = "bzf"))]
71 {
72 return Err(BifBinaryError::BzfFeatureDisabled);
73 }
74 #[cfg(feature = "bzf")]
75 {
76 BifContainer::Bzf
77 }
78 } else {
79 binary::expect_fourcc(magic, BIF_MAGIC).map_err(BifBinaryError::InvalidMagic)?;
80 BifContainer::Biff
81 };
82
83 let version = binary::read_fourcc(bytes, 4)?;
84 match options.input {
85 BifReadMode::CanonicalK1 => binary::expect_fourcc(version, BIF_VERSION_V10)
86 .map_err(BifBinaryError::InvalidVersion)?,
87 BifReadMode::CompatibilityAurora => {
88 binary::expect_any_fourcc(version, &[BIF_VERSION_V10, BIF_VERSION_V11])
89 .map_err(BifBinaryError::InvalidVersion)?
90 }
91 }
92
93 let variable_count = binary::checked_to_usize(binary::read_u32(bytes, 8)?, "variable_count")?;
94 let fixed_count = binary::checked_to_usize(binary::read_u32(bytes, 12)?, "fixed_count")?;
95
96 let variable_table_offset =
97 binary::checked_to_usize(binary::read_u32(bytes, 16)?, "variable_table_offset")?;
98 let variable_table_size = variable_count
99 .checked_mul(VARIABLE_ENTRY_SIZE)
100 .ok_or_else(|| BifBinaryError::InvalidHeader("variable table size overflow".into()))?;
101 binary::check_slice_in_bounds(
102 bytes,
103 variable_table_offset,
104 variable_table_size,
105 "variable resource table",
106 )?;
107
108 let parse_fixed_entries = matches!(options.input, BifReadMode::CompatibilityAurora);
109 let fixed_table_offset = variable_table_offset
110 .checked_add(variable_table_size)
111 .ok_or_else(|| BifBinaryError::InvalidHeader("fixed table offset overflow".into()))?;
112 if parse_fixed_entries {
113 let fixed_table_size = fixed_count
114 .checked_mul(FIXED_ENTRY_SIZE)
115 .ok_or_else(|| BifBinaryError::InvalidHeader("fixed table size overflow".into()))?;
116 binary::check_slice_in_bounds(
117 bytes,
118 fixed_table_offset,
119 fixed_table_size,
120 "fixed resource table",
121 )?;
122 }
123
124 let total_entry_count = if parse_fixed_entries {
125 variable_count
126 .checked_add(fixed_count)
127 .ok_or_else(|| BifBinaryError::InvalidHeader("resource count overflow".into()))?
128 } else {
129 variable_count
130 };
131 let mut resource_entries = Vec::with_capacity(total_entry_count);
132 for resource_index in 0..variable_count {
133 let base = variable_table_offset + resource_index * VARIABLE_ENTRY_SIZE;
134 let resource_id = binary::read_u32(bytes, base)?;
135 let data_offset_u32 = binary::read_u32(bytes, base + 4)?;
136 let data_offset = binary::checked_to_usize(data_offset_u32, "resource_data_offset")?;
137 let data_size =
138 binary::checked_to_usize(binary::read_u32(bytes, base + 8)?, "resource_data_size")?;
139 let resource_type_u32 = binary::read_u32(bytes, base + 12)?;
140 let resource_type_id = u16::try_from(resource_type_u32).map_err(|_| {
141 BifBinaryError::InvalidData(format!(
142 "resource[{resource_index}] type id {resource_type_u32} exceeds u16 range"
143 ))
144 })?;
145
146 resource_entries.push((
147 resource_id,
148 ResourceTypeCode::from_raw_id(resource_type_id),
149 data_offset,
150 data_size,
151 BifResourceStorage::Variable,
152 data_offset_u32,
153 ));
154 }
155
156 if parse_fixed_entries {
157 for fixed_index in 0..fixed_count {
158 let base = fixed_table_offset + fixed_index * FIXED_ENTRY_SIZE;
159 let resource_id = binary::read_u32(bytes, base)?;
160 let data_offset_u32 = binary::read_u32(bytes, base + 4)?;
161 let data_offset =
162 binary::checked_to_usize(data_offset_u32, "fixed_resource_data_offset")?;
163 let part_count = binary::read_u32(bytes, base + 8)?;
164 let data_size = binary::checked_to_usize(
165 binary::read_u32(bytes, base + 12)?,
166 "fixed_resource_data_size",
167 )?;
168 let resource_type_u32 = binary::read_u32(bytes, base + 16)?;
169 let resource_type_id = u16::try_from(resource_type_u32).map_err(|_| {
170 BifBinaryError::InvalidData(format!(
171 "fixed resource[{fixed_index}] type id {resource_type_u32} exceeds u16 range"
172 ))
173 })?;
174
175 resource_entries.push((
176 resource_id,
177 ResourceTypeCode::from_raw_id(resource_type_id),
178 data_offset,
179 data_size,
180 BifResourceStorage::Fixed { part_count },
181 data_offset_u32,
182 ));
183 }
184 }
185
186 let packed_sizes = if container == BifContainer::Bzf {
187 Some(compute_bzf_packed_sizes(&resource_entries, bytes.len())?)
188 } else {
189 None
190 };
191
192 let mut resources = Vec::with_capacity(resource_entries.len());
193 for (
194 resource_index,
195 (resource_id, resource_type, data_offset, data_size, storage, data_offset_u32),
196 ) in resource_entries.into_iter().enumerate()
197 {
198 let data = if let Some(packed_sizes) = &packed_sizes {
199 let packed_size = packed_sizes[resource_index];
200 check_data_slice_in_bounds(
201 bytes,
202 data_offset,
203 packed_size,
204 &format!("resource packed data[{resource_index}]"),
205 )?;
206 let packed_data = bytes
207 .get(data_offset..data_offset + packed_size)
208 .ok_or_else(|| {
209 BifBinaryError::InvalidData(format!(
210 "resource packed data slice missing for index {resource_index}"
211 ))
212 })?;
213 #[cfg(feature = "bzf")]
214 {
215 decode_bzf_payload(packed_data, data_size)?
216 }
217 #[cfg(not(feature = "bzf"))]
218 {
219 let _ = packed_data;
220 return Err(BifBinaryError::BzfFeatureDisabled);
221 }
222 } else {
223 check_data_slice_in_bounds(
224 bytes,
225 data_offset,
226 data_size,
227 &format!("resource data[{resource_index}]"),
228 )?;
229 bytes
230 .get(data_offset..data_offset + data_size)
231 .ok_or_else(|| {
232 BifBinaryError::InvalidData(format!(
233 "resource data slice missing for index {resource_index}"
234 ))
235 })?
236 .to_vec()
237 };
238
239 resources.push(BifResource {
240 resource_id: ResourceId::from_raw(resource_id),
241 resource_type,
242 storage,
243 data,
244 source_data_offset: Some(data_offset_u32),
245 });
246 }
247
248 let bif = Bif {
249 container,
250 resources,
251 };
252 crate::trace_debug!(
253 container = ?bif.container,
254 resource_count = bif.resources.len(),
255 "parsed bif/bzf from bytes"
256 );
257 Ok(bif)
258}
259
260fn check_data_slice_in_bounds(
261 bytes: &[u8],
262 offset: usize,
263 size: usize,
264 label: &str,
265) -> Result<(), BifBinaryError> {
266 binary::check_slice_in_bounds(bytes, offset, size, label)
267 .map_err(|error| BifBinaryError::InvalidData(error.to_string()))
268}
269
270fn compute_bzf_packed_sizes(
271 entries: &[(u32, ResourceTypeCode, usize, usize, BifResourceStorage, u32)],
272 file_len: usize,
273) -> Result<Vec<usize>, BifBinaryError> {
274 let mut by_offset: Vec<(usize, usize)> = entries
275 .iter()
276 .enumerate()
277 .map(|(index, (_, _, offset, _, _, _))| (index, *offset))
278 .collect();
279 by_offset.sort_by_key(|(_, offset)| *offset);
280
281 let mut packed_sizes = vec![0_usize; entries.len()];
282 for (entry_position, (entry_index, offset)) in by_offset.iter().enumerate() {
283 let end = if let Some((_, next_offset)) = by_offset.get(entry_position + 1) {
284 *next_offset
285 } else {
286 file_len
287 };
288 let packed_size = end.checked_sub(*offset).ok_or_else(|| {
289 BifBinaryError::InvalidData(format!(
290 "resource packed offset ordering invalid at entry index {entry_index}"
291 ))
292 })?;
293 packed_sizes[*entry_index] = packed_size;
294 }
295
296 Ok(packed_sizes)
297}
298
299#[cfg(feature = "bzf")]
300fn decode_bzf_payload(payload: &[u8], expected_size: usize) -> Result<Vec<u8>, BifBinaryError> {
301 if expected_size == 0 {
302 return Ok(Vec::new());
303 }
304
305 let mut attempt_len = payload.len();
306 loop {
307 let attempt = &payload[..attempt_len];
308 let decoded = decode_bzf_payload_raw(attempt, expected_size);
309 if let Ok(value) = decoded {
310 return Ok(value);
311 }
312
313 let trimmed_len = attempt
314 .iter()
315 .rposition(|byte| *byte != 0)
316 .map_or(0, |index| index + 1);
317 if trimmed_len == attempt_len {
318 return decoded;
319 }
320 attempt_len = trimmed_len;
321 }
322}
323
324#[cfg(feature = "bzf")]
325fn decode_bzf_payload_raw(payload: &[u8], expected_size: usize) -> Result<Vec<u8>, BifBinaryError> {
326 let mut reader = LzmaReader::new(
327 Cursor::new(payload),
328 u64::try_from(expected_size).expect("expected_size fits in u64"),
329 LzmaOptions::LC_DEFAULT,
330 LzmaOptions::LP_DEFAULT,
331 LzmaOptions::PB_DEFAULT,
332 LzmaOptions::DICT_SIZE_DEFAULT,
333 None,
334 )
335 .map_err(|error| {
336 BifBinaryError::InvalidData(format!("failed to initialize BZF decoder: {error}"))
337 })?;
338
339 let mut out = Vec::new();
340 reader.read_to_end(&mut out).map_err(|error| {
341 BifBinaryError::InvalidData(format!("failed to decode BZF payload: {error}"))
342 })?;
343 if out.len() != expected_size {
344 return Err(BifBinaryError::InvalidData(format!(
345 "decoded BZF payload length mismatch (expected {expected_size}, got {})",
346 out.len()
347 )));
348 }
349 Ok(out)
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::bif::{write_bif_to_vec, BifReadMode};
356 use rakata_core::ResourceId;
357
358 const K1_PLAYER_BIF: &[u8] = include_bytes!(concat!(
359 env!("CARGO_MANIFEST_DIR"),
360 "/../../fixtures/k1_player.bif"
361 ));
362
363 #[test]
364 fn parses_bif_fixture() {
365 let bif = read_bif_from_bytes(K1_PLAYER_BIF).expect("fixture should parse");
366 assert_eq!(bif.resources.len(), 3);
367
368 let first = &bif.resources[0];
369 assert_eq!(first.resource_id, ResourceId::from_raw(0x0140_0000));
370 assert_eq!(first.resource_type.raw_id(), 2002);
371
372 let second = &bif.resources[1];
373 assert_eq!(second.resource_id, ResourceId::from_raw(0x0140_0001));
374 assert_eq!(second.resource_type.raw_id(), 3008);
375 }
376
377 #[test]
378 fn roundtrip_synthetic_bif() {
379 let mut bif = Bif::new();
380 bif.push_resource(
381 ResourceId::from_raw(0x0010_0000),
382 ResourceTypeCode::from_raw_id(2017),
383 b"abc".to_vec(),
384 );
385 bif.push_resource(
386 ResourceId::from_raw(0x0010_0001),
387 ResourceTypeCode::from_raw_id(2018),
388 b"defghi".to_vec(),
389 );
390
391 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
392 let parsed = read_bif_from_bytes(&bytes).expect("read should succeed");
393
394 assert_eq!(parsed, bif);
395 }
396
397 #[test]
398 fn writer_is_deterministic_for_synthetic_bif() {
399 let mut bif = Bif::new();
400 bif.push_resource(
401 ResourceId::from_raw(0x0010_0000),
402 ResourceTypeCode::from_raw_id(2017),
403 b"abc".to_vec(),
404 );
405 bif.push_resource(
406 ResourceId::from_raw(0x0010_0001),
407 ResourceTypeCode::from_raw_id(2018),
408 b"defghi".to_vec(),
409 );
410 let first = write_bif_to_vec(&bif).expect("first write should succeed");
411 let second = write_bif_to_vec(&bif).expect("second write should succeed");
412 assert_eq!(first, second, "canonical BIF writer output drifted");
413 }
414
415 #[test]
416 fn writer_aligns_payload_offsets_to_four_bytes() {
417 let mut bif = Bif::new();
418 bif.push_resource(
419 ResourceId::from_raw(1),
420 ResourceTypeCode::from_raw_id(10),
421 vec![1, 2, 3],
422 );
423 bif.push_resource(
424 ResourceId::from_raw(2),
425 ResourceTypeCode::from_raw_id(10),
426 vec![4, 5],
427 );
428
429 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
430
431 let first_offset = u32::from_le_bytes(bytes[24..28].try_into().expect("slice"));
432 let second_offset = u32::from_le_bytes(bytes[40..44].try_into().expect("slice"));
433 assert_eq!(first_offset, 52);
434 assert_eq!(second_offset, 56);
435 }
436
437 #[test]
438 fn roundtrip_preserves_unknown_resource_type_ids() {
439 let mut bif = Bif::new();
440 bif.push_resource(
441 ResourceId::from_raw(7),
442 ResourceTypeCode::from_raw_id(42424),
443 vec![1, 2, 3, 4],
444 );
445
446 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
447 let parsed = read_bif_from_bytes(&bytes).expect("read should succeed");
448
449 assert_eq!(parsed.resources.len(), 1);
450 assert_eq!(parsed.resources[0].resource_type.raw_id(), 42424);
451 assert_eq!(parsed.resources[0].resource_type.known_type(), None);
452 }
453
454 #[test]
455 fn roundtrip_includes_fixed_resource_entries() {
456 let mut bif = Bif::new();
457 bif.push_resource(
458 ResourceId::from_raw(0x0010_0000),
459 ResourceTypeCode::from_raw_id(2017),
460 b"abc".to_vec(),
461 );
462 bif.push_fixed_resource(
463 ResourceId::from_raw(0x0010_4000),
464 ResourceTypeCode::from_raw_id(2027),
465 3,
466 b"fixed".to_vec(),
467 );
468
469 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
470 assert_eq!(
471 u32::from_le_bytes(bytes[8..12].try_into().expect("slice")),
472 1
473 );
474 assert_eq!(
475 u32::from_le_bytes(bytes[12..16].try_into().expect("slice")),
476 1
477 );
478
479 let parsed = read_bif_from_bytes_with_options(
480 &bytes,
481 BifReadOptions {
482 input: BifReadMode::CompatibilityAurora,
483 },
484 )
485 .expect("read should succeed");
486 assert_eq!(parsed, bif);
487 assert_eq!(
488 parsed.resources[1].storage,
489 BifResourceStorage::Fixed { part_count: 3 }
490 );
491 }
492
493 #[test]
494 fn parses_fixed_resource_table_entries() {
495 let mut bytes = vec![0_u8; FILE_HEADER_SIZE + FIXED_ENTRY_SIZE + 4];
496 bytes[0..4].copy_from_slice(&BIF_MAGIC);
497 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
498 bytes[12..16].copy_from_slice(&1_u32.to_le_bytes()); bytes[16..20].copy_from_slice(
500 &u32::try_from(FILE_HEADER_SIZE)
501 .expect("FILE_HEADER_SIZE fits in u32")
502 .to_le_bytes(),
503 );
504
505 let fixed_base = FILE_HEADER_SIZE;
506 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(
508 &u32::try_from(FILE_HEADER_SIZE + FIXED_ENTRY_SIZE)
509 .expect("header + entry size fits in u32")
510 .to_le_bytes(),
511 ); 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]
516 .copy_from_slice(&[1, 2, 3, 4]);
517
518 let bif = read_bif_from_bytes_with_options(
519 &bytes,
520 BifReadOptions {
521 input: BifReadMode::CompatibilityAurora,
522 },
523 )
524 .expect("fixed entries should parse");
525 assert_eq!(bif.resources.len(), 1);
526 assert_eq!(
527 bif.resources[0].storage,
528 BifResourceStorage::Fixed { part_count: 2 }
529 );
530 assert_eq!(bif.resources[0].data, vec![1, 2, 3, 4]);
531 assert_eq!(
532 bif.resource_by_id(rakata_core::ResourceId::from_raw(0x0010_4000_u32)),
533 Some([1, 2, 3, 4].as_slice())
534 );
535 }
536
537 #[test]
538 fn canonical_reader_accepts_fixed_resource_table_entries() {
539 let mut bytes = vec![0_u8; FILE_HEADER_SIZE + FIXED_ENTRY_SIZE];
540 bytes[0..4].copy_from_slice(&BIF_MAGIC);
541 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
542 bytes[12..16].copy_from_slice(&1_u32.to_le_bytes()); bytes[16..20].copy_from_slice(
544 &u32::try_from(FILE_HEADER_SIZE)
545 .expect("FILE_HEADER_SIZE fits in u32")
546 .to_le_bytes(),
547 );
548
549 let bif = read_bif_from_bytes(&bytes).expect("canonical mode should parse");
550 assert_eq!(bif.resources.len(), 0);
551 }
552
553 #[test]
554 fn canonical_reader_ignores_fixed_table_bounds_when_not_loading_fixed_entries() {
555 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
556 bytes[0..4].copy_from_slice(&BIF_MAGIC);
557 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
558 bytes[12..16].copy_from_slice(&1_u32.to_le_bytes()); bytes[16..20].copy_from_slice(
560 &u32::try_from(FILE_HEADER_SIZE)
561 .expect("FILE_HEADER_SIZE fits in u32")
562 .to_le_bytes(),
563 );
564
565 let bif = read_bif_from_bytes(&bytes).expect("canonical mode should ignore fixed table");
566 assert_eq!(bif.resources.len(), 0);
567
568 let err = read_bif_from_bytes_with_options(
569 &bytes,
570 BifReadOptions {
571 input: BifReadMode::CompatibilityAurora,
572 },
573 )
574 .expect_err("compat mode validates fixed table bounds");
575 assert!(matches!(err, BifBinaryError::InvalidHeader(_)));
576 }
577
578 #[test]
579 fn compatibility_reader_accepts_v11_bif_version() {
580 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
581 bytes[0..4].copy_from_slice(&BIF_MAGIC);
582 bytes[4..8].copy_from_slice(&BIF_VERSION_V11);
583 bytes[16..20].copy_from_slice(
584 &u32::try_from(FILE_HEADER_SIZE)
585 .expect("FILE_HEADER_SIZE fits in u32")
586 .to_le_bytes(),
587 );
588
589 let bif = read_bif_from_bytes_with_options(
590 &bytes,
591 BifReadOptions {
592 input: BifReadMode::CompatibilityAurora,
593 },
594 )
595 .expect("compat mode should accept v1.1");
596 assert_eq!(bif.resources.len(), 0);
597 }
598
599 #[test]
600 fn rejects_invalid_magic() {
601 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
602 bytes[0..4].copy_from_slice(b"NOPE");
603 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
604 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
605 assert!(matches!(err, BifBinaryError::InvalidMagic(_)));
606 }
607
608 #[test]
609 fn rejects_invalid_version() {
610 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
611 bytes[0..4].copy_from_slice(&BIF_MAGIC);
612 bytes[4..8].copy_from_slice(b"V9.9");
613 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
614 assert!(matches!(err, BifBinaryError::InvalidVersion(_)));
615 }
616
617 #[test]
618 fn rejects_truncated_header() {
619 let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
620 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
621 assert!(matches!(err, BifBinaryError::InvalidHeader(_)));
622 }
623
624 #[cfg(not(feature = "bzf"))]
625 #[test]
626 fn bzf_requires_feature_when_disabled() {
627 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
628 bytes[0..4].copy_from_slice(&BZF_MAGIC);
629 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
630
631 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
632 assert!(matches!(err, BifBinaryError::BzfFeatureDisabled));
633 }
634
635 #[cfg(feature = "bzf")]
636 #[test]
637 fn roundtrip_synthetic_bzf() {
638 let mut bif = Bif::new();
639 bif.container = BifContainer::Bzf;
640 bif.push_resource(
641 ResourceId::from_raw(0x0010_0000),
642 ResourceTypeCode::from_raw_id(2017),
643 b"Hello World 1".to_vec(),
644 );
645 bif.push_resource(
646 ResourceId::from_raw(0x0010_0001),
647 ResourceTypeCode::from_raw_id(2017),
648 b"Hello World 2".to_vec(),
649 );
650 bif.push_fixed_resource(
651 ResourceId::from_raw(0x0010_4000),
652 ResourceTypeCode::from_raw_id(2017),
653 2,
654 b"Hello Fixed".to_vec(),
655 );
656
657 let bytes = write_bif_to_vec(&bif).expect("write should succeed");
658 assert_eq!(&bytes[0..4], b"BZF ");
659 let parsed = read_bif_from_bytes_with_options(
660 &bytes,
661 BifReadOptions {
662 input: BifReadMode::CompatibilityAurora,
663 },
664 )
665 .expect("read should succeed");
666 assert_eq!(parsed, bif);
667 }
668
669 #[cfg(feature = "bzf")]
670 #[test]
671 fn bzf_reader_tolerates_trailing_zero_padding() {
672 let mut bif = Bif::new();
673 bif.container = BifContainer::Bzf;
674 bif.push_resource(
675 ResourceId::from_raw(0x0010_0000),
676 ResourceTypeCode::from_raw_id(2017),
677 b"Hello World".to_vec(),
678 );
679
680 let mut bytes = write_bif_to_vec(&bif).expect("write should succeed");
681 bytes.extend_from_slice(&[0, 0, 0, 0]);
682 let parsed = read_bif_from_bytes(&bytes).expect("read should succeed");
683 assert_eq!(parsed, bif);
684 }
685
686 #[test]
687 fn rejects_out_of_bounds_resource_data() {
688 let mut bytes = vec![0_u8; FILE_HEADER_SIZE + VARIABLE_ENTRY_SIZE];
689 bytes[0..4].copy_from_slice(&BIF_MAGIC);
690 bytes[4..8].copy_from_slice(&BIF_VERSION_V10);
691 bytes[8..12].copy_from_slice(&1_u32.to_le_bytes());
692 bytes[16..20].copy_from_slice(
693 &u32::try_from(FILE_HEADER_SIZE)
694 .expect("FILE_HEADER_SIZE fits in u32")
695 .to_le_bytes(),
696 );
697
698 let base = FILE_HEADER_SIZE;
699 bytes[base..base + 4].copy_from_slice(&1_u32.to_le_bytes());
700 bytes[base + 4..base + 8].copy_from_slice(&999_u32.to_le_bytes());
701 bytes[base + 8..base + 12].copy_from_slice(&10_u32.to_le_bytes());
702 bytes[base + 12..base + 16].copy_from_slice(&2017_u32.to_le_bytes());
703
704 let err = read_bif_from_bytes(&bytes).expect_err("must fail");
705 assert!(matches!(err, BifBinaryError::InvalidData(_)));
706 }
707
708 #[test]
709 fn resource_lookup_by_id_accepts_raw_and_typed_values() {
710 let mut bif = Bif::new();
711 let resource_id = ResourceId::from_raw(0x0010_0000);
712 bif.push_resource(
713 resource_id,
714 ResourceTypeCode::from_raw_id(2017),
715 b"abc".to_vec(),
716 );
717
718 assert_eq!(bif.resource_by_id(resource_id), Some(b"abc".as_slice()));
719 assert_eq!(
720 bif.resource_by_id(rakata_core::ResourceId::from_raw(resource_id.raw())),
721 Some(b"abc".as_slice())
722 );
723 }
724}