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