Skip to content

Commit

Permalink
Parse ServerInfo and StringTables.
Browse files Browse the repository at this point in the history
  • Loading branch information
abenea committed Sep 23, 2023
1 parent 5bb3868 commit 1c0f678
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 97 deletions.
53 changes: 53 additions & 0 deletions cs2-demo/proto/netmessages.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import "networkbasetypes.proto";

enum SVC_Messages {
svc_ServerInfo = 40;
svc_FlattenedSerializer = 41;
svc_ClassInfo = 42;
svc_SetPause = 43;
svc_CreateStringTable = 44;
svc_UpdateStringTable = 45;
svc_VoiceInit = 46;
svc_VoiceData = 47;
svc_Print = 48;
svc_Sounds = 49;
svc_SetView = 50;
svc_ClearAllStringTables = 51;
svc_CmdKeyValues = 52;
svc_BSPDecal = 53;
svc_SplitScreen = 54;
svc_PacketEntities = 55;
svc_Prefetch = 56;
svc_Menu = 57;
svc_GetCvarValue = 58;
svc_StopSound = 59;
svc_PeerList = 60;
svc_PacketReliable = 61;
svc_HLTVStatus = 62;
svc_ServerSteamID = 63;
svc_FullFrameSplit = 70;
svc_RconServerDetails = 71;
svc_UserMessage = 72;
svc_HltvReplay = 73;
svc_Broadcast_Command = 74;
svc_HltvFixupOperatorStatus = 75;
}

message CSVCMsg_ServerInfo {
optional int32 protocol = 1;
optional int32 server_count = 2;
optional bool is_dedicated = 3;
optional bool is_hltv = 4;
optional int32 c_os = 6;
optional int32 max_clients = 10;
optional int32 max_classes = 11;
optional int32 player_slot = 12 [default = -1];
optional float tick_interval = 13;
optional string game_dir = 14;
optional string map_name = 15;
optional string sky_name = 16;
optional string host_name = 17;
optional string addon_name = 18;
optional .CSVCMsg_GameSessionConfiguration game_session_config = 19;
optional bytes game_session_manifest = 20;
}
30 changes: 30 additions & 0 deletions cs2-demo/proto/networkbasetypes.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
message CMsgPlayerInfo {
optional string name = 1;
optional fixed64 xuid = 2;
optional int32 userid = 3;
optional fixed64 steamid = 4;
optional bool fakeplayer = 5;
optional bool ishltv = 6;
}

message CSVCMsg_GameSessionConfiguration {
optional bool is_multiplayer = 1;
optional bool is_loadsavegame = 2;
optional bool is_background_map = 3;
optional bool is_headless = 4;
optional uint32 min_client_limit = 5;
optional uint32 max_client_limit = 6;
optional uint32 max_clients = 7;
optional fixed32 tick_interval = 8;
optional string hostname = 9;
optional string savegamename = 10;
optional string s1_mapname = 11;
optional string gamemode = 12;
optional string server_ip_address = 13;
optional bytes data = 14;
optional bool is_localonly = 15;
optional bool no_steam_server = 19;
optional bool is_transition = 16;
optional string previouslevel = 17;
optional string landmarkname = 18;
}
21 changes: 15 additions & 6 deletions cs2-demo/src/demo_command.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::fmt;

use super::packet::Packet;
use super::proto::demo::{CDemoFileHeader, CDemoPacket, CDemoSendTables};
use super::{Error, Result};
use crate::proto::demo::{CDemoFullPacket, CDemoStringTables};
use crate::string_table::{parse_string_tables, StringTable};
use protobuf::Message;
use std::fmt;

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
Expand All @@ -17,13 +18,13 @@ pub enum DemoCommand {
SyncTick,
SendTables(CDemoSendTables),
ClassInfo,
StringTables,
StringTables(Vec<StringTable>),
Packet(Packet),
ConsoleCmd,
CustomData,
CustomDataCallbacks,
UserCmd,
FullPacket,
FullPacket(Vec<StringTable>, Packet),
SaveGame,
SpawnGroups,
AnimationData,
Expand All @@ -38,14 +39,22 @@ impl DemoCommand {
3 => DemoCommand::SyncTick,
4 => DemoCommand::SendTables(CDemoSendTables::parse_from_bytes(data)?),
5 => DemoCommand::ClassInfo,
6 => DemoCommand::StringTables,
6 => DemoCommand::StringTables(parse_string_tables(
CDemoStringTables::parse_from_bytes(data)?,
)?),
// 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,
13 => {
let mut fp = CDemoFullPacket::parse_from_bytes(data)?;
let string_tables =
parse_string_tables(fp.string_table.take().ok_or(Error::MissingStringTable)?)?;
let packet = Packet::try_new(fp.packet.take().ok_or(Error::MissingPacket)?)?;
DemoCommand::FullPacket(string_tables, packet)
}
14 => DemoCommand::SaveGame,
15 => DemoCommand::SpawnGroups,
16 => DemoCommand::AnimationData,
Expand Down
16 changes: 13 additions & 3 deletions cs2-demo/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
mod demo_command;
pub mod packet;
mod message;
mod packet;
pub mod proto;
mod string_table;

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

pub use self::demo_command::DemoCommand;
pub use crate::demo_command::DemoCommand;
pub use crate::message::Message;
pub use crate::string_table::{PlayerInfo, StringTable, UserInfo};

/// Error type for this library.
#[derive(thiserror::Error, Debug)]
Expand All @@ -22,6 +26,12 @@ pub enum Error {
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,
}

pub type Result<T> = std::result::Result<T, Error>;
Expand Down
82 changes: 82 additions & 0 deletions cs2-demo/src/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::proto::gameevents::*;
use crate::proto::netmessages::*;
use crate::Result;
use bitstream_io::BitRead;
use demo_format::read::ValveBitReader;
use paste::paste;
use protobuf::Message as protobuf_Message;
use std::fmt;
use std::io::{Read, Seek, SeekFrom};

/// Generates an enum with a variant for each supported Packet message type.
///
/// $enum is a proto enum listing the Packet message identifiers for a category of messages
/// $enum_prefix is the prefix for all the items in $enum
/// $msg_prefix is the prefix for proto message type names
/// $name is the proto message type name without $msg_prefix
macro_rules! create_message_impl {
($(
($enum:ident, $enum_prefix:ident, $msg_prefix:ident)
=> [ $($name:ident),* ]
),*) => {paste! {
pub enum Message {
Unknown(u32),
$($($name([<$msg_prefix $name>])),*),*
}

impl Message {
pub(crate) fn try_new<R: Read + Seek>(
reader: &mut bitstream_io::BitReader<R, bitstream_io::LittleEndian>,
mut buffer: &mut Vec<u8>,
) -> Result<Message> {
$($(const [<$name:upper>]: u32 = $enum::[<$enum_prefix $name>] as u32;)*)*
let msg_type = reader.read_ubitvar()?;
let size = reader.read_varint32()? as usize;
match msg_type {
$($(
[<$name:upper>] => {
let msg = [<$msg_prefix $name>]::parse_from_bytes(read_buffer(
&mut buffer, size, reader)?)?;
Ok(Message::$name(msg))
}
)*)*
_ => {
reader.seek_bits(SeekFrom::Current(size as i64 * 8))?;
Ok(Message::Unknown(msg_type))
}
}
}
}

impl fmt::Debug for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Message::Unknown(t) => write!(f, "Unknown({t})"),
$($(Message::$name(m) => write!(f, "{}({})", stringify!($name), m),)*)*
}
}
}
}};
}

