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#[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/// Reads a BIF archive from a reader with explicit options.
22#[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/// Reads a BIF archive from bytes.
37#[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/// Reads a BIF archive from bytes with explicit options.
46#[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    // Only the `bzf`-off build has a test that needs this, so importing it
162    // unconditionally would read as unused whenever the feature is on.
163    #[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()); // fixed_count
308        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()); // id
316        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        ); // offset
321        bytes[fixed_base + 8..fixed_base + 12].copy_from_slice(&2_u32.to_le_bytes()); // part_count
322        bytes[fixed_base + 12..fixed_base + 16].copy_from_slice(&4_u32.to_le_bytes()); // size
323        bytes[fixed_base + 16..fixed_base + 20].copy_from_slice(&2027_u32.to_le_bytes()); // type
324        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()); // fixed_count
352        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()); // fixed_count
368        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}