Browse Source

Implement rudementary Techblox save parsing

tags/v0.5.1
NGnius (Graham) 3 years ago
parent
commit
705fae29b3
28 changed files with 918 additions and 23 deletions
  1. +1
    -0
      .gitignore
  2. +9
    -2
      Cargo.toml
  3. +16
    -0
      parsable_macro_derive/Cargo.toml
  4. +49
    -0
      parsable_macro_derive/src/lib.rs
  5. +1
    -1
      src/cardlife/mod.rs
  6. +1
    -1
      src/cardlife_simple/mod.rs
  7. +7
    -5
      src/lib.rs
  8. +3
    -3
      src/robocraft/cubes.rs
  9. +1
    -1
      src/robocraft/mod.rs
  10. +44
    -0
      src/techblox/block_group_entity.rs
  11. +58
    -0
      src/techblox/blocks/block_entity.rs
  12. +113
    -0
      src/techblox/blocks/common_components.rs
  13. +16
    -0
      src/techblox/blocks/lookup_tables.rs
  14. +12
    -0
      src/techblox/blocks/mod.rs
  15. +54
    -0
      src/techblox/entity_header.rs
  16. +26
    -0
      src/techblox/entity_traits.rs
  17. +114
    -0
      src/techblox/gamesave.rs
  18. +19
    -0
      src/techblox/mod.rs
  19. +74
    -0
      src/techblox/murmur.rs
  20. +151
    -0
      src/techblox/parsing_tools.rs
  21. +46
    -0
      src/techblox/unity_types.rs
  22. +4
    -0
      test.sh
  23. BIN
      tests/GameSave.Techblox
  24. +8
    -1
      tests/cardlife_live.rs
  25. +6
    -1
      tests/clre_server.rs
  26. +10
    -0
      tests/robocraft_factory.rs
  27. +8
    -8
      tests/robocraft_factory_simple.rs
  28. +67
    -0
      tests/techblox_parsing.rs

+ 1
- 0
.gitignore View File

@@ -1,3 +1,4 @@
/target
Cargo.lock
/.idea
/parsable_macro_derive/target

+ 9
- 2
Cargo.toml View File

@@ -1,6 +1,6 @@
[package]
name = "libfj"
version = "0.4.1"
version = "0.5.1"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
edition = "2018"
description = "An unofficial collection of APIs used in FreeJam games and mods"
@@ -14,14 +14,21 @@ readme = "README.md"
[dependencies]
serde = { version = "^1", features = ["derive"]}
serde_json = "^1"
reqwest = { version = "^0.11", features = ["json"]}
reqwest = { version = "^0.11", features = ["json"], optional = true}
url = "^2.2"
ureq = { version = "^2", features = ["json"], optional = true}
base64 = "^0.13"
num_enum = "^0.5"
chrono = {version = "^0.4", optional = true}
fasthash = {version = "^0.4", optional = true}
libfj_parsable_macro_derive = {version = "0.5.3", optional = true}
#libfj_parsable_macro_derive = {path = "./parsable_macro_derive", optional = true}

[dev-dependencies]
tokio = { version = "1.4.0", features = ["macros"]}

[features]
simple = ["ureq"]
robocraft = ["reqwest"]
cardlife = ["reqwest"]
techblox = ["chrono", "fasthash", "libfj_parsable_macro_derive"]

+ 16
- 0
parsable_macro_derive/Cargo.toml View File

@@ -0,0 +1,16 @@
[package]
name = "libfj_parsable_macro_derive"
version = "0.5.3"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
edition = "2018"
description = "An unofficial collection of APIs used in FreeJam games and mods"
license = "MIT"
homepage = "https://github.com/NGnius/libfj"
repository = "https://github.com/NGnius/libfj"

[lib]
proc-macro = true

[dependencies]
syn = "1.0"
quote = "1.0"

+ 49
- 0
parsable_macro_derive/src/lib.rs View File

@@ -0,0 +1,49 @@
//! Macro implementation for using #[derive(Parsable)]
extern crate proc_macro;

use proc_macro::{TokenStream};
use syn::{DeriveInput, Data};
use quote::quote;

/// Macro generator
#[proc_macro_derive(Parsable)]
pub fn derive_parsable(struc: TokenStream) -> TokenStream {
let ast: &DeriveInput = &syn::parse(struc).unwrap();
let name = &ast.ident;
if let Data::Struct(data_struct) = &ast.data {
let mut p_fields_gen = vec![];
let mut d_fields_gen = vec![];
for field in &data_struct.fields {
let field_ident = &field.ident.clone().expect("Expected named field");
let field_type = &field.ty;
p_fields_gen.push(
quote! {
#field_ident: <#field_type>::parse(data)?
}
);
d_fields_gen.push(
quote! {
self.#field_ident.dump(data)?;
}
);
}
let final_gen = quote! {
impl Parsable for #name {
fn parse(data: &mut dyn std::io::Read) -> std::io::Result<Self> {
Ok(Self{
#(#p_fields_gen),*
})
}

fn dump(&self, data: &mut dyn std::io::Write) -> std::io::Result<usize> {
let mut write_count: usize = 0;
#(write_count += #d_fields_gen;)*
Ok(write_count)
}
}
};
return final_gen.into();
} else {
panic!("Expected Parsable auto-trait to be applied to struct");
}
}