create_message_impl! {
(SVC_Messages, svc_, CSVCMsg_) => [
ServerInfo
],
(EBaseGameEvents, GE_, CMsg) => [
Source1LegacyGameEvent,
Source1LegacyGameEventList
]
}

fn read_buffer<'a, R: Read + Seek>(
buffer: &'a mut Vec<u8>,
size: usize,
reader: &mut bitstream_io::BitReader<R, bitstream_io::LittleEndian>,
) -> Result<&'a [u8]> {
if buffer.len() < size {
buffer.resize(size, 0);
}
reader.read_bytes(&mut buffer[0..size])?;
Ok(&buffer[0..size])
}
84 changes: 4 additions & 80 deletions cs2-demo/src/packet.rs
Original file line number Diff line number Diff line change
@@ -1,72 +1,8 @@
use super::proto::demo::CDemoPacket;
use super::proto::gameevents::{
CMsgSource1LegacyGameEvent, CMsgSource1LegacyGameEventList, EBaseGameEvents,
};
use super::Result;
use bitstream_io::BitRead;
use demo_format::read::ValveBitReader;
use paste::paste;
use protobuf::Message as protobuf_Message;
use crate::message::Message;
use crate::proto::demo::CDemoPacket;
use crate::Result;
use std::fmt;
use std::io::{Cursor, Read, Seek, SeekFrom};

/// Generates an enum with a variant for each supported Packet message type.
///
/// $enum is a proto enum listing the Packet message identifiers for a category of messages
/// $enum_prefix is the prefix for all the items in $enum
/// $msg_prefix is the prefix for proto message type names
/// $name is the proto message type name without $msg_prefix
macro_rules! create_message_impl {
($(
($enum:ident, $enum_prefix:ident, $msg_prefix:ident)
=> [ $($name:ident),* ]
),*) => {paste! {
pub enum Message {
Unknown(u32),
$($($name([<$msg_prefix $name>])),*),*
}

impl Message {
pub(crate) fn try_new<R: Read + Seek>(
reader: &mut bitstream_io::BitReader<R, bitstream_io::LittleEndian>,
mut buffer: &mut Vec<u8>,
) -> Result<Message> {
$($(const [<$name:upper>]: u32 = $enum::[<$enum_prefix _ $name>] as u32;)*)*
let msg_type = reader.read_ubitvar()?;
let size = reader.read_varint32()? as usize;
match msg_type {
$($(
[<$name:upper>] => {
let msg = [<$msg_prefix $name>]::parse_from_bytes(read_buffer(
&mut buffer, size, reader)?)?;
Ok(Message::$name(msg))
}
)*)*
_ => {
reader.seek_bits(SeekFrom::Current(size as i64 * 8))?;
Ok(Message::Unknown(msg_type))
}
}
}
}

impl fmt::Debug for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Message::Unknown(t) => write!(f, "Unknown({})", t),
$($(Message::$name(m) => write!(f, "{}({})", stringify!($name), m),)*)*
}
}
}
}};
}

create_message_impl! {
(EBaseGameEvents, GE, CMsg) => [
Source1LegacyGameEvent,
Source1LegacyGameEventList
]
}
use std::io::Cursor;

pub struct Packet {
pub messages: Vec<Message>,
Expand Down Expand Up @@ -97,15 +33,3 @@ impl fmt::Debug for Packet {
}
}
}

fn read_buffer<'a, R: Read + Seek>(
buffer: &'a mut Vec<u8>,
size: usize,
reader: &mut bitstream_io::BitReader<R, bitstream_io::LittleEndian>,
) -> Result<&'a [u8]> {
if buffer.len() < size {
buffer.resize(size, 0);
}
reader.read_bytes(&mut buffer[0..size])?;
Ok(&buffer[0..size])
}
Loading

0 comments on commit 1c0f678

Please sign in to comment.