1mod label;
43mod reader;
44mod writer;
45
46pub use label::*;
47pub use reader::{read_gff, read_gff_from_bytes};
48pub use writer::{write_gff, write_gff_to_vec};
49
50use num_enum::{IntoPrimitive, TryFromPrimitive};
51use thiserror::Error;
52
53use rakata_core::{DecodeTextError, EncodeTextError, ResRef, StrRef, TextEncoding};
54
55use crate::binary::{self, DecodeBinary, EncodeBinary};
56
57const GFF_HEADER_SIZE: usize = 56;
59const STRUCT_ENTRY_SIZE: usize = 12;
61const FIELD_ENTRY_SIZE: usize = 12;
63const LABEL_SIZE: usize = 16;
65const GFF_VERSION_V32: [u8; 4] = *b"V3.2";
67const DEFAULT_TEXT_ENCODING: TextEncoding = TextEncoding::Windows1252;
69
70#[derive(Debug, Clone, PartialEq)]
72pub struct Gff {
73 pub file_type: [u8; 4],
75 pub root: GffStruct,
77}
78
79impl Gff {
80 pub fn new(file_type: [u8; 4], root: GffStruct) -> Self {
82 Self { file_type, root }
83 }
84
85 pub fn generic(root: GffStruct) -> Self {
87 Self {
88 file_type: *b"GFF ",
89 root,
90 }
91 }
92}
93
94impl DecodeBinary for Gff {
95 type Error = GffBinaryError;
96
97 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
98 read_gff_from_bytes(bytes)
99 }
100}
101
102impl EncodeBinary for Gff {
103 type Error = GffBinaryError;
104
105 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
106 write_gff_to_vec(self)
107 }
108}
109
110#[derive(Debug, Clone, PartialEq)]
112pub struct GffStruct {
113 pub struct_id: i32,
115 pub fields: Vec<GffField>,
117}
118
119impl GffStruct {
120 pub fn new(struct_id: i32) -> Self {
122 Self {
123 struct_id,
124 fields: Vec::new(),
125 }
126 }
127
128 pub fn with_fields(struct_id: i32, fields: Vec<GffField>) -> Self {
130 Self { struct_id, fields }
131 }
132
133 pub fn push_field(&mut self, label: impl TryInto<GffLabel>, value: GffValue) {
135 self.fields.push(GffField {
136 label: label
137 .try_into()
138 .unwrap_or_else(|_| panic!("failed to push field with invalid label")),
139 value,
140 });
141 }
142
143 pub fn field(&self, label: &str) -> Option<&GffValue> {
145 self.fields
146 .iter()
147 .find(|field| field.label == label)
148 .map(|field| &field.value)
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
154pub struct GffField {
155 pub label: GffLabel,
157 pub value: GffValue,
159}
160
161#[derive(Debug, Clone, PartialEq)]
163pub enum GffValue {
164 UInt8(u8),
166 Int8(i8),
168 UInt16(u16),
170 Int16(i16),
172 UInt32(u32),
174 Int32(i32),
176 UInt64(u64),
178 Int64(i64),
180 Single(f32),
182 Double(f64),
184 String(String),
186 ResRef(ResRef),
188 LocalizedString(GffLocalizedString),
190 Binary(Vec<u8>),
192 Struct(Box<GffStruct>),
194 List(Vec<GffStruct>),
196 Vector4([f32; 4]),
198 Vector3([f32; 3]),
200 StrRef(StrRef),
202}
203
204impl GffValue {
205 #[doc(hidden)]
213 pub fn resref_lit(value: &str) -> Self {
214 GffValue::ResRef(ResRef::new(value).expect("valid resref literal"))
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct GffLocalizedString {
221 pub string_ref: StrRef,
223 pub substrings: Vec<GffLocalizedSubstring>,
225}
226
227impl Default for GffLocalizedString {
228 fn default() -> Self {
233 Self::new(StrRef::invalid())
234 }
235}
236
237impl GffLocalizedString {
238 pub fn new(string_ref: impl Into<StrRef>) -> Self {
240 Self {
241 string_ref: string_ref.into(),
242 substrings: Vec::new(),
243 }
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct GffLocalizedSubstring {
250 pub string_id: u32,
252 pub text: String,
254}
255
256impl GffLocalizedSubstring {
257 pub fn language_id(&self) -> u32 {
259 self.string_id / 2
260 }
261
262 pub fn is_feminine(&self) -> bool {
264 self.string_id % 2 == 1
265 }
266}
267
268#[derive(Debug, Error)]
270pub enum GffBinaryError {
271 #[error(transparent)]
273 Io(#[from] std::io::Error),
274 #[error("invalid GFF header: {0}")]
276 InvalidHeader(String),
277 #[error("invalid GFF version: {0:?}")]
279 InvalidVersion([u8; 4]),
280 #[error("invalid GFF field type id: {0}")]
282 InvalidFieldType(u32),
283 #[error("invalid GFF data: {0}")]
285 InvalidData(String),
286 #[error("value overflow while writing `{0}`")]
288 ValueOverflow(&'static str),
289 #[error("label `{label}` encoded length {len} exceeds maximum {max}")]
291 LabelTooLong {
292 label: String,
294 len: usize,
296 max: usize,
298 },
299 #[error("GFF text encoding failed for {context}: {source}")]
301 TextEncoding {
302 context: String,
304 #[source]
306 source: EncodeTextError,
307 },
308 #[error("GFF text decoding failed for {context}: {source}")]
310 TextDecoding {
311 context: String,
313 #[source]
315 source: DecodeTextError,
316 },
317 #[error("unsupported language id {0} for localized string encoding")]
319 UnsupportedLanguageEncoding(u32),
320}
321
322impl From<binary::BinaryLayoutError> for GffBinaryError {
323 fn from(error: binary::BinaryLayoutError) -> Self {
324 Self::InvalidHeader(error.to_string())
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
329#[repr(u32)]
330pub(super) enum FieldType {
331 UInt8 = 0,
332 Int8 = 1,
333 UInt16 = 2,
334 Int16 = 3,
335 UInt32 = 4,
336 Int32 = 5,
337 UInt64 = 6,
338 Int64 = 7,
339 Single = 8,
340 Double = 9,
341 String = 10,
342 ResRef = 11,
343 LocalizedString = 12,
344 Binary = 13,
345 Struct = 14,
346 List = 15,
347 Vector4 = 16,
348 Vector3 = 17,
349 StrRef = 18,
350}
351
352pub(super) fn to_u32(value: usize, name: &'static str) -> Result<u32, GffBinaryError> {
353 u32::try_from(value).map_err(|_| GffBinaryError::ValueOverflow(name))
354}
355
356pub(super) fn to_usize(value: u32, name: &'static str) -> Result<usize, GffBinaryError> {
357 binary::checked_to_usize(value, name).map_err(|_| {
358 GffBinaryError::InvalidData(format!("{name} does not fit target platform usize"))
359 })
360}
361
362#[cfg(feature = "serde")]
367pub mod serde_json_fmt {
369 use super::*;
370 use serde::{Deserialize, Serialize};
371 use serde_json::{from_slice, from_str, to_string_pretty, to_vec};
372 use std::collections::BTreeMap;
373
374 pub fn write_gff_to_json(gff: &Gff) -> Result<String, GffBinaryError> {
376 let dto = GffDto::from(gff);
377 to_string_pretty(&dto).map_err(|e| GffBinaryError::InvalidData(e.to_string()))
378 }
379
380 pub fn write_gff_to_json_vec(gff: &Gff) -> Result<Vec<u8>, GffBinaryError> {
382 let dto = GffDto::from(gff);
383 to_vec(&dto).map_err(|e| GffBinaryError::InvalidData(e.to_string()))
384 }
385
386 pub fn read_gff_from_json(json: &str) -> Result<Gff, GffBinaryError> {
388 let dto: GffDto = from_str(json).map_err(|e| GffBinaryError::InvalidData(e.to_string()))?;
389 Gff::try_from(dto)
390 }
391
392 pub fn read_gff_from_json_bytes(bytes: &[u8]) -> Result<Gff, GffBinaryError> {
394 let dto: GffDto =
395 from_slice(bytes).map_err(|e| GffBinaryError::InvalidData(e.to_string()))?;
396 Gff::try_from(dto)
397 }
398
399 #[derive(Serialize, Deserialize)]
401 pub struct GffDto {
402 pub file_type: String,
404 pub root: GffStructDto,
406 }
407
408 #[derive(Serialize, Deserialize)]
410 pub struct GffStructDto {
411 pub struct_id: i32,
413 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
415 pub fields: BTreeMap<String, GffValueDto>,
416 }
417
418 #[derive(Serialize, Deserialize)]
420 #[serde(tag = "type", content = "value")]
421 pub enum GffValueDto {
422 UInt8(u8),
424 Int8(i8),
426 UInt16(u16),
428 Int16(i16),
430 UInt32(u32),
432 Int32(i32),
434 UInt64(u64),
436 Int64(i64),
438 Single(f32),
440 Double(f64),
442 String(String),
444 ResRef(String),
446 LocalizedString(GffLocalizedStringDto),
448 #[serde(with = "hex_bytes")]
450 Binary(Vec<u8>),
451 Struct(Box<GffStructDto>),
453 List(Vec<GffStructDto>),
455 Vector4([f32; 4]),
457 Vector3([f32; 3]),
459 StrRef(i32),
461 }
462
463 #[derive(Serialize, Deserialize)]
465 pub struct GffLocalizedStringDto {
466 pub str_ref: i32,
468 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
470 pub substrings: BTreeMap<u32, String>,
471 }
472
473 impl From<&Gff> for GffDto {
474 fn from(gff: &Gff) -> Self {
475 let file_type = rakata_core::text::decode_text(
476 &gff.file_type,
477 rakata_core::text::TextEncoding::Windows1252,
478 );
479 Self {
480 file_type,
481 root: GffStructDto::from(&gff.root),
482 }
483 }
484 }
485
486 impl TryFrom<GffDto> for Gff {
487 type Error = GffBinaryError;
488
489 fn try_from(dto: GffDto) -> Result<Self, Self::Error> {
490 let mut file_type = [0u8; 4];
491 let bytes = dto.file_type.as_bytes();
492 if bytes.len() > 4 {
493 return Err(GffBinaryError::InvalidHeader("file_type too long".into()));
494 }
495 file_type[..bytes.len()].copy_from_slice(bytes);
496
497 Ok(Self {
498 file_type,
499 root: GffStruct::try_from(dto.root)?,
500 })
501 }
502 }
503
504 impl From<&GffStruct> for GffStructDto {
505 fn from(s: &GffStruct) -> Self {
506 let mut fields = BTreeMap::new();
507 for field in &s.fields {
508 fields.insert(field.label.to_string(), GffValueDto::from(&field.value));
509 }
510 Self {
511 struct_id: s.struct_id,
512 fields,
513 }
514 }
515 }
516
517 impl TryFrom<GffStructDto> for GffStruct {
518 type Error = GffBinaryError;
519
520 fn try_from(dto: GffStructDto) -> Result<Self, Self::Error> {
521 let mut fields = Vec::with_capacity(dto.fields.len());
522 for (label, value_dto) in dto.fields {
523 fields.push(GffField {
524 label: label.try_into().map_err(|_| {
525 GffBinaryError::InvalidData("Invalid GFF label in JSON".into())
526 })?,
527 value: GffValue::try_from(value_dto)?,
528 });
529 }
530 Ok(Self {
531 struct_id: dto.struct_id,
532 fields,
533 })
534 }
535 }
536
537 impl From<&GffValue> for GffValueDto {
538 fn from(v: &GffValue) -> Self {
539 match v {
540 GffValue::UInt8(x) => Self::UInt8(*x),
541 GffValue::Int8(x) => Self::Int8(*x),
542 GffValue::UInt16(x) => Self::UInt16(*x),
543 GffValue::Int16(x) => Self::Int16(*x),
544 GffValue::UInt32(x) => Self::UInt32(*x),
545 GffValue::Int32(x) => Self::Int32(*x),
546 GffValue::UInt64(x) => Self::UInt64(*x),
547 GffValue::Int64(x) => Self::Int64(*x),
548 GffValue::Single(x) => Self::Single(*x),
549 GffValue::Double(x) => Self::Double(*x),
550 GffValue::String(x) => Self::String(x.clone()),
551 GffValue::ResRef(x) => Self::ResRef(x.to_string()),
552 GffValue::LocalizedString(x) => {
553 Self::LocalizedString(GffLocalizedStringDto::from(x))
554 }
555 GffValue::Binary(x) => Self::Binary(x.clone()),
556 GffValue::Struct(x) => Self::Struct(Box::new(GffStructDto::from(x.as_ref()))),
557 GffValue::List(x) => Self::List(x.iter().map(GffStructDto::from).collect()),
558 GffValue::Vector4(x) => Self::Vector4(*x),
559 GffValue::Vector3(x) => Self::Vector3(*x),
560 GffValue::StrRef(x) => Self::StrRef(x.raw()),
561 }
562 }
563 }
564
565 impl TryFrom<GffValueDto> for GffValue {
566 type Error = GffBinaryError;
567
568 fn try_from(dto: GffValueDto) -> Result<Self, Self::Error> {
569 Ok(match dto {
570 GffValueDto::UInt8(x) => Self::UInt8(x),
571 GffValueDto::Int8(x) => Self::Int8(x),
572 GffValueDto::UInt16(x) => Self::UInt16(x),
573 GffValueDto::Int16(x) => Self::Int16(x),
574 GffValueDto::UInt32(x) => Self::UInt32(x),
575 GffValueDto::Int32(x) => Self::Int32(x),
576 GffValueDto::UInt64(x) => Self::UInt64(x),
577 GffValueDto::Int64(x) => Self::Int64(x),
578 GffValueDto::Single(x) => Self::Single(x),
579 GffValueDto::Double(x) => Self::Double(x),
580 GffValueDto::String(x) => Self::String(x),
581 GffValueDto::ResRef(x) => Self::ResRef(
582 ResRef::new(&x)
583 .map_err(|e| GffBinaryError::InvalidData(format!("resref `{x}`: {e}")))?,
584 ),
585 GffValueDto::LocalizedString(x) => {
586 Self::LocalizedString(GffLocalizedString::from(x))
587 }
588 GffValueDto::Binary(x) => Self::Binary(x),
589 GffValueDto::Struct(x) => Self::Struct(Box::new(GffStruct::try_from(*x)?)),
590 GffValueDto::List(x) => {
591 let mut list = Vec::with_capacity(x.len());
592 for item in x {
593 list.push(GffStruct::try_from(item)?);
594 }
595 Self::List(list)
596 }
597 GffValueDto::Vector4(x) => Self::Vector4(x),
598 GffValueDto::Vector3(x) => Self::Vector3(x),
599 GffValueDto::StrRef(x) => Self::StrRef(StrRef::from_raw(x)),
600 })
601 }
602 }
603
604 impl From<&GffLocalizedString> for GffLocalizedStringDto {
605 fn from(s: &GffLocalizedString) -> Self {
606 let mut substrings = BTreeMap::new();
607 for sub in &s.substrings {
608 substrings.insert(sub.string_id, sub.text.clone());
609 }
610 Self {
611 str_ref: s.string_ref.raw(),
612 substrings,
613 }
614 }
615 }
616
617 impl From<GffLocalizedStringDto> for GffLocalizedString {
618 fn from(dto: GffLocalizedStringDto) -> Self {
619 let mut substrings = Vec::with_capacity(dto.substrings.len());
620 for (string_id, text) in dto.substrings {
621 substrings.push(GffLocalizedSubstring { string_id, text });
622 }
623 Self {
624 string_ref: StrRef::from_raw(dto.str_ref),
625 substrings,
626 }
627 }
628 }
629
630 mod hex_bytes {
631 use serde::{Deserialize, Deserializer, Serializer};
632
633 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
634 where
635 S: Serializer,
636 {
637 let hex = hex_encode(bytes);
638 serializer.serialize_str(&hex)
639 }
640
641 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
642 where
643 D: Deserializer<'de>,
644 {
645 let s = String::deserialize(deserializer)?;
646 hex_decode(&s).map_err(serde::de::Error::custom)
647 }
648
649 fn hex_encode(bytes: &[u8]) -> String {
650 use std::fmt::Write;
651 let mut s = String::with_capacity(bytes.len() * 2);
652 for b in bytes {
653 write!(&mut s, "{b:02X}").expect("writing to a String cannot fail");
654 }
655 s
656 }
657
658 fn hex_decode(s: &str) -> Result<Vec<u8>, String> {
659 if !s.len().is_multiple_of(2) {
660 return Err("odd length hex string".into());
661 }
662 let mut bytes = Vec::with_capacity(s.len() / 2);
663 for i in (0..s.len()).step_by(2) {
664 let byte_str = &s[i..i + 2];
665 let byte = u8::from_str_radix(byte_str, 16)
666 .map_err(|e| format!("invalid hex byte {}: {}", byte_str, e))?;
667 bytes.push(byte);
668 }
669 Ok(bytes)
670 }
671 }
672}
673
674#[cfg(feature = "serde")]
675pub use serde_json_fmt::{
676 read_gff_from_json, read_gff_from_json_bytes, write_gff_to_json, write_gff_to_json_vec,
677};
678
679#[cfg(test)]
680mod tests {
681 use super::GffLocalizedString;
682 use rakata_core::StrRef;
683
684 #[test]
685 fn a_default_localized_string_is_empty_and_unreferenced() {
686 let value = GffLocalizedString::default();
687
688 assert_eq!(value.string_ref, StrRef::invalid());
689 assert!(value.substrings.is_empty());
690 assert_eq!(value, GffLocalizedString::new(StrRef::invalid()));
691 }
692}