forked from StrafesNET/strafe-project
Merge branch 'roblox_bot_file'
This commit is contained in:
commit
9d48707675
2
lib/roblox_bot_file/.gitignore
vendored
Normal file
2
lib/roblox_bot_file/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/files
|
100
lib/roblox_bot_file/Cargo.lock
generated
Normal file
100
lib/roblox_bot_file/Cargo.lock
generated
Normal file
@ -0,0 +1,100 @@
|
||||
# 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 = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[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.2.0"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[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"
|
8
lib/roblox_bot_file/Cargo.toml
Normal file
8
lib/roblox_bot_file/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.14.1"
|
||||
bitflags = "2.6.0"
|
176
lib/roblox_bot_file/LICENSE-APACHE
Normal file
176
lib/roblox_bot_file/LICENSE-APACHE
Normal file
@ -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
|
23
lib/roblox_bot_file/LICENSE-MIT
Normal file
23
lib/roblox_bot_file/LICENSE-MIT
Normal file
@ -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.
|
46
lib/roblox_bot_file/README.md
Normal file
46
lib/roblox_bot_file/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
Roblox Bhop/Surf Bot File Format
|
||||
================================
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
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
|
||||
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{
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
#### 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>
|
4
lib/roblox_bot_file/src/lib.rs
Normal file
4
lib/roblox_bot_file/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod v0;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
41
lib/roblox_bot_file/src/tests.rs
Normal file
41
lib/roblox_bot_file/src/tests.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use crate::v0::TimedBlockId;
|
||||
|
||||
#[test]
|
||||
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::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);
|
||||
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_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
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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::v0::File::new(input).unwrap();
|
||||
|
||||
let block=bot_file.read_all()?;
|
||||
|
||||
println!("{:?}",t0.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
611
lib/roblox_bot_file/src/v0.rs
Normal file
611
lib/roblox_bot_file/src/v0.rs
Normal file
@ -0,0 +1,611 @@
|
||||
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(())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user