rakata_formats/key/
writer.rs

1//! KEY binary writer.
2
3use std::io::{Cursor, Write};
4
5use rakata_core::encode_text;
6
7use super::{
8    binary::{write_u16, write_u32},
9    Key, KeyBinaryError, FILE_ENTRY_SIZE, FILE_HEADER_SIZE, KEY_MAGIC, KEY_TEXT_ENCODING,
10    KEY_VERSION_V10,
11};
12
13/// Writes a KEY index to a writer.
14#[cfg_attr(
15    feature = "tracing",
16    tracing::instrument(level = "debug", skip(writer, key))
17)]
18pub fn write_key<W: Write>(writer: &mut W, key: &Key) -> Result<(), KeyBinaryError> {
19    let bif_count = u32::try_from(key.bif_entries.len())
20        .map_err(|_| KeyBinaryError::ValueOverflow("bif_count"))?;
21    let key_count = u32::try_from(key.resources.len())
22        .map_err(|_| KeyBinaryError::ValueOverflow("key_count"))?;
23
24    let file_table_offset = u32::try_from(FILE_HEADER_SIZE)
25        .map_err(|_| KeyBinaryError::ValueOverflow("file_table_offset"))?;
26    let file_table_size = key
27        .bif_entries
28        .len()
29        .checked_mul(FILE_ENTRY_SIZE)
30        .ok_or(KeyBinaryError::ValueOverflow("file_table_size"))?;
31
32    let mut encoded_filenames = Vec::with_capacity(key.bif_entries.len());
33    let mut filenames_table_size = 0usize;
34    for (index, bif_entry) in key.bif_entries.iter().enumerate() {
35        if bif_entry.filename.contains('\0') {
36            return Err(KeyBinaryError::FilenameContainsNul {
37                filename: bif_entry.filename.clone(),
38            });
39        }
40        let encoded = encode_text(&bif_entry.filename, KEY_TEXT_ENCODING).map_err(|source| {
41            KeyBinaryError::TextEncoding {
42                context: format!("bif_entries[{index}].filename"),
43                source,
44            }
45        })?;
46
47        let disk_len = encoded
48            .len()
49            .checked_add(1)
50            .ok_or(KeyBinaryError::ValueOverflow("filename_size"))?;
51        if disk_len > usize::from(u16::MAX) {
52            return Err(KeyBinaryError::FilenameTooLong {
53                filename: bif_entry.filename.clone(),
54                len: disk_len,
55                max: usize::from(u16::MAX),
56            });
57        }
58
59        filenames_table_size = filenames_table_size
60            .checked_add(disk_len)
61            .ok_or(KeyBinaryError::ValueOverflow("filenames_table_size"))?;
62        encoded_filenames.push(encoded);
63    }
64
65    let key_table_offset_usize = FILE_HEADER_SIZE
66        .checked_add(file_table_size)
67        .and_then(|offset| offset.checked_add(filenames_table_size))
68        .ok_or(KeyBinaryError::ValueOverflow("key_table_offset"))?;
69    let key_table_offset = u32::try_from(key_table_offset_usize)
70        .map_err(|_| KeyBinaryError::ValueOverflow("key_table_offset"))?;
71
72    writer.write_all(&KEY_MAGIC)?;
73    writer.write_all(&KEY_VERSION_V10)?;
74    write_u32(writer, bif_count)?;
75    write_u32(writer, key_count)?;
76    write_u32(writer, file_table_offset)?;
77    write_u32(writer, key_table_offset)?;
78    write_u32(writer, key.build_year)?;
79    write_u32(writer, key.build_day)?;
80    writer.write_all(&key.reserved)?;
81
82    let mut next_filename_offset = file_table_offset
83        .checked_add(
84            u32::try_from(file_table_size)
85                .map_err(|_| KeyBinaryError::ValueOverflow("file_table_size"))?,
86        )
87        .ok_or(KeyBinaryError::ValueOverflow("filename_offset"))?;
88
89    for (bif_entry, encoded_filename) in key.bif_entries.iter().zip(encoded_filenames.iter()) {
90        let disk_len = encoded_filename
91            .len()
92            .checked_add(1)
93            .ok_or(KeyBinaryError::ValueOverflow("filename_size"))?;
94        write_u32(writer, bif_entry.file_size)?;
95        write_u32(writer, next_filename_offset)?;
96        write_u16(
97            writer,
98            u16::try_from(disk_len).map_err(|_| KeyBinaryError::ValueOverflow("filename_size"))?,
99        )?;
100        write_u16(writer, bif_entry.drives)?;
101
102        next_filename_offset = next_filename_offset
103            .checked_add(
104                u32::try_from(disk_len)
105                    .map_err(|_| KeyBinaryError::ValueOverflow("filename_size"))?,
106            )
107            .ok_or(KeyBinaryError::ValueOverflow("filename_offset"))?;
108    }
109
110    for encoded_filename in &encoded_filenames {
111        writer.write_all(encoded_filename)?;
112        writer.write_all(&[0_u8])?;
113    }
114
115    for resource in &key.resources {
116        // ResRef guarantees ASCII-only content and length <= 16, so we can
117        // write the bytes directly without encoding or validation.
118        let resref_bytes = resource.resref.as_str().as_bytes();
119        let mut key_resref = [0_u8; 16];
120        key_resref[..resref_bytes.len()].copy_from_slice(resref_bytes);
121        writer.write_all(&key_resref)?;
122        write_u16(writer, resource.resource_type.raw_id())?;
123        write_u32(writer, resource.resource_id.raw())?;
124    }
125
126    crate::trace_debug!(
127        bif_count = key.bif_entries.len(),
128        resource_count = key.resources.len(),
129        "wrote key to writer"
130    );
131    Ok(())
132}
133
134/// Serializes a KEY index to bytes.
135#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(key)))]
136pub fn write_key_to_vec(key: &Key) -> Result<Vec<u8>, KeyBinaryError> {
137    let mut cursor = Cursor::new(Vec::new());
138    write_key(&mut cursor, key)?;
139    let bytes = cursor.into_inner();
140    crate::trace_debug!(bytes_len = bytes.len(), "serialized key to vec");
141    Ok(bytes)
142}