20 Commits

Author SHA1 Message Date
f8e53c7dfd add docs 2025-09-28 21:18:45 -07:00
1c3c400828 itertools default feature 2025-09-28 21:05:20 -07:00
a6e8402937 fix readme 2025-09-28 21:05:20 -07:00
0db4cfe03d itertools feature 2025-09-28 20:55:47 -07:00
362fc4e5a5 do not protect user from themselves, make it like normal take seek 2025-09-28 20:55:47 -07:00
8644fdf54d function thingy 2025-09-28 20:55:47 -07:00
dd08f536d9 supertrait 2025-09-28 20:55:47 -07:00
0bf0c9c5ee fix tests 2025-09-28 20:55:47 -07:00
a41c66480d create adapter 2025-09-28 20:19:07 -07:00
3b888f3181 rename fn 2025-09-28 20:12:03 -07:00
cc8899029f ta 2025-09-28 20:03:57 -07:00
f94ea9f7ee use constructors 2025-09-28 20:00:55 -07:00
2209b5218b itertools 2025-09-28 19:58:56 -07:00
6b472e81e3 different 2025-09-28 19:58:49 -07:00
7b27cc7135 simplify builder thing 2025-09-28 18:19:43 -07:00
9f162cc63f wrong 2025-09-26 19:33:18 -07:00
ee65eb6dfb yeah 2025-09-26 19:26:30 -07:00
80fa551cca builder 2025-09-26 18:44:54 -07:00
9884552b6a split header 2025-09-26 18:42:18 -07:00
5000d8885e binrw generic struct tech 2025-09-26 17:32:39 -07:00
5 changed files with 184 additions and 235 deletions

10
Cargo.lock generated
View File

@@ -50,6 +50,15 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "owo-colors" name = "owo-colors"
version = "3.5.0" version = "3.5.0"
@@ -80,6 +89,7 @@ version = "0.3.1"
dependencies = [ dependencies = [
"binrw", "binrw",
"bitflags", "bitflags",
"itertools",
] ]
[[package]] [[package]]

View File

@@ -6,3 +6,8 @@ edition = "2021"
[dependencies] [dependencies]
binrw = "0.14.1" binrw = "0.14.1"
bitflags = "2.6.0" bitflags = "2.6.0"
itertools = { version = "0.14.0", optional = true }
[features]
default = ["itertools"]
itertools = ["dep:itertools"]

View File

