Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f22b0cdb27
|
|||
|
6b395b970c
|
|||
|
0ea00baeb5
|
|||
|
6b4e56ca82
|
|||
|
d153e8284b
|
|||
|
f54af6600b
|
|||
|
fb4fb5b5b3
|
|||
|
5dafa3971c
|
|||
|
a1cd627836
|
|||
|
39049fa12e
|
|||
|
236e297cc1
|
|||
|
838aecbda5
|
|||
|
a5d5b30e0c
|
|||
|
99fb761a1d
|
|||
|
59fcc7c0fb
|
|||
|
775c510ee6
|
|||
|
22c01e5910
|
|||
|
8c3e3c9463
|
|||
|
f315069f96
|
|||
|
881a3bad11
|
|||
|
bd8207ac2d
|
|||
|
60ed1e2661
|
|||
|
680d49f6db
|
|||
|
504c3ff354
|
|||
|
89c5f94a28
|
|||
|
673255383d
|
|||
|
677cb86987
|
|||
|
59b36c8821
|
|||
|
0dc231d972
|
|||
|
4de222b1b6
|
|||
|
40e8f7c595
|
|||
|
6038241d7a
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -85,7 +85,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.8.1"
|
||||
version = "0.7.1-pre1"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.8.1"
|
||||
version = "0.7.1-pre1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
pub use binrw::Error as BinrwError;
|
||||
|
||||
pub mod v0;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
57
src/tests.rs
57
src/tests.rs
@@ -1,41 +1,60 @@
|
||||
use crate::v0;
|
||||
use crate::v0::{Block,BlockTimelines,FileHeader};
|
||||
use crate::v0::{Block,BlockTimelines,FileHeader,Timed};
|
||||
|
||||
#[test]
|
||||
fn deserialize_manual()->Result<(),binrw::Error>{
|
||||
fn deserialize_manual(){
|
||||
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)?;
|
||||
let timelines=BlockTimelines::from_reader(&header,&mut input)?;
|
||||
for block in timelines.offline_blocks(){
|
||||
let block_info=timelines.block_info(block.event).unwrap();
|
||||
let block_reader=block_info.take_seek(&mut input)?;
|
||||
let _block=Block::from_reader(block_reader)?;
|
||||
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 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 block in timelines.realtime_blocks(){
|
||||
let block_info=timelines.block_info(block.event).unwrap();
|
||||
let block_reader=block_info.take_seek(&mut input)?;
|
||||
let _block=Block::from_reader(block_reader)?;
|
||||
for &Timed{time,event:block_id} in timelines.realtime_blocks(){
|
||||
println!("realtime time={} block_id={:?}",time,block_id);
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_all()->Result<(),v0::Error>{
|
||||
#[cfg(feature="itertools")]
|
||||
fn deserialize_all()->Result<(),crate::v0::Error>{
|
||||
let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
|
||||
let _block=v0::read_all_to_block(std::io::Cursor::new(file))?;
|
||||
|
||||
let t0=std::time::Instant::now();
|
||||
|
||||
let _block=crate::v0::read_all_to_block(std::io::Cursor::new(file))?;
|
||||
|
||||
println!("{:?}",t0.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="itertools")]
|
||||
fn serialize_round_trip()->Result<(),binrw::Error>{
|
||||
use crate::v0::serialize;
|
||||
|
||||
let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
|
||||
let block=v0::read_all_to_block(std::io::Cursor::new(file.as_slice())).unwrap();
|
||||
let mut block=crate::v0::read_all_to_block(std::io::Cursor::new(file.as_slice())).unwrap();
|
||||
|
||||
let mut data=Vec::with_capacity(file.len());
|
||||
v0::serialize(&block,&mut std::io::Cursor::new(&mut data))?;
|
||||
serialize(&block,&mut std::io::Cursor::new(&mut data))?;
|
||||
|
||||
let block_rt=crate::v0::read_all_to_block(std::io::Cursor::new(data.as_slice())).unwrap();
|
||||
|
||||
assert_eq!(block_rt,block);
|
||||
|
||||
block.output_events.pop();
|
||||
|
||||
assert_eq!(block_rt,block);
|
||||
|
||||
// TODO: It encodes, but is it equal? Test something! PartialEq?
|
||||
Ok(())
|
||||
}
|
||||
|
||||
151
src/v0.rs
151
src/v0.rs
@@ -2,7 +2,6 @@ use std::io::{SeekFrom,Error as IoError};
|
||||
use binrw::binrw;
|
||||
use binrw::io::{TakeSeek,TakeSeekExt};
|
||||
use binrw::BinReaderExt;
|
||||
use crate::BinrwError;
|
||||
|
||||
// the bit chunks are deposited in reverse
|
||||
fn read_trey_float(bits:u32)->f32{
|
||||
@@ -34,7 +33,7 @@ fn write_trey_double(value:&f64)->u64{
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct Vector2{
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
@@ -45,7 +44,7 @@ pub struct Vector2{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct Vector3{
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
@@ -135,7 +134,7 @@ impl<A,B> PartialOrd<Timed<B>> for Timed<A>
|
||||
// input
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct InputEvent{
|
||||
#[br(try_map=GameControls::try_from_bits)]
|
||||
#[bw(map=GameControls::bits)]
|
||||
@@ -168,7 +167,7 @@ impl TickInfo{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct OutputEvent{
|
||||
#[br(try_map=TickInfo::try_from_bits)]
|
||||
#[bw(map=TickInfo::bits)]
|
||||
@@ -203,7 +202,7 @@ pub enum SoundType{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct SoundEvent{
|
||||
pub sound_type:SoundType,
|
||||
/// Roblox enum
|
||||
@@ -213,13 +212,13 @@ pub struct SoundEvent{
|
||||
// world
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventReset{
|
||||
pub position:Vector3,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventButton{
|
||||
pub button_id:u32,
|
||||
// This field does not exist in the final struct and
|
||||
@@ -231,7 +230,7 @@ pub struct WorldEventButton{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventSetTime{
|
||||
#[br(map=read_trey_double)]
|
||||
#[bw(map=write_trey_double)]
|
||||
@@ -243,7 +242,7 @@ pub struct WorldEventSetTime{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventSetPaused{
|
||||
#[br(map=|paused:u32|paused!=0)]
|
||||
#[bw(map=|&paused:&bool|paused as u32)]
|
||||
@@ -255,7 +254,7 @@ pub struct WorldEventSetPaused{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub enum WorldEvent{
|
||||
#[brw(magic=0u32)]
|
||||
Reset(WorldEventReset),
|
||||
@@ -270,7 +269,7 @@ pub enum WorldEvent{
|
||||
// gravity
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct GravityEvent{
|
||||
pub gravity:Vector3,
|
||||
}
|
||||
@@ -352,14 +351,14 @@ pub enum FlagReason{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventPrepare{
|
||||
pub mode:ModeID,
|
||||
pub style:Style,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventZone{
|
||||
pub mode:ModeID,
|
||||
#[br(temp)]
|
||||
@@ -369,7 +368,7 @@ pub struct RunEventZone{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventClear{
|
||||
pub mode:ModeSpec,
|
||||
#[br(temp)]
|
||||
@@ -379,21 +378,21 @@ pub struct RunEventClear{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventFlag{
|
||||
pub mode:ModeSpec,
|
||||
pub flag_reason:FlagReason,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventPractice{
|
||||
pub mode:ModeSpec,
|
||||
pub state_id:u32,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub enum RunEvent{
|
||||
#[brw(magic=0u32)]
|
||||
Prepare(RunEventPrepare),
|
||||
@@ -423,7 +422,7 @@ pub enum CameraEventType{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct CameraEvent{
|
||||
pub camera_event_type:CameraEventType,
|
||||
pub value:Vector3,
|
||||
@@ -447,7 +446,7 @@ pub enum SettingType{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct SettingEvent{
|
||||
pub setting_type:SettingType,
|
||||
#[br(map=read_trey_double)]
|
||||
@@ -456,8 +455,7 @@ pub struct SettingEvent{
|
||||
}
|
||||
|
||||
/// A segment of event timelines.
|
||||
/// Timelines are always be sorted.
|
||||
#[derive(Default)]
|
||||
#[derive(Debug,Default,PartialEq)]
|
||||
pub struct Block{
|
||||
pub input_events:Vec<Timed<InputEvent>>,
|
||||
pub output_events:Vec<Timed<OutputEvent>>,
|
||||
@@ -490,6 +488,22 @@ enum EventType{
|
||||
#[brw(magic=8u32)]
|
||||
Setting,
|
||||
}
|
||||
impl EventType{
|
||||
// internal function meant for array indexing
|
||||
fn from_usize(value:usize)->Self{
|
||||
match value{
|
||||
0=>Self::Input,
|
||||
1=>Self::Output,
|
||||
2=>Self::Sound,
|
||||
3=>Self::World,
|
||||
4=>Self::Gravity,
|
||||
5=>Self::Run,
|
||||
6=>Self::Camera,
|
||||
7=>Self::Setting,
|
||||
_=>panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct EventChunkHeader{
|
||||
@@ -498,15 +512,15 @@ struct EventChunkHeader{
|
||||
}
|
||||
|
||||
// binread args tech has been further refined
|
||||
fn read_data_into_events<R,T,F>(
|
||||
fn read_data_into_events<'a,R,T,F>(
|
||||
data:&mut R,
|
||||
events:&mut Vec<T>,
|
||||
num_events:usize,
|
||||
reserve_fn:F,
|
||||
)->Result<(),BinrwError>
|
||||
)->binrw::BinResult<()>
|
||||
where
|
||||
R:BinReaderExt,
|
||||
T:for<'a> binrw::BinRead<Args<'a>=()>,
|
||||
T:binrw::BinRead<Args<'a>=()>,
|
||||
F:Fn(&mut Vec<T>,usize),
|
||||
{
|
||||
reserve_fn(events,num_events);
|
||||
@@ -517,7 +531,7 @@ fn read_data_into_events<R,T,F>(
|
||||
}
|
||||
|
||||
impl Block{
|
||||
pub fn from_reader<R:BinReaderExt>(data:R)->Result<Block,BinrwError>{
|
||||
pub fn from_reader<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{
|
||||
let mut block=Block::default();
|
||||
// there is only supposed to be at most one of each type
|
||||
// of event chunk per block, so allocate the size exactly.
|
||||
@@ -526,7 +540,7 @@ impl Block{
|
||||
}
|
||||
/// Read a complete data block and append the elements to the timelines in this block.
|
||||
/// Reserves exactly enough information for the new data.
|
||||
pub fn extend_from_reader_exact<R:BinReaderExt>(&mut self,mut data:R)->Result<(),BinrwError>{
|
||||
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{
|
||||
@@ -543,7 +557,7 @@ impl Block{
|
||||
Ok(())
|
||||
}
|
||||
/// 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)->Result<(),BinrwError>{
|
||||
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{
|
||||
@@ -559,23 +573,13 @@ impl Block{
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn extend_from_block_id_iter<'a,R:BinReaderExt>(&mut self,mut data:R,block_timelines:&BlockTimelines,blocks:impl IntoIterator<Item=&'a Timed<BlockId>>)->Result<(),Error>{
|
||||
for timed in blocks{
|
||||
let take_seek=block_timelines
|
||||
.block_info(timed.event)?
|
||||
.take_seek(&mut data)
|
||||
.map_err(Error::Seek)?;
|
||||
self.extend_from_reader(take_seek).map_err(Error::InvalidData)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
InvalidBlockId(InvalidBlockId),
|
||||
InvalidBlockId(BlockId),
|
||||
Seek(IoError),
|
||||
InvalidData(BinrwError),
|
||||
InvalidData(binrw::Error),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
@@ -601,20 +605,6 @@ struct BlockPosition(
|
||||
u32
|
||||
);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidBlockId(pub BlockId);
|
||||
impl std::fmt::Display for InvalidBlockId{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for InvalidBlockId{}
|
||||
impl From<InvalidBlockId> for Error{
|
||||
fn from(value:InvalidBlockId)->Self{
|
||||
Self::InvalidBlockId(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The first 16 bytes of the file.
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
@@ -626,7 +616,7 @@ pub struct FileHeader{
|
||||
num_realtime_blocks:u32,
|
||||
}
|
||||
impl FileHeader{
|
||||
pub fn from_reader<R:BinReaderExt>(mut data:R)->Result<Self,BinrwError>{
|
||||
pub fn from_reader<R:BinReaderExt>(mut data:R)->binrw::BinResult<Self>{
|
||||
data.read_le()
|
||||
}
|
||||
fn block_position_count(&self)->u32{
|
||||
@@ -660,7 +650,7 @@ pub struct BlockTimelines{
|
||||
realtime_blocks_timeline:Vec<Timed<BlockId>>,
|
||||
}
|
||||
impl BlockTimelines{
|
||||
pub fn from_reader<R:BinReaderExt>(header:&FileHeader,mut data:R)->Result<Self,BinrwError>{
|
||||
pub fn from_reader<R:BinReaderExt>(header:&FileHeader,mut data:R)->binrw::BinResult<Self>{
|
||||
data.read_le_args(header)
|
||||
}
|
||||
/// "Offline" blocks (containing World, Gravity, Run, Camera, and Setting events) in chronological order.
|
||||
@@ -672,13 +662,12 @@ impl BlockTimelines{
|
||||
&self.realtime_blocks_timeline
|
||||
}
|
||||
/// Get BlockInfo for a specfic BlockId.
|
||||
pub fn block_info(&self,block_id:BlockId)->Result<BlockInfo,InvalidBlockId>{
|
||||
let BlockId(id)=block_id;
|
||||
if self.block_positions.len() as u32<=id{
|
||||
return Err(InvalidBlockId(block_id));
|
||||
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)));
|
||||
}
|
||||
let BlockPosition(start)=self.block_positions[id as usize];
|
||||
let BlockPosition(end)=self.block_positions[id as usize+1];
|
||||
let BlockPosition(start)=self.block_positions[block_id as usize];
|
||||
let BlockPosition(end)=self.block_positions[block_id as usize+1];
|
||||
Ok(BlockInfo(start..end))
|
||||
}
|
||||
}
|
||||
@@ -699,13 +688,24 @@ impl core::ops::Deref for BlockInfo{
|
||||
}
|
||||
}
|
||||
|
||||
fn read_to_block<'a,R:BinReaderExt>(mut data:R,block_timelines:&BlockTimelines,blocks:impl IntoIterator<Item=&'a Timed<BlockId>>)->Result<Block,Error>{
|
||||
let mut block=Block::default();
|
||||
for timed in 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)
|
||||
}
|
||||
|
||||
/// Read offline blocks 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.
|
||||
pub fn read_offline_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();
|
||||
block.extend_from_block_id_iter(data,&block_timelines,block_timelines.offline_blocks())?;
|
||||
let block=read_to_block(data,&block_timelines,block_timelines.offline_blocks())?;
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
@@ -714,37 +714,25 @@ pub fn read_offline_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
|
||||
pub fn read_realtime_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();
|
||||
block.extend_from_block_id_iter(data,&block_timelines,block_timelines.realtime_blocks())?;
|
||||
let block=read_to_block(data,&block_timelines,block_timelines.realtime_blocks())?;
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
/// 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();
|
||||
block.extend_from_block_id_iter(&mut data,&block_timelines,block_timelines.offline_blocks())?;
|
||||
block.extend_from_block_id_iter(&mut data,&block_timelines,block_timelines.realtime_blocks())?;
|
||||
let block=read_to_block(data,&block_timelines,itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()))?;
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
#[cfg(feature="itertools")]
|
||||
pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),BinrwError>{
|
||||
pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),binrw::Error>{
|
||||
use std::ops::Range;
|
||||
const MAX_BLOCK_SIZE:usize=1<<14;
|
||||
const FILE_VERSION:u32=0;
|
||||
const EVENT_TYPES:[EventType;8]=[
|
||||
EventType::Input,
|
||||
EventType::Output,
|
||||
EventType::Sound,
|
||||
EventType::World,
|
||||
EventType::Gravity,
|
||||
EventType::Run,
|
||||
EventType::Camera,
|
||||
EventType::Setting,
|
||||
];
|
||||
const EVENT_SIZE:[usize;8]=[
|
||||
8+4+2*4, // Input
|
||||
8+4+4*3*4, // Output
|
||||
@@ -942,11 +930,12 @@ pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),B
|
||||
file_header.write_le(writer)?;
|
||||
block_timelines.write_le(writer)?;
|
||||
for plan in plan_order{
|
||||
for (range,event_type) in plan.0.into_iter().zip(EVENT_TYPES){
|
||||
for (event_type_id,range) in plan.0.into_iter().enumerate(){
|
||||
let num_events=range.len();
|
||||
if num_events==0{
|
||||
continue;
|
||||
}
|
||||
let event_type=EventType::from_usize(event_type_id);
|
||||
let event_chunk_header=EventChunkHeader{
|
||||
event_type,
|
||||
num_events:num_events as u32,
|
||||
|
||||
Reference in New Issue
Block a user