+ 1
- 1
src/cardlife/mod.rs View File

@@ -1,4 +1,4 @@
//! Cardlife vanilla and modded (CLre) APIs (WIP)
//! Cardlife vanilla and modded (CLre) APIs (WIP).
//! LiveAPI and CLreServer are mostly complete, but some other APIs are missing.

mod client;


+ 1
- 1
src/cardlife_simple/mod.rs View File

@@ -1,3 +1,3 @@
//! Simple, blocking Cardlife API (WIP)
//! Simple, blocking Cardlife API (WIP).
//! Nothing is here yet, sorry!
// TODO

+ 7
- 5
src/lib.rs View File

@@ -1,11 +1,13 @@
//! An unofficial collection of APIs used in Robocraft and Cardlife.
//!
//! This crate is WIP, but the available APIs are tested and very usable.
#![warn(missing_docs)]

#[cfg(feature = "cardlife")]
pub mod cardlife;
#[cfg(all(feature = "simple", feature = "cardlife"))]
pub mod cardlife_simple;
#[cfg(feature = "robocraft")]
pub mod robocraft;
#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
pub mod robocraft_simple;
#[cfg(feature = "simple")]
pub mod cardlife_simple;
#[cfg(feature = "techblox")]
pub mod techblox;

+ 3
- 3
src/robocraft/cubes.rs View File

