From 5000d8885e54e0dff285df3804079e55575c8229 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Fri, 26 Sep 2025 17:31:42 -0700 Subject: [PATCH 01/20] binrw generic struct tech --- src/tests.rs | 6 +-- src/v0.rs | 104 +++++++++++++-------------------------------------- 2 files changed, 30 insertions(+), 80 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 5bcc349..bcc15ae 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,4 @@ -use crate::v0::{Error,File,TimedBlockId}; +use crate::v0::{Error,File,Timed}; #[test] fn _1()->Result<(),Error>{ @@ -6,14 +6,14 @@ fn _1()->Result<(),Error>{ let input=std::io::BufReader::new(file); let mut bot_file=File::new(input).unwrap(); println!("header={:?}",bot_file.header); - for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{ + for &Timed{time,event:block_id} in &bot_file.header.offline_blocks_timeline{ println!("offline time={} block_id={:?}",time,block_id); let block_info=bot_file.header.block_info(block_id)?; let _block=bot_file.data.read_block_info(block_info)?; // offline blocks include the following event types: // World, Gravity, Run, Camera, Setting } - for &TimedBlockId{time,block_id} in &bot_file.header.realtime_blocks_timeline{ + for &Timed{time,event:block_id} in &bot_file.header.realtime_blocks_timeline{ println!("realtime time={} block_id={:?}",time,block_id); let block_info=bot_file.header.block_info(block_id)?; let _block=bot_file.data.read_block_info(block_info)?; diff --git a/src/v0.rs b/src/v0.rs index ee754f5..d8e2320 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -69,6 +69,20 @@ impl GameControls{ } } +// generic timed event +#[binrw] +#[brw(little)] +#[derive(Debug)] +pub struct Timed + where + E:for<'a>binrw::BinRead=()>, + E:for<'a>binrw::BinWrite=()>, +{ + #[br(map=read_trey_double)] + pub time:f64, + pub event:E, +} + // input #[binrw] #[brw(little)] @@ -78,13 +92,6 @@ pub struct InputEvent{ pub game_controls:GameControls, pub mouse_pos:Vector2, } -#[binrw] -#[brw(little)] -pub struct TimedInputEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:InputEvent, -} // output bitflags::bitflags!{ @@ -119,13 +126,6 @@ pub struct OutputEvent{ pub velocity:Vector3, pub acceleration:Vector3, } -#[binrw] -#[brw(little)] -pub struct TimedOutputEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:OutputEvent, -} // sound #[binrw] @@ -156,13 +156,6 @@ pub struct SoundEvent{ /// Roblox enum pub material:u32, } -#[binrw] -#[brw(little)] -pub struct TimedSoundEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:SoundEvent, -} // world #[binrw] @@ -214,13 +207,6 @@ pub enum WorldEvent{ #[brw(magic=3u32)] SetPaused(WorldEventSetPaused), } -#[binrw] -#[brw(little)] -pub struct TimedWorldEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:WorldEvent, -} // gravity #[binrw] @@ -228,13 +214,6 @@ pub struct TimedWorldEvent{ pub struct GravityEvent{ pub gravity:Vector3, } -#[binrw] -#[brw(little)] -pub struct TimedGravityEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:GravityEvent, -} // run #[binrw] @@ -305,13 +284,6 @@ pub struct RunEvent{ pub mode:Mode, pub flag_reason:FlagReason, } -#[binrw] -#[brw(little)] -pub struct TimedRunEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:RunEvent, -} // camera #[binrw] @@ -329,13 +301,6 @@ pub struct CameraEvent{ pub camera_event_type:CameraEventType, pub value:Vector3, } -#[binrw] -#[brw(little)] -pub struct TimedCameraEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:CameraEvent, -} // setting #[binrw] @@ -360,24 +325,17 @@ pub struct SettingEvent{ #[br(map=read_trey_double)] pub value:f64, } -#[binrw] -#[brw(little)] -pub struct TimedSettingEvent{ - #[br(map=read_trey_double)] - pub time:f64, - pub event:SettingEvent, -} #[derive(Default)] pub struct Block{ - pub input_events:Vec, - pub output_events:Vec, - pub sound_events:Vec, - pub world_events:Vec, - pub gravity_events:Vec, - pub run_events:Vec, - pub camera_events:Vec, - pub setting_events:Vec, + pub input_events:Vec>, + pub output_events:Vec>, + pub sound_events:Vec>, + pub world_events:Vec>, + pub gravity_events:Vec>, + pub run_events:Vec>, + pub camera_events:Vec>, + pub setting_events:Vec>, } #[binrw] @@ -488,20 +446,12 @@ pub struct BlockId(#[br(map=|i:u32|i-1)]u32); #[derive(Debug,Clone,Copy)] pub struct BlockPosition(#[br(map=|i:u32|i-1)]u32); -#[binrw] -#[brw(little)] -#[derive(Debug,Clone,Copy)] -pub struct TimedBlockId{ - #[br(map=read_trey_double)] - pub time:f64, - pub block_id:BlockId, -} -impl PartialEq for TimedBlockId{ +impl PartialEq for Timed{ fn eq(&self,other:&Self)->bool{ self.time.eq(&other.time) } } -impl PartialOrd for TimedBlockId{ +impl PartialOrd for Timed{ fn partial_cmp(&self,other:&Self)->Option{ self.time.partial_cmp(&other.time) } @@ -518,9 +468,9 @@ pub struct FileHeader{ #[br(count=num_offline_blocks+num_realtime_blocks+1)] pub block_positions:Vec, #[br(count=num_offline_blocks)] - pub offline_blocks_timeline:Vec, + pub offline_blocks_timeline:Vec>, #[br(count=num_realtime_blocks)] - pub realtime_blocks_timeline:Vec, + pub realtime_blocks_timeline:Vec>, } pub struct BlockInfo{ start:u32, @@ -589,7 +539,7 @@ impl File{ self.header.realtime_blocks_timeline.iter(), ); let mut big_block=Block::default(); - for &TimedBlockId{time:_,block_id} in block_iter{ + for &Timed{time:_,event:block_id} in block_iter{ let block_info=self.header.block_info(block_id)?; self.data.read_block_info_into_block(block_info,&mut big_block)?; } -- 2.49.1 From 9884552b6a64565c9964e8621f0e1f655f6c4672 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Fri, 26 Sep 2025 18:42:18 -0700 Subject: [PATCH 02/20] split header --- src/v0.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index d8e2320..c247aef 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -465,18 +465,24 @@ pub struct FileHeader{ pub file_version:u32, pub num_offline_blocks:u32, pub num_realtime_blocks:u32, +} +#[binrw] +#[brw(little)] +#[derive(Debug)] +#[br(import(num_offline_blocks:u32,num_realtime_blocks:u32))] +pub struct BlockTimelines{ #[br(count=num_offline_blocks+num_realtime_blocks+1)] - pub block_positions:Vec, + block_positions:Vec, #[br(count=num_offline_blocks)] - pub offline_blocks_timeline:Vec>, + offline_blocks_timeline:Vec>, #[br(count=num_realtime_blocks)] - pub realtime_blocks_timeline:Vec>, + realtime_blocks_timeline:Vec>, } pub struct BlockInfo{ start:u32, length:u32, } -impl FileHeader{ +impl BlockTimelines{ pub fn block_info(&self,BlockId(block_id):BlockId)->Result{ if self.block_positions.len() as u32<=block_id{ return Err(Error::InvalidBlockId(BlockId(block_id))); -- 2.49.1 From 80fa551ccaaed02e1672ff9a4975999b411203f9 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Fri, 26 Sep 2025 18:44:54 -0700 Subject: [PATCH 03/20] builder --- src/v0.rs | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index c247aef..7a169e4 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,4 +1,6 @@ -use binrw::{binrw,BinReaderExt,io::TakeSeekExt}; +use binrw::binrw; +use binrw::BinReaderExt; +use std::collections::VecDeque; // the bit chunks are deposited in reverse fn read_trey_float(bits:u32)->f32{ @@ -528,29 +530,21 @@ impl,It1:Iterator> Iterator for MergeI } } -pub struct File{ - pub header:FileHeader, - pub data:FileData, +/// create this object and feed it data from the file. As you feed data it will evolve into types that can process the file +pub struct FileHeaderBuilder{} +impl FileHeaderBuilder{ + pub fn new()->Self{ + Self{} + } + pub fn try_read_header(&self,data:&[u8])->binrw::BinResult<(usize,BlockTimelinesBuilder)>{ + let mut cursor=std::io::Cursor::new(data); + let header=cursor.read_le()?; + Ok((cursor.position() as usize,BlockTimelinesBuilder{header})) + } } -impl File{ - pub fn new(mut data:R)->Result,binrw::Error>{ - Ok(File{ - header:data.read_le()?, - data:FileData{data}, - }) - } - pub fn read_all(&mut self)->Result{ - let block_iter=MergeIter::new( - self.header.offline_blocks_timeline.iter(), - self.header.realtime_blocks_timeline.iter(), - ); - let mut big_block=Block::default(); - for &Timed{time:_,event:block_id} in block_iter{ - let block_info=self.header.block_info(block_id)?; - self.data.read_block_info_into_block(block_info,&mut big_block)?; - } - Ok(big_block) - } + +pub struct BlockTimelinesBuilder{ + header:FileHeader, } pub struct FileData{ -- 2.49.1 From ee65eb6dfb5ff6772a1c4cae98a0cc38f781a32d Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Fri, 26 Sep 2025 19:22:03 -0700 Subject: [PATCH 04/20] yeah --- src/v0.rs | 156 ++++++++++++++++++++++-------------------------------- 1 file changed, 62 insertions(+), 94 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 7a169e4..e022fc9 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,6 +1,5 @@ use binrw::binrw; use binrw::BinReaderExt; -use std::collections::VecDeque; // the bit chunks are deposited in reverse fn read_trey_float(bits:u32)->f32{ @@ -369,7 +368,7 @@ struct EventChunkHeader{ } // binread args tech has been further refined -fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:&mut R,events:&mut Vec,num_events:usize)->binrw::BinResult<()>{ +fn read_data_into_events_exact<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:&mut R,events:&mut Vec,num_events:usize)->binrw::BinResult<()>{ // there is only supposed to be at most one of each type of event chunk per block, so no need to amortize. events.reserve_exact(num_events); for _ in 0..num_events{ @@ -377,7 +376,7 @@ fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:& } Ok(()) } -fn read_data_into_events_amortized<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:&mut R,events:&mut Vec,num_events:usize)->binrw::BinResult<()>{ +fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:&mut R,events:&mut Vec,num_events:usize)->binrw::BinResult<()>{ // this is used when reading multiple blocks into a single object, so amortize the allocation cost. events.reserve(num_events); for _ in 0..num_events{ @@ -387,39 +386,39 @@ fn read_data_into_events_amortized<'a,R:BinReaderExt,T:binrw::BinRead=( } impl Block{ - fn read(data:R)->binrw::BinResult{ + pub fn read(data:R)->binrw::BinResult{ let mut block=Block::default(); - Block::read_into(data,&mut block)?; + block.read_into_exact(data)?; Ok(block) } - fn read_into(mut data:R,block:&mut Block)->binrw::BinResult<()>{ + pub fn read_into_exact(&mut self,mut data:R)->binrw::BinResult<()>{ // well... this looks error prone while let Ok(event_chunk_header)=data.read_le::(){ match event_chunk_header.event_type{ - EventType::Input=>read_data_into_events(&mut data,&mut block.input_events,event_chunk_header.num_events as usize)?, - EventType::Output=>read_data_into_events(&mut data,&mut block.output_events,event_chunk_header.num_events as usize)?, - EventType::Sound=>read_data_into_events(&mut data,&mut block.sound_events,event_chunk_header.num_events as usize)?, - EventType::World=>read_data_into_events(&mut data,&mut block.world_events,event_chunk_header.num_events as usize)?, - EventType::Gravity=>read_data_into_events(&mut data,&mut block.gravity_events,event_chunk_header.num_events as usize)?, - EventType::Run=>read_data_into_events(&mut data,&mut block.run_events,event_chunk_header.num_events as usize)?, - EventType::Camera=>read_data_into_events(&mut data,&mut block.camera_events,event_chunk_header.num_events as usize)?, - EventType::Setting=>read_data_into_events(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?, + EventType::Input=>read_data_into_events_exact(&mut data,&mut self.input_events,event_chunk_header.num_events as usize)?, + EventType::Output=>read_data_into_events_exact(&mut data,&mut self.output_events,event_chunk_header.num_events as usize)?, + EventType::Sound=>read_data_into_events_exact(&mut data,&mut self.sound_events,event_chunk_header.num_events as usize)?, + EventType::World=>read_data_into_events_exact(&mut data,&mut self.world_events,event_chunk_header.num_events as usize)?, + EventType::Gravity=>read_data_into_events_exact(&mut data,&mut self.gravity_events,event_chunk_header.num_events as usize)?, + EventType::Run=>read_data_into_events_exact(&mut data,&mut self.run_events,event_chunk_header.num_events as usize)?, + EventType::Camera=>read_data_into_events_exact(&mut data,&mut self.camera_events,event_chunk_header.num_events as usize)?, + EventType::Setting=>read_data_into_events_exact(&mut data,&mut self.setting_events,event_chunk_header.num_events as usize)?, } } Ok(()) } - fn read_into_amortized(mut data:R,block:&mut Block)->binrw::BinResult<()>{ + pub fn read_into(&mut self,mut data:R)->binrw::BinResult<()>{ // sad code duplication while let Ok(event_chunk_header)=data.read_le::(){ match event_chunk_header.event_type{ - EventType::Input=>read_data_into_events_amortized(&mut data,&mut block.input_events,event_chunk_header.num_events as usize)?, - EventType::Output=>read_data_into_events_amortized(&mut data,&mut block.output_events,event_chunk_header.num_events as usize)?, - EventType::Sound=>read_data_into_events_amortized(&mut data,&mut block.sound_events,event_chunk_header.num_events as usize)?, - EventType::World=>read_data_into_events_amortized(&mut data,&mut block.world_events,event_chunk_header.num_events as usize)?, - EventType::Gravity=>read_data_into_events_amortized(&mut data,&mut block.gravity_events,event_chunk_header.num_events as usize)?, - EventType::Run=>read_data_into_events_amortized(&mut data,&mut block.run_events,event_chunk_header.num_events as usize)?, - EventType::Camera=>read_data_into_events_amortized(&mut data,&mut block.camera_events,event_chunk_header.num_events as usize)?, - EventType::Setting=>read_data_into_events_amortized(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?, + EventType::Input=>read_data_into_events(&mut data,&mut self.input_events,event_chunk_header.num_events as usize)?, + EventType::Output=>read_data_into_events(&mut data,&mut self.output_events,event_chunk_header.num_events as usize)?, + EventType::Sound=>read_data_into_events(&mut data,&mut self.sound_events,event_chunk_header.num_events as usize)?, + EventType::World=>read_data_into_events(&mut data,&mut self.world_events,event_chunk_header.num_events as usize)?, + EventType::Gravity=>read_data_into_events(&mut data,&mut self.gravity_events,event_chunk_header.num_events as usize)?, + EventType::Run=>read_data_into_events(&mut data,&mut self.run_events,event_chunk_header.num_events as usize)?, + EventType::Camera=>read_data_into_events(&mut data,&mut self.camera_events,event_chunk_header.num_events as usize)?, + EventType::Setting=>read_data_into_events(&mut data,&mut self.setting_events,event_chunk_header.num_events as usize)?, } } Ok(()) @@ -464,9 +463,9 @@ impl PartialOrd for Timed{ #[derive(Debug)] pub struct FileHeader{ #[brw(magic=b"qbot")] - pub file_version:u32, - pub num_offline_blocks:u32, - pub num_realtime_blocks:u32, + file_version:u32, + num_offline_blocks:u32, + num_realtime_blocks:u32, } #[binrw] #[brw(little)] @@ -480,11 +479,13 @@ pub struct BlockTimelines{ #[br(count=num_realtime_blocks)] realtime_blocks_timeline:Vec>, } -pub struct BlockInfo{ - start:u32, - length:u32, -} impl BlockTimelines{ + pub fn offline_blocks(&self)->&[Timed]{ + &self.offline_blocks_timeline + } + pub fn realtime_blocks(&self)->&[Timed]{ + &self.realtime_blocks_timeline + } pub fn block_info(&self,BlockId(block_id):BlockId)->Result{ if self.block_positions.len() as u32<=block_id{ return Err(Error::InvalidBlockId(BlockId(block_id))); @@ -494,78 +495,45 @@ impl BlockTimelines{ Ok(BlockInfo{start,length:end-start}) } } - -struct MergeIter,It1:Iterator>{ - it0:It0, - it1:It1, - item0:Option, - item1:Option, +pub struct BlockInfo{ + start:u32, + length:u32, } -impl,It1:Iterator> MergeIter{ - fn new(mut it0:It0,mut it1:It1)->Self{ - Self{ - item0:it0.next(), - item1:it1.next(), - it0, - it1, - } +impl BlockInfo{ + pub fn start(&self)->u32{ + self.start } -} -impl,It1:Iterator> Iterator for MergeIter{ - type Item=T; - fn next(&mut self)->Option{ - match (&self.item0,&self.item1){ - (None,None)=>None, - (Some(_),None)=>core::mem::replace(&mut self.item0,self.it0.next()), - (None,Some(_))=>core::mem::replace(&mut self.item1,self.it1.next()), - (Some(item0),Some(item1))=>match item0.partial_cmp(item1){ - Some(core::cmp::Ordering::Less) - |Some(core::cmp::Ordering::Equal) - |None - =>core::mem::replace(&mut self.item0,self.it0.next()), - Some(core::cmp::Ordering::Greater) - =>core::mem::replace(&mut self.item1,self.it1.next()), - }, - } + pub fn length(&self)->u32{ + self.length } } /// create this object and feed it data from the file. As you feed data it will evolve into types that can process the file -pub struct FileHeaderBuilder{} -impl FileHeaderBuilder{ - pub fn new()->Self{ - Self{} - } - pub fn try_read_header(&self,data:&[u8])->binrw::BinResult<(usize,BlockTimelinesBuilder)>{ - let mut cursor=std::io::Cursor::new(data); - let header=cursor.read_le()?; - Ok((cursor.position() as usize,BlockTimelinesBuilder{header})) - } -} - pub struct BlockTimelinesBuilder{ header:FileHeader, } +impl BlockTimelinesBuilder{ + pub fn try_new(data:&[u8])->binrw::BinResult<(usize,Self)>{ + let mut cursor=std::io::Cursor::new(data); + let header=cursor.read_le()?; + Ok((cursor.position() as usize,Self{header})) + } + pub fn header(&self)->&FileHeader{ + &self.header + } + pub fn try_read_block_timelines(&self,data:&[u8])->binrw::BinResult<(usize,BlockTimelines)>{ + let mut cursor=std::io::Cursor::new(data); + let block_timelines=cursor.read_le_args((self.header.num_offline_blocks,self.header.num_realtime_blocks))?; + Ok((cursor.position() as usize,block_timelines)) + } +} -pub struct FileData{ - data:R, -} -impl FileData{ - fn data_mut(&mut self)->&mut R{ - &mut self.data - } - fn block_reader(&mut self,block_info:BlockInfo)->Result,Error>{ - self.data.seek(std::io::SeekFrom::Start(block_info.start as u64)).map_err(Error::Seek)?; - Ok(self.data_mut().take_seek(block_info.length as u64)) - } - pub fn read_block_info(&mut self,block_info:BlockInfo)->Result{ - let data=self.block_reader(block_info)?; - let block=Block::read(data).map_err(Error::InvalidData)?; - Ok(block) - } - pub fn read_block_info_into_block(&mut self,block_info:BlockInfo,block:&mut Block)->Result<(),Error>{ - let data=self.block_reader(block_info)?; - Block::read_into_amortized(data,block).map_err(Error::InvalidData)?; - Ok(()) - } +pub fn read_all_to_block(mut data:R)->binrw::BinResult{ + let header:FileHeader=data.read_le()?; + let _block_timelines:BlockTimelines=data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks))?; + let mut block=Block::default(); + for _ in 0..(header.num_offline_blocks+header.num_realtime_blocks){ + block.read_into(&mut data)?; + } + Ok(block) } -- 2.49.1 From 9f162cc63f1e642518c3c1c2bc7449a72c77d871 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Fri, 26 Sep 2025 19:33:13 -0700 Subject: [PATCH 05/20] wrong --- src/v0.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v0.rs b/src/v0.rs index e022fc9..ec2f3e8 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -532,6 +532,7 @@ pub fn read_all_to_block(mut data:R)->binrw::BinResult{ let header:FileHeader=data.read_le()?; let _block_timelines:BlockTimelines=data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks))?; let mut block=Block::default(); + // this is wrong, it reads in file order rather than chronological order for _ in 0..(header.num_offline_blocks+header.num_realtime_blocks){ block.read_into(&mut data)?; } -- 2.49.1 From 7b27cc7135f0ed1be4d0d40d30d67b1c12bdc175 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 18:19:43 -0700 Subject: [PATCH 06/20] simplify builder thing --- src/v0.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index ec2f3e8..40838bd 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -508,22 +508,15 @@ impl BlockInfo{ } } -/// create this object and feed it data from the file. As you feed data it will evolve into types that can process the file -pub struct BlockTimelinesBuilder{ - header:FileHeader, -} -impl BlockTimelinesBuilder{ +impl FileHeader{ pub fn try_new(data:&[u8])->binrw::BinResult<(usize,Self)>{ let mut cursor=std::io::Cursor::new(data); let header=cursor.read_le()?; - Ok((cursor.position() as usize,Self{header})) - } - pub fn header(&self)->&FileHeader{ - &self.header + Ok((cursor.position() as usize,header)) } pub fn try_read_block_timelines(&self,data:&[u8])->binrw::BinResult<(usize,BlockTimelines)>{ let mut cursor=std::io::Cursor::new(data); - let block_timelines=cursor.read_le_args((self.header.num_offline_blocks,self.header.num_realtime_blocks))?; + let block_timelines=cursor.read_le_args((self.num_offline_blocks,self.num_realtime_blocks))?; Ok((cursor.position() as usize,block_timelines)) } } -- 2.49.1 From 6b472e81e38d398509ba44ccc67e530c9a50bc47 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 19:58:49 -0700 Subject: [PATCH 07/20] different --- src/v0.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 40838bd..ef31b90 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -467,6 +467,11 @@ pub struct FileHeader{ num_offline_blocks:u32, num_realtime_blocks:u32, } +impl FileHeader{ + pub fn from_reader(mut data:R)->binrw::BinResult{ + data.read_le() + } +} #[binrw] #[brw(little)] #[derive(Debug)] @@ -495,6 +500,11 @@ impl BlockTimelines{ Ok(BlockInfo{start,length:end-start}) } } +impl BlockTimelines{ + pub fn from_reader(header:&FileHeader,mut data:R)->binrw::BinResult{ + data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks)) + } +} pub struct BlockInfo{ start:u32, length:u32, @@ -508,19 +518,6 @@ impl BlockInfo{ } } -impl FileHeader{ - pub fn try_new(data:&[u8])->binrw::BinResult<(usize,Self)>{ - let mut cursor=std::io::Cursor::new(data); - let header=cursor.read_le()?; - Ok((cursor.position() as usize,header)) - } - pub fn try_read_block_timelines(&self,data:&[u8])->binrw::BinResult<(usize,BlockTimelines)>{ - let mut cursor=std::io::Cursor::new(data); - let block_timelines=cursor.read_le_args((self.num_offline_blocks,self.num_realtime_blocks))?; - Ok((cursor.position() as usize,block_timelines)) - } -} - pub fn read_all_to_block(mut data:R)->binrw::BinResult{ let header:FileHeader=data.read_le()?; let _block_timelines:BlockTimelines=data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks))?; -- 2.49.1 From 2209b5218b5c1e5d9f41ed0e43fcc3fa16052a41 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 19:58:56 -0700 Subject: [PATCH 08/20] itertools --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/v0.rs | 15 +++++++++------ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0451fac..93dcea8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "owo-colors" version = "3.5.0" @@ -80,6 +89,7 @@ version = "0.3.1" dependencies = [ "binrw", "bitflags", + "itertools", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3723d15..ddae69f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] binrw = "0.14.1" bitflags = "2.6.0" +itertools = "0.14.0" diff --git a/src/v0.rs b/src/v0.rs index ef31b90..24e75ed 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -518,13 +518,16 @@ impl BlockInfo{ } } -pub fn read_all_to_block(mut data:R)->binrw::BinResult{ - let header:FileHeader=data.read_le()?; - let _block_timelines:BlockTimelines=data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks))?; +use std::io::SeekFrom; +use binrw::io::TakeSeekExt; +pub fn read_all_to_block(mut data:R)->Result{ + let header:FileHeader=data.read_le().map_err(Error::InvalidData)?; + let block_timelines:BlockTimelines=data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks)).map_err(Error::InvalidData)?; let mut block=Block::default(); - // this is wrong, it reads in file order rather than chronological order - for _ in 0..(header.num_offline_blocks+header.num_realtime_blocks){ - block.read_into(&mut data)?; + for timed in itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()){ + let block_info=block_timelines.block_info(timed.event)?; + data.seek(SeekFrom::Start(block_info.start() as u64)).map_err(Error::Seek)?; + block.read_into((&mut data).take_seek(block_info.length() as u64)).map_err(Error::InvalidData)?; } Ok(block) } -- 2.49.1 From f94ea9f7ee230249a8d05e1c1f1dc7d3b75df341 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:00:48 -0700 Subject: [PATCH 09/20] use constructors --- src/v0.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 24e75ed..24594aa 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -521,8 +521,8 @@ impl BlockInfo{ use std::io::SeekFrom; use binrw::io::TakeSeekExt; pub fn read_all_to_block(mut data:R)->Result{ - let header:FileHeader=data.read_le().map_err(Error::InvalidData)?; - let block_timelines:BlockTimelines=data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks)).map_err(Error::InvalidData)?; + let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?; + let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?; let mut block=Block::default(); for timed in itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()){ let block_info=block_timelines.block_info(timed.event)?; -- 2.49.1 From cc8899029fb2f385d884cb3638cd1d0a1d4e2adc Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:03:57 -0700 Subject: [PATCH 10/20] ta --- src/v0.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index 24594aa..1bc9d81 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -527,7 +527,8 @@ pub fn read_all_to_block(mut data:R)->Result{ for timed in itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()){ let block_info=block_timelines.block_info(timed.event)?; data.seek(SeekFrom::Start(block_info.start() as u64)).map_err(Error::Seek)?; - block.read_into((&mut data).take_seek(block_info.length() as u64)).map_err(Error::InvalidData)?; + let take_seek=(&mut data).take_seek(block_info.length() as u64); + block.read_into(take_seek).map_err(Error::InvalidData)?; } Ok(block) } -- 2.49.1 From 3b888f31812aeae2dd6e128ae7692de4d49bba67 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:12:03 -0700 Subject: [PATCH 11/20] rename fn --- src/v0.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index 1bc9d81..bff8c1a 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -386,7 +386,7 @@ fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:& } impl Block{ - pub fn read(data:R)->binrw::BinResult{ + pub fn from_reader(data:R)->binrw::BinResult{ let mut block=Block::default(); block.read_into_exact(data)?; Ok(block) -- 2.49.1 From a41c66480d2f34325910e653b9829e100392c85e Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:19:05 -0700 Subject: [PATCH 12/20] create adapter --- src/v0.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index bff8c1a..d854521 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,4 +1,6 @@ +use std::io::{SeekFrom,Error as IoError}; use binrw::binrw; +use binrw::io::{TakeSeek,TakeSeekExt}; use binrw::BinReaderExt; // the bit chunks are deposited in reverse @@ -428,7 +430,7 @@ impl Block{ #[derive(Debug)] pub enum Error{ InvalidBlockId(BlockId), - Seek(std::io::Error), + Seek(IoError), InvalidData(binrw::Error), } impl std::fmt::Display for Error{ @@ -516,18 +518,21 @@ impl BlockInfo{ pub fn length(&self)->u32{ self.length } + pub fn take_seek<'a,R:BinReaderExt+TakeSeekExt>(&self,data:&'a mut R)->Result,IoError>{ + data.seek(SeekFrom::Start(self.start() as u64))?; + Ok(data.take_seek(self.length() as u64)) + } } -use std::io::SeekFrom; -use binrw::io::TakeSeekExt; pub fn read_all_to_block(mut data:R)->Result{ let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?; let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?; let mut block=Block::default(); for timed in itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()){ - let block_info=block_timelines.block_info(timed.event)?; - data.seek(SeekFrom::Start(block_info.start() as u64)).map_err(Error::Seek)?; - let take_seek=(&mut data).take_seek(block_info.length() as u64); + let take_seek=block_timelines + .block_info(timed.event)? + .take_seek(&mut data) + .map_err(Error::Seek)?; block.read_into(take_seek).map_err(Error::InvalidData)?; } Ok(block) -- 2.49.1 From 0bf0c9c5ee07bee89c33e3113e7493c2f7895dab Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:18:55 -0700 Subject: [PATCH 13/20] fix tests --- src/tests.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index bcc15ae..3a6e142 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,39 +1,36 @@ -use crate::v0::{Error,File,Timed}; +use crate::v0::{Block,BlockTimelines,FileHeader,Timed}; #[test] -fn _1()->Result<(),Error>{ - let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); - let input=std::io::BufReader::new(file); - let mut bot_file=File::new(input).unwrap(); - println!("header={:?}",bot_file.header); - for &Timed{time,event:block_id} in &bot_file.header.offline_blocks_timeline{ +fn _1(){ + let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); + let mut input=std::io::Cursor::new(file); + let header=FileHeader::from_reader(&mut input).unwrap(); + let timelines=BlockTimelines::from_reader(&header,&mut input).unwrap(); + println!("header={:?}",header); + for &Timed{time,event:block_id} in timelines.offline_blocks(){ println!("offline time={} block_id={:?}",time,block_id); - let block_info=bot_file.header.block_info(block_id)?; - let _block=bot_file.data.read_block_info(block_info)?; + let take_seek=timelines.block_info(block_id).unwrap().take_seek(&mut input).unwrap(); + let _block=Block::from_reader(take_seek).unwrap(); // offline blocks include the following event types: // World, Gravity, Run, Camera, Setting } - for &Timed{time,event:block_id} in &bot_file.header.realtime_blocks_timeline{ + for &Timed{time,event:block_id} in timelines.realtime_blocks(){ println!("realtime time={} block_id={:?}",time,block_id); - let block_info=bot_file.header.block_info(block_id)?; - let _block=bot_file.data.read_block_info(block_info)?; + let take_seek=timelines.block_info(block_id).unwrap().take_seek(&mut input).unwrap(); + let _block=Block::from_reader(take_seek).unwrap(); // realtime blocks include the following event types: // Input, Output, Sound } - - Ok(()) } +use crate::v0::{read_all_to_block,Error}; #[test] fn _2()->Result<(),Error>{ - let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); - let input=std::io::BufReader::new(file); + let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); let t0=std::time::Instant::now(); - let mut bot_file=File::new(input).unwrap(); - - let _block=bot_file.read_all()?; + let _block=read_all_to_block(std::io::Cursor::new(file))?; println!("{:?}",t0.elapsed()); -- 2.49.1 From dd08f536d9aabea8ba7a7013b2c263804af302c6 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:23:00 -0700 Subject: [PATCH 14/20] supertrait --- src/v0.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index d854521..3d2dd49 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -518,7 +518,7 @@ impl BlockInfo{ pub fn length(&self)->u32{ self.length } - pub fn take_seek<'a,R:BinReaderExt+TakeSeekExt>(&self,data:&'a mut R)->Result,IoError>{ + pub fn take_seek<'a,R:BinReaderExt>(&self,data:&'a mut R)->Result,IoError>{ data.seek(SeekFrom::Start(self.start() as u64))?; Ok(data.take_seek(self.length() as u64)) } -- 2.49.1 From 8644fdf54d78b138f444db6a96f9c5dd2522a95b Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:31:56 -0700 Subject: [PATCH 15/20] function thingy --- src/v0.rs | 65 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 3d2dd49..a717974 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -370,17 +370,18 @@ struct EventChunkHeader{ } // binread args tech has been further refined -fn read_data_into_events_exact<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:&mut R,events:&mut Vec,num_events:usize)->binrw::BinResult<()>{ - // there is only supposed to be at most one of each type of event chunk per block, so no need to amortize. - events.reserve_exact(num_events); - for _ in 0..num_events{ - events.push(data.read_le()?); - } - Ok(()) -} -fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:&mut R,events:&mut Vec,num_events:usize)->binrw::BinResult<()>{ - // this is used when reading multiple blocks into a single object, so amortize the allocation cost. - events.reserve(num_events); +fn read_data_into_events<'a,R,T,F>( + data:&mut R, + events:&mut Vec, + num_events:usize, + reserve_fn:F, +)->binrw::BinResult<()> + where + R:BinReaderExt, + T:binrw::BinRead=()>, + F:Fn(&mut Vec,usize), +{ + reserve_fn(events,num_events); for _ in 0..num_events{ events.push(data.read_le()?); } @@ -390,37 +391,39 @@ fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead=()>>(data:& impl Block{ pub fn from_reader(data:R)->binrw::BinResult{ let mut block=Block::default(); - block.read_into_exact(data)?; + // there is only supposed to be at most one of each type + // of event chunk per block, so allocate the size exactly. + block.extend_from_reader_exact(data)?; Ok(block) } - pub fn read_into_exact(&mut self,mut data:R)->binrw::BinResult<()>{ + pub fn extend_from_reader_exact(&mut self,mut data:R)->binrw::BinResult<()>{ // well... this looks error prone while let Ok(event_chunk_header)=data.read_le::(){ match event_chunk_header.event_type{ - EventType::Input=>read_data_into_events_exact(&mut data,&mut self.input_events,event_chunk_header.num_events as usize)?, - EventType::Output=>read_data_into_events_exact(&mut data,&mut self.output_events,event_chunk_header.num_events as usize)?, - EventType::Sound=>read_data_into_events_exact(&mut data,&mut self.sound_events,event_chunk_header.num_events as usize)?, - EventType::World=>read_data_into_events_exact(&mut data,&mut self.world_events,event_chunk_header.num_events as usize)?, - EventType::Gravity=>read_data_into_events_exact(&mut data,&mut self.gravity_events,event_chunk_header.num_events as usize)?, - EventType::Run=>read_data_into_events_exact(&mut data,&mut self.run_events,event_chunk_header.num_events as usize)?, - EventType::Camera=>read_data_into_events_exact(&mut data,&mut self.camera_events,event_chunk_header.num_events as usize)?, - EventType::Setting=>read_data_into_events_exact(&mut data,&mut self.setting_events,event_chunk_header.num_events as usize)?, + EventType::Input=>read_data_into_events(&mut data,&mut self.input_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::Output=>read_data_into_events(&mut data,&mut self.output_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::Sound=>read_data_into_events(&mut data,&mut self.sound_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::World=>read_data_into_events(&mut data,&mut self.world_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::Gravity=>read_data_into_events(&mut data,&mut self.gravity_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::Run=>read_data_into_events(&mut data,&mut self.run_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::Camera=>read_data_into_events(&mut data,&mut self.camera_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, + EventType::Setting=>read_data_into_events(&mut data,&mut self.setting_events,event_chunk_header.num_events as usize,Vec::reserve_exact)?, } } Ok(()) } - pub fn read_into(&mut self,mut data:R)->binrw::BinResult<()>{ + pub fn extend_from_reader(&mut self,mut data:R)->binrw::BinResult<()>{ // sad code duplication while let Ok(event_chunk_header)=data.read_le::(){ match event_chunk_header.event_type{ - EventType::Input=>read_data_into_events(&mut data,&mut self.input_events,event_chunk_header.num_events as usize)?, - EventType::Output=>read_data_into_events(&mut data,&mut self.output_events,event_chunk_header.num_events as usize)?, - EventType::Sound=>read_data_into_events(&mut data,&mut self.sound_events,event_chunk_header.num_events as usize)?, - EventType::World=>read_data_into_events(&mut data,&mut self.world_events,event_chunk_header.num_events as usize)?, - EventType::Gravity=>read_data_into_events(&mut data,&mut self.gravity_events,event_chunk_header.num_events as usize)?, - EventType::Run=>read_data_into_events(&mut data,&mut self.run_events,event_chunk_header.num_events as usize)?, - EventType::Camera=>read_data_into_events(&mut data,&mut self.camera_events,event_chunk_header.num_events as usize)?, - EventType::Setting=>read_data_into_events(&mut data,&mut self.setting_events,event_chunk_header.num_events as usize)?, + EventType::Input=>read_data_into_events(&mut data,&mut self.input_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::Output=>read_data_into_events(&mut data,&mut self.output_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::Sound=>read_data_into_events(&mut data,&mut self.sound_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::World=>read_data_into_events(&mut data,&mut self.world_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::Gravity=>read_data_into_events(&mut data,&mut self.gravity_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::Run=>read_data_into_events(&mut data,&mut self.run_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::Camera=>read_data_into_events(&mut data,&mut self.camera_events,event_chunk_header.num_events as usize,Vec::reserve)?, + EventType::Setting=>read_data_into_events(&mut data,&mut self.setting_events,event_chunk_header.num_events as usize,Vec::reserve)?, } } Ok(()) @@ -533,7 +536,7 @@ pub fn read_all_to_block(mut data:R)->Result{ .block_info(timed.event)? .take_seek(&mut data) .map_err(Error::Seek)?; - block.read_into(take_seek).map_err(Error::InvalidData)?; + block.extend_from_reader(take_seek).map_err(Error::InvalidData)?; } Ok(block) } -- 2.49.1 From 362fc4e5a5d2f9114ef03c71cf95b241375d86c7 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:43:45 -0700 Subject: [PATCH 16/20] do not protect user from themselves, make it like normal take seek --- src/v0.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index a717974..24bde96 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -521,7 +521,7 @@ impl BlockInfo{ pub fn length(&self)->u32{ self.length } - pub fn take_seek<'a,R:BinReaderExt>(&self,data:&'a mut R)->Result,IoError>{ + pub fn take_seek(&self,mut data:R)->Result,IoError>{ data.seek(SeekFrom::Start(self.start() as u64))?; Ok(data.take_seek(self.length() as u64)) } -- 2.49.1 From 0db4cfe03db64b66ffb64c6d0298c51198e78d0f Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:48:34 -0700 Subject: [PATCH 17/20] itertools feature --- Cargo.toml | 5 ++++- src/tests.rs | 2 ++ src/v0.rs | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ddae69f..c443ae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,7 @@ edition = "2021" [dependencies] binrw = "0.14.1" bitflags = "2.6.0" -itertools = "0.14.0" +itertools = { version = "0.14.0", optional = true } + +[features] +itertools = ["dep:itertools"] diff --git a/src/tests.rs b/src/tests.rs index 3a6e142..3d3c310 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -23,8 +23,10 @@ fn _1(){ } } +#[cfg(feature="itertools")] use crate::v0::{read_all_to_block,Error}; #[test] +#[cfg(feature="itertools")] fn _2()->Result<(),Error>{ let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); diff --git a/src/v0.rs b/src/v0.rs index 24bde96..6b5e595 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -527,6 +527,7 @@ impl BlockInfo{ } } +#[cfg(feature="itertools")] pub fn read_all_to_block(mut data:R)->Result{ let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?; let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?; -- 2.49.1 From a6e840293754027118d1bea0caaec1c31196da83 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 20:55:39 -0700 Subject: [PATCH 18/20] fix readme --- README.md | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index ebdf7a7..9ae6896 100644 --- a/README.md +++ b/README.md @@ -3,30 +3,37 @@ Roblox Bhop/Surf Bot File Format ## Example +Read the whole file with the itertools feature enabled: ```rust -use strafesnet_roblox_bot_file::{File,TimedBlockId}; +use strafesnet_roblox_bot_file::v0::read_all_to_block; -let file=std::fs::File::open("bot_file")?; -let input=std::io::BufReader::new(file); -let mut bot_file=File::new(input)?; +let file=std::fs::read("bot_file")?; +let mut input=std::io::Cursor::new(file); -// read the whole file -let block=bot_file.read_all()?; +let block=read_all_to_block(&mut input)?; +``` +Or decode individual blocks using block location info: +```rust +use strafesnet_roblox_bot_file::v0::{Block,BlockTimelines,FileHeader}; -// or do data streaming block by block -for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{ - // header is immutably borrowed - // while data is mutably borrowed - let block_info=bot_file.header.block_info(block_id)?; - let block=bot_file.data.read_block_info(block_info)?; - // offline blocks include the following event types: - // World, Gravity, Run, Camera, Setting +let file=std::fs::read("bot_file")?; +let mut input=std::io::Cursor::new(file); + +let header=FileHeader::from_reader(&mut input)?; +let timelines=BlockTimelines::from_reader(&header,&mut input)?; + +// offline blocks include the following event types: +// World, Gravity, Run, Camera, Setting +for timed in timelines.offline_blocks(){ + let block_info=timelines.block_info(timed.event)?; + let block=Block::from_reader(block_info.take_seek(&mut input)?)?; } -for &TimedBlockId{time,block_id} in &bot_file.header.realtime_blocks_timeline{ - let block_info=bot_file.header.block_info(block_id)?; - let block=bot_file.data.read_block_info(block_info)?; - // realtime blocks include the following event types: - // Input, Output, Sound + +// realtime blocks include the following event types: +// Input, Output, Sound +for timed in timelines.realtime_blocks(){ + let block_info=timelines.block_info(timed.event)?; + let block=Block::from_reader(block_info.take_seek(&mut input)?)?; } ``` -- 2.49.1 From 1c3c4008288c157f777b057dc9816fc27b13d1bd Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 21:02:55 -0700 Subject: [PATCH 19/20] itertools default feature --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index c443ae0..4456a1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ bitflags = "2.6.0" itertools = { version = "0.14.0", optional = true } [features] +default = ["itertools"] itertools = ["dep:itertools"] -- 2.49.1 From f8e53c7dfd6848c8e32dac5d26e90a8877bec388 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 28 Sep 2025 21:18:45 -0700 Subject: [PATCH 20/20] add docs --- src/v0.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/v0.rs b/src/v0.rs index 6b5e595..ec8f4b0 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -396,6 +396,8 @@ impl Block{ block.extend_from_reader_exact(data)?; Ok(block) } + /// Read a complete data block and append the elements to the timelines in this block. + /// Reserves exactly enough information for the new data. pub fn extend_from_reader_exact(&mut self,mut data:R)->binrw::BinResult<()>{ // well... this looks error prone while let Ok(event_chunk_header)=data.read_le::(){ @@ -412,6 +414,7 @@ impl Block{ } Ok(()) } + /// Read a complete data block and append the elements to the timelines in this block. pub fn extend_from_reader(&mut self,mut data:R)->binrw::BinResult<()>{ // sad code duplication while let Ok(event_chunk_header)=data.read_le::(){ @@ -521,12 +524,15 @@ impl BlockInfo{ pub fn length(&self)->u32{ self.length } + /// Create an adapter which seeks to the block start and reads at most the block length. pub fn take_seek(&self,mut data:R)->Result,IoError>{ data.seek(SeekFrom::Start(self.start() as u64))?; Ok(data.take_seek(self.length() as u64)) } } +/// Read the entire file and combine the timelines into a single Block. +/// Note that this reads the blocks in chronological order, not the order they appear in the file, so there is some seeking involved. #[cfg(feature="itertools")] pub fn read_all_to_block(mut data:R)->Result{ let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?; -- 2.49.1