Browse Source

Add parsing of the rest of the techblox save file

tags/v0.5.1
NGnius (Graham) 2 years ago
parent
commit
00c90574f4
10 changed files with 286 additions and 23 deletions
  1. +1
    -0
      .gitignore
  2. +2
    -1
      Cargo.toml
  3. +2
    -0
      src/techblox/blocks/mod.rs
  4. +61
    -0
      src/techblox/blocks/wire_entity.rs
  5. +57
    -0
      src/techblox/camera.rs
  6. +97
    -18
      src/techblox/gamesave.rs
  7. +4
    -1
      src/techblox/mod.rs
  8. +14
    -0
      src/techblox/parsing_tools.rs
  9. +12
    -0
      src/techblox/unity_types.rs
  10. +36
    -3
      tests/techblox_parsing.rs

+ 1
- 0
.gitignore View File

@@ -3,3 +3,4 @@ Cargo.lock
/.idea
/parsable_macro_derive/target
/tests/test-*.obj
/tests/GameSave2.Techblox

+ 2
- 1
Cargo.toml View File

@@ -21,6 +21,7 @@ base64 = "^0.13"
num_enum = "^0.5"
chrono = {version = "^0.4", optional = true}
fasthash = {version = "^0.4", optional = true}
half = {version = "^1.7", optional = true}
libfj_parsable_macro_derive = {version = "0.5.3", optional = true}
#libfj_parsable_macro_derive = {path = "./parsable_macro_derive", optional = true}
obj = {version = "^0.10", optional = true}
@@ -33,5 +34,5 @@ tokio = { version = "1.4.0", features = ["macros"]}
simple = ["ureq"]
robocraft = ["reqwest"]
cardlife = ["reqwest"]
techblox = ["chrono", "fasthash", "libfj_parsable_macro_derive"]
techblox = ["chrono", "fasthash", "half", "libfj_parsable_macro_derive"]
convert = ["obj", "genmesh"]

+ 2
- 0
src/techblox/blocks/mod.rs View File

@@ -3,6 +3,7 @@
mod block_entity;
mod common_components;
mod lookup_tables;
mod wire_entity;

pub use block_entity::{BlockEntity};
pub use common_components::{DBEntityStruct, PositionEntityStruct, ScalingEntityStruct, RotationEntityStruct,
@@ -10,3 +11,4 @@ SkewComponent, GridRotationStruct, SerializedGridConnectionsEntityStruct, Serial
SerializedCubeMaterialStruct, SerializedUniformBlockScaleEntityStruct, SerializedColourParameterEntityStruct,
BlockGroupEntityComponent};
pub(crate) use lookup_tables::*;
pub use wire_entity::{SerializedWireEntity, WireSaveDataStruct, SerializedGlobalWireSettingsEntity, GlobalWireSettingsEntityStruct};

+ 61
- 0
src/techblox/blocks/wire_entity.rs View File

@@ -0,0 +1,61 @@
use crate::techblox::{SerializedEntityComponent, SerializedEntityDescriptor, Parsable};

use libfj_parsable_macro_derive::*;

/// Wire save data
#[derive(Copy, Clone, Parsable)]
pub struct SerializedWireEntity {
/// Wiring save data component
pub save_data_component: WireSaveDataStruct,
}

impl SerializedEntityDescriptor for SerializedWireEntity {
fn serialized_components() -> u8 {
1
}

fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> {
vec![&self.save_data_component]
}
}

/// Wire connection information that is saved.
#[derive(Copy, Clone, Parsable)]
pub struct WireSaveDataStruct {
/// Wire source block index in save
pub source_block_index: u32,
/// Wire destination block index in save
pub destination_block_index: u32,
/// Wire source port index
pub source_port_usage: u8,
/// Wire destination port index
pub destination_port_usage: u8,
}

impl SerializedEntityComponent for WireSaveDataStruct {}

/// Wire settings data for a game
#[derive(Copy, Clone, Parsable)]
pub struct SerializedGlobalWireSettingsEntity {
/// Global wire settings
pub settings_component: GlobalWireSettingsEntityStruct,
}

impl SerializedEntityDescriptor for SerializedGlobalWireSettingsEntity {
fn serialized_components() -> u8 {
1
}

fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> {
vec![&self.settings_component]
}
}

/// Wire settings applied to the whole game save
#[derive(Copy, Clone, Parsable)]
pub struct GlobalWireSettingsEntityStruct {
/// Is using obsolete wiring system? (bool)
pub obsolete: u8,
}