@@ -18,7 +18,7 @@ impl Cubes {
/// Process the raw bytes containing block data from a Robocraft CRF bot
///
/// `cube_data` and `colour_data` correspond to the `cube_data` and `colour_data` fields of FactoryRobotGetInfo.
/// In generally, you should use `Cubes::from<FactoryRobotGetInfo>(data)` instead of this lower-level function.
/// In general, you should use `Cubes::from<FactoryRobotGetInfo>(data)` instead of this lower-level function.
pub fn parse(cube_data: &mut Vec<u8>, colour_data: &mut Vec<u8>) -> Result<Self, ()> {
// read first 4 bytes (cube count) from both arrays and make sure they match
let mut cube_buf = [0; 4];
@@ -133,7 +133,7 @@ pub struct Cube {
}

impl Cube {
fn parse_cube_data(&mut self, reader: &mut &[u8]) -> Result<usize, ()> {
fn parse_cube_data(&mut self, reader: &mut dyn Read) -> Result<usize, ()> {
let mut buf = [0; 4];
// read cube id
if let Ok(len) = reader.read(&mut buf) {
@@ -159,7 +159,7 @@ impl Cube {
Ok(8)
}
fn parse_colour_data(&mut self, reader: &mut &[u8]) -> Result<usize, ()> {
fn parse_colour_data(&mut self, reader: &mut dyn Read) -> Result<usize, ()> {
let mut buf = [0; 4];
if let Ok(len) = reader.read(&mut buf) {
if len != 4 {


+ 1
- 1
src/robocraft/mod.rs View File

@@ -1,4 +1,4 @@
//! Robocraft APIs for the CRF and leaderboards (WIP)
//! Robocraft APIs for the CRF and leaderboards (WIP).
//! FactoryAPI is mostly complete, but many other APIs are missing.

mod factory;


+ 44
- 0
src/techblox/block_group_entity.rs View File

@@ -0,0 +1,44 @@
use crate::techblox::{UnityFloat3, UnityQuaternion, SerializedEntityComponent, SerializedEntityDescriptor, Parsable};
use libfj_parsable_macro_derive::*;

/// Block group entity descriptor.
#[derive(Clone, Copy, Parsable)]
pub struct BlockGroupEntity {
/// Block group identifier
pub saved_block_group_id: SavedBlockGroupIdComponent,
/// Block group location information
pub block_group_transform: BlockGroupTransformEntityComponent,
}

impl BlockGroupEntity {}

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

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

/// Saved block group identifier entity component.
#[derive(Clone, Copy, Parsable)]
pub struct SavedBlockGroupIdComponent {
/// Block group identifier
pub saved_block_group_id: i32,
}

impl SerializedEntityComponent for SavedBlockGroupIdComponent {}

/// Block group entity component for storing position and rotation.
#[derive(Clone, Copy, Parsable)]
pub struct BlockGroupTransformEntityComponent {
/// Block group position
pub block_group_grid_position: UnityFloat3,
/// Block group rotation
pub block_group_grid_rotation: UnityQuaternion,
}

impl SerializedEntityComponent for BlockGroupTransformEntityComponent {}

+ 58
- 0
src/techblox/blocks/block_entity.rs View File

@@ -0,0 +1,58 @@
use crate::techblox::{SerializedEntityDescriptor, Parsable, SerializedEntityComponent};
use crate::techblox::blocks::{DBEntityStruct, PositionEntityStruct, ScalingEntityStruct, RotationEntityStruct,
SkewComponent, GridRotationStruct, SerializedGridConnectionsEntityStruct, SerializedBlockPlacementInfoStruct,
SerializedCubeMaterialStruct, SerializedUniformBlockScaleEntityStruct, SerializedColourParameterEntityStruct,
BlockGroupEntityComponent};
use libfj_parsable_macro_derive::*;

/// Block entity descriptor.
#[derive(Copy, Clone, Parsable)]
pub struct BlockEntity {
/// Database component
pub db_component: DBEntityStruct,
/// Position component
pub pos_component: PositionEntityStruct,
/// Scale component
pub scale_component: ScalingEntityStruct,
/// Rotation component
pub rot_component: RotationEntityStruct,
/// Skew matrix component
pub skew_component: SkewComponent,
/// Grid component
pub grid_component: GridRotationStruct,
// GridConnectionsEntityStruct is not serialized to disk
/// No-op serializer (this has no data!)
pub grid_conn_component: SerializedGridConnectionsEntityStruct,
// BlockPlacementInfoStruct has a disk serializer that does nothing (?)
/// No-op serializer (this has no data!)
pub placement_component: SerializedBlockPlacementInfoStruct,
/// Cube material component
pub material_component: SerializedCubeMaterialStruct,
/// Uniform scale component
pub uscale_component: SerializedUniformBlockScaleEntityStruct,
/// Colour component
pub colour_component: SerializedColourParameterEntityStruct,
/// Group component
pub group_component: BlockGroupEntityComponent,
}

impl SerializedEntityDescriptor for BlockEntity {
fn serialized_components() -> u8 {
12
}

fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent> {
vec![&self.db_component,
&self.pos_component,
&self.scale_component,
&self.rot_component,
&self.skew_component,
&self.grid_component,
&self.grid_conn_component,
&self.placement_component,
&self.material_component,
&self.uscale_component,
&self.colour_component,
&self.group_component]
}
}

+ 113
- 0
src/techblox/blocks/common_components.rs View File

@@ -0,0 +1,113 @@
use crate::techblox::{Parsable, SerializedEntityComponent, UnityFloat3,
UnityQuaternion, UnityFloat4x4};
use libfj_parsable_macro_derive::*;

/// Database entity component.
#[derive(Copy, Clone, Parsable)]
pub struct DBEntityStruct {
/// Database identifier
pub dbid: u32,
}

impl SerializedEntityComponent for DBEntityStruct {}

/// Position entity component.
#[derive(Copy, Clone, Parsable)]
pub struct PositionEntityStruct {
/// Entity position
pub position: UnityFloat3,
}

impl SerializedEntityComponent for PositionEntityStruct {}

/// Scaling entity component.
#[derive(Copy, Clone, Parsable)]
pub struct ScalingEntityStruct {
/// Entity position
pub scale: UnityFloat3,
}

impl SerializedEntityComponent for ScalingEntityStruct {}

/// Scaling entity component.
#[derive(Copy, Clone, Parsable)]
pub struct RotationEntityStruct {
/// Entity position
pub rotation: UnityQuaternion,
}

impl SerializedEntityComponent for RotationEntityStruct {}

/// Block skew component.
#[derive(Copy, Clone, Parsable)]
pub struct SkewComponent {
/// Block skew matrix
pub skew_matrix: UnityFloat4x4,
}

impl SerializedEntityComponent for SkewComponent {}

/// Block placement grid rotation component.
#[derive(Copy, Clone, Parsable)]
pub struct GridRotationStruct {
/// Grid rotation
pub rotation: UnityQuaternion,
/// Grid position
pub position: UnityFloat3,
}

impl SerializedEntityComponent for GridRotationStruct {}

// *** These don't contain anything but the game thinks they do ***
// GridConnectionsEntityStruct is not serialized to disk
// BlockPlacementInfoStruct has a disk serializer that does nothing (?)

/// Empty, basically useless except that Techblox says it exists while serializing.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedGridConnectionsEntityStruct {}

impl SerializedEntityComponent for SerializedGridConnectionsEntityStruct {}

/// Empty, basically useless except that Techblox says it exists while serializing.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedBlockPlacementInfoStruct {}

impl SerializedEntityComponent for SerializedBlockPlacementInfoStruct {}

// *** These do contain data again ***

/// Block material component.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedCubeMaterialStruct {
/// Material identifier
pub material_id: u8,
}

impl SerializedEntityComponent for SerializedCubeMaterialStruct {}

/// Block uniform scale component.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedUniformBlockScaleEntityStruct {
/// Uniform scale factor
pub scale_factor: u8,
}

impl SerializedEntityComponent for SerializedUniformBlockScaleEntityStruct {}

/// Block colour component.
#[derive(Copy, Clone, Parsable)]
pub struct SerializedColourParameterEntityStruct {
/// Index of colour in Techblox palette
pub index_in_palette: u8,
}

impl SerializedEntityComponent for SerializedColourParameterEntityStruct {}

/// Block group component.
#[derive(Copy, Clone, Parsable)]
pub struct BlockGroupEntityComponent {
/// Index of colour in Techblox palette
pub current_block_group: i32,
}

impl SerializedEntityComponent for BlockGroupEntityComponent {}

+ 16
- 0
src/techblox/blocks/lookup_tables.rs View File

@@ -0,0 +1,16 @@
use std::io::Read;

use crate::techblox::{Parsable, SerializedEntityDescriptor};
#[cfg(debug_assertions)]
use crate::techblox::blocks::*;

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)?),
_ => {
#[cfg(debug_assertions)]
println!("Unknown hash ID {}", hash);
return Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Unrecognised hash {}", hash)))
}
})
}

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

@@ -0,0 +1,12 @@
//! A (mostly) complete collection of Techblox blocks for serialization

mod block_entity;
mod common_components;
mod lookup_tables;

pub use block_entity::{BlockEntity};
pub use common_components::{DBEntityStruct, PositionEntityStruct, ScalingEntityStruct, RotationEntityStruct,
SkewComponent, GridRotationStruct, SerializedGridConnectionsEntityStruct, SerializedBlockPlacementInfoStruct,
SerializedCubeMaterialStruct, SerializedUniformBlockScaleEntityStruct, SerializedColourParameterEntityStruct,
BlockGroupEntityComponent};
pub(crate) use lookup_tables::*;

+ 54
- 0
src/techblox/entity_header.rs View File

@@ -0,0 +1,54 @@
use crate::techblox::{hashname, brute_force, Parsable};
use libfj_parsable_macro_derive::*;

/// An entity's header information.
///
/// This holds entity data common to all entities, such as entity type and ID.
#[derive(Clone, Copy, Parsable)]
pub struct EntityHeader {
/// Entity type hash
pub hash: u32,
/// Entity identifier
pub entity_id: u32,
/// Entity group identifier
pub group_id: u32,
/// Count of serialized components after this header (this is not the size in bytes)
pub component_count: u8,
}

impl EntityHeader {
/// Guess the original name from the hashed value by brute-force.
///
/// This is slow and cannot guarantee a correct result. Use is discouraged.
pub fn guess_name(&self) -> String {
brute_force(self.hash)
}

/// 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 {
hash: hashname(name),
entity_id,
group_id,
component_count,
}
}
}

impl std::convert::Into<EntityGroupID> for EntityHeader {
fn into(self) -> EntityGroupID {
EntityGroupID {
entity_id: self.entity_id,
group_id: self.group_id,
}
}
}

/// Entity identifier common among all components in the same entity
#[derive(Clone, Copy, Parsable)]
pub struct EntityGroupID {
/// Entity identifier
pub entity_id: u32,
/// Entity group identifier
pub group_id: u32
}

+ 26
- 0
src/techblox/entity_traits.rs View File

@@ -0,0 +1,26 @@
use std::io::{Read, Write};

/// Standard trait for parsing Techblox game save data.
pub trait Parsable {
/// Process information from raw data.
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> where Self: Sized;
/// Convert struct data back into raw bytes
fn dump(&self, writer: &mut dyn Write) -> std::io::Result<usize>;
}

/// Entity descriptor containing serialized components.
pub trait SerializedEntityDescriptor: Parsable {
/// Count of entity components that this descriptor contains
fn serialized_components() -> u8 where Self: Sized;
/// Components that this entity is comprised of
fn components<'a>(&'a self) -> Vec<&'a dyn SerializedEntityComponent>;
}

