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