2024-12-31 22:51:57 -08:00

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(())
}
}