612 lines
14 KiB
Rust
612 lines
14 KiB
Rust
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(())
|
|
}
|
|
}
|