Skip to content

Commit

Permalink
Use visitor pattern for cs2-demo.
Browse files Browse the repository at this point in the history
  • Loading branch information
abenea committed Jan 29, 2024
1 parent 9694b8f commit e44b38d
Show file tree
Hide file tree
Showing 14 changed files with 436 additions and 276 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cs2-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0"
bitstream-io.workspace = true
demo-format = { path = "../demo-format" }
protobuf = { version = "3.2.0", features = ["with-bytes"] }
serde = { version = "1.0" }
snap = "1.1"
thiserror = "1.0"
tracing = "0.1"
Expand Down
45 changes: 40 additions & 5 deletions cs2-demo/src/demo_command.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use super::packet::Packet;
use super::proto::demo::{CDemoFileHeader, CDemoPacket, CDemoSendTables};
use super::{Error, Result};
use crate::proto::demo::{CDemoClassInfo, CDemoFullPacket, CDemoStringTables};
use crate::string_table::{parse_string_tables, StringTable};
use demo_format::Tick;
use protobuf::CodedInputStream;
use protobuf::Message;
use std::fmt;

use crate::packet::Packet;
use crate::proto::demo::{
CDemoClassInfo, CDemoFileHeader, CDemoFullPacket, CDemoPacket, CDemoSendTables,
CDemoStringTables, EDemoCommands,
};
use crate::string_table::{parse_string_tables, StringTable};
use crate::{Error, Result};

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum DemoCommand {
Expand Down Expand Up @@ -73,3 +78,33 @@ impl fmt::Display for DemoCommand {
}
}
}

pub struct DemoParser<'a> {
reader: CodedInputStream<'a>,
}

impl<'a> DemoParser<'a> {
pub fn try_new_after_demo_type(read: &'a mut dyn std::io::Read) -> Result<Self> {
let mut reader = CodedInputStream::new(read);
reader.skip_raw_bytes(8)?;
Ok(Self { reader })
}

pub fn parse_next_demo_command(&mut self) -> Result<Option<(Tick, DemoCommand)>> {
if self.reader.eof()? {
return Ok(None);
}
let cmd_flags = self.reader.read_raw_varint32()?;
let cmd = cmd_flags & !(EDemoCommands::DEM_IsCompressed as u32);
let compressed = (cmd_flags & (EDemoCommands::DEM_IsCompressed as u32)) != 0;
let tick = self.reader.read_raw_varint32()? as i32;
let size = self.reader.read_raw_varint32()?;
let data = self.reader.read_raw_bytes(size)?;
let data = if compressed {
snap::raw::Decoder::new().decompress_vec(data.as_slice())?
} else {
data
};
Ok(Some((tick, DemoCommand::try_new(cmd, &data)?)))
}
}
6 changes: 2 additions & 4 deletions cs2-demo/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct Entities {
}

