Skip to main content

rakata_formats/rim/
writer.rs

1//! RIM binary writer.
2
3use std::io::{Cursor, Write};
4
5use crate::binary::write_u32;
6
7use super::{Rim, RimBinaryError, FILE_HEADER_SIZE, KEY_ENTRY_SIZE, RIM_MAGIC, RIM_VERSION_V10};
8
9/// Writes a RIM archive to a writer.
10///
11/// # Errors
12///
13/// [`RimBinaryError::ValueOverflow`] when the entry count, a key-table offset
14/// or the running data offset will not fit the `u32` the format gives it,
15/// naming the field. An archive can therefore be valid in memory and too
16/// large to write. [`RimBinaryError::Io`] when the writer fails, which can
17/// leave a partial archive.
18#[cfg_attr(
19    feature = "tracing",
20    tracing::instrument(level = "debug", skip(writer, rim))
21)]
22pub fn write_rim<W: Write>(writer: &mut W, rim: &Rim) -> Result<(), RimBinaryError> {
23    let entry_count = u32::try_from(rim.resources.len())
24        .map_err(|_| RimBinaryError::ValueOverflow("entry_count"))?;
25    let keys_offset = u32::try_from(FILE_HEADER_SIZE)
26        .map_err(|_| RimBinaryError::ValueOverflow("keys_offset"))?;
27    let mut next_data_offset = keys_offset
28        .checked_add(
29            entry_count
30                .checked_mul(
31                    u32::try_from(KEY_ENTRY_SIZE)
32                        .map_err(|_| RimBinaryError::ValueOverflow("KEY_ENTRY_SIZE"))?,
33                )
34                .ok_or(RimBinaryError::ValueOverflow("data_offset"))?,
35        )
36        .ok_or(RimBinaryError::ValueOverflow("data_offset"))?;
37
38    writer.write_all(&RIM_MAGIC)?;
39    writer.write_all(&RIM_VERSION_V10)?;
40    write_u32(writer, rim.reserved_0x08)?;
41    write_u32(writer, entry_count)?;
42    write_u32(writer, keys_offset)?;
43    write_u32(writer, rim.reserved_0x14)?;
44    writer.write_all(&rim.reserved_0x18)?;
45
46    for (index, resource) in rim.resources.iter().enumerate() {
47        // ResRef stores Windows-1252 bytes (the engine's native
48        // encoding) capped at 16 bytes, which is exactly the on-disk
49        writer.write_all(&resource.resref.to_padded())?;
50        write_u32(writer, u32::from(resource.resource_type.raw_id()))?;
51        write_u32(
52            writer,
53            u32::try_from(index).map_err(|_| RimBinaryError::ValueOverflow("resource_id"))?,
54        )?;
55        write_u32(writer, next_data_offset)?;
56        let data_len = u32::try_from(resource.data.len())
57            .map_err(|_| RimBinaryError::ValueOverflow("resource_size"))?;
58        write_u32(writer, data_len)?;
59        next_data_offset = next_data_offset
60            .checked_add(data_len)
61            .ok_or(RimBinaryError::ValueOverflow("data_offset"))?;
62    }
63
64    for resource in &rim.resources {
65        writer.write_all(&resource.data)?;
66    }
67
68    crate::trace_debug!(resource_count = rim.resources.len(), "wrote rim to writer");
69    Ok(())
70}
71
72/// Serializes a RIM archive to bytes.
73///
74/// # Errors
75///
76/// [`RimBinaryError::ValueOverflow`] on the terms [`write_rim`] gives. The
77/// `Vec` target has no I/O to fail at.
78#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(rim)))]
79pub fn write_rim_to_vec(rim: &Rim) -> Result<Vec<u8>, RimBinaryError> {
80    let mut cursor = Cursor::new(Vec::new());
81    write_rim(&mut cursor, rim)?;
82    let bytes = cursor.into_inner();
83    crate::trace_debug!(bytes_len = bytes.len(), "serialized rim to vec");
84    Ok(bytes)
85}