-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Also refactor the code to expose the DemoInfo format written by the parser. The new incomplete CS2 parser is behind an environment variable for now to prevent accidentally adding incomplete data to the HeadhshotBox server.
- Loading branch information
Showing
26 changed files
with
2,441 additions
and
1,067 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "cs2-demo" | ||
version = "0.0.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
bitstream-io.workspace = true | ||
demo-format = { path = "../demo-format" } | ||
protobuf = { version = "3.2.0", features = ["with-bytes"] } | ||
snap = "1.1" | ||
thiserror = "1.0" | ||
tracing = "0.1" | ||
paste = "1.0" | ||
|
||
[build-dependencies] | ||
protobuf-codegen = "3.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use std::{ffi::OsStr, fs}; | ||
|
||
fn main() { | ||
let proto_dir = "proto"; | ||
println!("cargo:rerun-if-changed={proto_dir}"); | ||
|
||
let proto_files = fs::read_dir(proto_dir) | ||
.unwrap() | ||
.filter_map(|res| res.map(|e| e.path()).ok()) | ||
.filter(|p| p.extension() == Some(OsStr::new("proto"))) | ||
.collect::<Vec<_>>(); | ||
protobuf_codegen::Codegen::new() | ||
.include(proto_dir) | ||
.inputs(proto_files) | ||
.cargo_out_dir("proto") | ||
.run_from_script(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
These proto files are copied from [SteamDatabase/GameTracking-CS2](https://github.com/SteamDatabase/GameTracking-CS2/tree/master/Protobufs). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
enum EDemoCommands { | ||
DEM_Error = -1; | ||
DEM_Stop = 0; | ||
DEM_FileHeader = 1; | ||
DEM_FileInfo = 2; | ||
DEM_SyncTick = 3; | ||
DEM_SendTables = 4; | ||
DEM_ClassInfo = 5; | ||
DEM_StringTables = 6; | ||
DEM_Packet = 7; | ||
DEM_SignonPacket = 8; | ||
DEM_ConsoleCmd = 9; | ||
DEM_CustomData = 10; | ||
DEM_CustomDataCallbacks = 11; | ||
DEM_UserCmd = 12; | ||
DEM_FullPacket = 13; | ||
DEM_SaveGame = 14; | ||
DEM_SpawnGroups = 15; | ||
DEM_AnimationData = 16; | ||
DEM_Max = 17; | ||
DEM_IsCompressed = 64; | ||
} | ||
|
||
message CDemoFileHeader { | ||
required string demo_file_stamp = 1; | ||
optional int32 network_protocol = 2; | ||
optional string server_name = 3; | ||
optional string client_name = 4; | ||
optional string map_name = 5; | ||
optional string game_directory = 6; | ||
optional int32 fullpackets_version = 7; | ||
optional bool allow_clientside_entities = 8; | ||
optional bool allow_clientside_particles = 9; | ||
optional string addons = 10; | ||
optional string demo_version_name = 11; | ||
optional string demo_version_guid = 12; | ||
optional int32 build_num = 13; | ||
optional string game = 14; | ||
} | ||
|
||
message CGameInfo { | ||
message CDotaGameInfo { | ||
message CPlayerInfo { | ||
optional string hero_name = 1; | ||
optional string player_name = 2; | ||
optional bool is_fake_client = 3; | ||
optional uint64 steamid = 4; | ||
optional int32 game_team = 5; | ||
} | ||
|
||
message CHeroSelectEvent { | ||
optional bool is_pick = 1; | ||
optional uint32 team = 2; | ||
optional uint32 hero_id = 3; | ||
} | ||
|
||
optional uint64 match_id = 1; | ||
optional int32 game_mode = 2; | ||
optional int32 game_winner = 3; | ||
repeated .CGameInfo.CDotaGameInfo.CPlayerInfo player_info = 4; | ||
optional uint32 leagueid = 5; | ||
repeated .CGameInfo.CDotaGameInfo.CHeroSelectEvent picks_bans = 6; | ||
optional uint32 radiant_team_id = 7; | ||
optional uint32 dire_team_id = 8; | ||
optional string radiant_team_tag = 9; | ||
optional string dire_team_tag = 10; | ||
optional uint32 end_time = 11; | ||
} | ||
|
||
optional .CGameInfo.CDotaGameInfo dota = 4; | ||
} | ||
|
||
message CDemoFileInfo { | ||
optional float playback_time = 1; | ||
optional int32 playback_ticks = 2; | ||
optional int32 playback_frames = 3; | ||
optional .CGameInfo game_info = 4; | ||
} | ||
|
||
message CDemoPacket { | ||
optional bytes data = 3; | ||
} | ||
|
||
message CDemoFullPacket { | ||
optional .CDemoStringTables string_table = 1; | ||
optional .CDemoPacket packet = 2; | ||
} | ||
|
||
message CDemoSaveGame { | ||
optional bytes data = 1; | ||
optional fixed64 steam_id = 2; | ||
optional fixed64 signature = 3; | ||
optional int32 version = 4; | ||
} | ||
|
||
message CDemoSyncTick { | ||
} | ||
|
||
message CDemoConsoleCmd { | ||
optional string cmdstring = 1; | ||
} | ||
|
||
message CDemoSendTables { | ||
optional bytes data = 1; | ||
} | ||
|
||
message CDemoClassInfo { | ||
message class_t { | ||
optional int32 class_id = 1; | ||
optional string network_name = 2; | ||
optional string table_name = 3; | ||
} | ||
|
||
repeated .CDemoClassInfo.class_t classes = 1; | ||
} | ||
|
||
message CDemoCustomData { | ||
optional int32 callback_index = 1; | ||
optional bytes data = 2; | ||
} | ||
|
||
message CDemoCustomDataCallbacks { | ||
repeated string save_id = 1; | ||
} | ||
|
||
message CDemoAnimationData { | ||
optional sint32 entity_id = 1; | ||
optional int32 start_tick = 2; | ||
optional int32 end_tick = 3; | ||
optional bytes data = 4; | ||
optional int64 data_checksum = 5; | ||
} | ||
|
||
message CDemoStringTables { | ||
message items_t { | ||
optional string str = 1; | ||
optional bytes data = 2; | ||
} | ||
|
||
message table_t { | ||
optional string table_name = 1; | ||
repeated .CDemoStringTables.items_t items = 2; | ||
repeated .CDemoStringTables.items_t items_clientside = 3; | ||
optional int32 table_flags = 4; | ||
} | ||
|
||
repeated .CDemoStringTables.table_t tables = 1; | ||
} | ||
|
||
message CDemoStop { | ||
} | ||
|
||
message CDemoUserCmd { | ||
optional int32 cmd_number = 1; | ||
optional bytes data = 2; | ||
} | ||
|
||
message CDemoSpawnGroups { | ||
repeated bytes msgs = 3; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
enum EBaseGameEvents { | ||
GE_VDebugGameSessionIDEvent = 200; | ||
GE_PlaceDecalEvent = 201; | ||
GE_ClearWorldDecalsEvent = 202; | ||
GE_ClearEntityDecalsEvent = 203; | ||
GE_ClearDecalsForSkeletonInstanceEvent = 204; | ||
GE_Source1LegacyGameEventList = 205; | ||
GE_Source1LegacyListenEvents = 206; | ||
GE_Source1LegacyGameEvent = 207; | ||
GE_SosStartSoundEvent = 208; | ||
GE_SosStopSoundEvent = 209; | ||
GE_SosSetSoundEventParams = 210; | ||
GE_SosSetLibraryStackFields = 211; | ||
GE_SosStopSoundEventHash = 212; | ||
} | ||
|
||
message CMsgSource1LegacyGameEventList { | ||
message key_t { | ||
optional int32 type = 1; | ||
optional string name = 2; | ||
} | ||
|
||
message descriptor_t { | ||
optional int32 eventid = 1; | ||
optional string name = 2; | ||
repeated .CMsgSource1LegacyGameEventList.key_t keys = 3; | ||
} | ||
|
||
repeated .CMsgSource1LegacyGameEventList.descriptor_t descriptors = 1; | ||
} | ||
|
||
message CMsgSource1LegacyGameEvent { | ||
message key_t { | ||
optional int32 type = 1; | ||
optional string val_string = 2; | ||
optional float val_float = 3; | ||
optional int32 val_long = 4; | ||
optional int32 val_short = 5; | ||
optional int32 val_byte = 6; | ||
optional bool val_bool = 7; | ||
optional uint64 val_uint64 = 8; | ||
} | ||
|
||
optional string event_name = 1; | ||
optional int32 eventid = 2; | ||
repeated .CMsgSource1LegacyGameEvent.key_t keys = 3; | ||
optional int32 server_tick = 4; | ||
optional int32 passthrough = 5; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use std::fmt; | ||
|
||
use super::packet::Packet; | ||
use super::proto::demo::{CDemoFileHeader, CDemoPacket, CDemoSendTables}; | ||
use super::{Error, Result}; | ||
use protobuf::Message; | ||
|
||
#[derive(Debug)] | ||
#[allow(clippy::large_enum_variant)] | ||
pub enum DemoCommand { | ||
/// The last packet dispatched. It means there are no more packet left to | ||
/// parse. | ||
Stop, | ||
/// The first packet. | ||
FileHeader(CDemoFileHeader), | ||
FileInfo, | ||
/// A sync tick. It contains no data. | ||
SyncTick, | ||
SendTables(CDemoSendTables), | ||
ClassInfo, | ||
StringTables, | ||
Packet(Packet), | ||
ConsoleCmd, | ||
CustomData, | ||
CustomDataCallbacks, | ||
UserCmd, | ||
FullPacket, | ||
SaveGame, | ||
SpawnGroups, | ||
AnimationData, | ||
} | ||
|
||
impl DemoCommand { | ||
pub fn try_new(cmd: u32, data: &[u8]) -> Result<Self> { | ||
let content = match cmd { | ||
0 => DemoCommand::Stop, | ||
1 => DemoCommand::FileHeader(CDemoFileHeader::parse_from_bytes(data)?), | ||
2 => DemoCommand::FileInfo, | ||
3 => DemoCommand::SyncTick, | ||
4 => DemoCommand::SendTables(CDemoSendTables::parse_from_bytes(data)?), | ||
5 => DemoCommand::ClassInfo, | ||
6 => DemoCommand::StringTables, | ||
// SignonPacket seems to be identical to Packet. | ||
7 | 8 => DemoCommand::Packet(Packet::try_new(CDemoPacket::parse_from_bytes(data)?)?), | ||
9 => DemoCommand::ConsoleCmd, | ||
10 => DemoCommand::CustomData, | ||
11 => DemoCommand::CustomDataCallbacks, | ||
12 => DemoCommand::UserCmd, | ||
13 => DemoCommand::FullPacket, | ||
14 => DemoCommand::SaveGame, | ||
15 => DemoCommand::SpawnGroups, | ||
16 => DemoCommand::AnimationData, | ||
_ => return Err(Error::UnknownPacketCommand(cmd)), | ||
}; | ||
Ok(content) | ||
} | ||
} | ||
|
||
impl fmt::Display for DemoCommand { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
match self { | ||
DemoCommand::FileHeader(m) => write!(f, "FileHeader {}", m), | ||
DemoCommand::SendTables(m) => write!(f, "SendTables {} bytes", m.data().len()), | ||
_ => write!(f, "{:?}", self), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
mod demo_command; | ||
pub mod packet; | ||
pub mod proto; | ||
|
||
use self::proto::demo::EDemoCommands; | ||
use protobuf::CodedInputStream; | ||
use std::io; | ||
|
||
pub use self::demo_command::DemoCommand; | ||
|
||
/// 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), | ||
} | ||
|
||
pub type Result<T> = std::result::Result<T, Error>; | ||
|
||
pub type Tick = i32; | ||
|
||
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)?))) | ||
} | ||
} |
Oops, something went wrong.