@@ -3,30 +3,37 @@ Roblox Bhop/Surf Bot File Format
## Example ## Example
Read the whole file with the itertools feature enabled:
```rust ```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 file=std::fs::read("bot_file")?;
let input=std::io::BufReader::new(file); let mut input=std::io::Cursor::new(file);
let mut bot_file=File::new(input)?;
// read the whole file let block=read_all_to_block(&mut input)?;
let block=bot_file.read_all()?; ```
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 let file=std::fs::read("bot_file")?;
for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{ let mut input=std::io::Cursor::new(file);
// header is immutably borrowed
// while data is mutably borrowed let header=FileHeader::from_reader(&mut input)?;
let block_info=bot_file.header.block_info(block_id)?; let timelines=BlockTimelines::from_reader(&header,&mut input)?;
let block=bot_file.data.read_block_info(block_info)?;
// offline blocks include the following event types: // offline blocks include the following event types:
// World, Gravity, Run, Camera, Setting // 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)?; // realtime blocks include the following event types:
let block=bot_file.data.read_block_info(block_info)?; // Input, Output, Sound
// realtime blocks include the following event types: for timed in timelines.realtime_blocks(){
// Input, Output, Sound let block_info=timelines.block_info(timed.event)?;
let block=Block::from_reader(block_info.take_seek(&mut input)?)?;
} }
``` ```

View File

@@ -1,39 +1,38 @@
use crate::v0::{Error,File,TimedBlockId}; use crate::v0::{Block,BlockTimelines,FileHeader,Timed};
#[test] #[test]
fn _1()->Result<(),Error>{ fn _1(){
let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
let input=std::io::BufReader::new(file); let mut input=std::io::Cursor::new(file);
let mut bot_file=File::new(input).unwrap(); let header=FileHeader::from_reader(&mut input).unwrap();
println!("header={:?}",bot_file.header); let timelines=BlockTimelines::from_reader(&header,&mut input).unwrap();
for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{ println!("header={:?}",header);
for &Timed{time,event:block_id} in timelines.offline_blocks(){
println!("offline time={} block_id={:?}",time,block_id); println!("offline time={} block_id={:?}",time,block_id);
let block_info=bot_file.header.block_info(block_id)?; let take_seek=timelines.block_info(block_id).unwrap().take_seek(&mut input).unwrap();
let _block=bot_file.data.read_block_info(block_info)?; let _block=Block::from_reader(take_seek).unwrap();
// offline blocks include the following event types: // offline blocks include the following event types:
// World, Gravity, Run, Camera, Setting // World, Gravity, Run, Camera, Setting
} }
for &TimedBlockId{time,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); println!("realtime time={} block_id={:?}",time,block_id);
let block_info=bot_file.header.block_info(block_id)?; let take_seek=timelines.block_info(block_id).unwrap().take_seek(&mut input).unwrap();
let _block=bot_file.data.read_block_info(block_info)?; let _block=Block::from_reader(take_seek).unwrap();
// realtime blocks include the following event types: // realtime blocks include the following event types:
// Input, Output, Sound // Input, Output, Sound
} }
Ok(())
} }
#[cfg(feature="itertools")]
use crate::v0::{read_all_to_block,Error};
#[test] #[test]
#[cfg(feature="itertools")]
fn _2()->Result<(),Error>{ fn _2()->Result<(),Error>{
let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
let input=std::io::BufReader::new(file);
let t0=std::time::Instant::now(); let t0=std::time::Instant::now();
let mut bot_file=File::new(input).unwrap(); let _block=read_all_to_block(std::io::Cursor::new(file))?;
let _block=bot_file.read_all()?;
println!("{:?}",t0.elapsed()); println!("{:?}",t0.elapsed());

322
src/v0.rs
View File

@@ -1,4 +1,7 @@
use binrw::{binrw,BinReaderExt,io::TakeSeekExt}; 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 // the bit chunks are deposited in reverse
fn read_trey_float(bits:u32)->f32{ fn read_trey_float(bits:u32)->f32{
@@ -69,6 +72,20 @@ impl GameControls{
} }
} }
// generic timed event
#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct Timed<E>
where
E:for<'a>binrw::BinRead<Args<'a>=()>,
E:for<'a>binrw::BinWrite<Args<'a>=()>,
{
#[br(map=read_trey_double)]
pub time:f64,
pub event:E,
}
// input // input
#[binrw] #[binrw]
#[brw(little)] #[brw(little)]
@@ -78,13 +95,6 @@ pub struct InputEvent{
pub game_controls:GameControls, pub game_controls:GameControls,
pub mouse_pos:Vector2, pub mouse_pos:Vector2,
} }
#[binrw]
#[brw(little)]
pub struct TimedInputEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:InputEvent,
}
// output // output
bitflags::bitflags!{ bitflags::bitflags!{
@@ -119,13 +129,6 @@ pub struct OutputEvent{
pub velocity:Vector3, pub velocity:Vector3,
pub acceleration:Vector3, pub acceleration:Vector3,
} }
#[binrw]
#[brw(little)]
pub struct TimedOutputEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:OutputEvent,
}
// sound // sound
#[binrw] #[binrw]
@@ -156,13 +159,6 @@ pub struct SoundEvent{
/// Roblox enum /// Roblox enum
pub material:u32, pub material:u32,
} }
#[binrw]
#[brw(little)]
pub struct TimedSoundEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:SoundEvent,
}
// world // world
#[binrw] #[binrw]
@@ -214,13 +210,6 @@ pub enum WorldEvent{
#[brw(magic=3u32)] #[brw(magic=3u32)]
SetPaused(WorldEventSetPaused), SetPaused(WorldEventSetPaused),
} }
#[binrw]
#[brw(little)]
pub struct TimedWorldEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:WorldEvent,
}
// gravity // gravity
#[binrw] #[binrw]
@@ -228,13 +217,6 @@ pub struct TimedWorldEvent{
pub struct GravityEvent{ pub struct GravityEvent{
pub gravity:Vector3, pub gravity:Vector3,
} }
#[binrw]
#[brw(little)]
pub struct TimedGravityEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:GravityEvent,
}
// run // run
#[binrw] #[binrw]
@@ -305,13 +287,6 @@ pub struct RunEvent{
pub mode:Mode, pub mode:Mode,
pub flag_reason:FlagReason, pub flag_reason:FlagReason,
} }
#[binrw]
#[brw(little)]
pub struct TimedRunEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:RunEvent,
}
// camera // camera
#[binrw] #[binrw]
@@ -329,13 +304,6 @@ pub struct CameraEvent{
pub camera_event_type:CameraEventType, pub camera_event_type:CameraEventType,
pub value:Vector3, pub value:Vector3,
} }
#[binrw]
#[brw(little)]
pub struct TimedCameraEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:CameraEvent,
}
// setting // setting
#[binrw] #[binrw]
@@ -360,24 +328,17 @@ pub struct SettingEvent{
#[br(map=read_trey_double)] #[br(map=read_trey_double)]
pub value:f64, pub value:f64,
} }
#[binrw]
#[brw(little)]
pub struct TimedSettingEvent{
#[br(map=read_trey_double)]
pub time:f64,
pub event:SettingEvent,
}
#[derive(Default)] #[derive(Default)]
pub struct Block{ pub struct Block{
pub input_events:Vec<TimedInputEvent>, pub input_events:Vec<Timed<InputEvent>>,
pub output_events:Vec<TimedOutputEvent>, pub output_events:Vec<Timed<OutputEvent>>,
pub sound_events:Vec<TimedSoundEvent>, pub sound_events:Vec<Timed<SoundEvent>>,
pub world_events:Vec<TimedWorldEvent>, pub world_events:Vec<Timed<WorldEvent>>,
pub gravity_events:Vec<TimedGravityEvent>, pub gravity_events:Vec<Timed<GravityEvent>>,
pub run_events:Vec<TimedRunEvent>, pub run_events:Vec<Timed<RunEvent>>,
pub camera_events:Vec<TimedCameraEvent>, pub camera_events:Vec<Timed<CameraEvent>>,
pub setting_events:Vec<TimedSettingEvent>, pub setting_events:Vec<Timed<SettingEvent>>,
} }
#[binrw] #[binrw]
@@ -409,17 +370,18 @@ struct EventChunkHeader{
} }
// binread args tech has been further refined // binread args tech has been further refined
fn read_data_into_events<'a,R:BinReaderExt,T:binrw::BinRead<Args<'a>=()>>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()>{ fn read_data_into_events<'a,R,T,F>(
// there is only supposed to be at most one of each type of event chunk per block, so no need to amortize. data:&mut R,
events.reserve_exact(num_events); events:&mut Vec<T>,
for _ in 0..num_events{ num_events:usize,
events.push(data.read_le()?); reserve_fn:F,
} )->binrw::BinResult<()>
Ok(()) where
} R:BinReaderExt,
fn read_data_into_events_amortized<'a,R:BinReaderExt,T:binrw::BinRead<Args<'a>=()>>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()>{ T:binrw::BinRead<Args<'a>=()>,
// this is used when reading multiple blocks into a single object, so amortize the allocation cost. F:Fn(&mut Vec<T>,usize),
events.reserve(num_events); {
reserve_fn(events,num_events);
for _ in 0..num_events{ for _ in 0..num_events{
events.push(data.read_le()?); events.push(data.read_le()?);
} }
@@ -427,39 +389,44 @@ fn read_data_into_events_amortized<'a,R:BinReaderExt,T:binrw::BinRead<Args<'a>=(
} }
impl Block{ impl Block{
fn read<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{ pub fn from_reader<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{
let mut block=Block::default(); let mut block=Block::default();
Block::read_into(data,&mut block)?; // 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) Ok(block)
} }
fn read_into<R:BinReaderExt>(mut data:R,block:&mut Block)->binrw::BinResult<()>{ /// 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<R:BinReaderExt>(&mut self,mut data:R)->binrw::BinResult<()>{
// well... this looks error prone // well... this looks error prone
while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){ while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
match event_chunk_header.event_type{ 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::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 block.output_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,Vec::reserve_exact)?,
EventType::Sound=>read_data_into_events(&mut data,&mut block.sound_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,Vec::reserve_exact)?,
EventType::World=>read_data_into_events(&mut data,&mut block.world_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,Vec::reserve_exact)?,
EventType::Gravity=>read_data_into_events(&mut data,&mut block.gravity_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,Vec::reserve_exact)?,
EventType::Run=>read_data_into_events(&mut data,&mut block.run_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,Vec::reserve_exact)?,
EventType::Camera=>read_data_into_events(&mut data,&mut block.camera_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,Vec::reserve_exact)?,
EventType::Setting=>read_data_into_events(&mut data,&mut block.setting_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,Vec::reserve_exact)?,
} }
} }
Ok(()) Ok(())
} }
fn read_into_amortized<R:BinReaderExt>(mut data:R,block:&mut Block)->binrw::BinResult<()>{ /// Read a complete data block and append the elements to the timelines in this block.
pub fn extend_from_reader<R:BinReaderExt>(&mut self,mut data:R)->binrw::BinResult<()>{
// sad code duplication // sad code duplication
while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){ while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
match event_chunk_header.event_type{ 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::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_amortized(&mut data,&mut block.output_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,Vec::reserve)?,
EventType::Sound=>read_data_into_events_amortized(&mut data,&mut block.sound_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,Vec::reserve)?,
EventType::World=>read_data_into_events_amortized(&mut data,&mut block.world_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,Vec::reserve)?,
EventType::Gravity=>read_data_into_events_amortized(&mut data,&mut block.gravity_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,Vec::reserve)?,
EventType::Run=>read_data_into_events_amortized(&mut data,&mut block.run_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,Vec::reserve)?,
EventType::Camera=>read_data_into_events_amortized(&mut data,&mut block.camera_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,Vec::reserve)?,
EventType::Setting=>read_data_into_events_amortized(&mut data,&mut block.setting_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,Vec::reserve)?,
} }
} }
Ok(()) Ok(())
@@ -469,7 +436,7 @@ impl Block{
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
InvalidBlockId(BlockId), InvalidBlockId(BlockId),
Seek(std::io::Error), Seek(IoError),
InvalidData(binrw::Error), InvalidData(binrw::Error),
} }
impl std::fmt::Display for Error{ impl std::fmt::Display for Error{
@@ -488,20 +455,12 @@ pub struct BlockId(#[br(map=|i:u32|i-1)]u32);
#[derive(Debug,Clone,Copy)] #[derive(Debug,Clone,Copy)]
pub struct BlockPosition(#[br(map=|i:u32|i-1)]u32); pub struct BlockPosition(#[br(map=|i:u32|i-1)]u32);
#[binrw] impl PartialEq for Timed<BlockId>{
#[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{
fn eq(&self,other:&Self)->bool{ fn eq(&self,other:&Self)->bool{
self.time.eq(&other.time) self.time.eq(&other.time)
} }
} }
impl PartialOrd for TimedBlockId{ impl PartialOrd for Timed<BlockId>{
fn partial_cmp(&self,other:&Self)->Option<core::cmp::Ordering>{ fn partial_cmp(&self,other:&Self)->Option<core::cmp::Ordering>{
self.time.partial_cmp(&other.time) self.time.partial_cmp(&other.time)
} }
@@ -512,21 +471,34 @@ impl PartialOrd for TimedBlockId{
#[derive(Debug)] #[derive(Debug)]
pub struct FileHeader{ pub struct FileHeader{
#[brw(magic=b"qbot")] #[brw(magic=b"qbot")]
pub file_version:u32, file_version:u32,
pub num_offline_blocks:u32, num_offline_blocks:u32,
pub num_realtime_blocks:u32, num_realtime_blocks:u32,
#[br(count=num_offline_blocks+num_realtime_blocks+1)]
pub block_positions:Vec<BlockPosition>,
#[br(count=num_offline_blocks)]
pub offline_blocks_timeline:Vec<TimedBlockId>,
#[br(count=num_realtime_blocks)]
pub realtime_blocks_timeline:Vec<TimedBlockId>,
}
pub struct BlockInfo{
start:u32,
length:u32,
} }
impl FileHeader{ impl FileHeader{
pub fn from_reader<R:BinReaderExt>(mut data:R)->binrw::BinResult<Self>{
data.read_le()
}
}
#[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)]
block_positions:Vec<BlockPosition>,
#[br(count=num_offline_blocks)]
offline_blocks_timeline:Vec<Timed<BlockId>>,
#[br(count=num_realtime_blocks)]
realtime_blocks_timeline:Vec<Timed<BlockId>>,
}
impl BlockTimelines{
pub fn offline_blocks(&self)->&[Timed<BlockId>]{
&self.offline_blocks_timeline
}
pub fn realtime_blocks(&self)->&[Timed<BlockId>]{
&self.realtime_blocks_timeline
}
pub fn block_info(&self,BlockId(block_id):BlockId)->Result<BlockInfo,Error>{ pub fn block_info(&self,BlockId(block_id):BlockId)->Result<BlockInfo,Error>{
if self.block_positions.len() as u32<=block_id{ if self.block_positions.len() as u32<=block_id{
return Err(Error::InvalidBlockId(BlockId(block_id))); return Err(Error::InvalidBlockId(BlockId(block_id)));
@@ -536,86 +508,42 @@ impl FileHeader{
Ok(BlockInfo{start,length:end-start}) Ok(BlockInfo{start,length:end-start})
} }
} }
impl BlockTimelines{
struct MergeIter<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>>{ pub fn from_reader<R:BinReaderExt>(header:&FileHeader,mut data:R)->binrw::BinResult<Self>{
it0:It0, data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks))
it1:It1,
item0:Option<T>,
item1:Option<T>,
}
impl<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>> MergeIter<T,It0,It1>{
fn new(mut it0:It0,mut it1:It1)->Self{
Self{
item0:it0.next(),
item1:it1.next(),
it0,
it1,
}
} }
} }
impl<T:PartialOrd,It0:Iterator<Item=T>,It1:Iterator<Item=T>> Iterator for MergeIter<T,It0,It1>{ pub struct BlockInfo{
type Item=T; start:u32,
fn next(&mut self)->Option<Self::Item>{ length:u32,
match (&self.item0,&self.item1){ }
(None,None)=>None, impl BlockInfo{
(Some(_),None)=>core::mem::replace(&mut self.item0,self.it0.next()), pub fn start(&self)->u32{
(None,Some(_))=>core::mem::replace(&mut self.item1,self.it1.next()), self.start
(Some(item0),Some(item1))=>match item0.partial_cmp(item1){ }
Some(core::cmp::Ordering::Less) pub fn length(&self)->u32{
|Some(core::cmp::Ordering::Equal) self.length
|None }
=>core::mem::replace(&mut self.item0,self.it0.next()), /// Create an adapter which seeks to the block start and reads at most the block length.
Some(core::cmp::Ordering::Greater) pub fn take_seek<R:BinReaderExt>(&self,mut data:R)->Result<TakeSeek<R>,IoError>{
=>core::mem::replace(&mut self.item1,self.it1.next()), data.seek(SeekFrom::Start(self.start() as u64))?;
}, Ok(data.take_seek(self.length() as u64))
}
} }
} }
pub struct File<R:BinReaderExt>{ /// Read the entire file and combine the timelines into a single Block.
pub header:FileHeader, /// Note that this reads the blocks in chronological order, not the order they appear in the file, so there is some seeking involved.
pub data:FileData<R>, #[cfg(feature="itertools")]
} pub fn read_all_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
impl<R:BinReaderExt> File<R>{ let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?;
pub fn new(mut data:R)->Result<File<R>,binrw::Error>{ let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?;
Ok(File{ let mut block=Block::default();
header:data.read_le()?, for timed in itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()){
data:FileData{data}, let take_seek=block_timelines
}) .block_info(timed.event)?
} .take_seek(&mut data)
pub fn read_all(&mut self)->Result<Block,Error>{ .map_err(Error::Seek)?;
let block_iter=MergeIter::new( block.extend_from_reader(take_seek).map_err(Error::InvalidData)?;
self.header.offline_blocks_timeline.iter(), }
self.header.realtime_blocks_timeline.iter(), Ok(block)
);
let mut big_block=Block::default();
for &TimedBlockId{time:_,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 FileData<R:BinReaderExt>{
data:R,
}
impl<R:BinReaderExt> FileData<R>{
fn data_mut(&mut self)->&mut R{
&mut self.data
}
fn block_reader(&mut self,block_info:BlockInfo)->Result<binrw::io::TakeSeek<&mut R>,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<Block,Error>{
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(())
}
} }