nwnrs-gff
nwnrs-gff reads and writes GFF V3.2, the structured container format
underlying a large portion of NWN gameplay data.
Scope
- parse typed GFF roots, structures, fields, and values
- preserve authored field order so stable editing remains possible
- write typed GFF documents back to binary form
- provide a compact typed vocabulary on which higher-level crates can build
The principal entry points are [read_gff_root], [write_gff_root], and
[GffRoot].
Example
use Cursor;
use ;
let mut root = new;
root.put_value?;
let mut bytes = new;
write_gff_root?;
bytes.set_position;
let decoded = read_gff_root?;
assert_eq!;
assert_eq!;
# Ok::
Public Surface
GffRootGffStructGffFieldGffFieldKindGffValueGffCExoLocStringGffErrorGffResultread_gff_rootwrite_gff_rootmerge_root_preserving_provenance
Core Model
GffRootcarries the outer file tag, version, root struct, and optional source provenanceGffStructis an ordered labeled field map keyed by unique labelsGffFieldseparates field metadata fromGffValueGffValuedoes not collapse field kinds into one lossy generic scalar typeGffCExoLocStringpreserves both the top-levelstr_refand the explicit localized override entries
Binary Layout
The crate models GFF V3.2.
0x00 file_type[4] e.g. "UTC ", "ARE ", "GIT "
0x04 file_version[4] "V3.2"
0x08 struct_offset u32
0x0C struct_count u32
0x10 field_offset u32
0x14 field_count u32
0x18 label_offset u32
0x1C label_count u32
0x20 field_data_offset u32
0x24 field_data_size u32
0x28 field_indices_offset u32
0x2C field_indices_size u32
0x30 list_indices_offset u32
0x34 list_indices_size u32
total header size: 56 bytes
After the header:
+----------------------+
| struct table | struct_count * 12
+----------------------+
| field table | field_count * 12
+----------------------+
| label table | label_count * 16
+----------------------+
| field data blob | variable
+----------------------+
| field index array | i32[]
+----------------------+
| list index array | i32[]
+----------------------+
Struct table entry:
i32 id
i32 data_or_offset
i32 field_count
Field table entry:
u32 field_kind
i32 label_index
i32 data_or_offset
Important indirections:
- if a struct has
field_count == 0, it has no fields - if a struct has
field_count == 1,data_or_offsetis the direct field index - if a struct has
field_count > 1,data_or_offsetis a byte offset into the field-index array - list fields point into the list-index array
- complex field kinds point into the field-data blob
Field-Kind Semantics
Inline 32-bit payloads:
ByteCharWordShortDwordIntFloat
Out-of-line payloads in the field-data blob:
Dword64Int64DoubleCExoStringResRefCExoLocStringVoid
Recursive payloads:
StructList
The practical point is that "GFF value" is not one uniform storage class. Reconstruction requires honoring the original split between inline scalars, out-of-line payloads, and recursive references.
Invariants
- the order of fields inside each [
GffStruct] is preserved explicitly - the root
file_typeandfile_versionremain first-class typed fields - each [
GffValue] retains its declared GFF field kind - writes are derived from the typed representation rather than from an unstructured map
- labels must be unique within a struct
- complex fields preserve raw payload bytes when that is needed for stable rewrites
merge_root_preserving_provenanceexists because naive merge logic tends to destroy stable ordering and untouched raw structure
See also
nwnrs-git, which layers typed area-instance semantics over raw GFF datanwnrs-erf, which often carries GFF payloads in NWN archives
Why This Crate Exists
GFF is one of the places where reverse engineering turns into systems design.
The difficult part is not only learning the table layout. It is deciding which
properties are structural enough to model:
- order
- typed field kind
- label identity
- recursive structure
- raw payload fidelity
This crate chooses to preserve all of those explicitly so higher layers can
lift GFF into domain types without pretending the underlying container is a
schema-free blob.