tf_demo_parser/demo/data/
userinfo.rs

1use crate::demo::message::packetentities::EntityId;
2use crate::demo::packet::stringtable::{ExtraData, StringTableEntry};
3use crate::demo::parser::analyser::UserId;
4use crate::{ReadResult, Stream};
5use bitbuffer::{BitRead, BitReadBuffer, BitReadStream, BitWrite, BitWriteStream, LittleEndian};
6
7#[derive(BitRead, Debug)]
8struct RawPlayerInfo {
9    pub name_bytes: [u8; 32],
10    pub user_id: u32,
11    #[size = 32]
12    pub steam_id: String,
13    pub extra: u32, // all my sources say these 4 bytes don't exist
14    pub friends_id: u32,
15    pub friends_name_bytes: [u8; 32], // seem to all be 0 now
16    pub is_fake_player: u8,
17    pub is_hl_tv: u8,
18    pub is_replay: u8,
19    pub custom_file: [u32; 4],
20    pub files_downloaded: u32,
21    pub more_extra: u8,
22}
23
24#[derive(BitWrite, Debug, Clone, Default)]
25pub struct PlayerInfo {
26    #[size = 32]
27    pub name: String,
28    pub user_id: UserId,
29    #[size = 32]
30    pub steam_id: String,
31    pub extra: u32, // all my sources say these 4 bytes don't exist
32    pub friends_id: u32,
33    pub friends_name_bytes: [u8; 32], // seem to all be 0 now
34    pub is_fake_player: u8,
35    pub is_hl_tv: u8,
36    pub is_replay: u8,
37    pub custom_file: [u32; 4],
38    pub files_downloaded: u32,
39    pub more_extra: u8,
40}
41
42impl From<RawPlayerInfo> for PlayerInfo {
43    fn from(raw: RawPlayerInfo) -> Self {
44        PlayerInfo {
45            name: String::from_utf8_lossy(&raw.name_bytes)
46                .trim_end_matches('\0')
47                .to_string(),
48            user_id: raw.user_id.into(),
49            steam_id: raw.steam_id,
50            extra: raw.extra,
51            friends_id: raw.friends_id,
52            friends_name_bytes: raw.friends_name_bytes,
53            is_fake_player: raw.is_fake_player,
54            is_hl_tv: raw.is_hl_tv,
55            is_replay: raw.is_replay,
56            custom_file: raw.custom_file,
57            files_downloaded: raw.files_downloaded,
58            more_extra: raw.more_extra,
59        }
60    }
61}
62
63#[derive(Clone, Debug, Default)]
64pub struct UserInfo {
65    pub entity_id: EntityId,
66    pub player_info: PlayerInfo,
67}
68
69impl UserInfo {
70    pub fn parse_from_string_table(
71        index: u16,
72        text: Option<&str>,
73        data: Option<Stream>,
74    ) -> ReadResult<Option<Self>> {
75        if let Some(mut data) = data {
76            // extra decode step to gracefully handle malformed utf8 names
77            let raw_info: RawPlayerInfo = data.read()?;
78
79            match text
80                .map(|text| text.parse::<u32>().map(|id| (id + 1).into()))
81                .unwrap_or_else(|| Ok((index as u32 + 1).into()))
82            {
83                Ok(entity_id) if !raw_info.steam_id.is_empty() => Ok(Some(UserInfo {
84                    player_info: raw_info.into(),
85                    entity_id,
86                })),
87                _ => Ok(None),
88            }
89        } else {
90            Ok(None)
91        }
92    }
93
94    pub fn encode_to_string_table(&self) -> ReadResult<StringTableEntry<'static>> {
95        let text = format!("{}", self.entity_id);
96        let mut extra_data = Vec::with_capacity(132);
97        {
98            let mut stream = BitWriteStream::new(&mut extra_data, LittleEndian);
99            self.player_info.write(&mut stream)?;
100        }
101
102        Ok(StringTableEntry {
103            text: Some(text.into()),
104            extra_data: Some(ExtraData::new(BitReadStream::new(
105                BitReadBuffer::new_owned(extra_data, LittleEndian),
106            ))),
107        })
108    }
109}