impl SerializedEntityComponent for GlobalWireSettingsEntityStruct {}

+ 57
- 0
src/techblox/camera.rs View File

@@ -0,0 +1,57 @@
use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent, UnityFloat3, UnityHalf3};

use libfj_parsable_macro_derive::*;

/// Player editing camera entity descriptor.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedFlyCamEntity {
/// Player camera in-game location
pub rb_component: SerializedRigidBodyEntityStruct,
}

impl SerializedEntityDescriptor for SerializedFlyCamEntity {
fn serialized_components() -> u8 {
2
}

fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> {
vec![&self.rb_component]
}
}

/// Physical object info for simulation
#[derive(Copy, Clone, Parsable)]
pub struct SerializedRigidBodyEntityStruct {
/// Rigid body location
pub position: UnityFloat3,
}

impl SerializedEntityComponent for SerializedRigidBodyEntityStruct {}

/// Player simulation camera entity descriptor.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedPhysicsCameraEntity {
/// In-game camera location information
pub cam_component: SerializedCameraEntityStruct,
}

impl SerializedEntityDescriptor for SerializedPhysicsCameraEntity {
fn serialized_components() -> u8 {
1
}

fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> {
vec![&self.cam_component]
}
}

/// Physics camera component
#[derive(Copy, Clone, Parsable)]
pub struct SerializedCameraEntityStruct {
/// Camera position in game world
pub position: UnityHalf3,
/// Camera euler rotation in game world
pub rotation: UnityHalf3,
}

impl SerializedEntityComponent for SerializedCameraEntityStruct {}

+ 97
- 18
src/techblox/gamesave.rs View File

@@ -1,8 +1,9 @@
use chrono::{naive::NaiveDate, Datelike};
use std::io::{Read, Write};

use crate::techblox::{EntityHeader, BlockGroupEntity, parse_i64, parse_u32, Parsable, SerializedEntityDescriptor};
use crate::techblox::blocks::lookup_hashname;
use crate::techblox::{EntityHeader, BlockGroupEntity, parse_i64, parse_u32, Parsable, SerializedEntityDescriptor,
SerializedFlyCamEntity, SerializedPhysicsCameraEntity};
use crate::techblox::blocks::{lookup_hashname, SerializedWireEntity, SerializedGlobalWireSettingsEntity};