/// Serializable entity component.
/// Components are the atomic unit of entities.
pub trait SerializedEntityComponent: Parsable {
/// Raw size of struct, in bytes.
fn size() -> usize where Self: Sized {
std::mem::size_of::<Self>()
}
}

+ 114
- 0
src/techblox/gamesave.rs View File

@@ -0,0 +1,114 @@
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;

/// A collection of cubes and other data from a GameSave.techblox file
//#[derive(Clone)]
pub struct GameSave {
/// Game version that this save was created by.
/// This may affect how the rest of the save file was parsed.
pub version: NaiveDate,

/// Unused magic value in file header.
pub magic1: 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,

/// Amount of block groups, as claimed by the file header.
pub group_len: u32,

/// Entity group descriptors for block group entities.
pub group_headers: Vec<EntityHeader>,

/// Block group entities.
pub cube_groups: Vec<BlockGroupEntity>,

/// Entity group descriptors for block entities.
pub cube_headers: Vec<EntityHeader>,

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

impl Parsable for GameSave {
/// Process a Techblox save file from raw bytes.
fn parse(data: &mut dyn Read) -> std::io::Result<Self> {
// parse version
let year = parse_u32(data)?; // parsed as i32 in-game for some reason
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 cube_count = parse_u32(data)?; // parsed as i32 in-game for some reason
let magic_val2 = 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);
let mut groups_e = Vec::<BlockGroupEntity>::with_capacity(group_count as usize);
for _i in 0..group_count {
groups_h.push(EntityHeader::parse(data)?);
groups_e.push(BlockGroupEntity::parse(data)?);
}

// parse cube data
let mut cubes_h = Vec::<EntityHeader>::with_capacity(cube_count as usize);
let mut cubes_e = Vec::<Box<dyn SerializedEntityDescriptor>>::with_capacity(cube_count as usize);
for _i in 0..cube_count {
let header = EntityHeader::parse(data)?;
let hash = header.hash;
cubes_h.push(header);
cubes_e.push(lookup_hashname(hash, data)?);
}
// TODO
Ok(Self {
version: date,
magic1: magic_val1,
cube_len: cube_count,
magic2: magic_val2,
group_len: group_count,
group_headers: groups_h,
cube_groups: groups_e,
cube_headers: cubes_h,
cube_entities: cubes_e,
})
}

fn dump(&self, writer: &mut dyn Write) -> std::io::Result<usize> {
let mut write_count: usize = 0;
// version
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)?;
write_count += self.cube_len.dump(writer)?;
// magic separator
write_count += self.magic2.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)?;
write_count += self.cube_groups[i].dump(writer)?;
}

