rakata_formats/bif/
writer.rs

1//! BIF binary writer.
2
3use std::io::{Cursor, Write};
4
5#[cfg(feature = "bzf")]
6use lzma_rust2::{LzmaOptions, LzmaWriter};
7
8use super::{
9    binary::write_u32, Bif, BifBinaryError, BifContainer, BifResourceStorage, BIF_MAGIC,
10    BIF_VERSION_V10, BZF_MAGIC, FILE_HEADER_SIZE, FIXED_ENTRY_SIZE, VARIABLE_ENTRY_SIZE,
11};
12
13/// Writes a BIF archive to a writer.
14#[cfg_attr(
15    feature = "tracing",
16    tracing::instrument(level = "debug", skip(writer, bif))
17)]
18pub fn write_bif<W: Write>(writer: &mut W, bif: &Bif) -> Result<(), BifBinaryError> {
19    let result = match bif.container {
20        BifContainer::Biff => write_bif_impl(writer, bif, false),
21        BifContainer::Bzf => {
22            #[cfg(not(feature = "bzf"))]
23            {
24                Err(BifBinaryError::BzfFeatureDisabled)
25            }
26            #[cfg(feature = "bzf")]
27            {
28                write_bif_impl(writer, bif, true)
29            }
30        }
31    };
32    if result.is_ok() {
33        crate::trace_debug!(
34            container = ?bif.container,
35            resource_count = bif.resources.len(),
36            "wrote bif/bzf to writer"
37        );
38    }
39    result
40}
41
42/// Serializes a BIF archive to bytes.
43#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(bif), fields(container = ?bif.container)))]
44pub fn write_bif_to_vec(bif: &Bif) -> Result<Vec<u8>, BifBinaryError> {
45    let mut cursor = Cursor::new(Vec::new());
46    write_bif(&mut cursor, bif)?;
47    let bytes = cursor.into_inner();
48    crate::trace_debug!(
49        container = ?bif.container,
50        bytes_len = bytes.len(),
51        "serialized bif/bzf to vec"
52    );
53    Ok(bytes)
54}
55
56#[cfg_attr(
57    feature = "tracing",
58    tracing::instrument(level = "debug", skip(writer, bif), fields(as_bzf, resource_count = bif.resources.len()))
59)]
60fn write_bif_impl<W: Write>(writer: &mut W, bif: &Bif, as_bzf: bool) -> Result<(), BifBinaryError> {
61    let variable_resources: Vec<_> = bif
62        .resources
63        .iter()
64        .filter(|resource| matches!(resource.storage, BifResourceStorage::Variable))
65        .collect();
66    let fixed_resources: Vec<_> = bif
67        .resources
68        .iter()
69        .filter(|resource| matches!(resource.storage, BifResourceStorage::Fixed { .. }))
70        .collect();
71
72    let variable_count = u32::try_from(variable_resources.len())
73        .map_err(|_| BifBinaryError::ValueOverflow("variable_count"))?;
74    let fixed_count = u32::try_from(fixed_resources.len())
75        .map_err(|_| BifBinaryError::ValueOverflow("fixed_count"))?;
76    let variable_table_offset = u32::try_from(FILE_HEADER_SIZE)
77        .map_err(|_| BifBinaryError::ValueOverflow("variable_table_offset"))?;
78
79    let variable_table_size = variable_resources
80        .len()
81        .checked_mul(VARIABLE_ENTRY_SIZE)
82        .ok_or(BifBinaryError::ValueOverflow("variable_table_size"))?;
83    let fixed_table_size = fixed_resources
84        .len()
85        .checked_mul(FIXED_ENTRY_SIZE)
86        .ok_or(BifBinaryError::ValueOverflow("fixed_table_size"))?;
87
88    #[cfg(feature = "bzf")]
89    let variable_payloads = if as_bzf {
90        variable_resources
91            .iter()
92            .map(|resource| encode_bzf_payload(&resource.data))
93            .collect::<Result<Vec<_>, _>>()?
94    } else {
95        Vec::new()
96    };
97    #[cfg(feature = "bzf")]
98    let fixed_payloads = if as_bzf {
99        fixed_resources
100            .iter()
101            .map(|resource| encode_bzf_payload(&resource.data))
102            .collect::<Result<Vec<_>, _>>()?
103    } else {
104        Vec::new()
105    };
106
107    let mut variable_offsets = Vec::with_capacity(variable_resources.len());
108    let mut fixed_offsets = Vec::with_capacity(fixed_resources.len());
109    let mut next_data_offset = FILE_HEADER_SIZE
110        .checked_add(variable_table_size)
111        .and_then(|offset| offset.checked_add(fixed_table_size))
112        .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
113
114    #[cfg(feature = "bzf")]
115    for (index, resource) in variable_resources.iter().enumerate() {
116        let target = if let Some(source) = resource.source_data_offset {
117            usize::try_from(source).expect("source offset fits in usize")
118        } else {
119            align_to_four(next_data_offset)
120                .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
121        };
122        variable_offsets.push(target);
123        let payload_len = if as_bzf {
124            variable_payloads[index].len()
125        } else {
126            resource.data.len()
127        };
128        next_data_offset = target
129            .checked_add(payload_len)
130            .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
131    }
132    #[cfg(not(feature = "bzf"))]
133    for resource in &variable_resources {
134        let target = if let Some(source) = resource.source_data_offset {
135            usize::try_from(source).expect("source offset fits in usize")
136        } else {
137            align_to_four(next_data_offset)
138                .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
139        };
140        variable_offsets.push(target);
141        next_data_offset = target
142            .checked_add(resource.data.len())
143            .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
144    }
145
146    #[cfg(feature = "bzf")]
147    for (index, resource) in fixed_resources.iter().enumerate() {
148        let target = if let Some(source) = resource.source_data_offset {
149            usize::try_from(source).expect("source offset fits in usize")
150        } else {
151            align_to_four(next_data_offset)
152                .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
153        };
154        fixed_offsets.push(target);
155        let payload_len = if as_bzf {
156            fixed_payloads[index].len()
157        } else {
158            resource.data.len()
159        };
160        next_data_offset = target
161            .checked_add(payload_len)
162            .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
163    }
164    #[cfg(not(feature = "bzf"))]
165    for resource in &fixed_resources {
166        let target = if let Some(source) = resource.source_data_offset {
167            usize::try_from(source).expect("source offset fits in usize")
168        } else {
169            align_to_four(next_data_offset)
170                .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
171        };
172        fixed_offsets.push(target);
173        next_data_offset = target
174            .checked_add(resource.data.len())
175            .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
176    }
177
178    if as_bzf {
179        writer.write_all(&BZF_MAGIC)?;
180    } else {
181        writer.write_all(&BIF_MAGIC)?;
182    }
183    writer.write_all(&BIF_VERSION_V10)?;
184    write_u32(writer, variable_count)?;
185    write_u32(writer, fixed_count)?;
186    write_u32(writer, variable_table_offset)?;
187
188    for (resource, data_offset) in variable_resources.iter().zip(variable_offsets.iter()) {
189        write_u32(writer, resource.resource_id.raw())?;
190        write_u32(
191            writer,
192            u32::try_from(*data_offset)
193                .map_err(|_| BifBinaryError::ValueOverflow("data_offset"))?,
194        )?;
195        write_u32(
196            writer,
197            u32::try_from(resource.data.len())
198                .map_err(|_| BifBinaryError::ValueOverflow("data_size"))?,
199        )?;
200        write_u32(writer, u32::from(resource.resource_type.raw_id()))?;
201    }
202
203    for (resource, data_offset) in fixed_resources.iter().zip(fixed_offsets.iter()) {
204        let part_count = match resource.storage {
205            BifResourceStorage::Fixed { part_count } => part_count,
206            BifResourceStorage::Variable => {
207                unreachable!("fixed resource vector must only contain fixed entries")
208            }
209        };
210        write_u32(writer, resource.resource_id.raw())?;
211        write_u32(
212            writer,
213            u32::try_from(*data_offset)
214                .map_err(|_| BifBinaryError::ValueOverflow("fixed_data_offset"))?,
215        )?;
216        write_u32(writer, part_count)?;
217        write_u32(
218            writer,
219            u32::try_from(resource.data.len())
220                .map_err(|_| BifBinaryError::ValueOverflow("fixed_data_size"))?,
221        )?;
222        write_u32(writer, u32::from(resource.resource_type.raw_id()))?;
223    }
224
225    let mut written_offset = FILE_HEADER_SIZE
226        .checked_add(variable_table_size)
227        .and_then(|offset| offset.checked_add(fixed_table_size))
228        .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
229    #[cfg(feature = "bzf")]
230    for (index, (resource, target_offset)) in variable_resources
231        .iter()
232        .zip(variable_offsets.iter())
233        .enumerate()
234    {
235        if written_offset < *target_offset {
236            let pad_len = target_offset
237                .checked_sub(written_offset)
238                .ok_or(BifBinaryError::ValueOverflow("padding"))?;
239            writer.write_all(&vec![0_u8; pad_len])?;
240            written_offset = *target_offset;
241        }
242
243        let payload = if as_bzf {
244            variable_payloads[index].as_slice()
245        } else {
246            resource.data.as_slice()
247        };
248
249        writer.write_all(payload)?;
250        written_offset = written_offset
251            .checked_add(payload.len())
252            .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
253    }
254    #[cfg(not(feature = "bzf"))]
255    for (resource, target_offset) in variable_resources.iter().zip(variable_offsets.iter()) {
256        if written_offset < *target_offset {
257            let pad_len = target_offset
258                .checked_sub(written_offset)
259                .ok_or(BifBinaryError::ValueOverflow("padding"))?;
260            writer.write_all(&vec![0_u8; pad_len])?;
261            written_offset = *target_offset;
262        }
263
264        let payload = resource.data.as_slice();
265
266        writer.write_all(payload)?;
267        written_offset = written_offset
268            .checked_add(payload.len())
269            .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
270    }
271
272    #[cfg(feature = "bzf")]
273    for (index, (resource, target_offset)) in
274        fixed_resources.iter().zip(fixed_offsets.iter()).enumerate()
275    {
276        if written_offset < *target_offset {
277            let pad_len = target_offset
278                .checked_sub(written_offset)
279                .ok_or(BifBinaryError::ValueOverflow("padding"))?;
280            writer.write_all(&vec![0_u8; pad_len])?;
281            written_offset = *target_offset;
282        }
283
284        let payload = if as_bzf {
285            fixed_payloads[index].as_slice()
286        } else {
287            resource.data.as_slice()
288        };
289
290        writer.write_all(payload)?;
291        written_offset = written_offset
292            .checked_add(payload.len())
293            .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
294    }
295    #[cfg(not(feature = "bzf"))]
296    for (resource, target_offset) in fixed_resources.iter().zip(fixed_offsets.iter()) {
297        if written_offset < *target_offset {
298            let pad_len = target_offset
299                .checked_sub(written_offset)
300                .ok_or(BifBinaryError::ValueOverflow("padding"))?;
301            writer.write_all(&vec![0_u8; pad_len])?;
302            written_offset = *target_offset;
303        }
304
305        let payload = resource.data.as_slice();
306
307        writer.write_all(payload)?;
308        written_offset = written_offset
309            .checked_add(payload.len())
310            .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
311    }
312
313    Ok(())
314}
315
316fn align_to_four(value: usize) -> Option<usize> {
317    let padding = (4 - (value % 4)) % 4;
318    value.checked_add(padding)
319}
320
321#[cfg(feature = "bzf")]
322fn encode_bzf_payload(payload: &[u8]) -> Result<Vec<u8>, BifBinaryError> {
323    let options = LzmaOptions::with_preset(6);
324    let mut writer =
325        LzmaWriter::new_no_header(Cursor::new(Vec::new()), &options, false).map_err(|error| {
326            BifBinaryError::InvalidData(format!("failed to initialize BZF encoder: {error}"))
327        })?;
328    writer.write_all(payload).map_err(|error| {
329        BifBinaryError::InvalidData(format!("failed to encode BZF payload: {error}"))
330    })?;
331    let cursor = writer.finish().map_err(|error| {
332        BifBinaryError::InvalidData(format!("failed to finalize BZF payload: {error}"))
333    })?;
334    Ok(cursor.into_inner())
335}