From 0e8edad446e2cf109e5e55bfab12634a4c9d5a49 Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Sat, 23 Oct 2021 22:16:40 -0500 Subject: [PATCH] Add zero-dependency writer --- examples/roundtrip.rs | 22 +++++ src/lib.rs | 183 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 examples/roundtrip.rs diff --git a/examples/roundtrip.rs b/examples/roundtrip.rs new file mode 100644 index 0000000..ae8cf8d --- /dev/null +++ b/examples/roundtrip.rs @@ -0,0 +1,22 @@ +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)?; + + let mut buffer = Vec::new(); + blotter_file.write(&mut buffer)?; + + let mut reader = buffer.as_slice(); + let second_blotter_file = BlotterFile::read(&mut reader)?; + eprintln!("{:#?}", second_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 ad20fcc..0b0e090 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +use std::io::{Read, Write}; + #[derive(Debug, Clone, Copy, PartialEq)] #[non_exhaustive] pub enum SaveType { @@ -62,6 +64,7 @@ enum CircuitStates { #[derive(Debug)] pub struct BlotterFile { + save_version: u8, game_version: [i32; 4], save_type: SaveType, mods: Vec, @@ -74,6 +77,7 @@ pub struct BlotterFile { impl BlotterFile { pub fn new(game_version: [i32; 4]) -> Self { Self { + save_version: 5, game_version, save_type: SaveType::World, mods: Vec::new(), @@ -88,7 +92,7 @@ impl BlotterFile { pub fn read(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { read_magic(reader, b"Logic World save")?; @@ -199,6 +203,7 @@ impl BlotterFile { read_magic(reader, b"redstone sux lol")?; Ok(Self { + save_version, game_version, save_type, mods, @@ -208,6 +213,89 @@ impl BlotterFile { 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)?; + } + 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.circuit_states { + CircuitStates::WorldFormat { circuit_states } => { + write_usize(circuit_states.len(), writer)?; + writer.write_all(&circuit_states)?; + } + CircuitStates::SubassemblyFormat { on_states } => { + write_usize(on_states.len(), writer)?; + for &v in on_states { + write_i32(v, writer)?; + } + } + } + + writer.write_all(b"redstone sux lol")?; + Ok(()) + } } #[derive(Debug)] @@ -227,7 +315,7 @@ impl From for Error { fn read_u8(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let mut bytes = [0u8; 1]; reader.read_exact(&mut bytes)?; @@ -236,7 +324,7 @@ where fn read_u16(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let mut bytes = [0u8; 2]; reader.read_exact(&mut bytes)?; @@ -245,7 +333,7 @@ where fn read_i32(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let mut bytes = [0u8; 4]; reader.read_exact(&mut bytes)?; @@ -254,7 +342,7 @@ where fn read_u32(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let mut bytes = [0u8; 4]; reader.read_exact(&mut bytes)?; @@ -263,7 +351,7 @@ where fn read_f32(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let mut bytes = [0u8; 4]; reader.read_exact(&mut bytes)?; @@ -272,14 +360,14 @@ where fn read_usize(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { read_i32(reader)?.try_into().map_err(|_| Error::InvalidSave) } fn read_string(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let string_len = read_usize(reader)?; let bytes = read_bytevec(reader, string_len)?; @@ -311,7 +399,7 @@ where fn read_magic(reader: &mut R, magic_bytes: &[u8]) -> Result<(), Error> where - R: std::io::Read, + R: Read, { let mut header = [0u8; 16]; reader.read_exact(&mut header)?; @@ -323,14 +411,14 @@ where fn read_version(reader: &mut R) -> Result<[i32; 4], Error> where - R: std::io::Read, + R: Read, { read_array(|| read_i32(reader)) } fn read_bytevec(reader: &mut R, len: usize) -> Result, Error> where - R: std::io::Read, + R: Read, { let mut vec = vec![0u8; len]; reader.read_exact(&mut vec)?; @@ -339,7 +427,7 @@ where fn read_peg_address(reader: &mut R) -> Result where - R: std::io::Read, + R: Read, { let peg_kind = read_u8(reader)?; let is_input = match peg_kind { @@ -355,3 +443,74 @@ where 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(()) +}