impl Entities {
pub fn read_packet_entities(
pub(crate) fn read_packet_entities(
&mut self,
msg: CSVCMsg_PacketEntities,
classes: &Classes,
Expand Down Expand Up @@ -133,15 +133,13 @@ impl Entity {

#[cfg(test)]
mod tests {
use std::rc::Rc;

use super::*;
use crate::testdata;

#[test]
fn test() -> Result<()> {
let send_tables = SendTables::try_new(testdata::send_tables())?;
let classes = Classes::try_new(testdata::class_info(), Rc::new(send_tables))?;
let classes = Classes::try_new(testdata::class_info(), send_tables)?;
let mut entities = Entities::default();
entities.read_packet_entities(testdata::packet_entities(), &classes)?;
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions cs2-demo/src/entity/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct Classes {
}

impl Classes {
pub fn try_new(msg: CDemoClassInfo, send_tables: Rc<SendTables>) -> Result<Self> {
pub fn try_new(msg: CDemoClassInfo, send_tables: SendTables) -> Result<Self> {
let serializers = send_tables
.serializers
.iter()
Expand Down Expand Up @@ -71,6 +71,6 @@ mod tests {
#[test]
fn test() {
let send_tables = SendTables::try_new(testdata::send_tables()).unwrap();
Classes::try_new(testdata::class_info(), Rc::new(send_tables)).unwrap();
Classes::try_new(testdata::class_info(), send_tables).unwrap();
}
}
36 changes: 36 additions & 0 deletions cs2-demo/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// Error type for this library.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Protobuf(#[from] protobuf::Error),
#[error("invalid demo type (expected: PBDEMS2, found: {found})")]
InvalidDemoType { found: String },
#[error("unknown packet command found: {0}")]
UnknownPacketCommand(u32),
#[error(transparent)]
Decompression(#[from] snap::Error),
#[error("missing string_table from CDemoFullPacket")]
MissingStringTable,
#[error("missing packet from CDemoFullPacket")]
MissingPacket,
#[error("cannot parse string table player index")]
InvalidPlayerIndex,
#[error("cannot parse sendtables")]
InvalidSendTables,
#[error("invalid entity id in PacketEntities")]
InvalidEntityId,
#[error("missing class_id in CDemoClassInfo")]
MissingClassId,
#[error("missing class name CDemoClassInfo")]
MissingClassName,
#[error("skipped class_id in CDemoClassInfo")]
SkippedClassId,
#[error("duplicate serializer in CDemoSendTables")]
DuplicateSerializer,
#[error("packet out of order")]
PacketOutOfOrder,
}

pub type Result<T> = std::result::Result<T, Error>;
76 changes: 76 additions & 0 deletions cs2-demo/src/game_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
pub mod de;

use std::collections::HashMap;

use crate::proto::gameevents::{
cmsg_source1legacy_game_event_list, CMsgSource1LegacyGameEventList,
};

pub type GameEventDescriptors = HashMap<i32, Descriptor>;

pub struct DescriptorKey {
pub type_: i32,
pub name: String,
}

impl From<cmsg_source1legacy_game_event_list::Key_t> for DescriptorKey {
fn from(k: cmsg_source1legacy_game_event_list::Key_t) -> Self {
Self {
name: k.name().to_string(),
type_: k.type_(),
}
}
}

pub struct Descriptor {
pub eventid: i32,
pub name: String,
pub keys: Vec<DescriptorKey>,
}

impl From<cmsg_source1legacy_game_event_list::Descriptor_t> for Descriptor {
fn from(d: cmsg_source1legacy_game_event_list::Descriptor_t) -> Self {
Descriptor {
eventid: d.eventid(),
name: d.name().to_string(),
keys: d.keys.into_iter().map(DescriptorKey::from).collect(),
}
}
}

impl std::fmt::Display for Descriptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "struct {} {{", self.name)?;
for key in &self.keys {
match key.type_ {
1 => writeln!(f, " {}: String,", key.name)?,
2 => writeln!(f, " {}: f32,", key.name)?,
3 => writeln!(f, " {}: i32, // long", key.name)?,
4 => writeln!(f, " {}: i32, // short", key.name)?,
5 => writeln!(f, " {}: i32, // byte", key.name)?,
6 => writeln!(f, " {}: bool,", key.name)?,
7 => writeln!(f, " {}: u64,", key.name)?,
8 => writeln!(f, " {}: i32, // long, strict_ehandle", key.name)?,
9 => writeln!(f, " {}: i32, // short, playercontroller", key.name)?,
t => writeln!(f, " {}: <unknown_{t}>", key.name)?,
};
}
writeln!(f, "}}")
}
}

#[allow(dead_code)]
pub(crate) fn dump_descriptors(descriptors: HashMap<i32, Descriptor>) {
let mut sorted: Vec<_> = descriptors.values().collect();
sorted.sort_by_key(|d| &d.name);
for d in sorted {
println!("{d}");
}
}

pub(crate) fn parse_game_event_list(gel: CMsgSource1LegacyGameEventList) -> GameEventDescriptors {
gel.descriptors
.into_iter()
.map(|d| (d.eventid(), Descriptor::from(d)))
.collect()
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::Descriptor;
use cs2_demo::proto::gameevents::{cmsg_source1legacy_game_event, CMsgSource1LegacyGameEvent};
use crate::proto::gameevents::{cmsg_source1legacy_game_event, CMsgSource1LegacyGameEvent};
use serde::{de, forward_to_deserialize_any};
use std::fmt::Display;

pub(crate) type Result<T> = std::result::Result<T, Error>;
pub type Result<T> = std::result::Result<T, Error>;

#[derive(thiserror::Error, Debug)]
pub(crate) enum Error {
pub enum Error {
#[error("{0}")]
Message(String),
#[error("event {event}.{key} expected type {descriptor_type} but got type {event_type}")]
Expand All @@ -25,7 +25,7 @@ pub(crate) enum Error {
},
}

pub(crate) fn from_cs2_event<'a, T: serde::Deserialize<'a>>(
pub fn from_proto<'a, T: serde::Deserialize<'a>>(
event: CMsgSource1LegacyGameEvent,
descriptor: &'a Descriptor,
) -> Result<T> {
Expand Down
78 changes: 6 additions & 72 deletions cs2-demo/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,85 +1,19 @@
mod demo_command;
pub mod entity;
mod error;
pub mod game_event;
mod message;
mod packet;
pub mod proto;
mod string_table;
#[cfg(test)]
mod testdata;
mod visit;

use crate::proto::demo::EDemoCommands;
use demo_format::Tick;
use protobuf::CodedInputStream;
use std::io;

pub use crate::demo_command::DemoCommand;
pub use crate::message::Message;
pub use crate::error::{Error, Result};
pub use crate::game_event::GameEventDescriptors;
pub use crate::string_table::{PlayerInfo, StringTable, UserInfo};

/// Error type for this library.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Protobuf(#[from] protobuf::Error),
#[error("invalid demo type (expected: PBDEMS2, found: {found})")]
InvalidDemoType { found: String },
#[error("unknown packet command found: {0}")]
UnknownPacketCommand(u32),
#[error(transparent)]
Decompression(#[from] snap::Error),
#[error("missing string_table from CDemoFullPacket")]
MissingStringTable,
#[error("missing packet from CDemoFullPacket")]
MissingPacket,
#[error("cannot parse string table player index")]
InvalidPlayerIndex,
#[error("cannot parse sendtables")]
InvalidSendTables,
#[error("invalid entity id in PacketEntities")]
InvalidEntityId,
#[error("missing class_id in CDemoClassInfo")]
MissingClassId,
#[error("missing class name CDemoClassInfo")]
MissingClassName,
#[error("skipped class_id in CDemoClassInfo")]
SkippedClassId,
#[error("duplicate serializer in CDemoSendTables")]
DuplicateSerializer,
}

pub type Result<T> = std::result::Result<T, Error>;

pub struct DemoParser<'a> {
reader: CodedInputStream<'a>,
}

impl<'a> DemoParser<'a> {
pub fn try_new_after_demo_type(read: &'a mut dyn io::Read) -> Result<Self> {
let mut reader = CodedInputStream::new(read);
reader.skip_raw_bytes(8)?;
Ok(Self { reader })
}

pub fn parse_next_demo_command(&mut self) -> Result<Option<(Tick, DemoCommand)>> {
if self.reader.eof()? {
return Ok(None);
}
let cmd_flags = self.reader.read_raw_varint32()?;
let cmd = cmd_flags & !(EDemoCommands::DEM_IsCompressed as u32);
let compressed = (cmd_flags & (EDemoCommands::DEM_IsCompressed as u32)) != 0;
let tick = self.reader.read_raw_varint32()? as i32;
let size = self.reader.read_raw_varint32()?;
let data = self.reader.read_raw_bytes(size)?;
let data = if compressed {
snap::raw::Decoder::new().decompress_vec(data.as_slice())?
} else {
data
};
Ok(Some((tick, DemoCommand::try_new(cmd, &data)?)))
}
}
pub use crate::visit::{parse_after_demo_type, Visitor};

#[allow(dead_code)]
pub(crate) fn dump<M>(msg: &M, file: &str)
Expand Down
Loading

0 comments on commit e44b38d

Please sign in to comment.