// dump cube data
for i in 0..self.cube_len as usize {
write_count += self.cube_headers[i].dump(writer)?;
write_count += self.cube_entities[i].dump(writer)?;
}
// TODO
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)
}
}

+ 19
- 0
src/techblox/mod.rs View File

@@ -0,0 +1,19 @@
//! Techblox APIs and functionality (WIP).

pub mod blocks;
mod gamesave;
mod entity_header;
mod entity_traits;
mod block_group_entity;
mod unity_types;
#[allow(dead_code)]
mod parsing_tools;
mod murmur;

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(crate) use parsing_tools::*;
pub(crate) use murmur::*;

+ 74
- 0
src/techblox/murmur.rs View File

@@ -0,0 +1,74 @@
use fasthash::murmur3::hash32_with_seed;
use std::sync::mpsc::{channel, Sender};
use std::thread;

const HASH_SEED: u32 = 4919;

const ASCII_LETTERS: &[u8] = &[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90, // A..Z
97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122]; // a..z

const ASCII_NUMBERS: &[u8] = &[48, 49, 50, 51, 52, 53, 54, 55, 56, 57]; // 0..9

const HASHNAME_ENDING: &[u8] = &[69, 110, 116, 105, 116, 121, // Entity
68, 101, 115, 99, 114, 105, 112, 116, 111, 114, // Descriptor
86, 42]; // EntityDescriptorV0

const MAX_LENGTH: usize = 10;

pub fn hashname(name: &str) -> u32 {
hash32_with_seed(name, HASH_SEED)
}

pub fn brute_force(hash: u32) -> String {
let (tx, rx) = channel::<String>();
let mut start = Vec::<u8>::new();
thread::spawn(move || brute_force_letter(hash, &mut start, &tx, 1));
//println!("All brute force possibilities explored");
if let Ok(res) = rx.recv_timeout(std::time::Duration::from_secs(30)) {
return res;
} else {
return "".to_string();
}
}

fn brute_force_letter(hash: u32, start: &mut Vec<u8>, tx: &Sender<String>, threadity: usize) {
if start.len() > 0 {
brute_force_endings(hash, start, tx);
}
if start.len() >= MAX_LENGTH { // do not continue extending forever
//handles.pop().unwrap().join().unwrap();
return;
}
let mut handles = Vec::<thread::JoinHandle::<_>>::new();
start.push(65); // add letter
let last_elem = start.len()-1;
for letter in ASCII_LETTERS {
start[last_elem] = *letter;
if threadity > 0 {
//thread::sleep(std::time::Duration::from_millis(50));
let mut new_start = start.clone();
let new_tx = tx.clone();
handles.push(thread::spawn(move || brute_force_letter(hash, &mut new_start, &new_tx, threadity-1)));
} else {
brute_force_letter(hash, start, tx, threadity);
}
}
for handle in handles {
handle.join().unwrap()
}
start.truncate(last_elem);
}

