Skip to main content

rakata_formats/twoda/
writer.rs

1//! 2DA binary writer.
2
3use std::collections::HashMap;
4use std::io::{Cursor, Write};
5
6use rakata_core::{encode_text, TextEncoding};
7
8use super::{
9    binary::{write_u16, write_u32},
10    validate_twoda, TwoDa, TwoDaBinaryError, TwoDaBinaryOptions, TWODA_MAGIC, TWODA_VERSION_V2B,
11};
12
13/// Writes a binary 2DA (`2DA V2.b`) to a writer.
14///
15/// # Errors
16///
17/// The same as [`write_twoda_with_options`] under the default options, which
18/// encode text as Windows-1252.
19#[cfg_attr(
20    feature = "tracing",
21    tracing::instrument(level = "debug", skip(writer, twoda))
22)]
23pub fn write_twoda<W: Write>(writer: &mut W, twoda: &TwoDa) -> Result<(), TwoDaBinaryError> {
24    write_twoda_with_options(writer, twoda, TwoDaBinaryOptions::default())
25}
26
27/// Writes a binary 2DA (`2DA V2.b`) to a writer with explicit text options.
28///
29/// # Errors
30///
31/// [`TwoDaBinaryError::InvalidTable`] for a table the format cannot express,
32/// checked in full before anything is written: a column name or row label
33/// holding a NUL or a tab, a cell holding a NUL, or a row whose width does not
34/// match the column count. Those three bytes are the format's own delimiters.
35///
36/// [`TwoDaBinaryError::ValueOverflow`] when the row count exceeds `u32`, or
37/// when a cell offset or the total cell-data size exceeds the `u16` the
38/// format gives it. A table can therefore be valid in memory and too large to
39/// write.
40///
41/// [`TwoDaBinaryError::TextEncoding`] when a value has no form in
42/// `options.text_encoding`, naming which one, and
43/// [`TwoDaBinaryError::Io`] when the writer fails. Either can leave a partial
44/// file, since both are raised during the write rather than before it.
45#[cfg_attr(
46    feature = "tracing",
47    tracing::instrument(level = "debug", skip(writer, twoda, options))
48)]
49pub fn write_twoda_with_options<W: Write>(
50    writer: &mut W,
51    twoda: &TwoDa,
52    options: TwoDaBinaryOptions,
53) -> Result<(), TwoDaBinaryError> {
54    validate_twoda(twoda)?;
55
56    writer.write_all(&TWODA_MAGIC)?;
57    writer.write_all(&TWODA_VERSION_V2B)?;
58    writer.write_all(b"\n")?;
59
60    for (index, header) in twoda.headers.iter().enumerate() {
61        let bytes =
62            encode_with_encoding(header, format!("header[{index}]"), options.text_encoding)?;
63        writer.write_all(&bytes)?;
64        writer.write_all(b"\t")?;
65    }
66    writer.write_all(&[0])?;
67
68    let row_count = u32::try_from(twoda.rows.len())
69        .map_err(|_| TwoDaBinaryError::ValueOverflow("row_count"))?;
70    write_u32(writer, row_count)?;
71
72    for (index, row) in twoda.rows.iter().enumerate() {
73        let bytes = encode_with_encoding(
74            &row.label,
75            format!("row label[{index}]"),
76            options.text_encoding,
77        )?;
78        writer.write_all(&bytes)?;
79        writer.write_all(b"\t")?;
80    }
81
82    let mut unique_values = Vec::<Vec<u8>>::new();
83    let mut offset_by_value = HashMap::<Vec<u8>, u16>::new();
84    let mut cell_offsets = Vec::<u16>::with_capacity(twoda.rows.len() * twoda.headers.len());
85    let mut data_size: usize = 0;
86
87    for (row_index, row) in twoda.rows.iter().enumerate() {
88        for (column_index, value) in row.cells.iter().enumerate() {
89            let mut encoded = encode_with_encoding(
90                value,
91                format!("cell[{row_index}][{column_index}]"),
92                options.text_encoding,
93            )?;
94            encoded.push(0);
95
96            let offset = if let Some(existing) = offset_by_value.get(&encoded) {
97                *existing
98            } else {
99                let offset = u16::try_from(data_size)
100                    .map_err(|_| TwoDaBinaryError::ValueOverflow("cell_offset"))?;
101                data_size = data_size
102                    .checked_add(encoded.len())
103                    .ok_or(TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
104                if data_size > usize::from(u16::MAX) {
105                    return Err(TwoDaBinaryError::ValueOverflow("cell_data_size"));
106                }
107                offset_by_value.insert(encoded.clone(), offset);
108                unique_values.push(encoded);
109                offset
110            };
111            cell_offsets.push(offset);
112        }
113    }
114
115    for offset in &cell_offsets {
116        write_u16(writer, *offset)?;
117    }
118    let data_size_u16 =
119        u16::try_from(data_size).map_err(|_| TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
120    write_u16(writer, data_size_u16)?;
121    for value in &unique_values {
122        writer.write_all(value)?;
123    }
124
125    Ok(())
126}
127
128/// Serializes a binary 2DA to a byte vector.
129///
130/// # Errors
131///
132/// The same as [`write_twoda_to_vec_with_options`] under the default options.
133#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(twoda)))]
134pub fn write_twoda_to_vec(twoda: &TwoDa) -> Result<Vec<u8>, TwoDaBinaryError> {
135    write_twoda_to_vec_with_options(twoda, TwoDaBinaryOptions::default())
136}
137
138/// Serializes a binary 2DA to a byte vector with explicit text options.
139///
140/// # Errors
141///
142/// Every non-I/O failure [`write_twoda_with_options`] describes. The `Vec`
143/// target has no I/O to fail at.
144#[cfg_attr(
145    feature = "tracing",
146    tracing::instrument(level = "debug", skip(twoda, options))
147)]
148pub fn write_twoda_to_vec_with_options(
149    twoda: &TwoDa,
150    options: TwoDaBinaryOptions,
151) -> Result<Vec<u8>, TwoDaBinaryError> {
152    let mut cursor = Cursor::new(Vec::new());
153    write_twoda_with_options(&mut cursor, twoda, options)?;
154    Ok(cursor.into_inner())
155}
156
157fn encode_with_encoding(
158    value: &str,
159    context: String,
160    encoding: TextEncoding,
161) -> Result<Vec<u8>, TwoDaBinaryError> {
162    encode_text(value, encoding)
163        .map_err(|source| TwoDaBinaryError::TextEncoding { context, source })
164}