Skip to main content

nwnrs_gff/
io.rs

1use std::io::{self, Read, Seek, SeekFrom, Write};
2
3use nwnrs_encoding::prelude::*;
4use nwnrs_io::prelude::*;
5use tracing::{debug, instrument};
6
7use crate::{
8    GffCExoLocString, GffError, GffField, GffFieldKind, GffFieldProvenance, GffResult, GffRoot,
9    GffStruct, GffStructProvenance, GffValue, HEADER_SIZE, ensure_label,
10};
11
12#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Header {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["struct_offset", "struct_count", "field_offset", "field_count",
                        "label_offset", "label_count", "field_data_offset",
                        "field_data_size", "field_indices_offset",
                        "field_indices_size", "list_indices_offset",
                        "list_indices_size"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.struct_offset, &self.struct_count, &self.field_offset,
                        &self.field_count, &self.label_offset, &self.label_count,
                        &self.field_data_offset, &self.field_data_size,
                        &self.field_indices_offset, &self.field_indices_size,
                        &self.list_indices_offset, &&self.list_indices_size];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "Header", names,
            values)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for Header {
    #[inline]
    fn clone(&self) -> Header {
        Header {
            struct_offset: ::core::clone::Clone::clone(&self.struct_offset),
            struct_count: ::core::clone::Clone::clone(&self.struct_count),
            field_offset: ::core::clone::Clone::clone(&self.field_offset),
            field_count: ::core::clone::Clone::clone(&self.field_count),
            label_offset: ::core::clone::Clone::clone(&self.label_offset),
            label_count: ::core::clone::Clone::clone(&self.label_count),
            field_data_offset: ::core::clone::Clone::clone(&self.field_data_offset),
            field_data_size: ::core::clone::Clone::clone(&self.field_data_size),
            field_indices_offset: ::core::clone::Clone::clone(&self.field_indices_offset),
            field_indices_size: ::core::clone::Clone::clone(&self.field_indices_size),
            list_indices_offset: ::core::clone::Clone::clone(&self.list_indices_offset),
            list_indices_size: ::core::clone::Clone::clone(&self.list_indices_size),
        }
    }
}Clone)]
13struct Header {
14    struct_offset:        u32,
15    struct_count:         u32,
16    field_offset:         u32,
17    field_count:          u32,
18    label_offset:         u32,
19    label_count:          u32,
20    field_data_offset:    u32,
21    field_data_size:      u32,
22    field_indices_offset: u32,
23    field_indices_size:   u32,
24    list_indices_offset:  u32,
25    list_indices_size:    u32,
26}
27
28#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RawStructEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "RawStructEntry", "id", &self.id, "data_or_offset",
            &self.data_or_offset, "field_count", &&self.field_count)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RawStructEntry {
    #[inline]
    fn clone(&self) -> RawStructEntry {
        RawStructEntry {
            id: ::core::clone::Clone::clone(&self.id),
            data_or_offset: ::core::clone::Clone::clone(&self.data_or_offset),
            field_count: ::core::clone::Clone::clone(&self.field_count),
        }
    }
}Clone)]
29struct RawStructEntry {
30    id:             i32,
31    data_or_offset: i32,
32    field_count:    i32,
33}
34
35#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RawFieldEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "RawFieldEntry",
            "field_kind", &self.field_kind, "label_index", &self.label_index,
            "data_or_offset", &&self.data_or_offset)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RawFieldEntry {
    #[inline]
    fn clone(&self) -> RawFieldEntry {
        RawFieldEntry {
            field_kind: ::core::clone::Clone::clone(&self.field_kind),
            label_index: ::core::clone::Clone::clone(&self.label_index),
            data_or_offset: ::core::clone::Clone::clone(&self.data_or_offset),
        }
    }
}Clone)]
36struct RawFieldEntry {
37    field_kind:     GffFieldKind,
38    label_index:    i32,
39    data_or_offset: i32,
40}
41
42#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RawLabelEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "RawLabelEntry",
            "text", &self.text, "bytes", &&self.bytes)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RawLabelEntry {
    #[inline]
    fn clone(&self) -> RawLabelEntry {
        RawLabelEntry {
            text: ::core::clone::Clone::clone(&self.text),
            bytes: ::core::clone::Clone::clone(&self.bytes),
        }
    }
}Clone)]
43struct RawLabelEntry {
44    text:  String,
45    bytes: [u8; 16],
46}
47
48#[derive(#[automatically_derived]
impl ::core::fmt::Debug for WriteState {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["labels", "structs", "fields", "field_data", "field_indices",
                        "list_indices"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.labels, &self.structs, &self.fields, &self.field_data,
                        &self.field_indices, &&self.list_indices];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "WriteState",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for WriteState {
    #[inline]
    fn default() -> WriteState {
        WriteState {
            labels: ::core::default::Default::default(),
            structs: ::core::default::Default::default(),
            fields: ::core::default::Default::default(),
            field_data: ::core::default::Default::default(),
            field_indices: ::core::default::Default::default(),
            list_indices: ::core::default::Default::default(),
        }
    }
}Default)]
49struct WriteState {
50    labels:        Vec<RawLabelEntry>,
51    structs:       Vec<WriteStructEntry>,
52    fields:        Vec<WriteFieldEntry>,
53    field_data:    Vec<u8>,
54    field_indices: Vec<i32>,
55    list_indices:  Vec<i32>,
56}
57
58#[derive(#[automatically_derived]
impl ::core::fmt::Debug for WriteStructEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "WriteStructEntry", "id", &self.id, "data_or_offset",
            &self.data_or_offset, "field_count", &&self.field_count)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for WriteStructEntry {
    #[inline]
    fn clone(&self) -> WriteStructEntry {
        WriteStructEntry {
            id: ::core::clone::Clone::clone(&self.id),
            data_or_offset: ::core::clone::Clone::clone(&self.data_or_offset),
            field_count: ::core::clone::Clone::clone(&self.field_count),
        }
    }
}Clone, #[automatically_derived]
impl ::core::default::Default for WriteStructEntry {
    #[inline]
    fn default() -> WriteStructEntry {
        WriteStructEntry {
            id: ::core::default::Default::default(),
            data_or_offset: ::core::default::Default::default(),
            field_count: ::core::default::Default::default(),
        }
    }
}Default)]
59struct WriteStructEntry {
60    id:             i32,
61    data_or_offset: i32,
62    field_count:    i32,
63}
64
65#[derive(#[automatically_derived]
impl ::core::fmt::Debug for WriteFieldEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "WriteFieldEntry", "field_kind", &self.field_kind, "label_index",
            &self.label_index, "data_or_offset", &&self.data_or_offset)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for WriteFieldEntry {
    #[inline]
    fn clone(&self) -> WriteFieldEntry {
        WriteFieldEntry {
            field_kind: ::core::clone::Clone::clone(&self.field_kind),
            label_index: ::core::clone::Clone::clone(&self.label_index),
            data_or_offset: ::core::clone::Clone::clone(&self.data_or_offset),
        }
    }
}Clone)]
66struct WriteFieldEntry {
67    field_kind:     GffFieldKind,
68    label_index:    i32,
69    data_or_offset: i32,
70}
71
72/// Reads a complete GFF document from `reader`.
73///
74/// # Errors
75///
76/// Returns [`GffError`] if the data cannot be read or does not conform to the
77/// GFF V3.2 format.
78#[allow(clippy :: redundant_closure_call)]
match (move ||
                {

                    #[allow(unknown_lints, unreachable_code, clippy ::
                    diverging_sub_expression, clippy :: empty_loop, clippy ::
                    let_unit_value, clippy :: let_with_type_underscore, clippy
                    :: needless_return, clippy :: unreachable)]
                    if false {
                        let __tracing_attr_fake_return: GffResult<GffRoot> =
                            loop {};
                        return __tracing_attr_fake_return;
                    }
                    {
                        let start = reader.stream_position()?;
                        reader.seek(SeekFrom::Start(start))?;
                        let mut bytes = Vec::new();
                        reader.read_to_end(&mut bytes)?;
                        let mut reader = io::Cursor::new(bytes.clone());
                        let start = 0;
                        let file_type = read_str_or_err(&mut reader, 4)?;
                        let file_version = read_str_or_err(&mut reader, 4)?;
                        expect(file_type.len() == 4,
                                "GFF file type must be 4 bytes")?;
                        expect(file_version == "V3.2",
                                ::alloc::__export::must_use({
                                        ::alloc::fmt::format(format_args!("unsupported gff version {0}",
                                                file_version))
                                    }))?;
                        let header =
                            Header {
                                struct_offset: read_u32(&mut reader)?,
                                struct_count: read_u32(&mut reader)?,
                                field_offset: read_u32(&mut reader)?,
                                field_count: read_u32(&mut reader)?,
                                label_offset: read_u32(&mut reader)?,
                                label_count: read_u32(&mut reader)?,
                                field_data_offset: read_u32(&mut reader)?,
                                field_data_size: read_u32(&mut reader)?,
                                field_indices_offset: read_u32(&mut reader)?,
                                field_indices_size: read_u32(&mut reader)?,
                                list_indices_offset: read_u32(&mut reader)?,
                                list_indices_size: read_u32(&mut reader)?,
                            };
                        expect(usize::try_from(header.struct_offset).ok() ==
                                    Some(HEADER_SIZE), "unexpected struct offset")?;
                        let labels = read_labels(&mut reader, start, &header)?;
                        let fields =
                            read_field_entries(&mut reader, start, &header)?;
                        let field_indices =
                            read_i32_array(&mut reader,
                                    start + u64::from(header.field_indices_offset),
                                    header.field_indices_size)?;
                        let list_indices =
                            read_i32_array(&mut reader,
                                    start + u64::from(header.list_indices_offset),
                                    header.list_indices_size)?;
                        let structs =
                            read_struct_entries(&mut reader, start, &header)?;
                        let root_struct =
                            parse_struct(0, &mut reader, start, &header, &labels,
                                    &fields, &field_indices, &list_indices, &structs)?;
                        let mut root =
                            GffRoot {
                                file_type,
                                file_version,
                                root: root_struct,
                                source_bytes: Some(bytes),
                                source_snapshot: None,
                            };
                        root.source_snapshot = Some(root.snapshot());
                        {
                            use ::tracing::__macro_support::Callsite as _;
                            static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                                {
                                    static META: ::tracing::Metadata<'static> =
                                        {
                                            ::tracing_core::metadata::Metadata::new("event src/io.rs:149",
                                                "nwnrs_gff::io", ::tracing::Level::DEBUG,
                                                ::tracing_core::__macro_support::Option::Some("src/io.rs"),
                                                ::tracing_core::__macro_support::Option::Some(149u32),
                                                ::tracing_core::__macro_support::Option::Some("nwnrs_gff::io"),
                                                ::tracing_core::field::FieldSet::new(&["message",
                                                                {
                                                                    const NAME:
                                                                        ::tracing::__macro_support::FieldName<{
                                                                            ::tracing::__macro_support::FieldName::len("file_type")
                                                                        }> =
                                                                        ::tracing::__macro_support::FieldName::new("file_type");
                                                                    NAME.as_str()
                                                                }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                                ::tracing::metadata::Kind::EVENT)
                                        };
                                    ::tracing::callsite::DefaultCallsite::new(&META)
                                };
                            let enabled =
                                ::tracing::Level::DEBUG <=
                                            ::tracing::level_filters::STATIC_MAX_LEVEL &&
                                        ::tracing::Level::DEBUG <=
                                            ::tracing::level_filters::LevelFilter::current() &&
                                    {
                                        let interest = __CALLSITE.interest();
                                        !interest.is_never() &&
                                            ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                                interest)
                                    };
                            if enabled {
                                (|value_set: ::tracing::field::ValueSet|
                                            {
                                                let meta = __CALLSITE.metadata();
                                                ::tracing::Event::dispatch(meta, &value_set);
                                                ;
                                            })({
                                        #[allow(unused_imports)]
                                        use ::tracing::field::{debug, display, Value};
                                        __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("read gff root")
                                                                    as &dyn ::tracing::field::Value)),
                                                        (::tracing::__macro_support::Option::Some(&::tracing::field::display(&root.file_type)
                                                                    as &dyn ::tracing::field::Value))])
                                    });
                            } else { ; }
                        };
                        Ok(root)
                    }
                })()
    {
        #[allow(clippy :: unit_arg)]
        Ok(x) => Ok(x),
    Err(e) => {
        {
            use ::tracing::__macro_support::Callsite as _;
            static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                {
                    static META: ::tracing::Metadata<'static> =
                        {
                            ::tracing_core::metadata::Metadata::new("event src/io.rs:78",
                                "nwnrs_gff::io", ::tracing::Level::ERROR,
                                ::tracing_core::__macro_support::Option::Some("src/io.rs"),
                                ::tracing_core::__macro_support::Option::Some(78u32),
                                ::tracing_core::__macro_support::Option::Some("nwnrs_gff::io"),
                                ::tracing_core::field::FieldSet::new(&[{
                                                    const NAME:
                                                        ::tracing::__macro_support::FieldName<{
                                                            ::tracing::__macro_support::FieldName::len("error")
                                                        }> =
                                                        ::tracing::__macro_support::FieldName::new("error");
                                                    NAME.as_str()
                                                }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                ::tracing::metadata::Kind::EVENT)
                        };
                    ::tracing::callsite::DefaultCallsite::new(&META)
                };
            let enabled =
                ::tracing::Level::ERROR <=
                            ::tracing::level_filters::STATIC_MAX_LEVEL &&
                        ::tracing::Level::ERROR <=
                            ::tracing::level_filters::LevelFilter::current() &&
                    {
                        let interest = __CALLSITE.interest();
                        !interest.is_never() &&
                            ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                interest)
                    };
            if enabled {
                (|value_set: ::tracing::field::ValueSet|
                            {
                                let meta = __CALLSITE.metadata();
                                ::tracing::Event::dispatch(meta, &value_set);
                                ;
                            })({
                        #[allow(unused_imports)]
                        use ::tracing::field::{debug, display, Value};
                        __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
                                                    as &dyn ::tracing::field::Value))])
                    });
            } else { ; }
        };
        Err(e)
    }
}#[instrument(level = "debug", skip_all, err)]
79pub fn read_gff_root<R: Read + Seek>(reader: &mut R) -> GffResult<GffRoot> {
80    let start = reader.stream_position()?;
81    reader.seek(SeekFrom::Start(start))?;
82    let mut bytes = Vec::new();
83    reader.read_to_end(&mut bytes)?;
84    let mut reader = io::Cursor::new(bytes.clone());
85    let start = 0;
86
87    let file_type = read_str_or_err(&mut reader, 4)?;
88    let file_version = read_str_or_err(&mut reader, 4)?;
89    expect(file_type.len() == 4, "GFF file type must be 4 bytes")?;
90    expect(
91        file_version == "V3.2",
92        format!("unsupported gff version {file_version}"),
93    )?;
94
95    let header = Header {
96        struct_offset:        read_u32(&mut reader)?,
97        struct_count:         read_u32(&mut reader)?,
98        field_offset:         read_u32(&mut reader)?,
99        field_count:          read_u32(&mut reader)?,
100        label_offset:         read_u32(&mut reader)?,
101        label_count:          read_u32(&mut reader)?,
102        field_data_offset:    read_u32(&mut reader)?,
103        field_data_size:      read_u32(&mut reader)?,
104        field_indices_offset: read_u32(&mut reader)?,
105        field_indices_size:   read_u32(&mut reader)?,
106        list_indices_offset:  read_u32(&mut reader)?,
107        list_indices_size:    read_u32(&mut reader)?,
108    };
109
110    expect(
111        usize::try_from(header.struct_offset).ok() == Some(HEADER_SIZE),
112        "unexpected struct offset",
113    )?;
114
115    let labels = read_labels(&mut reader, start, &header)?;
116    let fields = read_field_entries(&mut reader, start, &header)?;
117    let field_indices = read_i32_array(
118        &mut reader,
119        start + u64::from(header.field_indices_offset),
120        header.field_indices_size,
121    )?;
122    let list_indices = read_i32_array(
123        &mut reader,
124        start + u64::from(header.list_indices_offset),
125        header.list_indices_size,
126    )?;
127    let structs = read_struct_entries(&mut reader, start, &header)?;
128
129    let root_struct = parse_struct(
130        0,
131        &mut reader,
132        start,
133        &header,
134        &labels,
135        &fields,
136        &field_indices,
137        &list_indices,
138        &structs,
139    )?;
140
141    let mut root = GffRoot {
142        file_type,
143        file_version,
144        root: root_struct,
145        source_bytes: Some(bytes),
146        source_snapshot: None,
147    };
148    root.source_snapshot = Some(root.snapshot());
149    debug!(file_type = %root.file_type, "read gff root");
150    Ok(root)
151}
152
153/// Writes a complete GFF document to `writer`.
154///
155/// # Errors
156///
157/// Returns [`GffError`] if the GFF data is invalid or the write fails.
158#[allow(clippy :: redundant_closure_call)]
match (move ||
                {

                    #[allow(unknown_lints, unreachable_code, clippy ::
                    diverging_sub_expression, clippy :: empty_loop, clippy ::
                    let_unit_value, clippy :: let_with_type_underscore, clippy
                    :: needless_return, clippy :: unreachable)]
                    if false {
                        let __tracing_attr_fake_return: GffResult<()> = loop {};
                        return __tracing_attr_fake_return;
                    }
                    {
                        expect(root.file_type.len() == 4,
                                "GFF file type must be 4 bytes")?;
                        expect(root.file_version.len() == 4,
                                "GFF file version must be 4 bytes")?;
                        expect(root.root.id == -1, "root struct id must be -1")?;
                        if let (Some(source_bytes), Some(source_snapshot)) =
                                    (&root.source_bytes, &root.source_snapshot) &&
                                *source_snapshot == root.snapshot() {
                            writer.write_all(source_bytes)?;
                            return Ok(());
                        }
                        let mut state = WriteState::default();
                        let root_idx = collect_struct(&root.root, &mut state)?;
                        expect(root_idx == 0,
                                "root struct must serialize as struct 0")?;
                        let start = writer.stream_position()?;
                        writer.write_all(root.file_type.as_bytes())?;
                        writer.write_all(root.file_version.as_bytes())?;
                        let mut offset =
                            to_u32_len(HEADER_SIZE, "GFF header size")?;
                        write_u32(writer, offset)?;
                        let struct_count =
                            to_u32_len(state.structs.len(), "GFF struct count")?;
                        write_u32(writer, struct_count)?;
                        offset =
                            offset.checked_add(struct_count.saturating_mul(12)).ok_or_else(||
                                        GffError::msg("GFF struct table offset overflow"))?;
                        write_u32(writer, offset)?;
                        let field_count =
                            to_u32_len(state.fields.len(), "GFF field count")?;
                        write_u32(writer, field_count)?;
                        offset =
                            offset.checked_add(field_count.saturating_mul(12)).ok_or_else(||
                                        GffError::msg("GFF field table offset overflow"))?;
                        write_u32(writer, offset)?;
                        let label_count =
                            to_u32_len(state.labels.len(), "GFF label count")?;
                        write_u32(writer, label_count)?;
                        offset =
                            offset.checked_add(label_count.saturating_mul(16)).ok_or_else(||
                                        GffError::msg("GFF label table offset overflow"))?;
                        write_u32(writer, offset)?;
                        let field_data_size =
                            to_u32_len(state.field_data.len(), "GFF field data size")?;
                        write_u32(writer, field_data_size)?;
                        offset =
                            offset.checked_add(field_data_size).ok_or_else(||
                                        GffError::msg("GFF field data offset overflow"))?;
                        write_u32(writer, offset)?;
                        let field_indices_size =
                            state.field_indices.len().checked_mul(4).ok_or_else(||
                                        GffError::msg("GFF field indices size overflow"))?;
                        let field_indices_size =
                            to_u32_len(field_indices_size, "GFF field indices size")?;
                        write_u32(writer, field_indices_size)?;
                        offset =
                            offset.checked_add(field_indices_size).ok_or_else(||
                                        GffError::msg("GFF field indices offset overflow"))?;
                        write_u32(writer, offset)?;
                        let list_indices_size =
                            state.list_indices.len().checked_mul(4).ok_or_else(||
                                        GffError::msg("GFF list indices size overflow"))?;
                        write_u32(writer,
                                to_u32_len(list_indices_size, "GFF list indices size")?)?;
                        for entry in &state.structs {
                            write_i32(writer, entry.id)?;
                            write_i32(writer, entry.data_or_offset)?;
                            write_i32(writer, entry.field_count)?;
                        }
                        for entry in &state.fields {
                            write_u32(writer, entry.field_kind as u32)?;
                            write_i32(writer, entry.label_index)?;
                            write_i32(writer, entry.data_or_offset)?;
                        }
                        for label in &state.labels {
                            ensure_label(&label.text)?;
                            writer.write_all(&label.bytes)?;
                        }
                        writer.write_all(&state.field_data)?;
                        for value in &state.field_indices {
                            write_i32(writer, *value)?;
                        }
                        for value in &state.list_indices {
                            write_i32(writer, *value)?;
                        }
                        let expected_end =
                            start + (HEADER_SIZE as u64) +
                                                    (state.structs.len() as u64 * 12) +
                                                (state.fields.len() as u64 * 12) +
                                            (state.labels.len() as u64 * 16) +
                                        state.field_data.len() as u64 +
                                    (state.field_indices.len() as u64 * 4) +
                                (state.list_indices.len() as u64 * 4);
                        expect(writer.stream_position()? == expected_end,
                                "writer length mismatch")?;
                        {
                            use ::tracing::__macro_support::Callsite as _;
                            static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                                {
                                    static META: ::tracing::Metadata<'static> =
                                        {
                                            ::tracing_core::metadata::Metadata::new("event src/io.rs:279",
                                                "nwnrs_gff::io", ::tracing::Level::DEBUG,
                                                ::tracing_core::__macro_support::Option::Some("src/io.rs"),
                                                ::tracing_core::__macro_support::Option::Some(279u32),
                                                ::tracing_core::__macro_support::Option::Some("nwnrs_gff::io"),
                                                ::tracing_core::field::FieldSet::new(&["message",
                                                                {
                                                                    const NAME:
                                                                        ::tracing::__macro_support::FieldName<{
                                                                            ::tracing::__macro_support::FieldName::len("structs")
                                                                        }> =
                                                                        ::tracing::__macro_support::FieldName::new("structs");
                                                                    NAME.as_str()
                                                                },
                                                                {
                                                                    const NAME:
                                                                        ::tracing::__macro_support::FieldName<{
                                                                            ::tracing::__macro_support::FieldName::len("fields")
                                                                        }> =
                                                                        ::tracing::__macro_support::FieldName::new("fields");
                                                                    NAME.as_str()
                                                                }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                                ::tracing::metadata::Kind::EVENT)
                                        };
                                    ::tracing::callsite::DefaultCallsite::new(&META)
                                };
                            let enabled =
                                ::tracing::Level::DEBUG <=
                                            ::tracing::level_filters::STATIC_MAX_LEVEL &&
                                        ::tracing::Level::DEBUG <=
                                            ::tracing::level_filters::LevelFilter::current() &&
                                    {
                                        let interest = __CALLSITE.interest();
                                        !interest.is_never() &&
                                            ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                                interest)
                                    };
                            if enabled {
                                (|value_set: ::tracing::field::ValueSet|
                                            {
                                                let meta = __CALLSITE.metadata();
                                                ::tracing::Event::dispatch(meta, &value_set);
                                                ;
                                            })({
                                        #[allow(unused_imports)]
                                        use ::tracing::field::{debug, display, Value};
                                        __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("wrote gff root")
                                                                    as &dyn ::tracing::field::Value)),
                                                        (::tracing::__macro_support::Option::Some(&state.structs.len()
                                                                    as &dyn ::tracing::field::Value)),
                                                        (::tracing::__macro_support::Option::Some(&state.fields.len()
                                                                    as &dyn ::tracing::field::Value))])
                                    });
                            } else { ; }
                        };
                        Ok(())
                    }
                })()
    {
        #[allow(clippy :: unit_arg)]
        Ok(x) => Ok(x),
    Err(e) => {
        {
            use ::tracing::__macro_support::Callsite as _;
            static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                {
                    static META: ::tracing::Metadata<'static> =
                        {
                            ::tracing_core::metadata::Metadata::new("event src/io.rs:158",
                                "nwnrs_gff::io", ::tracing::Level::ERROR,
                                ::tracing_core::__macro_support::Option::Some("src/io.rs"),
                                ::tracing_core::__macro_support::Option::Some(158u32),
                                ::tracing_core::__macro_support::Option::Some("nwnrs_gff::io"),
                                ::tracing_core::field::FieldSet::new(&[{
                                                    const NAME:
                                                        ::tracing::__macro_support::FieldName<{
                                                            ::tracing::__macro_support::FieldName::len("error")
                                                        }> =
                                                        ::tracing::__macro_support::FieldName::new("error");
                                                    NAME.as_str()
                                                }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                ::tracing::metadata::Kind::EVENT)
                        };
                    ::tracing::callsite::DefaultCallsite::new(&META)
                };
            let enabled =
                ::tracing::Level::ERROR <=
                            ::tracing::level_filters::STATIC_MAX_LEVEL &&
                        ::tracing::Level::ERROR <=
                            ::tracing::level_filters::LevelFilter::current() &&
                    {
                        let interest = __CALLSITE.interest();
                        !interest.is_never() &&
                            ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                interest)
                    };
            if enabled {
                (|value_set: ::tracing::field::ValueSet|
                            {
                                let meta = __CALLSITE.metadata();
                                ::tracing::Event::dispatch(meta, &value_set);
                                ;
                            })({
                        #[allow(unused_imports)]
                        use ::tracing::field::{debug, display, Value};
                        __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
                                                    as &dyn ::tracing::field::Value))])
                    });
            } else { ; }
        };
        Err(e)
    }
}#[instrument(
159    level = "debug",
160    skip_all,
161    err,
162    fields(file_type = %root.file_type, version = %root.file_version)
163)]
164pub fn write_gff_root<W: Write + Seek>(writer: &mut W, root: &GffRoot) -> GffResult<()> {
165    expect(root.file_type.len() == 4, "GFF file type must be 4 bytes")?;
166    expect(
167        root.file_version.len() == 4,
168        "GFF file version must be 4 bytes",
169    )?;
170    expect(root.root.id == -1, "root struct id must be -1")?;
171    if let (Some(source_bytes), Some(source_snapshot)) = (&root.source_bytes, &root.source_snapshot)
172        && *source_snapshot == root.snapshot()
173    {
174        writer.write_all(source_bytes)?;
175        return Ok(());
176    }
177
178    let mut state = WriteState::default();
179    let root_idx = collect_struct(&root.root, &mut state)?;
180    expect(root_idx == 0, "root struct must serialize as struct 0")?;
181
182    let start = writer.stream_position()?;
183    writer.write_all(root.file_type.as_bytes())?;
184    writer.write_all(root.file_version.as_bytes())?;
185
186    let mut offset = to_u32_len(HEADER_SIZE, "GFF header size")?;
187
188    write_u32(writer, offset)?;
189    let struct_count = to_u32_len(state.structs.len(), "GFF struct count")?;
190    write_u32(writer, struct_count)?;
191    offset = offset
192        .checked_add(struct_count.saturating_mul(12))
193        .ok_or_else(|| GffError::msg("GFF struct table offset overflow"))?;
194
195    write_u32(writer, offset)?;
196    let field_count = to_u32_len(state.fields.len(), "GFF field count")?;
197    write_u32(writer, field_count)?;
198    offset = offset
199        .checked_add(field_count.saturating_mul(12))
200        .ok_or_else(|| GffError::msg("GFF field table offset overflow"))?;
201
202    write_u32(writer, offset)?;
203    let label_count = to_u32_len(state.labels.len(), "GFF label count")?;
204    write_u32(writer, label_count)?;
205    offset = offset
206        .checked_add(label_count.saturating_mul(16))
207        .ok_or_else(|| GffError::msg("GFF label table offset overflow"))?;
208
209    write_u32(writer, offset)?;
210    let field_data_size = to_u32_len(state.field_data.len(), "GFF field data size")?;
211    write_u32(writer, field_data_size)?;
212    offset = offset
213        .checked_add(field_data_size)
214        .ok_or_else(|| GffError::msg("GFF field data offset overflow"))?;
215
216    write_u32(writer, offset)?;
217    let field_indices_size = state
218        .field_indices
219        .len()
220        .checked_mul(4)
221        .ok_or_else(|| GffError::msg("GFF field indices size overflow"))?;
222    let field_indices_size = to_u32_len(field_indices_size, "GFF field indices size")?;
223    write_u32(writer, field_indices_size)?;
224    offset = offset
225        .checked_add(field_indices_size)
226        .ok_or_else(|| GffError::msg("GFF field indices offset overflow"))?;
227
228    write_u32(writer, offset)?;
229    let list_indices_size = state
230        .list_indices
231        .len()
232        .checked_mul(4)
233        .ok_or_else(|| GffError::msg("GFF list indices size overflow"))?;
234    write_u32(
235        writer,
236        to_u32_len(list_indices_size, "GFF list indices size")?,
237    )?;
238
239    for entry in &state.structs {
240        write_i32(writer, entry.id)?;
241        write_i32(writer, entry.data_or_offset)?;
242        write_i32(writer, entry.field_count)?;
243    }
244
245    for entry in &state.fields {
246        write_u32(writer, entry.field_kind as u32)?;
247        write_i32(writer, entry.label_index)?;
248        write_i32(writer, entry.data_or_offset)?;
249    }
250
251    for label in &state.labels {
252        ensure_label(&label.text)?;
253        writer.write_all(&label.bytes)?;
254    }
255
256    writer.write_all(&state.field_data)?;
257
258    for value in &state.field_indices {
259        write_i32(writer, *value)?;
260    }
261
262    for value in &state.list_indices {
263        write_i32(writer, *value)?;
264    }
265
266    let expected_end = start
267        + (HEADER_SIZE as u64)
268        + (state.structs.len() as u64 * 12)
269        + (state.fields.len() as u64 * 12)
270        + (state.labels.len() as u64 * 16)
271        + state.field_data.len() as u64
272        + (state.field_indices.len() as u64 * 4)
273        + (state.list_indices.len() as u64 * 4);
274    expect(
275        writer.stream_position()? == expected_end,
276        "writer length mismatch",
277    )?;
278
279    debug!(
280        structs = state.structs.len(),
281        fields = state.fields.len(),
282        "wrote gff root"
283    );
284    Ok(())
285}
286
287#[allow(clippy::too_many_arguments)]
288fn parse_struct<R: Read + Seek>(
289    struct_idx: usize,
290    reader: &mut R,
291    start: u64,
292    header: &Header,
293    labels: &[RawLabelEntry],
294    fields: &[RawFieldEntry],
295    field_indices: &[i32],
296    list_indices: &[i32],
297    structs: &[RawStructEntry],
298) -> GffResult<GffStruct> {
299    let entry = structs
300        .get(struct_idx)
301        .ok_or_else(|| GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid struct index {0}",
                struct_idx))
    })format!("invalid struct index {struct_idx}")))?;
