1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 10:14:06 +02:00

generated code for parsing game events

This commit is contained in:
Robin Appelman 2019-03-02 00:39:22 +01:00
commit cc7f9a48cd
18 changed files with 8518 additions and 64 deletions

4
Cargo.lock generated
View file

@ -9,13 +9,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "bitstream_reader"
version = "0.3.1"
dependencies = [
"bitstream_reader_derive 0.3.0",
"bitstream_reader_derive 0.3.1",
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitstream_reader_derive"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",

7969
src/demo/gameevent_gen.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,21 @@
use std::collections::HashMap;
use crate::{ParseError, Result};
#[derive(Debug)]
pub struct GameEventDefinition {
id: u32,
name: String,
entries: Vec<GameEventEntry>,
}
#[derive(Debug)]
pub struct GameEventEntry {
name: String,
kind: GameEventValueType,
}
#[derive(Debug, Clone, Copy)]
pub enum GameEventValueType {
String,
Float,
@ -21,11 +26,13 @@ pub enum GameEventValueType {
Local,
}
#[derive(Debug)]
pub struct GameEvent {
kind: GameEventType,
values: HashMap<String, GameEventValue>,
}
#[derive(Debug, Clone)]
pub enum GameEventValue {
String(String),
Float(f32),
@ -36,6 +43,95 @@ pub enum GameEventValue {
Local,
}
pub trait FromGameEventValue: Sized {
fn from_value(value: GameEventValue, name: &str) -> Result<Self>;
}
impl FromGameEventValue for String {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::String(val) => Ok(val),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
impl FromGameEventValue for f32 {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::Float(val) => Ok(val),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
impl FromGameEventValue for u32 {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::Long(val) => Ok(val),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
impl FromGameEventValue for u16 {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::Short(val) => Ok(val),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
impl FromGameEventValue for u8 {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::Byte(val) => Ok(val),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
impl FromGameEventValue for bool {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::Boolean(val) => Ok(val),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
impl FromGameEventValue for () {
fn from_value(value: GameEventValue, name: &str) -> Result<Self> {
match value {
GameEventValue::Local => Ok(()),
_ => Err(ParseError::InvalidGameEvent {
name: name.to_string(),
value,
})
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum GameEventType {
ServerSpawnEvent,
ServerChangelevelFailedEvent,
@ -414,3 +510,12 @@ pub enum GameEventType {
ReplayReplaysAvailableEvent,
ReplayServerErrorEvent,
}
pub struct RawGameEvent {
pub name: String,
pub values: HashMap<String, GameEventValue>,
}
pub trait FromRawGameEvent: Sized {
fn from_raw_event(values: HashMap<String, GameEventValue>) -> Result<Self>;
}

View file

@ -0,0 +1,47 @@
use bitstream_reader::{BitRead, BitReadSized, LittleEndian};
use crate::{ReadResult, Stream};
use crate::demo::sendprop::read_bit_coord;
use crate::demo::vector::Vector;
#[derive(Debug, Clone)]
pub struct BSPDecalMessage {
pub position: Vector,
pub texture_index: u16,
pub ent_index: u16,
pub model_index: u16,
pub low_priority: bool,
}
impl BitRead<LittleEndian> for BSPDecalMessage {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let position = {
let has_x = stream.read()?;
let has_y = stream.read()?;
let has_z = stream.read()?;
Vector {
x: if has_x { read_bit_coord(stream)? } else { 0f32 },
y: if has_y { read_bit_coord(stream)? } else { 0f32 },
z: if has_z { read_bit_coord(stream)? } else { 0f32 },
}
};
let texture_index = stream.read_sized(9)?;
let (ent_index, model_index): (u16, u16) = if stream.read()? {
(stream.read_sized(11)?, stream.read_sized(12)?)
} else {
(0, 0)
};
let low_priority = stream.read()?;
Ok(BSPDecalMessage {
position,
texture_index,
ent_index,
model_index,
low_priority,
})
}
}

View file

@ -0,0 +1,7 @@
use bitstream_reader::{BitRead, BitReadSized, LittleEndian};
use crate::{ReadResult, Stream};
pub struct GameEventMessage {
}

View file

@ -5,9 +5,12 @@ pub use generated::*;
use crate::{Parse, ParseError, ParserState, Result, Stream};
mod classinfo;
mod generated;
mod stringtable;
pub mod classinfo;
pub mod generated;
pub mod stringtable;
pub mod voice;
pub mod bspdecal;
pub mod usermessage;
#[derive(Primitive, Debug)]
pub enum MessageType {
@ -46,8 +49,4 @@ impl Parse for MessageType {
let prop_type: Option<MessageType> = MessageType::from_u8(raw);
prop_type.ok_or(ParseError::InvalidMessageType(raw))
}
fn skip(stream: &mut Stream) -> Result<()> {
stream.skip(6).map_err(ParseError::from)
}
}

View file

@ -1,16 +1,16 @@
use std::collections::HashMap;
use arraydeque::{ArrayDeque, Wrapping};
use bitstream_reader::{BitRead, BitReadSized, LittleEndian};
use bitstream_reader::{BitRead, BitReadSized, BitStream, LittleEndian};
use crate::{ReadResult, Stream};
use crate::{Parse, ParseError, ParserState, ReadResult, Stream, Result};
use crate::demo::packet::stringtable::{ExtraData, FixedUserdataSize, StringTable, StringTableEntry};
pub struct CreateStringTableMessage {
pub table: StringTable,
}
struct StringTableMeta {
pub struct StringTableMeta {
pub max_entries: u16,
pub fixed_userdata_size: Option<FixedUserdataSize>,
}
@ -47,7 +47,7 @@ impl BitRead<LittleEndian> for CreateStringTableMessage {
fixed_userdata_size,
};
let entries = parse_string_table_entries(stream, &table_meta, entity_count, Vec::new())?;
let entries = parse_string_table_entries(stream, &table_meta, entity_count, &Vec::new())?;
let mut entries: Vec<(u16, StringTableEntry)> = entries.into_iter().collect();
// verify that there are no holes in our indexes
@ -71,14 +71,40 @@ impl BitRead<LittleEndian> for CreateStringTableMessage {
}
}
pub struct UpdateStringTableMessage {
pub entries: HashMap<u16, StringTableEntry>,
pub table_id: u8,
}
impl Parse for UpdateStringTableMessage {
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> {
let table_id = stream.read_sized(5)?;
let changed: u16 = if stream.read()? { stream.read()? } else { 1 };
let len = stream.read_int(20)?;
let mut data = stream.read_bits(len)?;
let entries = match state.string_tables.get(table_id as usize) {
Some(table) => parse_string_table_entries(&mut data, &table.get_table_meta(), changed, &table.entries),
None => return Err(ParseError::StringTableNotFound(table_id))
}?;
Ok(UpdateStringTableMessage {
table_id,
entries,
})
}
}
fn parse_string_table_entries(
stream: &mut Stream,
table_meta: &StringTableMeta,
entry_count: u16,
existing_entries: Vec<StringTableEntry>,
existing_entries: &Vec<StringTableEntry>,
) -> ReadResult<HashMap<u16, StringTableEntry>> {
let entry_bits = 16 - table_meta.max_entries.leading_zeros();
let mut entries = HashMap::new();
let mut entries = HashMap::with_capacity(entry_count as usize);
let mut last_entry: i16 = -1;
let mut history: ArrayDeque<[String; 32], Wrapping> = ArrayDeque::new();
@ -138,10 +164,13 @@ fn parse_string_table_entries(
extra_data: user_data,
}
};
// optimize: any way to get rid of the clone here?
// `entries` always outlives `history` without reallocation
let text = entry.text.clone();
entries.insert(index, entry);
unsafe {
history.push_back(text);
// not 100% sure we should be pushing front here, and not appending
history.push_front(text);
}
}

View file

@ -0,0 +1,233 @@
use bitstream_reader::{BitRead, BitReadSized, LittleEndian};
use enum_primitive_derive::Primitive;
use num_traits::{FromPrimitive, ToPrimitive};
use crate::{ReadResult, Stream};
use crate::demo::message::usermessage::UserMessage::SayText2;
#[derive(Primitive, Clone, Copy, Debug)]
pub enum UserMessageType {
Geiger = 0,
Train = 1,
HudText = 2,
SayText = 3,
SayText2 = 4,
TextMsg = 5,
ResetHUD = 6,
GameTitle = 7,
ItemPickup = 8,
ShowMenu = 9,
Shake = 10,
Fade = 11,
VGUIMenu = 12,
Rumble = 13,
CloseCaption = 14,
SendAudio = 15,
VoiceMask = 16,
RequestState = 17,
Damage = 18,
HintText = 19,
KeyHintText = 20,
HudMsg = 21,
AmmoDenied = 22,
AchievementEvent = 23,
UpdateRadar = 24,
VoiceSubtitle = 25,
HudNotify = 26,
HudNotifyCustom = 27,
PlayerStatsUpdate = 28,
PlayerIgnited = 29,
PlayerIgnitedInv = 30,
HudArenaNotify = 31,
UpdateAchievement = 32,
TrainingMsg = 33,
TrainingObjective = 34,
DamageDodged = 35,
PlayerJarated = 36,
PlayerExtinguished = 37,
PlayerJaratedFade = 38,
PlayerShieldBlocked = 39,
BreakModel = 40,
CheapBreakModel = 41,
BreakModelPumpkin = 42,
BreakModelRocketDud = 43,
CallVoteFailed = 44,
VoteStart = 45,
VotePass = 46,
VoteFailed = 47,
VoteSetup = 48,
PlayerBonusPoints = 49,
SpawnFlyingBird = 50,
PlayerGodRayEffect = 51,
SPHapWeapEvent = 52,
HapDmg = 53,
HapPunch = 54,
HapSetDrag = 55,
HapSet = 56,
HapMeleeContact = 57,
Unknown = 255,
}
pub enum UserMessage {
SayText2(SayText2Message),
Text(TextMessage),
ResetHUD(ResetHudMessage),
Train(TrainMessage),
VoiceSubtitle(VoiceSubtitleMessage),
Shake(ShakeMessage),
Unknown(UnknownUserMessage),
}
impl BitRead<LittleEndian> for UserMessage {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let message_type_opt: Option<UserMessageType> = UserMessageType::from_u8(stream.read()?);
let message_type = message_type_opt.unwrap_or(UserMessageType::Unknown);
let length = stream.read_int(11)?;
let data = stream.read_bits(length)?;
Ok(match message_type {
UserMessageType::SayText2 => UserMessage::SayText2(stream.read()?),
UserMessageType::TextMsg => UserMessage::Text(stream.read()?),
UserMessageType::ResetHUD => UserMessage::ResetHUD(stream.read()?),
UserMessageType::Train => UserMessage::Train(stream.read()?),
UserMessageType::VoiceSubtitle => UserMessage::VoiceSubtitle(stream.read()?),
UserMessageType::Shake => UserMessage::Shake(stream.read()?),
_ => UserMessage::Unknown(stream.read()?),
})
}
}
#[derive(Debug, Clone)]
pub enum SayText2Kind {
ChatAll,
ChatTeam,
ChatAllDead,
NameChange,
}
impl BitRead<LittleEndian> for SayText2Kind {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let raw: String = stream.read()?;
Ok(match raw.as_str() {
"TF_Chat_Team" => SayText2Kind::ChatTeam,
"TF_Chat_AllDead" => SayText2Kind::ChatAllDead,
"#TF_Name_Change" => SayText2Kind::NameChange,
_ => SayText2Kind::ChatAll
})
}
}
#[derive(Debug, Clone)]
pub struct SayText2Message {
client: u8,
raw: u8,
kind: SayText2Kind,
from: String,
text: String,
}
impl BitRead<LittleEndian> for SayText2Message {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let client = stream.read()?;
let raw = stream.read()?;
let (kind, from, text): (SayText2Kind, String, String) = if stream.read::<u8>()? == 1 {
let first = stream.read::<u8>()?;
if first == 7 {
let _color = stream.read_string(Some(6))?;
} else {
let _ = stream.skip(8)?;
}
let text: String = stream.read()?;
if text.starts_with("*DEAD*") {
// grave talk is in the format '*DEAD* \u0003$from\u0001: $text'b
let start = text.find(char::from(3)).unwrap_or(0);
let end = text.find(char::from(1)).unwrap_or(0);
let from: String = String::from_utf8(text.bytes().skip(start + 1).take(end - start - 1).collect())?;
let text: String = String::from_utf8(text.bytes().skip(end + 5).collect())?;
let kind = SayText2Kind::ChatAllDead;
(kind, from, text)
} else {
(SayText2Kind::ChatAll, "".to_owned(), text)
}
} else {
let _ = stream.set_pos(stream.pos() - 8)?;
let kind = stream.read()?;
let from = stream.read()?;
let text = stream.read()?;
let _ = stream.skip(16)?;
(kind, from, text)
};
// cleanup color codes
let mut text = text
.replace(char::from(1), "")
.replace(char::from(3), "");
while let Some(pos) = text.find(char::from(7)) {
text = String::from_utf8(text.bytes().take(pos).chain(text.bytes().skip(pos + 7)).collect())?;
}
Ok(SayText2Message {
client,
raw,
kind,
from,
text,
})
}
}
#[derive(BitRead, Debug, Clone)]
#[discriminant_bits = 8]
pub enum HudTextLocation {
PrintNotify = 1,
PrintConsole,
PrintTalk,
PrintCenter,
}
#[derive(BitRead, Debug, Clone)]
pub struct TextMessage {
pub location: HudTextLocation,
pub text: String,
#[size = 4]
pub substitute: Vec<String>,
}
#[derive(BitRead, Debug, Clone)]
pub struct ResetHudMessage {
pub data: u8
}
#[derive(BitRead, Debug, Clone)]
pub struct TrainMessage {
pub data: u8
}
#[derive(BitRead, Debug, Clone)]
pub struct VoiceSubtitleMessage {
client: u8,
menu: u8,
item: u8,
}
#[derive(BitRead, Debug, Clone)]
pub struct ShakeMessage {
command: u8,
amplitude: f32,
frequency: f32,
duration: f32,
}
#[derive(Debug, Clone)]
pub struct UnknownUserMessage {
data: Stream
}
impl BitRead<LittleEndian> for UnknownUserMessage {
fn read(stream: &mut Stream) -> ReadResult<Self> {
Ok(UnknownUserMessage {
data: stream.read_bits(stream.bits_left())?
})
}
}

65
src/demo/message/voice.rs Normal file
View file

@ -0,0 +1,65 @@
use bitstream_reader::{BitRead, BitStream, LittleEndian};
use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
#[derive(Debug, Clone)]
pub struct VoiceInitMessage {
codec: String,
quality: u8,
extra_data: u16,
}
impl BitRead<LittleEndian> for VoiceInitMessage {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let codec = stream.read()?;
let quality = stream.read()?;
let extra_data = if quality == 255 {
stream.read()?
} else if codec == "vaudio_celt" {
11025
} else {
0
};
Ok(VoiceInitMessage {
codec,
quality,
extra_data,
})
}
}
#[derive(BitRead, Debug, Clone)]
#[endianness = "LittleEndian"]
pub struct VoiceDataMessage {
client: u8,
proximity: u8,
length: u16,
#[size = "length"]
data: Stream,
}
#[derive(Debug, Clone)]
pub struct ParseSoundsMessage {
pub reliable: bool,
pub num: u8,
pub length: u16,
pub data: Stream,
}
impl BitRead<LittleEndian> for ParseSoundsMessage {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let reliable = stream.read()?;
let num = if reliable { 1u8 } else { stream.read()? };
let length = if reliable { stream.read_sized::<u16>(8)? } else { stream.read()? };
let data = stream.read_sized(length as usize)?;
Ok(ParseSoundsMessage {
reliable,
num,
length,
data,
})
}
}

View file

@ -1,6 +1,7 @@
use bitstream_reader::{BitBuffer, BitStream, LittleEndian};
pub mod gamevent;
pub mod gameevent_gen;
pub mod header;
pub mod message;
pub mod packet;

View file

@ -47,16 +47,4 @@ impl Parse for MessagePacket {
};
Ok(packet)
}
fn skip(stream: &mut Stream) -> Result<()> {
let _ = stream.skip(32 * 2)?;
for i in 0..6 {
Vector::skip(stream)?;
}
let _ = stream.skip(32 * 2)?;
let length: usize = stream.read_int::<usize>(32)?;
stream.skip(length * 8).map_err(ParseError::from)
}
}

View file

@ -2,8 +2,9 @@ use std::fmt;
use bitstream_reader::{BitRead, LittleEndian};
use crate::demo::sendprop::SendPropFlag::Exclude;
use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
use crate::demo::message::stringtable::StringTableMeta;
use crate::demo::sendprop::SendPropFlag::Exclude;
#[derive(BitRead, Clone, Copy, Debug)]
pub struct FixedUserdataSize {
@ -23,6 +24,15 @@ pub struct StringTable {
pub compressed: bool,
}
impl StringTable {
pub fn get_table_meta(&self) -> StringTableMeta {
StringTableMeta {
fixed_userdata_size: self.fixed_userdata_size,
max_entries: self.max_entries,
}
}
}
impl BitRead<LittleEndian> for StringTable {
fn read(stream: &mut Stream) -> ReadResult<Self> {
let name = stream.read()?;

View file

@ -1,5 +1,8 @@
use std::collections::HashMap;
use bitstream_reader::{BitRead, LittleEndian, ReadError};
use crate::demo::gamevent::GameEventValue;
use crate::demo::header::Header;
use crate::demo::packet::Packet;
pub use crate::demo::parser::state::ParserState;
@ -22,6 +25,15 @@ pub enum ParseError {
InvalidSendPropArray(String),
/// Expected amount of data left after parsing an object
DataRemaining(usize),
/// String table that was send for update doesn't exist
StringTableNotFound(u8),
/// A unknown game event type was read
UnknownGameEvent(String),
/// A read game event doesn't contain the expected values
InvalidGameEvent {
name: String,
value: GameEventValue,
},
}
impl From<ReadError> for ParseError {
@ -34,21 +46,12 @@ pub type Result<T> = std::result::Result<T, ParseError>;
pub trait Parse: Sized {
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self>;
fn skip(stream: &mut Stream) -> Result<()> {
let _ = Self::parse(stream, &ParserState::new())?;
Ok(())
}
}
impl<T: BitRead<LittleEndian>> Parse for T {
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> {
Self::read(stream).map_err(ParseError::from)
}
fn skip(stream: &mut Stream) -> Result<()> {
let _ = Self::parse(stream, &ParserState::new())?;
Ok(())
}
}
pub struct DemoParser {
@ -68,10 +71,6 @@ impl DemoParser {
T::parse(&mut self.stream, &self.state)
}
pub fn skip<T: Parse>(&mut self) -> Result<()> {
T::skip(&mut self.stream)
}
pub fn stream_pos(&self) -> usize {
self.stream.pos()
}
@ -81,19 +80,6 @@ impl DemoParser {
Ok(())
}
pub fn split_packets(mut self) -> Result<Vec<Stream>> {
let _ = self.skip::<Header>()?;
let mut streams = vec![];
while self.stream.bits_left() > 7 {
let start = self.stream.pos();
let _ = self.skip::<Packet>()?;
let end = self.stream.pos();
let _ = self.stream.set_pos(start);
streams.push(self.stream.read_bits(end - start)?);
}
Ok(streams)
}
pub fn parse_demo(mut self) -> Result<(Header, Vec<Packet>)> {
let header = self.read::<Header>()?;
let mut packets = vec![];

View file

@ -2,13 +2,17 @@ use crate::demo::gamevent::GameEventDefinition;
use crate::demo::sendprop::SendProp;
use crate::Stream;
use std::collections::HashMap;
use crate::demo::packet::stringtable::StringTable;
#[derive(Default, Debug)]
pub struct ParserState {
pub version: u32,
pub static_baselines: HashMap<u32, StaticBaseline>,
pub event_definitions: HashMap<u32, GameEventDefinition>,
pub string_tables: Vec<StringTable>
}
#[derive(Debug)]
pub struct StaticBaseline {
class_id: u32,
raw: Stream,
@ -17,10 +21,6 @@ pub struct StaticBaseline {
impl ParserState {
pub fn new() -> Self {
ParserState {
version: 0,
static_baselines: HashMap::new(),
event_definitions: HashMap::new(),
}
ParserState::default()
}
}

View file

@ -184,6 +184,7 @@ impl BitRead<LittleEndian> for SendPropFlags {
}
}
#[derive(Debug, Clone)]
pub enum SendPropValue {
Vector(Vector),
VectorXY(VectorXY),
@ -193,7 +194,23 @@ pub enum SendPropValue {
Array(Vec<SendPropValue>),
}
#[derive(Debug)]
pub struct SendProp {
definition: SendPropDefinition,
value: SendPropValue,
}
pub fn read_bit_coord(stream: &mut Stream) -> ReadResult<f32> {
let has_int = stream.read()?;
let has_frac = stream.read()?;
Ok(if has_int || has_frac {
let sign = if stream.read()? { -1f32 } else { 1f32 };
let int_val: u16 = if has_int { stream.read_sized::<u16>(14)? + 1 } else { 0 };
let frac_val: u8 = if has_frac { stream.read_sized(5)? } else { 0 };
let value = int_val as f32 + (frac_val as f32 * (1f32 / 32f32));
value * sign
} else {
0f32
})
}

View file

@ -1,13 +1,13 @@
use bitstream_reader::BitRead;
#[derive(BitRead, Debug)]
#[derive(BitRead, Debug, Clone, Copy)]
pub struct Vector {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(BitRead, Debug)]
#[derive(BitRead, Debug, Clone, Copy)]
pub struct VectorXY {
pub x: f32,
pub y: f32,

View file

@ -6,4 +6,3 @@ pub use crate::demo::{
pub use bitstream_reader::Result as ReadResult;
mod demo;
mod state;

View file

@ -1 +0,0 @@