fn brute_force_endings(hash: u32, start: &mut Vec<u8>, tx: &Sender<String>) {
start.extend(HASHNAME_ENDING); // add ending
let last_elem = start.len()-1;
for num in ASCII_NUMBERS {
start[last_elem] = *num;
if hash32_with_seed(&start, HASH_SEED) == hash {
let result = String::from_utf8(start.clone()).unwrap();
println!("Found match `{}`", result);
tx.send(result).unwrap();
}
}
start.truncate(start.len()-HASHNAME_ENDING.len()); // remove ending
}

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

@@ -0,0 +1,151 @@
use std::io::{Read, Write};
use crate::techblox::Parsable;

// reading

pub fn parse_header_u32(reader: &mut dyn Read) -> std::io::Result<u32> {
// this is possibly wrong
let mut u32_buf = [0; 4];
//u32_buf[3] = parse_u8(reader)?;
//u32_buf[2] = parse_u8(reader)?;
//u32_buf[1] = parse_u8(reader)?;
//u32_buf[0] = parse_u8(reader)?;
reader.read(&mut u32_buf)?;
Ok(u32::from_le_bytes(u32_buf))
}

pub fn parse_u8(reader: &mut dyn Read) -> std::io::Result<u8> {
let mut u8_buf = [0; 1];
reader.read(&mut u8_buf)?;
Ok(u8_buf[0])
}

pub fn parse_u32(reader: &mut dyn Read) -> std::io::Result<u32> {
let mut u32_buf = [0; 4];
reader.read(&mut u32_buf)?;
Ok(u32::from_le_bytes(u32_buf))
}

pub fn parse_i32(reader: &mut dyn Read) -> std::io::Result<i32> {
let mut i32_buf = [0; 4];
reader.read(&mut i32_buf)?;
Ok(i32::from_le_bytes(i32_buf))
}

pub fn parse_u64(reader: &mut dyn Read) -> std::io::Result<u64> {
let mut u64_buf = [0; 8];
reader.read(&mut u64_buf)?;
Ok(u64::from_le_bytes(u64_buf))
}

pub fn parse_i64(reader: &mut dyn Read) -> std::io::Result<i64> {
let mut i64_buf = [0; 8];
reader.read(&mut i64_buf)?;
Ok(i64::from_le_bytes(i64_buf))
}

pub fn parse_f32(reader: &mut dyn Read) -> std::io::Result<f32> {
let mut f32_buf = [0; 4];
reader.read(&mut f32_buf)?;
Ok(f32::from_le_bytes(f32_buf))
}

// writing

pub fn dump_u8(data: u8, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&data.to_le_bytes())
}

pub fn dump_u32(data: u32, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&data.to_le_bytes())
}

pub fn dump_i32(data: i32, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&data.to_le_bytes())
}

pub fn dump_u64(data: u64, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&data.to_le_bytes())
}

pub fn dump_i64(data: i64, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&data.to_le_bytes())
}

pub fn dump_f32(data: f32, writer: &mut dyn Write) -> std::io::Result<usize> {
writer.write(&data.to_le_bytes())
}

// trait implementations

impl Parsable for u8 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 1];
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())
}
}

impl Parsable for u32 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 4];
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())
}
}

impl Parsable for i32 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 4];
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())
}
}

impl Parsable for u64 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 8];
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())
}
}

impl Parsable for i64 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 8];
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())
}
}

impl Parsable for f32 {
fn parse(reader: &mut dyn Read) -> std::io::Result<Self> {
let mut buf = [0; 4];
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())
}
}

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

@@ -0,0 +1,46 @@
use crate::techblox::{Parsable};
use libfj_parsable_macro_derive::*;

/// Unity-like floating-point vector for 3-dimensional space.
#[derive(Clone, Copy, Parsable)]
pub struct UnityFloat3 {
/// x coordinate
pub x: f32,
/// y coordinate
pub y: f32,
/// z coordinate
pub z: f32,
}

/// Unity-like floating-point vector for 4-dimensional space.
#[derive(Clone, Copy, Parsable)]
pub struct UnityFloat4 {
/// x coordinate
pub x: f32,
/// y coordinate
pub y: f32,
/// z coordinate
pub z: f32,
/// w coordinate
pub w: f32,
}

