Compare commits
11 Commits
52d58efa23
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8125467196 | ||
|
|
88f05e8efe | ||
|
|
4b1cb50566 | ||
| a0eab7c68f | |||
|
|
d4ae8f2d14 | ||
|
|
bca4a60718 | ||
|
|
ee98def536 | ||
|
|
ba48e89617 | ||
|
|
14c422983e | ||
|
|
840ac071b1 | ||
|
|
c48e8f3e42 |
6
.github/workflows/test.yaml
vendored
6
.github/workflows/test.yaml
vendored
@@ -23,11 +23,11 @@ jobs:
|
|||||||
|
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
crate: [analysis, backend]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
lfs: 'true'
|
||||||
|
submodules: 'recursive'
|
||||||
- name: Install latest nightly
|
- name: Install latest nightly
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -19,9 +19,9 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.88"
|
version = "1.0.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
|
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@@ -37,9 +37,9 @@ checksum = "ef3a13b71496a92e8c00ebe576b260655b56935cd5118d5a8949788b651b5e07"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.7.1"
|
version = "1.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -161,9 +161,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -173,9 +173,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -192,9 +192,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.158"
|
version = "0.2.161"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@@ -222,9 +222,9 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.0"
|
version = "1.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "petgraph"
|
name = "petgraph"
|
||||||
@@ -300,18 +300,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.86"
|
version = "1.0.88"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost"
|
name = "prost"
|
||||||
version = "0.13.2"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995"
|
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"prost-derive",
|
"prost-derive",
|
||||||
@@ -319,9 +319,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-build"
|
name = "prost-build"
|
||||||
version = "0.13.2"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302"
|
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck",
|
"heck",
|
||||||
@@ -340,9 +340,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-derive"
|
name = "prost-derive"
|
||||||
version = "0.13.2"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac"
|
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools",
|
"itertools",
|
||||||
@@ -353,9 +353,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.13.2"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519"
|
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
@@ -386,9 +386,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.6"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -398,9 +398,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.7"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -415,9 +415,9 @@ checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.4"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
@@ -446,9 +446,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.77"
|
version = "2.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -457,9 +457,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.12.0"
|
version = "3.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
|
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
prost = "0.13.2"
|
prost = { version = "0.13.2" }
|
||||||
prost-types = "0.13.2"
|
prost-types = { version = "0.13.2" }
|
||||||
|
|
||||||
# For decompressing any compressed frames
|
# For decompressing any compressed frames
|
||||||
snap = "1.1.1"
|
snap = "1.1.1"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# CSDemo
|
# CSDemo
|
||||||
A parser for cs2 demo files
|
A parser for cs2 demo files.
|
||||||
|
Migrated from [GitHub](https://github.com/Lol3rrr/csdemo)
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- `protoc` needs to be installed locally
|
- `protoc` needs to be installed locally
|
||||||
|
|||||||
@@ -7,28 +7,70 @@ fn main() {
|
|||||||
|
|
||||||
mod eager {
|
mod eager {
|
||||||
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
|
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
|
||||||
fn no_entities_mirage() -> csdemo::parser::FirstPassOutput {
|
fn no_entities_mirage() {
|
||||||
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
|
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
|
||||||
|
|
||||||
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
|
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
|
||||||
|
|
||||||
csdemo::parser::parse(
|
let demo = csdemo::parser::parse(
|
||||||
csdemo::FrameIterator::parse(container.inner),
|
csdemo::FrameIterator::parse(container.inner),
|
||||||
csdemo::parser::EntityFilter::disabled(),
|
csdemo::parser::EntityFilter::disabled(),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
|
||||||
|
for event in demo.events {
|
||||||
|
divan::black_box(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
|
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
|
||||||
fn entities_mirage() -> csdemo::parser::FirstPassOutput {
|
fn entities_mirage() {
|
||||||
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
|
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
|
||||||
|
|
||||||
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
|
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
|
||||||
|
|
||||||
csdemo::parser::parse(
|
let demo = csdemo::parser::parse(
|
||||||
csdemo::FrameIterator::parse(container.inner),
|
csdemo::FrameIterator::parse(container.inner),
|
||||||
csdemo::parser::EntityFilter::all(),
|
csdemo::parser::EntityFilter::all(),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
|
||||||
|
for event in demo.events {
|
||||||
|
divan::black_box(event);
|
||||||
|
}
|
||||||
|
for entity in demo.entity_states.ticks {
|
||||||
|
divan::black_box(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod lazy {
|
||||||
|
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
|
||||||
|
fn no_entities_mirage() {
|
||||||
|
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
|
||||||
|
|
||||||
|
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
|
||||||
|
|
||||||
|
let demo = csdemo::lazyparser::LazyParser::new(container);
|
||||||
|
|
||||||
|
for event in demo.events() {
|
||||||
|
divan::black_box(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
|
||||||
|
fn entities_mirage() {
|
||||||
|
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
|
||||||
|
|
||||||
|
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
|
||||||
|
|
||||||
|
let demo = csdemo::lazyparser::LazyParser::new(container);
|
||||||
|
|
||||||
|
for event in demo.events() {
|
||||||
|
divan::black_box(event);
|
||||||
|
}
|
||||||
|
for entity in demo.entities() {
|
||||||
|
divan::black_box(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
examples/lazy-ancient-entity.rs
Normal file
25
examples/lazy-ancient-entity.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const DATA: &[u8] = include_bytes!("../testfiles/de_ancient.dem");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let container = csdemo::Container::parse(DATA).unwrap();
|
||||||
|
|
||||||
|
let demo = csdemo::lazyparser::LazyParser::new(container);
|
||||||
|
|
||||||
|
for entity in demo.entities() {
|
||||||
|
core::hint::black_box(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
for tick in output.entity_states.ticks.iter() {
|
||||||
|
for state in tick.states.iter() {
|
||||||
|
if state.class.as_str() != "CCSPlayerPawn" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for prop in state.props.iter() {
|
||||||
|
println!("{:?} = {:?}", prop.prop_info.prop_name, prop.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use crate::{csgo_proto, RawValue, UserId};
|
|||||||
|
|
||||||
macro_rules! define_event {
|
macro_rules! define_event {
|
||||||
($name:ident, $target:path $(, ($field:ident, $field_ty:ty))*) => {
|
($name:ident, $target:path $(, ($field:ident, $field_ty:ty))*) => {
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
$(pub $field: Option<$field_ty>,)*
|
$(pub $field: Option<$field_ty>,)*
|
||||||
@@ -231,7 +231,7 @@ type ParseFn = fn(
|
|||||||
event: csgo_proto::CMsgSource1LegacyGameEvent,
|
event: csgo_proto::CMsgSource1LegacyGameEvent,
|
||||||
) -> Result<GameEvent, ParseGameEventError>;
|
) -> Result<GameEvent, ParseGameEventError>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum GameEvent {
|
pub enum GameEvent {
|
||||||
HltvVersionInfo(HltvVersionInfo),
|
HltvVersionInfo(HltvVersionInfo),
|
||||||
|
|||||||
146
src/lazyparser.rs
Normal file
146
src/lazyparser.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use crate::{Container, FrameIterator};
|
||||||
|
|
||||||
|
mod events;
|
||||||
|
pub use events::LazyEventIterator;
|
||||||
|
|
||||||
|
mod entities;
|
||||||
|
pub use entities::LazyEntityIterator;
|
||||||
|
|
||||||
|
pub struct LazyParser<'b> {
|
||||||
|
container: Container<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> LazyParser<'b> {
|
||||||
|
pub fn new(container: Container<'b>) -> Self {
|
||||||
|
Self { container }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_header(&self) -> Option<crate::csgo_proto::CDemoFileHeader> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
for frame in FrameIterator::parse(self.container.inner) {
|
||||||
|
if let crate::DemoCommand::FileHeader = frame.cmd {
|
||||||
|
let data = frame.decompress_with_buf(&mut buffer).ok()?;
|
||||||
|
let raw: crate::csgo_proto::CDemoFileHeader = prost::Message::decode(data).ok()?;
|
||||||
|
return Some(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_info(&self) -> Option<crate::csgo_proto::CDemoFileInfo> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
for frame in FrameIterator::parse(self.container.inner) {
|
||||||
|
if let crate::DemoCommand::FileInfo = frame.cmd {
|
||||||
|
let data = frame.decompress_with_buf(&mut buffer).ok()?;
|
||||||
|
let raw: crate::csgo_proto::CDemoFileInfo = prost::Message::decode(data).ok()?;
|
||||||
|
return Some(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_info(&self) -> std::collections::HashMap<crate::UserId, crate::parser::Player> {
|
||||||
|
let mut result = std::collections::HashMap::new();
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
for frame in FrameIterator::parse(self.container.inner) {
|
||||||
|
let packet = match frame.cmd {
|
||||||
|
crate::DemoCommand::Packet | crate::DemoCommand::SignonPacket => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let raw: crate::csgo_proto::CDemoPacket = match prost::Message::decode(data) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
raw
|
||||||
|
}
|
||||||
|
crate::DemoCommand::FullPacket => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let raw: crate::csgo_proto::CDemoFullPacket = match prost::Message::decode(data)
|
||||||
|
{
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
match raw.packet {
|
||||||
|
Some(p) => p,
|
||||||
|
None => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bitreader = crate::bitreader::Bitreader::new(packet.data());
|
||||||
|
|
||||||
|
while bitreader.bits_remaining().unwrap_or(0) > 8 {
|
||||||
|
let msg_type = match bitreader.read_u_bit_var() {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => break,
|
||||||
|
};
|
||||||
|
let size = match bitreader.read_varint() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => break,
|
||||||
|
};
|
||||||
|
let msg_bytes = match bitreader.read_n_bytes(size as usize) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(msg_bytes.len(), size as usize);
|
||||||
|
|
||||||
|
let net_msg_type =
|
||||||
|
match crate::netmessagetypes::NetmessageType::try_from(msg_type as i32) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match net_msg_type {
|
||||||
|
crate::netmessagetypes::NetmessageType::CS_UM_EndOfMatchAllPlayersData => {
|
||||||
|
let raw: crate::csgo_proto::CcsUsrMsgEndOfMatchAllPlayersData =
|
||||||
|
match prost::Message::decode(msg_bytes.as_slice()) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
for data in raw.allplayerdata {
|
||||||
|
result.insert(
|
||||||
|
crate::UserId(data.slot()),
|
||||||
|
crate::parser::Player {
|
||||||
|
name: data.name.unwrap(),
|
||||||
|
xuid: data.xuid.unwrap(),
|
||||||
|
team: data.teamnumber.unwrap(),
|
||||||
|
color: data.playercolor.unwrap(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_unknown => {
|
||||||
|
// dbg!(unknown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events(&self) -> LazyEventIterator<'b> {
|
||||||
|
LazyEventIterator::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entities(&self) -> LazyEntityIterator<'b> {
|
||||||
|
LazyEntityIterator::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
345
src/lazyparser/entities.rs
Normal file
345
src/lazyparser/entities.rs
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
use crate::{
|
||||||
|
parser::{
|
||||||
|
decoder, entities, propcontroller, sendtables, update_entity, Class, FirstPassError, Paths,
|
||||||
|
},
|
||||||
|
DemoCommand, FrameIterator,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub struct LazyEntityIterator<'b> {
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
frames: FrameIterator<'b>,
|
||||||
|
|
||||||
|
current_tick: u32,
|
||||||
|
pending_entities: VecDeque<(u32, entities::EntityState)>,
|
||||||
|
|
||||||
|
paths: Paths,
|
||||||
|
baselines: std::collections::HashMap<u32, Vec<u8>>,
|
||||||
|
serializers: std::collections::HashMap<String, sendtables::Serializer>,
|
||||||
|
qf_mapper: decoder::QfMapper,
|
||||||
|
prop_controller: propcontroller::PropController,
|
||||||
|
entity_ctx: entities::EntityContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> LazyEntityIterator<'b> {
|
||||||
|
pub(crate) fn new(parser: &super::LazyParser<'b>) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: Vec::new(),
|
||||||
|
frames: FrameIterator::parse(parser.container.inner),
|
||||||
|
|
||||||
|
current_tick: 0,
|
||||||
|
pending_entities: VecDeque::with_capacity(64),
|
||||||
|
|
||||||
|
paths: Paths::new(),
|
||||||
|
baselines: std::collections::HashMap::new(),
|
||||||
|
serializers: std::collections::HashMap::new(),
|
||||||
|
qf_mapper: decoder::QfMapper {
|
||||||
|
idx: 0,
|
||||||
|
map: std::collections::HashMap::new(),
|
||||||
|
},
|
||||||
|
prop_controller: propcontroller::PropController::new(),
|
||||||
|
entity_ctx: entities::EntityContext {
|
||||||
|
entities: std::collections::HashMap::new(),
|
||||||
|
cls_to_class: std::collections::HashMap::new(),
|
||||||
|
filter: entities::EntityFilter::all(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> LazyEntityIterator<'b> {
|
||||||
|
fn inner_parse_packet(
|
||||||
|
raw: &crate::csgo_proto::CDemoPacket,
|
||||||
|
entity_ctx: &mut entities::EntityContext,
|
||||||
|
paths: &mut Paths,
|
||||||
|
qf_mapper: &mut decoder::QfMapper,
|
||||||
|
baselines: &mut std::collections::HashMap<u32, Vec<u8>>,
|
||||||
|
prop_controller: &propcontroller::PropController,
|
||||||
|
entity_states: &mut VecDeque<(u32, entities::EntityState)>,
|
||||||
|
current_tick: &mut u32,
|
||||||
|
) -> Result<(), FirstPassError> {
|
||||||
|
let mut bitreader = crate::bitreader::Bitreader::new(raw.data());
|
||||||
|
|
||||||
|
let mut msg_bytes = Vec::new();
|
||||||
|
|
||||||
|
while bitreader.bits_remaining().unwrap_or(0) > 8 {
|
||||||
|
let msg_type = bitreader.read_u_bit_var()?;
|
||||||
|
let size = bitreader.read_varint()?;
|
||||||
|
msg_bytes.clear();
|
||||||
|
msg_bytes.resize(size as usize, 0);
|
||||||
|
bitreader.read_n_bytes_mut(size as usize, &mut msg_bytes)?;
|
||||||
|
|
||||||
|
assert_eq!(msg_bytes.len(), size as usize);
|
||||||
|
|
||||||
|
let net_msg_type =
|
||||||
|
match crate::netmessagetypes::NetmessageType::try_from(msg_type as i32) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match net_msg_type {
|
||||||
|
crate::netmessagetypes::NetmessageType::net_Tick => {
|
||||||
|
let raw: crate::csgo_proto::CnetMsgTick =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice())?;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
*current_tick <= raw.tick(),
|
||||||
|
"Current Tick {} <= Tick Packet {}",
|
||||||
|
*current_tick,
|
||||||
|
raw.tick()
|
||||||
|
);
|
||||||
|
if raw.tick() > *current_tick {
|
||||||
|
*current_tick = raw.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// How to handle these things?
|
||||||
|
crate::netmessagetypes::NetmessageType::svc_ClearAllStringTables => {}
|
||||||
|
crate::netmessagetypes::NetmessageType::svc_CreateStringTable => {}
|
||||||
|
crate::netmessagetypes::NetmessageType::svc_UpdateStringTable => {}
|
||||||
|
crate::netmessagetypes::NetmessageType::svc_PacketEntities => {
|
||||||
|
let raw: crate::csgo_proto::CsvcMsgPacketEntities =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice())?;
|
||||||
|
|
||||||
|
if entity_ctx.filter.enabled {
|
||||||
|
let mut bitreader = crate::bitreader::Bitreader::new(raw.entity_data());
|
||||||
|
let mut entity_id: i32 = -1;
|
||||||
|
for _ in 0..raw.updated_entries() {
|
||||||
|
entity_id += 1 + (bitreader.read_u_bit_var()? as i32);
|
||||||
|
|
||||||
|
match bitreader.read_nbits(2)? {
|
||||||
|
0b01 | 0b11 => {
|
||||||
|
entity_ctx.entities.remove(&entity_id);
|
||||||
|
}
|
||||||
|
0b10 => {
|
||||||
|
let cls =
|
||||||
|
entity_ctx.create_entity(entity_id, &mut bitreader)?;
|
||||||
|
|
||||||
|
if let Some(baseline_bytes) = baselines.get(&cls) {
|
||||||
|
let mut br =
|
||||||
|
crate::bitreader::Bitreader::new(baseline_bytes);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// How should we handle is this?
|
||||||
|
let _state = update_entity(
|
||||||
|
entity_id,
|
||||||
|
&mut br,
|
||||||
|
entity_ctx,
|
||||||
|
paths,
|
||||||
|
qf_mapper,
|
||||||
|
prop_controller,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = update_entity(
|
||||||
|
entity_id,
|
||||||
|
&mut bitreader,
|
||||||
|
entity_ctx,
|
||||||
|
paths,
|
||||||
|
qf_mapper,
|
||||||
|
prop_controller,
|
||||||
|
)?;
|
||||||
|
if let Some(state) = state {
|
||||||
|
entity_states.push_back((*current_tick, state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0b00 => {
|
||||||
|
if raw.has_pvs_vis_bits() > 0
|
||||||
|
&& bitreader.read_nbits(2)? & 0x01 == 1
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = update_entity(
|
||||||
|
entity_id,
|
||||||
|
&mut bitreader,
|
||||||
|
entity_ctx,
|
||||||
|
paths,
|
||||||
|
qf_mapper,
|
||||||
|
prop_controller,
|
||||||
|
)?;
|
||||||
|
if let Some(state) = state {
|
||||||
|
entity_states.push_back((*current_tick, state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unknown => {
|
||||||
|
panic!("{:?}", unknown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// dbg!(unknown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> Iterator for LazyEntityIterator<'b> {
|
||||||
|
type Item = Result<(u32, crate::parser::entities::EntityState), ()>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some(tmp) = self.pending_entities.pop_front() {
|
||||||
|
return Some(Ok(tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(frame) = self.frames.next() {
|
||||||
|
match frame.cmd {
|
||||||
|
DemoCommand::SignonPacket | DemoCommand::Packet => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
let raw: crate::csgo_proto::CDemoPacket = match prost::Message::decode(data) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = Self::inner_parse_packet(
|
||||||
|
&raw,
|
||||||
|
&mut self.entity_ctx,
|
||||||
|
&mut self.paths,
|
||||||
|
&mut self.qf_mapper,
|
||||||
|
&mut self.baselines,
|
||||||
|
&mut self.prop_controller,
|
||||||
|
&mut self.pending_entities,
|
||||||
|
&mut self.current_tick,
|
||||||
|
) {
|
||||||
|
return Some(Err(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DemoCommand::FullPacket => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
let raw: crate::csgo_proto::CDemoFullPacket = match prost::Message::decode(data)
|
||||||
|
{
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(packet) = raw.packet {
|
||||||
|
if let Err(e) = Self::inner_parse_packet(
|
||||||
|
&packet,
|
||||||
|
&mut self.entity_ctx,
|
||||||
|
&mut self.paths,
|
||||||
|
&mut self.qf_mapper,
|
||||||
|
&mut self.baselines,
|
||||||
|
&mut self.prop_controller,
|
||||||
|
&mut self.pending_entities,
|
||||||
|
&mut self.current_tick,
|
||||||
|
) {
|
||||||
|
return Some(Err(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling all the "meta" related stuff
|
||||||
|
DemoCommand::StringTables => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
let raw: crate::csgo_proto::CDemoStringTables =
|
||||||
|
match prost::Message::decode(data) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
for table in raw.tables.into_iter() {
|
||||||
|
if table.table_name() == "instancebaseline" {
|
||||||
|
for item in table.items.into_iter() {
|
||||||
|
let k = item.str().parse::<u32>().unwrap_or(u32::MAX);
|
||||||
|
self.baselines.insert(k, item.data.unwrap_or(Vec::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DemoCommand::SendTables => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
let tables: crate::csgo_proto::CDemoSendTables =
|
||||||
|
match prost::Message::decode(data) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bitreader = crate::bitreader::Bitreader::new(tables.data());
|
||||||
|
|
||||||
|
let n_bytes = match bitreader.read_varint() {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
let bytes = match bitreader.read_n_bytes(n_bytes as usize) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serializer_msg: crate::csgo_proto::CsvcMsgFlattenedSerializer =
|
||||||
|
match prost::Message::decode(bytes.as_slice()) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// std::fs::write("send_table.b", bytes.as_slice());
|
||||||
|
|
||||||
|
assert!(self.serializers.is_empty());
|
||||||
|
self.serializers = match sendtables::get_serializers(
|
||||||
|
&serializer_msg,
|
||||||
|
&mut self.qf_mapper,
|
||||||
|
&mut self.prop_controller,
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
DemoCommand::ClassInfo => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
let raw: crate::csgo_proto::CDemoClassInfo = match prost::Message::decode(data)
|
||||||
|
{
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.entity_ctx.cls_to_class.clear();
|
||||||
|
|
||||||
|
for class_t in raw.classes {
|
||||||
|
let cls_id = class_t.class_id();
|
||||||
|
let network_name = class_t.network_name();
|
||||||
|
|
||||||
|
if let Some(ser) = self.serializers.remove(network_name) {
|
||||||
|
self.entity_ctx.cls_to_class.insert(
|
||||||
|
cls_id as u32,
|
||||||
|
Class {
|
||||||
|
name: network_name.into(),
|
||||||
|
serializer: ser,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tmp) = self.pending_entities.pop_front() {
|
||||||
|
return Some(Ok(tmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
213
src/lazyparser/events.rs
Normal file
213
src/lazyparser/events.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
use crate::{parser::GameEventMapping, DemoEvent, FrameIterator};
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub struct LazyEventIterator<'b> {
|
||||||
|
pub(super) buffer: Vec<u8>,
|
||||||
|
pub(super) frames: FrameIterator<'b>,
|
||||||
|
|
||||||
|
pub(super) pending_events: VecDeque<crate::DemoEvent>,
|
||||||
|
pub(super) event_mapper: GameEventMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> LazyEventIterator<'b> {
|
||||||
|
pub(crate) fn new(parser: &super::LazyParser<'b>) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: Vec::new(),
|
||||||
|
frames: FrameIterator::parse(parser.container.inner),
|
||||||
|
|
||||||
|
pending_events: VecDeque::with_capacity(64),
|
||||||
|
event_mapper: crate::parser::GameEventMapping {
|
||||||
|
mapping: std::collections::HashMap::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> LazyEventIterator<'b> {
|
||||||
|
fn inner_parse_packet(
|
||||||
|
raw: &crate::csgo_proto::CDemoPacket,
|
||||||
|
events: &mut VecDeque<DemoEvent>,
|
||||||
|
event_mapper: &mut GameEventMapping,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let mut bitreader = crate::bitreader::Bitreader::new(raw.data());
|
||||||
|
|
||||||
|
while bitreader.bits_remaining().unwrap_or(0) > 8 {
|
||||||
|
let msg_type = bitreader.read_u_bit_var().map_err(|e| ())?;
|
||||||
|
let size = bitreader.read_varint().map_err(|e| ())?;
|
||||||
|
let msg_bytes = bitreader.read_n_bytes(size as usize).map_err(|e| ())?;
|
||||||
|
|
||||||
|
assert_eq!(msg_bytes.len(), size as usize);
|
||||||
|
|
||||||
|
let net_msg_type =
|
||||||
|
match crate::netmessagetypes::NetmessageType::try_from(msg_type as i32) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match net_msg_type {
|
||||||
|
crate::netmessagetypes::NetmessageType::GE_Source1LegacyGameEventList => {
|
||||||
|
let event_list: crate::csgo_proto::CsvcMsgGameEventList =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
|
||||||
|
|
||||||
|
event_mapper.mapping.clear();
|
||||||
|
for event in event_list.descriptors {
|
||||||
|
event_mapper
|
||||||
|
.mapping
|
||||||
|
.insert(event.eventid(), (event.name().to_owned(), event.keys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::netmessagetypes::NetmessageType::svc_ServerInfo => {
|
||||||
|
let raw: crate::csgo_proto::CsvcMsgServerInfo =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
|
||||||
|
|
||||||
|
events.push_back(DemoEvent::ServerInfo(Box::new(raw)));
|
||||||
|
}
|
||||||
|
crate::netmessagetypes::NetmessageType::net_Tick => {
|
||||||
|
let raw: crate::csgo_proto::CnetMsgTick =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
|
||||||
|
|
||||||
|
events.push_back(DemoEvent::Tick(Box::new(raw)));
|
||||||
|
}
|
||||||
|
crate::netmessagetypes::NetmessageType::GE_Source1LegacyGameEvent => {
|
||||||
|
let raw: crate::csgo_proto::CMsgSource1LegacyGameEvent =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
|
||||||
|
|
||||||
|
match event_mapper.mapping.get(&raw.eventid()) {
|
||||||
|
Some((name, keys)) => {
|
||||||
|
match crate::game_event::EVENT_PARSERS.get(name) {
|
||||||
|
Some(parser) => {
|
||||||
|
let parsed = parser
|
||||||
|
.parse(keys.as_slice(), raw.clone())
|
||||||
|
.map_err(|e| ())?;
|
||||||
|
|
||||||
|
events.push_back(DemoEvent::GameEvent(Box::new(parsed)));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("No parser for {:?}", name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("Unknown Event - ID: {}", raw.eventid());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
crate::netmessagetypes::NetmessageType::CS_UM_ServerRankUpdate => {
|
||||||
|
let raw: crate::csgo_proto::CcsUsrMsgServerRankUpdate =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
|
||||||
|
|
||||||
|
events.push_back(DemoEvent::RankUpdate(Box::new(raw)));
|
||||||
|
}
|
||||||
|
crate::netmessagetypes::NetmessageType::CS_UM_ServerRankRevealAll => {
|
||||||
|
let raw: crate::csgo_proto::CcsUsrMsgServerRankRevealAll =
|
||||||
|
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
|
||||||
|
|
||||||
|
events.push_back(DemoEvent::RankReveal(Box::new(raw)));
|
||||||
|
}
|
||||||
|
crate::netmessagetypes::NetmessageType::net_SignonState
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_ClearAllStringTables
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_CreateStringTable
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_UpdateStringTable
|
||||||
|
| crate::netmessagetypes::NetmessageType::net_SetConVar
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_ClassInfo
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_VoiceInit
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_PacketEntities
|
||||||
|
| crate::netmessagetypes::NetmessageType::svc_UserCmds
|
||||||
|
| crate::netmessagetypes::NetmessageType::GE_SosStartSoundEvent
|
||||||
|
| crate::netmessagetypes::NetmessageType::GE_SosStopSoundEvent
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_GE_PlayerAnimationEvent
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_GE_RadioIconEvent
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_GE_FireBullets
|
||||||
|
| crate::netmessagetypes::NetmessageType::UM_SayText2
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_UM_XpUpdate
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_UM_WeaponSound
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_UM_RadioText
|
||||||
|
| crate::netmessagetypes::NetmessageType::TE_WorldDecal
|
||||||
|
| crate::netmessagetypes::NetmessageType::TE_EffectDispatch
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_UM_EndOfMatchAllPlayersData
|
||||||
|
| crate::netmessagetypes::NetmessageType::TE_PhysicsProp
|
||||||
|
| crate::netmessagetypes::NetmessageType::UM_TextMsg
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_UM_VoteFailed
|
||||||
|
| crate::netmessagetypes::NetmessageType::net_SpawnGroup_Load
|
||||||
|
| crate::netmessagetypes::NetmessageType::CS_UM_MatchEndConditions
|
||||||
|
| crate::netmessagetypes::NetmessageType::TE_Explosion => {}
|
||||||
|
_unknown => {
|
||||||
|
// dbg!(unknown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> Iterator for LazyEventIterator<'b> {
|
||||||
|
type Item = Result<crate::DemoEvent, ()>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
use crate::DemoCommand;
|
||||||
|
|
||||||
|
if let Some(event) = self.pending_events.pop_front() {
|
||||||
|
return Some(Ok(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(frame) = self.frames.next() {
|
||||||
|
match frame.cmd {
|
||||||
|
DemoCommand::SignonPacket | DemoCommand::Packet => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw: crate::csgo_proto::CDemoPacket = match prost::Message::decode(data) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = Self::inner_parse_packet(
|
||||||
|
&raw,
|
||||||
|
&mut self.pending_events,
|
||||||
|
&mut self.event_mapper,
|
||||||
|
) {
|
||||||
|
return Some(Err(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DemoCommand::FullPacket => {
|
||||||
|
let data = match frame.decompress_with_buf(&mut self.buffer) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw: crate::csgo_proto::CDemoFullPacket = match prost::Message::decode(data)
|
||||||
|
{
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return Some(Err(())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
if let Some(packet) = raw.packet {
|
||||||
|
if let Err(e) = Self::inner_parse_packet(
|
||||||
|
&packet,
|
||||||
|
&mut self.pending_events,
|
||||||
|
&mut self.event_mapper,
|
||||||
|
) {
|
||||||
|
return Some(Err(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(event) = self.pending_events.pop_front() {
|
||||||
|
return Some(Ok(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ pub mod game_event;
|
|||||||
mod values;
|
mod values;
|
||||||
pub use values::*;
|
pub use values::*;
|
||||||
|
|
||||||
|
pub mod lazyparser;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
pub mod csgo_proto {
|
pub mod csgo_proto {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::csgo_proto;
|
use crate::csgo_proto;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum DemoEvent {
|
pub enum DemoEvent {
|
||||||
GameEvent(Box<crate::game_event::GameEvent>),
|
GameEvent(Box<crate::game_event::GameEvent>),
|
||||||
ServerInfo(Box<csgo_proto::CsvcMsgServerInfo>),
|
ServerInfo(Box<csgo_proto::CsvcMsgServerInfo>),
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use crate::{packet::DemoEvent, DemoCommand, Frame, FrameDecompressError, UserId}
|
|||||||
mod fieldpath;
|
mod fieldpath;
|
||||||
pub use fieldpath::{FieldPath, Paths};
|
pub use fieldpath::{FieldPath, Paths};
|
||||||
|
|
||||||
mod decoder;
|
pub(crate) mod decoder;
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
mod propcontroller;
|
pub(crate) mod propcontroller;
|
||||||
mod sendtables;
|
pub(crate) mod sendtables;
|
||||||
mod variant;
|
pub(crate) mod variant;
|
||||||
|
|
||||||
pub use entities::EntityFilter;
|
pub use entities::EntityFilter;
|
||||||
pub use variant::Variant;
|
pub use variant::Variant;
|
||||||
@@ -39,7 +39,7 @@ impl From<crate::game_event::ParseGameEventError> for FirstPassError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub xuid: u64,
|
pub xuid: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -95,8 +95,8 @@ pub struct FirstPassOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct GameEventMapping {
|
pub(crate) struct GameEventMapping {
|
||||||
mapping: std::collections::HashMap<
|
pub mapping: std::collections::HashMap<
|
||||||
i32,
|
i32,
|
||||||
(
|
(
|
||||||
String,
|
String,
|
||||||
@@ -107,8 +107,8 @@ struct GameEventMapping {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Class {
|
pub struct Class {
|
||||||
name: std::sync::Arc<str>,
|
pub(crate) name: std::sync::Arc<str>,
|
||||||
serializer: sendtables::Serializer,
|
pub(crate) serializer: sendtables::Serializer,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<'b, FI>(frames: FI, filter: EntityFilter) -> Result<FirstPassOutput, FirstPassError>
|
pub fn parse<'b, FI>(frames: FI, filter: EntityFilter) -> Result<FirstPassOutput, FirstPassError>
|
||||||
@@ -248,7 +248,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_other => {
|
_ => {
|
||||||
// dbg!(other);
|
// dbg!(other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,7 +501,7 @@ fn inner_parse_packet(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity(
|
pub(crate) fn update_entity(
|
||||||
entity_id: i32,
|
entity_id: i32,
|
||||||
bitreader: &mut crate::bitreader::Bitreader,
|
bitreader: &mut crate::bitreader::Bitreader,
|
||||||
entity_ctx: &mut entities::EntityContext,
|
entity_ctx: &mut entities::EntityContext,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub struct EntityContext {
|
|||||||
pub filter: EntityFilter,
|
pub filter: EntityFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct EntityState {
|
pub struct EntityState {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub class: Arc<str>,
|
pub class: Arc<str>,
|
||||||
@@ -16,9 +16,8 @@ pub struct EntityState {
|
|||||||
pub props: Vec<EntityProp>,
|
pub props: Vec<EntityProp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct EntityProp {
|
pub struct EntityProp {
|
||||||
pub field_info: super::sendtables::FieldInfo,
|
|
||||||
pub prop_info: super::propcontroller::PropInfo,
|
pub prop_info: super::propcontroller::PropInfo,
|
||||||
pub value: super::variant::Variant,
|
pub value: super::variant::Variant,
|
||||||
}
|
}
|
||||||
@@ -88,7 +87,6 @@ impl EntityContext {
|
|||||||
if let Some(fi) = field_info {
|
if let Some(fi) = field_info {
|
||||||
if let Some(prop_info) = prop_controller.prop_infos.get(&fi.prop_id) {
|
if let Some(prop_info) = prop_controller.prop_infos.get(&fi.prop_id) {
|
||||||
fields.push(EntityProp {
|
fields.push(EntityProp {
|
||||||
field_info: fi,
|
|
||||||
prop_info: prop_info.clone(),
|
prop_info: prop_info.clone(),
|
||||||
value: result,
|
value: result,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ pub struct PropController {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpecialIDs {}
|
pub struct SpecialIDs {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PropInfo {
|
pub struct PropInfo {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
// pub prop_type: PropType,
|
// pub prop_type: PropType,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub struct Serializer {
|
|||||||
pub fields: Vec<Field>,
|
pub fields: Vec<Field>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct FieldInfo {
|
pub struct FieldInfo {
|
||||||
pub decoder: decoder::Decoder,
|
pub decoder: decoder::Decoder,
|
||||||
pub should_parse: bool,
|
pub should_parse: bool,
|
||||||
@@ -1276,6 +1276,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore = "Need to fix up the values"]
|
||||||
fn parse_ancient_example_sendtables_ccsplayerpawn() {
|
fn parse_ancient_example_sendtables_ccsplayerpawn() {
|
||||||
use decoder::Decoder::*;
|
use decoder::Decoder::*;
|
||||||
use Field::*;
|
use Field::*;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum RawValue {
|
pub enum RawValue {
|
||||||
String(String),
|
String(String),
|
||||||
F32(f32),
|
F32(f32),
|
||||||
|
|||||||
55
tests/lazy.rs
Normal file
55
tests/lazy.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#[test]
|
||||||
|
fn cmp_lazy_nonlazy_events() {
|
||||||
|
let content = std::fs::read("testfiles/mirage.dem").unwrap();
|
||||||
|
|
||||||
|
let container = csdemo::Container::parse(&content).unwrap();
|
||||||
|
let demo = csdemo::parser::parse(
|
||||||
|
csdemo::FrameIterator::parse(container.inner),
|
||||||
|
csdemo::parser::EntityFilter::disabled(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lazy_demo =
|
||||||
|
csdemo::lazyparser::LazyParser::new(csdemo::Container::parse(&content).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(demo.player_info, lazy_demo.player_info());
|
||||||
|
|
||||||
|
for (normal, lazied) in demo
|
||||||
|
.events
|
||||||
|
.into_iter()
|
||||||
|
.zip(lazy_demo.events().filter_map(|e| e.ok()))
|
||||||
|
{
|
||||||
|
assert_eq!(normal, lazied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cmp_lazy_nonlazy_entities() {
|
||||||
|
let content = std::fs::read("testfiles/mirage.dem").unwrap();
|
||||||
|
|
||||||
|
let container = csdemo::Container::parse(&content).unwrap();
|
||||||
|
let demo = csdemo::parser::parse(
|
||||||
|
csdemo::FrameIterator::parse(container.inner),
|
||||||
|
csdemo::parser::EntityFilter::all(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lazy_demo =
|
||||||
|
csdemo::lazyparser::LazyParser::new(csdemo::Container::parse(&content).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(demo.player_info, lazy_demo.player_info());
|
||||||
|
|
||||||
|
let mut normal_iter = demo
|
||||||
|
.entity_states
|
||||||
|
.ticks
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|t| t.states.into_iter().map(move |s| (t.tick, s)));
|
||||||
|
let mut lazy_iter = lazy_demo.entities().filter_map(|e| e.ok());
|
||||||
|
|
||||||
|
while let Some(normal) = normal_iter.next() {
|
||||||
|
let lazy = lazy_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(normal, lazy);
|
||||||
|
}
|
||||||
|
assert_eq!(None, lazy_iter.next());
|
||||||
|
}
|
||||||
@@ -34,8 +34,6 @@ fn mirage_1() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -54,6 +52,4 @@ fn ancient_1() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!("de_ancient", output.header.map_name());
|
assert_eq!("de_ancient", output.header.map_name());
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user