Skip to main content

rakata_formats/bif/
reader.rs

1//! BIF binary reader.
2
3use 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/// Reads a BIF archive from a reader.
11///
12/// The stream is consumed from its current position.
13///
14/// # Errors
15///
16/// The same as [`read_bif_with_options`] under
17/// [`BifReadMode::CanonicalK1`](crate::bif::BifReadMode::CanonicalK1), which
18/// requires `V1  ` and loads only variable-table entries, the way the K1
19/// runtime does.
20#[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/// Reads a BIF archive from a reader with explicit options.
29///
30/// # Errors
31///
32/// [`BifBinaryError::Io`] when the stream will not read to end, and whatever
33/// [`read_bif_from_bytes_with_options`] reports for the bytes it collected.
34#[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/// Reads a BIF archive from bytes.
49///
50/// # Errors
51///
52/// The same as [`read_bif_from_bytes_with_options`] under
53/// [`BifReadMode::CanonicalK1`](crate::bif::BifReadMode::CanonicalK1).
54#[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/// Reads a BIF archive from bytes with explicit options.
63///
64/// # Errors
65///
66/// [`BifBinaryError::InvalidMagic`] when the signature is neither `BIFF` nor
67/// `BZF `, and [`BifBinaryError::InvalidVersion`] for a version
68/// `options.input` does not accept: `CanonicalK1` takes `V1  ` alone, where
69/// [`CompatibilityAurora`](crate::bif::BifReadMode::CompatibilityAurora) also
70/// takes `V1.1`.
71///
72/// [`BifBinaryError::InvalidHeader`] when the header is truncated, either
73/// table runs past the end of `bytes`, or the two entry counts sum past
74/// `u32`. [`BifBinaryError::InvalidData`] when an entry's declared data slice
75/// escapes the archive.
76///
77/// [`BifBinaryError::BzfFeatureDisabled`] for a `BZF ` archive when the crate
78/// was built without the `bzf` feature. The signature is recognised either
79/// way, so this says the payload cannot be decompressed rather than that the
80/// file is unknown.
81///
82/// A BIF names no resources: the resrefs live in the `chitin.key` that points
83/// at it, so nothing here can fail on a name.
84#[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    // Only the `bzf`-off build has a test that needs this, so importing it
200    // unconditionally would read as unused whenever the feature is on.
201    #[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()); // fixed_count
346        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()); // id
354        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        ); // offset
359        bytes[fixed_base + 8..fixed_base + 12].copy_from_slice(&2_u32.to_le_bytes()); // part_count
360        bytes[fixed_base + 12..fixed_base + 16].copy_from_slice(&4_u32.to_le_bytes()); // size
361        bytes[fixed_base + 16..fixed_base + 20].copy_from_slice(&2027_u32.to_le_bytes()); // type
362        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()); // fixed_count
390        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()); // fixed_count
406        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}