/// Unity-like floating-point vector matrix for 4-dimensional space.
#[derive(Clone, Copy, Parsable)]
pub struct UnityFloat4x4 {
/// c0 row(?)
pub c0: UnityFloat4,
/// c1 row(?)
pub c1: UnityFloat4,
/// c2 row(?)
pub c2: UnityFloat4,
/// c3 row(?)
pub c3: UnityFloat4,
}

/// Unity-like floating-point quaternion for rotation in 3-dimensional space.
#[derive(Clone, Copy, Parsable)]
pub struct UnityQuaternion {
/// Rotational orientation
pub value: UnityFloat4,
}

+ 4
- 0
test.sh View File

@@ -0,0 +1,4 @@
#!/bin/bash
RUST_BACKTRACE=1 cargo test --all-features -- --nocapture
# RUST_BACKTRACE=1 cargo test --features techblox -- --nocapture
exit $?

BIN
tests/GameSave.Techblox View File


+ 8
- 1
tests/cardlife_live.rs View File

@@ -1,14 +1,19 @@
#[cfg(feature = "cardlife")]
use libfj::cardlife;

#[cfg(feature = "cardlife")]
const EMAIL: &str = "";
#[cfg(feature = "cardlife")]
const PASSWORD: &str = "";

#[cfg(feature = "cardlife")]
#[test]
fn live_api_init() -> Result<(), ()> {
cardlife::LiveAPI::new();
Ok(())
}

#[cfg(feature = "cardlife")]
#[tokio::test]
async fn live_api_init_auth() -> Result<(), ()> {
let live = cardlife::LiveAPI::login_email(EMAIL, PASSWORD).await;
@@ -16,6 +21,7 @@ async fn live_api_init_auth() -> Result<(), ()> {
Ok(())
}

#[cfg(feature = "cardlife")]
#[tokio::test]
async fn live_api_authenticate() -> Result<(), ()> {
let mut live = cardlife::LiveAPI::new();
@@ -30,6 +36,7 @@ async fn live_api_authenticate() -> Result<(), ()> {
Ok(())
}

#[cfg(feature = "cardlife")]
#[tokio::test]
async fn live_api_lobbies() -> Result<(), ()> {
//let live = cardlife::LiveAPI::login_email(EMAIL, PASSWORD).await.unwrap();
@@ -43,4 +50,4 @@ async fn live_api_lobbies() -> Result<(), ()> {
println!("LiveGameInfo.to_string() -> `{}`", game.to_string());
}*/
Ok(())
}
}

+ 6
- 1
tests/clre_server.rs View File

@@ -1,12 +1,16 @@
#[cfg(feature = "cardlife")]
use libfj::cardlife;

#[cfg(feature = "cardlife")]
#[test]
fn clre_server_init() -> Result<(), ()> {
assert!(cardlife::CLreServer::new("http://localhost:5030").is_ok());
Ok(())
}

/*#[tokio::test]
/*
#[cfg(feature = "cardlife")]
#[tokio::test]
async fn clre_server_game() -> Result<(), ()> {
let server = cardlife::CLreServer::new("http://localhost:5030").unwrap();
let result = server.game_info().await;
@@ -18,6 +22,7 @@ async fn clre_server_game() -> Result<(), ()> {
Ok(())
}

#[cfg(feature = "cardlife")]
#[tokio::test]
async fn clre_server_status() -> Result<(), ()> {
let server = cardlife::CLreServer::new("http://localhost:5030").unwrap();


+ 10
- 0
tests/robocraft_factory.rs View File

@@ -1,12 +1,16 @@
#[cfg(feature = "robocraft")]
use libfj::robocraft;
#[cfg(feature = "robocraft")]
use std::convert::From;

#[cfg(feature = "robocraft")]
#[test]
fn robocraft_factory_api_init() -> Result<(), ()> {
robocraft::FactoryAPI::new();
Ok(())
}

#[cfg(feature = "robocraft")]
#[tokio::test]
async fn robocraft_factory_default_query() -> Result<(), ()> {
let api = robocraft::FactoryAPI::new();
@@ -25,10 +29,12 @@ async fn robocraft_factory_default_query() -> Result<(), ()> {
Ok(())
}

#[cfg(feature = "robocraft")]
fn builder() -> robocraft::FactorySearchBuilder {
robocraft::FactoryAPI::new().list_builder()
}

#[cfg(feature = "robocraft")]
fn assert_factory_list(robo_info: robocraft::FactoryInfo<robocraft::RoboShopItemsInfo>) -> Result<(), ()> {
assert_ne!(robo_info.response.roboshop_items.len(), 0);
assert_eq!(robo_info.status_code, 200);
@@ -42,6 +48,7 @@ fn assert_factory_list(robo_info: robocraft::FactoryInfo<robocraft::RoboShopItem
Ok(())
}

#[cfg(feature = "robocraft")]
#[tokio::test]
async fn robocraft_factory_custom_query() -> Result<(), ()> {
let api = robocraft::FactoryAPI::new();
@@ -67,6 +74,7 @@ async fn robocraft_factory_custom_query() -> Result<(), ()> {
Ok(())
}

#[cfg(feature = "robocraft")]
#[tokio::test]
async fn robocraft_factory_player_query() -> Result<(), ()> {
let result = builder()
@@ -78,6 +86,7 @@ async fn robocraft_factory_player_query() -> Result<(), ()> {
assert_factory_list(result.unwrap())
}

#[cfg(feature = "robocraft")]
#[tokio::test]
async fn robocraft_factory_robot_query() -> Result<(), ()> {
let api = robocraft::FactoryAPI::new();
@@ -91,6 +100,7 @@ async fn robocraft_factory_robot_query() -> Result<(), ()> {
Ok(())
}

#[cfg(feature = "robocraft")]
#[tokio::test]
async fn robocraft_factory_robot_cubes() -> Result<(), ()> {
let api = robocraft::FactoryAPI::new();


+ 8
- 8
tests/robocraft_factory_simple.rs View File

@@ -1,21 +1,21 @@
#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
use libfj::robocraft_simple;
#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
use libfj::robocraft;

#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
#[test]
fn robocraft_factory_api_init_simple() -> Result<(), ()> {
robocraft_simple::FactoryAPI::new();
Ok(())
}

#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
fn builder() -> robocraft_simple::FactorySearchBuilder {
robocraft_simple::FactoryAPI::new().list_builder()
}

#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
fn assert_factory_list(robo_info: robocraft::FactoryInfo<robocraft::RoboShopItemsInfo>) -> Result<(), ()> {
assert_ne!(robo_info.response.roboshop_items.len(), 0);
assert_eq!(robo_info.status_code, 200);
@@ -30,7 +30,7 @@ fn assert_factory_list(robo_info: robocraft::FactoryInfo<robocraft::RoboShopItem
}

#[test]
#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
fn robocraft_factory_custom_query_simple() -> Result<(), ()> {
let result = builder()
.movement_or(robocraft::FactoryMovementType::Wheels)
@@ -55,7 +55,7 @@ fn robocraft_factory_custom_query_simple() -> Result<(), ()> {
}

#[test]
#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
fn robocraft_factory_player_query() -> Result<(), ()> {
let result = builder()
.text("Baerentoeter".to_string())
@@ -67,7 +67,7 @@ fn robocraft_factory_player_query() -> Result<(), ()> {
}

#[test]
#[cfg(feature = "simple")]
#[cfg(all(feature = "simple", feature = "robocraft"))]
fn robocraft_factory_robot_query() -> Result<(), ()> {
let api = robocraft_simple::FactoryAPI::new();
let result = api.get(6478345 /* featured robot id*/);


