1use 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#[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#[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#[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#[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 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 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}