use base64::{decode_config_buf, STANDARD}; use std::io::Read; // TODO(maybe) parse iteratively instead of one-shot /// A collection of cube data /// /// This holds all data parsed from cube_data and colour_data. /// Individual Cube structs can be iterated through. #[derive(Clone)] pub struct Cubes { /// Parsed cube count (the first 32 bits of data parsed to `u32`) pub provided_len: u32, cubes: Vec, } 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 general, you should use `Cubes::from(data)` instead of this lower-level function. pub fn parse(cube_data: &mut Vec, colour_data: &mut Vec) -> Result { // read first 4 bytes (cube count) from both arrays and make sure they match let mut cube_buf = [0; 4]; let mut colour_buf = [0; 4]; let mut cube_slice = cube_data.as_slice(); let mut colour_slice = colour_data.as_slice(); if let Ok(len) = cube_slice.read(&mut cube_buf) { if len != 4 { //println!("Failed reading cube_data len"); return Err(()); } } else { //println!("Failed to read cube_data"); return Err(()); } if let Ok(len) = colour_slice.read(&mut colour_buf) { if len != 4 { //println!("Failed reading colour_data len"); return Err(()); } } else { //println!("Failed to read colour_data"); return Err(()); } if !(cube_buf[0] == colour_buf[0] && cube_buf[1] == colour_buf[1] && cube_buf[2] == colour_buf[2] && cube_buf[3] == colour_buf[3]) { //println!("Values do not match"); return Err(()); } let mut cube_i = 4; let mut colour_i = 4; let mut parsed_cubes = Vec::with_capacity(cube_data.len() / 8); while cube_i < cube_data.len() && colour_i < colour_data.len() { let mut new_cube = Cube::default(); if let Ok(cube_add) = new_cube.parse_cube_data(&mut cube_slice) { if let Ok(colour_add) = new_cube.parse_colour_data(&mut colour_slice) { cube_i += cube_add; colour_i += colour_add; parsed_cubes.push(new_cube); } else { // colour_data read error return Err(()); } } else { // cube_data read error return Err(()); } } Ok(Self { provided_len: u32::from_le_bytes(cube_buf), cubes: parsed_cubes, }) } /// Dump the raw bytes containing block data for a Robocraft bot. /// /// The first tuple item is cube data, and the second item is colour data. /// Use this to write a modified robot to file. /// This is the inverse of `Cubes::parse(...)`. /// /// I'm not sure what this would actually be useful for... pub fn dump(&self) -> (Vec, Vec) { let mut cube_buf = Vec::new(); let mut colour_buf = Vec::new(); cube_buf.extend(&self.provided_len.to_le_bytes()); colour_buf.extend(&self.provided_len.to_le_bytes()); for c in self.into_iter() { cube_buf.extend(&c.dump_cube_data()); colour_buf.extend(&c.dump_colour_data()); } (cube_buf, colour_buf) } /// Get the actual amount of cubes. /// /// This differs from `provided_len` by being the amount of cubes parsed (successfully), instead of something parsed from block data. /// For any valid robot data, `data.provided_len == data.len()`. pub fn len(&self) -> usize { self.cubes.len() } } impl<'a> std::iter::IntoIterator for &'a Cubes { type Item = &'a Cube; type IntoIter = std::slice::Iter<'a, Cube>; fn into_iter(self) -> Self::IntoIter { self.cubes.iter() } } /// A single block in a Robocraft robot. /// /// From the front of a Robocraft garage bay, looking at the back, all positions are measured from the back bottom right corner. #[derive(Copy, Clone)] pub struct Cube { /// The cube id pub id: u32, /// The cube's x position (left to right) pub x: u8, // left to right /// The cube's y position (bottom to top) pub y: u8, // bottom to top /// The cube's z position (back to front) pub z: u8, // back to front /// The cube's orientation pub orientation: u8, /// The cube's colour, one of the 24 possible colours in Robocraft pub colour: u8, } impl Cube { fn parse_cube_data(&mut self, reader: &mut dyn Read) -> Result { let mut buf = [0; 4]; // read cube id if let Ok(len) = reader.read(&mut buf) { if len != 4 { return Err(()); } self.id = u32::from_le_bytes(buf); } else { return Err(()); } // read x, y, z, orientation if let Ok(len) = reader.read(&mut buf) { if len != 4 { return Err(()); } self.x = buf[0]; self.y = buf[1]; self.z = buf[2]; self.orientation = buf[3]; } else { return Err(()); } Ok(8) } fn parse_colour_data(&mut self, reader: &mut dyn Read) -> Result { let mut buf = [0; 4]; if let Ok(len) = reader.read(&mut buf) { if len != 4 { return Err(()); } self.colour = buf[0]; } else { return Err(()); } Ok(4) } /// Dump the raw cube data as used in the Robocraft CRF. /// /// This is useless by itself, use `Cubes.dump()` for a valid robot. pub fn dump_cube_data(&self) -> [u8; 8] { let id_buf = self.id.to_le_bytes(); [id_buf[0], id_buf[1], id_buf[2], id_buf[3], self.x, self.y, self.z, self.orientation] } /// Dump the raw colour data as used in the Robocraft CRF. /// /// This is useless by itself, use `Cubes.dump()` for a valid robot. pub fn dump_colour_data(&self) -> [u8; 4] { [self.colour, self.x, self.y, self.z] } } impl std::default::Default for Cube { fn default() -> Self { Self { id: 0, x: 0, y: 0, z: 0, orientation: 0, colour: 0, } } } impl std::convert::From for Cubes { fn from(other: crate::robocraft::FactoryRobotGetInfo) -> Self { let mut cube_buf = Vec::new(); let mut colour_buf = Vec::new(); decode_config_buf(other.cube_data, STANDARD, &mut cube_buf).unwrap(); decode_config_buf(other.colour_data, STANDARD, &mut colour_buf).unwrap(); Self::parse(&mut cube_buf, &mut colour_buf).unwrap() } } impl std::convert::From> for Cubes { fn from(other: crate::robocraft::FactoryInfo) -> Self { Self::from(other.response) } } impl std::string::ToString for Cube { fn to_string(&self) -> String { format!("{{x: {}, y: {}, z: {}}} ({})", self.x, self.y, self.z, self.id) } }