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"
|
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]]
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -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)?)?;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
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]
|
#[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
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
|
// 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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user