Skip to main content

rakata_formats/key/
reader.rs

1//! KEY binary reader.
2
3use std::io::Read;
4
5use rakata_core::{decode_text_strict, ResRef, ResourceId, ResourceTypeCode};
6
7use super::{
8    binary, Key, KeyBifEntry, KeyBinaryError, KeyReadMode, KeyReadOptions, KeyResourceEntry,
9    FILE_ENTRY_SIZE, FILE_HEADER_SIZE, KEY_ENTRY_SIZE, KEY_MAGIC, KEY_TEXT_ENCODING,
10    KEY_VERSION_V10, KEY_VERSION_V11,
11};
12
13/// Reads a KEY index from a reader.
14///
15/// The stream is consumed from its current position.
16///
17/// # Errors
18///
19/// The same as [`read_key_with_options`] under
20/// [`KeyReadMode::CanonicalK1`](crate::key::KeyReadMode::CanonicalK1), which
21/// takes `V1  ` alone.
22#[cfg_attr(
23    feature = "tracing",
24    tracing::instrument(level = "debug", skip(reader))
25)]
26pub fn read_key<R: Read>(reader: &mut R) -> Result<Key, KeyBinaryError> {
27    read_key_with_options(reader, KeyReadOptions::default())
28}
29
30/// Reads a KEY index from a reader with explicit options.
31///
32/// # Errors
33///
34/// [`KeyBinaryError::Io`] when the stream will not read to end, and whatever
35/// [`read_key_from_bytes_with_options`] reports for the bytes it collected.
36#[cfg_attr(
37    feature = "tracing",
38    tracing::instrument(level = "debug", skip(reader))
39)]
40pub fn read_key_with_options<R: Read>(
41    reader: &mut R,
42    options: KeyReadOptions,
43) -> Result<Key, KeyBinaryError> {
44    let mut bytes = Vec::new();
45    reader.read_to_end(&mut bytes)?;
46    crate::trace_debug!(bytes_len = bytes.len(), "read key bytes from reader");
47    read_key_from_bytes_with_options(&bytes, options)
48}
49
50/// Reads a KEY index from bytes.
51///
52/// # Errors
53///
54/// The same as [`read_key_from_bytes_with_options`] under
55/// [`KeyReadMode::CanonicalK1`](crate::key::KeyReadMode::CanonicalK1).
56#[cfg_attr(
57    feature = "tracing",
58    tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
59)]
60pub fn read_key_from_bytes(bytes: &[u8]) -> Result<Key, KeyBinaryError> {
61    read_key_from_bytes_with_options(bytes, KeyReadOptions::default())
62}
63
64/// Reads a KEY index from bytes with explicit options.
65///
66/// # Errors
67///
68/// [`KeyBinaryError::InvalidMagic`] when the signature is not `KEY `, and
69/// [`KeyBinaryError::InvalidVersion`] for a version `options.input` does not
70/// accept: `CanonicalK1` takes `V1  ` alone, where
71/// [`CompatibilityAurora`](crate::key::KeyReadMode::CompatibilityAurora) also
72/// takes `V1.1`.
73///
74/// [`KeyBinaryError::InvalidHeader`] when the header is truncated, either
75/// table's size overflows, or a table or a BIF filename runs past the end of
76/// `bytes`. [`KeyBinaryError::TextDecoding`] for a BIF filename that is not
77/// valid text in the KEY's encoding.
78///
79/// The BIFs a KEY names are not opened, so a KEY pointing at files that are
80/// not on disk reads without complaint. That failure arrives at first lookup
81/// instead.
82#[cfg_attr(
83    feature = "tracing",
84    tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
85)]
86pub fn read_key_from_bytes_with_options(
87    bytes: &[u8],
88    options: KeyReadOptions,
89) -> Result<Key, KeyBinaryError> {
90    if bytes.len() < FILE_HEADER_SIZE {
91        return Err(KeyBinaryError::InvalidHeader(
92            "file smaller than KEY header".into(),
93        ));
94    }
95
96    let magic = binary::read_fourcc(bytes, 0)?;
97    binary::expect_fourcc(magic, KEY_MAGIC).map_err(KeyBinaryError::InvalidMagic)?;
98
99    let version = binary::read_fourcc(bytes, 4)?;
100    match options.input {
101        KeyReadMode::CanonicalK1 => binary::expect_fourcc(version, KEY_VERSION_V10)
102            .map_err(KeyBinaryError::InvalidVersion)?,
103        KeyReadMode::CompatibilityAurora => {
104            binary::expect_any_fourcc(version, &[KEY_VERSION_V10, KEY_VERSION_V11])
105                .map_err(KeyBinaryError::InvalidVersion)?
106        }
107    }
108
109    let bif_count = binary::checked_to_usize(binary::read_u32(bytes, 8)?, "bif_count")?;
110    let key_count = binary::checked_to_usize(binary::read_u32(bytes, 12)?, "key_count")?;
111    let file_table_offset =
112        binary::checked_to_usize(binary::read_u32(bytes, 16)?, "file_table_offset")?;
113    let key_table_offset =
114        binary::checked_to_usize(binary::read_u32(bytes, 20)?, "key_table_offset")?;
115    let build_year = binary::read_u32(bytes, 24)?;
116    let build_day = binary::read_u32(bytes, 28)?;
117    let mut reserved = [0u8; 32];
118    if let Some(slice) = bytes.get(32..64) {
119        reserved.copy_from_slice(slice);
120    }
121
122    let file_table_size = bif_count
123        .checked_mul(FILE_ENTRY_SIZE)
124        .ok_or_else(|| KeyBinaryError::InvalidHeader("file table size overflow".into()))?;
125    binary::check_slice_in_bounds(bytes, file_table_offset, file_table_size, "file table")?;
126
127    let mut bif_entries = Vec::with_capacity(bif_count);
128    for bif_index in 0..bif_count {
129        let base = file_table_offset + bif_index * FILE_ENTRY_SIZE;
130        let file_size = binary::read_u32(bytes, base)?;
131        let filename_offset =
132            binary::checked_to_usize(binary::read_u32(bytes, base + 4)?, "filename_offset")?;
133        let filename_size = usize::from(binary::read_u16(bytes, base + 8)?);
134        let drives = binary::read_u16(bytes, base + 10)?;
135
136        binary::check_slice_in_bounds(
137            bytes,
138            filename_offset,
139            filename_size,
140            &format!("filename[{bif_index}]"),
141        )?;
142
143        let raw_filename = bytes
144            .get(filename_offset..filename_offset + filename_size)
145            .ok_or_else(|| {
146                KeyBinaryError::InvalidHeader(format!(
147                    "filename bytes missing for entry {bif_index}"
148                ))
149            })?;
150        let filename_end = raw_filename
151            .iter()
152            .rposition(|byte| *byte != 0)
153            .map_or(0, |pos| pos + 1);
154        let filename = decode_text_strict(&raw_filename[..filename_end], KEY_TEXT_ENCODING)
155            .map_err(|source| KeyBinaryError::TextDecoding {
156                context: format!("bif_entries[{bif_index}].filename"),
157                source,
158            })?;
159
160        bif_entries.push(KeyBifEntry {
161            filename,
162            file_size,
163            drives,
164        });
165    }
166
167    let key_table_size = key_count
168        .checked_mul(KEY_ENTRY_SIZE)
169        .ok_or_else(|| KeyBinaryError::InvalidHeader("key table size overflow".into()))?;
170    binary::check_slice_in_bounds(bytes, key_table_offset, key_table_size, "key table")?;
171
172    let mut resources = Vec::with_capacity(key_count);
173    for key_index in 0..key_count {
174        let base = key_table_offset + key_index * KEY_ENTRY_SIZE;
175        let raw_resref = bytes
176            .get(base..base + 16)
177            .ok_or_else(|| KeyBinaryError::InvalidData("resref bytes missing".into()))?;
178        let resref_end = raw_resref.iter().position(|byte| *byte == 0).unwrap_or(16);
179        let resref_str =
180            decode_text_strict(&raw_resref[..resref_end], KEY_TEXT_ENCODING).map_err(|source| {
181                KeyBinaryError::TextDecoding {
182                    context: format!("resources[{key_index}].resref"),
183                    source,
184                }
185            })?;
186        let resref = ResRef::new(&resref_str).map_err(|source| KeyBinaryError::InvalidResRef {
187            context: format!("resources[{key_index}].resref"),
188            source,
189        })?;
190        let resource_type = ResourceTypeCode::from_raw_id(binary::read_u16(bytes, base + 16)?);
191        let resource_id = ResourceId::from_raw(binary::read_u32(bytes, base + 18)?);
192
193        resources.push(KeyResourceEntry {
194            resref,
195            resource_type,
196            resource_id,
197        });
198    }
199
200    let key = Key {
201        build_year,
202        build_day,
203        bif_entries,
204        resources,
205        reserved,
206    };
207    crate::trace_debug!(
208        bif_count = key.bif_entries.len(),
209        resource_count = key.resources.len(),
210        "parsed key from bytes"
211    );
212    Ok(key)
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::key::{pack_resource_id, write_key_to_vec, KeyReadMode};
219    use rakata_core::ResourceId;
220
221    #[test]
222    fn roundtrip_synthetic_key() {
223        let mut key = Key::new();
224        key.build_year = 123;
225        key.build_day = 45;
226        key.push_bif_entry("data/models.bif", 111_222, 1);
227        key.push_bif_entry("data/textures.bif", 333_444, 1);
228        key.push_resource(
229            ResRef::new("m13aa").unwrap(),
230            ResourceTypeCode::from_raw_id(2014),
231            pack_resource_id(0, 1).expect("pack id").into(),
232        );
233        key.push_resource(
234            ResRef::new("m13ab").unwrap(),
235            ResourceTypeCode::from_raw_id(2016),
236            pack_resource_id(0, 2).expect("pack id").into(),
237        );
238        key.push_resource(
239            ResRef::new("lbl_map").unwrap(),
240            ResourceTypeCode::from_raw_id(2017),
241            pack_resource_id(1, 7).expect("pack id").into(),
242        );
243
244        let bytes = write_key_to_vec(&key).expect("write should succeed");
245        let parsed = read_key_from_bytes(&bytes).expect("read should succeed");
246
247        assert_eq!(parsed, key);
248    }
249
250    #[test]
251    fn writer_is_deterministic_for_synthetic_key() {
252        let mut key = Key::new();
253        key.build_year = 123;
254        key.build_day = 45;
255        key.push_bif_entry("data/models.bif", 111_222, 1);
256        key.push_bif_entry("data/textures.bif", 333_444, 1);
257        key.push_resource(
258            ResRef::new("m13aa").unwrap(),
259            ResourceTypeCode::from_raw_id(2014),
260            pack_resource_id(0, 1).expect("pack id").into(),
261        );
262        key.push_resource(
263            ResRef::new("m13ab").unwrap(),
264            ResourceTypeCode::from_raw_id(2016),
265            pack_resource_id(0, 2).expect("pack id").into(),
266        );
267        key.push_resource(
268            ResRef::new("lbl_map").unwrap(),
269            ResourceTypeCode::from_raw_id(2017),
270            pack_resource_id(1, 7).expect("pack id").into(),
271        );
272
273        let first = write_key_to_vec(&key).expect("first write should succeed");
274        let second = write_key_to_vec(&key).expect("second write should succeed");
275        assert_eq!(first, second, "canonical KEY writer output drifted");
276    }
277
278    #[test]
279    fn roundtrip_preserves_unknown_resource_type_ids() {
280        let mut key = Key::new();
281        key.push_bif_entry("data/custom.bif", 42, 0);
282        key.push_resource(
283            ResRef::new("mystery").unwrap(),
284            ResourceTypeCode::from_raw_id(42424),
285            pack_resource_id(0, 5).expect("pack id").into(),
286        );
287
288        let bytes = write_key_to_vec(&key).expect("write should succeed");
289        let parsed = read_key_from_bytes(&bytes).expect("read should succeed");
290
291        assert_eq!(parsed.resources.len(), 1);
292        assert_eq!(parsed.resources[0].resource_type.raw_id(), 42424);
293        assert_eq!(parsed.resources[0].resource_type.known_type(), None);
294    }
295
296    #[test]
297    fn resource_id_helpers_work() {
298        let entry = KeyResourceEntry::from_indices(
299            ResRef::new("alpha").expect("valid resref"),
300            ResourceTypeCode::from_raw_id(2017),
301            0xabc,
302            0x54321,
303        )
304        .expect("indices should fit");
305
306        assert_eq!(entry.bif_index(), 0xabc);
307        assert_eq!(entry.resource_index(), 0x54321);
308    }
309
310    #[test]
311    fn rejects_invalid_resource_id_parts() {
312        let err = pack_resource_id(0x1000, 0).expect_err("must fail");
313        assert!(matches!(err, KeyBinaryError::InvalidResourceIdParts { .. }));
314
315        let err = pack_resource_id(0, 0x10_0000).expect_err("must fail");
316        assert!(matches!(err, KeyBinaryError::InvalidResourceIdParts { .. }));
317    }
318
319    #[test]
320    fn compatibility_reader_accepts_v11_key_version() {
321        let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
322        bytes[0..4].copy_from_slice(&KEY_MAGIC);
323        bytes[4..8].copy_from_slice(&KEY_VERSION_V11);
324        bytes[16..20].copy_from_slice(
325            &u32::try_from(FILE_HEADER_SIZE)
326                .expect("FILE_HEADER_SIZE fits in u32")
327                .to_le_bytes(),
328        );
329        bytes[20..24].copy_from_slice(
330            &u32::try_from(FILE_HEADER_SIZE)
331                .expect("FILE_HEADER_SIZE fits in u32")
332                .to_le_bytes(),
333        );
334
335        let key = read_key_from_bytes_with_options(
336            &bytes,
337            KeyReadOptions {
338                input: KeyReadMode::CompatibilityAurora,
339            },
340        )
341        .expect("v1.1 should parse");
342        assert_eq!(key.bif_entries.len(), 0);
343        assert_eq!(key.resources.len(), 0);
344    }
345
346    #[test]
347    fn canonical_reader_rejects_v11_key_version() {
348        let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
349        bytes[0..4].copy_from_slice(&KEY_MAGIC);
350        bytes[4..8].copy_from_slice(&KEY_VERSION_V11);
351        bytes[16..20].copy_from_slice(
352            &u32::try_from(FILE_HEADER_SIZE)
353                .expect("FILE_HEADER_SIZE fits in u32")
354                .to_le_bytes(),
355        );
356        bytes[20..24].copy_from_slice(
357            &u32::try_from(FILE_HEADER_SIZE)
358                .expect("FILE_HEADER_SIZE fits in u32")
359                .to_le_bytes(),
360        );
361
362        let err = read_key_from_bytes(&bytes).expect_err("canonical mode must fail");
363        assert!(matches!(err, KeyBinaryError::InvalidVersion(_)));
364    }
365
366    #[test]
367    fn rejects_invalid_magic() {
368        let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
369        bytes[0..4].copy_from_slice(b"NOPE");
370        bytes[4..8].copy_from_slice(&KEY_VERSION_V10);
371        let err = read_key_from_bytes(&bytes).expect_err("must fail");
372        assert!(matches!(err, KeyBinaryError::InvalidMagic(_)));
373    }
374
375    #[test]
376    fn rejects_invalid_version() {
377        let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
378        bytes[0..4].copy_from_slice(&KEY_MAGIC);
379        bytes[4..8].copy_from_slice(b"V9.9");
380        let err = read_key_from_bytes(&bytes).expect_err("must fail");
381        assert!(matches!(err, KeyBinaryError::InvalidVersion(_)));
382    }
383
384    #[test]
385    fn rejects_truncated_header() {
386        let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
387        let err = read_key_from_bytes(&bytes).expect_err("must fail");
388        assert!(matches!(err, KeyBinaryError::InvalidHeader(_)));
389    }
390
391    #[test]
392    fn rejects_out_of_bounds_filename_offset() {
393        let mut bytes = vec![0_u8; FILE_HEADER_SIZE + FILE_ENTRY_SIZE];
394        bytes[0..4].copy_from_slice(&KEY_MAGIC);
395        bytes[4..8].copy_from_slice(&KEY_VERSION_V10);
396        bytes[8..12].copy_from_slice(&1_u32.to_le_bytes());
397        bytes[16..20].copy_from_slice(
398            &u32::try_from(FILE_HEADER_SIZE)
399                .expect("FILE_HEADER_SIZE fits in u32")
400                .to_le_bytes(),
401        );
402        bytes[20..24].copy_from_slice(
403            &u32::try_from(FILE_HEADER_SIZE + FILE_ENTRY_SIZE)
404                .expect("header + entry size fits in u32")
405                .to_le_bytes(),
406        );
407
408        // File entry 0.
409        let base = FILE_HEADER_SIZE;
410        bytes[base + 4..base + 8].copy_from_slice(&999_u32.to_le_bytes());
411        bytes[base + 8..base + 10].copy_from_slice(&10_u16.to_le_bytes());
412
413        let err = read_key_from_bytes(&bytes).expect_err("must fail");
414        assert!(matches!(err, KeyBinaryError::InvalidHeader(_)));
415    }
416
417    #[test]
418    fn resref_validation_rejects_long_names() {
419        // ResRef validation happens at construction time, not write time
420        let result = ResRef::new("resref_is_way_too_long");
421        assert!(result.is_err());
422    }
423
424    #[test]
425    fn resource_lookup_by_packed_id_accepts_raw_and_typed_values() {
426        let mut key = Key::new();
427        let packed = ResourceId::from_parts(2, 7).expect("valid packed id");
428        key.push_resource(
429            ResRef::new("alpha").unwrap(),
430            ResourceTypeCode::from_raw_id(2017),
431            packed,
432        );
433
434        assert!(key.resource_by_id(packed).is_some());
435        assert!(key
436            .resource_by_id(rakata_core::ResourceId::from_raw(packed.raw()))
437            .is_some());
438    }
439
440    #[test]
441    fn pack_resource_id_accepts_boundary_values() {
442        let packed = pack_resource_id(0x0fff, 0x0f_ffff).expect("max parts should pack");
443        let resource_id = ResourceId::from_raw(packed);
444
445        assert_eq!(resource_id.bif_index(), 0x0fff);
446        assert_eq!(resource_id.resource_index(), 0x0f_ffff);
447    }
448
449    #[test]
450    fn duplicate_key_entries_keep_order_and_first_lookup() {
451        let mut key = Key::new();
452        key.push_bif_entry("data/test.bif", 10, 0);
453        let first_id = ResourceId::from_parts(0, 1).expect("valid id");
454        let second_id = ResourceId::from_parts(0, 2).expect("valid id");
455        key.push_resource(
456            ResRef::new("dup_res").unwrap(),
457            ResourceTypeCode::from_raw_id(2017),
458            first_id,
459        );
460        key.push_resource(
461            ResRef::new("dup_res").unwrap(),
462            ResourceTypeCode::from_raw_id(2017),
463            second_id,
464        );
465
466        let bytes = write_key_to_vec(&key).expect("write should succeed");
467        let parsed = read_key_from_bytes(&bytes).expect("read should succeed");
468
469        assert_eq!(parsed.resources.len(), 2);
470        assert_eq!(parsed.resources[0].resource_id, first_id);
471        assert_eq!(parsed.resources[1].resource_id, second_id);
472        assert_eq!(
473            parsed
474                .resource(
475                    &ResRef::new("dup_res").unwrap(),
476                    ResourceTypeCode::from_raw_id(2017)
477                )
478                .expect("duplicate key should resolve"),
479            &parsed.resources[0]
480        );
481    }
482
483    #[test]
484    fn reserved_bytes_survive_roundtrip() {
485        let mut key = Key::new();
486        key.reserved[0] = 0xAB;
487        key.reserved[15] = 0xCD;
488        key.reserved[31] = 0xEF;
489
490        let bytes = write_key_to_vec(&key).expect("write should succeed");
491        let parsed = read_key_from_bytes(&bytes).expect("read should succeed");
492
493        assert_eq!(parsed.reserved[0], 0xAB);
494        assert_eq!(parsed.reserved[15], 0xCD);
495        assert_eq!(parsed.reserved[31], 0xEF);
496    }
497}