+ 67
- 0
tests/techblox_parsing.rs View File

@@ -0,0 +1,67 @@
#[cfg(feature = "techblox")]
use libfj::techblox;
#[cfg(feature = "techblox")]
use libfj::techblox::{SerializedEntityDescriptor, Parsable, blocks};
#[cfg(feature = "techblox")]
use std::io::Read;
#[cfg(feature = "techblox")]
use std::fs::File;

#[cfg(feature = "techblox")]
const GAMESAVE_PATH: &str = "tests/GameSave.Techblox";

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

#[cfg(feature = "techblox")]
#[test]
fn techblox_gamesave_parse() -> Result<(), ()> {
let mut f = File::open(GAMESAVE_PATH).map_err(|_| ())?;
let mut buf = Vec::new();
f.read_to_end(&mut buf).map_err(|_| ())?;
let gs = techblox::GameSave::parse(&mut buf.as_slice()).map_err(|_| ())?;
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());
}
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);
}
println!("{}", gs.to_string());
Ok(())
}

#[allow(dead_code)]
#[cfg(feature = "techblox")]
//#[test]
fn techblox_gamesave_brute_force() -> Result<(), ()> {
// this is slow and not very important, so it's probably better to not test this
let mut f = File::open(GAMESAVE_PATH).map_err(|_| ())?;
let mut buf = Vec::new();
f.read_to_end(&mut buf).map_err(|_| ())?;
let gs = techblox::GameSave::parse(&mut buf.as_slice()).map_err(|_| ())?;
println!("murmurhash3: {} -> {}", gs.group_headers[0].guess_name(), gs.group_headers[0].hash);
Ok(())
}

#[cfg(feature = "techblox")]
#[test]
fn hash_tb_name() {
for name in HASHNAMES {
println!("MurmurHash3: {} -> {}", name, crate::techblox::EntityHeader::from_name(name, 0, 0, 0).hash);
}
}

Loading…
Cancel
Save