use std::{fmt, io};
use nwnrs_io::prelude::*;
use nwnrs_localization::prelude::*;
type GffByte = u8;
type GffChar = i8;
type GffWord = u16;
type GffShort = i16;
type GffDword = u32;
type GffInt = i32;
type GffFloat = f32;
type GffDword64 = u64;
type GffInt64 = i64;
type GffDouble = f64;
type GffCExoString = String;
type GffResRef = String;
type GffVoid = Vec<u8>;
type GffList = Vec<GffStruct>;
pub(crate) const HEADER_SIZE: usize = 56;
#[derive(Debug, Clone, PartialEq)]
pub struct GffCExoLocString {
pub str_ref: StrRef,
pub entries: Vec<(i32, String)>,
}
impl Default for GffCExoLocString {
fn default() -> Self {
Self {
str_ref: BAD_STRREF,
entries: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum GffFieldKind {
Byte = 0,
Char = 1,
Word = 2,
Short = 3,
Dword = 4,
Int = 5,
Dword64 = 6,
Int64 = 7,
Float = 8,
Double = 9,
CExoString = 10,
ResRef = 11,
CExoLocString = 12,
Void = 13,
Struct = 14,
List = 15,
}
impl GffFieldKind {
#[must_use]
pub fn is_complex(self) -> bool {
matches!(
self,
Self::Dword64
| Self::Int64
| Self::Double
| Self::CExoString
| Self::ResRef
| Self::CExoLocString
| Self::Void
| Self::Struct
| Self::List
)
}
pub(crate) fn from_u32(value: u32) -> Option<Self> {
Some(match value {
0 => Self::Byte,
1 => Self::Char,
2 => Self::Word,
3 => Self::Short,
4 => Self::Dword,
5 => Self::Int,
6 => Self::Dword64,
7 => Self::Int64,
8 => Self::Float,
9 => Self::Double,
10 => Self::CExoString,
11 => Self::ResRef,
12 => Self::CExoLocString,
13 => Self::Void,
14 => Self::Struct,
15 => Self::List,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GffValue {
Byte(GffByte),
Char(GffChar),
Word(GffWord),
Short(GffShort),
Dword(GffDword),
Int(GffInt),
Float(GffFloat),
Dword64(GffDword64),
Int64(GffInt64),
Double(GffDouble),
CExoString(GffCExoString),
ResRef(GffResRef),
CExoLocString(GffCExoLocString),
Void(GffVoid),
Struct(GffStruct),
List(GffList),
}
impl GffValue {
#[must_use]
pub fn kind(&self) -> GffFieldKind {
match self {
Self::Byte(_) => GffFieldKind::Byte,
Self::Char(_) => GffFieldKind::Char,
Self::Word(_) => GffFieldKind::Word,
Self::Short(_) => GffFieldKind::Short,
Self::Dword(_) => GffFieldKind::Dword,
Self::Int(_) => GffFieldKind::Int,
Self::Float(_) => GffFieldKind::Float,
Self::Dword64(_) => GffFieldKind::Dword64,
Self::Int64(_) => GffFieldKind::Int64,
Self::Double(_) => GffFieldKind::Double,
Self::CExoString(_) => GffFieldKind::CExoString,
Self::ResRef(_) => GffFieldKind::ResRef,
Self::CExoLocString(_) => GffFieldKind::CExoLocString,
Self::Void(_) => GffFieldKind::Void,
Self::Struct(_) => GffFieldKind::Struct,
Self::List(_) => GffFieldKind::List,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GffField {
pub(crate) value: GffValue,
pub(crate) provenance: Option<GffFieldProvenance>,
}
impl GffField {
#[must_use]
pub fn new(value: GffValue) -> Self {
Self {
value,
provenance: None,
}
}
pub(crate) fn with_provenance(value: GffValue, provenance: GffFieldProvenance) -> Self {
Self {
value,
provenance: Some(provenance),
}
}
#[must_use]
pub fn kind(&self) -> GffFieldKind {
self.value.kind()
}
#[must_use]
pub fn value(&self) -> &GffValue {
&self.value
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct GffFieldProvenance {
pub(crate) label_bytes: [u8; 16],
pub(crate) original_value: GffValue,
pub(crate) raw_data_or_offset: i32,
pub(crate) raw_field_data: Option<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GffStruct {
pub id: i32,
pub(crate) fields: Vec<(String, GffField)>,
pub(crate) provenance: Option<GffStructProvenance>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct GffStructProvenance {
pub(crate) field_labels: Vec<String>,
}
impl GffStruct {
#[must_use]
pub fn new(id: i32) -> Self {
Self {
id,
fields: Vec::new(),
provenance: None,
}
}
#[must_use]
pub fn fields(&self) -> &[(String, GffField)] {
self.fields.as_slice()
}
pub fn put_field(&mut self, label: impl Into<String>, field: GffField) -> GffResult<()> {
let label = label.into();
ensure_label(&label)?;
if let Some((_, existing)) = self.fields.iter_mut().find(|(name, _)| *name == label) {
*existing = field;
} else {
self.fields.push((label, field));
}
Ok(())
}
pub fn put_value(&mut self, label: impl Into<String>, value: GffValue) -> GffResult<()> {
self.put_field(label, GffField::new(value))
}
#[must_use]
pub fn get_field(&self, label: &str) -> Option<&GffField> {
self.fields
.iter()
.find_map(|(name, field)| (name == label).then_some(field))
}
pub fn remove(&mut self, label: &str) -> Option<GffField> {
let idx = self.fields.iter().position(|(name, _)| name == label)?;
Some(self.fields.remove(idx).1)
}
}
#[derive(Debug, Clone)]
pub struct GffRoot {
pub file_type: String,
pub file_version: String,
pub root: GffStruct,
pub(crate) source_bytes: Option<Vec<u8>>,
pub(crate) source_snapshot: Option<GffRootSnapshot>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct GffRootSnapshot {
pub(crate) file_type: String,
pub(crate) file_version: String,
pub(crate) root: GffStruct,
}
impl GffRoot {
pub fn new(file_type: impl Into<String>) -> Self {
Self {
file_type: file_type.into(),
file_version: "V3.2".to_string(),
root: GffStruct::new(-1),
source_bytes: None,
source_snapshot: None,
}
}
#[must_use]
pub fn fields(&self) -> &[(String, GffField)] {
self.root.fields()
}
pub fn put_value(&mut self, label: impl Into<String>, value: GffValue) -> GffResult<()> {
self.root.put_value(label, value)
}
pub(crate) fn snapshot(&self) -> GffRootSnapshot {
GffRootSnapshot {
file_type: self.file_type.clone(),
file_version: self.file_version.clone(),
root: self.root.clone(),
}
}
}
#[derive(Debug)]
pub enum GffError {
Io(io::Error),
Expectation(ExpectationError),
Message(String),
}
impl GffError {
pub(crate) fn msg(message: impl Into<String>) -> Self {
Self::Message(message.into())
}
}
impl fmt::Display for GffError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => error.fmt(f),
Self::Expectation(error) => error.fmt(f),
Self::Message(message) => f.write_str(message),
}
}
}
impl std::error::Error for GffError {}
impl From<io::Error> for GffError {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl From<ExpectationError> for GffError {
fn from(value: ExpectationError) -> Self {
Self::Expectation(value)
}
}
pub type GffResult<T> = Result<T, GffError>;
pub(crate) fn ensure_label(label: &str) -> GffResult<()> {
nwnrs_io::expect(
label.len() <= 16,
format!("invalid GFF label length for {label:?}"),
)?;
Ok(())
}
impl PartialEq for GffRoot {
fn eq(&self, other: &Self) -> bool {
self.file_type == other.file_type
&& self.file_version == other.file_version
&& self.root == other.root
}
}