Skip to main content

nwnrs_gff/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = "# nwnrs-gff\n\n`nwnrs-gff` reads and writes `GFF V3.2`, the structured container format\nunderlying a large portion of NWN gameplay data.\n\n## Scope\n\n- parse typed GFF roots, structures, fields, and values\n- preserve authored field order so stable editing remains possible\n- write typed GFF documents back to binary form\n- provide a compact typed vocabulary on which higher-level crates can build\n\nThe principal entry points are [`read_gff_root`], [`write_gff_root`], and\n[`GffRoot`].\n\n## Example\n\n```rust\nuse std::io::Cursor;\n\nuse nwnrs_gff::{GffRoot, GffValue, read_gff_root, write_gff_root};\n\nlet mut root = GffRoot::new(\"UTC \");\nroot.put_value(\"Tag\", GffValue::CExoString(\"nw_chicken\".to_string()))?;\n\nlet mut bytes = Cursor::new(Vec::new());\nwrite_gff_root(&mut bytes, &root)?;\nbytes.set_position(0);\n\nlet decoded = read_gff_root(&mut bytes)?;\nassert_eq!(decoded.file_type, \"UTC \");\nassert_eq!(decoded.fields().len(), 1);\n# Ok::<(), nwnrs_gff::GffError>(())\n```\n\n## Public Surface\n\n- `GffRoot`\n- `GffStruct`\n- `GffField`\n- `GffFieldKind`\n- `GffValue`\n- `GffCExoLocString`\n- `GffError`\n- `GffResult`\n- `read_gff_root`\n- `write_gff_root`\n- `merge_root_preserving_provenance`\n\n## Core Model\n\n- `GffRoot` carries the outer file tag, version, root struct, and optional\n  source provenance\n- `GffStruct` is an ordered labeled field map keyed by unique labels\n- `GffField` separates field metadata from `GffValue`\n- `GffValue` does not collapse field kinds into one lossy generic scalar type\n- `GffCExoLocString` preserves both the top-level `str_ref` and the explicit\n  localized override entries\n\n## Binary Layout\n\nThe crate models `GFF V3.2`.\n\n```text\n0x00  file_type[4]          e.g. \"UTC \", \"ARE \", \"GIT \"\n0x04  file_version[4]       \"V3.2\"\n0x08  struct_offset         u32\n0x0C  struct_count          u32\n0x10  field_offset          u32\n0x14  field_count           u32\n0x18  label_offset          u32\n0x1C  label_count           u32\n0x20  field_data_offset     u32\n0x24  field_data_size       u32\n0x28  field_indices_offset  u32\n0x2C  field_indices_size    u32\n0x30  list_indices_offset   u32\n0x34  list_indices_size     u32\n\ntotal header size: 56 bytes\n```\n\nAfter the header:\n\n```text\n+----------------------+\n| struct table         | struct_count * 12\n+----------------------+\n| field table          | field_count * 12\n+----------------------+\n| label table          | label_count * 16\n+----------------------+\n| field data blob      | variable\n+----------------------+\n| field index array    | i32[]\n+----------------------+\n| list index array     | i32[]\n+----------------------+\n```\n\nStruct table entry:\n\n```text\ni32 id\ni32 data_or_offset\ni32 field_count\n```\n\nField table entry:\n\n```text\nu32 field_kind\ni32 label_index\ni32 data_or_offset\n```\n\nImportant indirections:\n\n- if a struct has `field_count == 0`, it has no fields\n- if a struct has `field_count == 1`, `data_or_offset` is the direct field\n  index\n- if a struct has `field_count > 1`, `data_or_offset` is a byte offset into the\n  field-index array\n- list fields point into the list-index array\n- complex field kinds point into the field-data blob\n\n## Field-Kind Semantics\n\nInline 32-bit payloads:\n\n- `Byte`\n- `Char`\n- `Word`\n- `Short`\n- `Dword`\n- `Int`\n- `Float`\n\nOut-of-line payloads in the field-data blob:\n\n- `Dword64`\n- `Int64`\n- `Double`\n- `CExoString`\n- `ResRef`\n- `CExoLocString`\n- `Void`\n\nRecursive payloads:\n\n- `Struct`\n- `List`\n\nThe practical point is that \"GFF value\" is not one uniform storage class.\nReconstruction requires honoring the original split between inline scalars,\nout-of-line payloads, and recursive references.\n\n## Invariants\n\n- the order of fields inside each [`GffStruct`] is preserved explicitly\n- the root `file_type` and `file_version` remain first-class typed fields\n- each [`GffValue`] retains its declared GFF field kind\n- writes are derived from the typed representation rather than from an\n  unstructured map\n- labels must be unique within a struct\n- complex fields preserve raw payload bytes when that is needed for stable\n  rewrites\n- `merge_root_preserving_provenance` exists because naive merge logic tends to\n  destroy stable ordering and untouched raw structure\n\n## See also\n\n- [`nwnrs-git`](https://docs.rs/nwnrs-git), which layers typed area-instance\n  semantics over raw GFF data\n- [`nwnrs-erf`](https://docs.rs/nwnrs-erf), which often carries GFF payloads in\n  NWN archives\n\n## Why This Crate Exists\n\n`GFF` is one of the places where reverse engineering turns into systems design.\nThe difficult part is not only learning the table layout. It is deciding which\nproperties are structural enough to model:\n\n- order\n- typed field kind\n- label identity\n- recursive structure\n- raw payload fidelity\n\nThis crate chooses to preserve all of those explicitly so higher layers can\nlift `GFF` into domain types without pretending the underlying container is a\nschema-free blob.\n"include_str!("../README.md")]
3
4mod io;
5mod merge;
6mod types;
7
8pub use io::*;
9pub use merge::*;
10pub use types::*;
11
12/// Common imports for consumers of this crate.
13pub mod prelude {
14    pub use crate::{
15        GffCExoLocString, GffError, GffField, GffFieldKind, GffResult, GffRoot, GffStruct,
16        GffValue, merge_root_preserving_provenance, read_gff_root, write_gff_root,
17    };
18}