diff --git a/resources/test/text_property_noarray.bin b/resources/test/text_property_noarray.bin new file mode 100644 index 0000000..cfce44c Binary files /dev/null and b/resources/test/text_property_noarray.bin differ diff --git a/src/properties/text_property.rs b/src/properties/text_property.rs index 389da9c..af420c2 100644 --- a/src/properties/text_property.rs +++ b/src/properties/text_property.rs @@ -4,13 +4,14 @@ use std::{ }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use unreal_helpers::{UnrealReadExt, UnrealWriteExt}; use crate::{ cursor_ext::{ReadExt, WriteExt}, error::{DeserializeError, Error}, }; -use super::{impl_write, PropertyOptions, PropertyTrait}; +use super::{impl_read, impl_read_header, impl_write, PropertyOptions, PropertyTrait}; /// A property that stores GVAS Text. #[derive(Clone, PartialEq, Eq, Hash)] @@ -21,12 +22,14 @@ pub enum TextProperty { // Workaround for https://github.com/serde-rs/json/issues/664 [u8; 0], ), + /// A triple `TextProperty`. + Triple(Option, Option, Option), /// A rich `TextProperty`. Rich(RichText), /// A simple `TextProperty`. Simple(Vec), /// `TextProperty` type 8. - Type8(String, String, String), + Type8(Option, String, String), } /// A struct describing a rich `TextProperty`. @@ -81,47 +84,31 @@ impl TextProperty { } } + impl_read!(options); + impl_read_header!(options); + #[inline] - pub(crate) fn read( + pub(crate) fn read_body( cursor: &mut R, - include_header: bool, options: &mut PropertyOptions, ) -> Result { - validate!( - cursor, - !include_header, - "TextProperty only supported in arrays" - ); - let component_type = cursor.read_u32::()?; - validate!( - cursor, - component_type <= 2 || component_type == 8, - "Unexpected component {component_type}" - ); - - let expect_indicator = match component_type { - 1 => 3, - 8 => 0, - _ => 255, - }; let indicator = cursor.read_u8()?; - validate!( - cursor, - indicator == expect_indicator, - "Unexpected indicator {} for component {}, expected {}", - indicator, - component_type, - expect_indicator - ); - - if component_type == 0 { + + if component_type == 0 && indicator == 255 { // Empty text let count = cursor.read_u32::()?; validate!(cursor, count == 0, "Unexpected count {count}"); Ok(TextProperty::Empty([])) - } else if component_type == 1 { + } else if component_type == 0 && indicator == 0 { + // Triple text + let string1 = cursor.read_fstring()?; + let string2 = cursor.read_fstring()?; + let string3 = cursor.read_fstring()?; + + Ok(TextProperty::Triple(string1, string2, string3)) + } else if component_type == 1 && indicator == 3 { // Rich text let num_flags = cursor.read_u8()?; validate!(cursor, num_flags == 8, "Unexpected num_flags {num_flags}"); @@ -170,7 +157,7 @@ impl TextProperty { pattern, text_format, })) - } else if component_type == 2 { + } else if component_type == 2 && indicator == 255 { // Simple text let count = cursor.read_u32::()?; @@ -181,8 +168,8 @@ impl TextProperty { } Ok(TextProperty::Simple(strings)) - } else if component_type == 8 { - let unknown = cursor.read_string()?; + } else if component_type == 8 && indicator == 0 { + let unknown = cursor.read_fstring()?; let guid = cursor.read_string()?; let value = cursor.read_string()?; @@ -190,7 +177,7 @@ impl TextProperty { } else { // Unknown text Err(DeserializeError::InvalidProperty( - format!("Unexpected component_type {}", component_type), + format!("Unexpected component_type {component_type}, indicator {indicator}"), cursor.stream_position()?, ))? } @@ -209,6 +196,14 @@ impl TextProperty { cursor.write_u32::(0)?; } + TextProperty::Triple(string1, string2, string3) => { + cursor.write_u32::(0)?; + cursor.write_u8(0)?; + cursor.write_fstring(string1.as_deref())?; + cursor.write_fstring(string2.as_deref())?; + cursor.write_fstring(string3.as_deref())?; + } + TextProperty::Rich(value) => { cursor.write_u32::(1)?; cursor.write_u8(3)?; @@ -246,7 +241,7 @@ impl TextProperty { TextProperty::Type8(unknown, guid, value) => { cursor.write_u32::(8)?; cursor.write_u8(0)?; - cursor.write_string(unknown)?; + cursor.write_fstring(unknown.as_deref())?; cursor.write_string(guid)?; cursor.write_string(value)?; } @@ -263,6 +258,11 @@ impl Debug for TextProperty { TextProperty::Simple(values) => f.debug_list().entries(values).finish(), TextProperty::Empty(_) => f.write_str("Empty"), TextProperty::Type8(_, _, value) => value.fmt(f), + TextProperty::Triple(string1, string2, string3) => { + string1.fmt(f)?; + string2.fmt(f)?; + string3.fmt(f) + } } } } diff --git a/tests/text_property_noarray.rs b/tests/text_property_noarray.rs new file mode 100644 index 0000000..c5a327b --- /dev/null +++ b/tests/text_property_noarray.rs @@ -0,0 +1,47 @@ +use gvas::GvasFile; +use std::{ + fs::File, + io::{Cursor, Read}, + path::Path, +}; + +#[test] +fn read_text_property_noarray() { + let path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/test/text_property_noarray.bin"); + let mut file = File::open(path).expect("Failed to open test asset"); + + // Read the file in to a GvasFile + let _gvas = GvasFile::read(&mut file).expect("Failed to parse gvas file"); +} + +#[test] +fn write_text_property_noarray() { + let path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/test/text_property_noarray.bin"); + let mut file = File::open(path).expect("Failed to open test asset"); + + // Read the file in to a Vec + let mut data = Vec::new(); + file.read_to_end(&mut data) + .expect("Failed to read test asset"); + + // Convert the Vec to a GvasFile + let mut cursor = Cursor::new(data); + let file = GvasFile::read(&mut cursor).expect("Failed to parse gvas file"); + + // Convert the GvasFile back to a Vec + let mut writer = Cursor::new(Vec::new()); + file.write(&mut writer) + .expect("Failed to serialize gvas file"); + + // Compare the two Vecs + assert_eq!(cursor.get_ref(), writer.get_ref()); + + // Read the file back in again + let mut reader = Cursor::new(writer.get_ref().to_owned()); + let read_back = GvasFile::read(&mut reader).expect("Failed to parse serialized save file"); + + // Compare the two GvasFiles + assert_eq!(file, read_back); +}