@@ -3,4 +3,4 @@ Cargo.lock | |||
/.idea | |||
/parsable_macro_derive/target | |||
/tests/test-*.obj | |||
/tests/GameSave2.Techblox | |||
/tests/*.out.Techblox |
@@ -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 {} |
@@ -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 | |||
} | |||
} |
@@ -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<Box<dyn SerializedEntityDescriptor>> { | |||
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("<Unknown>")); | |||
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 | |||
} |
@@ -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}; |
@@ -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 | |||
} | |||
} |
@@ -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 {} |
@@ -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 {} |
@@ -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 | |||
} | |||
} |
@@ -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 {} |
@@ -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<String> { | |||
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 { | |||
@@ -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)?); | |||
} | |||
@@ -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 $? |
@@ -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()); | |||