/// A collection of cubes and other data from a GameSave.techblox file
//#[derive(Clone)]
@@ -11,14 +12,17 @@ pub struct GameSave {
/// This may affect how the rest of the save file was parsed.
pub version: NaiveDate,

/// Unused magic value in file header.
pub magic1: i64,
/// Time when file was saved, corresponding to ticks since 0 AD
/// https://docs.microsoft.com/en-us/dotnet/api/system.datetime.ticks?view=netframework-4.7.2
/// Not used for deserialization so not required to be sensible.
pub ticks: i64,

/// Amount of cubes present in the save data, as claimed by the file header.
pub cube_len: u32,

/// Unused magic value in file header.
pub magic2: u32,
/// Maximum block entity identifier in the game save.
/// Not used for deserialization so not required to be correct.
pub max_entity_id: u32,

/// Amount of block groups, as claimed by the file header.
pub group_len: u32,
@@ -33,7 +37,34 @@ pub struct GameSave {
pub cube_headers: Vec<EntityHeader>,

/// Blocks
pub cube_entities: Vec<Box<dyn SerializedEntityDescriptor>>
pub cube_entities: Vec<Box<dyn SerializedEntityDescriptor>>,

/// Amount of wires in the save data, as claimed by the file.
pub wire_len: u32,

/// Entity group descriptor for wire entities.
pub wire_headers: Vec<EntityHeader>,

/// Wires
pub wire_entities: Vec<SerializedWireEntity>,

/// Entity group descriptor for wire settings
pub wire_settings_header: EntityHeader,

/// Wire settings
pub wire_settings_entity: SerializedGlobalWireSettingsEntity,

/// Entity group descriptor for player fly camera
pub flycam_header: EntityHeader,

/// Player edit mode fly camera
pub flycam_entity: SerializedFlyCamEntity,

/// Entity group descriptor for player simulation mode camera
pub phycam_header: EntityHeader,

/// Player simulation mode camera
pub phycam_entity: SerializedPhysicsCameraEntity,
}

impl Parsable for GameSave {
@@ -44,9 +75,9 @@ impl Parsable for GameSave {
let month = parse_u32(data)?;
let day = parse_u32(data)?;
let date = NaiveDate::from_ymd(year as i32, month, day);
let magic_val1 = parse_i64(data)?; // unused
let ticks = parse_i64(data)?; // unused
let cube_count = parse_u32(data)?; // parsed as i32 in-game for some reason
let magic_val2 = parse_u32(data)?; // unused
let max_e_id = parse_u32(data)?; // unused
let group_count = parse_u32(data)?; // parsed as i32 in-game for some reason
// parse block groups
let mut groups_h = Vec::<EntityHeader>::with_capacity(group_count as usize);
@@ -65,17 +96,47 @@ impl Parsable for GameSave {
cubes_h.push(header);
cubes_e.push(lookup_hashname(hash, data)?);
}
// TODO

// parse wire data
let wire_count = parse_u32(data)?;
let mut wires_h = Vec::<EntityHeader>::with_capacity(wire_count as usize);
let mut wires_e = Vec::<SerializedWireEntity>::with_capacity(wire_count as usize);
for _i in 0..wire_count {
wires_h.push(EntityHeader::parse(data)?);
wires_e.push(SerializedWireEntity::parse(data)?);
}

// parse global wire settings
let wire_settings_h = EntityHeader::parse(data)?;
let wire_settings_e = SerializedGlobalWireSettingsEntity::parse(data)?;

// parse player cameras
let flycam_h = EntityHeader::parse(data)?;
let flycam_e = SerializedFlyCamEntity::parse(data)?;

let phycam_h = EntityHeader::parse(data)?;
let phycam_e = SerializedPhysicsCameraEntity::parse(data)?;

// build struct
Ok(Self {
version: date,
magic1: magic_val1,
ticks: ticks,
cube_len: cube_count,
magic2: magic_val2,
max_entity_id: max_e_id,
group_len: group_count,
group_headers: groups_h,
cube_groups: groups_e,
cube_headers: cubes_h,
cube_entities: cubes_e,
wire_len: wire_count,
wire_headers: wires_h,
wire_entities: wires_e,
wire_settings_header: wire_settings_h,
wire_settings_entity: wire_settings_e,
flycam_header: flycam_h,
flycam_entity: flycam_e,
phycam_header: phycam_h,
phycam_entity: phycam_e,
})
}

@@ -85,12 +146,13 @@ impl Parsable for GameSave {
write_count += self.version.year().dump(writer)?;
write_count += self.version.month().dump(writer)?;
write_count += self.version.day().dump(writer)?;
// magic separator
write_count += self.magic1.dump(writer)?;
// unused separator \/
write_count += self.ticks.dump(writer)?;
write_count += self.cube_len.dump(writer)?;
// magic separator
write_count += self.magic2.dump(writer)?;
// unused separator \/
write_count += self.max_entity_id.dump(writer)?;
write_count += self.group_len.dump(writer)?;

// dump block groups
for i in 0..self.group_len as usize {
write_count += self.group_headers[i].dump(writer)?;
@@ -102,13 +164,30 @@ impl Parsable for GameSave {
write_count += self.cube_headers[i].dump(writer)?;
write_count += self.cube_entities[i].dump(writer)?;
}
// TODO

// dump wire data
write_count += self.wire_len.dump(writer)?;
for i in 0..self.wire_len as usize {
write_count += self.wire_headers[i].dump(writer)?;
write_count += self.wire_entities[i].dump(writer)?;
}

// dump global wire settings
write_count += self.wire_settings_header.dump(writer)?;
write_count += self.wire_settings_entity.dump(writer)?;

// dump player cameras
write_count += self.flycam_header.dump(writer)?;
write_count += self.flycam_entity.dump(writer)?;

write_count += self.phycam_header.dump(writer)?;
write_count += self.phycam_entity.dump(writer)?;
Ok(write_count)
}
}

impl std::string::ToString for GameSave {
fn to_string(&self) -> String {
format!("{}g {}c (v{})", self.group_len, self.cube_len, self.version)
format!("{}g {}c {}w (v{})", self.group_len, self.cube_len, self.wire_len, self.version)
}
}

+ 4
- 1
src/techblox/mod.rs View File

@@ -1,6 +1,7 @@
//! Techblox APIs and functionality (WIP).

pub mod blocks;
mod camera;
mod gamesave;
mod entity_header;
mod entity_traits;
@@ -10,10 +11,12 @@ mod unity_types;
mod parsing_tools;
mod murmur;

pub use camera::{SerializedFlyCamEntity, SerializedRigidBodyEntityStruct,
SerializedPhysicsCameraEntity, SerializedCameraEntityStruct};
pub use gamesave::{GameSave};
pub use entity_header::{EntityHeader, EntityGroupID};
pub use entity_traits::{Parsable, SerializedEntityComponent, SerializedEntityDescriptor};
pub use block_group_entity::{BlockGroupEntity, BlockGroupTransformEntityComponent, SavedBlockGroupIdComponent};
pub use unity_types::{UnityFloat3, UnityFloat4, UnityQuaternion, UnityFloat4x4};
pub use unity_types::{UnityFloat3, UnityHalf3, UnityFloat4, UnityQuaternion, UnityFloat4x4};
pub(crate) use parsing_tools::*;
pub(crate) use murmur::*;

+ 14
- 0
src/techblox/parsing_tools.rs View File

@@ -1,5 +1,6 @@
use std::io::{Read, Write};
use crate::techblox::Parsable;
use half::f16;

// reading

@@ -149,3 +150,16 @@ impl Parsable for f32 {
writer.write(&self.to_le_bytes())
}
}


impl Parsable for f16 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 2];
reader.read(&mut buf)?;
Ok(Self::from_le_bytes(buf))
}

