1mod coerce;
41mod document;
42mod label;
43mod path;
44mod reader;
45mod walk;
46mod writer;
47
48pub use coerce::*;
49pub use document::{Change, GffDocument, GffDocumentError};
50pub use label::*;
51pub use path::{GffPath, GffPathError, GffPathParseError, GffPathSegment};
52pub use reader::{read_gff, read_gff_from_bytes};
53pub use walk::GffWalk;
54pub use writer::{write_gff, write_gff_to_vec};
55
56use num_enum::{IntoPrimitive, TryFromPrimitive};
57use thiserror::Error;
58
59use rakata_core::{DecodeTextError, EncodeTextError, ResRef, StrRef, TextEncoding};
60
61use crate::binary::{self, DecodeBinary, EncodeBinary};
62
63const GFF_HEADER_SIZE: usize = 56;
65const STRUCT_ENTRY_SIZE: usize = 12;
67const FIELD_ENTRY_SIZE: usize = 12;
69const LABEL_SIZE: usize = 16;
71const GFF_VERSION_V32: [u8; 4] = *b"V3.2";
73const DEFAULT_TEXT_ENCODING: TextEncoding = TextEncoding::Windows1252;
75
76pub const GENERIC_FILE_TYPE: [u8; 4] = *b"GFF ";
89
90#[derive(Debug, Clone, PartialEq)]
92pub struct Gff {
93 pub file_type: [u8; 4],
95 pub root: GffStruct,
97}
98
99impl Gff {
100 pub fn new(file_type: [u8; 4], root: GffStruct) -> Self {
102 Self { file_type, root }
103 }
104
105 pub fn generic(root: GffStruct) -> Self {
107 Self {
108 file_type: GENERIC_FILE_TYPE,
109 root,
110 }
111 }
112}
113
114impl DecodeBinary for Gff {
115 type Error = GffBinaryError;
116
117 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
118 read_gff_from_bytes(bytes)
119 }
120}
121
122impl EncodeBinary for Gff {
123 type Error = GffBinaryError;
124
125 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
126 write_gff_to_vec(self)
127 }
128}
129
130#[derive(Debug, Clone, PartialEq)]
132pub struct GffStruct {
133 pub struct_id: i32,
135 pub fields: Vec<GffField>,
137}
138
139impl GffStruct {
140 pub fn new(struct_id: i32) -> Self {
142 Self {
143 struct_id,
144 fields: Vec::new(),
145 }
146 }
147
148 pub fn with_fields(struct_id: i32, fields: Vec<GffField>) -> Self {
150 Self { struct_id, fields }
151 }
152
153 pub fn push_field(&mut self, label: GffLabel, value: GffValue) {
161 self.fields.push(GffField { label, value });
162 }
163
164 pub fn field(&self, label: &str) -> Option<&GffValue> {
174 self.fields
175 .iter()
176 .find(|field| field.label == label)
177 .map(|field| &field.value)
178 }
179}
180
181#[derive(Debug, Clone, PartialEq)]
183pub struct GffField {
184 pub label: GffLabel,
186 pub value: GffValue,
188}
189
190macro_rules! gff_field_types {
210 ($emit:ident) => {
211 $emit! {
212 UInt8 = 0, "BYTE", "unsigned 8-bit integer", u8, u8;
213 Int8 = 1, "CHAR", "signed 8-bit integer", i8, i8;
214 UInt16 = 2, "WORD", "unsigned 16-bit integer", u16, u16;
215 Int16 = 3, "SHORT", "signed 16-bit integer", i16, i16;
216 UInt32 = 4, "DWORD", "unsigned 32-bit integer", u32, u32;
217 Int32 = 5, "INT", "signed 32-bit integer", i32, i32;
218 UInt64 = 6, "DWORD64", "unsigned 64-bit integer", u64, u64;
219 Int64 = 7, "INT64", "signed 64-bit integer", i64, i64;
220 Single = 8, "FLOAT", "32-bit floating point", f32, f32;
221 Double = 9, "DOUBLE", "64-bit floating point", f64, f64;
222 String = 10, "CExoString", "variable-length string", String, String;
223 ResRef = 11, "CResRef", "resource reference, at most 16 bytes", ResRef, String;
224 LocalizedString = 12, "CExoLocString",
225 "localized string with an optional TLK reference",
226 GffLocalizedString, GffLocalizedStringDto;
227 Binary = 13, "VOID", "raw binary blob", Vec<u8>,
228 #[serde(with = "hex_bytes")] Vec<u8>;
229 Struct = 14, "Struct", "nested struct", Box<GffStruct>, Box<GffStructDto>;
230 List = 15, "List", "struct list", Vec<GffStruct>, Vec<GffStructDto>;
231 Vector4 = 16, "Quaternion", "four packed `f32`, scalar first", [f32; 4], [f32; 4];
232 Vector3 = 17, "Vector", "three packed `f32`", [f32; 3], [f32; 3];
233 }
234 };
235}
236
237pub(crate) use gff_field_types;
238
239macro_rules! emit_gff_value {
240 ($($variant:ident = $id:literal, $wire:literal, $description:literal, $value:ty,
241 $(#[$dto_attr:meta])* $dto:ty;)*) => {
242 #[derive(Debug, Clone, PartialEq)]
244 pub enum GffValue {
245 $(
246 #[doc = concat!("`", $wire, "`: ", $description, ".")]
247 $variant($value),
248 )*
249 }
250 };
251}
252
253gff_field_types!(emit_gff_value);
254
255impl GffValue {
256 #[doc(hidden)]
264 pub fn resref_lit(value: &str) -> Self {
265 GffValue::ResRef(ResRef::new(value).expect("valid resref literal"))
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
271pub struct GffLocalizedString {
272 pub string_ref: StrRef,
274 pub substrings: Vec<GffLocalizedSubstring>,
276}
277
278impl Default for GffLocalizedString {
279 fn default() -> Self {
284 Self::new(StrRef::invalid())
285 }
286}
287
288impl GffLocalizedString {
289 pub fn new(string_ref: impl Into<StrRef>) -> Self {
291 Self {
292 string_ref: string_ref.into(),
293 substrings: Vec::new(),
294 }
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct GffLocalizedSubstring {
301 pub string_id: u32,
303 pub text: String,
305}
306
307impl GffLocalizedSubstring {
308 pub fn language_id(&self) -> u32 {
310 self.string_id / 2
311 }
312
313 pub fn is_feminine(&self) -> bool {
315 self.string_id % 2 == 1
316 }
317}
318
319#[derive(Debug, Error)]
321pub enum GffBinaryError {
322 #[error(transparent)]
324 Io(#[from] std::io::Error),
325 #[error("invalid GFF header: {0}")]
327 InvalidHeader(String),
328 #[error("invalid GFF version: {0:?}")]
330 InvalidVersion([u8; 4]),
331 #[error("invalid GFF field type id: {0}")]
333 InvalidFieldType(u32),
334 #[error("invalid GFF data: {0}")]
336 InvalidData(String),
337 #[error("value overflow while writing `{0}`")]
339 ValueOverflow(&'static str),
340 #[error("label `{label}` encoded length {len} exceeds maximum {max}")]
342 LabelTooLong {
343 label: String,
345 len: usize,
347 max: usize,
349 },
350 #[error("GFF text encoding failed for {context}: {source}")]
352 TextEncoding {
353 context: String,
355 #[source]
357 source: EncodeTextError,
358 },
359 #[error("GFF text decoding failed for {context}: {source}")]
361 TextDecoding {
362 context: String,
364 #[source]
366 source: DecodeTextError,
367 },
368 #[error("unsupported language id {0} for localized string encoding")]
370 UnsupportedLanguageEncoding(u32),
371}
372
373impl From<binary::BinaryLayoutError> for GffBinaryError {
374 fn from(error: binary::BinaryLayoutError) -> Self {
375 Self::InvalidHeader(error.to_string())
376 }
377}
378
379macro_rules! emit_field_type {
380 ($($variant:ident = $id:literal, $wire:literal, $description:literal, $value:ty,
381 $(#[$dto_attr:meta])* $dto:ty;)*) => {
382 #[derive(Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
383 #[repr(u32)]
384 pub(super) enum FieldType {
385 $($variant = $id,)*
386 }
387 };
388}
389
390gff_field_types!(emit_field_type);
391
392pub(super) fn to_u32(value: usize, name: &'static str) -> Result<u32, GffBinaryError> {
393 u32::try_from(value).map_err(|_| GffBinaryError::ValueOverflow(name))
394}
395
396pub(super) fn to_usize(value: u32, name: &'static str) -> Result<usize, GffBinaryError> {
397 binary::checked_to_usize(value, name).map_err(|_| {
398 GffBinaryError::InvalidData(format!("{name} does not fit target platform usize"))
399 })
400}
401
402#[cfg(feature = "serde")]
407pub mod serde_json_fmt {
409 use super::*;
410 use serde::{Deserialize, Serialize};
411 use serde_json::{from_slice, from_str, to_string_pretty, to_vec};
412
413 pub fn write_gff_to_json(gff: &Gff) -> Result<String, GffBinaryError> {
421 let dto = GffDto::from(gff);
422 to_string_pretty(&dto).map_err(|e| GffBinaryError::InvalidData(e.to_string()))
423 }
424
425 pub fn write_gff_to_json_vec(gff: &Gff) -> Result<Vec<u8>, GffBinaryError> {
431 let dto = GffDto::from(gff);
432 to_vec(&dto).map_err(|e| GffBinaryError::InvalidData(e.to_string()))
433 }
434
435 pub fn read_gff_from_json(json: &str) -> Result<Gff, GffBinaryError> {
448 let dto: GffDto = from_str(json).map_err(|e| GffBinaryError::InvalidData(e.to_string()))?;
449 Gff::try_from(dto)
450 }
451
452 pub fn read_gff_from_json_bytes(bytes: &[u8]) -> Result<Gff, GffBinaryError> {
458 let dto: GffDto =
459 from_slice(bytes).map_err(|e| GffBinaryError::InvalidData(e.to_string()))?;
460 Gff::try_from(dto)
461 }
462
463 #[derive(Serialize, Deserialize)]
465 pub struct GffDto {
466 pub file_type: String,
468 pub root: GffStructDto,
470 }
471
472 #[derive(Serialize, Deserialize)]
474 pub struct GffStructDto {
475 pub struct_id: i32,
477 #[serde(default, skip_serializing_if = "Vec::is_empty")]
484 pub fields: Vec<GffFieldDto>,
485 }
486
487 #[derive(Serialize, Deserialize)]
489 pub struct GffFieldDto {
490 pub label: String,
492 #[serde(flatten)]
495 pub value: GffValueDto,
496 }
497
498 macro_rules! emit_value_dto {
499 ($($variant:ident = $id:literal, $wire:literal, $description:literal, $value:ty,
500 $(#[$dto_attr:meta])* $dto:ty;)*) => {
501 #[derive(Serialize, Deserialize)]
507 #[serde(tag = "type", content = "value")]
508 pub enum GffValueDto {
509 $(
510 #[doc = concat!("`", $wire, "`: ", $description, ".")]
511 $(#[$dto_attr])*
512 $variant($dto),
513 )*
514 }
515 };
516 }
517
518 gff_field_types!(emit_value_dto);
519
520 #[derive(Serialize, Deserialize)]
522 pub struct GffLocalizedStringDto {
523 pub str_ref: i32,
525 #[serde(default, skip_serializing_if = "Vec::is_empty")]
527 pub substrings: Vec<GffSubstringDto>,
528 }
529
530 #[derive(Serialize, Deserialize)]
538 pub struct GffSubstringDto {
539 pub string_id: u32,
541 pub text: String,
543 }
544
545 impl From<&Gff> for GffDto {
546 fn from(gff: &Gff) -> Self {
547 let file_type = rakata_core::text::decode_text(
548 &gff.file_type,
549 rakata_core::text::TextEncoding::Windows1252,
550 );
551 Self {
552 file_type,
553 root: GffStructDto::from(&gff.root),
554 }
555 }
556 }
557
558 impl TryFrom<GffDto> for Gff {
559 type Error = GffBinaryError;
560
561 fn try_from(dto: GffDto) -> Result<Self, Self::Error> {
562 let mut file_type = [0u8; 4];
563 let bytes = dto.file_type.as_bytes();
564 if bytes.len() > 4 {
565 return Err(GffBinaryError::InvalidHeader("file_type too long".into()));
566 }
567 file_type[..bytes.len()].copy_from_slice(bytes);
568
569 Ok(Self {
570 file_type,
571 root: GffStruct::try_from(dto.root)?,
572 })
573 }
574 }
575
576 impl From<&GffStruct> for GffStructDto {
577 fn from(s: &GffStruct) -> Self {
578 Self {
579 struct_id: s.struct_id,
580 fields: s
581 .fields
582 .iter()
583 .map(|field| GffFieldDto {
584 label: field.label.to_string(),
585 value: GffValueDto::from(&field.value),
586 })
587 .collect(),
588 }
589 }
590 }
591
592 impl TryFrom<GffStructDto> for GffStruct {
593 type Error = GffBinaryError;
594
595 fn try_from(dto: GffStructDto) -> Result<Self, Self::Error> {
596 let mut fields = Vec::with_capacity(dto.fields.len());
597 for field in dto.fields {
598 fields.push(GffField {
599 label: field.label.try_into().map_err(|_| {
600 GffBinaryError::InvalidData("Invalid GFF label in JSON".into())
601 })?,
602 value: GffValue::try_from(field.value)?,
603 });
604 }
605 Ok(Self {
606 struct_id: dto.struct_id,
607 fields,
608 })
609 }
610 }
611
612 impl From<&GffValue> for GffValueDto {
613 fn from(v: &GffValue) -> Self {
614 match v {
615 GffValue::UInt8(x) => Self::UInt8(*x),
616 GffValue::Int8(x) => Self::Int8(*x),
617 GffValue::UInt16(x) => Self::UInt16(*x),
618 GffValue::Int16(x) => Self::Int16(*x),
619 GffValue::UInt32(x) => Self::UInt32(*x),
620 GffValue::Int32(x) => Self::Int32(*x),
621 GffValue::UInt64(x) => Self::UInt64(*x),
622 GffValue::Int64(x) => Self::Int64(*x),
623 GffValue::Single(x) => Self::Single(*x),
624 GffValue::Double(x) => Self::Double(*x),
625 GffValue::String(x) => Self::String(x.clone()),
626 GffValue::ResRef(x) => Self::ResRef(x.to_string()),
627 GffValue::LocalizedString(x) => {
628 Self::LocalizedString(GffLocalizedStringDto::from(x))
629 }
630 GffValue::Binary(x) => Self::Binary(x.clone()),
631 GffValue::Struct(x) => Self::Struct(Box::new(GffStructDto::from(x.as_ref()))),
632 GffValue::List(x) => Self::List(x.iter().map(GffStructDto::from).collect()),
633 GffValue::Vector4(x) => Self::Vector4(*x),
634 GffValue::Vector3(x) => Self::Vector3(*x),
635 }
636 }
637 }
638
639 impl TryFrom<GffValueDto> for GffValue {
640 type Error = GffBinaryError;
641
642 fn try_from(dto: GffValueDto) -> Result<Self, Self::Error> {
643 Ok(match dto {
644 GffValueDto::UInt8(x) => Self::UInt8(x),
645 GffValueDto::Int8(x) => Self::Int8(x),
646 GffValueDto::UInt16(x) => Self::UInt16(x),
647 GffValueDto::Int16(x) => Self::Int16(x),
648 GffValueDto::UInt32(x) => Self::UInt32(x),
649 GffValueDto::Int32(x) => Self::Int32(x),
650 GffValueDto::UInt64(x) => Self::UInt64(x),
651 GffValueDto::Int64(x) => Self::Int64(x),
652 GffValueDto::Single(x) => Self::Single(x),
653 GffValueDto::Double(x) => Self::Double(x),
654 GffValueDto::String(x) => Self::String(x),
655 GffValueDto::ResRef(x) => Self::ResRef(
656 ResRef::new(&x)
657 .map_err(|e| GffBinaryError::InvalidData(format!("resref `{x}`: {e}")))?,
658 ),
659 GffValueDto::LocalizedString(x) => {
660 Self::LocalizedString(GffLocalizedString::from(x))
661 }
662 GffValueDto::Binary(x) => Self::Binary(x),
663 GffValueDto::Struct(x) => Self::Struct(Box::new(GffStruct::try_from(*x)?)),
664 GffValueDto::List(x) => {
665 let mut list = Vec::with_capacity(x.len());
666 for item in x {
667 list.push(GffStruct::try_from(item)?);
668 }
669 Self::List(list)
670 }
671 GffValueDto::Vector4(x) => Self::Vector4(x),
672 GffValueDto::Vector3(x) => Self::Vector3(x),
673 })
674 }
675 }
676
677 impl From<&GffLocalizedString> for GffLocalizedStringDto {
678 fn from(s: &GffLocalizedString) -> Self {
679 Self {
680 str_ref: s.string_ref.raw(),
681 substrings: s
682 .substrings
683 .iter()
684 .map(|sub| GffSubstringDto {
685 string_id: sub.string_id,
686 text: sub.text.clone(),
687 })
688 .collect(),
689 }
690 }
691 }
692
693 impl From<GffLocalizedStringDto> for GffLocalizedString {
694 fn from(dto: GffLocalizedStringDto) -> Self {
695 let substrings = dto
696 .substrings
697 .into_iter()
698 .map(|sub| GffLocalizedSubstring {
699 string_id: sub.string_id,
700 text: sub.text,
701 })
702 .collect();
703 Self {
704 string_ref: StrRef::from_raw(dto.str_ref),
705 substrings,
706 }
707 }
708 }
709
710 mod hex_bytes {
711 use serde::{Deserialize, Deserializer, Serializer};
712
713 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
714 where
715 S: Serializer,
716 {
717 let hex = hex_encode(bytes);
718 serializer.serialize_str(&hex)
719 }
720
721 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
722 where
723 D: Deserializer<'de>,
724 {
725 let s = String::deserialize(deserializer)?;
726 hex_decode(&s).map_err(serde::de::Error::custom)
727 }
728
729 fn hex_encode(bytes: &[u8]) -> String {
730 use std::fmt::Write;
731 let mut s = String::with_capacity(bytes.len() * 2);
732 for b in bytes {
733 write!(&mut s, "{b:02X}").expect("writing to a String cannot fail");
734 }
735 s
736 }
737
738 fn hex_decode(s: &str) -> Result<Vec<u8>, String> {
739 if !s.len().is_multiple_of(2) {
740 return Err("odd length hex string".into());
741 }
742 let mut bytes = Vec::with_capacity(s.len() / 2);
743 for i in (0..s.len()).step_by(2) {
744 let byte_str = &s[i..i + 2];
745 let byte = u8::from_str_radix(byte_str, 16)
746 .map_err(|e| format!("invalid hex byte {}: {}", byte_str, e))?;
747 bytes.push(byte);
748 }
749 Ok(bytes)
750 }
751 }
752}
753
754#[cfg(feature = "serde")]
755pub use serde_json_fmt::{
756 read_gff_from_json, read_gff_from_json_bytes, write_gff_to_json, write_gff_to_json_vec,
757};
758
759#[cfg(test)]
760mod tests {
761 use super::GffLocalizedString;
762 use rakata_core::StrRef;
763
764 #[test]
765 fn a_default_localized_string_is_empty_and_unreferenced() {
766 let value = GffLocalizedString::default();
767
768 assert_eq!(value.string_ref, StrRef::invalid());
769 assert!(value.substrings.is_empty());
770 assert_eq!(value, GffLocalizedString::new(StrRef::invalid()));
771 }
772}