use binrw::{binrw,BinReaderExt,io::TakeSeekExt}; // the bit chunks are deposited in reverse fn read_trey_float(bits:u32)->f32{ let s=bits&1; let e=(bits>>1)&((1<<8)-1); let m=(bits>>(1+8))&((1<<23)-1); f32::from_bits(m|(e<<23)|(s<<31)) } fn read_trey_double(bits:u64)->f64{ let s=bits&1; let e=(bits>>1)&((1<<11)-1); let m=(bits>>(1+11))&((1<<52)-1); f64::from_bits(m|(e<<52)|(s<<63)) } #[binrw] #[brw(little)] pub struct Vector2{ #[br(map=read_trey_float)] pub x:f32, #[br(map=read_trey_float)] pub y:f32, } #[binrw] #[brw(little)] pub struct Vector3{ #[br(map=read_trey_float)] pub x:f32, #[br(map=read_trey_float)] pub y:f32, #[br(map=read_trey_float)] pub z:f32, } bitflags::bitflags!{ pub struct GameControls:u32{ const MoveForward=1<<0; const MoveLeft=1<<1; const MoveBack=1<<2; const MoveRight=1<<3; const MoveUp=1<<4; const MoveDown=1<<5; const LookUp=1<<6; const LookLeft=1<<7; const LookDown=1<<8; const LookRight=1<<9; const Jump=1<<10; const Crouch=1<<11; const Sprint=1<<12; const Zoom=1<<13; const Use=1<<14; const Action1=1<<15; const Action2=1<<16; } } #[derive(Debug)] pub struct GameControlsError; impl std::fmt::Display for GameControlsError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for GameControlsError{} impl GameControls{ fn try_from_bits(bits:u32)->Result<Self,GameControlsError>{ Self::from_bits(bits).ok_or(GameControlsError) } } // input #[binrw] #[brw(little)] pub struct InputEvent{ #[br(try_map=GameControls::try_from_bits)] #[bw(map=GameControls::bits)] 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!{ pub struct TickInfo:u32{ const TickEnd=1<<0; const Jump=1<<1; const Strafe=1<<2; const Touching=1<<3; } } #[derive(Debug)] pub struct TickInfoError; impl std::fmt::Display for TickInfoError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for TickInfoError{} impl TickInfo{ fn try_from_bits(bits:u32)->Result<Self,TickInfoError>{ Self::from_bits(bits).ok_or(TickInfoError) } } #[binrw] #[brw(little)] pub struct OutputEvent{ #[br(try_map=TickInfo::try_from_bits)] #[bw(map=TickInfo::bits)] pub tick_info:TickInfo, pub angles:Vector3, pub position:Vector3, 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] #[brw(little)] pub enum SoundType{ #[brw(magic=101u32)] TrackGround, #[brw(magic=102u32)] TrackLadder, #[brw(magic=103u32)] TrackWater, #[brw(magic=104u32)] TrackAir, #[brw(magic=201u32)] JumpGround, #[brw(magic=202u32)] JumpLadder, #[brw(magic=301u32)] SmashGround, #[brw(magic=302u32)] SmashWall, } #[binrw] #[brw(little)] pub struct SoundEvent{ pub sound_type:SoundType, /// Roblox enum pub material:u32, } #[binrw] #[brw(little)] pub struct TimedSoundEvent{ #[br(map=read_trey_double)] pub time:f64, pub event:SoundEvent, } // world #[binrw] #[brw(little)] pub struct WorldEventReset{ pub position:Vector3, } #[binrw] #[brw(little)] pub struct WorldEventButton{ #[br(pad_after=8)] pub button_id:u32, } #[binrw] #[brw(little)] pub struct WorldEventSetTime{ #[br(map=read_trey_double)] #[br(pad_after=4)] pub time:f64, } #[binrw] #[brw(little)] pub struct WorldEventSetPaused{ #[br(map=|paused:u32|paused!=0)] #[bw(map=|paused:&bool|*paused as u32)] #[br(pad_after=8)] pub paused:bool, } #[binrw] #[brw(little)] pub enum WorldEvent{ #[brw(magic=0u32)] Reset(WorldEventReset), #[brw(magic=1u32)] Button(WorldEventButton), #[brw(magic=2u32)] SetTime(WorldEventSetTime), #[brw(magic=3u32)] SetPaused(WorldEventSetPaused), } #[binrw] #[brw(little)] pub struct TimedWorldEvent{ #[br(map=read_trey_double)] pub time:f64, pub event:WorldEvent, } // gravity #[binrw] #[brw(little)] 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] #[brw(little)] pub enum RunEventType{ #[brw(magic=0u32)] Prepare, #[brw(magic=1u32)] Start, #[brw(magic=2u32)] Finish, #[brw(magic=3u32)] Clear, #[brw(magic=4u32)] Flag, #[brw(magic=5u32)] LoadState, #[brw(magic=6u32)] SaveState, } #[binrw] #[brw(little)] pub enum Mode{ #[brw(magic=0i32)] Main, #[brw(magic=1i32)] Bonus, #[brw(magic=-1i32)] All, #[brw(magic=-2i32)] Invalid, #[brw(magic=-3i32)] InProgress, } #[binrw] #[brw(little)] pub enum FlagReason{ #[brw(magic=0u32)] Anticheat, #[brw(magic=1u32)] StyleChange, #[brw(magic=2u32)] Clock, #[brw(magic=3u32)] Pause, #[brw(magic=4u32)] Flying, #[brw(magic=5u32)] Gravity, #[brw(magic=6u32)] Timescale, #[brw(magic=7u32)] Timetravel, #[brw(magic=8u32)] Teleport, #[brw(magic=9u32)] Practice, // b"data" #[brw(magic=1635017060u32)] None, } #[binrw] #[brw(little)] pub struct RunEvent{ pub run_event_type:RunEventType, 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] #[brw(little)] pub enum CameraEventType{ #[brw(magic=0u32)] CameraPunch, #[brw(magic=1u32)] Transform, } #[binrw] #[brw(little)] 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] #[brw(little)] pub enum SettingType{ #[brw(magic=0u32)] FieldOfView, #[brw(magic=1u32)] Sensitivity, #[brw(magic=2u32)] VerticalSensitivityMultiplier, #[brw(magic=3u32)] AbsoluteSensitivity, #[brw(magic=4u32)] TurnSpeed, } #[binrw] #[brw(little)] pub struct SettingEvent{ pub setting_type:SettingType, #[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>, } #[binrw] #[brw(little)] enum EventType{ #[brw(magic=1u32)] Input, #[brw(magic=2u32)] Output, #[brw(magic=3u32)] Sound, #[brw(magic=4u32)] World, #[brw(magic=5u32)] Gravity, #[brw(magic=6u32)] Run, #[brw(magic=7u32)] Camera, #[brw(magic=8u32)] Setting, } #[binrw] #[brw(little)] struct EventChunkHeader{ event_type:EventType, num_events:u32, } // first time I've managed to write BinRead generics without this stupid T::Args<'a>:Required thing blocking me fn read_data_into_events<'a,R:BinReaderExt,T>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()> where T:binrw::BinRead, T::Args<'a>:binrw::__private::Required, { // 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>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()> where T:binrw::BinRead, T::Args<'a>:binrw::__private::Required, { // this is used when reading multiple blocks into a single object, so amortize the allocation cost. events.reserve(num_events); for _ in 0..num_events{ events.push(data.read_le()?); } Ok(()) } impl Block{ fn read<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{ let mut block=Block::default(); Block::read_into(data,&mut block)?; Ok(block) } fn read_into<R:BinReaderExt>(mut data:R,block:&mut Block)->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)?, } } Ok(()) } fn read_into_amortized<R:BinReaderExt>(mut data:R,block:&mut Block)->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)?, } } Ok(()) } } #[derive(Debug)] pub enum Error{ InvalidBlockId(BlockId), Seek(std::io::Error), InvalidData(binrw::Error), } impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for Error{} #[binrw] #[brw(little)] #[derive(Debug,Clone,Copy)] pub struct BlockId(#[br(map=|i:u32|i-1)]u32); #[binrw] #[brw(little)] #[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{ fn eq(&self,other:&Self)->bool{ self.time.eq(&other.time) } } impl PartialOrd for TimedBlockId{ fn partial_cmp(&self,other:&Self)->Option<core::cmp::Ordering>{ self.time.partial_cmp(&other.time) } } #[binrw] #[brw(little)] #[derive(Debug)] pub struct FileHeader{ #[brw(magic=b"qbot")] pub file_version:u32, pub num_offline_events:u32, pub num_realtime_events:u32, #[br(count=num_offline_events+num_realtime_events+1)] pub block_positions:Vec<BlockPosition>, #[br(count=num_offline_events)] pub offline_blocks_timeline:Vec<TimedBlockId>, #[br(count=num_realtime_events)] pub realtime_blocks_timeline:Vec<TimedBlockId>, } pub struct BlockInfo{ start:u32, length:u32, } impl FileHeader{ pub fn block_info(&self,block_id:BlockId)->Result<BlockInfo,Error>{ if self.block_positions.len() as u32<=block_id.0{ return Err(Error::InvalidBlockId(block_id)); } let start=self.block_positions[block_id.0 as usize].0; let end=self.block_positions[block_id.0 as usize+1].0; 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<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 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(()) } }