diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/binary-serialize-derive/Cargo.lock b/binary-serialize-derive/Cargo.lock new file mode 100644 index 0000000..94b9d96 --- /dev/null +++ b/binary-serialize-derive/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "binary-serialize-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/binary-serialize-derive/Cargo.toml b/binary-serialize-derive/Cargo.toml new file mode 100644 index 0000000..1ba97b3 --- /dev/null +++ b/binary-serialize-derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "binary-serialize-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.101" +quote = "1.0.40" +syn = { version = "2.0.106", features = ["full"] } diff --git a/binary-serialize-derive/src/lib.rs b/binary-serialize-derive/src/lib.rs new file mode 100644 index 0000000..57aeadf --- /dev/null +++ b/binary-serialize-derive/src/lib.rs @@ -0,0 +1,36 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(BinarySerializable)] +pub fn derive_binary_serializable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; + + let fields = match input.data { + syn::Data::Struct(s) => s.fields, + _ => panic!("#[derive(BinarySerializable)] only works on structs"), + }; + + let field_writes = fields.iter().map(|f| { + let ident = f.ident.as_ref().unwrap(); + quote! { + BinarySerializable::write_to(&self.#ident, writer)?; + } + }); + + let expanded = quote! { + impl BinarySerializable for #name { + fn write_to(&self, writer: &mut W) -> std::io::Result<()> { + #(#field_writes)* + return Ok(()); + } + } + }; + + TokenStream::from(expanded) +} + diff --git a/logicworld-subassembly/Cargo.lock b/logicworld-subassembly/Cargo.lock new file mode 100644 index 0000000..b0d05f3 --- /dev/null +++ b/logicworld-subassembly/Cargo.lock @@ -0,0 +1,80 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "binary-serialize-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "logicworld-subassembly" +version = "0.1.0" +dependencies = [ + "binary-serialize-derive", + "quaternion", + "vecmath", +] + +[[package]] +name = "piston-float" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad78bf43dcf80e8f950c92b84f938a0fc7590b7f6866fbcbeca781609c115590" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quaternion" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a23cd933813dea6a8e24391c80634deba6a70ca5a14b2b944b8cb6d871e071" +dependencies = [ + "vecmath", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "vecmath" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ae1e0d85bca567dee1dcf87fb1ca2e792792f66f87dced8381f99cd91156a" +dependencies = [ + "piston-float", +] diff --git a/logicworld-subassembly/Cargo.toml b/logicworld-subassembly/Cargo.toml new file mode 100644 index 0000000..999980f --- /dev/null +++ b/logicworld-subassembly/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "logicworld-subassembly" +version = "0.1.0" +edition = "2021" + +[dependencies] +binary-serialize-derive = { path = "../binary-serialize-derive" } +quaternion = "2.0.0" +vecmath = "1.0.0" diff --git a/logicworld-subassembly/src/lib.rs b/logicworld-subassembly/src/lib.rs new file mode 100644 index 0000000..d2e9e70 --- /dev/null +++ b/logicworld-subassembly/src/lib.rs @@ -0,0 +1,128 @@ +use std::{ + fs::{self, File}, i32, io::{self, Write}, path::Path +}; + +use crate::lw::{BinarySerializable, ComponentIdMap, ModVersion, Version}; + +pub mod lw; + +enum SaveType { + _World = 1, + Subassembly = 2, +} + +pub fn create_subassembly( + name: String, + path: &Path, + components: &[lw::Component], + wires: &[lw::Wire], +) -> io::Result<()> { + let directory = Path::join(path, &name); + fs::create_dir_all(&directory)?; + + let mut file = File::create(Path::join(&directory, "data.partialworld"))?; + write_subassembly_file(&mut file, &components, &wires)?; + + let mut placing_info_file = File::create(Path::join(&directory, "LastMainPlacingInfo.jecs"))?; + write_placing_info_file(&mut placing_info_file)?; + + let mut placements_file = File::create(Path::join(&directory, "placements.jecs"))?; + write_placements_file(&mut placements_file)?; + + let mut meta_file = File::create(Path::join(&directory, "meta.jecs"))?; + write_meta_file(&mut meta_file, &name)?; + + return Ok(()); +} + +fn write_subassembly_file( + file: &mut impl Write, + components: &[lw::Component], + wires: &[lw::Wire], +) -> io::Result<()> { + // Header: + file.write_all(b"Logic World save")?; // Magic bytes + file.write_all(&[7 as u8])?; // Version 7 + Version::new(0, 92, 0, 455).write_to(file)?; // Game Version + file.write_all(&[SaveType::Subassembly as u8])?; + + (components.len() as i32).write_to(file)?; + (wires.len() as i32).write_to(file)?; + + // Body: + let mods = vec![ModVersion { + id: "MHG".to_owned(), + version: Version::new(0, 91, 4, -1), + }]; + mods.write_to(file)?; + + let component_id_map = vec![ComponentIdMap { + numeric_id: 15, + text_id: "MHG.CircuitBoard".to_owned(), + }]; + component_id_map.write_to(file)?; + + for component in components { + component.write_to(file)?; + } + + for wire in wires { + wire.write_to(file)?; + } + + 0i32.write_to(file)?; // We don't have any turned on nets + + // Footer: + file.write_all(b"redstone sux lol")?; + + return Ok(()); +} + +fn write_placing_info_file(file: &mut impl Write) -> io::Result<()> { + file.write_all( + b"PlacingInfo: + RaiseOnChangedEvents: true + Flipped: false + RotationAboutUpVector: 180 + Offset: + x: 0 + y: 0 + BoardIsFlat: true + BoardRotationState: 0 +", + ) +} +fn write_placements_file(file: &mut impl Write) -> io::Result<()> { + file.write_all( + b"Placements: + - + CornerMidpointPositionBeforeFlipping: + x: 0 + y: 0 + z: 0 + RotationBeforeFlipping: + x: 0 + y: 0 + z: 0 + w: 1 + PlacementType: OnTerrain + Flipped: false + BoardRotationState: 0 + Type: Standard +data_format_version: 1 +", + ) +} + +fn write_meta_file(file: &mut impl Write, title: &str) -> io::Result<()> { + file.write_all( + format!( + "Title: {} +Column: null +Category: null +", + title + ) + .as_bytes(), + ) +} diff --git a/logicworld-subassembly/src/lw.rs b/logicworld-subassembly/src/lw.rs new file mode 100644 index 0000000..10d9cde --- /dev/null +++ b/logicworld-subassembly/src/lw.rs @@ -0,0 +1,149 @@ +use std::io::{self, Write}; + +use binary_serialize_derive::BinarySerializable; +use quaternion::Quaternion; +use vecmath::Vector3; + +pub trait BinarySerializable { + fn write_to(&self, writer: &mut W) -> io::Result<()>; +} + +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum PegType { + Input = 1, + Output = 2, +} + +pub type Int = i32; +pub type Float = f32; +pub type ComponentAddress = u32; + +#[derive(BinarySerializable)] +pub struct Version { + pub major: Int, + pub minor: Int, + pub build: Int, + pub revision: Int, +} +impl Version { + pub fn new(major: Int, minor: Int, build: Int, revision: Int) -> Self { + Self { + major, + minor, + build, + revision, + } + } +} + +#[derive(BinarySerializable)] +pub struct ModVersion { + pub id: String, + pub version: Version, +} + +#[derive(BinarySerializable)] +pub struct Input { + circuit_state_id: Int, +} +#[derive(BinarySerializable)] +pub struct Output { + circuit_state_id: Int, +} + +#[derive(BinarySerializable)] +pub struct ComponentIdMap { + pub numeric_id: i16, + pub text_id: String, +} + +#[derive(BinarySerializable)] +pub struct PegAddress { + peg_type: PegType, + component_address: ComponentAddress, + index: Int, +} + +#[derive(BinarySerializable)] +pub struct Component { + pub address: ComponentAddress, + pub parent: ComponentAddress, + pub numeric_id: u16, + pub position: Vector3, + pub rotation: Quaternion, + pub inputs: Vec, + pub outputs: Vec, + pub custom_data: Vec, +} + +#[derive(BinarySerializable)] +pub struct Wire { + first_point: PegAddress, + second_point: PegAddress, + circuit_state_id: Int, + wire_rotation: Float, +} + +impl BinarySerializable for Vec { + fn write_to(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&(self.len() as Int).to_le_bytes())?; + for value in self { + value.write_to(writer)?; + } + return Ok(()); + } +} + +impl BinarySerializable for Vec { + fn write_to(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&(self.len() as Int).to_le_bytes())?; + writer.write_all(&self)?; + return Ok(()); + } +} + +impl BinarySerializable for PegType { + fn write_to(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&[*self as u8]) + } +} + +impl BinarySerializable for String { + fn write_to(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&(self.len() as Int).to_le_bytes())?; + writer.write_all(self.as_bytes())?; + return Ok(()); + } +} + +impl BinarySerializable for Vector3 { + fn write_to(&self, writer: &mut W) -> io::Result<()> { + self[0].write_to(writer)?; // x + self[1].write_to(writer)?; // y + self[2].write_to(writer)?; // z + return Ok(()); + } +} + +impl BinarySerializable for Quaternion { + fn write_to(&self, writer: &mut W) -> io::Result<()> { + self.1[0].write_to(writer)?; // x + self.1[1].write_to(writer)?; // y + self.1[2].write_to(writer)?; // z + self.0.write_to(writer)?; // w + return Ok(()); + } +} + +macro_rules! impl_binary_num { + ($($t:ty),*) => { + $(impl BinarySerializable for $t { + fn write_to(&self, writer: &mut W) -> std::io::Result<()> { + writer.write_all(&self.to_le_bytes()) + } + })* + }; +} + +impl_binary_num!(i16, u16, i32, u32, f32);