diff --git a/.gitignore b/.gitignore index cd8f466..797a63e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ Cargo.lock /.idea /parsable_macro_derive/target /tests/test-*.obj -/tests/GameSave2.Techblox +/tests/*.out.Techblox diff --git a/src/techblox/blocks/engine.rs b/src/techblox/blocks/engine.rs new file mode 100644 index 0000000..176750f --- /dev/null +++ b/src/techblox/blocks/engine.rs @@ -0,0 +1,39 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, +blocks::{BlockEntity}}; +use libfj_parsable_macro_derive::*; + +/// Engine entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct EngineBlockEntity { + /// parent block entity + pub block: BlockEntity, + /// Engine tweakables component + pub tweak_component: EngineBlockTweakableComponent, +} + +impl SerializedEntityDescriptor for EngineBlockEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + 1 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.tweak_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("EngineBlockEntityDescriptor") // 1757314505 + } +} + +/// Engine settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct EngineBlockTweakableComponent { + /// Engine power (percent?) + pub power: f32, + /// Is the engine's transmission automatic? (bool) + pub automatic_gears: u32, // why is this not stored as u8 like the other bools? +} + +impl SerializedEntityComponent for EngineBlockTweakableComponent {} diff --git a/src/techblox/blocks/joint.rs b/src/techblox/blocks/joint.rs new file mode 100644 index 0000000..eb14a01 --- /dev/null +++ b/src/techblox/blocks/joint.rs @@ -0,0 +1,24 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, +blocks::{BlockEntity}}; +use libfj_parsable_macro_derive::*; + +/// Joint block entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct JointBlockEntity { + /// parent block entity + pub block: BlockEntity, +} + +impl SerializedEntityDescriptor for JointBlockEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + self.block.components() + } + + fn hash_name(&self) -> u32 { + Self::hash("JointBlockEntityDescriptorV3") // 3586818581 + } +} diff --git a/src/techblox/blocks/lookup_tables.rs b/src/techblox/blocks/lookup_tables.rs index 79ba0dc..bcb695d 100644 --- a/src/techblox/blocks/lookup_tables.rs +++ b/src/techblox/blocks/lookup_tables.rs @@ -1,16 +1,100 @@ use std::io::Read; use crate::techblox::{Parsable, SerializedEntityDescriptor}; -#[cfg(debug_assertions)] use crate::techblox::blocks::*; +const HASHNAMES: &[&str] = &[ + // Block group info entities + "BlockGroupEntityDescriptorV0", + // Block entities + "StandardBlockEntityDescriptorV4", + "BatteryEntityDescriptorV4", + "MotorEntityDescriptorV7", + "LeverEntityDescriptorV7", + "ButtonEntityDescriptorV6", + "JointBlockEntityDescriptorV3", + "ServoEntityDescriptorV7", + "PistonEntityDescriptorV6", + "DampedSpringEntityDescriptorV5", + "DampedAngularSpringEntityDescriptorV4", + "SpawnPointEntityDescriptorV6", + "BuildingSpawnPointEntityDescriptorV4", + "TriggerEntityDescriptorV6", + "PilotSeatEntityDescriptorV4", + "PilotSeatEntityDescriptorV3", + "TextBlockEntityDescriptorV4", + "PassengerSeatEntityDescriptorV4", + "PassengerSeatEntityDescriptorV3", + "LogicBlockEntityDescriptorV1", + "TyreEntityDescriptorV1", + "ObjectIDEntityDescriptorV1", + "MoverEntityDescriptorV1", + "RotatorEntityDescriptorV1", + "DamperEntityDescriptorV1", + "AdvancedDamperEntityDescriptorV1", + "CoMEntityDescriptor", + "FilterBlockEntityDescriptorV1", + "ConstrainerEntityDescriptorV1", + "NumberToTextBlockEntityDescriptorV1", + "CentreHudBlockEntityDescriptorV1", + "ObjectiveHudBlockEntityDescriptorV1", + "GameStatsHudBlockEntityDescriptorV1", + "GameOverHudBlockEntityDescriptorV1", + "TimerBlockEntityDescriptorV1", + "BitBlockEntityDescriptorV2", + "ConstantBlockEntityDescriptor", + "CounterBlockEntityDescriptorV1", + "SimpleSfxEntityDescriptorV1", + "LoopedSfxEntityDescriptorV1", + "MusicBlockEntityDescriptorV1", + "ProjectileBlockEntityDescriptorV1", + "DamagingSurfaceEntityDescriptorV1", + "DestructionManagerEntityDescriptorV1", + "ChunkDestructionBlockEntityDescriptorV1", + "ClusterDestructionBlockEntityDescriptorV1", + "PickupBlockEntityDescriptorV1", + "PointLightEntityDescriptorV1", + "SpotLightEntityDescriptorV1", + "SunLightEntityDescriptorV1", + "AmbientLightEntityDescriptorV1", + "FogEntityDescriptorV1", + "SkyEntityDescriptorV1", + "SynchronizedWireBlockEntityDescriptor", + "WheelRigEntityDescriptor", + "WheelRigSteerableEntityDescriptor", + "EngineBlockEntityDescriptor", + // Other Non-block entities (stored after blocks in game saves) + "WireEntityDescriptorMock", + "GlobalWireSettingsEntityDescriptor", + "FlyCamEntityDescriptorV0", + "CharacterCameraEntityDescriptorV1", +]; + pub fn lookup_hashname(hash: u32, data: &mut dyn Read) -> std::io::Result> { Ok(match hash { 1357220432 /*StandardBlockEntityDescriptorV4*/ => Box::new(BlockEntity::parse(data)?), + 2281299333 /*PilotSeatEntityDescriptorV4*/ => Box::new(PilotSeatEntity::parse(data)?), + 1360086092 /*PassengerSeatEntityDescriptorV4*/ => Box::new(PassengerSeatEntity::parse(data)?), + 1757314505 /*EngineBlockEntityDescriptor*/ => Box::new(EngineBlockEntity::parse(data)?), + 3586818581 /*JointBlockEntityDescriptorV3*/ => Box::new(JointBlockEntity::parse(data)?), + 3789998433 /*DampedAngularSpringEntityDescriptorV4*/ => Box::new(DampedAngularSpringEntity::parse(data)?), + 2892049599 /*DampedSpringEntityDescriptorV5*/ => Box::new(DampedSpringEntity::parse(data)?), + 1156723746 /*WheelRigEntityDescriptor*/ => Box::new(WheelRigEntity::parse(data)?), + 1864425618 /*WheelRigSteerableEntityDescriptor*/ => Box::new(WheelRigSteerableEntity::parse(data)?), + 1517625162 /*TyreEntityDescriptorV1*/ => Box::new(TyreEntity::parse(data)?), _ => { #[cfg(debug_assertions)] - println!("Unknown hash ID {}", hash); + println!("Unknown hash ID {} (missing entry for {})", hash, lookup_name_by_hash(hash).unwrap_or("")); return Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Unrecognised hash {}", hash))) } }) } + +pub fn lookup_name_by_hash(hash: u32) -> Option<&'static str> { + for name in HASHNAMES { + if crate::techblox::hashname(name) == hash { + return Some(name); + } + } + None +} diff --git a/src/techblox/blocks/mod.rs b/src/techblox/blocks/mod.rs index b0ff2a3..e1f926c 100644 --- a/src/techblox/blocks/mod.rs +++ b/src/techblox/blocks/mod.rs @@ -2,7 +2,14 @@ mod block_entity; mod common_components; +mod engine; +mod joint; mod lookup_tables; +mod pilot_seat; +mod passenger_seat; +mod spring; +mod tyre; +mod wheel_rig; mod wire_entity; pub use block_entity::{BlockEntity}; @@ -10,5 +17,13 @@ pub use common_components::{DBEntityStruct, PositionEntityStruct, ScalingEntityS SkewComponent, GridRotationStruct, SerializedGridConnectionsEntityStruct, SerializedBlockPlacementInfoStruct, SerializedCubeMaterialStruct, SerializedUniformBlockScaleEntityStruct, SerializedColourParameterEntityStruct, BlockGroupEntityComponent}; +pub use engine::{EngineBlockEntity, EngineBlockTweakableComponent}; +pub use joint::{JointBlockEntity}; +pub use pilot_seat::{PilotSeatEntity, SeatFollowCamComponent}; +pub use passenger_seat::PassengerSeatEntity; pub(crate) use lookup_tables::*; +pub use spring::{DampedAngularSpringEntity, TweakableJointDampingComponent, DampedAngularSpringROStruct, +DampedSpringEntity, DampedSpringROStruct}; +pub use tyre::{TyreEntity}; +pub use wheel_rig::{WheelRigEntity, WheelRigTweakableStruct, WheelRigSteerableEntity, WheelRigSteerableTweakableStruct}; pub use wire_entity::{SerializedWireEntity, WireSaveDataStruct, SerializedGlobalWireSettingsEntity, GlobalWireSettingsEntityStruct}; diff --git a/src/techblox/blocks/passenger_seat.rs b/src/techblox/blocks/passenger_seat.rs new file mode 100644 index 0000000..00269de --- /dev/null +++ b/src/techblox/blocks/passenger_seat.rs @@ -0,0 +1,28 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, +blocks::{BlockEntity, SeatFollowCamComponent}}; +use libfj_parsable_macro_derive::*; + +/// Passenger seat entity descriptor (V4) +#[derive(Copy, Clone, Parsable)] +pub struct PassengerSeatEntity { + /// parent block entity + pub block: BlockEntity, + /// Seat following camera component + pub cam_component: SeatFollowCamComponent, +} + +impl SerializedEntityDescriptor for PassengerSeatEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + 1 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.cam_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("PassengerSeatEntityDescriptorV4") // 1360086092 + } +} diff --git a/src/techblox/blocks/pilot_seat.rs b/src/techblox/blocks/pilot_seat.rs new file mode 100644 index 0000000..cdff457 --- /dev/null +++ b/src/techblox/blocks/pilot_seat.rs @@ -0,0 +1,36 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, blocks::BlockEntity}; +use libfj_parsable_macro_derive::*; + +/// Pilot seat entity descriptor (V4) +#[derive(Copy, Clone, Parsable)] +pub struct PilotSeatEntity { + /// parent block entity + pub block: BlockEntity, + /// Seat following camera component + pub cam_component: SeatFollowCamComponent, +} + +impl SerializedEntityDescriptor for PilotSeatEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + 1 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.cam_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("PilotSeatEntityDescriptorV4") // 2281299333 + } +} + +/// Seat settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct SeatFollowCamComponent { + /// Should the camera follow the seat? (bool) + pub follow: u8, +} + +impl SerializedEntityComponent for SeatFollowCamComponent {} diff --git a/src/techblox/blocks/spring.rs b/src/techblox/blocks/spring.rs new file mode 100644 index 0000000..e6a153f --- /dev/null +++ b/src/techblox/blocks/spring.rs @@ -0,0 +1,90 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, +blocks::{BlockEntity}}; +use libfj_parsable_macro_derive::*; + +/// Damped angular spring entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct DampedAngularSpringEntity { + /// parent block entity + pub block: BlockEntity, + /// Joint tweakables component + pub tweak_component: TweakableJointDampingComponent, + /// Spring tweakables component + pub spring_component: DampedAngularSpringROStruct, +} + +impl SerializedEntityDescriptor for DampedAngularSpringEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + 2 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.tweak_component); + c.push(&self.spring_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("DampedAngularSpringEntityDescriptorV4") // 3789998433 + } +} + +/// Damped spring entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct DampedSpringEntity { + /// parent block entity + pub block: BlockEntity, + /// Joint tweakables component + pub tweak_component: TweakableJointDampingComponent, + /// Spring tweakables component + pub spring_component: DampedSpringROStruct, +} + +impl SerializedEntityDescriptor for DampedSpringEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + 2 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.tweak_component); + c.push(&self.spring_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("DampedSpringEntityDescriptorV5") // 2892049599 + } +} + +/// Joint settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct TweakableJointDampingComponent { + /// Joint stiffness (percent?) + pub stiffness: f32, + /// Force damping (percent?) + pub damping: f32, +} + +impl SerializedEntityComponent for TweakableJointDampingComponent {} + +/// Damped angular spring settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct DampedSpringROStruct { + /// Maximum spring extension + pub max_extension: f32, +} + +impl SerializedEntityComponent for DampedSpringROStruct {} + +/// Damped angular spring settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct DampedAngularSpringROStruct { + /// Minimum sprint extension + pub joint_min: f32, + /// Maximum sprint extension + pub joint_max: f32, +} + +impl SerializedEntityComponent for DampedAngularSpringROStruct {} diff --git a/src/techblox/blocks/tyre.rs b/src/techblox/blocks/tyre.rs new file mode 100644 index 0000000..e7a179c --- /dev/null +++ b/src/techblox/blocks/tyre.rs @@ -0,0 +1,24 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, +blocks::{BlockEntity}}; +use libfj_parsable_macro_derive::*; + +/// Tire entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct TyreEntity { + /// parent block entity + pub block: BlockEntity, +} + +impl SerializedEntityDescriptor for TyreEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + self.block.components() + } + + fn hash_name(&self) -> u32 { + Self::hash("TyreEntityDescriptorV1") // 1517625162 + } +} diff --git a/src/techblox/blocks/wheel_rig.rs b/src/techblox/blocks/wheel_rig.rs new file mode 100644 index 0000000..e3f3888 --- /dev/null +++ b/src/techblox/blocks/wheel_rig.rs @@ -0,0 +1,74 @@ +use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, +blocks::{BlockEntity, TweakableJointDampingComponent}}; +use libfj_parsable_macro_derive::*; + +/// Wheel rig entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct WheelRigEntity { + /// parent block entity + pub block: BlockEntity, + /// Wheel tweakables component + pub tweak_component: WheelRigTweakableStruct, + /// Joint tweakables component + pub joint_component: TweakableJointDampingComponent, +} + +impl SerializedEntityDescriptor for WheelRigEntity { + fn serialized_components() -> u8 { + BlockEntity::serialized_components() + 2 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.tweak_component); + c.push(&self.joint_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("WheelRigEntityDescriptor") // 1156723746 + } +} + +/// Wheel rig entity descriptor +#[derive(Copy, Clone, Parsable)] +pub struct WheelRigSteerableEntity { + /// parent wheel rig entity + pub block: WheelRigEntity, + /// Steering tweakables component + pub tweak_component: WheelRigSteerableTweakableStruct, +} + +impl SerializedEntityDescriptor for WheelRigSteerableEntity { + fn serialized_components() -> u8 { + WheelRigEntity::serialized_components() + 1 + } + + fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> { + let mut c = self.block.components(); + c.push(&self.tweak_component); + return c; + } + + fn hash_name(&self) -> u32 { + Self::hash("WheelRigSteerableEntityDescriptor") // 1864425618 + } +} + +/// Wheel rig settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct WheelRigTweakableStruct { + /// Brake force (percent?) + pub braking_strength: f32, +} + +impl SerializedEntityComponent for WheelRigTweakableStruct {} + +/// Steering wheel rig settings entity component. +#[derive(Copy, Clone, Parsable)] +pub struct WheelRigSteerableTweakableStruct { + /// Wheel steering angle (max?) + pub steer_angle: f32, +} + +impl SerializedEntityComponent for WheelRigSteerableTweakableStruct {} diff --git a/src/techblox/entity_header.rs b/src/techblox/entity_header.rs index 229f193..7513d77 100644 --- a/src/techblox/entity_header.rs +++ b/src/techblox/entity_header.rs @@ -1,4 +1,4 @@ -use crate::techblox::{hashname, brute_force, Parsable}; +use crate::techblox::{hashname, brute_force, Parsable, blocks::lookup_name_by_hash}; use libfj_parsable_macro_derive::*; /// An entity's header information. @@ -24,6 +24,17 @@ impl EntityHeader { brute_force(self.hash) } + /// Lookup the name from the header's hash from a list of known entity names. + /// + /// This is much faster than guess_name() and is guaranteed to return a correct result if one exists. + /// If the hash has no known correct name, None is returned instead. + pub fn lookup_name(&self) -> Option { + if let Some(name) = lookup_name_by_hash(self.hash) { + return Some(name.to_string()); + } + None + } + /// Create an entity header using the hash of `name`. pub fn from_name(name: &str, entity_id: u32, group_id: u32, component_count: u8) -> Self { Self { diff --git a/src/techblox/gamesave.rs b/src/techblox/gamesave.rs index e2458ca..d458387 100644 --- a/src/techblox/gamesave.rs +++ b/src/techblox/gamesave.rs @@ -93,6 +93,8 @@ impl Parsable for GameSave { for _i in 0..cube_count { let header = EntityHeader::parse(data)?; let hash = header.hash; + #[cfg(debug_assertions)] + println!("Handling block {} (hash: {} id:{}/{} components: {})", cubes_h.len(), hash, header.entity_id, header.group_id, header.component_count); cubes_h.push(header); cubes_e.push(lookup_hashname(hash, data)?); } diff --git a/test.sh b/test.sh index 5f9f80e..157f5fa 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,5 @@ #!/bin/bash RUST_BACKTRACE=1 cargo test --all-features -- --nocapture +# RUST_BACKTRACE=1 cargo test --release --all-features -- --nocapture # RUST_BACKTRACE=1 cargo test --features techblox -- --nocapture exit $? diff --git a/tests/All.Techblox b/tests/All.Techblox new file mode 100644 index 0000000..f200d1c Binary files /dev/null and b/tests/All.Techblox differ diff --git a/tests/techblox_parsing.rs b/tests/techblox_parsing.rs index 7546e34..09de5ad 100644 --- a/tests/techblox_parsing.rs +++ b/tests/techblox_parsing.rs @@ -10,7 +10,11 @@ use std::fs::{File, OpenOptions}; #[cfg(feature = "techblox")] const GAMESAVE_PATH: &str = "tests/GameSave.Techblox"; #[cfg(feature = "techblox")] -const GAMESAVE_PATH2: &str = "tests/GameSave2.Techblox"; +const GAMESAVE_PATH_OUT: &str = "tests/GameSave.out.Techblox"; +#[cfg(feature = "techblox")] +const GAMESAVE_PATH_ALL: &str = "tests/All.Techblox"; +#[cfg(feature = "techblox")] +const GAMESAVE_PATH_ALL_OUT: &str = "tests/All.out.Techblox"; #[cfg(feature = "techblox")] const HASHNAMES: &[&str] = &[ @@ -148,15 +152,70 @@ fn hash_tb_name() { #[cfg(feature = "techblox")] #[test] fn techblox_gamesave_perfect_parse() -> Result<(), ()> { - let mut in_file = File::open(GAMESAVE_PATH).map_err(|_| ())?; + let mut in_file = File::open(GAMESAVE_PATH_ALL).map_err(|_| ())?; + let mut buf = Vec::new(); + in_file.read_to_end(&mut buf).map_err(|_| ())?; + let gs = techblox::GameSave::parse(&mut buf.as_slice()).map_err(|_| ())?; + let mut out_file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(GAMESAVE_PATH_OUT) + .map_err(|_| ())?; + gs.dump(&mut out_file).map_err(|_| ())?; + assert_eq!(in_file.stream_position().unwrap(), out_file.stream_position().unwrap()); + Ok(()) +} + +#[cfg(feature = "techblox")] +#[test] +fn techblox_gamesave_parse_all() -> Result<(), ()> { + let mut in_file = File::open(GAMESAVE_PATH_ALL).map_err(|_| ())?; let mut buf = Vec::new(); in_file.read_to_end(&mut buf).map_err(|_| ())?; let gs = techblox::GameSave::parse(&mut buf.as_slice()).map_err(|_| ())?; + + // verify + for i in 1..(gs.group_len as usize) { + assert_eq!(gs.group_headers[i-1].hash, gs.group_headers[i].hash); + //println!("#{} count {} vs {}", i, gs.group_headers[i-1].component_count, gs.group_headers[i].component_count); + assert_eq!(gs.group_headers[i-1].component_count, gs.group_headers[i].component_count); + } + for i in 0..(gs.group_len as usize) { + assert_eq!(gs.group_headers[i].component_count, techblox::BlockGroupEntity::serialized_components()); + assert_eq!(gs.group_headers[i].hash, gs.cube_groups[i].hash_name()); + } + for i in 1..(gs.cube_len as usize) { + //assert_eq!(gs.cube_headers[i-1].hash, gs.cube_headers[i].hash); + //println!("#{} count {} vs {}", i, gs.cube_headers[i-1].component_count, gs.cube_headers[i].component_count); + if gs.cube_headers[i-1].hash == gs.cube_headers[i].hash { + assert_eq!(gs.group_headers[i-1].component_count, gs.group_headers[i].component_count); + } + } + for i in 0..(gs.cube_len as usize) { + assert!(gs.cube_headers[i].component_count >= blocks::BlockEntity::serialized_components()); + //println!("#{} components: {}", i, gs.cube_headers[i].component_count); + assert_eq!(gs.cube_headers[i].hash, gs.cube_entities[i].hash_name()); + } + + //println!("Parsed wire settings hash: {} obsolete? {}", gs.wire_settings_header.hash, gs.wire_settings_entity.settings_component.obsolete != 0); + assert_eq!(gs.wire_settings_header.hash, EntityHeader::from_name("GlobalWireSettingsEntityDescriptor", 0, 0, 0).hash); + assert_eq!(gs.wire_settings_header.hash, gs.wire_settings_entity.hash_name()); + + //println!("Parsed Flycam hash: {}", gs.flycam_header.hash); + assert_eq!(gs.flycam_header.hash, EntityHeader::from_name("FlyCamEntityDescriptorV0", 0, 0, 0).hash); + assert_eq!(gs.flycam_header.hash, gs.flycam_entity.hash_name()); + + //println!("Parsed Phycam hash: {}", gs.phycam_header.hash); + assert_eq!(gs.phycam_header.hash, EntityHeader::from_name("CharacterCameraEntityDescriptorV1", 0, 0, 0).hash); + assert_eq!(gs.phycam_header.hash, gs.phycam_entity.hash_name()); + + // write out let mut out_file = OpenOptions::new() .write(true) .truncate(true) .create(true) - .open(GAMESAVE_PATH2) + .open(GAMESAVE_PATH_ALL_OUT) .map_err(|_| ())?; gs.dump(&mut out_file).map_err(|_| ())?; assert_eq!(in_file.stream_position().unwrap(), out_file.stream_position().unwrap());