Add some more types and improve typing for player events

This commit is contained in:
Lol3rrr
2024-09-15 16:13:40 +02:00
parent c479fb5b69
commit cfff05a82f
5 changed files with 291 additions and 98 deletions

View File

@@ -1,60 +1,4 @@
use crate::csgo_proto; use crate::{csgo_proto, RawValue, UserId};
#[derive(Debug)]
pub enum RawValue {
String(String),
F32(f32),
I32(i32),
Bool(bool),
U64(u64),
}
impl TryFrom<crate::csgo_proto::c_msg_source1_legacy_game_event::KeyT> for RawValue {
type Error = ();
fn try_from(value: crate::csgo_proto::c_msg_source1_legacy_game_event::KeyT) -> Result<Self, Self::Error> {
match value.r#type() {
1 if value.val_string.is_some() => Ok(Self::String(value.val_string.unwrap())),
2 if value.val_float.is_some() => Ok(Self::F32(value.val_float.unwrap())),
3 if value.val_long.is_some() => Ok(Self::I32(value.val_long.unwrap())),
4 if value.val_short.is_some() => Ok(Self::I32(value.val_short.unwrap() as i32)),
5 if value.val_byte.is_some() => Ok(Self::I32(value.val_byte.unwrap() as i32)),
6 if value.val_bool.is_some() => Ok(Self::Bool(value.val_bool.unwrap())),
7 if value.val_uint64.is_some() => Ok(Self::U64(value.val_uint64.unwrap())),
8 if value.val_long.is_some() => Ok(Self::I32(value.val_long.unwrap())),
9 if value.val_short.is_some() => Ok(Self::I32(value.val_short.unwrap() as i32)),
_ => Err(()),
}
}
}
impl TryFrom<RawValue> for i32 {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::I32(v) => Ok(v),
_ => Err(()),
}
}
}
impl TryFrom<RawValue> for bool {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::Bool(v) => Ok(v),
_ => Err(()),
}
}
}
impl TryFrom<RawValue> for String {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::String(v) => Ok(v),
_ => Err(()),
}
}
}
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))*) => {
@@ -87,12 +31,48 @@ macro_rules! define_event {
define_event!(HltvVersionInfo, GameEvent::HltvVersionInfo); define_event!(HltvVersionInfo, GameEvent::HltvVersionInfo);
define_event!(ItemEquip, GameEvent::ItemEquip, (userid, i32), (hassilencer, bool), (hastracers, bool), (item, String), (issilenced, bool), (canzoom, bool), (ispainted, bool), (weptype, i32), (defindex, i32)); define_event!(
define_event!(ItemPickup, GameEvent::ItemPickup, (userid, RawValue), (item, RawValue), (silent, RawValue), (defindex, RawValue)); ItemEquip,
GameEvent::ItemEquip,
(userid, UserId),
(hassilencer, bool),
(hastracers, bool),
(item, String),
(issilenced, bool),
(canzoom, bool),
(ispainted, bool),
(weptype, i32),
(defindex, i32)
);
define_event!(
ItemPickup,
GameEvent::ItemPickup,
(userid, UserId),
(item, RawValue),
(silent, RawValue),
(defindex, RawValue)
);
define_event!(WeaponReload, GameEvent::WeaponReload, (userid, RawValue), (userid_pawn, RawValue)); define_event!(
define_event!(WeaponZoom, GameEvent::WeaponZoom, (userid, RawValue), (userid_pawn, RawValue)); WeaponReload,
define_event!(WeaponFire, GameEvent::WeaponFire, (userid, RawValue), (weapon, RawValue), (silenced, RawValue), (userid_pawn, RawValue)); GameEvent::WeaponReload,
(userid, UserId),
(userid_pawn, RawValue)
);
define_event!(
WeaponZoom,
GameEvent::WeaponZoom,
(userid, UserId),
(userid_pawn, RawValue)
);
define_event!(
WeaponFire,
GameEvent::WeaponFire,
(userid, UserId),
(weapon, RawValue),
(silenced, RawValue),
(userid_pawn, RawValue)
);
define_event!(SmokeGrenadeDetonate, GameEvent::SmokeGrenadeDetonate); define_event!(SmokeGrenadeDetonate, GameEvent::SmokeGrenadeDetonate);
define_event!(SmokeGrenadeExpired, GameEvent::SmokeGrenadeExpired); define_event!(SmokeGrenadeExpired, GameEvent::SmokeGrenadeExpired);
@@ -103,16 +83,106 @@ define_event!(FlashbangDetonate, GameEvent::FlashbangDetonate);
define_event!(DecoyStarted, GameEvent::DecoyStarted); define_event!(DecoyStarted, GameEvent::DecoyStarted);
define_event!(DecoyDetonate, GameEvent::DecoyDetonate); define_event!(DecoyDetonate, GameEvent::DecoyDetonate);
define_event!(PlayerConnect, GameEvent::PlayerConnect, (address, RawValue), (bot, RawValue), (name, RawValue), (userid, RawValue), (networkid, RawValue), (xuid, RawValue)); define_event!(
define_event!(PlayerConnectFull, GameEvent::PlayerConnectFull, (userid, RawValue)); PlayerConnect,
define_event!(PlayerDisconnect, GameEvent::PlayerDisconnect, (userid, RawValue), (reason, RawValue), (name, RawValue), (networkid, RawValue), (xuid, RawValue)); GameEvent::PlayerConnect,
define_event!(PlayerFootstep, GameEvent::PlayerFootstep); (address, RawValue),
define_event!(PlayerJump, GameEvent::PlayerJump); (bot, RawValue),
define_event!(PlayerHurt, GameEvent::PlayerHurt); (name, RawValue),
define_event!(PlayerDeath, GameEvent::PlayerDeath); (userid, i32),
define_event!(PlayerSpawn, GameEvent::PlayerSpawn); (networkid, RawValue),
define_event!(PlayerBlind, GameEvent::PlayerBlind); (xuid, RawValue)
define_event!(PlayerTeam, GameEvent::PlayerTeam, (userid, RawValue), (team, RawValue), (oldteam, RawValue), (disconnect, RawValue), (silent, RawValue), (isbot, RawValue), (userid_pawn, RawValue)); );
define_event!(
PlayerConnectFull,
GameEvent::PlayerConnectFull,
(userid, UserId)
);
define_event!(
PlayerDisconnect,
GameEvent::PlayerDisconnect,
(userid, UserId),
(reason, RawValue),
(name, RawValue),
(networkid, RawValue),
(xuid, RawValue)
);
define_event!(
PlayerFootstep,
GameEvent::PlayerFootstep,
(userid, UserId),
(userid_pawn, RawValue)
);
define_event!(PlayerJump, GameEvent::PlayerJump, (userid, i32));
define_event!(
PlayerHurt,
GameEvent::PlayerHurt,
(userid, UserId),
(attacker, UserId),
(health, RawValue),
(armor, RawValue),
(weapon, String),
(dmg_health, RawValue),
(dmg_armor, RawValue),
(hitgroup, RawValue),
(userid_pawn, RawValue),
(attacker_pawn, RawValue)
);
define_event!(
PlayerDeath,
GameEvent::PlayerDeath,
(userid, UserId),
(attacker, UserId),
(assister, UserId),
(assistedflash, bool),
(weapon, String),
(weapon_itemid, String),
(weapon_fauxitemid, String),
(weapon_originalowner_xuid, String),
(headshot, bool),
(dominated, RawValue),
(revenge, RawValue),
(wipe, RawValue),
(penetrated, RawValue),
(noreplay, bool),
(noscope, bool),
(thrusmoke, bool),
(attackerblind, bool),
(distance, RawValue),
(userid_pawn, RawValue),
(attacker_pawn, RawValue),
(assister_pawn, RawValue),
(dmg_health, RawValue),
(dmg_armor, RawValue),
(hitgroup, RawValue),
(attackerinair, bool)
);
define_event!(
PlayerSpawn,
GameEvent::PlayerSpawn,
(userid, UserId),
(inrestart, RawValue),
(userid_pawn, RawValue)
);
define_event!(
PlayerBlind,
GameEvent::PlayerBlind,
(userid, UserId),
(attacker, RawValue),
(entityid, RawValue),
(blind_duration, RawValue)
);
define_event!(
PlayerTeam,
GameEvent::PlayerTeam,
(userid, UserId),
(team, RawValue),
(oldteam, RawValue),
(disconnect, RawValue),
(silent, RawValue),
(isbot, RawValue),
(userid_pawn, RawValue)
);
define_event!(BulletDamage, GameEvent::BulletDamage); define_event!(BulletDamage, GameEvent::BulletDamage);
@@ -138,12 +208,18 @@ define_event!(RoundPreRestart, GameEvent::RoundPreRestart);
define_event!(RoundTimeWarning, GameEvent::RoundTimeWarning); define_event!(RoundTimeWarning, GameEvent::RoundTimeWarning);
define_event!(RoundFinalBeep, GameEvent::RoundFinalBeep); define_event!(RoundFinalBeep, GameEvent::RoundFinalBeep);
define_event!(BuyTimeEnded, GameEvent::BuyTimeEnded); define_event!(BuyTimeEnded, GameEvent::BuyTimeEnded);
define_event!(RoundAnnounceLastRoundHalf, GameEvent::RoundAnnounceLastRoundHalf); define_event!(
RoundAnnounceLastRoundHalf,
GameEvent::RoundAnnounceLastRoundHalf
);
define_event!(AnnouncePhaseEnd, GameEvent::AnnouncePhaseEnd); define_event!(AnnouncePhaseEnd, GameEvent::AnnouncePhaseEnd);
define_event!(WinPanelMatch, GameEvent::WinPanelMatch); define_event!(WinPanelMatch, GameEvent::WinPanelMatch);
type ParseFn = fn(keys: &[csgo_proto::csvc_msg_game_event_list::KeyT], event: csgo_proto::CMsgSource1LegacyGameEvent) -> Result<GameEvent, ParseGameEventError>; type ParseFn = fn(
keys: &[csgo_proto::csvc_msg_game_event_list::KeyT],
event: csgo_proto::CMsgSource1LegacyGameEvent,
) -> Result<GameEvent, ParseGameEventError>;
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@@ -273,17 +349,22 @@ pub static EVENT_PARSERS: phf::Map<&'static str, GameEventParser> = phf::phf_map
}; };
pub struct GameEventParser { pub struct GameEventParser {
inner: fn(keys: &[csgo_proto::csvc_msg_game_event_list::KeyT], event: csgo_proto::CMsgSource1LegacyGameEvent) -> Result<GameEvent, ParseGameEventError>, inner: fn(
keys: &[csgo_proto::csvc_msg_game_event_list::KeyT],
event: csgo_proto::CMsgSource1LegacyGameEvent,
) -> Result<GameEvent, ParseGameEventError>,
} }
impl GameEventParser { impl GameEventParser {
pub const fn new(func: ParseFn) -> Self { pub const fn new(func: ParseFn) -> Self {
Self { Self { inner: func }
inner: func,
}
} }
pub fn parse(&self, keys: &[csgo_proto::csvc_msg_game_event_list::KeyT], event: csgo_proto::CMsgSource1LegacyGameEvent) -> Result<GameEvent, ParseGameEventError> { pub fn parse(
&self,
keys: &[csgo_proto::csvc_msg_game_event_list::KeyT],
event: csgo_proto::CMsgSource1LegacyGameEvent,
) -> Result<GameEvent, ParseGameEventError> {
if keys.len() != event.keys.len() { if keys.len() != event.keys.len() {
return Err(ParseGameEventError::MismatchedKeysFields); return Err(ParseGameEventError::MismatchedKeysFields);
} }

View File

@@ -16,6 +16,9 @@ mod packet;
pub use packet::DemoEvent; pub use packet::DemoEvent;
pub mod game_event; pub mod game_event;
mod values;
pub use values::*;
pub mod parser; pub mod parser;
pub mod csgo_proto { pub mod csgo_proto {

View File

@@ -1,4 +1,4 @@
use crate::{packet::DemoEvent, DemoCommand, Frame}; use crate::{packet::DemoEvent, DemoCommand, Frame, UserId};
#[derive(Debug)] #[derive(Debug)]
pub enum FirstPassError { pub enum FirstPassError {
@@ -8,7 +8,7 @@ pub enum FirstPassError {
MissingFileHeader, MissingFileHeader,
MissingFileInfo, MissingFileInfo,
Bitreader(crate::bitreader::BitReadError), Bitreader(crate::bitreader::BitReadError),
ParseGameEventError(crate::game_event::ParseGameEventError) ParseGameEventError(crate::game_event::ParseGameEventError),
} }
impl From<prost::DecodeError> for FirstPassError { impl From<prost::DecodeError> for FirstPassError {
@@ -38,12 +38,18 @@ pub struct FirstPassOutput {
pub header: crate::csgo_proto::CDemoFileHeader, pub header: crate::csgo_proto::CDemoFileHeader,
pub info: crate::csgo_proto::CDemoFileInfo, pub info: crate::csgo_proto::CDemoFileInfo,
pub events: Vec<DemoEvent>, pub events: Vec<DemoEvent>,
pub player_info: std::collections::HashMap<i32, Player>, pub player_info: std::collections::HashMap<UserId, Player>,
} }
#[derive(Debug)] #[derive(Debug)]
struct GameEventMapping { struct GameEventMapping {
mapping: std::collections::HashMap<i32, (String, Vec<crate::csgo_proto::csvc_msg_game_event_list::KeyT>)>, mapping: std::collections::HashMap<
i32,
(
String,
Vec<crate::csgo_proto::csvc_msg_game_event_list::KeyT>,
),
>,
} }
pub fn parse<'b, FI>(frames: FI) -> Result<FirstPassOutput, FirstPassError> pub fn parse<'b, FI>(frames: FI) -> Result<FirstPassOutput, FirstPassError>
@@ -87,14 +93,19 @@ where
let header = header.ok_or(FirstPassError::MissingFileHeader)?; let header = header.ok_or(FirstPassError::MissingFileHeader)?;
let info = file_info.ok_or(FirstPassError::MissingFileInfo)?; let info = file_info.ok_or(FirstPassError::MissingFileInfo)?;
Ok(FirstPassOutput { header, info, events, player_info }) Ok(FirstPassOutput {
header,
info,
events,
player_info,
})
} }
fn parse_fullpacket( fn parse_fullpacket(
data: &[u8], data: &[u8],
events: &mut Vec<DemoEvent>, events: &mut Vec<DemoEvent>,
event_mapper: &mut GameEventMapping, event_mapper: &mut GameEventMapping,
player_info: &mut std::collections::HashMap<i32, Player>, player_info: &mut std::collections::HashMap<UserId, Player>,
) -> Result<(), FirstPassError> { ) -> Result<(), FirstPassError> {
let raw: crate::csgo_proto::CDemoFullPacket = prost::Message::decode(data)?; let raw: crate::csgo_proto::CDemoFullPacket = prost::Message::decode(data)?;
@@ -115,7 +126,7 @@ fn parse_packet(
data: &[u8], data: &[u8],
events: &mut Vec<DemoEvent>, events: &mut Vec<DemoEvent>,
event_mapper: &mut GameEventMapping, event_mapper: &mut GameEventMapping,
player_info: &mut std::collections::HashMap<i32, Player>, player_info: &mut std::collections::HashMap<UserId, Player>,
) -> Result<(), FirstPassError> { ) -> Result<(), FirstPassError> {
let raw: crate::csgo_proto::CDemoPacket = prost::Message::decode(data)?; let raw: crate::csgo_proto::CDemoPacket = prost::Message::decode(data)?;
@@ -128,7 +139,7 @@ fn inner_parse_packet(
raw: &crate::csgo_proto::CDemoPacket, raw: &crate::csgo_proto::CDemoPacket,
events: &mut Vec<DemoEvent>, events: &mut Vec<DemoEvent>,
event_mapper: &mut GameEventMapping, event_mapper: &mut GameEventMapping,
player_info: &mut std::collections::HashMap<i32, Player>, player_info: &mut std::collections::HashMap<UserId, Player>,
) -> Result<(), FirstPassError> { ) -> Result<(), FirstPassError> {
let mut bitreader = crate::bitreader::Bitreader::new(raw.data()); let mut bitreader = crate::bitreader::Bitreader::new(raw.data());
@@ -167,7 +178,8 @@ fn inner_parse_packet(
events.push(DemoEvent::ServerInfo(raw)); events.push(DemoEvent::ServerInfo(raw));
} }
crate::netmessagetypes::NetmessageType::net_SignonState => { crate::netmessagetypes::NetmessageType::net_SignonState => {
let raw: crate::csgo_proto::CnetMsgSignonState = prost::Message::decode(msg_bytes.as_slice())?; let raw: crate::csgo_proto::CnetMsgSignonState =
prost::Message::decode(msg_bytes.as_slice())?;
dbg!(raw); dbg!(raw);
} }
crate::netmessagetypes::NetmessageType::net_Tick => { crate::netmessagetypes::NetmessageType::net_Tick => {
@@ -228,13 +240,17 @@ fn inner_parse_packet(
crate::netmessagetypes::NetmessageType::TE_EffectDispatch => {} crate::netmessagetypes::NetmessageType::TE_EffectDispatch => {}
crate::netmessagetypes::NetmessageType::CS_UM_PlayerStatsUpdate => {} crate::netmessagetypes::NetmessageType::CS_UM_PlayerStatsUpdate => {}
crate::netmessagetypes::NetmessageType::CS_UM_EndOfMatchAllPlayersData => { crate::netmessagetypes::NetmessageType::CS_UM_EndOfMatchAllPlayersData => {
let raw: crate::csgo_proto::CcsUsrMsgEndOfMatchAllPlayersData = prost::Message::decode(msg_bytes.as_slice())?; let raw: crate::csgo_proto::CcsUsrMsgEndOfMatchAllPlayersData =
prost::Message::decode(msg_bytes.as_slice())?;
for data in raw.allplayerdata { for data in raw.allplayerdata {
player_info.insert(data.slot(), Player { player_info.insert(
UserId(data.slot()),
Player {
name: data.name.unwrap(), name: data.name.unwrap(),
xuid: data.xuid.unwrap(), xuid: data.xuid.unwrap(),
}); },
);
} }
} }
crate::netmessagetypes::NetmessageType::TE_PhysicsProp => {} crate::netmessagetypes::NetmessageType::TE_PhysicsProp => {}

71
src/values.rs Normal file
View File

@@ -0,0 +1,71 @@
#[derive(Debug)]
pub enum RawValue {
String(String),
F32(f32),
I32(i32),
Bool(bool),
U64(u64),
}
impl TryFrom<crate::csgo_proto::c_msg_source1_legacy_game_event::KeyT> for RawValue {
type Error = ();
fn try_from(
value: crate::csgo_proto::c_msg_source1_legacy_game_event::KeyT,
) -> Result<Self, Self::Error> {
match value.r#type() {
1 if value.val_string.is_some() => Ok(Self::String(value.val_string.unwrap())),
2 if value.val_float.is_some() => Ok(Self::F32(value.val_float.unwrap())),
3 if value.val_long.is_some() => Ok(Self::I32(value.val_long.unwrap())),
4 if value.val_short.is_some() => Ok(Self::I32(value.val_short.unwrap() as i32)),
5 if value.val_byte.is_some() => Ok(Self::I32(value.val_byte.unwrap() as i32)),
6 if value.val_bool.is_some() => Ok(Self::Bool(value.val_bool.unwrap())),
7 if value.val_uint64.is_some() => Ok(Self::U64(value.val_uint64.unwrap())),
8 if value.val_long.is_some() => Ok(Self::I32(value.val_long.unwrap())),
9 if value.val_short.is_some() => Ok(Self::I32(value.val_short.unwrap() as i32)),
_ => Err(()),
}
}
}
impl TryFrom<RawValue> for i32 {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::I32(v) => Ok(v),
_ => Err(()),
}
}
}
impl TryFrom<RawValue> for bool {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::Bool(v) => Ok(v),
_ => Err(()),
}
}
}
impl TryFrom<RawValue> for String {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::String(v) => Ok(v),
_ => Err(()),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct UserId(pub(crate) i32);
impl TryFrom<RawValue> for UserId {
type Error = ();
fn try_from(value: RawValue) -> Result<Self, Self::Error> {
match value {
RawValue::I32(v) => Ok(Self(v)),
_ => Err(()),
}
}
}

View File

@@ -1,3 +1,5 @@
use csdemo::{game_event::GameEvent, DemoEvent};
#[test] #[test]
fn mirage_1() { fn mirage_1() {
let content = std::fs::read("testfiles/mirage.dem").unwrap(); let content = std::fs::read("testfiles/mirage.dem").unwrap();
@@ -11,7 +13,27 @@ fn mirage_1() {
assert_eq!("de_mirage", output.header.map_name()); assert_eq!("de_mirage", output.header.map_name());
todo!() for event in output.events.iter() {
match event {
DemoEvent::GameEvent(gevent) => match gevent {
GameEvent::PlayerDeath(death) => {
assert!(
death.remaining.is_empty(),
"Remaining for PlayerDeath: {:?}",
death.remaining
);
let died_user = output
.player_info
.get(death.userid.as_ref().unwrap())
.unwrap();
dbg!(died_user);
}
_ => {}
},
_ => {}
};
}
} }
#[test] #[test]