Skip to main content

rakata_formats/gff/
writer.rs

1//! GFF V3.2 binary writer.
2
3use std::collections::HashMap;
4use std::io::{Cursor, Write};
5
6use rakata_core::{encode_text, text_encoding_for_language, TextEncoding};
7
8use super::{
9    binary::write_u32, to_u32, FieldType, Gff, GffBinaryError, GffValue, DEFAULT_TEXT_ENCODING,
10    FIELD_ENTRY_SIZE, GFF_HEADER_SIZE, GFF_VERSION_V32, LABEL_SIZE, STRUCT_ENTRY_SIZE,
11};
12
13/// Writes a GFF in binary V3.2 format.
14///
15/// # Errors
16///
17/// [`GffBinaryError::ValueOverflow`] when a table offset or size will not fit
18/// its `u32`, naming the field. The whole layout is computed before anything
19/// is written, so an overflow leaves the writer untouched.
20///
21/// [`GffBinaryError::LabelTooLong`] when a label exceeds the 16 bytes the
22/// label table gives it, measured after encoding, and
23/// [`GffBinaryError::TextEncoding`] when a label or string has no form in its
24/// target encoding. [`GffBinaryError::UnsupportedLanguageEncoding`] for a
25/// localized substring whose language id maps to no codepage.
26///
27/// [`GffBinaryError::InvalidData`] when the root struct does not build as
28/// index 0, and [`GffBinaryError::Io`] when the writer fails, which can leave
29/// a partial file.
30#[cfg_attr(
31    feature = "tracing",
32    tracing::instrument(level = "debug", skip(writer, gff), fields(file_type = ?gff.file_type))
33)]
34pub fn write_gff<W: Write>(writer: &mut W, gff: &Gff) -> Result<(), GffBinaryError> {
35    let mut state = GffWriterState::default();
36    let root_index = state.build_struct(&gff.root)?;
37    if root_index != 0 {
38        return Err(GffBinaryError::InvalidData(
39            "internal writer error: root struct index is not zero".into(),
40        ));
41    }
42
43    let struct_offset = GFF_HEADER_SIZE;
44    let struct_table_size = state
45        .structs
46        .len()
47        .checked_mul(STRUCT_ENTRY_SIZE)
48        .ok_or(GffBinaryError::ValueOverflow("struct_table_size"))?;
49    let field_offset = struct_offset
50        .checked_add(struct_table_size)
51        .ok_or(GffBinaryError::ValueOverflow("field_offset"))?;
52    let field_table_size = state
53        .fields
54        .len()
55        .checked_mul(FIELD_ENTRY_SIZE)
56        .ok_or(GffBinaryError::ValueOverflow("field_table_size"))?;
57    let label_offset = field_offset
58        .checked_add(field_table_size)
59        .ok_or(GffBinaryError::ValueOverflow("label_offset"))?;
60    let labels_size = state
61        .labels
62        .len()
63        .checked_mul(LABEL_SIZE)
64        .ok_or(GffBinaryError::ValueOverflow("labels_size"))?;
65    let field_data_offset = label_offset
66        .checked_add(labels_size)
67        .ok_or(GffBinaryError::ValueOverflow("field_data_offset"))?;
68    let field_indices_offset = field_data_offset
69        .checked_add(state.field_data.len())
70        .ok_or(GffBinaryError::ValueOverflow("field_indices_offset"))?;
71    let list_indices_offset = field_indices_offset
72        .checked_add(state.field_indices.len())
73        .ok_or(GffBinaryError::ValueOverflow("list_indices_offset"))?;
74
75    writer.write_all(&gff.file_type)?;
76    writer.write_all(&GFF_VERSION_V32)?;
77    write_u32(writer, to_u32(struct_offset, "struct_offset")?)?;
78    write_u32(writer, to_u32(state.structs.len(), "struct_count")?)?;
79    write_u32(writer, to_u32(field_offset, "field_offset")?)?;
80    write_u32(writer, to_u32(state.fields.len(), "field_count")?)?;
81    write_u32(writer, to_u32(label_offset, "label_offset")?)?;
82    write_u32(writer, to_u32(state.labels.len(), "label_count")?)?;
83    write_u32(writer, to_u32(field_data_offset, "field_data_offset")?)?;
84    write_u32(writer, to_u32(state.field_data.len(), "field_data_count")?)?;
85    write_u32(
86        writer,
87        to_u32(field_indices_offset, "field_indices_offset")?,
88    )?;
89    write_u32(
90        writer,
91        to_u32(state.field_indices.len(), "field_indices_count")?,
92    )?;
93    write_u32(writer, to_u32(list_indices_offset, "list_indices_offset")?)?;
94    write_u32(
95        writer,
96        to_u32(state.list_indices.len(), "list_indices_count")?,
97    )?;
98
99    for entry in &state.structs {
100        write_u32(writer, u32::from_le_bytes(entry.struct_id.to_le_bytes()))?;
101        write_u32(writer, entry.data_or_offset)?;
102        write_u32(writer, entry.field_count)?;
103    }
104    for entry in &state.fields {
105        write_u32(writer, entry.field_type)?;
106        write_u32(writer, entry.label_index)?;
107        write_u32(writer, entry.data_or_offset)?;
108    }
109    for label in &state.labels {
110        let encoded =
111            encode_with_context(label, format!("label `{label}`"), DEFAULT_TEXT_ENCODING)?;
112        if encoded.len() > LABEL_SIZE {
113            return Err(GffBinaryError::LabelTooLong {
114                label: label.clone(),
115                len: encoded.len(),
116                max: LABEL_SIZE,
117            });
118        }
119        let mut field = [0_u8; LABEL_SIZE];
120        field[..encoded.len()].copy_from_slice(&encoded);
121        writer.write_all(&field)?;
122    }
123    writer.write_all(&state.field_data)?;
124    writer.write_all(&state.field_indices)?;
125    writer.write_all(&state.list_indices)?;
126    crate::trace_debug!(
127        struct_count = state.structs.len(),
128        field_count = state.fields.len(),
129        label_count = state.labels.len(),
130        field_data_len = state.field_data.len(),
131        field_indices_len = state.field_indices.len(),
132        list_indices_len = state.list_indices.len(),
133        "wrote gff tables to writer"
134    );
135    Ok(())
136}
137
138/// Serializes a GFF to a byte vector.
139///
140/// # Errors
141///
142/// Every non-I/O failure [`write_gff`] describes. The `Vec` target has no I/O
143/// to fail at.
144#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(gff)))]
145pub fn write_gff_to_vec(gff: &Gff) -> Result<Vec<u8>, GffBinaryError> {
146    let mut cursor = Cursor::new(Vec::new());
147    write_gff(&mut cursor, gff)?;
148    let bytes = cursor.into_inner();
149    crate::trace_debug!(bytes_len = bytes.len(), "serialized gff to vec");
150    Ok(bytes)
151}
152
153#[derive(Debug, Clone, Copy)]
154struct TempStructEntry {
155    struct_id: i32,
156    data_or_offset: u32,
157    field_count: u32,
158}
159
160#[derive(Debug, Clone, Copy)]
161struct TempFieldEntry {
162    field_type: u32,
163    label_index: u32,
164    data_or_offset: u32,
165}
166
167#[derive(Default)]
168struct GffWriterState {
169    labels: Vec<String>,
170    label_indices: HashMap<String, u32>,
171    structs: Vec<TempStructEntry>,
172    fields: Vec<TempFieldEntry>,
173    field_data: Vec<u8>,
174    field_indices: Vec<u8>,
175    list_indices: Vec<u8>,
176}
177
178impl GffWriterState {
179    fn build_struct(&mut self, structure: &super::GffStruct) -> Result<u32, GffBinaryError> {
180        let struct_index = self.structs.len();
181        let struct_index_u32 = to_u32(struct_index, "struct_index")?;
182        self.structs.push(TempStructEntry {
183            struct_id: structure.struct_id,
184            data_or_offset: u32::MAX,
185            field_count: 0,
186        });
187
188        let mut field_indices = Vec::with_capacity(structure.fields.len());
189        for field in &structure.fields {
190            field_indices.push(self.build_field(field)?);
191        }
192
193        let (data_or_offset, field_count) = match field_indices.len() {
194            0 => (u32::MAX, 0),
195            1 => (field_indices[0], 1),
196            _ => {
197                let offset = to_u32(self.field_indices.len(), "field_indices_offset")?;
198                for field_index in &field_indices {
199                    push_u32(&mut self.field_indices, *field_index);
200                }
201                (offset, to_u32(field_indices.len(), "field_count")?)
202            }
203        };
204        self.structs[struct_index] = TempStructEntry {
205            struct_id: structure.struct_id,
206            data_or_offset,
207            field_count,
208        };
209        Ok(struct_index_u32)
210    }
211
212    fn build_field(&mut self, field: &super::GffField) -> Result<u32, GffBinaryError> {
213        let field_index = self.fields.len();
214        let field_index_u32 = to_u32(field_index, "field_index")?;
215        let label_index = self.intern_label(field.label.as_str())?;
216        // Reserve the field slot before recursive payload building so parent
217        // field indices remain stable when nested structs/lists emit more
218        // fields during recursion.
219        self.fields.push(TempFieldEntry {
220            field_type: 0,
221            label_index,
222            data_or_offset: 0,
223        });
224        let (field_type, data_or_offset) = self.encode_field(&field.value)?;
225        self.fields[field_index] = TempFieldEntry {
226            field_type: u32::from(field_type),
227            label_index,
228            data_or_offset,
229        };
230        Ok(field_index_u32)
231    }
232
233    fn encode_field(&mut self, value: &GffValue) -> Result<(FieldType, u32), GffBinaryError> {
234        let payload = match value {
235            GffValue::UInt8(v) => (FieldType::UInt8, u32::from(*v)),
236            GffValue::Int8(v) => (
237                FieldType::Int8,
238                u32::from_le_bytes(i32::from(*v).to_le_bytes()),
239            ),
240            GffValue::UInt16(v) => (FieldType::UInt16, u32::from(*v)),
241            GffValue::Int16(v) => (
242                FieldType::Int16,
243                u32::from_le_bytes(i32::from(*v).to_le_bytes()),
244            ),
245            GffValue::UInt32(v) => (FieldType::UInt32, *v),
246            GffValue::Int32(v) => (FieldType::Int32, u32::from_le_bytes(v.to_le_bytes())),
247            GffValue::Single(v) => (FieldType::Single, v.to_bits()),
248            GffValue::UInt64(v) => {
249                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
250                self.field_data.extend_from_slice(&v.to_le_bytes());
251                (FieldType::UInt64, offset)
252            }
253            GffValue::Int64(v) => {
254                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
255                self.field_data.extend_from_slice(&v.to_le_bytes());
256                (FieldType::Int64, offset)
257            }
258            GffValue::Double(v) => {
259                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
260                self.field_data.extend_from_slice(&v.to_le_bytes());
261                (FieldType::Double, offset)
262            }
263            GffValue::String(v) => {
264                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
265                let encoded = encode_with_context(v, "string field".into(), DEFAULT_TEXT_ENCODING)?;
266                push_u32(
267                    &mut self.field_data,
268                    to_u32(encoded.len(), "string_length")?,
269                );
270                self.field_data.extend_from_slice(&encoded);
271                (FieldType::String, offset)
272            }
273            GffValue::ResRef(v) => {
274                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
275                // ResRef already stores Windows-1252 bytes (the GFF
276                // on-disk encoding for resref fields), so they go
277                // straight out without going back through encode.
278                let encoded = v.as_bytes();
279                let len_u8 = u8::try_from(encoded.len())
280                    .map_err(|_| GffBinaryError::ValueOverflow("resref_length"))?;
281                self.field_data.push(len_u8);
282                self.field_data.extend_from_slice(encoded);
283                (FieldType::ResRef, offset)
284            }
285            GffValue::LocalizedString(v) => {
286                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
287                let mut payload = Vec::new();
288                push_u32(
289                    &mut payload,
290                    u32::from_le_bytes(v.string_ref.raw().to_le_bytes()),
291                );
292                push_u32(
293                    &mut payload,
294                    to_u32(v.substrings.len(), "locstring_substring_count")?,
295                );
296                for (substring_index, substring) in v.substrings.iter().enumerate() {
297                    push_u32(&mut payload, substring.string_id);
298                    let language_id = substring.string_id / 2;
299                    let encoding = text_encoding_for_language(language_id)
300                        // Optional enhancement track: language IDs 70..=72
301                        // remain unsupported by default because they are not
302                        // required for vanilla K1/K2 parity.
303                        .map_err(|err| {
304                            GffBinaryError::UnsupportedLanguageEncoding(err.language_id.raw())
305                        })?;
306                    let encoded = encode_with_context(
307                        &substring.text,
308                        format!("locstring[{substring_index}] text"),
309                        encoding,
310                    )?;
311                    push_u32(
312                        &mut payload,
313                        to_u32(encoded.len(), "locstring_substring_length")?,
314                    );
315                    payload.extend_from_slice(&encoded);
316                }
317                push_u32(
318                    &mut self.field_data,
319                    to_u32(payload.len(), "locstring_payload_size")?,
320                );
321                self.field_data.extend_from_slice(&payload);
322                (FieldType::LocalizedString, offset)
323            }
324            GffValue::Binary(v) => {
325                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
326                push_u32(&mut self.field_data, to_u32(v.len(), "binary_length")?);
327                self.field_data.extend_from_slice(v);
328                (FieldType::Binary, offset)
329            }
330            GffValue::Struct(v) => (FieldType::Struct, self.build_struct(v)?),
331            GffValue::List(v) => {
332                let offset = to_u32(self.list_indices.len(), "list_indices_offset")?;
333                push_u32(&mut self.list_indices, to_u32(v.len(), "list_count")?);
334                // Reserve contiguous index slots first so nested list writes
335                // inside list structs cannot interleave and corrupt this list.
336                let indices_base = self.list_indices.len();
337                self.list_indices.resize(
338                    indices_base
339                        .checked_add(
340                            v.len()
341                                .checked_mul(4)
342                                .ok_or(GffBinaryError::ValueOverflow("list_indices_size"))?,
343                        )
344                        .ok_or(GffBinaryError::ValueOverflow("list_indices_size"))?,
345                    0,
346                );
347                for (index, item) in v.iter().enumerate() {
348                    let struct_index = self.build_struct(item)?;
349                    let slot = indices_base
350                        .checked_add(
351                            index
352                                .checked_mul(4)
353                                .ok_or(GffBinaryError::ValueOverflow("list_index_slot"))?,
354                        )
355                        .ok_or(GffBinaryError::ValueOverflow("list_index_slot"))?;
356                    self.list_indices[slot..slot + 4].copy_from_slice(&struct_index.to_le_bytes());
357                }
358                (FieldType::List, offset)
359            }
360            GffValue::Vector4(v) => {
361                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
362                for value in v {
363                    self.field_data.extend_from_slice(&value.to_le_bytes());
364                }
365                (FieldType::Vector4, offset)
366            }
367            GffValue::Vector3(v) => {
368                let offset = to_u32(self.field_data.len(), "field_data_offset")?;
369                for value in v {
370                    self.field_data.extend_from_slice(&value.to_le_bytes());
371                }
372                (FieldType::Vector3, offset)
373            }
374        };
375        Ok(payload)
376    }
377
378    fn intern_label(&mut self, label: &str) -> Result<u32, GffBinaryError> {
379        if let Some(existing) = self.label_indices.get(label) {
380            return Ok(*existing);
381        }
382        let encoded =
383            encode_with_context(label, format!("label `{label}`"), DEFAULT_TEXT_ENCODING)?;
384        if encoded.len() > LABEL_SIZE {
385            return Err(GffBinaryError::LabelTooLong {
386                label: label.to_string(),
387                len: encoded.len(),
388                max: LABEL_SIZE,
389            });
390        }
391        let index = to_u32(self.labels.len(), "label_index")?;
392        self.labels.push(label.to_string());
393        self.label_indices.insert(label.to_string(), index);
394        Ok(index)
395    }
396}
397
398fn encode_with_context(
399    text: &str,
400    context: String,
401    encoding: TextEncoding,
402) -> Result<Vec<u8>, GffBinaryError> {
403    encode_text(text, encoding).map_err(|source| GffBinaryError::TextEncoding { context, source })
404}
405
406fn push_u32(bytes: &mut Vec<u8>, value: u32) {
407    bytes.extend_from_slice(&value.to_le_bytes());
408}