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