From 032a38cd8d192bc806cc0404ec14e87aa6c68180 Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:37:51 -0400 Subject: [PATCH] Refactor StructPropertyValue --- src/properties/array_property.rs | 11 +- src/properties/mod.rs | 19 +- src/properties/struct_property.rs | 495 +++++++----- src/properties/struct_types.rs | 6 + tests/common/profile0.rs | 959 +++++++++++------------ tests/common/regression.rs | 1 + tests/common/saveslot3.rs | 476 +++++------ tests/common/slot1.rs | 229 +++++- tests/common/vector2d.rs | 294 ++++--- tests/gvas_tests/test_property.rs | 12 +- tests/serde_tests/serde_json_template.rs | 209 ++--- 11 files changed, 1421 insertions(+), 1290 deletions(-) diff --git a/src/properties/array_property.rs b/src/properties/array_property.rs index 4ab2ab6..9ae74a1 100644 --- a/src/properties/array_property.rs +++ b/src/properties/array_property.rs @@ -18,7 +18,7 @@ use super::{ int_property::{BoolProperty, ByteProperty, BytePropertyValue, FloatProperty, IntProperty}, name_property::NameProperty, str_property::StrProperty, - struct_property::StructProperty, + struct_property::{StructProperty, StructPropertyValue}, Property, PropertyOptions, PropertyTrait, }; @@ -78,7 +78,7 @@ pub enum ArrayProperty { #[cfg_attr(feature = "serde", serde(default))] guid: Guid, /// An array of values. - structs: Vec, + structs: Vec, }, /// Any other Property value Properties { @@ -226,7 +226,7 @@ impl ArrayProperty { ("StructProperty", Some((field_name, type_name, guid))) => match properties .iter() .map(|p| match p { - Property::StructProperty(struct_property) => Ok(struct_property.clone()), + Property::StructPropertyValue(value) => Ok(value.clone()), _ => Err(p), }) .collect::>() @@ -324,9 +324,8 @@ impl ArrayProperty { let properties_start = cursor.stream_position()?; for _ in 0..property_count { - properties.push( - StructProperty::read_with_type_name(cursor, &struct_name, options)?.into(), - ); + let value = StructProperty::read_body(cursor, &struct_name, options)?; + properties.push(Property::from(value)); } let properties_end = cursor.stream_position()?; let actual_size = properties_end - properties_start; diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 13a417e..2d1eddb 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -30,7 +30,7 @@ use self::{ object_property::ObjectProperty, set_property::SetProperty, str_property::StrProperty, - struct_property::StructProperty, + struct_property::{StructProperty, StructPropertyValue}, text_property::TextProperty, unknown_property::UnknownProperty, }; @@ -536,6 +536,8 @@ pub enum Property { StrProperty, /// A `StructProperty`. StructProperty, + /// A raw `StructPropertyValue`. + StructPropertyValue, /// A `TextProperty`. TextProperty, /// A `UInt16Property`. @@ -585,7 +587,20 @@ impl Property { Ok(MulticastSparseDelegateProperty::read(cursor, include_header)?.into()) } "FieldPathProperty" => Ok(FieldPathProperty::read(cursor, include_header)?.into()), - "StructProperty" => Ok(StructProperty::read(cursor, include_header, options)?.into()), + "StructProperty" => match include_header { + true => Ok(StructProperty::read(cursor, include_header, options)?.into()), + false => { + let struct_path = options.properties_stack.join("."); + let Some(hint) = options.hints.get(&struct_path) else { + Err(DeserializeError::MissingHint( + "StructProperty".into(), + struct_path.into_boxed_str(), + cursor.stream_position()?, + ))? + }; + Ok(StructProperty::read_body(cursor, hint, options)?.into()) + } + }, "ArrayProperty" => Ok(ArrayProperty::read(cursor, include_header, options)?.into()), "SetProperty" => Ok(SetProperty::read(cursor, include_header, options)?.into()), "MapProperty" => Ok(MapProperty::read(cursor, include_header, options)?.into()), diff --git a/src/properties/struct_property.rs b/src/properties/struct_property.rs index eebc386..6ee8a34 100644 --- a/src/properties/struct_property.rs +++ b/src/properties/struct_property.rs @@ -17,11 +17,10 @@ use crate::{ }; use super::{ - impl_write, impl_write_header_part, - int_property::{DoubleProperty, FloatProperty, IntProperty, UInt32Property, UInt64Property}, - make_matcher, + impl_write, impl_write_header_part, make_matcher, struct_types::{ - DateTime, IntPoint, QuatD, QuatF, RotatorD, RotatorF, Vector2D, Vector2F, VectorD, VectorF, + DateTime, IntPoint, QuatD, QuatF, RotatorD, RotatorF, Timespan, Vector2D, Vector2F, + VectorD, VectorF, }, Property, PropertyOptions, PropertyTrait, }; @@ -44,6 +43,8 @@ pub struct StructProperty { #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Guid::is_zero"))] #[cfg_attr(feature = "serde", serde(default))] pub guid: Guid, + /// Type name. + pub type_name: String, /// The value of the property. #[cfg_attr(feature = "serde", serde(flatten))] pub value: StructPropertyValue, @@ -72,7 +73,7 @@ pub enum StructPropertyValue { /// A `DateTime` value. DateTime(DateTime), /// A `Timespan` value - Timespan(DateTime), + Timespan(Timespan), /// A `Guid` value. Guid(Guid), /// A `LinearColor` value. @@ -80,19 +81,18 @@ pub enum StructPropertyValue { /// An `IntPoint` value. IntPoint(IntPoint), /// A custom struct value. - CustomStruct { - /// Type name. - type_name: String, - /// Properties. - properties: HashableIndexMap>, - }, + CustomStruct(HashableIndexMap>), } impl StructProperty { /// Creates a new `StructProperty` instance. #[inline] - pub fn new(guid: Guid, value: StructPropertyValue) -> Self { - StructProperty { guid, value } + pub fn new(guid: Guid, type_name: String, value: StructPropertyValue) -> Self { + StructProperty { + guid, + type_name, + value, + } } #[inline] @@ -102,169 +102,80 @@ impl StructProperty { options: &mut PropertyOptions, ) -> Result { if include_header { - Ok(Self::read_real(cursor, true, None, options)?) + Self::read_header(cursor, options) } else { - let struct_path = options.properties_stack.join("."); - let Some(hint) = options.hints.get(&struct_path) else { - Err(DeserializeError::MissingHint( - "StructProperty".into(), - struct_path.into_boxed_str(), - cursor.stream_position()?, - ))? - }; - let hint = &hint.clone(); - Self::read_with_type_name(cursor, hint, options) + Err(DeserializeError::invalid_property( + "StructProperty::read() include_header must be true, use read_body() instead", + cursor, + ))? } } #[inline] - fn read_real( + fn read_header( cursor: &mut R, - include_header: bool, - type_name: Option, options: &mut PropertyOptions, ) -> Result { - if include_header { - let _length = cursor.read_u64::()?; + let length = cursor.read_u32::()?; + + let array_index = cursor.read_u32::()?; + if array_index != 0 { + let position = cursor.stream_position()? - 4; + Err(DeserializeError::InvalidArrayIndex(array_index, position))? } - let type_name = match include_header { - true => cursor.read_string()?, - false => match type_name { - Some(t) => t, - None => Err(DeserializeError::missing_argument("type_name", cursor))?, - }, - }; + let type_name = cursor.read_string()?; - let guid = match include_header { - true => cursor.read_guid()?, - false => Guid::default(), - }; + let guid = cursor.read_guid()?; - if include_header { - let separator = cursor.read_u8()?; - assert_eq!(separator, 0); + let terminator = cursor.read_u8()?; + if terminator != 0 { + let position = cursor.stream_position()? - 1; + Err(DeserializeError::InvalidTerminator(terminator, position))? } - let value = match type_name.as_str() { - "Vector" => match options.large_world_coordinates { - true => StructPropertyValue::VectorD(VectorD::new( - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - )), - false => StructPropertyValue::VectorF(VectorF::new( - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - )), - }, - "Vector2D" => match options.large_world_coordinates { - true => StructPropertyValue::Vector2D(Vector2D::new( - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - )), - false => StructPropertyValue::Vector2F(Vector2F::new( - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - )), - }, - "Rotator" => match options.large_world_coordinates { - true => StructPropertyValue::RotatorD(RotatorD::new( - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - )), - false => StructPropertyValue::RotatorF(RotatorF::new( - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - )), - }, - "Quat" => match options.large_world_coordinates { - true => StructPropertyValue::QuatD(QuatD::new( - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - DoubleProperty::read(cursor, false)?.value.0, - )), - false => StructPropertyValue::QuatF(QuatF::new( - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - )), - }, - "DateTime" => StructPropertyValue::DateTime(DateTime { - ticks: UInt64Property::read(cursor, false)?.value, - }), - "Timespan" => StructPropertyValue::Timespan(DateTime { - ticks: UInt64Property::read(cursor, false)?.value, - }), - "LinearColor" => StructPropertyValue::LinearColor(LinearColor::new( - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - FloatProperty::read(cursor, false)?.value.0, - )), - "IntPoint" => StructPropertyValue::IntPoint(IntPoint { - x: IntProperty::read(cursor, false)?.value, - y: IntProperty::read(cursor, false)?.value, - }), - "Guid" => StructPropertyValue::Guid(Guid::from([ - UInt32Property::read(cursor, false)?.value, - UInt32Property::read(cursor, false)?.value, - UInt32Property::read(cursor, false)?.value, - UInt32Property::read(cursor, false)?.value, - ])), - _ => { - let mut properties = HashableIndexMap::new(); - loop { - let property_name = cursor.read_string()?; - if property_name == "None" { - break; - } - let property_type = cursor.read_string()?; - let _property_stack_entry = - ScopedStackEntry::new(options.properties_stack, property_name.clone()); - - let property = Property::new(cursor, &property_type, true, options, None)?; - insert_property(&mut properties, property_name, property); - } - StructPropertyValue::CustomStruct { - type_name, - properties, - } - } - }; + let start = cursor.stream_position()?; + let value = Self::read_body(cursor, &type_name, options)?; + let end = cursor.stream_position()?; + if end - start != length as u64 { + Err(DeserializeError::InvalidValueSize( + length as u64, + end - start, + start, + ))? + } - Ok(StructProperty { guid, value }) + Ok(StructProperty { + guid, + type_name, + value, + }) } #[inline] - pub(crate) fn read_with_type_name( + pub(crate) fn read_body( cursor: &mut R, type_name: &str, options: &mut PropertyOptions, - ) -> Result { - Self::read_real(cursor, false, Some(type_name.to_string()), options) + ) -> Result { + let value = match type_name { + "Vector" => StructPropertyValue::read_vector(cursor, options)?, + "Vector2D" => StructPropertyValue::read_vector2(cursor, options)?, + "Rotator" => StructPropertyValue::read_rotator(cursor, options)?, + "Quat" => StructPropertyValue::read_quat(cursor, options)?, + "DateTime" => StructPropertyValue::read_datetime(cursor)?, + "Timespan" => StructPropertyValue::read_timespan(cursor)?, + "LinearColor" => StructPropertyValue::read_linearcolor(cursor)?, + "IntPoint" => StructPropertyValue::read_intpoint(cursor)?, + "Guid" => StructPropertyValue::read_guid(cursor)?, + _ => StructPropertyValue::read_custom(cursor, options)?, + }; + Ok(value) } #[inline] - fn get_property_name(&self) -> Result<&str, Error> { - let property_name = match &self.value { - StructPropertyValue::Vector2F(_) | StructPropertyValue::Vector2D(_) => "Vector2D", - StructPropertyValue::VectorF(_) | StructPropertyValue::VectorD(_) => "Vector", - StructPropertyValue::RotatorF(_) | StructPropertyValue::RotatorD(_) => "Rotator", - StructPropertyValue::QuatF(_) | StructPropertyValue::QuatD(_) => "Quat", - StructPropertyValue::DateTime(_) => "DateTime", - StructPropertyValue::Timespan(_) => "Timespan", - StructPropertyValue::Guid(_) => "Guid", - StructPropertyValue::LinearColor(_) => "LinearColor", - StructPropertyValue::IntPoint(_) => "IntPoint", - StructPropertyValue::CustomStruct { type_name, .. } => type_name, - }; - Ok(property_name) + fn get_property_type(&self) -> Result<&str, Error> { + Ok(&self.type_name) } } @@ -288,7 +199,7 @@ fn insert_property(map: &mut IndexMap>, key: String, prope impl PropertyTrait for StructProperty { impl_write!( StructProperty, - (write_string, fn, get_property_name), + (write_string, fn, get_property_type), (write_guid, guid) ); @@ -298,7 +209,33 @@ impl PropertyTrait for StructProperty { cursor: &mut W, options: &mut PropertyOptions, ) -> Result { - match &self.value { + self.value.write_body(cursor, options) + } +} + +impl PropertyTrait for StructPropertyValue { + #[inline] + fn write( + &self, + writer: &mut W, + include_header: bool, + options: &mut PropertyOptions, + ) -> Result { + if !include_header { + return self.write_body(writer, options); + } + Err(SerializeError::invalid_value( + "StructPropertyValue can not be serialized with a header", + ))? + } + + #[inline] + fn write_body( + &self, + cursor: &mut W, + options: &mut PropertyOptions, + ) -> Result { + match self { StructPropertyValue::Vector2F(vector) => { validate!( !options.large_world_coordinates, @@ -403,10 +340,7 @@ impl PropertyTrait for StructProperty { cursor.write_guid(guid)?; Ok(16) } - StructPropertyValue::CustomStruct { - properties: HashableIndexMap(properties), - .. - } => { + StructPropertyValue::CustomStruct(properties) => { let mut len = 0; for (key, values) in properties { for value in values { @@ -421,103 +355,248 @@ impl PropertyTrait for StructProperty { } } -impl From for StructProperty { +impl StructPropertyValue { + fn read_custom( + cursor: &mut R, + options: &mut PropertyOptions, + ) -> Result { + let mut properties = HashableIndexMap::new(); + loop { + let property_name = cursor.read_string()?; + if property_name == "None" { + break; + } + let property_type = cursor.read_string()?; + let _property_stack_entry = + ScopedStackEntry::new(options.properties_stack, property_name.clone()); + + let property = Property::new(cursor, &property_type, true, options, None)?; + insert_property(&mut properties, property_name, property); + } + Ok(StructPropertyValue::CustomStruct(properties)) + } + + fn read_guid(cursor: &mut R) -> Result { + Ok(Self::Guid(cursor.read_guid()?)) + } + + fn read_intpoint(cursor: &mut R) -> Result { + Ok(Self::IntPoint(IntPoint::new( + cursor.read_i32::()?, + cursor.read_i32::()?, + ))) + } + + fn read_linearcolor(cursor: &mut R) -> Result { + Ok(Self::LinearColor(LinearColor::new( + cursor.read_f32::()?, + cursor.read_f32::()?, + cursor.read_f32::()?, + cursor.read_f32::()?, + ))) + } + + fn read_timespan(cursor: &mut R) -> Result { + Ok(Self::Timespan(Timespan::new( + cursor.read_u64::()?, + ))) + } + + fn read_datetime(cursor: &mut R) -> Result { + Ok(Self::DateTime(DateTime::new( + cursor.read_u64::()?, + ))) + } + + fn read_quat( + cursor: &mut R, + options: &mut PropertyOptions, + ) -> Result { + match options.large_world_coordinates { + true => Ok(Self::QuatD(QuatD::new( + cursor.read_f64::()?, + cursor.read_f64::()?, + cursor.read_f64::()?, + cursor.read_f64::()?, + ))), + false => Ok(Self::QuatF(QuatF::new( + cursor.read_f32::()?, + cursor.read_f32::()?, + cursor.read_f32::()?, + cursor.read_f32::()?, + ))), + } + } + + fn read_rotator( + cursor: &mut R, + options: &mut PropertyOptions, + ) -> Result { + match options.large_world_coordinates { + true => Ok(Self::RotatorD(RotatorD::new( + cursor.read_f64::()?, + cursor.read_f64::()?, + cursor.read_f64::()?, + ))), + false => Ok(Self::RotatorF(RotatorF::new( + cursor.read_f32::()?, + cursor.read_f32::()?, + cursor.read_f32::()?, + ))), + } + } + + fn read_vector2( + cursor: &mut R, + options: &mut PropertyOptions, + ) -> Result { + match options.large_world_coordinates { + true => Ok(Self::Vector2D(Vector2D::new( + cursor.read_f64::()?, + cursor.read_f64::()?, + ))), + false => Ok(Self::Vector2F(Vector2F::new( + cursor.read_f32::()?, + cursor.read_f32::()?, + ))), + } + } + + fn read_vector( + cursor: &mut R, + options: &mut PropertyOptions, + ) -> Result { + match options.large_world_coordinates { + true => Ok(Self::VectorD(VectorD::new( + cursor.read_f64::()?, + cursor.read_f64::()?, + cursor.read_f64::()?, + ))), + false => Ok(Self::VectorF(VectorF::new( + cursor.read_f32::()?, + cursor.read_f32::()?, + cursor.read_f32::()?, + ))), + } + } + make_matcher!(VectorF, get_vector_f, get_vector_f_mut); + make_matcher!(VectorD, get_vector_d, get_vector_d_mut); + make_matcher!(RotatorF, get_rotator_f, get_rotator_f_mut); + make_matcher!(RotatorD, get_rotator_d, get_rotator_d_mut); + make_matcher!(QuatF, get_quat_f, get_quat_f_mut); + make_matcher!(QuatD, get_quat_d, get_quat_d_mut); + make_matcher!(DateTime, get_date_time, get_date_time_mut); + make_matcher!(IntPoint, get_int_point, get_int_point_mut); + make_matcher!(Guid, get_guid, get_guid_mut); + + /// Retrieves the enum value as a `CustomStruct`. + #[inline] + pub fn get_custom_struct(&self) -> Option<&HashableIndexMap>> { + match self { + Self::CustomStruct(properties) => Some(properties), + _ => None, + } + } + + /// Retrieves the mutable enum value as a `CustomStruct`. + #[inline] + pub fn get_custom_struct_mut( + &mut self, + ) -> Option<&mut HashableIndexMap>> { + match self { + Self::CustomStruct(properties) => Some(properties), + _ => None, + } + } +} + +impl From for StructPropertyValue { + #[inline] + fn from(value: Vector2F) -> Self { + StructPropertyValue::Vector2F(value) + } +} + +impl From for StructPropertyValue { + #[inline] + fn from(value: Vector2D) -> Self { + StructPropertyValue::Vector2D(value) + } +} + +impl From for StructPropertyValue { #[inline] fn from(vector: VectorF) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::VectorF(vector)) + StructPropertyValue::VectorF(vector) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(vector: VectorD) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::VectorD(vector)) + StructPropertyValue::VectorD(vector) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(rotator: RotatorF) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::RotatorF(rotator)) + StructPropertyValue::RotatorF(rotator) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(rotator: RotatorD) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::RotatorD(rotator)) + StructPropertyValue::RotatorD(rotator) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(quat: QuatF) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::QuatF(quat)) + StructPropertyValue::QuatF(quat) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(quat: QuatD) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::QuatD(quat)) + StructPropertyValue::QuatD(quat) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(date_time: DateTime) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::DateTime(date_time)) + StructPropertyValue::DateTime(date_time) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] - fn from(int_point: IntPoint) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::IntPoint(int_point)) + fn from(timespan: Timespan) -> Self { + StructPropertyValue::Timespan(timespan) } } -impl From for StructProperty { +impl From for StructPropertyValue { #[inline] fn from(guid: Guid) -> Self { - Self::new(Guid([0u8; 16]), StructPropertyValue::Guid(guid)) + StructPropertyValue::Guid(guid) } } -impl StructPropertyValue { - make_matcher!(VectorF, get_vector_f, get_vector_f_mut); - make_matcher!(VectorD, get_vector_d, get_vector_d_mut); - make_matcher!(RotatorF, get_rotator_f, get_rotator_f_mut); - make_matcher!(RotatorD, get_rotator_d, get_rotator_d_mut); - make_matcher!(QuatF, get_quat_f, get_quat_f_mut); - make_matcher!(QuatD, get_quat_d, get_quat_d_mut); - make_matcher!(DateTime, get_date_time, get_date_time_mut); - make_matcher!(IntPoint, get_int_point, get_int_point_mut); - make_matcher!(Guid, get_guid, get_guid_mut); - - /// Retrieves the enum value as a `CustomStruct`. +impl From for StructPropertyValue { #[inline] - pub fn get_custom_struct(&self) -> Option<(&String, &IndexMap>)> { - match self { - Self::CustomStruct { - type_name, - properties: HashableIndexMap(properties), - } => Some((type_name, properties)), - _ => None, - } + fn from(linear_color: LinearColor) -> Self { + StructPropertyValue::LinearColor(linear_color) } +} - /// Retrieves the mutable enum value as a `CustomStruct`. +impl From for StructPropertyValue { #[inline] - pub fn get_custom_struct_mut( - &mut self, - ) -> Option<(&mut String, &mut IndexMap>)> { - match self { - Self::CustomStruct { - type_name, - properties: HashableIndexMap(properties), - } => Some((type_name, properties)), - _ => None, - } + fn from(int_point: IntPoint) -> Self { + StructPropertyValue::IntPoint(int_point) } } diff --git a/src/properties/struct_types.rs b/src/properties/struct_types.rs index b36e371..49f78c8 100644 --- a/src/properties/struct_types.rs +++ b/src/properties/struct_types.rs @@ -155,6 +155,12 @@ make_struct!( (ticks, u64, "Ticks."), ); +make_struct!( + Timespan, + "A struct that stores a duration.", + (ticks, u64, "Ticks."), +); + make_struct!( LinearColor, "A structure storing linear color.", diff --git a/tests/common/profile0.rs b/tests/common/profile0.rs index b0d5b4b..a5e6aa0 100644 --- a/tests/common/profile0.rs +++ b/tests/common/profile0.rs @@ -280,536 +280,487 @@ pub(crate) const PROFILE_0_JSON: &str = r#"{ }, "UnlockLayer": { "type": "StructProperty", + "type_name": "UnlockLayer", "CustomStruct": { - "type_name": "UnlockLayer", - "properties": { - "ownCounts": [ - { - "type": "MapProperty", - "name_ints": { - "unlock.c1.t2": 1, - "unlock.c1.t3": 1, - "unlock.c1.m1": 1, - "unlock.c1.m2": 1, - "unlock.milestone.oilwell": 1, - "unlock.upgrade.oilpower": 1, - "unlock.milestone.Workhorse": 1, - "unlock.c1.m2bonus": 1, - "unlock.c1.m3": 1, - "unlock.CustomEngine": 1, - "unlock.placeable.geothermal": 1, - "unlock.upgrade.geothermal": 1, - "unlock.milestone.oilPower": 1, - "unlock.c1.m4": 1, - "unlock.milestone.coppermine": 1, - "unlock.milestone.coiler": 1, - "unlock.milestone.limestonemine": 1, - "unlock.milestone.concreteYard": 1, - "unlock.upgrade.concreteyard": 1, - "unlock.upgrade.coiler": 1, - "unlock.c1.mspecial1": 1, - "unlock.milestone.autoBranching": 1, - "unlock.c1.m6": 1, - "unlock.milestone.MediumHeight": 1, - "unlock.c1.m7": 1, - "unlock.milestone.saltsifter": 1, - "unlock.milestone.coalMine": 1, - "unlock.upgrade.saltsifter": 1, - "unlock.c1.m8": 1, - "unlock.milestone.coalpower": 1, - "unlock.milestone.Industrial": 1, - "unlock.milestone.ironmine": 1, - "unlock.c1.m9": 1, - "unlock.c1.m8bonus1": 1, - "unlock.c1.m8bonus2": 1, - "unlock.milestone.steelMill": 1, - "unlock.milestone.plasticizer": 1, - "unlock.c1.mspecial2": 1, - "unlock.c1.m9bonus": 1, - "unlock.milestone.furnitureAssembler": 1, - "unlock.upgrade.plasticizer": 1, - "unlock.upgrade.furnitureassembler": 1, - "unlock.upgrade.coalpower": 1, - "unlock.upgrade.steelmill": 1, - "unlock.c1.m10a1": 1, - "unlock.c1.m10b1": 1, - "unlock.milestone.oilrefinery": 1, - "unlock.milestone.neonrefinery": 1, - "unlock.milestone.goodsFactory": 1, - "unlock.milestone.hardmold": 1, - "unlock.milestone.electronicsFab": 1, - "unlock.c1.m11": 1, - "unlock.c1.m10bonus": 1, - "unlock.milestone.incinerator": 1, - "unlock.milestone.oreGassifier": 1, - "unlock.c1.mspecial3": 1, - "unlock.c1.m11bonus1": 1, - "unlock.c1.m11bonus2": 1, - "unlock.placeable.electronicsfab": 1, - "unlock.upgrade.goodsfactory": 1, - "unlock.c1.m12": 1, - "unlock.milestone.tooldie": 1, - "unlock.c1.m12bonus": 1, - "unlock.c1.m13": 1, - "unlock.upgrade.tooldie": 1, - "unlock.upgrade.oilrefinery": 1, - "unlock.c1.m10a2": 1, - "unlock.milestone.Electric": 1, - "unlock.c1.m13bonus1": 1, - "unlock.c1.m13bonus2": 1, - "unlock.c1.m14": 1, - "unlock.milestone.hullyard": 1, - "unlock.milestone.MaxHeight": 1, - "unlock.HybridEngine": 1, - "unlock.upgrade.neonrefinery": 1, - "unlock.upgrade.electronicsfab": 1, - "unlock.upgrade.hullyard": 1, - "unlock.upgrade.incinerator": 1, - "unlock.upgrade.oregassifier": 1, - "unlock.upgrade.hardmold": 1, - "unlock.milestone.luxuryAssembler": 1, - "unlock.c1.mspecial4": 1, - "unlock.c1.m14bonus": 1, - "unlock.milestone.rescue": 1, - "unlock.c1.m15": 1, - "unlock.milestone.glassSmelter": 1, - "unlock.milestone.signworks": 1, - "unlock.c1.m16": 1, - "unlock.c1.m15bonus1": 1, - "unlock.c1.m15bonus2": 1, - "unlock.milestone.chipfab": 1, - "unlock.upgrade.glasssmelter": 1, - "unlock.stationSpeed": 1, - "unlock.c1.m17a": 1, - "unlock.c1.m17b": 1, - "unlock.c1.m16bonus": 1, - "unlock.milestone.tubePlant": 1, - "unlock.milestone.diesel": 1, - "unlock.milestone.motor": 1, - "unlock.upgrade.motorAssembly": 1, - "unlock.milestone.heavyworks": 1, - "unlock.milestone.framer": 1, - "unlock.c1.m18a": 1, - "unlock.c1.m18b": 1, - "unlock.c1.m17bonus": 1, - "placeholder": 1, - "unlock.c1.m4bonus": 1, - "unlock.c1.m5": 1, - "unlock.c1.m4bonus2": 1, - "unlock.DieselEngine": 1, - "unlock.upgrade.signworks": 1, - "unlock.upgrade.chipfab": 1, - "unlock.RescueEngine": 1, - "unlock.upgrade.tubeplant": 1, - "unlock.upgrade.luxuryassembler": 1, - "unlock.upgrade.framer": 1, - "unlock.upgrade.heavyworks": 1, - "unlock.milestone.armory": 1, - "unlock.milestone.boosterplant": 1, - "unlock.milestone.rocketfactory": 1, - "unlock.milestone.Bullet": 1, - "unlock.c1.m19a": 1, - "unlock.c1.m19b": 1, - "unlock.c1.m18bonus": 1, - "unlock.BulletEngine": 1, - "unlock.upgrade.rocketfactory": 1 - } + "ownCounts": [ + { + "type": "MapProperty", + "name_ints": { + "unlock.c1.t2": 1, + "unlock.c1.t3": 1, + "unlock.c1.m1": 1, + "unlock.c1.m2": 1, + "unlock.milestone.oilwell": 1, + "unlock.upgrade.oilpower": 1, + "unlock.milestone.Workhorse": 1, + "unlock.c1.m2bonus": 1, + "unlock.c1.m3": 1, + "unlock.CustomEngine": 1, + "unlock.placeable.geothermal": 1, + "unlock.upgrade.geothermal": 1, + "unlock.milestone.oilPower": 1, + "unlock.c1.m4": 1, + "unlock.milestone.coppermine": 1, + "unlock.milestone.coiler": 1, + "unlock.milestone.limestonemine": 1, + "unlock.milestone.concreteYard": 1, + "unlock.upgrade.concreteyard": 1, + "unlock.upgrade.coiler": 1, + "unlock.c1.mspecial1": 1, + "unlock.milestone.autoBranching": 1, + "unlock.c1.m6": 1, + "unlock.milestone.MediumHeight": 1, + "unlock.c1.m7": 1, + "unlock.milestone.saltsifter": 1, + "unlock.milestone.coalMine": 1, + "unlock.upgrade.saltsifter": 1, + "unlock.c1.m8": 1, + "unlock.milestone.coalpower": 1, + "unlock.milestone.Industrial": 1, + "unlock.milestone.ironmine": 1, + "unlock.c1.m9": 1, + "unlock.c1.m8bonus1": 1, + "unlock.c1.m8bonus2": 1, + "unlock.milestone.steelMill": 1, + "unlock.milestone.plasticizer": 1, + "unlock.c1.mspecial2": 1, + "unlock.c1.m9bonus": 1, + "unlock.milestone.furnitureAssembler": 1, + "unlock.upgrade.plasticizer": 1, + "unlock.upgrade.furnitureassembler": 1, + "unlock.upgrade.coalpower": 1, + "unlock.upgrade.steelmill": 1, + "unlock.c1.m10a1": 1, + "unlock.c1.m10b1": 1, + "unlock.milestone.oilrefinery": 1, + "unlock.milestone.neonrefinery": 1, + "unlock.milestone.goodsFactory": 1, + "unlock.milestone.hardmold": 1, + "unlock.milestone.electronicsFab": 1, + "unlock.c1.m11": 1, + "unlock.c1.m10bonus": 1, + "unlock.milestone.incinerator": 1, + "unlock.milestone.oreGassifier": 1, + "unlock.c1.mspecial3": 1, + "unlock.c1.m11bonus1": 1, + "unlock.c1.m11bonus2": 1, + "unlock.placeable.electronicsfab": 1, + "unlock.upgrade.goodsfactory": 1, + "unlock.c1.m12": 1, + "unlock.milestone.tooldie": 1, + "unlock.c1.m12bonus": 1, + "unlock.c1.m13": 1, + "unlock.upgrade.tooldie": 1, + "unlock.upgrade.oilrefinery": 1, + "unlock.c1.m10a2": 1, + "unlock.milestone.Electric": 1, + "unlock.c1.m13bonus1": 1, + "unlock.c1.m13bonus2": 1, + "unlock.c1.m14": 1, + "unlock.milestone.hullyard": 1, + "unlock.milestone.MaxHeight": 1, + "unlock.HybridEngine": 1, + "unlock.upgrade.neonrefinery": 1, + "unlock.upgrade.electronicsfab": 1, + "unlock.upgrade.hullyard": 1, + "unlock.upgrade.incinerator": 1, + "unlock.upgrade.oregassifier": 1, + "unlock.upgrade.hardmold": 1, + "unlock.milestone.luxuryAssembler": 1, + "unlock.c1.mspecial4": 1, + "unlock.c1.m14bonus": 1, + "unlock.milestone.rescue": 1, + "unlock.c1.m15": 1, + "unlock.milestone.glassSmelter": 1, + "unlock.milestone.signworks": 1, + "unlock.c1.m16": 1, + "unlock.c1.m15bonus1": 1, + "unlock.c1.m15bonus2": 1, + "unlock.milestone.chipfab": 1, + "unlock.upgrade.glasssmelter": 1, + "unlock.stationSpeed": 1, + "unlock.c1.m17a": 1, + "unlock.c1.m17b": 1, + "unlock.c1.m16bonus": 1, + "unlock.milestone.tubePlant": 1, + "unlock.milestone.diesel": 1, + "unlock.milestone.motor": 1, + "unlock.upgrade.motorAssembly": 1, + "unlock.milestone.heavyworks": 1, + "unlock.milestone.framer": 1, + "unlock.c1.m18a": 1, + "unlock.c1.m18b": 1, + "unlock.c1.m17bonus": 1, + "placeholder": 1, + "unlock.c1.m4bonus": 1, + "unlock.c1.m5": 1, + "unlock.c1.m4bonus2": 1, + "unlock.DieselEngine": 1, + "unlock.upgrade.signworks": 1, + "unlock.upgrade.chipfab": 1, + "unlock.RescueEngine": 1, + "unlock.upgrade.tubeplant": 1, + "unlock.upgrade.luxuryassembler": 1, + "unlock.upgrade.framer": 1, + "unlock.upgrade.heavyworks": 1, + "unlock.milestone.armory": 1, + "unlock.milestone.boosterplant": 1, + "unlock.milestone.rocketfactory": 1, + "unlock.milestone.Bullet": 1, + "unlock.c1.m19a": 1, + "unlock.c1.m19b": 1, + "unlock.c1.m18bonus": 1, + "unlock.BulletEngine": 1, + "unlock.upgrade.rocketfactory": 1 } - ], - "historicCounts": [ - { - "type": "MapProperty", - "name_ints": { - "unlock.c1.t2": 1, - "unlock.c1.t3": 1, - "unlock.c1.m1": 1, - "unlock.c1.m2": 1, - "unlock.milestone.oilwell": 1, - "unlock.upgrade.oilpower": 1, - "unlock.milestone.Workhorse": 1, - "unlock.c1.m2bonus": 1, - "unlock.c1.m3": 1, - "unlock.CustomEngine": 1, - "unlock.placeable.geothermal": 1, - "unlock.upgrade.geothermal": 1, - "unlock.milestone.oilPower": 1, - "unlock.c1.m4": 1, - "unlock.milestone.coppermine": 1, - "unlock.milestone.coiler": 1, - "unlock.milestone.limestonemine": 1, - "unlock.milestone.concreteYard": 1, - "unlock.upgrade.concreteyard": 1, - "unlock.upgrade.coiler": 1, - "unlock.c1.mspecial1": 1, - "unlock.milestone.autoBranching": 1, - "unlock.c1.m6": 1, - "unlock.milestone.MediumHeight": 1, - "unlock.c1.m7": 1, - "unlock.milestone.saltsifter": 1, - "unlock.milestone.coalMine": 1, - "unlock.upgrade.saltsifter": 1, - "unlock.c1.m8": 1, - "unlock.milestone.coalpower": 1, - "unlock.milestone.Industrial": 1, - "unlock.milestone.ironmine": 1, - "unlock.c1.m9": 1, - "unlock.c1.m8bonus1": 1, - "unlock.c1.m8bonus2": 1, - "unlock.milestone.steelMill": 1, - "unlock.milestone.plasticizer": 1, - "unlock.c1.mspecial2": 1, - "unlock.c1.m9bonus": 1, - "unlock.milestone.furnitureAssembler": 1, - "unlock.upgrade.plasticizer": 1, - "unlock.upgrade.furnitureassembler": 1, - "unlock.upgrade.coalpower": 1, - "unlock.upgrade.steelmill": 1, - "unlock.c1.m10a1": 1, - "unlock.c1.m10b1": 1, - "unlock.milestone.oilrefinery": 1, - "unlock.milestone.neonrefinery": 1, - "unlock.milestone.goodsFactory": 1, - "unlock.milestone.hardmold": 1, - "unlock.milestone.electronicsFab": 1, - "unlock.c1.m11": 1, - "unlock.c1.m10bonus": 1, - "unlock.milestone.incinerator": 1, - "unlock.milestone.oreGassifier": 1, - "unlock.c1.mspecial3": 1, - "unlock.c1.m11bonus1": 1, - "unlock.c1.m11bonus2": 1, - "unlock.placeable.electronicsfab": 1, - "unlock.upgrade.goodsfactory": 1, - "unlock.c1.m12": 1, - "unlock.milestone.tooldie": 1, - "unlock.c1.m12bonus": 1, - "unlock.c1.m13": 1, - "unlock.upgrade.tooldie": 1, - "unlock.upgrade.oilrefinery": 1, - "unlock.c1.m10a2": 1, - "unlock.milestone.Electric": 1, - "unlock.c1.m13bonus1": 1, - "unlock.c1.m13bonus2": 1, - "unlock.c1.m14": 1, - "unlock.milestone.hullyard": 1, - "unlock.milestone.MaxHeight": 1, - "unlock.HybridEngine": 1, - "unlock.upgrade.neonrefinery": 1, - "unlock.upgrade.electronicsfab": 1, - "unlock.upgrade.hullyard": 1, - "unlock.upgrade.incinerator": 1, - "unlock.upgrade.oregassifier": 1, - "unlock.upgrade.hardmold": 1, - "unlock.milestone.luxuryAssembler": 1, - "unlock.c1.mspecial4": 1, - "unlock.c1.m14bonus": 1, - "unlock.milestone.rescue": 1, - "unlock.c1.m15": 1, - "unlock.milestone.glassSmelter": 1, - "unlock.milestone.signworks": 1, - "unlock.c1.m16": 1, - "unlock.c1.m15bonus1": 1, - "unlock.c1.m15bonus2": 1, - "unlock.milestone.chipfab": 1, - "unlock.upgrade.glasssmelter": 1, - "unlock.stationSpeed": 1, - "unlock.c1.m17a": 1, - "unlock.c1.m17b": 1, - "unlock.c1.m16bonus": 1, - "unlock.milestone.tubePlant": 1, - "unlock.milestone.diesel": 1, - "unlock.milestone.motor": 1, - "unlock.upgrade.motorAssembly": 1, - "unlock.milestone.heavyworks": 1, - "unlock.milestone.framer": 1, - "unlock.c1.m18a": 1, - "unlock.c1.m18b": 1, - "unlock.c1.m17bonus": 1, - "placeholder": 1, - "unlock.c1.m4bonus": 1, - "unlock.c1.m5": 1, - "unlock.c1.m4bonus2": 1, - "unlock.DieselEngine": 1, - "unlock.upgrade.signworks": 1, - "unlock.upgrade.chipfab": 1, - "unlock.RescueEngine": 1, - "unlock.upgrade.tubeplant": 1, - "unlock.upgrade.luxuryassembler": 1, - "unlock.upgrade.framer": 1, - "unlock.upgrade.heavyworks": 1, - "unlock.milestone.armory": 1, - "unlock.milestone.boosterplant": 1, - "unlock.milestone.rocketfactory": 1, - "unlock.milestone.Bullet": 1, - "unlock.c1.m19a": 1, - "unlock.c1.m19b": 1, - "unlock.c1.m18bonus": 1, - "unlock.BulletEngine": 1, - "unlock.upgrade.rocketfactory": 1 - } + } + ], + "historicCounts": [ + { + "type": "MapProperty", + "name_ints": { + "unlock.c1.t2": 1, + "unlock.c1.t3": 1, + "unlock.c1.m1": 1, + "unlock.c1.m2": 1, + "unlock.milestone.oilwell": 1, + "unlock.upgrade.oilpower": 1, + "unlock.milestone.Workhorse": 1, + "unlock.c1.m2bonus": 1, + "unlock.c1.m3": 1, + "unlock.CustomEngine": 1, + "unlock.placeable.geothermal": 1, + "unlock.upgrade.geothermal": 1, + "unlock.milestone.oilPower": 1, + "unlock.c1.m4": 1, + "unlock.milestone.coppermine": 1, + "unlock.milestone.coiler": 1, + "unlock.milestone.limestonemine": 1, + "unlock.milestone.concreteYard": 1, + "unlock.upgrade.concreteyard": 1, + "unlock.upgrade.coiler": 1, + "unlock.c1.mspecial1": 1, + "unlock.milestone.autoBranching": 1, + "unlock.c1.m6": 1, + "unlock.milestone.MediumHeight": 1, + "unlock.c1.m7": 1, + "unlock.milestone.saltsifter": 1, + "unlock.milestone.coalMine": 1, + "unlock.upgrade.saltsifter": 1, + "unlock.c1.m8": 1, + "unlock.milestone.coalpower": 1, + "unlock.milestone.Industrial": 1, + "unlock.milestone.ironmine": 1, + "unlock.c1.m9": 1, + "unlock.c1.m8bonus1": 1, + "unlock.c1.m8bonus2": 1, + "unlock.milestone.steelMill": 1, + "unlock.milestone.plasticizer": 1, + "unlock.c1.mspecial2": 1, + "unlock.c1.m9bonus": 1, + "unlock.milestone.furnitureAssembler": 1, + "unlock.upgrade.plasticizer": 1, + "unlock.upgrade.furnitureassembler": 1, + "unlock.upgrade.coalpower": 1, + "unlock.upgrade.steelmill": 1, + "unlock.c1.m10a1": 1, + "unlock.c1.m10b1": 1, + "unlock.milestone.oilrefinery": 1, + "unlock.milestone.neonrefinery": 1, + "unlock.milestone.goodsFactory": 1, + "unlock.milestone.hardmold": 1, + "unlock.milestone.electronicsFab": 1, + "unlock.c1.m11": 1, + "unlock.c1.m10bonus": 1, + "unlock.milestone.incinerator": 1, + "unlock.milestone.oreGassifier": 1, + "unlock.c1.mspecial3": 1, + "unlock.c1.m11bonus1": 1, + "unlock.c1.m11bonus2": 1, + "unlock.placeable.electronicsfab": 1, + "unlock.upgrade.goodsfactory": 1, + "unlock.c1.m12": 1, + "unlock.milestone.tooldie": 1, + "unlock.c1.m12bonus": 1, + "unlock.c1.m13": 1, + "unlock.upgrade.tooldie": 1, + "unlock.upgrade.oilrefinery": 1, + "unlock.c1.m10a2": 1, + "unlock.milestone.Electric": 1, + "unlock.c1.m13bonus1": 1, + "unlock.c1.m13bonus2": 1, + "unlock.c1.m14": 1, + "unlock.milestone.hullyard": 1, + "unlock.milestone.MaxHeight": 1, + "unlock.HybridEngine": 1, + "unlock.upgrade.neonrefinery": 1, + "unlock.upgrade.electronicsfab": 1, + "unlock.upgrade.hullyard": 1, + "unlock.upgrade.incinerator": 1, + "unlock.upgrade.oregassifier": 1, + "unlock.upgrade.hardmold": 1, + "unlock.milestone.luxuryAssembler": 1, + "unlock.c1.mspecial4": 1, + "unlock.c1.m14bonus": 1, + "unlock.milestone.rescue": 1, + "unlock.c1.m15": 1, + "unlock.milestone.glassSmelter": 1, + "unlock.milestone.signworks": 1, + "unlock.c1.m16": 1, + "unlock.c1.m15bonus1": 1, + "unlock.c1.m15bonus2": 1, + "unlock.milestone.chipfab": 1, + "unlock.upgrade.glasssmelter": 1, + "unlock.stationSpeed": 1, + "unlock.c1.m17a": 1, + "unlock.c1.m17b": 1, + "unlock.c1.m16bonus": 1, + "unlock.milestone.tubePlant": 1, + "unlock.milestone.diesel": 1, + "unlock.milestone.motor": 1, + "unlock.upgrade.motorAssembly": 1, + "unlock.milestone.heavyworks": 1, + "unlock.milestone.framer": 1, + "unlock.c1.m18a": 1, + "unlock.c1.m18b": 1, + "unlock.c1.m17bonus": 1, + "placeholder": 1, + "unlock.c1.m4bonus": 1, + "unlock.c1.m5": 1, + "unlock.c1.m4bonus2": 1, + "unlock.DieselEngine": 1, + "unlock.upgrade.signworks": 1, + "unlock.upgrade.chipfab": 1, + "unlock.RescueEngine": 1, + "unlock.upgrade.tubeplant": 1, + "unlock.upgrade.luxuryassembler": 1, + "unlock.upgrade.framer": 1, + "unlock.upgrade.heavyworks": 1, + "unlock.milestone.armory": 1, + "unlock.milestone.boosterplant": 1, + "unlock.milestone.rocketfactory": 1, + "unlock.milestone.Bullet": 1, + "unlock.c1.m19a": 1, + "unlock.c1.m19b": 1, + "unlock.c1.m18bonus": 1, + "unlock.BulletEngine": 1, + "unlock.upgrade.rocketfactory": 1 } - ] - } + } + ] } }, "achievementHistoryScope": { "type": "StructProperty", + "type_name": "MetaMetricStorageScope", "CustomStruct": { - "type_name": "MetaMetricStorageScope", - "properties": { - "metrics": [ - { - "type": "MapProperty", - "value_type": "StructProperty", - "name_props": { - "Profit": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 737 - } - } - ] + "metrics": [ + { + "type": "MapProperty", + "value_type": "StructProperty", + "name_props": { + "Profit": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 737 + } } - } - }, - "export.rate": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "Energy": 0, - "Mainframes": 0 - } - } - ] + ] + } + }, + "export.rate": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "Energy": 0, + "Mainframes": 0 + } } - } - }, - "produce": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "Energy": 1043, - "CrudeOil": 1047 - } - } - ] + ] + } + }, + "produce": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "Energy": 1043, + "CrudeOil": 1047 + } } - } - }, - "cycle": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "placeable.geothermal": 70 - } - } - ] + ] + } + }, + "cycle": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "placeable.geothermal": 70 + } } - } - }, - "build.track": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 8341 - } - } - ] + ] + } + }, + "build.track": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 8341 + } } - } - }, - "build.branch": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 1848 - } - } - ] + ] + } + }, + "build.branch": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 1848 + } } - } - }, - "build.train": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 1120 - } - } - ] + ] + } + }, + "build.train": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 1120 + } } - } - }, - "arrive.length": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 13 - } - } - ] + ] + } + }, + "arrive.length": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 13 + } } - } - }, - "arrive.freight": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "Water": 2067, - "Steel": 2059 - } - } - ] + ] + } + }, + "arrive.freight": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "Water": 2067, + "Steel": 2059 + } } - } - }, - "salvage.track": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 1128 - } - } - ] + ] + } + }, + "salvage.track": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 1128 + } } - } - }, - "salvage.branch": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 59 - } - } - ] + ] + } + }, + "salvage.branch": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 59 + } } - } - }, - "upgrade": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 268, - "placeable.waterpump": 32 - } - } - ] + ] + } + }, + "upgrade": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 268, + "placeable.waterpump": 32 + } } - } - }, - "Path": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "None": 61 - } - } - ] + ] + } + }, + "Path": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "None": 61 + } } - } - }, - "mission": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "82A9B46F42BF6C5555D608A6619B35E6": 2 - } - } - ] + ] + } + }, + "mission": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "82A9B46F42BF6C5555D608A6619B35E6": 2 + } } - } - }, - "maxupgrade": { - "type": "StructProperty", - "CustomStruct": { - "type_name": "this type hint is unused", - "properties": { - "valueByFilter": [ - { - "type": "MapProperty", - "name_ints": { - "city": 2 - } - } - ] + ] + } + }, + "maxupgrade": { + "type": "StructPropertyValue", + "CustomStruct": { + "valueByFilter": [ + { + "type": "MapProperty", + "name_ints": { + "city": 2 + } } - } + ] } } } - ] - } + } + ] } } } diff --git a/tests/common/regression.rs b/tests/common/regression.rs index 1f94031..3825fb1 100644 --- a/tests/common/regression.rs +++ b/tests/common/regression.rs @@ -62,6 +62,7 @@ pub const REGRESSION_01_JSON: &str = r#"{ "properties": { "Thing": { "type": "StructProperty", + "type_name": "Guid", "Guid": "D49982B3-DF3D-D549-B4AE-57C71D5838E4" } } diff --git a/tests/common/saveslot3.rs b/tests/common/saveslot3.rs index 2339d73..7247169 100644 --- a/tests/common/saveslot3.rs +++ b/tests/common/saveslot3.rs @@ -274,9 +274,13 @@ pub(crate) fn expected() -> GvasFile { properties: HashableIndexMap::from([ ( String::from("LastSaveTime"), - Property::from(StructProperty::from(DateTime { - ticks: 638160761644140000, - })), + Property::from(StructProperty { + type_name: String::from("DateTime"), + guid: Guid::default(), + value: StructPropertyValue::from(DateTime { + ticks: 638160761644140000, + }), + }), ), ( String::from("PlayerClass"), @@ -294,97 +298,77 @@ pub(crate) fn expected() -> GvasFile { HashableIndexMap::from([ ( Property::from(NameProperty::from("unlock.welcomescreen.seen")), - Property::from(StructProperty::new( - Guid::from(0), - StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AsFloat"), - vec![Property::from(FloatProperty::new(0f32))], - ), - ( - String::from("AsString"), - vec![Property::from(StrProperty::new(None))], - ), - ]), - }, + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AsFloat"), + vec![Property::from(FloatProperty::new(0f32))], + ), + ( + String::from("AsString"), + vec![Property::from(StrProperty::new(None))], + ), + ]), )), ), ( Property::from(NameProperty::from("game.tutorial.finished")), - Property::from(StructProperty::new( - Guid::from(0), - StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AsFloat"), - vec![Property::from(FloatProperty::new(1f32))], - ), - ( - String::from("AsString"), - vec![Property::from(StrProperty::new(None))], - ), - ]), - }, + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AsFloat"), + vec![Property::from(FloatProperty::new(1f32))], + ), + ( + String::from("AsString"), + vec![Property::from(StrProperty::new(None))], + ), + ]), )), ), ( Property::from(NameProperty::from("game.tutorial.skipped")), - Property::from(StructProperty::new( - Guid::from(0), - StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AsFloat"), - vec![Property::from(FloatProperty::new(1f32))], - ), - ( - String::from("AsString"), - vec![Property::from(StrProperty::new(None))], - ), - ]), - }, + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AsFloat"), + vec![Property::from(FloatProperty::new(1f32))], + ), + ( + String::from("AsString"), + vec![Property::from(StrProperty::new(None))], + ), + ]), )), ), ( Property::from(NameProperty::from("dialogs.messages.seen.Rumiko.0.50")), - Property::from(StructProperty::new( - Guid::from(0), - StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AsFloat"), - vec![Property::from(FloatProperty::new(1f32))], - ), - ( - String::from("AsString"), - vec![Property::from(StrProperty::new(None))], - ), - ]), - }, + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AsFloat"), + vec![Property::from(FloatProperty::new(1f32))], + ), + ( + String::from("AsString"), + vec![Property::from(StrProperty::new(None))], + ), + ]), )), ), ( Property::from(NameProperty::from("codex.Rumiko")), - Property::from(StructProperty::new( - Guid::from(0), - StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AsFloat"), - vec![Property::from(FloatProperty::new(1f32))], - ), - ( - String::from("AsString"), - vec![Property::from(StrProperty::new(None))], - ), - ]), - }, + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AsFloat"), + vec![Property::from(FloatProperty::new(1f32))], + ), + ( + String::from("AsString"), + vec![Property::from(StrProperty::new(None))], + ), + ]), )), ), ]), @@ -398,65 +382,55 @@ pub(crate) fn expected() -> GvasFile { allocation_flags: 0, value: HashableIndexMap::from([ ( - Property::from(StructProperty { - guid: Guid::from(0), - value: StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AttributeName"), - vec![Property::from(StrProperty::from( - "Currency_Blueprints", - ))], - ), - ( - String::from("Attribute"), - vec![Property::from(FieldPathProperty::new( - FieldPath::new( - Vec::from([String::from( - "Currency_Blueprints", - )]), - String::from("/Script/CD.CDPlayerAttributeSet"), - ), - ))], - ), - ( - String::from("AttributeOwner"), - vec![Property::from(ObjectProperty::from("None"))], - ), - ]), - }, - }), + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AttributeName"), + vec![Property::from(StrProperty::from( + "Currency_Blueprints", + ))], + ), + ( + String::from("Attribute"), + vec![Property::from(FieldPathProperty::new( + FieldPath::new( + Vec::from([String::from("Currency_Blueprints")]), + String::from("/Script/CD.CDPlayerAttributeSet"), + ), + ))], + ), + ( + String::from("AttributeOwner"), + vec![Property::from(ObjectProperty::from("None"))], + ), + ]), + )), Property::from(FloatProperty::new(0f32)), ), ( - Property::from(StructProperty { - guid: Guid::from(0), - value: StructPropertyValue::CustomStruct { - type_name: String::from("Struct"), - properties: HashableIndexMap::from([ - ( - String::from("AttributeName"), - vec![Property::from(StrProperty::from( - "Currency_Electrum", - ))], - ), - ( - String::from("Attribute"), - vec![Property::from(FieldPathProperty::new( - FieldPath::new( - Vec::from([String::from("Currency_Electrum")]), - String::from("/Script/CD.CDPlayerAttributeSet"), - ), - ))], - ), - ( - String::from("AttributeOwner"), - vec![Property::from(ObjectProperty::from("None"))], - ), - ]), - }, - }), + Property::from(StructPropertyValue::CustomStruct( + HashableIndexMap::from([ + ( + String::from("AttributeName"), + vec![Property::from(StrProperty::from( + "Currency_Electrum", + ))], + ), + ( + String::from("Attribute"), + vec![Property::from(FieldPathProperty::new( + FieldPath::new( + Vec::from([String::from("Currency_Electrum")]), + String::from("/Script/CD.CDPlayerAttributeSet"), + ), + ))], + ), + ( + String::from("AttributeOwner"), + vec![Property::from(ObjectProperty::from("None"))], + ), + ]), + )), Property::from(FloatProperty::new(0f32)), ), ]), @@ -545,6 +519,7 @@ pub(crate) const SAVESLOT_03_JSON: &str = r#"{ "properties": { "LastSaveTime": { "type": "StructProperty", + "type_name": "DateTime", "DateTime": { "ticks": 638160761644140000 } @@ -562,98 +537,83 @@ pub(crate) const SAVESLOT_03_JSON: &str = r#"{ "value_type": "StructProperty", "name_props": { "unlock.welcomescreen.seen": { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AsFloat": [ - { - "type": "FloatProperty", - "value": 0.0 - } - ], - "AsString": [ - { - "type": "StrProperty" - } - ] - } + "AsFloat": [ + { + "type": "FloatProperty", + "value": 0.0 + } + ], + "AsString": [ + { + "type": "StrProperty" + } + ] } }, "game.tutorial.finished": { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AsFloat": [ - { - "type": "FloatProperty", - "value": 1.0 - } - ], - "AsString": [ - { - "type": "StrProperty" - } - ] - } + "AsFloat": [ + { + "type": "FloatProperty", + "value": 1.0 + } + ], + "AsString": [ + { + "type": "StrProperty" + } + ] } }, "game.tutorial.skipped": { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AsFloat": [ - { - "type": "FloatProperty", - "value": 1.0 - } - ], - "AsString": [ - { - "type": "StrProperty" - } - ] - } + "AsFloat": [ + { + "type": "FloatProperty", + "value": 1.0 + } + ], + "AsString": [ + { + "type": "StrProperty" + } + ] } }, "dialogs.messages.seen.Rumiko.0.50": { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AsFloat": [ - { - "type": "FloatProperty", - "value": 1.0 - } - ], - "AsString": [ - { - "type": "StrProperty" - } - ] - } + "AsFloat": [ + { + "type": "FloatProperty", + "value": 1.0 + } + ], + "AsString": [ + { + "type": "StrProperty" + } + ] } }, "codex.Rumiko": { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AsFloat": [ - { - "type": "FloatProperty", - "value": 1.0 - } - ], - "AsString": [ - { - "type": "StrProperty" - } - ] - } + "AsFloat": [ + { + "type": "FloatProperty", + "value": 1.0 + } + ], + "AsString": [ + { + "type": "StrProperty" + } + ] } } } @@ -666,34 +626,31 @@ pub(crate) const SAVESLOT_03_JSON: &str = r#"{ "value": [ [ { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AttributeName": [ - { - "type": "StrProperty", - "value": "Currency_Blueprints" - } - ], - "Attribute": [ - { - "type": "FieldPathProperty", - "value": { - "path": [ - "Currency_Blueprints" - ], - "resolved_owner": "/Script/CD.CDPlayerAttributeSet" - } - } - ], - "AttributeOwner": [ - { - "type": "ObjectProperty", - "value": "None" + "AttributeName": [ + { + "type": "StrProperty", + "value": "Currency_Blueprints" + } + ], + "Attribute": [ + { + "type": "FieldPathProperty", + "value": { + "path": [ + "Currency_Blueprints" + ], + "resolved_owner": "/Script/CD.CDPlayerAttributeSet" } - ] - } + } + ], + "AttributeOwner": [ + { + "type": "ObjectProperty", + "value": "None" + } + ] } }, { @@ -703,34 +660,31 @@ pub(crate) const SAVESLOT_03_JSON: &str = r#"{ ], [ { - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "Struct", - "properties": { - "AttributeName": [ - { - "type": "StrProperty", - "value": "Currency_Electrum" - } - ], - "Attribute": [ - { - "type": "FieldPathProperty", - "value": { - "path": [ - "Currency_Electrum" - ], - "resolved_owner": "/Script/CD.CDPlayerAttributeSet" - } - } - ], - "AttributeOwner": [ - { - "type": "ObjectProperty", - "value": "None" + "AttributeName": [ + { + "type": "StrProperty", + "value": "Currency_Electrum" + } + ], + "Attribute": [ + { + "type": "FieldPathProperty", + "value": { + "path": [ + "Currency_Electrum" + ], + "resolved_owner": "/Script/CD.CDPlayerAttributeSet" } - ] - } + } + ], + "AttributeOwner": [ + { + "type": "ObjectProperty", + "value": "None" + } + ] } }, { diff --git a/tests/common/slot1.rs b/tests/common/slot1.rs index dc9109a..5fd429a 100644 --- a/tests/common/slot1.rs +++ b/tests/common/slot1.rs @@ -315,21 +315,20 @@ pub(crate) fn expected() -> GvasFile { ( String::from("struct_property"), Property::from(StructProperty { + type_name: String::from("CustomStruct"), guid: Guid::default(), - value: StructPropertyValue::CustomStruct { - type_name: String::from("CustomStruct"), - properties: HashableIndexMap::from([( - String::from("test_field"), - vec![Property::from(UInt64Property::new(12345u64))], - )]), - }, + value: StructPropertyValue::CustomStruct(HashableIndexMap::from([( + String::from("test_field"), + vec![Property::from(UInt64Property::new(12345u64))], + )])), }), ), ( String::from("date_time_property"), Property::from(StructProperty { + type_name: String::from("DateTime"), guid: Guid::default(), - value: StructPropertyValue::DateTime(DateTime { + value: StructPropertyValue::from(DateTime { ticks: 637864237380020000, }), }), @@ -341,26 +340,14 @@ pub(crate) fn expected() -> GvasFile { type_name: String::from("CustomStruct"), guid: Guid::default(), structs: vec![ - StructProperty { - guid: Guid::default(), - value: StructPropertyValue::CustomStruct { - type_name: String::from("CustomStruct"), - properties: HashableIndexMap::from([( - String::from("test_field"), - vec![Property::from(UInt64Property::new(10u64))], - )]), - }, - }, - StructProperty { - guid: Guid::default(), - value: StructPropertyValue::CustomStruct { - type_name: String::from("CustomStruct"), - properties: HashableIndexMap::from([( - String::from("test_field"), - vec![Property::from(UInt64Property::new(10u64))], - )]), - }, - }, + StructPropertyValue::CustomStruct(HashableIndexMap::from([( + String::from("test_field"), + vec![Property::from(UInt64Property::new(10u64))], + )])), + StructPropertyValue::CustomStruct(HashableIndexMap::from([( + String::from("test_field"), + vec![Property::from(UInt64Property::new(10u64))], + )])), ], }), ), @@ -383,3 +370,189 @@ pub(crate) fn expected() -> GvasFile { ]), } } + +pub const SLOT1_JSON: &str = r#"{ + "header": { + "type": "Version2", + "package_file_version": 522, + "engine_version": { + "major": 4, + "minor": 27, + "patch": 2, + "change_list": 18319896, + "branch": "++UE4+Release-4.27" + }, + "custom_version_format": 3, + "custom_versions": { + "22D5549C-BE4F-26A8-4607-2194D082B461": 43, + "E432D8B0-0D4F-891F-B77E-CFACA24AFD36": 10, + "2843C6E1-534D-2CA2-868E-6CA38CBD1764": 0, + "3CC15E37-FB48-E406-F084-00B57E712A26": 4, + "ED68B0E4-E942-94F4-0BDA-31A241BB462E": 40, + "3F74FCCF-8044-B043-DF14-919373201D17": 37, + "B5492BB0-E944-20BB-B732-04A36003E452": 3, + "5C10E4A4-B549-A159-C440-C5A7EEDF7E54": 0, + "C931C839-DC47-E65A-179C-449A7C8E1C3E": 0, + "331BF078-984F-EAEB-EA84-B4B9A25AB9CC": 14, + "0F383166-E043-4D2D-27CF-09805AA95669": 0, + "9F8BF812-FC4A-7588-0CD9-7CA629BD3A38": 45, + "4CE75A7B-104C-70D2-9857-58A95A2A210B": 13, + "186929D7-DD4B-D61D-A864-E29D8438C13C": 3, + "7852A1C2-FE4A-E7BF-FF90-176C55F71D53": 1, + "D4A3AC6E-C14C-EC40-ED8B-86B7C58F4209": 3, + "DD75E529-2746-A3E0-76D2-109DEADC2C23": 17, + "5DA643AF-4749-D37F-8E3E-739805BBC1D9": 15, + "EC6C266B-8F4B-C71E-D9E4-0BA307FC4209": 1, + "613DF70D-EA47-3FA2-E989-27B79A49410C": 1, + "86181D60-844F-64AC-DED3-16AAD6C7EA0D": 47, + "686308E7-584C-236B-701B-3984915E2616": 1, + "D6BCFF9D-5801-4F49-8212-21E288A8923C": 10, + "ACD0AEF2-6F41-FE9A-7FAA-6486FCD626FA": 1, + "0B1F4F17-A545-C6B4-E82E-3FB17D91FBD0": 10, + "834AF935-6C40-58E2-F509-18A37C241096": 41, + "6EC18FB6-E242-1B8B-5C21-53B4FE448805": 1, + "0685E1B2-C2CF-7342-BBF4-4EA507BA8B75": 1, + "3689F564-BA42-1BFD-8972-96BA4EFAD0D5": 1, + "27D80E6F-9548-09A6-8D99-919CA40E1890": 2, + "E79E7F71-3A49-B0E9-3291-B3880781381B": 8, + "50326854-AF48-9980-9698-C88BB7F9ADFB": 0, + "B3DC7D8E-BB47-DA80-A246-D39FF64D9893": 1, + "CDB08ACB-DE4B-8CE7-9313-62A862EFE914": 0, + "965196AB-FC08-D845-8D22-D7B79E56AD78": 1, + "0EB75099-174E-1AB4-0DFA-CCBBD67F8157": 1, + "F20A68FB-A34B-EF59-B519-A8BA3D44C873": 2, + "9186E0AF-5249-0D3A-3B67-73B61E2DF27C": 2, + "BDFDB52E-104D-AC01-8FF3-3681DAA59333": 5, + "4F359D50-2F49-E6F6-B285-49A71C633C07": 0, + "EAB762A4-3A4E-99F4-1FEC-C199B2E12482": 4, + "194D0C43-7049-5471-699B-6987E5B090DF": 15, + "BD32FEAA-144C-9553-255E-6AB6DDD13210": 1, + "8EE1AF23-584E-E14C-52C2-618DB7BE53B9": 11, + "40EB564A-DC11-F510-7E34-D392E76AC9B2": 2, + "004A8AD7-9746-58E8-B519-A8BAB4467D48": 18, + "86F87955-1F4C-3A93-7B08-BA832FB96163": 2, + "52BE2F61-0B40-53DA-914F-0D917C85B19F": 1, + "367A23A4-C941-EACA-F818-A28FF31B6858": 4, + "753F4E80-494B-8870-068C-D6A4DCB67E3C": 5, + "2923A576-B545-2309-41D8-AE98D86A2FCF": 5, + "0769BC5F-AE40-C855-84F1-678E3FF1FF5E": 1, + "FA7AF5FC-8342-7650-58E6-A9B9322DA0FF": 68, + "F37ABB24-834F-4656-C22D-2F1FFF96AD49": 5, + "ED0A3111-614D-552E-A39A-67AF2C08A1C5": 17, + "4E7CE782-A543-2333-C513-6BB4F30D3197": 0, + "12E426FB-4D4B-151F-0A55-7293702F1D96": 3 + }, + "save_game_class_name": "/Script/UE4SaveFile.TestSaveGame" + }, + "properties": { + "u8_test": { + "type": "ByteProperty", + "name": "None", + "Byte": 129 + }, + "i8_test": { + "type": "Int8Property", + "value": -123 + }, + "ushort_test": { + "type": "UInt16Property", + "value": 65530 + }, + "short_test": { + "type": "Int16Property", + "value": -32764 + }, + "uint32_test": { + "type": "UInt32Property", + "value": 4294967294 + }, + "int32_test": { + "type": "IntProperty", + "value": -2147483647 + }, + "ulong_test": { + "type": "UInt64Property", + "value": 18446744073709551614 + }, + "long_test": { + "type": "Int64Property", + "value": -9223372036854775807 + }, + "f_property": { + "type": "FloatProperty", + "value": 3.14159 + }, + "d_property": { + "type": "DoubleProperty", + "value": 3.14159265358979 + }, + "str_property": { + "type": "StrProperty", + "value": "Hello world" + }, + "struct_property": { + "type": "StructProperty", + "type_name": "CustomStruct", + "CustomStruct": { + "test_field": [ + { + "type": "UInt64Property", + "value": 12345 + } + ] + } + }, + "date_time_property": { + "type": "StructProperty", + "type_name": "DateTime", + "DateTime": { + "ticks": 637864237380020000 + } + }, + "array_of_structs": { + "type": "ArrayProperty", + "field_name": "array_of_structs", + "type_name": "CustomStruct", + "structs": [ + { + "CustomStruct": { + "test_field": [ + { + "type": "UInt64Property", + "value": 10 + } + ] + } + }, + { + "CustomStruct": { + "test_field": [ + { + "type": "UInt64Property", + "value": 10 + } + ] + } + } + ] + }, + "array_of_ints": { + "type": "ArrayProperty", + "ints": [ + 12, + 12, + 12, + 12, + 12 + ] + }, + "array_of_strings": { + "type": "ArrayProperty", + "strings": [ + "Hello world from array", + "Hello world from array", + "Hello world from array" + ] + } + } +}"#; diff --git a/tests/common/vector2d.rs b/tests/common/vector2d.rs index f6fdc6f..50c2d0a 100644 --- a/tests/common/vector2d.rs +++ b/tests/common/vector2d.rs @@ -718,82 +718,79 @@ pub(crate) fn expected() -> GvasFile { String::from("AudioSettings"), Property::StructProperty(StructProperty { guid: Guid::default(), - value: StructPropertyValue::CustomStruct { - type_name: String::from("GameAudioSettings"), - properties: HashableIndexMap::from([ - ( - String::from("MasterLevel"), - vec![Property::FloatProperty(FloatProperty { - value: OrderedFloat::from(0.20348908), - })], - ), - ( - String::from("MusicLevel"), - vec![Property::FloatProperty(FloatProperty { - value: OrderedFloat::from(0.1511635), - })], - ), - ( - String::from("SFXLevel"), - vec![Property::FloatProperty(FloatProperty { - value: OrderedFloat::from(0.5436054), - })], - ), - ]), - }, + type_name: String::from("GameAudioSettings"), + value: StructPropertyValue::CustomStruct(HashableIndexMap::from([ + ( + String::from("MasterLevel"), + vec![Property::FloatProperty(FloatProperty { + value: OrderedFloat::from(0.20348908), + })], + ), + ( + String::from("MusicLevel"), + vec![Property::FloatProperty(FloatProperty { + value: OrderedFloat::from(0.1511635), + })], + ), + ( + String::from("SFXLevel"), + vec![Property::FloatProperty(FloatProperty { + value: OrderedFloat::from(0.5436054), + })], + ), + ])), }), ), ( String::from("GameSettings"), Property::StructProperty(StructProperty { guid: Guid::default(), - value: StructPropertyValue::CustomStruct { - type_name: String::from("GameSettings"), - properties: HashableIndexMap::from([ - ( - String::from("CurrentSaveSlot"), - vec![Property::StrProperty(StrProperty::from("SAVE2"))], - ), - ( - String::from("LoadTutorial"), - vec![Property::BoolProperty(BoolProperty::new(false))], - ), - ( - String::from("DisplayNewOrders"), - vec![Property::BoolProperty(BoolProperty::new(false))], - ), - ( - String::from("EscapeExitsTool"), - vec![Property::BoolProperty(BoolProperty::new(false))], - ), - ( - String::from("UseDarkMode"), - vec![Property::BoolProperty(BoolProperty::new(true))], - ), - ( - String::from("AnimateDayCycle"), - vec![Property::BoolProperty(BoolProperty::new(false))], - ), - ( - String::from("EnableTractorCollision"), - vec![Property::BoolProperty(BoolProperty::new(false))], - ), - ( - String::from("ShowInventory"), - vec![Property::BoolProperty(BoolProperty::new(true))], - ), - ( - String::from("CameraAngle"), - vec![Property::StructProperty(StructProperty { - guid: Guid::default(), - value: StructPropertyValue::Vector2D(Vector2D { - x: OrderedFloat::from(30.574748247861862), - y: OrderedFloat::from(60.42525175213814), - }), - })], - ), - ]), - }, + type_name: String::from("GameSettings"), + value: StructPropertyValue::CustomStruct(HashableIndexMap::from([ + ( + String::from("CurrentSaveSlot"), + vec![Property::StrProperty(StrProperty::from("SAVE2"))], + ), + ( + String::from("LoadTutorial"), + vec![Property::BoolProperty(BoolProperty::new(false))], + ), + ( + String::from("DisplayNewOrders"), + vec![Property::BoolProperty(BoolProperty::new(false))], + ), + ( + String::from("EscapeExitsTool"), + vec![Property::BoolProperty(BoolProperty::new(false))], + ), + ( + String::from("UseDarkMode"), + vec![Property::BoolProperty(BoolProperty::new(true))], + ), + ( + String::from("AnimateDayCycle"), + vec![Property::BoolProperty(BoolProperty::new(false))], + ), + ( + String::from("EnableTractorCollision"), + vec![Property::BoolProperty(BoolProperty::new(false))], + ), + ( + String::from("ShowInventory"), + vec![Property::BoolProperty(BoolProperty::new(true))], + ), + ( + String::from("CameraAngle"), + vec![Property::from(StructProperty { + type_name: String::from("Vector2D"), + guid: Guid::default(), + value: StructPropertyValue::Vector2D(Vector2D { + x: OrderedFloat::from(30.574748247861862), + y: OrderedFloat::from(60.42525175213814), + }), + })], + ), + ])), }), ), ( @@ -1213,93 +1210,90 @@ pub const VECTOR2D_JSON: &str = r#"{ }, "AudioSettings": { "type": "StructProperty", + "type_name": "GameAudioSettings", "CustomStruct": { - "type_name": "GameAudioSettings", - "properties": { - "MasterLevel": [ - { - "type": "FloatProperty", - "value": 0.20348908 - } - ], - "MusicLevel": [ - { - "type": "FloatProperty", - "value": 0.1511635 - } - ], - "SFXLevel": [ - { - "type": "FloatProperty", - "value": 0.5436054 - } - ] - } + "MasterLevel": [ + { + "type": "FloatProperty", + "value": 0.20348908 + } + ], + "MusicLevel": [ + { + "type": "FloatProperty", + "value": 0.1511635 + } + ], + "SFXLevel": [ + { + "type": "FloatProperty", + "value": 0.5436054 + } + ] } }, "GameSettings": { "type": "StructProperty", + "type_name": "GameSettings", "CustomStruct": { - "type_name": "GameSettings", - "properties": { - "CurrentSaveSlot": [ - { - "type": "StrProperty", - "value": "SAVE2" - } - ], - "LoadTutorial": [ - { - "type": "BoolProperty", - "value": false - } - ], - "DisplayNewOrders": [ - { - "type": "BoolProperty", - "value": false - } - ], - "EscapeExitsTool": [ - { - "type": "BoolProperty", - "value": false - } - ], - "UseDarkMode": [ - { - "type": "BoolProperty", - "value": true - } - ], - "AnimateDayCycle": [ - { - "type": "BoolProperty", - "value": false - } - ], - "EnableTractorCollision": [ - { - "type": "BoolProperty", - "value": false - } - ], - "ShowInventory": [ - { - "type": "BoolProperty", - "value": true - } - ], - "CameraAngle": [ - { - "type": "StructProperty", - "Vector2D": { - "x": 30.574748247861862, - "y": 60.42525175213814 - } + "CurrentSaveSlot": [ + { + "type": "StrProperty", + "value": "SAVE2" + } + ], + "LoadTutorial": [ + { + "type": "BoolProperty", + "value": false + } + ], + "DisplayNewOrders": [ + { + "type": "BoolProperty", + "value": false + } + ], + "EscapeExitsTool": [ + { + "type": "BoolProperty", + "value": false + } + ], + "UseDarkMode": [ + { + "type": "BoolProperty", + "value": true + } + ], + "AnimateDayCycle": [ + { + "type": "BoolProperty", + "value": false + } + ], + "EnableTractorCollision": [ + { + "type": "BoolProperty", + "value": false + } + ], + "ShowInventory": [ + { + "type": "BoolProperty", + "value": true + } + ], + "CameraAngle": [ + { + "type": "StructProperty", + "type_name": "Vector2D", + "Vector2D": { + "x": 30.574748247861862, + "y": 60.42525175213814 } - ] - } + } + ] } }, "HighScore": { diff --git a/tests/gvas_tests/test_property.rs b/tests/gvas_tests/test_property.rs index 87fcd6a..654612f 100644 --- a/tests/gvas_tests/test_property.rs +++ b/tests/gvas_tests/test_property.rs @@ -13,7 +13,7 @@ use gvas::{ map_property::MapProperty, set_property::SetProperty, str_property::StrProperty, - struct_property::StructProperty, + struct_property::{StructProperty, StructPropertyValue}, struct_types::VectorF, text_property::TextProperty, Property, PropertyOptions, PropertyTrait, @@ -96,7 +96,11 @@ test_property!( test_property!( test_struct, StructProperty, - StructProperty::from(VectorF::new(0f32, 1f32, 2f32)) + StructProperty::new( + Guid::default(), + "Vector".to_string(), + StructPropertyValue::from(VectorF::new(0f32, 1f32, 2f32)) + ) ); // ArrayProperty @@ -131,8 +135,8 @@ test_property!( Guid::from(0u128) )), vec![ - Property::from(StructProperty::from(VectorF::new(0f32, 1f32, 2f32))), - Property::from(StructProperty::from(VectorF::new(3f32, 4f32, 5f32))), + Property::from(StructPropertyValue::from(VectorF::new(0f32, 1f32, 2f32))), + Property::from(StructPropertyValue::from(VectorF::new(3f32, 4f32, 5f32))), ], ) .expect("ArrayProperty::new") diff --git a/tests/serde_tests/serde_json_template.rs b/tests/serde_tests/serde_json_template.rs index 083bd1e..8b71502 100644 --- a/tests/serde_tests/serde_json_template.rs +++ b/tests/serde_tests/serde_json_template.rs @@ -16,9 +16,10 @@ use gvas::{ object_property::ObjectProperty, set_property::SetProperty, str_property::StrProperty, - struct_property::{StructProperty, StructPropertyValue}, + struct_property::StructPropertyValue, struct_types::{ - DateTime, IntPoint, LinearColor, QuatD, QuatF, RotatorD, RotatorF, VectorD, VectorF, + DateTime, IntPoint, LinearColor, QuatD, QuatF, RotatorD, RotatorF, Timespan, VectorD, + VectorF, }, text_property::{ DateTimeStyle, FText, FTextHistory, FormatArgumentValue, NumberFormattingOptions, @@ -87,6 +88,11 @@ fn file_regression_01() { file(REGRESSION_01_PATH, regression::REGRESSION_01_JSON); } +#[test] +fn file_slot1() { + file(SLOT1_PATH, slot1::SLOT1_JSON); +} + #[test] fn file_saveslot_03() { file_with_hints( @@ -605,14 +611,8 @@ fn array_struct() { String::from("StructProperty"), Some((String::from("fn"), String::from("tn"), Guid([0x11u8; 16]))), vec![ - Property::StructProperty(StructProperty::new( - Guid([0x22u8; 16]), - StructPropertyValue::DateTime(DateTime { ticks: 0 }), - )), - Property::StructProperty(StructProperty::new( - Guid::default(), - StructPropertyValue::DateTime(DateTime { ticks: 1 }), - )), + Property::from(StructPropertyValue::from(DateTime { ticks: 0 })), + Property::from(StructPropertyValue::from(DateTime { ticks: 1 })), ], ) .expect("ArrayProperty::new"), @@ -624,7 +624,6 @@ fn array_struct() { "guid": "11111111-1111-1111-1111-111111111111", "structs": [ { - "guid": "22222222-2222-2222-2222-222222222222", "DateTime": { "ticks": 0 } @@ -1088,24 +1087,15 @@ fn map_struct_float() { 0, HashableIndexMap::from([ ( - Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::VectorF(VectorF::new(0f32, 1f32, 2f32)), - )), + Property::from(StructPropertyValue::from(VectorF::new(0f32, 1f32, 2f32))), Property::FloatProperty(FloatProperty::new(0f32)), ), ( - Property::StructProperty(StructProperty::new( - Guid([0x11u8; 16]), - StructPropertyValue::Timespan(DateTime::new(0)), - )), + Property::from(StructPropertyValue::from(Timespan::new(0))), Property::FloatProperty(FloatProperty::new(1f32)), ), ( - Property::StructProperty(StructProperty::new( - Guid([0x22u8; 16]), - StructPropertyValue::DateTime(DateTime::new(0)), - )), + Property::from(StructPropertyValue::from(DateTime::new(0))), Property::FloatProperty(FloatProperty::new(2f32)), ), ]), @@ -1118,7 +1108,7 @@ fn map_struct_float() { "value": [ [ { - "type": "StructProperty", + "type": "StructPropertyValue", "VectorF": { "x": 0.0, "y": 1.0, @@ -1132,8 +1122,7 @@ fn map_struct_float() { ], [ { - "type": "StructProperty", - "guid": "11111111-1111-1111-1111-111111111111", + "type": "StructPropertyValue", "Timespan": { "ticks": 0 } @@ -1145,8 +1134,7 @@ fn map_struct_float() { ], [ { - "type": "StructProperty", - "guid": "22222222-2222-2222-2222-222222222222", + "type": "StructPropertyValue", "DateTime": { "ticks": 0 } @@ -1388,12 +1376,9 @@ fn str_some() { #[test] fn struct_vectorf() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::VectorF(VectorF::new(0f32, 1f32, 2f32)), - )), + &Property::from(StructPropertyValue::from(VectorF::new(0f32, 1f32, 2f32))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "VectorF": { "x": 0.0, "y": 1.0, @@ -1406,12 +1391,9 @@ fn struct_vectorf() { #[test] fn struct_vectord() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::VectorD(VectorD::new(0f64, 1f64, 2f64)), - )), + &Property::from(StructPropertyValue::from(VectorD::new(0f64, 1f64, 2f64))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "VectorD": { "x": 0.0, "y": 1.0, @@ -1424,12 +1406,9 @@ fn struct_vectord() { #[test] fn struct_rotatorf() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::RotatorF(RotatorF::new(0f32, 1f32, 2f32)), - )), + &Property::from(StructPropertyValue::from(RotatorF::new(0f32, 1f32, 2f32))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "RotatorF": { "pitch": 0.0, "yaw": 1.0, @@ -1442,12 +1421,9 @@ fn struct_rotatorf() { #[test] fn struct_rotatord() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::RotatorD(RotatorD::new(0f64, 1f64, 2f64)), - )), + &Property::from(StructPropertyValue::from(RotatorD::new(0f64, 1f64, 2f64))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "RotatorD": { "pitch": 0.0, "yaw": 1.0, @@ -1460,12 +1436,11 @@ fn struct_rotatord() { #[test] fn struct_quatf() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::QuatF(QuatF::new(0f32, 1f32, 2f32, 3f32)), - )), + &Property::from(StructPropertyValue::from(QuatF::new( + 0f32, 1f32, 2f32, 3f32, + ))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "QuatF": { "x": 0.0, "y": 1.0, @@ -1479,12 +1454,11 @@ fn struct_quatf() { #[test] fn struct_quatd() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::QuatD(QuatD::new(0f64, 1f64, 2f64, 3f64)), - )), + &Property::from(StructPropertyValue::from(QuatD::new( + 0f64, 1f64, 2f64, 3f64, + ))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "QuatD": { "x": 0.0, "y": 1.0, @@ -1498,12 +1472,11 @@ fn struct_quatd() { #[test] fn struct_datetime() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid([0u8; 16]), - StructPropertyValue::QuatD(QuatD::new(0f64, 1f64, 2f64, 3f64)), - )), + &Property::from(StructPropertyValue::from(QuatD::new( + 0f64, 1f64, 2f64, 3f64, + ))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "QuatD": { "x": 0.0, "y": 1.0, @@ -1517,12 +1490,11 @@ fn struct_datetime() { #[test] fn struct_linearcolor() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid::default(), - StructPropertyValue::LinearColor(LinearColor::new(0.0, 1.0, 2.0, 3.0)), - )), + &Property::from(StructPropertyValue::from(LinearColor::new( + 0.0, 1.0, 2.0, 3.0, + ))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "LinearColor": { "r": 0.0, "g": 1.0, @@ -1536,12 +1508,9 @@ fn struct_linearcolor() { #[test] fn struct_intpoint() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid::default(), - StructPropertyValue::IntPoint(IntPoint::new(0, 1)), - )), + &Property::from(StructPropertyValue::from(IntPoint::new(0, 1))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "IntPoint": { "x": 0, "y": 1 @@ -1553,28 +1522,21 @@ fn struct_intpoint() { #[test] fn struct_custom() { serde_json( - &Property::StructProperty(StructProperty::new( - Guid::default(), - StructPropertyValue::CustomStruct { - type_name: String::from("custom name"), - properties: HashableIndexMap::from([( - String::from("key"), - vec![Property::from(StrProperty::from("value"))], - )]), - }, - )), + &Property::from(StructPropertyValue::CustomStruct(HashableIndexMap::from([ + ( + String::from("key"), + vec![Property::from(StrProperty::from("value"))], + ), + ]))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "custom name", - "properties": { - "key": [ - { - "type": "StrProperty", - "value": "value" - } - ] - } + "key": [ + { + "type": "StrProperty", + "value": "value" + } + ] } }"#, ) @@ -1583,42 +1545,35 @@ fn struct_custom() { #[test] fn struct_array_index() { serde_json( - &Property::StructProperty(StructProperty { - guid: Guid::default(), - value: StructPropertyValue::CustomStruct { - type_name: String::from("TowersTrackedQuests"), - properties: HashableIndexMap::from([( - String::from("TrackedQuestsNames"), - vec![ - Property::NameProperty(NameProperty { - array_index: 0, - value: Some(String::from("QU91_InvestigateTower_B2")), - }), - Property::NameProperty(NameProperty { - array_index: 1, - value: Some(String::from("QU91_InvestigateTower_B2")), - }), - ], - )]), - }, - }), + &Property::from(StructPropertyValue::CustomStruct(HashableIndexMap::from([ + ( + String::from("TrackedQuestsNames"), + vec![ + Property::NameProperty(NameProperty { + array_index: 0, + value: Some(String::from("QU91_InvestigateTower_B2")), + }), + Property::NameProperty(NameProperty { + array_index: 1, + value: Some(String::from("QU91_InvestigateTower_B2")), + }), + ], + ), + ]))), r#"{ - "type": "StructProperty", + "type": "StructPropertyValue", "CustomStruct": { - "type_name": "TowersTrackedQuests", - "properties": { - "TrackedQuestsNames": [ - { - "type": "NameProperty", - "value": "QU91_InvestigateTower_B2" - }, - { - "type": "NameProperty", - "array_index": 1, - "value": "QU91_InvestigateTower_B2" - } - ] - } + "TrackedQuestsNames": [ + { + "type": "NameProperty", + "value": "QU91_InvestigateTower_B2" + }, + { + "type": "NameProperty", + "array_index": 1, + "value": "QU91_InvestigateTower_B2" + } + ] } }"#, )