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"
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]]

View File

@@ -6,3 +6,8 @@ edition = "2021"
[dependencies]
binrw = "0.14.1"
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
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)?)?;
}
```

View File

@@ -1,39 +1,38 @@
use crate::v0::{Error,File,TimedBlockId};
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 &TimedBlockId{time,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 &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);
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(())
}
#[cfg(feature="itertools")]
use crate::v0::{read_all_to_block,Error};
#[test]
#[cfg(feature="itertools")]
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());

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
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
#[binrw]
#[brw(little)]
@@ -78,13 +95,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 +129,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 +159,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 +210,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 +217,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 +287,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 +304,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 +328,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<TimedInputEvent>,
pub output_events:Vec<TimedOutputEvent>,
pub sound_events:Vec<TimedSoundEvent>,
pub world_events:Vec<TimedWorldEvent>,
pub gravity_events:Vec<TimedGravityEvent>,
pub run_events:Vec<TimedRunEvent>,
pub camera_events:Vec<TimedCameraEvent>,
pub setting_events:Vec<TimedSettingEvent>,
pub input_events:Vec<Timed<InputEvent>>,
pub output_events:Vec<Timed<OutputEvent>>,
pub sound_events:Vec<Timed<SoundEvent>>,
pub world_events:Vec<Timed<WorldEvent>>,
pub gravity_events:Vec<Timed<GravityEvent>>,
pub run_events:Vec<Timed<RunEvent>>,
pub camera_events:Vec<Timed<CameraEvent>>,
pub setting_events:Vec<Timed<SettingEvent>>,
}
#[binrw]
@@ -409,17 +370,18 @@ struct EventChunkHeader{
}
// 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<()>{
// 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_amortized<'a,R:BinReaderExt,T:binrw::BinRead<Args<'a>=()>>(data:&mut R,events:&mut Vec<T>,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<T>,
num_events:usize,
reserve_fn:F,
)->binrw::BinResult<()>
where
R:BinReaderExt,
T:binrw::BinRead<Args<'a>=()>,
F:Fn(&mut Vec<T>,usize),
{
reserve_fn(events,num_events);
for _ in 0..num_events{
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{
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();
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)
}
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
while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
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(&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(())
}
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
while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
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,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(())
@@ -469,7 +436,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{
@@ -488,20 +455,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<BlockId>{
fn eq(&self,other:&Self)->bool{
self.time.eq(&other.time)
}
}
impl PartialOrd for TimedBlockId{
impl PartialOrd for Timed<BlockId>{
fn partial_cmp(&self,other:&Self)->Option<core::cmp::Ordering>{
self.time.partial_cmp(&other.time)
}
@@ -512,21 +471,34 @@ impl PartialOrd for TimedBlockId{
#[derive(Debug)]
pub struct FileHeader{
#[brw(magic=b"qbot")]
pub file_version:u32,
pub num_offline_blocks:u32,
pub 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,
file_version:u32,
num_offline_blocks:u32,
num_realtime_blocks:u32,
}
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>{
if self.block_positions.len() as u32<=block_id{
return Err(Error::InvalidBlockId(BlockId(block_id)));
@@ -536,86 +508,42 @@ impl FileHeader{
Ok(BlockInfo{start,length:end-start})
}
}
struct MergeIter<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>>{
it0:It0,
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 BlockTimelines{
pub fn from_reader<R:BinReaderExt>(header:&FileHeader,mut data:R)->binrw::BinResult<Self>{
data.read_le_args((header.num_offline_blocks,header.num_realtime_blocks))
}
}
impl<T:PartialOrd,It0:Iterator<Item=T>,It1:Iterator<Item=T>> Iterator for MergeIter<T,It0,It1>{
type Item=T;
fn next(&mut self)->Option<Self::Item>{
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 struct BlockInfo{
start:u32,
length:u32,
}
impl BlockInfo{
pub fn start(&self)->u32{
self.start
}
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<R:BinReaderExt>(&self,mut data:R)->Result<TakeSeek<R>,IoError>{
data.seek(SeekFrom::Start(self.start() as u64))?;
Ok(data.take_seek(self.length() as u64))
}
}
pub struct File<R:BinReaderExt>{
pub header:FileHeader,
pub data:FileData<R>,
}
impl<R:BinReaderExt> File<R>{
pub fn new(mut data:R)->Result<File<R>,binrw::Error>{
Ok(File{
header:data.read_le()?,
data:FileData{data},
})
}
pub fn read_all(&mut self)->Result<Block,Error>{
let block_iter=MergeIter::new(
self.header.offline_blocks_timeline.iter(),
self.header.realtime_blocks_timeline.iter(),
);
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(())
}
/// 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<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
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 take_seek=block_timelines
.block_info(timed.event)?
.take_seek(&mut data)
.map_err(Error::Seek)?;
block.extend_from_reader(take_seek).map_err(Error::InvalidData)?;
}
Ok(block)
}