302
303    let field_refs: Vec<usize> = match entry.field_count {
304        0 => Vec::new(),
305        1 => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [to_usize(entry.data_or_offset, "struct field index")?]))vec![to_usize(entry.data_or_offset, "struct field index")?],
306        count if count > 1 => {
307            let start_idx = to_usize(entry.data_or_offset / 4, "field indices offset")?;
308            let end_idx = start_idx + to_usize(count, "struct field count")?;
309            field_indices
310                .get(start_idx..end_idx)
311                .ok_or_else(|| GffError::msg("field indices slice out of bounds"))?
312                .iter()
313                .map(|idx| to_usize(*idx, "field index"))
314                .collect::<GffResult<Vec<_>>>()?
315        }
316        _ => return Err(GffError::msg("negative field count in struct")),
317    };
318
319    let mut gff_struct = GffStruct::new(entry.id);
320    let mut field_labels = Vec::with_capacity(field_refs.len());
321
322    for field_idx in field_refs {
323        let raw_field = fields
324            .get(field_idx)
325            .ok_or_else(|| GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid field index {0}",
                field_idx))
    })format!("invalid field index {field_idx}")))?;
326        let label = labels
327            .get(to_usize(raw_field.label_index, "label index")?)
328            .ok_or_else(|| GffError::msg("invalid label index"))?
329            .text
330            .clone();
331        field_labels.push(label.clone());
332
333        if gff_struct.get_field(&label).is_some() {
334            return Err(GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("duplicate label in struct: {0}",
                label))
    })format!("duplicate label in struct: {label}")));
