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#[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#[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}