Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f8e53c7dfd
|
|||
|
1c3c400828
|
|||
|
a6e8402937
|
|||
|
0db4cfe03d
|
|||
|
362fc4e5a5
|
|||
|
8644fdf54d
|
|||
|
dd08f536d9
|
|||
|
0bf0c9c5ee
|
|||
|
a41c66480d
|
|||
|
3b888f3181
|
|||
|
cc8899029f
|
|||
|
f94ea9f7ee
|
|||
|
2209b5218b
|
|||
|
6b472e81e3
|
|||
|
7b27cc7135
|
|||
|
9f162cc63f
|
|||
|
ee65eb6dfb
|
|||
|
80fa551cca
|
|||
|
9884552b6a
|
|||
|
5000d8885e
|
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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]]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
45
README.md
45
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)?)?;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
37
src/tests.rs
37
src/tests.rs
@@ -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
322
src/v0.rs
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user