335        }
336
337        let field = parse_field(
338            raw_field,
339            reader,
340            start,
341            header,
342            labels,
343            fields,
344            field_indices,
345            list_indices,
346            structs,
347        )?;
348        gff_struct.put_field(label, field)?;
349    }
350
351    gff_struct.provenance = Some(GffStructProvenance {
352        field_labels,
353    });
354
355    Ok(gff_struct)
356}
357
358#[allow(clippy::too_many_arguments)]
359fn parse_field<R: Read + Seek>(
360    raw: &RawFieldEntry,
361    reader: &mut R,
362    start: u64,
363    header: &Header,
364    labels: &[RawLabelEntry],
365    fields: &[RawFieldEntry],
366    field_indices: &[i32],
367    list_indices: &[i32],
368    structs: &[RawStructEntry],
369) -> GffResult<GffField> {
370    let label_bytes = labels
371        .get(to_usize(raw.label_index, "label index")?)
372        .ok_or_else(|| GffError::msg("invalid label index"))?
373        .bytes;
374    let (value, raw_field_data) = match raw.field_kind {
375        GffFieldKind::Byte => (
376            GffValue::Byte(
377                u8::try_from(raw.data_or_offset)
378                    .map_err(|_error| GffError::msg("byte field value out of range"))?,
379            ),
380            None,
381        ),
382        GffFieldKind::Char => (
383            GffValue::Char(
384                i8::try_from(raw.data_or_offset)
385                    .map_err(|_error| GffError::msg("char field value out of range"))?,
386            ),
387            None,
388        ),
389        GffFieldKind::Word => (
390            GffValue::Word(
391                u16::try_from(raw.data_or_offset)
392                    .map_err(|_error| GffError::msg("word field value out of range"))?,
393            ),
394            None,
395        ),
396        GffFieldKind::Short => (
397            GffValue::Short(
398                i16::try_from(raw.data_or_offset)
399                    .map_err(|_error| GffError::msg("short field value out of range"))?,
400            ),
401            None,
402        ),
403        GffFieldKind::Dword => (
404            GffValue::Dword(u32::from_ne_bytes(raw.data_or_offset.to_ne_bytes())),
405            None,
406        ),
407        GffFieldKind::Int => (GffValue::Int(raw.data_or_offset), None),
408        GffFieldKind::Float => (
409            GffValue::Float(f32::from_bits(u32::from_ne_bytes(
410                raw.data_or_offset.to_ne_bytes(),
411            ))),
412            None,
413        ),
414        GffFieldKind::Dword64 => {
415            seek_field_data(reader, start, header, raw.data_or_offset)?;
416            let bytes = read_bytes_or_err(reader, 8)?;
417            let mut data = [0_u8; 8];
418            data.copy_from_slice(&bytes);
419            (GffValue::Dword64(u64::from_le_bytes(data)), Some(bytes))
420        }
421        GffFieldKind::Int64 => {
422            seek_field_data(reader, start, header, raw.data_or_offset)?;
423            let bytes = read_bytes_or_err(reader, 8)?;
424            let mut data = [0_u8; 8];
425            data.copy_from_slice(&bytes);
426            (GffValue::Int64(i64::from_le_bytes(data)), Some(bytes))
427        }
428        GffFieldKind::Double => {
429            seek_field_data(reader, start, header, raw.data_or_offset)?;
430            let bytes = read_bytes_or_err(reader, 8)?;
431            let mut data = [0_u8; 8];
432            data.copy_from_slice(&bytes);
433            (
434                GffValue::Double(f64::from_bits(u64::from_le_bytes(data))),
435                Some(bytes),
436            )
437        }
438        GffFieldKind::CExoString => {
439            seek_field_data(reader, start, header, raw.data_or_offset)?;
440            let size = read_i32(reader)?;
441            let bytes = read_bytes_or_err(reader, to_usize(size, "CExoString length")?)?;
442            let decoded =
443                from_nwnrs_encoding(&bytes).map_err(|error| GffError::msg(error.to_string()))?;
444            let mut raw_bytes = size.to_le_bytes().to_vec();
445            raw_bytes.extend_from_slice(&bytes);
446            (GffValue::CExoString(decoded), Some(raw_bytes))
447        }
448        GffFieldKind::ResRef => {
449            seek_field_data(reader, start, header, raw.data_or_offset)?;
450            let size = usize::try_from(read_i8(reader)?)
451                .map_err(|_error| GffError::msg("negative ResRef length"))?;
452            let bytes = read_bytes_or_err(reader, size)?;
453            let mut raw_bytes =
454                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [u8::try_from(size).map_err(|_error|
                            GffError::msg("ResRef too long"))?]))vec![u8::try_from(size).map_err(|_error| GffError::msg("ResRef too long"))?];
455            raw_bytes.extend_from_slice(&bytes);
456            (
457                GffValue::ResRef(String::from_utf8_lossy(&bytes).to_string()),
458                Some(raw_bytes),
459            )
460        }
461        GffFieldKind::CExoLocString => {
462            seek_field_data(reader, start, header, raw.data_or_offset)?;
463            let total_size = read_i32(reader)?;
464            let payload_start = reader.stream_position()?;
465            let str_ref = read_u32(reader)?;
466            let count = read_i32(reader)?;
467            let mut entries = Vec::with_capacity(to_usize(count, "locstring count")?);
468            for _ in 0..count {
469                let language = read_i32(reader)?;
470                let size = read_i32(reader)?;
471                let bytes = read_bytes_or_err(reader, to_usize(size, "locstring entry length")?)?;
472                let decoded = from_nwnrs_encoding(&bytes)
473                    .map_err(|error| GffError::msg(error.to_string()))?;
474                entries.push((language, decoded));
475            }
476            let consumed = reader.stream_position()? - payload_start;
477            expect(
478                consumed
479                    == u64::try_from(total_size)
480                        .map_err(|_error| GffError::msg("negative CExoLocString payload size"))?,
481                "invalid CExoLocString payload size",
482            )?;
483            let mut raw_bytes = total_size.to_le_bytes().to_vec();
484            reader.seek(SeekFrom::Start(payload_start))?;
485            raw_bytes.extend_from_slice(&read_bytes_or_err(
486                reader,
487                usize::try_from(total_size)
488                    .map_err(|_error| GffError::msg("negative CExoLocString payload size"))?,
489            )?);
490            (
491                GffValue::CExoLocString(GffCExoLocString {
492                    str_ref,
493                    entries,
494                }),
495                Some(raw_bytes),
496            )
497        }
498        GffFieldKind::Void => {
499            seek_field_data(reader, start, header, raw.data_or_offset)?;
500            let size = read_u32(reader)?;
501            let bytes = read_bytes_or_err(
502                reader,
503                usize::try_from(size)
504                    .map_err(|_error| GffError::msg("void field size exceeds usize"))?,
505            )?;
506            let mut raw_bytes = size.to_le_bytes().to_vec();
507            raw_bytes.extend_from_slice(&bytes);
508            (GffValue::Void(bytes), Some(raw_bytes))
509        }
510        GffFieldKind::Struct => (
511            GffValue::Struct(parse_struct(
512                to_usize(raw.data_or_offset, "struct field offset")?,
513                reader,
514                start,
515                header,
516                labels,
517                fields,
518                field_indices,
519                list_indices,
520                structs,
521            )?),
522            None,
523        ),
524        GffFieldKind::List => {
525            let offset = to_usize(raw.data_or_offset / 4, "list offset")?;
526            let count = *list_indices
527                .get(offset)
528                .ok_or_else(|| GffError::msg("list size offset out of bounds"))?;
529            let start_idx = offset + 1;
530            let end_idx = start_idx + to_usize(count, "list size")?;
531            let list = list_indices
532                .get(start_idx..end_idx)
533                .ok_or_else(|| GffError::msg("list indices slice out of bounds"))?
534                .iter()
535                .map(|idx| {
536                    parse_struct(
537                        to_usize(*idx, "list struct index")?,
538                        reader,
539                        start,
540                        header,
541                        labels,
542                        fields,
543                        field_indices,
544                        list_indices,
545                        structs,
546                    )
547                })
548                .collect::<GffResult<Vec<_>>>()?;
549            (GffValue::List(list), None)
550        }
551    };
552    let original_value = value.clone();
553    Ok(GffField::with_provenance(
554        value,
555        GffFieldProvenance {
556            label_bytes,
557            original_value,
558            raw_data_or_offset: raw.data_or_offset,
559            raw_field_data,
560        },
561    ))
562}
563
564#[allow(clippy::too_many_lines)]
565fn collect_struct(structure: &GffStruct, state: &mut WriteState) -> GffResult<i32> {
566    let struct_idx = to_i32_len(state.structs.len(), "GFF struct index")?;
567    state.structs.push(WriteStructEntry {
568        id: structure.id,
569        ..WriteStructEntry::default()
570    });
571
572    let mut struct_field_ids = Vec::new();
573    for (label, field) in structure.fields() {
574        ensure_label(label)?;
575        let label_index = to_i32_len(
576            get_or_insert_label(label, field.provenance.as_ref(), &mut state.labels),
577            "GFF label index",
578        )?;
579        let field_idx = to_i32_len(state.fields.len(), "GFF field index")?;
580        state.fields.push(WriteFieldEntry {
581            field_kind: field.kind(),
582            label_index,
583            data_or_offset: 0,
584        });
585        struct_field_ids.push(field_idx);
586
587        let data_or_offset = match field.value() {
588            GffValue::Byte(value) => i32::from(*value),
589            GffValue::Char(value) => i32::from(*value),
590            GffValue::Word(value) => i32::from(*value),
591            GffValue::Short(value) => i32::from(*value),
592            GffValue::Dword(value) => i32::from_ne_bytes(value.to_ne_bytes()),
593            GffValue::Int(value) => *value,
594            GffValue::Float(value) => i32::from_ne_bytes(value.to_bits().to_ne_bytes()),
595            GffValue::Dword64(value) => {
596                let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
597                state.field_data.extend_from_slice(&value.to_le_bytes());
598                offset
599            }
600            GffValue::Int64(value) => {
601                let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
602                state.field_data.extend_from_slice(&value.to_le_bytes());
603                offset
604            }
605            GffValue::Double(value) => {
606                let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
607                state
608                    .field_data
609                    .extend_from_slice(&value.to_bits().to_le_bytes());
610                offset
611            }
612            GffValue::CExoString(value) => {
613                if let Some(provenance) = &field.provenance
614                    && provenance.original_value == *field.value()
615                    && let Some(raw_bytes) = &provenance.raw_field_data
616                {
617                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
618                    state.field_data.extend_from_slice(raw_bytes);
619                    offset
620                } else {
621                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
622                    let encoded = to_nwnrs_encoding(value)
623                        .map_err(|error| GffError::msg(error.to_string()))?;
624                    state.field_data.extend_from_slice(
625                        &to_i32_len(encoded.len(), "CExoString length")?.to_le_bytes(),
626                    );
627                    state.field_data.extend_from_slice(&encoded);
628                    offset
629                }
630            }
631            GffValue::ResRef(value) => {
632                if let Some(provenance) = &field.provenance
633                    && provenance.original_value == *field.value()
634                    && let Some(raw_bytes) = &provenance.raw_field_data
635                {
636                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
637                    state.field_data.extend_from_slice(raw_bytes);
638                    offset
639                } else {
640                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
641                    expect(u8::try_from(value.len()).is_ok(), "ResRef too long for GFF")?;
642                    state.field_data.push(
643                        u8::try_from(value.len())
644                            .map_err(|_error| GffError::msg("ResRef too long for GFF"))?,
645                    );
646                    state.field_data.extend_from_slice(value.as_bytes());
647                    offset
648                }
649            }
650            GffValue::CExoLocString(value) => {
651                if let Some(provenance) = &field.provenance
652                    && provenance.original_value == *field.value()
653                    && let Some(raw_bytes) = &provenance.raw_field_data
654                {
655                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
656                    state.field_data.extend_from_slice(raw_bytes);
657                    offset
658                } else {
659                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
660                    let mut payload = Vec::new();
661                    for (language, text) in &value.entries {
662                        let encoded = to_nwnrs_encoding(text)
663                            .map_err(|error| GffError::msg(error.to_string()))?;
664                        payload.extend_from_slice(&language.to_le_bytes());
665                        payload.extend_from_slice(
666                            &to_i32_len(encoded.len(), "CExoLocString entry length")?.to_le_bytes(),
667                        );
668                        payload.extend_from_slice(&encoded);
669                    }
670                    state.field_data.extend_from_slice(
671                        &to_i32_len(payload.len() + 8, "CExoLocString payload size")?.to_le_bytes(),
672                    );
673                    state
674                        .field_data
675                        .extend_from_slice(&value.str_ref.to_le_bytes());
676                    state.field_data.extend_from_slice(
677                        &to_i32_len(value.entries.len(), "CExoLocString entry count")?
678                            .to_le_bytes(),
679                    );
680                    state.field_data.extend_from_slice(&payload);
681                    offset
682                }
683            }
684            GffValue::Void(value) => {
685                if let Some(provenance) = &field.provenance
686                    && provenance.original_value == *field.value()
687                    && let Some(raw_bytes) = &provenance.raw_field_data
688                {
689                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
690                    state.field_data.extend_from_slice(raw_bytes);
691                    offset
692                } else {
693                    let offset = to_i32_len(state.field_data.len(), "GFF field data offset")?;
694                    state
695                        .field_data
696                        .extend_from_slice(&to_u32_len(value.len(), "void length")?.to_le_bytes());
697                    state.field_data.extend_from_slice(value);
698                    offset
699                }
700            }
701            GffValue::Struct(child) => collect_struct(child, state)?,
702            GffValue::List(list) => {
703                let offset = to_i32_len(
704                    state
705                        .list_indices
706                        .len()
707                        .checked_mul(4)
708                        .ok_or_else(|| GffError::msg("GFF list indices size overflow"))?,
709                    "GFF list offset",
710                )?;
711                let list_len = to_i32_len(list.len(), "GFF list size")?;
712                let reserved = list
713                    .len()
714                    .checked_add(1)
715                    .ok_or_else(|| GffError::msg("GFF list reservation overflow"))?;
716                let list_start = state.list_indices.len();
717                state.list_indices.resize(
718                    state
719                        .list_indices
720                        .len()
721                        .checked_add(reserved)
722                        .ok_or_else(|| GffError::msg("GFF list indices size overflow"))?,
723                    0,
724                );
725                *state
726                    .list_indices
727                    .get_mut(list_start)
728                    .ok_or_else(|| GffError::msg("GFF list header slot out of range"))? = list_len;
729
730                for (idx, child) in list.iter().enumerate() {
731                    *state
732                        .list_indices
733                        .get_mut(list_start + idx + 1)
734                        .ok_or_else(|| GffError::msg("GFF list child slot out of range"))? =
735                        collect_struct(child, state)?;
736                }
737                offset
738            }
739        };
740
741        state
742            .fields
743            .get_mut(to_usize(field_idx, "GFF field index")?)
744            .ok_or_else(|| GffError::msg("GFF field entry out of range"))?
745            .data_or_offset = data_or_offset;
746    }
747
748    let entry = state
749        .structs
750        .get_mut(to_usize(struct_idx, "GFF struct index")?)
751        .ok_or_else(|| GffError::msg("GFF struct entry out of range"))?;
752    entry.field_count = to_i32_len(struct_field_ids.len(), "GFF struct field count")?;
753    entry.data_or_offset = match struct_field_ids.len() {
754        0 => 0,
755        1 => *struct_field_ids
756            .first()
757            .ok_or_else(|| GffError::msg("missing GFF struct field index"))?,
758        _ => {
759            let offset = to_i32_len(
760                state
761                    .field_indices
762                    .len()
763                    .checked_mul(4)
764                    .ok_or_else(|| GffError::msg("GFF field indices size overflow"))?,
765                "GFF field indices offset",
766            )?;
767            state.field_indices.extend(struct_field_ids);
768            offset
769        }
770    };
771
772    Ok(struct_idx)
773}
774
775fn get_or_insert_label(
776    label: &str,
777    provenance: Option<&GffFieldProvenance>,
778    labels: &mut Vec<RawLabelEntry>,
779) -> usize {
780    if let Some(idx) = labels.iter().position(|existing| existing.text == label) {
781        idx
782    } else {
783        let bytes = provenance
784            .filter(|provenance| trim_trailing_nuls(&provenance.label_bytes) == label)
785            .map_or_else(
786                || {
787                    let mut padded = [0_u8; 16];
788                    let label_bytes = label.as_bytes();
789                    if let Some(prefix) = padded.get_mut(..label_bytes.len()) {
790                        prefix.copy_from_slice(label_bytes);
791                    }
792                    padded
793                },
794                |provenance| provenance.label_bytes,
795            );
796        labels.push(RawLabelEntry {
797            text: label.to_string(),
798            bytes,
799        });
800        labels.len() - 1
801    }
802}
803
804fn read_labels<R: Read + Seek>(
805    reader: &mut R,
806    start: u64,
807    header: &Header,
808) -> GffResult<Vec<RawLabelEntry>> {
809    reader.seek(SeekFrom::Start(start + u64::from(header.label_offset)))?;
810    (0..header.label_count)
811        .map(|_| {
812            let bytes = read_bytes_or_err(reader, 16)?;
813            let mut raw = [0_u8; 16];
814            raw.copy_from_slice(&bytes);
815            Ok(RawLabelEntry {
816                text:  trim_trailing_nuls(&bytes),
817                bytes: raw,
818            })
819        })
820        .collect()
821}
822
823fn read_field_entries<R: Read + Seek>(
824    reader: &mut R,
825    start: u64,
826    header: &Header,
827) -> GffResult<Vec<RawFieldEntry>> {
828    reader.seek(SeekFrom::Start(start + u64::from(header.field_offset)))?;
829    (0..header.field_count)
830        .map(|_| {
831            let kind = read_u32(reader)?;
832            let field_kind = GffFieldKind::from_u32(kind)
833                .ok_or_else(|| GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid GFF field kind {0}", kind))
    })format!("invalid GFF field kind {kind}")))?;