fn dump(&self, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&self.to_le_bytes())
}
}

+ 12
- 0
src/techblox/unity_types.rs View File

@@ -1,5 +1,6 @@
use crate::techblox::{Parsable};
use libfj_parsable_macro_derive::*;
use half::f16;

/// Unity-like floating-point vector for 3-dimensional space.
#[derive(Clone, Copy, Parsable)]
@@ -12,6 +13,17 @@ pub struct UnityFloat3 {
pub z: f32,
}

/// Unity-like half-precision vector for 3-dimensional space.
#[derive(Clone, Copy, Parsable)]
pub struct UnityHalf3 {
/// x coordinate
pub x: f16,
/// y coordinate
pub y: f16,
/// z coordinate
pub z: f16,
}

/// Unity-like floating-point vector for 4-dimensional space.
#[derive(Clone, Copy, Parsable)]
pub struct UnityFloat4 {


+ 36
- 3
tests/techblox_parsing.rs View File

@@ -1,18 +1,24 @@
#[cfg(feature = "techblox")]
use libfj::techblox;
#[cfg(feature = "techblox")]
use libfj::techblox::{SerializedEntityDescriptor, Parsable, blocks};
use libfj::techblox::{SerializedEntityDescriptor, Parsable, blocks, EntityHeader};
#[cfg(feature = "techblox")]
use std::io::Read;
use std::io::{Read, Seek};
#[cfg(feature = "techblox")]
use std::fs::File;
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";

#[cfg(feature = "techblox")]
const HASHNAMES: &[&str] = &[
"StandardBlockEntityDescriptorV4",
"WireEntityDescriptorMock",
"GlobalWireSettingsEntityDescriptor",
"FlyCamEntityDescriptorV0",
"CharacterCameraEntityDescriptorV1",
];

#[cfg(feature = "techblox")]
@@ -41,6 +47,15 @@ fn techblox_gamesave_parse() -> Result<(), ()> {
assert!(gs.cube_headers[i].component_count >= blocks::BlockEntity::serialized_components());
//println!("#{} components: {}", i, gs.cube_headers[i].component_count);
}

//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);

//println!("Parsed Flycam hash: {}", gs.flycam_header.hash);
assert_eq!(gs.flycam_header.hash, EntityHeader::from_name("FlyCamEntityDescriptorV0", 0, 0, 0).hash);

//println!("Parsed Phycam hash: {}", gs.phycam_header.hash);
assert_eq!(gs.phycam_header.hash, EntityHeader::from_name("CharacterCameraEntityDescriptorV1", 0, 0, 0).hash);
println!("{}", gs.to_string());
Ok(())
}
@@ -65,3 +80,21 @@ fn hash_tb_name() {
println!("MurmurHash3: {} -> {}", name, crate::techblox::EntityHeader::from_name(name, 0, 0, 0).hash);
}
}

#[cfg(feature = "techblox")]
#[test]
fn techblox_gamesave_perfect_parse() -> Result<(), ()> {
let mut in_file = File::open(GAMESAVE_PATH).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_PATH2)
.map_err(|_| ())?;
gs.dump(&mut out_file).map_err(|_| ())?;
assert_eq!(in_file.stream_position().unwrap(), out_file.stream_position().unwrap());
Ok(())
}

Loading…
Cancel
Save