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#[cfg_attr(
15    feature = "tracing",
16    tracing::instrument(level = "debug", skip(writer, twoda))
17)]
18pub fn write_twoda<W: Write>(writer: &mut W, twoda: &TwoDa) -> Result<(), TwoDaBinaryError> {
19    write_twoda_with_options(writer, twoda, TwoDaBinaryOptions::default())
20}
21
22/// Writes a binary 2DA (`2DA V2.b`) to a writer with explicit text options.
23#[cfg_attr(
24    feature = "tracing",
25    tracing::instrument(level = "debug", skip(writer, twoda, options))
26)]
27pub fn write_twoda_with_options<W: Write>(
28    writer: &mut W,
29    twoda: &TwoDa,
30    options: TwoDaBinaryOptions,
31) -> Result<(), TwoDaBinaryError> {
32    validate_twoda(twoda)?;
33
34    writer.write_all(&TWODA_MAGIC)?;
35    writer.write_all(&TWODA_VERSION_V2B)?;
36    writer.write_all(b"\n")?;
37
38    for (index, header) in twoda.headers.iter().enumerate() {
39        let bytes =
40            encode_with_encoding(header, format!("header[{index}]"), options.text_encoding)?;
41        writer.write_all(&bytes)?;
42        writer.write_all(b"\t")?;
43    }
44    writer.write_all(&[0])?;
45
46    let row_count = u32::try_from(twoda.rows.len())
47        .map_err(|_| TwoDaBinaryError::ValueOverflow("row_count"))?;
48    write_u32(writer, row_count)?;
49
50    for (index, row) in twoda.rows.iter().enumerate() {
51        let bytes = encode_with_encoding(
52            &row.label,
53            format!("row label[{index}]"),
54            options.text_encoding,
55        )?;
56        writer.write_all(&bytes)?;
57        writer.write_all(b"\t")?;
58    }
59
60    let mut unique_values = Vec::<Vec<u8>>::new();
61    let mut offset_by_value = HashMap::<Vec<u8>, u16>::new();
62    let mut cell_offsets = Vec::<u16>::with_capacity(twoda.rows.len() * twoda.headers.len());
63    let mut data_size: usize = 0;
64
65    for (row_index, row) in twoda.rows.iter().enumerate() {
66        for (column_index, value) in row.cells.iter().enumerate() {
67            let mut encoded = encode_with_encoding(
68                value,
69                format!("cell[{row_index}][{column_index}]"),
70                options.text_encoding,
71            )?;
72            encoded.push(0);
73
74            let offset = if let Some(existing) = offset_by_value.get(&encoded) {
75                *existing
76            } else {
77                let offset = u16::try_from(data_size)
78                    .map_err(|_| TwoDaBinaryError::ValueOverflow("cell_offset"))?;
79                data_size = data_size
80                    .checked_add(encoded.len())
81                    .ok_or(TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
82                if data_size > usize::from(u16::MAX) {
83                    return Err(TwoDaBinaryError::ValueOverflow("cell_data_size"));
84                }
85                offset_by_value.insert(encoded.clone(), offset);
86                unique_values.push(encoded);
87                offset
88            };
89            cell_offsets.push(offset);
90        }
91    }
92
93    for offset in &cell_offsets {
94        write_u16(writer, *offset)?;
95    }
96    let data_size_u16 =
97        u16::try_from(data_size).map_err(|_| TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
98    write_u16(writer, data_size_u16)?;
99    for value in &unique_values {
100        writer.write_all(value)?;
101    }
102
103    Ok(())
104}
105
106/// Serializes a binary 2DA to a byte vector.
107#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(twoda)))]
108pub fn write_twoda_to_vec(twoda: &TwoDa) -> Result<Vec<u8>, TwoDaBinaryError> {
109    write_twoda_to_vec_with_options(twoda, TwoDaBinaryOptions::default())
110}
111
112/// Serializes a binary 2DA to a byte vector with explicit text options.
113#[cfg_attr(
114    feature = "tracing",
115    tracing::instrument(level = "debug", skip(twoda, options))
116)]
117pub fn write_twoda_to_vec_with_options(
118    twoda: &TwoDa,
119    options: TwoDaBinaryOptions,
120) -> Result<Vec<u8>, TwoDaBinaryError> {
121    let mut cursor = Cursor::new(Vec::new());
122    write_twoda_with_options(&mut cursor, twoda, options)?;
123    Ok(cursor.into_inner())
124}
125
126fn encode_with_encoding(
127    value: &str,
128    context: String,
129    encoding: TextEncoding,
130) -> Result<Vec<u8>, TwoDaBinaryError> {
131    encode_text(value, encoding)
132        .map_err(|source| TwoDaBinaryError::TextEncoding { context, source })
133}