From 51a7710e9f9d018b10409431f6af81f76b909db1 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 23 Jun 2021 11:16:10 -0400 Subject: [PATCH] Implement techblox game save deserialization for all blocks available in-game --- .gitignore | 2 +- src/techblox/blocks/engine.rs | 39 +++++++++++ src/techblox/blocks/joint.rs | 24 +++++++ src/techblox/blocks/lookup_tables.rs | 88 ++++++++++++++++++++++++- src/techblox/blocks/mod.rs | 15 +++++ src/techblox/blocks/passenger_seat.rs | 28 ++++++++ src/techblox/blocks/pilot_seat.rs | 36 +++++++++++ src/techblox/blocks/spring.rs | 90 ++++++++++++++++++++++++++ src/techblox/blocks/tyre.rs | 24 +++++++ src/techblox/blocks/wheel_rig.rs | 74 +++++++++++++++++++++ src/techblox/entity_header.rs | 13 +++- src/techblox/gamesave.rs | 2 + test.sh | 1 + tests/All.Techblox | Bin 0 -> 21736 bytes tests/techblox_parsing.rs | 65 ++++++++++++++++++- 15 files changed, 494 insertions(+), 7 deletions(-) create mode 100644 src/techblox/blocks/engine.rs create mode 100644 src/techblox/blocks/joint.rs create mode 100644 src/techblox/blocks/passenger_seat.rs create mode 100644 src/techblox/blocks/pilot_seat.rs create mode 100644 src/techblox/blocks/spring.rs create mode 100644 src/techblox/blocks/tyre.rs create mode 100644 src/techblox/blocks/wheel_rig.rs create mode 100644 tests/All.Techblox 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 0000000000000000000000000000000000000000..f200d1cd06165b6ba987127e9eceba1062a53de9 GIT binary patch literal 21736 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 literal 0 HcmV?d00001 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());