From da11614c38e83d6172941f44ffaf2873f7a18142 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 04:36:10 -0800
Subject: [PATCH 01/31] Initial commit

---
 .gitignore |  1 +
 Cargo.lock | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml |  7 ++++
 src/lib.rs |  1 +
 src/v1.rs  |  1 +
 5 files changed, 103 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Cargo.lock
 create mode 100644 Cargo.toml
 create mode 100644 src/lib.rs
 create mode 100644 src/v1.rs

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..4e57f40
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,93 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "array-init"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
+
+[[package]]
+name = "binrw"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d4bca59c20d6f40c2cc0802afbe1e788b89096f61bdf7aeea6bf00f10c2909b"
+dependencies = [
+ "array-init",
+ "binrw_derive",
+ "bytemuck",
+]
+
+[[package]]
+name = "binrw_derive"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8ba42866ce5bced2645bfa15e97eef2c62d2bdb530510538de8dd3d04efff3c"
+dependencies = [
+ "either",
+ "owo-colors",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "strafesnet_roblox_bot_file"
+version = "0.1.0"
+dependencies = [
+ "binrw",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4f209b1
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "strafesnet_roblox_bot_file"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+binrw = "0.14.1"
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..a3a6d96
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1 @@
+pub mod v1;
diff --git a/src/v1.rs b/src/v1.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/v1.rs
@@ -0,0 +1 @@
+

From b0a1e4b1267ec5b14191988f2b0a70d577c19b77 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 04:37:57 -0800
Subject: [PATCH 02/31] open source

---
 LICENSE-APACHE | 176 +++++++++++++++++++++++++++++++++++++++++++++++++
 LICENSE-MIT    |  23 +++++++
 2 files changed, 199 insertions(+)
 create mode 100644 LICENSE-APACHE
 create mode 100644 LICENSE-MIT

diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..a7e77cb
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,176 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..468cd79
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
\ No newline at end of file

From 39196119ff2d3b02f421ec425726b057e6293a7a Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 04:36:15 -0800
Subject: [PATCH 03/31] lib

---
 src/v1.rs | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 332 insertions(+)

diff --git a/src/v1.rs b/src/v1.rs
index 8b13789..14c75cf 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -1 +1,333 @@
+use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
 
+#[binrw]
+#[brw(little)]
+pub enum Bool{
+	#[brw(magic=0u32)]
+	False,
+	#[brw(magic=1u32)]
+	True,
+}
+#[binrw]
+#[brw(little)]
+pub struct Vector2{
+	pub x:f32,
+	pub y:f32,
+}
+#[binrw]
+#[brw(little)]
+pub struct Vector3{
+	pub x:f32,
+	pub y:f32,
+	pub z:f32,
+}
+
+// input
+#[binrw]
+#[brw(little)]
+pub struct InputEvent{
+	pub game_controls:u32,
+	pub mouse_pos:Vector2,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedInputEvent{
+	pub time:f64,
+	pub event:InputEvent,
+}
+
+// output
+#[binrw]
+#[brw(little)]
+pub struct OutputEvent{
+	pub tick_info:u32,
+	pub angles:Vector3,
+	pub position:Vector3,
+	pub velocity:Vector3,
+	pub acceleration:Vector3,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedOutputEvent{
+	pub time:f64,
+	pub event:OutputEvent,
+}
+
+// sound
+#[binrw]
+#[brw(little)]
+pub struct SoundEvent{
+	pub sound_type:u32,
+	pub material:u32,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedSoundEvent{
+	pub time:f64,
+	pub event:SoundEvent,
+}
+
+// world
+#[binrw]
+#[brw(little)]
+pub struct WorldEventReset{
+	pub position:Vector3,
+}
+#[binrw]
+#[brw(little)]
+pub struct WorldEventButton{
+	pub button_id:u32,
+	#[brw(magic=b"quatdata")]
+	__:(),
+}
+#[binrw]
+#[brw(little)]
+pub struct WorldEventSetTime{
+	pub time:f64,
+	#[brw(magic=b"data")]
+	__:(),
+}
+#[binrw]
+#[brw(little)]
+pub struct WorldEventSetPaused{
+	pub paused:Bool,
+	#[brw(magic=b"quatdata")]
+	__:(),
+}
+#[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{
+	pub time:f64,
+	pub event:WorldEvent,
+}
+
+// gravity
+#[binrw]
+#[brw(little)]
+pub struct GravityEvent{
+	pub gravity:Vector3,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedGravityEvent{
+	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 struct RunEvent{
+	run_event_type:RunEventType,
+	mode_id:i32,
+	flag_reason_id:u32,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedRunEvent{
+	pub time:f64,
+	pub event:RunEvent,
+}
+
+// camera
+#[binrw]
+#[brw(little)]
+pub struct CameraEvent{
+	pub camera_event_id:u32,
+	pub value:Vector3,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedCameraEvent{
+	pub time:f64,
+	pub event:CameraEvent,
+}
+
+// setting
+#[binrw]
+#[brw(little)]
+pub struct SettingEvent{
+	pub setting_id:u32,
+	pub value:f64,
+}
+#[binrw]
+#[brw(little)]
+pub struct TimedSettingEvent{
+	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(())
+}
+
+impl Block{
+	fn read<R:BinReaderExt>(mut data:R)->binrw::BinResult<Block>{
+		let mut block=Block::default();
+		// 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(block)
+	}
+}
+
+#[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)]
+pub struct BlockId(u32);
+
+#[binrw]
+#[brw(little)]
+pub struct TimedBlockId{
+	pub time:f64,
+	pub block_id:BlockId,
+}
+
+#[binrw]
+#[brw(little)]
+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<u32>,
+	#[br(count=num_offline_events)]
+	pub offline_blocks_timeline:Vec<TimedBlockId>,
+	#[br(count=num_realtime_events)]
+	pub realtime_blocks_timeline:Vec<TimedBlockId>,
+}
+
+pub struct File<R:BinReaderExt>{
+	header:FileHeader,
+	//reference to the data
+	data:R,
+}
+
+impl<R:BinReaderExt> File<R>{
+	pub fn new(mut input:R)->Result<File<R>,binrw::Error>{
+		Ok(File{
+			header:input.read_le()?,
+			data:input,
+		})
+	}
+	fn data_mut(&mut self)->&mut R{
+		&mut self.data
+	}
+	fn block_reader(&mut self,block_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
+		if self.header.block_positions.len() as u32<=block_id.0{
+			return Err(Error::InvalidBlockId(block_id))
+		}
+		let block_start=self.header.block_positions[block_id.0 as usize] as u64;
+		let block_end=self.header.block_positions[block_id.0 as usize+1] as u64;
+		self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?;
+		Ok(self.data_mut().take_seek(block_end-block_start))
+	}
+	pub fn read_block(&mut self,block_id:BlockId)->Result<Block,Error>{
+		let data=self.block_reader(block_id)?;
+		let block=Block::read(data).map_err(Error::InvalidData)?;
+		Ok(block)
+	}
+}

From 1f78c98c6fdb2bbb021d1f84a8ba4a94cad9286f Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 04:46:59 -0800
Subject: [PATCH 04/31] readme

---
 README.md | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)
 create mode 100644 README.md

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cf08a3d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+Roblox Bhop/Surf Bot File Format
+================================
+
+## Example
+
+```rust
+use strafesnet_roblox_bot_file::File;
+
+let file=std::fs::File::open("bot_file")?;
+let input=std::io::BufReader::new(file);
+let bot_file=File::new(input)?;
+
+for &(time,block_id) in &bot_file.header.offline_blocks_timeline{
+	let block=bot_file.read_block(block_id)?;
+	// offline blocks include the following event types:
+	// World, Gravity, Run, Camera, Setting
+}
+for &(time,block_id) in &bot_file.header.realtime_blocks_timeline{
+	let block=bot_file.read_block(block_id)?;
+	// realtime blocks include the following event types:
+	// Input, Output, Sound
+}
+```
+
+#### License
+
+<sup>
+Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
+2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
+</sup>
+
+<br>
+
+<sub>
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
+be dual licensed as above, without any additional terms or conditions.
+</sub>

From 22543defa89982cf727f30dff99ff12b73e2527a Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 04:51:20 -0800
Subject: [PATCH 05/31] convenient impl

---
 src/v1.rs | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/v1.rs b/src/v1.rs
index 14c75cf..e78ac66 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -2,12 +2,21 @@ use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
 
 #[binrw]
 #[brw(little)]
+#[derive(Clone,Copy,Debug)]
 pub enum Bool{
 	#[brw(magic=0u32)]
 	False,
 	#[brw(magic=1u32)]
 	True,
 }
+impl Into<bool> for Bool{
+	fn into(self)->bool{
+		match self{
+			Bool::False=>false,
+			Bool::True=>true,
+		}
+	}
+}
 #[binrw]
 #[brw(little)]
 pub struct Vector2{

From 0c251f90fd32e5d7d7bc5e5127970d47f650f525 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 21:08:58 -0800
Subject: [PATCH 06/31] fixups for test

---
 src/v1.rs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index e78ac66..3386644 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -284,11 +284,12 @@ impl std::error::Error for Error{}
 
 #[binrw]
 #[brw(little)]
-#[derive(Debug)]
+#[derive(Debug,Clone,Copy)]
 pub struct BlockId(u32);
 
 #[binrw]
 #[brw(little)]
+#[derive(Debug,Clone,Copy)]
 pub struct TimedBlockId{
 	pub time:f64,
 	pub block_id:BlockId,
@@ -296,6 +297,7 @@ pub struct TimedBlockId{
 
 #[binrw]
 #[brw(little)]
+#[derive(Debug)]
 pub struct FileHeader{
 	#[brw(magic=b"qbot")]
 	pub file_version:u32,
@@ -310,7 +312,7 @@ pub struct FileHeader{
 }
 
 pub struct File<R:BinReaderExt>{
-	header:FileHeader,
+	pub header:FileHeader,
 	//reference to the data
 	data:R,
 }

From 083a735d8570ce64b072eef04f0d486d0285ec04 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 21:09:05 -0800
Subject: [PATCH 07/31] test

---
 src/lib.rs   |  3 +++
 src/tests.rs | 23 +++++++++++++++++++++++
 2 files changed, 26 insertions(+)
 create mode 100644 src/tests.rs

diff --git a/src/lib.rs b/src/lib.rs
index a3a6d96..d80700a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1,4 @@
 pub mod v1;
+
+#[cfg(test)]
+mod tests;
diff --git a/src/tests.rs b/src/tests.rs
new file mode 100644
index 0000000..bcaa243
--- /dev/null
+++ b/src/tests.rs
@@ -0,0 +1,23 @@
+use crate::v1::TimedBlockId;
+
+#[test]
+fn _1()->Result<(),crate::v1::Error>{
+	let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
+	let input=std::io::BufReader::new(file);
+	let mut bot_file=crate::v1::File::new(input).unwrap();
+	println!("header={:?}",bot_file.header);
+	for TimedBlockId{time,block_id} in bot_file.header.offline_blocks_timeline.clone(){
+		println!("offline time={} block_id={:?}",time,block_id);
+		let block=bot_file.read_block(block_id)?;
+		// offline blocks include the following event types:
+		// World, Gravity, Run, Camera, Setting
+	}
+	for TimedBlockId{time,block_id} in bot_file.header.realtime_blocks_timeline.clone(){
+		println!("realtime time={} block_id={:?}",time,block_id);
+		let block=bot_file.read_block(block_id)?;
+		// realtime blocks include the following event types:
+		// Input, Output, Sound
+	}
+
+	Ok(())
+}

From 43ee18a2caa7b4f1643ccb3c1c17247c474ee8cc Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 30 Dec 2024 23:12:05 -0800
Subject: [PATCH 08/31] fix 1-indexed block id

---
 src/v1.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/v1.rs b/src/v1.rs
index 3386644..72b4257 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -285,7 +285,7 @@ impl std::error::Error for Error{}
 #[binrw]
 #[brw(little)]
 #[derive(Debug,Clone,Copy)]
-pub struct BlockId(u32);
+pub struct BlockId(#[br(map=|i:u32|i-1)]u32);
 
 #[binrw]
 #[brw(little)]

From 9003f88a9d9773e6636e3321c7ffd9cdcf86d40b Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 01:44:28 -0800
Subject: [PATCH 09/31] trey floating point format is not standard

---
 src/v1.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/src/v1.rs b/src/v1.rs
index 72b4257..2aea4f6 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -1,5 +1,55 @@
 use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
 
+// whatever this is
+fn trey_float(f:f32)->f32{
+	let bits=f.to_bits();
+	let s=bits&1!=0;
+	let e=((bits>>1)&((1<<8)-1)) as i32;
+	let m=(bits>>(1+8))&((1<<23)-1);
+	if e==255{
+		if m==0{
+			if s{
+				f32::NEG_INFINITY
+			}else{
+				f32::INFINITY
+			}
+		}else if m==1{
+			f32::NAN
+		}else{
+			// this is supposed to be QNAN but idk how to say it in rust
+			f32::NAN
+		}
+	}else if e==0{
+		if s{-(m as f32)*2.0f32.powi(-149)}else{(m as f32)*2.0f32.powi(-149)}
+	}else{
+		if s{-(m as f32/2.0f32.powi(23)+1.0)*2.0f32.powi(e-127)}else{(m as f32/2.0f32.powi(23)+1.0)*2.0f32.powi(e-127)}
+	}
+}
+fn trey_double(f:f64)->f64{
+	let bits=f.to_bits();
+	let s=bits&1!=0;
+	let e=((bits>>1)&((1<<11)-1)) as i32;
+	let m=(bits>>(1+11))&((1<<52)-1);
+	if e==2047{
+		if m==0{
+			if s{
+				f64::NEG_INFINITY
+			}else{
+				f64::INFINITY
+			}
+		}else if m==1{
+			f64::NAN
+		}else{
+			// this is supposed to be QNAN but idk how to say it in rust
+			f64::NAN
+		}
+	}else if e==0{
+		if s{-(m as f64)*2.0f64.powi(-1074)}else{(m as f64)*2.0f64.powi(-1074)}
+	}else{
+		if s{-(m as f64/2.0f64.powi(52)+1.0)*2.0f64.powi(e-1023)}else{(m as f64/2.0f64.powi(52)+1.0)*2.0f64.powi(e-1023)}
+	}
+}
+
 #[binrw]
 #[brw(little)]
 #[derive(Clone,Copy,Debug)]
@@ -20,14 +70,19 @@ impl Into<bool> for Bool{
 #[binrw]
 #[brw(little)]
 pub struct Vector2{
+	#[br(map=trey_float)]
 	pub x:f32,
+	#[br(map=trey_float)]
 	pub y:f32,
 }
 #[binrw]
 #[brw(little)]
 pub struct Vector3{
+	#[br(map=trey_float)]
 	pub x:f32,
+	#[br(map=trey_float)]
 	pub y:f32,
+	#[br(map=trey_float)]
 	pub z:f32,
 }
 
@@ -41,6 +96,7 @@ pub struct InputEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedInputEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:InputEvent,
 }
@@ -58,6 +114,7 @@ pub struct OutputEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedOutputEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:OutputEvent,
 }
@@ -72,6 +129,7 @@ pub struct SoundEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedSoundEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:SoundEvent,
 }
@@ -92,6 +150,7 @@ pub struct WorldEventButton{
 #[binrw]
 #[brw(little)]
 pub struct WorldEventSetTime{
+	#[br(map=trey_double)]
 	pub time:f64,
 	#[brw(magic=b"data")]
 	__:(),
@@ -118,6 +177,7 @@ pub enum WorldEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedWorldEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:WorldEvent,
 }
@@ -131,6 +191,7 @@ pub struct GravityEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedGravityEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:GravityEvent,
 }
@@ -164,6 +225,7 @@ pub struct RunEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedRunEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:RunEvent,
 }
@@ -178,6 +240,7 @@ pub struct CameraEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedCameraEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:CameraEvent,
 }
@@ -187,11 +250,13 @@ pub struct TimedCameraEvent{
 #[brw(little)]
 pub struct SettingEvent{
 	pub setting_id:u32,
+	#[br(map=trey_double)]
 	pub value:f64,
 }
 #[binrw]
 #[brw(little)]
 pub struct TimedSettingEvent{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub event:SettingEvent,
 }
@@ -291,6 +356,7 @@ pub struct BlockId(#[br(map=|i:u32|i-1)]u32);
 #[brw(little)]
 #[derive(Debug,Clone,Copy)]
 pub struct TimedBlockId{
+	#[br(map=trey_double)]
 	pub time:f64,
 	pub block_id:BlockId,
 }

From 716354df2b320da5b12185b31044bf6bae82224d Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 01:57:22 -0800
Subject: [PATCH 10/31] trey decoder 2.0

---
 src/v1.rs | 48 +++++++-----------------------------------------
 1 file changed, 7 insertions(+), 41 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 2aea4f6..a15d53c 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -1,53 +1,19 @@
 use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
 
-// whatever this is
+// the bit chunks are deposited in reverse
 fn trey_float(f:f32)->f32{
 	let bits=f.to_bits();
-	let s=bits&1!=0;
-	let e=((bits>>1)&((1<<8)-1)) as i32;
+	let s=bits&1;
+	let e=(bits>>1)&((1<<8)-1);
 	let m=(bits>>(1+8))&((1<<23)-1);
-	if e==255{
-		if m==0{
-			if s{
-				f32::NEG_INFINITY
-			}else{
-				f32::INFINITY
-			}
-		}else if m==1{
-			f32::NAN
-		}else{
-			// this is supposed to be QNAN but idk how to say it in rust
-			f32::NAN
-		}
-	}else if e==0{
-		if s{-(m as f32)*2.0f32.powi(-149)}else{(m as f32)*2.0f32.powi(-149)}
-	}else{
-		if s{-(m as f32/2.0f32.powi(23)+1.0)*2.0f32.powi(e-127)}else{(m as f32/2.0f32.powi(23)+1.0)*2.0f32.powi(e-127)}
-	}
+	f32::from_bits(m|(e<<23)|(s<<31))
 }
 fn trey_double(f:f64)->f64{
 	let bits=f.to_bits();
-	let s=bits&1!=0;
-	let e=((bits>>1)&((1<<11)-1)) as i32;
+	let s=bits&1;
+	let e=(bits>>1)&((1<<11)-1);
 	let m=(bits>>(1+11))&((1<<52)-1);
-	if e==2047{
-		if m==0{
-			if s{
-				f64::NEG_INFINITY
-			}else{
-				f64::INFINITY
-			}
-		}else if m==1{
-			f64::NAN
-		}else{
-			// this is supposed to be QNAN but idk how to say it in rust
-			f64::NAN
-		}
-	}else if e==0{
-		if s{-(m as f64)*2.0f64.powi(-1074)}else{(m as f64)*2.0f64.powi(-1074)}
-	}else{
-		if s{-(m as f64/2.0f64.powi(52)+1.0)*2.0f64.powi(e-1023)}else{(m as f64/2.0f64.powi(52)+1.0)*2.0f64.powi(e-1023)}
-	}
+	f64::from_bits(m|(e<<52)|(s<<63))
 }
 
 #[binrw]

From 3ddd31a489757cb019e8b580ac6fd0e449a8c74f Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 03:36:33 -0800
Subject: [PATCH 11/31] split borrow to prevent clone

---
 src/tests.rs |  8 ++++----
 src/v1.rs    | 28 ++++++++++++++++------------
 2 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/src/tests.rs b/src/tests.rs
index bcaa243..1e44e2a 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -6,15 +6,15 @@ fn _1()->Result<(),crate::v1::Error>{
 	let input=std::io::BufReader::new(file);
 	let mut bot_file=crate::v1::File::new(input).unwrap();
 	println!("header={:?}",bot_file.header);
-	for TimedBlockId{time,block_id} in bot_file.header.offline_blocks_timeline.clone(){
+	for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{
 		println!("offline time={} block_id={:?}",time,block_id);
-		let block=bot_file.read_block(block_id)?;
+		let block=bot_file.data.read_block(&bot_file.header,block_id)?;
 		// offline blocks include the following event types:
 		// World, Gravity, Run, Camera, Setting
 	}
-	for TimedBlockId{time,block_id} in bot_file.header.realtime_blocks_timeline.clone(){
+	for &TimedBlockId{time,block_id} in &bot_file.header.realtime_blocks_timeline{
 		println!("realtime time={} block_id={:?}",time,block_id);
-		let block=bot_file.read_block(block_id)?;
+		let block=bot_file.data.read_block(&bot_file.header,block_id)?;
 		// realtime blocks include the following event types:
 		// Input, Output, Sound
 	}
diff --git a/src/v1.rs b/src/v1.rs
index a15d53c..6762578 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -345,31 +345,35 @@ pub struct FileHeader{
 
 pub struct File<R:BinReaderExt>{
 	pub header:FileHeader,
-	//reference to the data
-	data:R,
+	pub data:FileData<R>,
 }
-
 impl<R:BinReaderExt> File<R>{
-	pub fn new(mut input:R)->Result<File<R>,binrw::Error>{
+	pub fn new(mut data:R)->Result<File<R>,binrw::Error>{
 		Ok(File{
-			header:input.read_le()?,
-			data:input,
+			header:data.read_le()?,
+			data:FileData{data},
 		})
 	}
+}
+
+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_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
-		if self.header.block_positions.len() as u32<=block_id.0{
+	fn block_reader(&mut self,header:&FileHeader,block_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
+		if header.block_positions.len() as u32<=block_id.0{
 			return Err(Error::InvalidBlockId(block_id))
 		}
-		let block_start=self.header.block_positions[block_id.0 as usize] as u64;
-		let block_end=self.header.block_positions[block_id.0 as usize+1] as u64;
+		let block_start=header.block_positions[block_id.0 as usize] as u64;
+		let block_end=header.block_positions[block_id.0 as usize+1] as u64;
 		self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?;
 		Ok(self.data_mut().take_seek(block_end-block_start))
 	}
-	pub fn read_block(&mut self,block_id:BlockId)->Result<Block,Error>{
-		let data=self.block_reader(block_id)?;
+	pub fn read_block(&mut self,header:&FileHeader,block_id:BlockId)->Result<Block,Error>{
+		let data=self.block_reader(header,block_id)?;
 		let block=Block::read(data).map_err(Error::InvalidData)?;
 		Ok(block)
 	}

From 8b976c8e7d3be86878fd12cf3c195cb816faac98 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 03:46:11 -0800
Subject: [PATCH 12/31] slightly different api

---
 src/tests.rs |  6 ++++--
 src/v1.rs    | 29 +++++++++++++++++++----------
 2 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/src/tests.rs b/src/tests.rs
index 1e44e2a..d9c1da7 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -8,13 +8,15 @@ fn _1()->Result<(),crate::v1::Error>{
 	println!("header={:?}",bot_file.header);
 	for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{
 		println!("offline time={} block_id={:?}",time,block_id);
-		let block=bot_file.data.read_block(&bot_file.header,block_id)?;
+		let block_info=bot_file.header.block_info(block_id)?;
+		let block=bot_file.data.read_block_info(block_info)?;
 		// offline blocks include the following event types:
 		// World, Gravity, Run, Camera, Setting
 	}
 	for &TimedBlockId{time,block_id} in &bot_file.header.realtime_blocks_timeline{
 		println!("realtime time={} block_id={:?}",time,block_id);
-		let block=bot_file.data.read_block(&bot_file.header,block_id)?;
+		let block_info=bot_file.header.block_info(block_id)?;
+		let block=bot_file.data.read_block_info(block_info)?;
 		// realtime blocks include the following event types:
 		// Input, Output, Sound
 	}
diff --git a/src/v1.rs b/src/v1.rs
index 6762578..7e8415b 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -342,6 +342,20 @@ pub struct FileHeader{
 	#[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];
+		let end=self.block_positions[block_id.0 as usize+1];
+		Ok(BlockInfo{start,length:end-start})
+	}
+}
 
 pub struct File<R:BinReaderExt>{
 	pub header:FileHeader,
@@ -363,17 +377,12 @@ impl<R:BinReaderExt> FileData<R>{
 	fn data_mut(&mut self)->&mut R{
 		&mut self.data
 	}
-	fn block_reader(&mut self,header:&FileHeader,block_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
-		if header.block_positions.len() as u32<=block_id.0{
-			return Err(Error::InvalidBlockId(block_id))
-		}
-		let block_start=header.block_positions[block_id.0 as usize] as u64;
-		let block_end=header.block_positions[block_id.0 as usize+1] as u64;
-		self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?;
-		Ok(self.data_mut().take_seek(block_end-block_start))
+	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(&mut self,header:&FileHeader,block_id:BlockId)->Result<Block,Error>{
-		let data=self.block_reader(header,block_id)?;
+	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)
 	}

From 40a4920b120cff0fd6db7a1b61c4dab74a33053d Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 03:59:54 -0800
Subject: [PATCH 13/31] fix example

---
 README.md | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index cf08a3d..918eba2 100644
--- a/README.md
+++ b/README.md
@@ -4,19 +4,22 @@ Roblox Bhop/Surf Bot File Format
 ## Example
 
 ```rust
-use strafesnet_roblox_bot_file::File;
+use strafesnet_roblox_bot_file::{File,TimedBlockId};
 
 let file=std::fs::File::open("bot_file")?;
 let input=std::io::BufReader::new(file);
-let bot_file=File::new(input)?;
-
-for &(time,block_id) in &bot_file.header.offline_blocks_timeline{
-	let block=bot_file.read_block(block_id)?;
+let mut bot_file=File::new(input)?;
+for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{
+	// header is immutably borrowed
+	// while data is mutably borrowed
+	let block_info=bot_file.header.block_info(block_id)?;
+	let block=bot_file.data.read_block_info(block_info)?;
 	// offline blocks include the following event types:
 	// World, Gravity, Run, Camera, Setting
 }
-for &(time,block_id) in &bot_file.header.realtime_blocks_timeline{
-	let block=bot_file.read_block(block_id)?;
+for &TimedBlockId{time,block_id} in &bot_file.header.realtime_blocks_timeline{
+	let block_info=bot_file.header.block_info(block_id)?;
+	let block=bot_file.data.read_block_info(block_info)?;
 	// realtime blocks include the following event types:
 	// Input, Output, Sound
 }

From c3e5696a83fd3d2c78be267b55c433ebb45a6fb2 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 04:20:10 -0800
Subject: [PATCH 14/31] no stupid Bool thing

---
 src/v1.rs | 21 +++------------------
 1 file changed, 3 insertions(+), 18 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 7e8415b..621ba29 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -16,23 +16,6 @@ fn trey_double(f:f64)->f64{
 	f64::from_bits(m|(e<<52)|(s<<63))
 }
 
-#[binrw]
-#[brw(little)]
-#[derive(Clone,Copy,Debug)]
-pub enum Bool{
-	#[brw(magic=0u32)]
-	False,
-	#[brw(magic=1u32)]
-	True,
-}
-impl Into<bool> for Bool{
-	fn into(self)->bool{
-		match self{
-			Bool::False=>false,
-			Bool::True=>true,
-		}
-	}
-}
 #[binrw]
 #[brw(little)]
 pub struct Vector2{
@@ -124,7 +107,9 @@ pub struct WorldEventSetTime{
 #[binrw]
 #[brw(little)]
 pub struct WorldEventSetPaused{
-	pub paused:Bool,
+	#[br(map=|paused:u32|paused!=0)]
+	#[bw(map=|paused:&bool|*paused as u32)]
+	pub paused:bool,
 	#[brw(magic=b"quatdata")]
 	__:(),
 }

From 65cc20bdf99f07b3c6e0d82ff0c3f8c3cb904581 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 16:56:48 -0800
Subject: [PATCH 15/31] one less step

---
 src/v1.rs | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 621ba29..b341c0d 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -1,15 +1,13 @@
 use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
 
 // the bit chunks are deposited in reverse
-fn trey_float(f:f32)->f32{
-	let bits=f.to_bits();
+fn 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 trey_double(f:f64)->f64{
-	let bits=f.to_bits();
+fn trey_double(bits:u64)->f64{
 	let s=bits&1;
 	let e=(bits>>1)&((1<<11)-1);
 	let m=(bits>>(1+11))&((1<<52)-1);

From dd8cf2544d67eeeb76e92f6182eee82994fd5df1 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 16:56:55 -0800
Subject: [PATCH 16/31] rename functions

---
 src/v1.rs | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index b341c0d..887caa3 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -1,13 +1,13 @@
 use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
 
 // the bit chunks are deposited in reverse
-fn trey_float(bits:u32)->f32{
+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 trey_double(bits:u64)->f64{
+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);
@@ -17,19 +17,19 @@ fn trey_double(bits:u64)->f64{
 #[binrw]
 #[brw(little)]
 pub struct Vector2{
-	#[br(map=trey_float)]
+	#[br(map=read_trey_float)]
 	pub x:f32,
-	#[br(map=trey_float)]
+	#[br(map=read_trey_float)]
 	pub y:f32,
 }
 #[binrw]
 #[brw(little)]
 pub struct Vector3{
-	#[br(map=trey_float)]
+	#[br(map=read_trey_float)]
 	pub x:f32,
-	#[br(map=trey_float)]
+	#[br(map=read_trey_float)]
 	pub y:f32,
-	#[br(map=trey_float)]
+	#[br(map=read_trey_float)]
 	pub z:f32,
 }
 
@@ -43,7 +43,7 @@ pub struct InputEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedInputEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:InputEvent,
 }
@@ -61,7 +61,7 @@ pub struct OutputEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedOutputEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:OutputEvent,
 }
@@ -76,7 +76,7 @@ pub struct SoundEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedSoundEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:SoundEvent,
 }
@@ -97,7 +97,7 @@ pub struct WorldEventButton{
 #[binrw]
 #[brw(little)]
 pub struct WorldEventSetTime{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	#[brw(magic=b"data")]
 	__:(),
@@ -126,7 +126,7 @@ pub enum WorldEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedWorldEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:WorldEvent,
 }
@@ -140,7 +140,7 @@ pub struct GravityEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedGravityEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:GravityEvent,
 }
@@ -174,7 +174,7 @@ pub struct RunEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedRunEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:RunEvent,
 }
@@ -189,7 +189,7 @@ pub struct CameraEvent{
 #[binrw]
 #[brw(little)]
 pub struct TimedCameraEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:CameraEvent,
 }
@@ -199,13 +199,13 @@ pub struct TimedCameraEvent{
 #[brw(little)]
 pub struct SettingEvent{
 	pub setting_id:u32,
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub value:f64,
 }
 #[binrw]
 #[brw(little)]
 pub struct TimedSettingEvent{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub event:SettingEvent,
 }
@@ -305,7 +305,7 @@ pub struct BlockId(#[br(map=|i:u32|i-1)]u32);
 #[brw(little)]
 #[derive(Debug,Clone,Copy)]
 pub struct TimedBlockId{
-	#[br(map=trey_double)]
+	#[br(map=read_trey_double)]
 	pub time:f64,
 	pub block_id:BlockId,
 }

From 4a1e26e5be5212ebfc9992eb249138decbe2086d Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 16:59:50 -0800
Subject: [PATCH 17/31] use padding instead of magic

---
 src/v1.rs | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 887caa3..07bc82d 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -90,26 +90,23 @@ pub struct WorldEventReset{
 #[binrw]
 #[brw(little)]
 pub struct WorldEventButton{
+	#[br(pad_after=8)]
 	pub button_id:u32,
-	#[brw(magic=b"quatdata")]
-	__:(),
 }
 #[binrw]
 #[brw(little)]
 pub struct WorldEventSetTime{
 	#[br(map=read_trey_double)]
+	#[br(pad_after=4)]
 	pub time:f64,
-	#[brw(magic=b"data")]
-	__:(),
 }
 #[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,
-	#[brw(magic=b"quatdata")]
-	__:(),
 }
 #[binrw]
 #[brw(little)]

From 38d106d008c789c07304ecb71abd6a7d1e411b34 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 18:37:36 -0800
Subject: [PATCH 18/31] read_all

---
 src/v1.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 98 insertions(+), 4 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 07bc82d..42a0567 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -249,8 +249,8 @@ struct EventChunkHeader{
 // 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,
+	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);
@@ -259,10 +259,26 @@ where
 	}
 	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>(mut data:R)->binrw::BinResult<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{
@@ -276,7 +292,23 @@ impl Block{
 				EventType::Setting=>read_data_into_events(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?,
 			}
 		}
-		Ok(block)
+		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(())
 	}
 }
 
@@ -306,6 +338,16 @@ pub struct TimedBlockId{
 	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)]
@@ -337,6 +379,41 @@ impl FileHeader{
 	}
 }
 
+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>,
@@ -348,6 +425,18 @@ impl<R:BinReaderExt> File<R>{
 			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_info,&mut big_block)?;
+		}
+		Ok(big_block)
+	}
 }
 
 pub struct FileData<R:BinReaderExt>{
@@ -366,4 +455,9 @@ impl<R:BinReaderExt> FileData<R>{
 		let block=Block::read(data).map_err(Error::InvalidData)?;
 		Ok(block)
 	}
+	pub fn read_block_info_into(&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(())
+	}
 }

From 5177061ae5afe9eafb45a52996dbfc2629aa849a Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 18:40:27 -0800
Subject: [PATCH 19/31] test read_all

---
 src/tests.rs | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/tests.rs b/src/tests.rs
index d9c1da7..fedd861 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -23,3 +23,14 @@ fn _1()->Result<(),crate::v1::Error>{
 
 	Ok(())
 }
+
+#[test]
+fn _2()->Result<(),crate::v1::Error>{
+	let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
+	let input=std::io::BufReader::new(file);
+	let mut bot_file=crate::v1::File::new(input).unwrap();
+
+	let block=bot_file.read_all()?;
+
+	Ok(())
+}

From 942e5ee1d8ed7db08c3a7d63ca05d447355a50f9 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 18:42:02 -0800
Subject: [PATCH 20/31] update example

---
 README.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/README.md b/README.md
index 918eba2..ebdf7a7 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,11 @@ use strafesnet_roblox_bot_file::{File,TimedBlockId};
 let file=std::fs::File::open("bot_file")?;
 let input=std::io::BufReader::new(file);
 let mut bot_file=File::new(input)?;
+
+// read the whole file
+let block=bot_file.read_all()?;
+
+// or do data streaming block by block
 for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{
 	// header is immutably borrowed
 	// while data is mutably borrowed

From 5b58efe05261d9c11c72894816feb3d55b4ccacc Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 19:07:00 -0800
Subject: [PATCH 21/31] fix 1-indexed block positions

---
 src/v1.rs | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 42a0567..60b71fe 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -329,6 +329,10 @@ impl std::error::Error for Error{}
 #[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)]
@@ -358,7 +362,7 @@ pub struct FileHeader{
 	pub num_offline_events:u32,
 	pub num_realtime_events:u32,
 	#[br(count=num_offline_events+num_realtime_events+1)]
-	pub block_positions:Vec<u32>,
+	pub block_positions:Vec<BlockPosition>,
 	#[br(count=num_offline_events)]
 	pub offline_blocks_timeline:Vec<TimedBlockId>,
 	#[br(count=num_realtime_events)]
@@ -373,8 +377,8 @@ impl FileHeader{
 		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];
-		let end=self.block_positions[block_id.0 as usize+1];
+		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})
 	}
 }

From 301b4681804673e094d2edd955d57c3c373aa655 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 21:18:29 -0800
Subject: [PATCH 22/31] ignore test files

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index ea8c4bf..51124b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /target
+/files

From 53bde2bc3f1af11e68e0566c389f4c8cefde43b8 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 21:33:48 -0800
Subject: [PATCH 23/31] test time

---
 src/tests.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/tests.rs b/src/tests.rs
index fedd861..b030ef4 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -28,9 +28,14 @@ fn _1()->Result<(),crate::v1::Error>{
 fn _2()->Result<(),crate::v1::Error>{
 	let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
 	let input=std::io::BufReader::new(file);
+
+	let t0=std::time::Instant::now();
+
 	let mut bot_file=crate::v1::File::new(input).unwrap();
 
 	let block=bot_file.read_all()?;
 
+	println!("{:?}",t0.elapsed());
+
 	Ok(())
 }

From a78be6b79ab792c3e2471967809c2e8b2d2e784f Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 21:50:20 -0800
Subject: [PATCH 24/31] rename symbol

---
 src/v1.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 60b71fe..8600e76 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -437,7 +437,7 @@ impl<R:BinReaderExt> File<R>{
 		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_info,&mut big_block)?;
+			self.data.read_block_info_into_block(block_info,&mut big_block)?;
 		}
 		Ok(big_block)
 	}
@@ -459,7 +459,7 @@ impl<R:BinReaderExt> FileData<R>{
 		let block=Block::read(data).map_err(Error::InvalidData)?;
 		Ok(block)
 	}
-	pub fn read_block_info_into(&mut self,block_info:BlockInfo,block:&mut Block)->Result<(),Error>{
+	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(())

From 9f0505cdc9edefb71aadeca8e41b65c8a0e2d6e9 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 22:17:09 -0800
Subject: [PATCH 25/31] expand on id types with enums

---
 src/v1.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 89 insertions(+), 5 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 8600e76..762c0af 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -69,8 +69,29 @@ pub struct TimedOutputEvent{
 // 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:u32,
+	pub sound_type:SoundType,
+	/// Roblox enum
 	pub material:u32,
 }
 #[binrw]
@@ -163,10 +184,51 @@ pub enum RunEventType{
 }
 #[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{
 	run_event_type:RunEventType,
-	mode_id:i32,
-	flag_reason_id:u32,
+	mode:Mode,
+	flag_reason:FlagReason,
 }
 #[binrw]
 #[brw(little)]
@@ -179,8 +241,16 @@ pub struct TimedRunEvent{
 // camera
 #[binrw]
 #[brw(little)]
+pub enum CameraEventType{
+	#[brw(magic=0u32)]
+	CameraPunch,
+	#[brw(magic=1u32)]
+	Transform,
+}
+#[binrw]
+#[brw(little)]
 pub struct CameraEvent{
-	pub camera_event_id:u32,
+	pub camera_event_type:CameraEventType,
 	pub value:Vector3,
 }
 #[binrw]
@@ -194,8 +264,22 @@ pub struct TimedCameraEvent{
 // 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_id:u32,
+	pub setting_type:SettingType,
 	#[br(map=read_trey_double)]
 	pub value:f64,
 }

From f7f937aa67a318d4db1baa8c505587e0251eaa92 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 22:32:21 -0800
Subject: [PATCH 26/31] bitflags dep

---
 Cargo.lock | 7 +++++++
 Cargo.toml | 1 +
 2 files changed, 8 insertions(+)

diff --git a/Cargo.lock b/Cargo.lock
index 4e57f40..2408728 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -32,6 +32,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
 [[package]]
 name = "bytemuck"
 version = "1.21.0"
@@ -73,6 +79,7 @@ name = "strafesnet_roblox_bot_file"
 version = "0.1.0"
 dependencies = [
  "binrw",
+ "bitflags",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 4f209b1..afd8180 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,3 +5,4 @@ edition = "2021"
 
 [dependencies]
 binrw = "0.14.1"
+bitflags = "2.6.0"

From 728d13080f50f5c740c15f4ad4d0b7ed173f5450 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 22:33:14 -0800
Subject: [PATCH 27/31] bitflags

---
 src/v1.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 62 insertions(+), 2 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index 762c0af..d839554 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -33,11 +33,48 @@ pub struct Vector3{
 	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{
-	pub game_controls:u32,
+	#[br(try_map=GameControls::try_from_bits)]
+	#[bw(map=GameControls::bits)]
+	pub game_controls:GameControls,
 	pub mouse_pos:Vector2,
 }
 #[binrw]
@@ -49,10 +86,33 @@ pub struct TimedInputEvent{
 }
 
 // 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{
-	pub tick_info:u32,
+	#[br(try_map=TickInfo::try_from_bits)]
+	#[bw(map=TickInfo::bits)]
+	pub tick_info:TickInfo,
 	pub angles:Vector3,
 	pub position:Vector3,
 	pub velocity:Vector3,

From 79c12b1af12bb9cb340bb8b152d2ebba78513bd1 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 22:48:40 -0800
Subject: [PATCH 28/31] pub typo

---
 src/v1.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/v1.rs b/src/v1.rs
index d839554..f448d70 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -286,9 +286,9 @@ pub enum FlagReason{
 #[binrw]
 #[brw(little)]
 pub struct RunEvent{
-	run_event_type:RunEventType,
-	mode:Mode,
-	flag_reason:FlagReason,
+	pub run_event_type:RunEventType,
+	pub mode:Mode,
+	pub flag_reason:FlagReason,
 }
 #[binrw]
 #[brw(little)]

From 08269a2d3f6ecd381710a5b6f29c75926a082506 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 22:50:14 -0800
Subject: [PATCH 29/31] version typo

---
 src/lib.rs           |  2 +-
 src/tests.rs         | 10 +++++-----
 src/{v1.rs => v0.rs} |  0
 3 files changed, 6 insertions(+), 6 deletions(-)
 rename src/{v1.rs => v0.rs} (100%)

diff --git a/src/lib.rs b/src/lib.rs
index d80700a..cc08a0d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-pub mod v1;
+pub mod v0;
 
 #[cfg(test)]
 mod tests;
diff --git a/src/tests.rs b/src/tests.rs
index b030ef4..d1d3f84 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -1,10 +1,10 @@
-use crate::v1::TimedBlockId;
+use crate::v0::TimedBlockId;
 
 #[test]
-fn _1()->Result<(),crate::v1::Error>{
+fn _1()->Result<(),crate::v0::Error>{
 	let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
 	let input=std::io::BufReader::new(file);
-	let mut bot_file=crate::v1::File::new(input).unwrap();
+	let mut bot_file=crate::v0::File::new(input).unwrap();
 	println!("header={:?}",bot_file.header);
 	for &TimedBlockId{time,block_id} in &bot_file.header.offline_blocks_timeline{
 		println!("offline time={} block_id={:?}",time,block_id);
@@ -25,13 +25,13 @@ fn _1()->Result<(),crate::v1::Error>{
 }
 
 #[test]
-fn _2()->Result<(),crate::v1::Error>{
+fn _2()->Result<(),crate::v0::Error>{
 	let file=std::fs::File::open("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
 	let input=std::io::BufReader::new(file);
 
 	let t0=std::time::Instant::now();
 
-	let mut bot_file=crate::v1::File::new(input).unwrap();
+	let mut bot_file=crate::v0::File::new(input).unwrap();
 
 	let block=bot_file.read_all()?;
 
diff --git a/src/v1.rs b/src/v0.rs
similarity index 100%
rename from src/v1.rs
rename to src/v0.rs

From 0f448fd707b2d98c4611b8fb348a0f330dd0b04c Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Tue, 31 Dec 2024 22:50:24 -0800
Subject: [PATCH 30/31] v0.2.0 fixes

---
 Cargo.lock | 2 +-
 Cargo.toml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2408728..42ad11c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -76,7 +76,7 @@ dependencies = [
 
 [[package]]
 name = "strafesnet_roblox_bot_file"
-version = "0.1.0"
+version = "0.2.0"
 dependencies = [
  "binrw",
  "bitflags",
diff --git a/Cargo.toml b/Cargo.toml
index afd8180..90803cf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "strafesnet_roblox_bot_file"
-version = "0.1.0"
+version = "0.2.0"
 edition = "2021"
 
 [dependencies]

From ea98660f01b972acc1e63978174cbe0808c953ab Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Thu, 2 Jan 2025 19:56:29 -0800
Subject: [PATCH 31/31] roblox_bot_file: move into folder

---
 .gitignore => lib/roblox_bot_file/.gitignore         | 0
 Cargo.lock => lib/roblox_bot_file/Cargo.lock         | 0
 Cargo.toml => lib/roblox_bot_file/Cargo.toml         | 0
 LICENSE-APACHE => lib/roblox_bot_file/LICENSE-APACHE | 0
 LICENSE-MIT => lib/roblox_bot_file/LICENSE-MIT       | 0
 README.md => lib/roblox_bot_file/README.md           | 0
 {src => lib/roblox_bot_file/src}/lib.rs              | 0
 {src => lib/roblox_bot_file/src}/tests.rs            | 0
 {src => lib/roblox_bot_file/src}/v0.rs               | 0
 9 files changed, 0 insertions(+), 0 deletions(-)
 rename .gitignore => lib/roblox_bot_file/.gitignore (100%)
 rename Cargo.lock => lib/roblox_bot_file/Cargo.lock (100%)
 rename Cargo.toml => lib/roblox_bot_file/Cargo.toml (100%)
 rename LICENSE-APACHE => lib/roblox_bot_file/LICENSE-APACHE (100%)
 rename LICENSE-MIT => lib/roblox_bot_file/LICENSE-MIT (100%)
 rename README.md => lib/roblox_bot_file/README.md (100%)
 rename {src => lib/roblox_bot_file/src}/lib.rs (100%)
 rename {src => lib/roblox_bot_file/src}/tests.rs (100%)
 rename {src => lib/roblox_bot_file/src}/v0.rs (100%)

diff --git a/.gitignore b/lib/roblox_bot_file/.gitignore
similarity index 100%
rename from .gitignore
rename to lib/roblox_bot_file/.gitignore
diff --git a/Cargo.lock b/lib/roblox_bot_file/Cargo.lock
similarity index 100%
rename from Cargo.lock
rename to lib/roblox_bot_file/Cargo.lock
diff --git a/Cargo.toml b/lib/roblox_bot_file/Cargo.toml
similarity index 100%
rename from Cargo.toml
rename to lib/roblox_bot_file/Cargo.toml
diff --git a/LICENSE-APACHE b/lib/roblox_bot_file/LICENSE-APACHE
similarity index 100%
rename from LICENSE-APACHE
rename to lib/roblox_bot_file/LICENSE-APACHE
diff --git a/LICENSE-MIT b/lib/roblox_bot_file/LICENSE-MIT
similarity index 100%
rename from LICENSE-MIT
rename to lib/roblox_bot_file/LICENSE-MIT
diff --git a/README.md b/lib/roblox_bot_file/README.md
similarity index 100%
rename from README.md
rename to lib/roblox_bot_file/README.md
diff --git a/src/lib.rs b/lib/roblox_bot_file/src/lib.rs
similarity index 100%
rename from src/lib.rs
rename to lib/roblox_bot_file/src/lib.rs
diff --git a/src/tests.rs b/lib/roblox_bot_file/src/tests.rs
similarity index 100%
rename from src/tests.rs
rename to lib/roblox_bot_file/src/tests.rs
diff --git a/src/v0.rs b/lib/roblox_bot_file/src/v0.rs
similarity index 100%
rename from src/v0.rs
rename to lib/roblox_bot_file/src/v0.rs