834            Ok(RawFieldEntry {
835                field_kind,
836                label_index: read_i32(reader)?,
837                data_or_offset: read_i32(reader)?,
838            })
839        })
840        .collect()
841}
842
843fn read_struct_entries<R: Read + Seek>(
844    reader: &mut R,
845    start: u64,
846    header: &Header,
847) -> GffResult<Vec<RawStructEntry>> {
848    reader.seek(SeekFrom::Start(start + u64::from(header.struct_offset)))?;
849    (0..header.struct_count)
850        .map(|_| {
851            Ok(RawStructEntry {
852                id:             read_i32(reader)?,
853                data_or_offset: read_i32(reader)?,
854                field_count:    read_i32(reader)?,
855            })
856        })
857        .collect()
858}
859
860fn read_i32_array<R: Read + Seek>(
861    reader: &mut R,
862    offset: u64,
863    size_in_bytes: u32,
864) -> GffResult<Vec<i32>> {
865    reader.seek(SeekFrom::Start(offset))?;
866    let count = usize::try_from(size_in_bytes)
867        .map_err(|_error| GffError::msg("GFF i32 array size exceeds usize"))?
868        / 4;
869    (0..count)
870        .map(|_| read_i32(reader).map_err(GffError::from))
871        .collect()
872}
873
874fn seek_field_data<R: Seek>(
875    reader: &mut R,
876    start: u64,
877    header: &Header,
878    data_or_offset: i32,
879) -> GffResult<()> {
880    let offset = to_usize(data_or_offset, "field data offset")?;
881    expect(
882        usize::try_from(header.field_data_size)
883            .ok()
884            .is_some_and(|field_data_size| offset < field_data_size),
885        "field data offset out of range",
886    )?;
887    reader.seek(SeekFrom::Start(
888        start + u64::from(header.field_data_offset) + offset as u64,
889    ))?;
890    Ok(())
891}
892
893fn trim_trailing_nuls(bytes: &[u8]) -> String {
894    let end = bytes
895        .iter()
896        .position(|byte| *byte == 0)
897        .unwrap_or(bytes.len());
898    String::from_utf8_lossy(bytes.get(..end).unwrap_or(&[])).to_string()
899}
900
901fn to_usize(value: i32, what: &str) -> GffResult<usize> {
902    usize::try_from(value).map_err(|_error| GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("negative {0}: {1}", what, value))
    })format!("negative {what}: {value}")))
