Skip to main content

rakata_formats/erf/
writer.rs

1//! ERF/MOD/HAK binary writer.
2
3use std::io::{Cursor, Write};
4
5use rakata_core::encode_text;
6
7use super::{
8    binary::{write_u16, write_u32},
9    Erf, ErfBinaryError, ErfFileType, ErfWriteMode, ErfWriteOptions, ModLayout, ERF_TEXT_ENCODING,
10    ERF_VERSION_V10, FILE_HEADER_SIZE, KEY_ENTRY_SIZE, MOD_BLANK_BLOCK_ENTRY_SIZE,
11    RESOURCE_ENTRY_SIZE,
12};
13
14/// Writes an ERF/MOD/HAK archive to a writer.
15#[cfg_attr(
16    feature = "tracing",
17    tracing::instrument(level = "debug", skip(writer, erf))
18)]
19pub fn write_erf<W: Write>(writer: &mut W, erf: &Erf) -> Result<(), ErfBinaryError> {
20    write_erf_with_options(writer, erf, ErfWriteOptions::default())
21}
22
23/// Writes an ERF/MOD/HAK archive to a writer with explicit layout options.
24#[cfg_attr(
25    feature = "tracing",
26    tracing::instrument(level = "debug", skip(writer, erf), fields(file_type = ?erf.file_type, mod_layout = ?options.mod_layout))
27)]
28pub fn write_erf_with_options<W: Write>(
29    writer: &mut W,
30    erf: &Erf,
31    options: ErfWriteOptions,
32) -> Result<(), ErfBinaryError> {
33    let entry_count = u32::try_from(erf.resources.len())
34        .map_err(|_| ErfBinaryError::ValueOverflow("entry_count"))?;
35
36    let mut localized_bytes = Vec::new();
37    for (index, entry) in erf.localized_strings.iter().enumerate() {
38        push_u32(&mut localized_bytes, entry.language_id.raw());
39        let encoded = encode_text(&entry.text, ERF_TEXT_ENCODING).map_err(|source| {
40            ErfBinaryError::TextEncoding {
41                context: format!("localized_strings[{index}]"),
42                source,
43            }
44        })?;
45        let encoded_len = u32::try_from(encoded.len())
46            .map_err(|_| ErfBinaryError::ValueOverflow("localized_string_len"))?;
47        push_u32(&mut localized_bytes, encoded_len);
48        localized_bytes.extend_from_slice(&encoded);
49    }
50    let language_count = u32::try_from(erf.localized_strings.len())
51        .map_err(|_| ErfBinaryError::ValueOverflow("language_count"))?;
52    let localized_size = u32::try_from(localized_bytes.len())
53        .map_err(|_| ErfBinaryError::ValueOverflow("localized_string_size"))?;
54
55    let localized_offset = u32::try_from(FILE_HEADER_SIZE)
56        .map_err(|_| ErfBinaryError::ValueOverflow("localized_offset"))?;
57    let keys_offset = localized_offset
58        .checked_add(localized_size)
59        .ok_or(ErfBinaryError::ValueOverflow("keys_offset"))?;
60    let mod_blank_block_size = if erf.file_type == ErfFileType::Mod
61        && options.mod_layout == ModLayout::WithBlankBlock
62    {
63        entry_count
64            .checked_mul(u32::try_from(MOD_BLANK_BLOCK_ENTRY_SIZE).expect("constant fits in u32"))
65            .ok_or(ErfBinaryError::ValueOverflow("mod_blank_block_size"))?
66    } else {
67        0
68    };
69    let resources_offset = keys_offset
70        .checked_add(
71            entry_count
72                .checked_mul(u32::try_from(KEY_ENTRY_SIZE).expect("constant fits in u32"))
73                .ok_or(ErfBinaryError::ValueOverflow("resources_offset"))?,
74        )
75        .and_then(|offset| offset.checked_add(mod_blank_block_size))
76        .ok_or(ErfBinaryError::ValueOverflow("resources_offset"))?;
77
78    let serialized_file_type = match options.output {
79        ErfWriteMode::CanonicalK1 if erf.file_type == ErfFileType::Sav => ErfFileType::Mod,
80        _ => erf.file_type,
81    };
82
83    writer.write_all(&serialized_file_type.fourcc())?;
84    writer.write_all(&ERF_VERSION_V10)?;
85    write_u32(writer, language_count)?;
86    write_u32(writer, localized_size)?;
87    write_u32(writer, entry_count)?;
88    write_u32(writer, localized_offset)?;
89    write_u32(writer, keys_offset)?;
90    write_u32(writer, resources_offset)?;
91    write_u32(writer, erf.build_year)?;
92    write_u32(writer, erf.build_day)?;
93    write_u32(
94        writer,
95        u32::from_le_bytes(erf.description_strref.raw().to_le_bytes()),
96    )?;
97    writer.write_all(&erf.reserved)?;
98
99    writer.write_all(&localized_bytes)?;
100
101    for (index, resource) in erf.resources.iter().enumerate() {
102        // ResRef stores Windows-1252 bytes (the engine's native
103        // encoding) capped at 16 bytes, which is exactly the on-disk
104        // shape, so we write `as_bytes()` directly with no transcoding.
105        let resref_bytes = resource.resref.as_bytes();
106        let mut key_resref = [0_u8; 16];
107        key_resref[..resref_bytes.len()].copy_from_slice(resref_bytes);
108        writer.write_all(&key_resref)?;
109        write_u32(
110            writer,
111            u32::try_from(index).map_err(|_| ErfBinaryError::ValueOverflow("resource_id"))?,
112        )?;
113        write_u16(writer, resource.resource_type.raw_id())?;
114        write_u16(writer, 0)?;
115    }
116
117    if mod_blank_block_size > 0 {
118        writer.write_all(&vec![
119            0_u8;
120            usize::try_from(mod_blank_block_size).map_err(
121                |_| { ErfBinaryError::ValueOverflow("mod_blank_block_size") }
122            )?
123        ])?;
124    }
125
126    let mut next_data_offset = resources_offset
127        .checked_add(
128            entry_count
129                .checked_mul(u32::try_from(RESOURCE_ENTRY_SIZE).expect("constant fits in u32"))
130                .ok_or(ErfBinaryError::ValueOverflow("data_offset"))?,
131        )
132        .ok_or(ErfBinaryError::ValueOverflow("data_offset"))?;
133    for resource in &erf.resources {
134        write_u32(writer, next_data_offset)?;
135        let data_len = u32::try_from(resource.data.len())
136            .map_err(|_| ErfBinaryError::ValueOverflow("resource_size"))?;
137        write_u32(writer, data_len)?;
138        next_data_offset = next_data_offset
139            .checked_add(data_len)
140            .ok_or(ErfBinaryError::ValueOverflow("data_offset"))?;
141    }
142
143    for resource in &erf.resources {
144        writer.write_all(&resource.data)?;
145    }
146    crate::trace_debug!(
147        file_type = ?erf.file_type,
148        localized_string_count = erf.localized_strings.len(),
149        resource_count = erf.resources.len(),
150        "wrote erf-family archive to writer"
151    );
152    Ok(())
153}
154
155/// Writes a save archive to a writer.
156///
157/// Output is canonicalized to the KotOR `MOD ` header signature.
158#[cfg_attr(
159    feature = "tracing",
160    tracing::instrument(level = "debug", skip(writer, erf))
161)]
162pub fn write_save_archive<W: Write>(writer: &mut W, erf: &Erf) -> Result<(), ErfBinaryError> {
163    let mut archive = erf.clone();
164    archive.file_type = ErfFileType::Mod;
165    write_erf(writer, &archive)
166}
167
168/// Serializes an ERF/MOD/HAK archive to bytes.
169#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(erf)))]
170pub fn write_erf_to_vec(erf: &Erf) -> Result<Vec<u8>, ErfBinaryError> {
171    write_erf_to_vec_with_options(erf, ErfWriteOptions::default())
172}
173
174/// Serializes an ERF/MOD/HAK archive to bytes with explicit layout options.
175#[cfg_attr(
176    feature = "tracing",
177    tracing::instrument(level = "debug", skip(erf), fields(file_type = ?erf.file_type, mod_layout = ?options.mod_layout))
178)]
179pub fn write_erf_to_vec_with_options(
180    erf: &Erf,
181    options: ErfWriteOptions,
182) -> Result<Vec<u8>, ErfBinaryError> {
183    let mut cursor = Cursor::new(Vec::new());
184    write_erf_with_options(&mut cursor, erf, options)?;
185    let bytes = cursor.into_inner();
186    crate::trace_debug!(
187        bytes_len = bytes.len(),
188        "serialized erf-family archive to vec"
189    );
190    Ok(bytes)
191}
192
193/// Serializes a save archive to bytes.
194///
195/// Output is canonicalized to the KotOR `MOD ` header signature.
196#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(erf)))]
197pub fn write_save_archive_to_vec(erf: &Erf) -> Result<Vec<u8>, ErfBinaryError> {
198    let mut cursor = Cursor::new(Vec::new());
199    write_save_archive(&mut cursor, erf)?;
200    let bytes = cursor.into_inner();
201    crate::trace_debug!(bytes_len = bytes.len(), "serialized save archive to vec");
202    Ok(bytes)
203}
204
205fn push_u32(bytes: &mut Vec<u8>, value: u32) {
206    bytes.extend_from_slice(&value.to_le_bytes());
207}