use std::{ fmt::Debug, fs::File, io::{Cursor, Read, Write}, path::PathBuf, }; use binrw::{BinRead, BinWrite, BinWriterExt, FilePtr64}; use diff::Diff; use crate::{ field_of::FieldOf, types::{Bool, Field, Rgba, WString, WobbleType, F64, U32}, }; #[derive(BinRead, Debug)] pub struct PenHeader { pub pen_count: u32, #[br(args { inner: binrw::args! { pen_count } })] pub data: FilePtr64, } // Manually implement BinWrite as FilePtr does not support serialization // See: https://github.com/jam1garner/binrw/issues/4 impl BinWrite for PenHeader { type Args<'a> = (); fn write_options( &self, writer: &mut W, endian: binrw::Endian, args: Self::Args<'_>, ) -> binrw::prelude::BinResult<()> { let pen_count: u32 = self.data.pens.len().try_into().unwrap(); pen_count.write_options(writer, endian, args)?; // Write address of data, which is placed after this field let data_offset: u64 = writer.stream_position().unwrap() + 8; data_offset.write_options(writer, endian, args)?; self.data.pens.write_options(writer, endian, args) } } #[derive(BinRead, BinWrite, Debug, Diff, PartialEq)] #[diff(attr( #[derive(Debug, PartialEq)] ))] #[br(import {pen_count: u32})] pub struct Pens { #[br(count = pen_count)] pub pens: Vec, } #[cfg_attr(feature = "default-debug", derive(Debug))] #[derive(BinRead, BinWrite, Clone, Diff, PartialEq)] #[diff(attr( #[derive(Debug, PartialEq)] ))] // #[brw(magic(236u32))] // Number of fields within this struct #[brw(magic(370u32))] // Number of fields within this struct pub struct Pen { pub color: FieldOf, pub name: WString, pub disabled: Bool, pub use_default: Bool, pub loop_count: U32, pub speed: F64, // Changes with wobble relative speed pub power: F64, pub frequency: U32, pub pulse_width: U32, pub start_tc: U32, pub end_tc: U32, pub polygon_tc: U32, pub jump_speed: F64, _unknown_2: [Field; 10], pub laser_off_tc: U32, pub wave: U32, // Only available if continue_mode is false pub pulse_width_2: F64, pub wobble_enable: U32, pub wobble_diameter: F64, pub wobble_distance: F64, _unknown_4: [Field; 8], pub min_jump_tc: U32, pub max_jump_tc: U32, pub jump_limit: F64, _unknown_5: [Field; 2], pub frequency_2: F64, _unknown_6: [Field; 152], pub wobble_type: FieldOf, pub continue_mode: U32, _unknown_7: [Field; 12], pub wobble_diameter_2: F64, // Only with wobble type ellipse _unknown_8: [Field; 26], _unknown_9: [Field; 134], } impl Pen { pub fn read_from_file(path: &PathBuf) -> Self { let mut input: File = File::open(path).expect("Failed to open input file"); let mut buffer: Cursor> = Cursor::new(vec![]); input .read_to_end(buffer.get_mut()) .expect("Failed to read input file"); Pen::read_le(&mut buffer).expect("Failed to deserialize input as object") } pub fn write_to_file(&self, path: &PathBuf) { let mut buffer: Cursor> = Cursor::new(vec![]); buffer.write_le(self).expect("Failed to serialize object"); let mut output: File = File::create(path).expect("Failed to open output file"); output .write_all(buffer.into_inner().as_slice()) .expect("Failed to write to output file"); } } // Custom Debug implementation to only print known fields #[cfg(not(feature = "default-debug"))] impl Debug for Pen { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Pen") .field("color", &self.color) .field("name", &self.name) .field("disabled", &self.disabled) .field("use_default", &self.use_default) .field("loop_count", &self.loop_count) .field("speed", &self.speed) .field("power", &self.power) .field("frequency", &self.frequency) .field("start_tc", &self.start_tc) .field("end_tc", &self.end_tc) .field("polygon_tc", &self.polygon_tc) .field("jump_speed", &self.jump_speed) .field("laser_off_tc", &self.laser_off_tc) .field("wave", &self.wave) .field("wobble_enable", &self.wobble_enable) .field("wobble_diameter", &self.wobble_diameter) .field("wobble_distance", &self.wobble_distance) .field("min_jump_tc", &self.min_jump_tc) .field("max_jump_tc", &self.max_jump_tc) .field("jump_limit", &self.jump_limit) .field("frequency_2", &self.frequency_2) .field("wobble_type", &self.wobble_type) .field("continue_mode", &self.continue_mode) .field("wobble_diameter_2", &self.wobble_diameter_2) .finish() } }