1use std::io::{Read, Write};
2use byteorder::{ReadBytesExt, LittleEndian, WriteBytesExt};
3use thiserror::Error;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub struct Header {
8 pub print_time: u32,
10 pub filament_0_usage: u32,
12 pub filament_1_usage: u32,
14
15 pub multi_extruder_type: u16,
17
18 pub layer_height: u16,
20
21 pub reserved0: u16,
23
24 pub perimeter_shells: u16,
26
27 pub print_speed: u16,
29
30 pub hotbed_temp: u16,
32
33 pub extruder_0_temp: u16,
35
36 pub extruder_1_temp: u16,
38
39 pub reserved1: u16,
41
42}
43
44#[derive(Clone,Debug,Eq,PartialEq)]
45pub struct XGCode {
46 pub header: Header,
47 pub thumbnail: Vec<u8>,
48 pub gcode: Vec<u8>
49}
50
51#[derive(Copy,Clone,Debug,Eq,PartialEq)]
52pub struct XGCodeRef<'a> {
53 pub header: Header,
54 pub thumbnail: &'a [u8],
55 pub gcode: &'a [u8],
56}
57
58
59
60#[derive(Debug,Error)]
61pub enum Error {
62 #[error("Bad magic header")] BadMagic(Box<[u8; 16]>),
63 #[error("Bad header size")] BadHeaderSize(u32),
64 #[error("Thumb size negative")] ThumbSizeNegative(i32),
65 #[error("GCode too big")] ThumbnailTooLarge(usize),
66 #[error("Second goffset not found")] SecondGOffsetNotFound,
67 #[error("Data in reserved field")] DataInReservedField {offset: u16, value: u16},
68 #[error("IO error")] IO(#[from] std::io::Error),
69}
70
71const XGCODE_MAGIC: &'static [u8; 16] = b"xgcode 1.0\n\0\0\0\0\0";
72const THUMB_OFFSET: u32 = 0x3A;
73
74impl XGCode {
75
76 pub fn read<R: Read>(mut source: R) -> Result<Self, Error> {
77 let mut magic = [0; 16];
78 source.read_exact(&mut magic)?;
79
80 if &magic != XGCODE_MAGIC { return Err(Error::BadMagic(Box::new(magic))) }
81
82 let thumb_offset = source.read_u32::<LittleEndian>()?;
83 if thumb_offset != THUMB_OFFSET { return Err(Error::BadHeaderSize(thumb_offset)) }
84
85 let gcode_offset = source.read_u32::<LittleEndian>()?;
86 let thumb_size = (gcode_offset as usize).checked_sub(THUMB_OFFSET as usize)
87 .ok_or(Error::ThumbSizeNegative(gcode_offset as i32 - THUMB_OFFSET as i32))?;
88
89
90 let gcode_offset2 = source.read_u32::<LittleEndian>()?;
91 if gcode_offset != gcode_offset2 { return Err(Error::SecondGOffsetNotFound)}
92
93 let print_time = source.read_u32::<LittleEndian>()?;
94 let filament_0_usage = source.read_u32::<LittleEndian>()?;
95 let filament_1_usage = source.read_u32::<LittleEndian>()?;
96 let multi_extruder_type = source.read_u16::<LittleEndian>()?;
97 let layer_height = source.read_u16::<LittleEndian>()?;
98 let reserved0 = source.read_u16::<LittleEndian>()?;
99 let perimeter_shells = source.read_u16::<LittleEndian>()?;
100 let print_speed = source.read_u16::<LittleEndian>()?;
101 let hotbed_temp = source.read_u16::<LittleEndian>()?;
102 let extruder_0_temp = source.read_u16::<LittleEndian>()?;
103 let extruder_1_temp = source.read_u16::<LittleEndian>()?;
104 let reserved1 = source.read_u16::<LittleEndian>()?;
105
106 let header = Header { print_time, filament_0_usage, filament_1_usage, multi_extruder_type, layer_height, perimeter_shells, print_speed, hotbed_temp, extruder_0_temp, extruder_1_temp, reserved0, reserved1 };
107
108 let mut thumbnail = vec![0; thumb_size];
109 source.read_exact(&mut thumbnail)?;
110
111 let mut gcode = vec![];
112 source.read_to_end(&mut gcode)?;
113
114 Ok(XGCode{ header, thumbnail, gcode })
115
116 }
117
118 pub fn as_ref(&self) -> XGCodeRef {
119 XGCodeRef { header: self.header, thumbnail: &self.thumbnail[..], gcode: &self.gcode[..] }
120 }
121
122 pub fn write<W: Write>(&self, writer: W) -> Result<(), Error> {
123 self.as_ref().write(writer)
124
125
126 }
127
128}
129
130impl<'a> XGCodeRef<'a> {
131 fn write<W: Write>(&self, mut writer: W) -> Result<(), Error> {
132
133 let gcode_offset = THUMB_OFFSET as usize + self.thumbnail.len();
134 if gcode_offset > (u32::MAX as usize) { return Err(Error::ThumbnailTooLarge(self.thumbnail.len()))}
135
136 writer.write_all(XGCODE_MAGIC)?;
137 writer.write_u32::<LittleEndian>(THUMB_OFFSET)?;
138 writer.write_u32::<LittleEndian>(gcode_offset as u32)?;
139 writer.write_u32::<LittleEndian>(gcode_offset as u32)?; writer.write_u32::<LittleEndian>(self.header.print_time)?;
142 writer.write_u32::<LittleEndian>(self.header.filament_0_usage)?;
143 writer.write_u32::<LittleEndian>(self.header.filament_1_usage)?;
144 writer.write_u16::<LittleEndian>(self.header.multi_extruder_type)?;
145 writer.write_u16::<LittleEndian>(self.header.layer_height)?;
146 writer.write_u16::<LittleEndian>(self.header.reserved0)?;
147 writer.write_u16::<LittleEndian>(self.header.perimeter_shells)?;
148 writer.write_u16::<LittleEndian>(self.header.print_speed)?;
149 writer.write_u16::<LittleEndian>(self.header.hotbed_temp)?;
150 writer.write_u16::<LittleEndian>(self.header.extruder_0_temp)?;
151 writer.write_u16::<LittleEndian>(self.header.extruder_1_temp)?;
152 writer.write_u16::<LittleEndian>(self.header.reserved1)?;
153
154 writer.write_all(self.thumbnail)?;
155 writer.write_all(self.gcode)?;
156
157 Ok(())
158 }
159}
160
161#[cfg(test)]
162mod tests {
163
164
165 use crate::XGCode;
166
167 #[test]
168 fn test_sample_file() {
169 let file = include_bytes!("../test/20mm_Box.gx");
170 let parsed = XGCode::read(&mut &file[..]).unwrap();
171
172 assert_eq!(&parsed.thumbnail[..2], b"BM");
174
175 let mut file2 = vec![];
176 parsed.write(&mut file2).unwrap();
177
178
179 assert_eq!(file, &file2[..]);
180
181 }
182
183}