diff --git a/examples/load.rs b/examples/load.rs index e872ff6..fec2d16 100644 --- a/examples/load.rs +++ b/examples/load.rs @@ -2,7 +2,7 @@ use std::{env::args_os, fs::File, io::BufReader, process::exit}; use blotter::BlotterFile; -fn main() -> Result<(), blotter::Error> { +fn main() -> Result<(), blotter::error::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)?; diff --git a/examples/roundtrip.rs b/examples/roundtrip.rs index ae8cf8d..613d9d1 100644 --- a/examples/roundtrip.rs +++ b/examples/roundtrip.rs @@ -2,7 +2,7 @@ use std::{env::args_os, fs::File, io::BufReader, process::exit}; use blotter::BlotterFile; -fn main() -> Result<(), blotter::Error> { +fn main() -> Result<(), blotter::error::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)?; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6ec2e15 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,13 @@ +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + IoError(std::io::Error), + InvalidSave, + IncompatibleVersion(u8), +} + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Self::IoError(error) + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..08a85c9 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,120 @@ +use crate::error::Error; +use std::io::{Read, Write}; + +pub fn read_u8(reader: &mut R) -> Result { + let mut bytes = [0u8; 1]; + reader.read_exact(&mut bytes)?; + Ok(u8::from_le_bytes(bytes)) +} + +pub fn write_u8(v: u8, writer: &mut W) -> Result<(), Error> { + writer.write_all(&v.to_le_bytes())?; + Ok(()) +} + +pub fn read_u16(reader: &mut R) -> Result { + let mut bytes = [0u8; 2]; + reader.read_exact(&mut bytes)?; + Ok(u16::from_le_bytes(bytes)) +} + +pub fn write_u16(v: u16, writer: &mut W) -> Result<(), Error> { + writer.write_all(&v.to_le_bytes())?; + Ok(()) +} + +pub fn read_i32(reader: &mut R) -> Result { + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(i32::from_le_bytes(bytes)) +} + +pub fn write_i32(v: i32, writer: &mut W) -> Result<(), Error> { + writer.write_all(&v.to_le_bytes())?; + Ok(()) +} + +pub fn read_u32(reader: &mut R) -> Result { + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(u32::from_le_bytes(bytes)) +} + +pub fn write_u32(v: u32, writer: &mut W) -> Result<(), Error> { + writer.write_all(&v.to_le_bytes())?; + Ok(()) +} + +pub fn read_f32(reader: &mut R) -> Result { + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(f32::from_le_bytes(bytes)) +} + +pub fn write_f32(v: f32, writer: &mut W) -> Result<(), Error> { + writer.write_all(&v.to_le_bytes())?; + Ok(()) +} + +pub fn read_usize(reader: &mut R) -> Result { + read_i32(reader)?.try_into().map_err(|_| Error::InvalidSave) +} + +pub fn write_usize(v: usize, writer: &mut W) -> Result<(), Error> { + let v: i32 = v.try_into().map_err(|_| Error::InvalidSave)?; + write_i32(v, writer) +} + +pub fn read_string(reader: &mut R) -> Result { + let string_len = read_usize(reader)?; + let bytes = read_bytevec(reader, string_len)?; + String::from_utf8(bytes).map_err(|_| Error::InvalidSave) +} + +pub fn write_str(s: &str, writer: &mut W) -> Result<(), Error> { + write_usize(s.len(), writer)?; + writer.write_all(s.as_bytes())?; + Ok(()) +} + +pub 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) +} + +pub 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) +} + +pub fn read_magic(reader: &mut R, magic_bytes: &[u8]) -> Result<(), Error> { + let mut header = [0u8; 16]; + reader.read_exact(&mut header)?; + if header != *magic_bytes { + return Err(Error::InvalidSave); + } + Ok(()) +} + +pub fn read_version(reader: &mut R) -> Result<[i32; 4], Error> { + read_array(|| read_i32(reader)) +} + +pub fn read_bytevec(reader: &mut R, len: usize) -> Result, Error> { + let mut vec = vec![0u8; len]; + reader.read_exact(&mut vec)?; + Ok(vec) +} diff --git a/src/lib.rs b/src/lib.rs index 8c0439b..40a2c69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,517 +1,31 @@ +pub mod error; +pub(crate) mod io; +pub mod v5; + +use io::read_u8; use std::io::{Read, Write}; -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SaveType { - World, - Subassembly, -} +use crate::error::Error; +use crate::io::read_magic; #[derive(Debug)] -pub struct ModInfo { - pub mod_id: String, - pub mod_version: [i32; 4], -} - -#[derive(Debug)] -pub struct ComponentType { - pub numeric_id: u16, - pub text_id: String, -} - -#[derive(Debug)] -pub struct Input { - pub circuit_state_id: i32, -} - -#[derive(Debug)] -pub struct Output { - pub circuit_state_id: i32, -} - -#[derive(Debug)] -pub struct Component { - pub address: u32, - pub parent: u32, - pub type_id: u16, - pub position: [f32; 3], - pub rotation: [f32; 4], - pub inputs: Vec, - pub outputs: Vec, - pub custom_data: Option>, -} - -#[derive(Debug)] -pub struct PegAddress { - pub is_input: bool, - pub component_address: u32, - pub peg_index: i32, -} - -#[derive(Debug)] -pub struct Wire { - pub start_peg: PegAddress, - pub end_peg: PegAddress, - pub circuit_state_id: i32, - pub rotation: f32, -} - -#[derive(Debug)] -pub enum CircuitStates { - WorldFormat { circuit_states: Vec }, - SubassemblyFormat { on_states: Vec }, -} - -#[derive(Debug)] -pub struct BlotterFile { - pub save_version: u8, - pub game_version: [i32; 4], - pub save_type: SaveType, - pub mods: Vec, - pub component_types: Vec, - pub components: Vec, - pub wires: Vec, - pub circuit_states: CircuitStates, +pub enum BlotterFile { + V5(v5::BlotterFile), } impl BlotterFile { - pub fn new(game_version: [i32; 4]) -> Self { - Self { - save_version: 5, - 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: Read, - { + pub fn read(reader: &mut R) -> Result { read_magic(reader, b"Logic World save")?; - let save_version = read_u8(reader)?; - if save_version != 5 { - return Err(Error::IncompatibleVersion(save_version)); + match save_version { + 5 => v5::BlotterFile::read_after_save_version(reader).map(Self::V5), + _ => 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::InvalidSave); - } - }; - - 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 { - save_version, - game_version, - save_type, - mods, - component_types, - components, - wires, - circuit_states, - }) } - pub fn write(&self, writer: &mut W) -> Result<(), Error> - where - W: Write, - { - writer.write_all(b"Logic World save")?; - - write_u8(self.save_version, writer)?; - for &v in &self.game_version { - write_i32(v, writer)?; + pub fn write(&self, writer: &mut W) -> Result<(), Error> { + match self { + Self::V5(file) => file.write(writer), } - let save_type_id = match self.save_type { - SaveType::World => 1, - SaveType::Subassembly => 2, - }; - write_u8(save_type_id, writer)?; - - write_usize(self.components.len(), writer)?; - write_usize(self.wires.len(), writer)?; - - write_usize(self.mods.len(), writer)?; - for mod_info in &self.mods { - write_str(&mod_info.mod_id, writer)?; - for &v in &mod_info.mod_version { - write_i32(v, writer)?; - } - } - - write_usize(self.component_types.len(), writer)?; - for component_type in &self.component_types { - write_u16(component_type.numeric_id, writer)?; - write_str(&component_type.text_id, writer)?; - } - - for component in &self.components { - write_u32(component.address, writer)?; - write_u32(component.parent, writer)?; - write_u16(component.type_id, writer)?; - for &v in &component.position { - write_f32(v, writer)?; - } - for &v in &component.rotation { - write_f32(v, writer)?; - } - write_usize(component.inputs.len(), writer)?; - for input in &component.inputs { - write_i32(input.circuit_state_id, writer)?; - } - write_usize(component.outputs.len(), writer)?; - for output in &component.outputs { - write_i32(output.circuit_state_id, writer)?; - } - if let Some(bytes) = &component.custom_data { - write_usize(bytes.len(), writer)?; - writer.write_all(&bytes)?; - } else { - write_i32(-1, writer)?; - } - } - - for wire in &self.wires { - write_peg_address(&wire.start_peg, writer)?; - write_peg_address(&wire.end_peg, writer)?; - write_i32(wire.circuit_state_id, writer)?; - write_f32(wire.rotation, writer)?; - } - - match (self.save_type, &self.circuit_states) { - (SaveType::World, CircuitStates::WorldFormat { circuit_states }) => { - write_usize(circuit_states.len(), writer)?; - writer.write_all(&circuit_states)?; - } - (SaveType::Subassembly, CircuitStates::SubassemblyFormat { on_states }) => { - write_usize(on_states.len(), writer)?; - for &v in on_states { - write_i32(v, writer)?; - } - } - _ => { - return Err(Error::InvalidSave); - } - } - - writer.write_all(b"redstone sux lol")?; - Ok(()) } } - -#[derive(Debug)] -#[non_exhaustive] -pub enum Error { - IoError(std::io::Error), - InvalidSave, - IncompatibleVersion(u8), -} - -impl From for Error { - fn from(error: std::io::Error) -> Self { - Self::IoError(error) - } -} - -fn read_u8(reader: &mut R) -> Result -where - R: 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: 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: 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: 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: 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: Read, -{ - read_i32(reader)?.try_into().map_err(|_| Error::InvalidSave) -} - -fn read_string(reader: &mut R) -> Result -where - R: 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: 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: Read, -{ - read_array(|| read_i32(reader)) -} - -fn read_bytevec(reader: &mut R, len: usize) -> Result, Error> -where - R: Read, -{ - let mut vec = vec![0u8; len]; - reader.read_exact(&mut vec)?; - Ok(vec) -} - -fn read_peg_address(reader: &mut R) -> Result -where - R: 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, - }) -} - -fn write_u8(v: u8, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - writer.write_all(&v.to_le_bytes())?; - Ok(()) -} - -fn write_u16(v: u16, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - writer.write_all(&v.to_le_bytes())?; - Ok(()) -} - -fn write_i32(v: i32, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - writer.write_all(&v.to_le_bytes())?; - Ok(()) -} - -fn write_u32(v: u32, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - writer.write_all(&v.to_le_bytes())?; - Ok(()) -} - -fn write_f32(v: f32, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - writer.write_all(&v.to_le_bytes())?; - Ok(()) -} - -fn write_usize(v: usize, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - let v: i32 = v.try_into().map_err(|_| Error::InvalidSave)?; - write_i32(v, writer) -} - -fn write_str(s: &str, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - write_usize(s.len(), writer)?; - writer.write_all(s.as_bytes())?; - Ok(()) -} - -fn write_peg_address(address: &PegAddress, writer: &mut W) -> Result<(), Error> -where - W: Write, -{ - let peg_type_id = match address.is_input { - false => 0, - true => 1, - }; - write_u8(peg_type_id, writer)?; - write_u32(address.component_address, writer)?; - write_i32(address.peg_index, writer)?; - Ok(()) -} diff --git a/src/v5.rs b/src/v5.rs new file mode 100644 index 0000000..7fa2616 --- /dev/null +++ b/src/v5.rs @@ -0,0 +1,327 @@ +use crate::error::Error; +use crate::io::*; +use std::io::{Read, Write}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SaveType { + World, + Subassembly, +} + +#[derive(Debug)] +pub struct ModInfo { + pub mod_id: String, + pub mod_version: [i32; 4], +} + +#[derive(Debug)] +pub struct ComponentType { + pub numeric_id: u16, + pub text_id: String, +} + +#[derive(Debug)] +pub struct Input { + pub circuit_state_id: i32, +} + +#[derive(Debug)] +pub struct Output { + pub circuit_state_id: i32, +} + +#[derive(Debug)] +pub struct Component { + pub address: u32, + pub parent: u32, + pub type_id: u16, + pub position: [f32; 3], + pub rotation: [f32; 4], + pub inputs: Vec, + pub outputs: Vec, + pub custom_data: Option>, +} + +#[derive(Debug)] +pub struct PegAddress { + pub is_input: bool, + pub component_address: u32, + pub peg_index: i32, +} + +#[derive(Debug)] +pub struct Wire { + pub start_peg: PegAddress, + pub end_peg: PegAddress, + pub circuit_state_id: i32, + pub rotation: f32, +} + +#[derive(Debug)] +pub enum CircuitStates { + WorldFormat { circuit_states: Vec }, + SubassemblyFormat { on_states: Vec }, +} + +#[derive(Debug)] +pub struct BlotterFile { + pub save_version: u8, + pub game_version: [i32; 4], + pub save_type: SaveType, + pub mods: Vec, + pub component_types: Vec, + pub components: Vec, + pub wires: Vec, + pub circuit_states: CircuitStates, +} + +impl BlotterFile { + pub fn new(game_version: [i32; 4]) -> Self { + Self { + save_version: 5, + 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 { + read_magic(reader, b"Logic World save")?; + + let save_version = read_u8(reader)?; + if save_version != 5 { + return Err(Error::IncompatibleVersion(save_version)); + } + Self::read_after_save_version(reader) + } + + pub(crate) fn read_after_save_version(reader: &mut R) -> Result { + 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::InvalidSave); + } + }; + + 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 { + save_version: 5, + game_version, + save_type, + mods, + component_types, + components, + wires, + circuit_states, + }) + } + + pub fn write(&self, writer: &mut W) -> Result<(), Error> { + writer.write_all(b"Logic World save")?; + + write_u8(self.save_version, writer)?; + for &v in &self.game_version { + write_i32(v, writer)?; + } + let save_type_id = match self.save_type { + SaveType::World => 1, + SaveType::Subassembly => 2, + }; + write_u8(save_type_id, writer)?; + + write_usize(self.components.len(), writer)?; + write_usize(self.wires.len(), writer)?; + + write_usize(self.mods.len(), writer)?; + for mod_info in &self.mods { + write_str(&mod_info.mod_id, writer)?; + for &v in &mod_info.mod_version { + write_i32(v, writer)?; + } + } + + write_usize(self.component_types.len(), writer)?; + for component_type in &self.component_types { + write_u16(component_type.numeric_id, writer)?; + write_str(&component_type.text_id, writer)?; + } + + for component in &self.components { + write_u32(component.address, writer)?; + write_u32(component.parent, writer)?; + write_u16(component.type_id, writer)?; + for &v in &component.position { + write_f32(v, writer)?; + } + for &v in &component.rotation { + write_f32(v, writer)?; + } + write_usize(component.inputs.len(), writer)?; + for input in &component.inputs { + write_i32(input.circuit_state_id, writer)?; + } + write_usize(component.outputs.len(), writer)?; + for output in &component.outputs { + write_i32(output.circuit_state_id, writer)?; + } + if let Some(bytes) = &component.custom_data { + write_usize(bytes.len(), writer)?; + writer.write_all(&bytes)?; + } else { + write_i32(-1, writer)?; + } + } + + for wire in &self.wires { + write_peg_address(&wire.start_peg, writer)?; + write_peg_address(&wire.end_peg, writer)?; + write_i32(wire.circuit_state_id, writer)?; + write_f32(wire.rotation, writer)?; + } + + match (self.save_type, &self.circuit_states) { + (SaveType::World, CircuitStates::WorldFormat { circuit_states }) => { + write_usize(circuit_states.len(), writer)?; + writer.write_all(&circuit_states)?; + } + (SaveType::Subassembly, CircuitStates::SubassemblyFormat { on_states }) => { + write_usize(on_states.len(), writer)?; + for &v in on_states { + write_i32(v, writer)?; + } + } + _ => { + return Err(Error::InvalidSave); + } + } + + writer.write_all(b"redstone sux lol")?; + Ok(()) + } +} + +fn read_peg_address(reader: &mut R) -> Result { + 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, + }) +} + +fn write_peg_address(address: &PegAddress, writer: &mut W) -> Result<(), Error> { + let peg_type_id = match address.is_input { + false => 0, + true => 1, + }; + write_u8(peg_type_id, writer)?; + write_u32(address.component_address, writer)?; + write_i32(address.peg_index, writer)?; + Ok(()) +}