903}
904
905fn to_i32_len(value: usize, what: &str) -> GffResult<i32> {
906    i32::try_from(value).map_err(|_error| GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} exceeds 32-bit range", what))
    })format!("{what} exceeds 32-bit range")))
907}
908
909fn to_u32_len(value: usize, what: &str) -> GffResult<u32> {
910    u32::try_from(value).map_err(|_error| GffError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} exceeds 32-bit range", what))
    })format!("{what} exceeds 32-bit range")))
911}
912
913fn read_i8<R: Read>(reader: &mut R) -> io::Result<i8> {
914    let mut bytes = [0_u8; 1];
915    reader.read_exact(&mut bytes)?;
916    Ok(i8::from_ne_bytes([bytes[0]]))
917}
918
919fn read_u32<R: Read>(reader: &mut R) -> io::Result<u32> {
920    let mut bytes = [0_u8; 4];
921    reader.read_exact(&mut bytes)?;
922    Ok(u32::from_le_bytes(bytes))
923}
924
925fn read_i32<R: Read>(reader: &mut R) -> io::Result<i32> {
926    let mut bytes = [0_u8; 4];
927    reader.read_exact(&mut bytes)?;
928    Ok(i32::from_le_bytes(bytes))
929}
930
931fn write_u32<W: Write>(writer: &mut W, value: u32) -> io::Result<()> {
932    writer.write_all(&value.to_le_bytes())
933}
934
935fn write_i32<W: Write>(writer: &mut W, value: i32) -> io::Result<()> {
936    writer.write_all(&value.to_le_bytes())
937}
938
939#[allow(clippy::panic)]
940#[cfg(test)]
941mod tests {
942    use std::io::Cursor;
943
944    use super::{read_gff_root, write_gff_root};
945    use crate::{GffRoot, GffStruct, GffValue};
946
947    #[test]
948    fn edited_source_backed_gff_preserves_value_edit() {
949        let mut original = GffRoot::new("UTC ");
950        if let Err(error) =
951            original.put_value("Comment", GffValue::CExoString("before".to_string()))
952        {
953            panic!("seed gff: {error}");
954        }
955        let mut encoded = Cursor::new(Vec::new());
956        if let Err(error) = write_gff_root(&mut encoded, &original) {
957            panic!("encode gff: {error}");
958        }
959
960        let mut parsed = match read_gff_root(&mut Cursor::new(encoded.into_inner())) {
961            Ok(root) => root,
962            Err(error) => panic!("parse gff: {error}"),
963        };
964        if let Err(error) = parsed.put_value("Comment", GffValue::CExoString("after".to_string())) {
965            panic!("edit gff: {error}");
966        }
967
968        let mut output = Cursor::new(Vec::new());
969        if let Err(error) = write_gff_root(&mut output, &parsed) {
970            panic!("rewrite gff: {error}");
971        }
972        let reparsed = match read_gff_root(&mut Cursor::new(output.into_inner())) {
973            Ok(root) => root,
974            Err(error) => panic!("reparse gff: {error}"),
975        };
976        let comment =
977            match reparsed
978                .root
979                .get_field("Comment")
980                .and_then(|field| match field.value() {
981                    GffValue::CExoString(value) => Some(value.clone()),
982                    _ => None,
983                }) {
984                Some(comment) => comment,
985                None => panic!("comment field"),
986            };
987        assert_eq!(comment, "after");
988    }
989
990    #[test]
991    fn edited_source_backed_gff_allows_structural_field_edits() {
992        let mut original = GffRoot::new("UTC ");
993        if let Err(error) = original.put_value("First", GffValue::CExoString("one".to_string())) {
994            panic!("seed first: {error}");
995        }
996        if let Err(error) = original.put_value("Second", GffValue::Int(2)) {
997            panic!("seed second: {error}");
998        }
999        let mut encoded = Cursor::new(Vec::new());
1000        if let Err(error) = write_gff_root(&mut encoded, &original) {
1001            panic!("encode gff: {error}");
1002        }
1003
1004        let mut parsed = match read_gff_root(&mut Cursor::new(encoded.into_inner())) {
1005            Ok(root) => root,
1006            Err(error) => panic!("parse gff: {error}"),
1007        };
1008        parsed.root.remove("First");
1009        if let Err(error) = parsed.put_value("Third", GffValue::Struct(GffStruct::new(7))) {
1010            panic!("insert third: {error}");
1011        }
1012
1013        let mut output = Cursor::new(Vec::new());
1014        if let Err(error) = write_gff_root(&mut output, &parsed) {
1015            panic!("rewrite gff: {error}");
1016        }
1017        let reparsed = match read_gff_root(&mut Cursor::new(output.into_inner())) {
1018            Ok(root) => root,
1019            Err(error) => panic!("reparse gff: {error}"),
1020        };
1021
1022        assert!(reparsed.root.get_field("First").is_none());
1023        assert!(matches!(
1024            reparsed.root.get_field("Second").map(|field| field.value()),
1025            Some(GffValue::Int(2))
1026        ));
1027        assert!(matches!(
1028            reparsed.root.get_field("Third").map(|field| field.value()),
1029            Some(GffValue::Struct(_))
1030        ));
1031    }
1032
1033    #[test]
1034    fn edited_source_backed_gff_allows_list_resize() {
1035        let mut original = GffRoot::new("UTC ");
1036        if let Err(error) = original.put_value(
1037            "Items",
1038            GffValue::List(vec![GffStruct::new(1), GffStruct::new(2)]),
1039        ) {
1040            panic!("seed list: {error}");
1041        }
1042        let mut encoded = Cursor::new(Vec::new());
1043        if let Err(error) = write_gff_root(&mut encoded, &original) {
1044            panic!("encode gff: {error}");
1045        }
1046
1047        let mut parsed = match read_gff_root(&mut Cursor::new(encoded.into_inner())) {
1048            Ok(root) => root,
1049            Err(error) => panic!("parse gff: {error}"),
1050        };
1051        if let Err(error) = parsed.put_value("Items", GffValue::List(vec![GffStruct::new(1)])) {
1052            panic!("shrink list: {error}");
1053        }
1054
1055        let mut output = Cursor::new(Vec::new());
1056        if let Err(error) = write_gff_root(&mut output, &parsed) {
1057            panic!("rewrite gff: {error}");
1058        }
1059        let reparsed = match read_gff_root(&mut Cursor::new(output.into_inner())) {
1060            Ok(root) => root,
1061            Err(error) => panic!("reparse gff: {error}"),
1062        };
1063
1064        assert!(matches!(
1065            reparsed.root.get_field("Items").map(|field| field.value()),
1066            Some(GffValue::List(items)) if items.len() == 1
1067        ));
1068    }
1069
1070    #[test]
1071    fn malformed_gff_index_offsets_are_rejected() {
1072        let mut original = GffRoot::new("UTC ");
1073        if let Err(error) = original.put_value("Items", GffValue::List(vec![GffStruct::new(1)])) {
1074            panic!("seed list: {error}");
1075        }
1076        let mut encoded = Cursor::new(Vec::new());
1077        if let Err(error) = write_gff_root(&mut encoded, &original) {
1078            panic!("encode gff: {error}");
1079        }
1080        let mut bytes = encoded.into_inner();
1081
1082        let list_indices_offset_bytes = match bytes.get(48..52) {
1083            Some(bytes) => bytes,
1084            None => panic!("fixture list index offset should exist"),
1085        };
1086        let list_indices_offset = match <[u8; 4]>::try_from(list_indices_offset_bytes) {
1087            Ok(bytes) => u32::from_le_bytes(bytes),
1088            Err(_error) => panic!("offset"),
1089        };
1090        if let Some(offset_bytes) = bytes.get_mut(48..52) {
1091            offset_bytes.copy_from_slice(&(list_indices_offset + 1).to_le_bytes());
1092        } else {
1093            panic!("fixture list index offset should exist");
1094        }
1095
1096        let error = match read_gff_root(&mut Cursor::new(bytes)) {
1097            Ok(_root) => panic!("malformed gff should fail"),
1098            Err(error) => error,
1099        };
1100        assert!(
1101            error.to_string().contains("failed to fill whole buffer")
1102                || error.to_string().contains("out of bounds")
1103                || error.to_string().contains("range"),
1104            "unexpected error: {error}"
1105        );
1106    }
1107}