Skip to main content

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///
15/// # Errors
16///
17/// [`KeyBinaryError::InvalidData`] when a BIF filename holds a NUL, which the
18/// null-terminated filename table cannot represent.
19///
20/// [`KeyBinaryError::ValueOverflow`] when either count, a table offset or
21/// size, or a filename's on-disk length will not fit its field, naming which.
22/// The filename length is a `u16`, so it is the narrowest of these.
23///
24/// [`KeyBinaryError::TextEncoding`] when a filename has no form in the KEY's
25/// encoding, and [`KeyBinaryError::Io`] when the writer fails, which can leave
26/// a partial file.
27#[cfg_attr(
28    feature = "tracing",
29    tracing::instrument(level = "debug", skip(writer, key))
30)]
31pub fn write_key<W: Write>(writer: &mut W, key: &Key) -> Result<(), KeyBinaryError> {
32    let bif_count = u32::try_from(key.bif_entries.len())
33        .map_err(|_| KeyBinaryError::ValueOverflow("bif_count"))?;
34    let key_count = u32::try_from(key.resources.len())
35        .map_err(|_| KeyBinaryError::ValueOverflow("key_count"))?;
36
37    let file_table_offset = u32::try_from(FILE_HEADER_SIZE)
38        .map_err(|_| KeyBinaryError::ValueOverflow("file_table_offset"))?;
39    let file_table_size = key
40        .bif_entries
41        .len()
42        .checked_mul(FILE_ENTRY_SIZE)
43        .ok_or(KeyBinaryError::ValueOverflow("file_table_size"))?;
44
45    let mut encoded_filenames = Vec::with_capacity(key.bif_entries.len());
46    let mut filenames_table_size = 0usize;
47    for (index, bif_entry) in key.bif_entries.iter().enumerate() {
48        if bif_entry.filename.contains('\0') {
49            return Err(KeyBinaryError::FilenameContainsNul {
50                filename: bif_entry.filename.clone(),
51            });
52        }
53        let encoded = encode_text(&bif_entry.filename, KEY_TEXT_ENCODING).map_err(|source| {
54            KeyBinaryError::TextEncoding {
55                context: format!("bif_entries[{index}].filename"),
56                source,
57            }
58        })?;
59
60        let disk_len = encoded
61            .len()
62            .checked_add(1)
63            .ok_or(KeyBinaryError::ValueOverflow("filename_size"))?;
64        if disk_len > usize::from(u16::MAX) {
65            return Err(KeyBinaryError::FilenameTooLong {
66                filename: bif_entry.filename.clone(),
67                len: disk_len,
68                max: usize::from(u16::MAX),
69            });
70        }
71
72        filenames_table_size = filenames_table_size
73            .checked_add(disk_len)
74            .ok_or(KeyBinaryError::ValueOverflow("filenames_table_size"))?;
75        encoded_filenames.push(encoded);
76    }
77
78    let key_table_offset_usize = FILE_HEADER_SIZE
79        .checked_add(file_table_size)
80        .and_then(|offset| offset.checked_add(filenames_table_size))
81        .ok_or(KeyBinaryError::ValueOverflow("key_table_offset"))?;
82    let key_table_offset = u32::try_from(key_table_offset_usize)
83        .map_err(|_| KeyBinaryError::ValueOverflow("key_table_offset"))?;
84
85    writer.write_all(&KEY_MAGIC)?;
86    writer.write_all(&KEY_VERSION_V10)?;
87    write_u32(writer, bif_count)?;
88    write_u32(writer, key_count)?;
89    write_u32(writer, file_table_offset)?;
90    write_u32(writer, key_table_offset)?;
91    write_u32(writer, key.build_year)?;
92    write_u32(writer, key.build_day)?;
93    writer.write_all(&key.reserved)?;
94
95    let mut next_filename_offset = file_table_offset
96        .checked_add(
97            u32::try_from(file_table_size)
98                .map_err(|_| KeyBinaryError::ValueOverflow("file_table_size"))?,
99        )
100        .ok_or(KeyBinaryError::ValueOverflow("filename_offset"))?;
101
102    for (bif_entry, encoded_filename) in key.bif_entries.iter().zip(encoded_filenames.iter()) {
103        let disk_len = encoded_filename
104            .len()
105            .checked_add(1)
106            .ok_or(KeyBinaryError::ValueOverflow("filename_size"))?;
107        write_u32(writer, bif_entry.file_size)?;
108        write_u32(writer, next_filename_offset)?;
109        write_u16(
110            writer,
111            u16::try_from(disk_len).map_err(|_| KeyBinaryError::ValueOverflow("filename_size"))?,
112        )?;
113        write_u16(writer, bif_entry.drives)?;
114
115        next_filename_offset = next_filename_offset
116            .checked_add(
117                u32::try_from(disk_len)
118                    .map_err(|_| KeyBinaryError::ValueOverflow("filename_size"))?,
119            )
120            .ok_or(KeyBinaryError::ValueOverflow("filename_offset"))?;
121    }
122
123    for encoded_filename in &encoded_filenames {
124        writer.write_all(encoded_filename)?;
125        writer.write_all(&[0_u8])?;
126    }
127
128    for resource in &key.resources {
129        // ResRef stores Windows-1252 bytes (the engine's native
130        // encoding) capped at 16 bytes, which is exactly the on-disk
131        writer.write_all(&resource.resref.to_padded())?;
132        write_u16(writer, resource.resource_type.raw_id())?;
133        write_u32(writer, resource.resource_id.raw())?;
134    }
135
136    crate::trace_debug!(
137        bif_count = key.bif_entries.len(),
138        resource_count = key.resources.len(),
139        "wrote key to writer"
140    );
141    Ok(())
142}
143
144/// Serializes a KEY index to bytes.
145///
146/// # Errors
147///
148/// Every non-I/O failure [`write_key`] describes. The `Vec` target has no I/O
149/// to fail at.
150#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(key)))]
151pub fn write_key_to_vec(key: &Key) -> Result<Vec<u8>, KeyBinaryError> {
152    let mut cursor = Cursor::new(Vec::new());
153    write_key(&mut cursor, key)?;
154    let bytes = cursor.into_inner();
155    crate::trace_debug!(bytes_len = bytes.len(), "serialized key to vec");
156    Ok(bytes)
157}