#![allow(dead_code)]
pub mod bin_gff;
use base64ct::{Base64, Encoding};
use encoding_rs::WINDOWS_1252;
use std::collections::HashMap;
use std::mem;
use std::rc::Rc;
use crate::parsing::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Struct {
pub id: u32,
pub fields: Vec<Field>,
}
impl Struct {
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
pub fn get(&self, index: usize) -> Option<&Field> {
self.fields.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Field> {
self.fields.get_mut(index)
}
pub fn find_field(&self, label: &str) -> Option<&Field> {
self.fields.iter().find(|f| *f.label == label)
}
pub fn find(&self, label: &str) -> Option<&FieldValue> {
Some(&self.find_field(label)?.value)
}
pub fn find_field_mut(&mut self, label: &str) -> Option<&mut Field> {
self.fields.iter_mut().find(|f| *f.label == label)
}
pub fn find_mut(&mut self, label: &str) -> Option<&mut FieldValue> {
Some(&mut self.find_field_mut(label)?.value)
}
pub fn find_all(&self, label: &str) -> Vec<&FieldValue> {
self.fields
.iter()
.filter(|f| *f.label == label)
.map(|f| &f.value)
.collect()
}
pub fn find_all_mut(&mut self, label: &str) -> Vec<&mut FieldValue> {
self.fields
.iter_mut()
.filter(|f| *f.label == label)
.map(|f| &mut f.value)
.collect()
}
pub fn to_string_pretty(&self, child_indent: &str) -> String {
let mut ret = format!(
"(struct id={} len={})",
if self.id != u32::MAX {
self.id as i64
} else {
-1
},
self.len()
);
if !self.is_empty() {
ret += "\n";
ret += &self
.fields
.iter()
.map(|f| child_indent.to_string() + "├╴ " + &f.to_string_pretty(child_indent))
.collect::<Vec<_>>()
.join("\n");
}
ret
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Field {
pub label: Rc<FixedSizeString<16>>,
#[serde(flatten)]
pub value: FieldValue,
}
impl Field {
pub fn to_string_pretty(&self, child_indent: &str) -> String {
format!(
"{} = {}",
self.label,
self.value.to_string_pretty(child_indent)
)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", content = "value", rename_all = "lowercase")]
pub enum FieldValue {
Byte(u8),
Char(i8),
Word(u16),
Short(i16),
DWord(u32),
Int(i32),
DWord64(u64),
Int64(i64),
Float(f32),
Double(f64),
#[serde(rename = "cexostr")]
String(String),
ResRef(String),
#[serde(rename = "cexolocstr")]
LocString(LocString),
#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
Void(Vec<u8>),
Struct(Struct),
List(List),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct List(Vec<Struct>);
impl std::ops::Deref for List {
type Target = Vec<Struct>;
fn deref(&self) -> &Vec<Struct> {
&self.0
}
}
impl std::ops::DerefMut for List {
fn deref_mut(&mut self) -> &mut Vec<Struct> {
&mut self.0
}
}
impl List {
pub fn to_string_pretty(&self, child_indent: &str) -> String {
let content_indent = child_indent.to_string() + "| ";
let mut ret = format!("(list len={})", self.len());
if self.len() > 0 {
ret += "\n";
ret += &self
.iter()
.map(|f| child_indent.to_string() + "├╴ " + &f.to_string_pretty(&content_indent))
.collect::<Vec<_>>()
.join("\n");
}
ret
}
}
impl FieldValue {
pub fn get_type_str(&self) -> &'static str {
match self {
FieldValue::Byte(_) => "byte",
FieldValue::Char(_) => "char",
FieldValue::Word(_) => "word",
FieldValue::Short(_) => "short",
FieldValue::DWord(_) => "dword",
FieldValue::Int(_) => "int",
FieldValue::DWord64(_) => "dword64",
FieldValue::Int64(_) => "int64",
FieldValue::Float(_) => "float",
FieldValue::Double(_) => "double",
FieldValue::String(_) => "cexostr",
FieldValue::ResRef(_) => "resref",
FieldValue::LocString(_) => "cexolocstr",
FieldValue::Void(_) => "void",
FieldValue::Struct(_) => "struct",
FieldValue::List(_) => "list",
}
}
pub fn to_string_pretty(&self, child_indent: &str) -> String {
match self {
FieldValue::Byte(v) => format!("{} (byte)", v),
FieldValue::Char(v) => format!("{} (char)", v),
FieldValue::Word(v) => format!("{} (word)", v),
FieldValue::Short(v) => format!("{} (short)", v),
FieldValue::DWord(v) => format!("{} (dword)", v),
FieldValue::Int(v) => format!("{} (int)", v),
FieldValue::DWord64(v) => format!("{} (dword64)", v),
FieldValue::Int64(v) => format!("{} (int64)", v),
FieldValue::Float(v) => format!("{} (float)", v),
FieldValue::Double(v) => format!("{} (double)", v),
FieldValue::String(v) => format!("{:?} (cexostr)", v),
FieldValue::ResRef(v) => format!("{:?} (resref)", v),
FieldValue::LocString(v) => format!("{:?} (cexolocstr)", v),
FieldValue::Void(v) => format!("{:?} (void)", Base64::encode_string(&v)),
FieldValue::Struct(v) => v.to_string_pretty(&(child_indent.to_string() + "| ")),
FieldValue::List(v) => v.to_string_pretty(&(child_indent.to_string() + "| ")),
}
}
pub fn unwrap_byte(&self) -> u8 {
match self {
FieldValue::Byte(v) => *v,
_ => panic!("not a GFF Byte"),
}
}
pub fn unwrap_char(&self) -> i8 {
match self {
FieldValue::Char(v) => *v,
_ => panic!("not a GFF Char"),
}
}
pub fn unwrap_word(&self) -> u16 {
match self {
FieldValue::Word(v) => *v,
_ => panic!("not a GFF Word"),
}
}
pub fn unwrap_short(&self) -> i16 {
match self {
FieldValue::Short(v) => *v,
_ => panic!("not a GFF Short"),
}
}
pub fn unwrap_dword(&self) -> u32 {
match self {
FieldValue::DWord(v) => *v,
_ => panic!("not a GFF DWord"),
}
}
pub fn unwrap_int(&self) -> i32 {
match self {
FieldValue::Int(v) => *v,
_ => panic!("not a GFF Int"),
}
}
pub fn unwrap_dword64(&self) -> u64 {
match self {
FieldValue::DWord64(v) => *v,
_ => panic!("not a GFF DWord64"),
}
}
pub fn unwrap_int64(&self) -> i64 {
match self {
FieldValue::Int64(v) => *v,
_ => panic!("not a GFF Int64"),
}
}
pub fn unwrap_float(&self) -> f32 {
match self {
FieldValue::Float(v) => *v,
_ => panic!("not a GFF Int64"),
}
}
pub fn unwrap_double(&self) -> f64 {
match self {
FieldValue::Double(v) => *v,
_ => panic!("not a GFF Int64"),
}
}
pub fn unwrap_string(&self) -> &String {
match self {
FieldValue::String(v) => v,
_ => panic!("not a GFF String"),
}
}
pub fn unwrap_resref(&self) -> &String {
match self {
FieldValue::ResRef(v) => v,
_ => panic!("not a GFF ResRef"),
}
}
pub fn unwrap_locstring(&self) -> &LocString {
match self {
FieldValue::LocString(v) => v,
_ => panic!("not a GFF LocString"),
}
}
pub fn unwrap_void(&self) -> &Vec<u8> {
match self {
FieldValue::Void(v) => v,
_ => panic!("not a GFF Void"),
}
}
pub fn unwrap_struct(&self) -> &Struct {
match self {
FieldValue::Struct(v) => v,
_ => panic!("not a GFF Struct"),
}
}
pub fn unwrap_list(&self) -> &Vec<Struct> {
match self {
FieldValue::List(v) => v,
_ => panic!("not a GFF List"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LocString {
pub strref: u32,
pub strings: Vec<(i32, String)>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Gff {
pub file_type: FixedSizeString<4>,
pub file_version: FixedSizeString<4>,
pub root: Struct,
}
impl Gff {
pub fn from_bytes(input: &[u8]) -> NWNParseResult<Self> {
let (input, bingff) = bin_gff::Gff::from_bytes(input)?;
Ok((
input,
Self::from_bin_gff(&bingff).expect("should have been checked while parsing bingff"),
))
}
pub fn from_bin_gff(bingff: &bin_gff::Gff) -> Result<Self, Box<dyn std::error::Error>> {
fn build_struct(
gff_struct: &bin_gff::Struct,
bingff: &bin_gff::Gff,
labels: &Vec<Rc<FixedSizeString<16>>>,
) -> Struct {
let fields = gff_struct
.get_field_indices(&bingff.field_indices)
.unwrap()
.iter()
.map(|field_index| {
let gff_field = bingff.fields.get(*field_index as usize).unwrap();
let label = labels.get(gff_field.label_index as usize).unwrap().clone();
let value = match gff_field.value {
bin_gff::FieldValue::Invalid => panic!(), bin_gff::FieldValue::Byte(value) => FieldValue::Byte(value),
bin_gff::FieldValue::Char(value) => FieldValue::Char(value),
bin_gff::FieldValue::Word(value) => FieldValue::Word(value),
bin_gff::FieldValue::Short(value) => FieldValue::Short(value),
bin_gff::FieldValue::DWord(value) => FieldValue::DWord(value),
bin_gff::FieldValue::Int(value) => FieldValue::Int(value),
bin_gff::FieldValue::DWord64 { data_offset: _ } => FieldValue::DWord64(
gff_field.value.get_dword64(&bingff.field_data).unwrap(),
),
bin_gff::FieldValue::Int64 { data_offset: _ } => FieldValue::Int64(
gff_field.value.get_int64(&bingff.field_data).unwrap(),
),
bin_gff::FieldValue::Float(value) => FieldValue::Float(value),
bin_gff::FieldValue::Double { data_offset: _ } => FieldValue::Double(
gff_field.value.get_double(&bingff.field_data).unwrap(),
),
bin_gff::FieldValue::String { data_offset: _ } => FieldValue::String(
gff_field
.value
.get_string(&bingff.field_data)
.unwrap()
.to_string(),
),
bin_gff::FieldValue::ResRef { data_offset: _ } => FieldValue::ResRef(
gff_field
.value
.get_resref(&bingff.field_data)
.unwrap()
.to_string(),
),
bin_gff::FieldValue::LocString { data_offset: _ } => {
let gff_locstr =
gff_field.value.get_locstring(&bingff.field_data).unwrap();
FieldValue::LocString(LocString {
strref: gff_locstr.0,
strings: gff_locstr
.1
.into_iter()
.map(|ls| (ls.0, ls.1.to_string()))
.collect(),
})
}
bin_gff::FieldValue::Void { data_offset: _ } => FieldValue::Void(
gff_field
.value
.get_void(&bingff.field_data)
.unwrap()
.to_vec(),
),
bin_gff::FieldValue::Struct { struct_index } => {
FieldValue::Struct(build_struct(
bingff.structs.get(struct_index as usize).unwrap(),
bingff,
labels,
))
}
bin_gff::FieldValue::List { list_offset: _ } => {
let list = gff_field
.value
.get_list_struct_indices(&bingff.list_indices)
.expect("Bad list indices / data");
FieldValue::List(List(
list.iter()
.map(|struct_index| {
build_struct(
bingff.structs.get(*struct_index as usize).unwrap(),
bingff,
labels,
)
})
.collect(),
))
}
};
Field { label, value }
})
.collect::<Vec<_>>();
Struct {
id: gff_struct.id,
fields,
}
}
let labels = bingff
.labels
.clone()
.into_iter()
.map(Rc::new)
.collect::<Vec<_>>();
let root = build_struct(
bingff.structs.first().expect("Missing root struct"),
bingff,
&labels,
);
Ok(Self {
file_type: bingff.header.file_type,
file_version: bingff.header.file_version,
root,
})
}
pub fn to_string_pretty(&self) -> String {
let mut ret = String::new();
ret += &format!(
"========== GFF {:?} {:?} ==========",
self.file_type, self.file_version
);
ret += &self.root.to_string_pretty("");
ret
}
pub fn to_bin_gff(&self) -> bin_gff::Gff {
let mut bingff = bin_gff::Gff {
header: bin_gff::Header::new(self.file_type, self.file_version),
structs: vec![],
fields: vec![],
labels: vec![],
field_data: vec![],
field_indices: vec![],
list_indices: vec![],
};
let mut labels_map = HashMap::<String, usize>::new();
fn register_field(
field: &Field,
bingff: &mut bin_gff::Gff,
labels_map: &mut HashMap<String, usize>,
) -> u32 {
let label_index = if let Some(index) = labels_map.get(field.label.as_str()) {
*index as u32
} else {
let index = labels_map.len();
bingff.labels.push(*field.label);
labels_map.insert(field.label.to_string(), index);
index as u32
};
let field_index = bingff.fields.len();
bingff.fields.push(bin_gff::Field {
label_index,
value: bin_gff::FieldValue::Invalid,
});
let value = match &field.value {
FieldValue::Byte(v) => bin_gff::FieldValue::Byte(*v),
FieldValue::Char(v) => bin_gff::FieldValue::Char(*v),
FieldValue::Word(v) => bin_gff::FieldValue::Word(*v),
FieldValue::Short(v) => bin_gff::FieldValue::Short(*v),
FieldValue::DWord(v) => bin_gff::FieldValue::DWord(*v),
FieldValue::Int(v) => bin_gff::FieldValue::Int(*v),
FieldValue::DWord64(v) => {
let data_offset = bingff.field_data.len() as u32;
bingff.field_data.extend(v.to_le_bytes());
bin_gff::FieldValue::DWord64 { data_offset }
}
FieldValue::Int64(v) => {
let data_offset = bingff.field_data.len() as u32;
bingff.field_data.extend(v.to_le_bytes());
bin_gff::FieldValue::Int64 { data_offset }
}
FieldValue::Float(v) => bin_gff::FieldValue::Float(*v),
FieldValue::Double(v) => {
let data_offset = bingff.field_data.len() as u32;
bingff.field_data.extend(v.to_le_bytes());
bin_gff::FieldValue::Double { data_offset }
}
FieldValue::String(v) => {
let data_offset = bingff.field_data.len() as u32;
bingff.field_data.extend((v.len() as u32).to_le_bytes());
bingff.field_data.extend(v.as_bytes());
bin_gff::FieldValue::String { data_offset }
}
FieldValue::ResRef(v) => {
let data_offset = bingff.field_data.len() as u32;
let (str_data, _, _) = WINDOWS_1252.encode(v);
bingff
.field_data
.extend((str_data.len() as u8).to_le_bytes());
bingff.field_data.extend(str_data.as_ref());
bin_gff::FieldValue::ResRef { data_offset }
}
FieldValue::LocString(v) => {
let data_offset = bingff.field_data.len() as u32;
let total_len = mem::size_of::<u32>() * 2
+ v.strings
.iter()
.map(|(_lang, text)| mem::size_of::<u32>() * 2 + text.as_bytes().len())
.sum::<usize>();
bingff.field_data.reserve(mem::size_of::<u32>() + total_len);
bingff.field_data.extend((total_len as u32).to_le_bytes());
bingff.field_data.extend(v.strref.to_le_bytes());
bingff
.field_data
.extend((v.strings.len() as u32).to_le_bytes());
for (id, text) in &v.strings {
bingff.field_data.extend(id.to_le_bytes());
bingff.field_data.extend((text.len() as u32).to_le_bytes());
bingff.field_data.extend(text.as_bytes());
}
bin_gff::FieldValue::LocString { data_offset }
}
FieldValue::Void(v) => {
let data_offset = bingff.field_data.len() as u32;
bingff.field_data.extend((v.len() as u32).to_le_bytes());
bingff.field_data.extend(v);
bin_gff::FieldValue::Void { data_offset }
}
FieldValue::Struct(v) => {
let struct_index = register_struct(v, bingff, labels_map);
bin_gff::FieldValue::Struct { struct_index }
}
FieldValue::List(v) => {
let list_offset = (bingff.list_indices.len() * 4) as u32;
bingff
.list_indices
.resize(bingff.list_indices.len() + 1 + v.len(), 0);
bingff.list_indices[(list_offset / 4) as usize] = v.len() as u32;
for (i, gffstruct) in v.iter().enumerate() {
bingff.list_indices[(list_offset / 4) as usize + 1 + i] =
register_struct(gffstruct, bingff, labels_map);
}
bin_gff::FieldValue::List { list_offset }
}
};
bingff.fields[field_index].value = value;
field_index as u32
}
fn register_struct(
gffstruct: &Struct,
bingff: &mut bin_gff::Gff,
labels_map: &mut HashMap<String, usize>,
) -> u32 {
let struct_index = bingff.structs.len();
bingff.structs.push(bin_gff::Struct {
id: gffstruct.id,
data_or_data_offset: 0,
field_count: gffstruct.len() as u32,
});
bingff.structs[struct_index].data_or_data_offset = match gffstruct.len() {
0 => 0,
1 => register_field(&gffstruct.fields[0], bingff, labels_map),
_cnt => {
let dodo = (bingff.field_indices.len() * mem::size_of::<u32>()) as u32;
bingff
.field_indices
.resize(bingff.field_indices.len() + gffstruct.fields.len(), 0);
for (i, field) in gffstruct.fields.iter().enumerate() {
let field_index = register_field(field, bingff, labels_map);
let field_indices_index = dodo as usize / mem::size_of::<u32>() + i;
bingff.field_indices[field_indices_index] = field_index;
}
dodo
}
};
struct_index as u32
}
register_struct(&self.root, &mut bingff, &mut labels_map);
let mut header = bin_gff::Header::new(self.file_type, self.file_version);
header.update(&bingff);
bingff.header = header;
bingff
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_bin_gff().to_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gff_read_doge() {
let doge_bytes = include_bytes!("../unittest/nwn2_doge.utc");
let (_, bingff) = bin_gff::Gff::from_bytes(doge_bytes).unwrap();
let (_, bingff_rebuilt) = bin_gff::Gff::from_bytes(&bingff.to_bytes()).unwrap();
assert_eq!(bingff.to_bytes(), doge_bytes);
assert_eq!(bingff_rebuilt.to_bytes(), doge_bytes);
let gff = Gff::from_bin_gff(&bingff).unwrap();
assert_eq!(gff.root.find("Subrace").unwrap().unwrap_byte(), 24);
assert_eq!(gff.root.find("Appearance_Type").unwrap().unwrap_word(), 181);
assert_eq!(gff.root.find("HitPoints").unwrap().unwrap_short(), 9);
assert_eq!(gff.root.find("DecayTime").unwrap().unwrap_dword(), 5000);
assert_eq!(gff.root.find("WalkRate").unwrap().unwrap_int(), 5);
assert!((gff.root.find("ChallengeRating").unwrap().unwrap_float() - 100f32).abs() < 0.001);
assert_eq!(gff.root.find("Tag").unwrap().unwrap_string(), "secret_doge");
assert_eq!(
gff.root.find("ScriptDamaged").unwrap().unwrap_resref(),
"nw_c2_default6"
);
let locstr = gff.root.find("Description").unwrap().unwrap_locstring();
assert_eq!(locstr.strref, 175081);
assert_eq!(
&locstr.strings,
&[(
0,
"Une indicible intelligence pétille dans ses yeux fous...\r\nWow...".to_string()
)]
);
let gffstruct = gff.root.find("ACRtAnkle").unwrap().unwrap_struct();
assert_eq!(gffstruct.id, 0);
assert_eq!(gffstruct.fields.len(), 3);
let gfflist = gff.root.find("FeatList").unwrap().unwrap_list();
assert_eq!(gfflist.len(), 2);
let tint_struct = gff
.root
.find("Tint_Hair")
.unwrap()
.unwrap_struct()
.find("Tintable")
.unwrap()
.unwrap_struct()
.find("Tint")
.unwrap()
.unwrap_struct()
.find("1")
.unwrap()
.unwrap_struct();
assert_eq!(tint_struct.find("r").unwrap().unwrap_byte(), 247);
assert_eq!(tint_struct.find("a").unwrap().unwrap_byte(), 255);
assert_eq!(tint_struct.find("b").unwrap().unwrap_byte(), 0);
assert_eq!(tint_struct.find("g").unwrap().unwrap_byte(), 223);
let item_struct = &gff.root.find("Equip_ItemList").unwrap().unwrap_list()[0];
assert_eq!(item_struct.id, 16384);
assert_eq!(
item_struct.find("EquippedRes").unwrap().unwrap_resref(),
"nw_it_crewps005"
);
let skill_struct = &gff.root.find("SkillList").unwrap().unwrap_list()[6];
assert_eq!(skill_struct.id, 0);
assert_eq!(skill_struct.find("Rank").unwrap().unwrap_byte(), 2);
assert_eq!(gff.to_bytes(), doge_bytes);
}
#[test]
fn test_gff_read_krogar() {
let krogar_bytes = include_bytes!("../unittest/krogar.bic");
let (_, gff) = Gff::from_bytes(krogar_bytes).unwrap();
assert_eq!(gff.file_type, "BIC ");
assert_eq!(gff.file_version, "V3.2");
assert_eq!(gff.root.id, u32::max_value());
assert_eq!(gff.root.find("ACLtElbow").unwrap().unwrap_struct().id, 4);
assert_eq!(gff.root.find("IsPC").unwrap().unwrap_byte(), 1);
assert_eq!(gff.root.find("RefSaveThrow").unwrap().unwrap_char(), 13);
assert_eq!(gff.root.find("SoundSetFile").unwrap().unwrap_word(), 363);
assert_eq!(gff.root.find("HitPoints").unwrap().unwrap_short(), 320);
assert_eq!(gff.root.find("Gold").unwrap().unwrap_dword(), 6400);
assert_eq!(gff.root.find("Age").unwrap().unwrap_int(), 50);
assert_eq!(gff.root.find("XpMod").unwrap().unwrap_float(), 1f32);
assert_eq!(
gff.root.find("Deity").unwrap().unwrap_string(),
"Gorm Gulthyn"
);
assert_eq!(
gff.root.find("ScriptHeartbeat").unwrap().unwrap_resref(),
"gb_player_heart"
);
let first_name = gff.root.find("FirstName").unwrap().unwrap_locstring();
assert_eq!(first_name.strref, u32::max_value());
assert_eq!(first_name.strings.len(), 2);
assert_eq!(first_name.strings[0], (0, "Krogar".to_string()));
assert_eq!(first_name.strings[1], (2, "Krogar".to_string()));
let lvl_stat_list = gff.root.find("LvlStatList").unwrap().unwrap_list();
assert_eq!(lvl_stat_list.len(), 30);
assert_eq!(
lvl_stat_list
.get(5)
.unwrap()
.find("FeatList")
.unwrap()
.unwrap_list()
.get(0)
.unwrap()
.find("Feat")
.unwrap()
.unwrap_word(),
389
);
let mut bingff = gff.to_bin_gff();
bingff.structs[4804].data_or_data_offset = 4294967295;
assert_eq!(bingff.to_bytes(), krogar_bytes);
}
#[test]
fn test_gff_read_towncrier() {
let towncrier_bytes = include_bytes!("../unittest/nwn1_towncrier.utc");
let (_, gff) = Gff::from_bytes(towncrier_bytes).unwrap();
assert_eq!(gff.root.find("Race").unwrap().unwrap_byte(), 3);
assert_eq!(gff.root.find("PortraitId").unwrap().unwrap_word(), 957);
assert_eq!(gff.root.find("HitPoints").unwrap().unwrap_short(), 70);
assert_eq!(gff.root.find("DecayTime").unwrap().unwrap_dword(), 5000);
assert_eq!(gff.root.find("WalkRate").unwrap().unwrap_int(), 7);
assert!((gff.root.find("ChallengeRating").unwrap().unwrap_float() - 11f32).abs() < 0.001);
assert_eq!(gff.root.find("Tag").unwrap().unwrap_string(), "TownCrier");
assert_eq!(
gff.root.find("ScriptHeartbeat").unwrap().unwrap_resref(),
"ohb_towncrier"
);
let locstr = gff.root.find("FirstName").unwrap().unwrap_locstring();
assert_eq!(locstr.strref, 12599);
assert_eq!(&locstr.strings, &[(0, "Town Crier".to_string())]);
let gfflist = gff.root.find("FeatList").unwrap().unwrap_list();
assert_eq!(gfflist.len(), 20);
let item_struct = &gff.root.find("ItemList").unwrap().unwrap_list()[1];
assert_eq!(item_struct.id, 1);
assert_eq!(
item_struct.find("InventoryRes").unwrap().unwrap_resref(),
"nw_it_torch001"
);
assert_eq!(item_struct.find("Repos_PosX").unwrap().unwrap_word(), 2);
assert_eq!(item_struct.find("Repos_Posy").unwrap().unwrap_word(), 0);
assert_eq!(gff.to_bytes(), towncrier_bytes);
}
#[test]
fn test_gff_araignee() {
let araignee_bytes = include_bytes!("../unittest/araignéesouterrains.ute");
let (_, gff) = Gff::from_bytes(araignee_bytes).unwrap();
assert_eq!(gff.to_bytes(), araignee_bytes);
}
extern crate test;
#[bench]
fn bench_krogar(b: &mut test::Bencher) {
b.iter(|| {
let krogar_bytes = include_bytes!("../unittest/krogar.bic");
let (_, _gff) = bin_gff::Gff::from_bytes(krogar_bytes).unwrap();
});
}
}