From 0940cca8015fb6acc1f661164b9159d26f75ef11 Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Sat, 23 Oct 2021 21:54:15 -0500 Subject: [PATCH] Rewriting with zero dependencies --- .gitignore | 1 + Cargo.toml | 5 +- examples/load.rs | 16 ++ src/lib.rs | 430 ++++++++++++++++++++++++++++++++++++----------- 4 files changed, 347 insertions(+), 105 deletions(-) create mode 100644 examples/load.rs diff --git a/.gitignore b/.gitignore index 96ef6c0..eb1d4ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5626a92..40d5063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,8 @@ [package] name = "blotter" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -declio = "0.2.0" +[dependencies] \ No newline at end of file diff --git a/examples/load.rs b/examples/load.rs new file mode 100644 index 0000000..e872ff6 --- /dev/null +++ b/examples/load.rs @@ -0,0 +1,16 @@ +use std::{env::args_os, fs::File, io::BufReader, process::exit}; + +use blotter::BlotterFile; + +fn main() -> Result<(), blotter::Error> { + let infile = args_os().nth(1).unwrap_or_else(usage); + let mut reader = BufReader::new(File::open(infile)?); + let blotter_file = BlotterFile::read(&mut reader)?; + println!("{:#?}", blotter_file); + Ok(()) +} + +fn usage() -> T { + eprintln!("missing argument: input file name"); + exit(1); +} diff --git a/src/lib.rs b/src/lib.rs index 1fc462b..ad20fcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,131 +1,357 @@ -use declio::ctx::Len; -use declio::util::{magic_bytes, zero_one, LittleEndian}; -use declio::{Decode, Encode}; -use std::convert::TryInto; - -type ComponentId = LittleEndian; -type ComponentAddress = LittleEndian; -type Int = LittleEndian; -type Float = LittleEndian; -type Version = [Int; 4]; - -#[derive(Debug, Clone, Copy, PartialEq, Encode, Decode)] -#[declio(id_type = "u8")] -enum SaveType { - #[declio(id = "1")] +#[derive(Debug, Clone, Copy, PartialEq)] +#[non_exhaustive] +pub enum SaveType { World, - #[declio(id = "2")] Subassembly, } -#[derive(Debug, Encode, Decode)] -pub struct BlotterFile { - header: Header, - save_version: u8, - game_version: Version, - save_type: SaveType, - components_len: Int, - wires_len: Int, - mods_len: Int, - #[declio(ctx = "Len(mods_len.0.try_into()?)")] - mods: Vec, - component_ids_len: Int, - #[declio(ctx = "Len(component_ids_len.0.try_into()?)")] - component_ids: Vec, - #[declio(ctx = "Len(components_len.0.try_into()?)")] - components: Vec, - #[declio(ctx = "Len(wires_len.0.try_into()?)")] - wires: Vec, - #[declio(ctx = "*save_type")] - circuit_states: CircuitStates, - footer: Footer, +#[derive(Debug)] +struct ModInfo { + mod_id: String, + mod_version: [i32; 4], } -magic_bytes! { - #[derive(Debug)] - Header(b"Logic World save"); - - #[derive(Debug)] - Footer(b"redstone sux lol"); +#[derive(Debug)] +struct ComponentType { + numeric_id: u16, + text_id: String, } -#[derive(Debug, Encode, Decode)] -struct Text { - len: Int, - #[declio(with = "declio::util::utf8", ctx = "Len(len.0.try_into()?)")] - value: String, -} - -#[derive(Debug, Encode, Decode)] -struct Mod { - mod_id: Text, - version: Version, -} - -#[derive(Debug, Encode, Decode)] -struct ComponentIdMapping { - numeric_id: ComponentId, - text_id: Text, -} - -#[derive(Debug, Encode, Decode)] +#[derive(Debug)] struct Input { - circuit_state_id: Int, + circuit_state_id: i32, } -#[derive(Debug, Encode, Decode)] +#[derive(Debug)] struct Output { - circuit_state_id: Int, + circuit_state_id: i32, } -#[derive(Debug, Encode, Decode)] +#[derive(Debug)] struct Component { - address: ComponentAddress, - parent: ComponentAddress, - type_id: ComponentId, - position: [Float; 3], - rotation: [Float; 4], - inputs_len: Int, - #[declio(ctx = "Len(inputs_len.0.try_into()?)")] + address: u32, + parent: u32, + type_id: u16, + position: [f32; 3], + rotation: [f32; 4], inputs: Vec, - outputs_len: Int, - #[declio(ctx = "Len(outputs_len.0.try_into()?)")] outputs: Vec, - custom_data_len: Int, - #[declio(skip_if = "custom_data_len.0 == -1")] - #[declio(ctx = "Len(custom_data_len.0.try_into()?)")] custom_data: Option>, } -#[derive(Debug, Encode, Decode)] +#[derive(Debug)] struct PegAddress { - #[declio(with = "zero_one")] is_input: bool, - component: ComponentAddress, - index: Int, + component_address: u32, + peg_index: i32, } -#[derive(Debug, Encode, Decode)] +#[derive(Debug)] struct Wire { - peg_1: PegAddress, - peg_2: PegAddress, - circuit_state_id: Int, - rotation: Float, + start_peg: PegAddress, + end_peg: PegAddress, + circuit_state_id: i32, + rotation: f32, } -#[derive(Debug, Encode, Decode)] -#[declio(ctx = "save_type: SaveType", id_expr = "save_type")] +#[derive(Debug)] enum CircuitStates { - #[declio(id = "SaveType::World")] - WorldFormat { - len: Int, - #[declio(ctx = "Len(len.0.try_into()?)")] - circuit_states: Vec, - }, - #[declio(id = "SaveType::Subassembly")] - SubassemblyFormat { - len: Int, - #[declio(ctx = "Len(len.0.try_into()?)")] - on_states: Vec, - }, + WorldFormat { circuit_states: Vec }, + SubassemblyFormat { on_states: Vec }, +} + +#[derive(Debug)] +pub struct BlotterFile { + game_version: [i32; 4], + save_type: SaveType, + mods: Vec, + component_types: Vec, + components: Vec, + wires: Vec, + circuit_states: CircuitStates, +} + +impl BlotterFile { + pub fn new(game_version: [i32; 4]) -> Self { + Self { + game_version, + save_type: SaveType::World, + mods: Vec::new(), + component_types: Vec::new(), + components: Vec::new(), + wires: Vec::new(), + circuit_states: CircuitStates::WorldFormat { + circuit_states: Vec::new(), + }, + } + } + + pub fn read(reader: &mut R) -> Result + where + R: std::io::Read, + { + read_magic(reader, b"Logic World save")?; + + let save_version = read_u8(reader)?; + if save_version != 5 { + return Err(Error::IncompatibleVersion(save_version)); + } + + let game_version = read_version(reader)?; + + let save_type_id = read_u8(reader)?; + let save_type = match save_type_id { + 1 => SaveType::World, + 2 => SaveType::Subassembly, + _ => { + return Err(Error::UnknownSaveType(save_type_id)); + } + }; + + let num_components = read_usize(reader)?; + let num_wires = read_usize(reader)?; + + let num_mods = read_usize(reader)?; + let mods = read_vec(num_mods, || { + let mod_id = read_string(reader)?; + let mod_version = read_version(reader)?; + Ok(ModInfo { + mod_id, + mod_version, + }) + })?; + + let num_component_types: usize = read_usize(reader)?; + let component_types = read_vec(num_component_types, || { + let numeric_id = read_u16(reader)?; + let text_id = read_string(reader)?; + Ok(ComponentType { + numeric_id, + text_id, + }) + })?; + + let components = read_vec(num_components, || { + let address = read_u32(reader)?; + let parent = read_u32(reader)?; + let type_id = read_u16(reader)?; + let position = read_array(|| read_f32(reader))?; + let rotation = read_array(|| read_f32(reader))?; + + let num_inputs = read_usize(reader)?; + let inputs = read_vec(num_inputs, || { + let circuit_state_id = read_i32(reader)?; + Ok(Input { circuit_state_id }) + })?; + + let num_outputs = read_usize(reader)?; + let outputs = read_vec(num_outputs, || { + let circuit_state_id = read_i32(reader)?; + Ok(Output { circuit_state_id }) + })?; + + let custom_data_len = read_i32(reader)?; + let custom_data = if custom_data_len < 0 { + None + } else { + Some(read_bytevec(reader, custom_data_len as usize)?) + }; + + Ok(Component { + address, + parent, + type_id, + position, + rotation, + inputs, + outputs, + custom_data, + }) + })?; + + let wires = read_vec(num_wires, || { + let start_peg = read_peg_address(reader)?; + let end_peg = read_peg_address(reader)?; + let circuit_state_id = read_i32(reader)?; + let rotation = read_f32(reader)?; + + Ok(Wire { + start_peg, + end_peg, + circuit_state_id, + rotation, + }) + })?; + + let circuit_states = match save_type { + SaveType::World => { + let num_bytes = read_usize(reader)?; + let circuit_states = read_bytevec(reader, num_bytes)?; + CircuitStates::WorldFormat { circuit_states } + } + SaveType::Subassembly => { + let num_on_states = read_usize(reader)?; + let on_states = read_vec(num_on_states, || read_i32(reader))?; + CircuitStates::SubassemblyFormat { on_states } + } + }; + + read_magic(reader, b"redstone sux lol")?; + + Ok(Self { + game_version, + save_type, + mods, + component_types, + components, + wires, + circuit_states, + }) + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + IoError(std::io::Error), + InvalidSave, + IncompatibleVersion(u8), + UnknownSaveType(u8), +} + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Self::IoError(error) + } +} + +fn read_u8(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let mut bytes = [0u8; 1]; + reader.read_exact(&mut bytes)?; + Ok(u8::from_le_bytes(bytes)) +} + +fn read_u16(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let mut bytes = [0u8; 2]; + reader.read_exact(&mut bytes)?; + Ok(u16::from_le_bytes(bytes)) +} + +fn read_i32(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(i32::from_le_bytes(bytes)) +} + +fn read_u32(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(u32::from_le_bytes(bytes)) +} + +fn read_f32(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(f32::from_le_bytes(bytes)) +} + +fn read_usize(reader: &mut R) -> Result +where + R: std::io::Read, +{ + read_i32(reader)?.try_into().map_err(|_| Error::InvalidSave) +} + +fn read_string(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let string_len = read_usize(reader)?; + let bytes = read_bytevec(reader, string_len)?; + String::from_utf8(bytes).map_err(|_| Error::InvalidSave) +} + +fn read_vec(len: usize, mut reader: F) -> Result, Error> +where + F: FnMut() -> Result, +{ + let mut vec = Vec::with_capacity(len); + for _ in 0..len { + vec.push(reader()?); + } + Ok(vec) +} + +fn read_array(mut reader: F) -> Result<[T; LEN], Error> +where + F: FnMut() -> Result, + T: Default + Copy, +{ + let mut arr = [Default::default(); LEN]; + for slot in &mut arr { + *slot = reader()?; + } + Ok(arr) +} + +fn read_magic(reader: &mut R, magic_bytes: &[u8]) -> Result<(), Error> +where + R: std::io::Read, +{ + let mut header = [0u8; 16]; + reader.read_exact(&mut header)?; + if header != *magic_bytes { + return Err(Error::InvalidSave); + } + Ok(()) +} + +fn read_version(reader: &mut R) -> Result<[i32; 4], Error> +where + R: std::io::Read, +{ + read_array(|| read_i32(reader)) +} + +fn read_bytevec(reader: &mut R, len: usize) -> Result, Error> +where + R: std::io::Read, +{ + let mut vec = vec![0u8; len]; + reader.read_exact(&mut vec)?; + Ok(vec) +} + +fn read_peg_address(reader: &mut R) -> Result +where + R: std::io::Read, +{ + let peg_kind = read_u8(reader)?; + let is_input = match peg_kind { + 0 => false, + 1 => true, + _ => return Err(Error::InvalidSave), + }; + let component_address = read_u32(reader)?; + let peg_index = read_i32(reader)?; + Ok(PegAddress { + is_input, + component_address, + peg_index, + }) }