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:
parent
cd552319ac
commit
cc7f9a48cd
18 changed files with 8518 additions and 64 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -9,13 +9,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
name = "bitstream_reader"
|
name = "bitstream_reader"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
dependencies = [
|
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)",
|
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitstream_reader_derive"
|
name = "bitstream_reader_derive"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
||||||
7969
src/demo/gameevent_gen.rs
Normal file
7969
src/demo/gameevent_gen.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +1,21 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{ParseError, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GameEventDefinition {
|
pub struct GameEventDefinition {
|
||||||
id: u32,
|
id: u32,
|
||||||
name: String,
|
name: String,
|
||||||
entries: Vec<GameEventEntry>,
|
entries: Vec<GameEventEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GameEventEntry {
|
pub struct GameEventEntry {
|
||||||
name: String,
|
name: String,
|
||||||
kind: GameEventValueType,
|
kind: GameEventValueType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum GameEventValueType {
|
pub enum GameEventValueType {
|
||||||
String,
|
String,
|
||||||
Float,
|
Float,
|
||||||
|
|
@ -21,11 +26,13 @@ pub enum GameEventValueType {
|
||||||
Local,
|
Local,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GameEvent {
|
pub struct GameEvent {
|
||||||
kind: GameEventType,
|
kind: GameEventType,
|
||||||
values: HashMap<String, GameEventValue>,
|
values: HashMap<String, GameEventValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum GameEventValue {
|
pub enum GameEventValue {
|
||||||
String(String),
|
String(String),
|
||||||
Float(f32),
|
Float(f32),
|
||||||
|
|
@ -36,6 +43,95 @@ pub enum GameEventValue {
|
||||||
Local,
|
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 {
|
pub enum GameEventType {
|
||||||
ServerSpawnEvent,
|
ServerSpawnEvent,
|
||||||
ServerChangelevelFailedEvent,
|
ServerChangelevelFailedEvent,
|
||||||
|
|
@ -414,3 +510,12 @@ pub enum GameEventType {
|
||||||
ReplayReplaysAvailableEvent,
|
ReplayReplaysAvailableEvent,
|
||||||
ReplayServerErrorEvent,
|
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>;
|
||||||
|
}
|
||||||
47
src/demo/message/bspdecal.rs
Normal file
47
src/demo/message/bspdecal.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
7
src/demo/message/gameevent.rs
Normal file
7
src/demo/message/gameevent.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
use bitstream_reader::{BitRead, BitReadSized, LittleEndian};
|
||||||
|
|
||||||
|
use crate::{ReadResult, Stream};
|
||||||
|
|
||||||
|
pub struct GameEventMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -5,9 +5,12 @@ pub use generated::*;
|
||||||
|
|
||||||
use crate::{Parse, ParseError, ParserState, Result, Stream};
|
use crate::{Parse, ParseError, ParserState, Result, Stream};
|
||||||
|
|
||||||
mod classinfo;
|
pub mod classinfo;
|
||||||
mod generated;
|
pub mod generated;
|
||||||
mod stringtable;
|
pub mod stringtable;
|
||||||
|
pub mod voice;
|
||||||
|
pub mod bspdecal;
|
||||||
|
pub mod usermessage;
|
||||||
|
|
||||||
#[derive(Primitive, Debug)]
|
#[derive(Primitive, Debug)]
|
||||||
pub enum MessageType {
|
pub enum MessageType {
|
||||||
|
|
@ -46,8 +49,4 @@ impl Parse for MessageType {
|
||||||
let prop_type: Option<MessageType> = MessageType::from_u8(raw);
|
let prop_type: Option<MessageType> = MessageType::from_u8(raw);
|
||||||
prop_type.ok_or(ParseError::InvalidMessageType(raw))
|
prop_type.ok_or(ParseError::InvalidMessageType(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip(stream: &mut Stream) -> Result<()> {
|
|
||||||
stream.skip(6).map_err(ParseError::from)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use arraydeque::{ArrayDeque, Wrapping};
|
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};
|
use crate::demo::packet::stringtable::{ExtraData, FixedUserdataSize, StringTable, StringTableEntry};
|
||||||
|
|
||||||
pub struct CreateStringTableMessage {
|
pub struct CreateStringTableMessage {
|
||||||
pub table: StringTable,
|
pub table: StringTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StringTableMeta {
|
pub struct StringTableMeta {
|
||||||
pub max_entries: u16,
|
pub max_entries: u16,
|
||||||
pub fixed_userdata_size: Option<FixedUserdataSize>,
|
pub fixed_userdata_size: Option<FixedUserdataSize>,
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ impl BitRead<LittleEndian> for CreateStringTableMessage {
|
||||||
fixed_userdata_size,
|
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();
|
let mut entries: Vec<(u16, StringTableEntry)> = entries.into_iter().collect();
|
||||||
|
|
||||||
// verify that there are no holes in our indexes
|
// 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(
|
fn parse_string_table_entries(
|
||||||
stream: &mut Stream,
|
stream: &mut Stream,
|
||||||
table_meta: &StringTableMeta,
|
table_meta: &StringTableMeta,
|
||||||
entry_count: u16,
|
entry_count: u16,
|
||||||
existing_entries: Vec<StringTableEntry>,
|
existing_entries: &Vec<StringTableEntry>,
|
||||||
) -> ReadResult<HashMap<u16, StringTableEntry>> {
|
) -> ReadResult<HashMap<u16, StringTableEntry>> {
|
||||||
let entry_bits = 16 - table_meta.max_entries.leading_zeros();
|
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 last_entry: i16 = -1;
|
||||||
let mut history: ArrayDeque<[String; 32], Wrapping> = ArrayDeque::new();
|
let mut history: ArrayDeque<[String; 32], Wrapping> = ArrayDeque::new();
|
||||||
|
|
@ -138,10 +164,13 @@ fn parse_string_table_entries(
|
||||||
extra_data: user_data,
|
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();
|
let text = entry.text.clone();
|
||||||
entries.insert(index, entry);
|
entries.insert(index, entry);
|
||||||
unsafe {
|
unsafe {
|
||||||
history.push_back(text);
|
// not 100% sure we should be pushing front here, and not appending
|
||||||
|
history.push_front(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
233
src/demo/message/usermessage.rs
Normal file
233
src/demo/message/usermessage.rs
Normal 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
65
src/demo/message/voice.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use bitstream_reader::{BitBuffer, BitStream, LittleEndian};
|
use bitstream_reader::{BitBuffer, BitStream, LittleEndian};
|
||||||
|
|
||||||
pub mod gamevent;
|
pub mod gamevent;
|
||||||
|
pub mod gameevent_gen;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,4 @@ impl Parse for MessagePacket {
|
||||||
};
|
};
|
||||||
Ok(packet)
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ use std::fmt;
|
||||||
|
|
||||||
use bitstream_reader::{BitRead, LittleEndian};
|
use bitstream_reader::{BitRead, LittleEndian};
|
||||||
|
|
||||||
use crate::demo::sendprop::SendPropFlag::Exclude;
|
|
||||||
use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
|
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)]
|
#[derive(BitRead, Clone, Copy, Debug)]
|
||||||
pub struct FixedUserdataSize {
|
pub struct FixedUserdataSize {
|
||||||
|
|
@ -23,6 +24,15 @@ pub struct StringTable {
|
||||||
pub compressed: bool,
|
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 {
|
impl BitRead<LittleEndian> for StringTable {
|
||||||
fn read(stream: &mut Stream) -> ReadResult<Self> {
|
fn read(stream: &mut Stream) -> ReadResult<Self> {
|
||||||
let name = stream.read()?;
|
let name = stream.read()?;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bitstream_reader::{BitRead, LittleEndian, ReadError};
|
use bitstream_reader::{BitRead, LittleEndian, ReadError};
|
||||||
|
|
||||||
|
use crate::demo::gamevent::GameEventValue;
|
||||||
use crate::demo::header::Header;
|
use crate::demo::header::Header;
|
||||||
use crate::demo::packet::Packet;
|
use crate::demo::packet::Packet;
|
||||||
pub use crate::demo::parser::state::ParserState;
|
pub use crate::demo::parser::state::ParserState;
|
||||||
|
|
@ -22,6 +25,15 @@ pub enum ParseError {
|
||||||
InvalidSendPropArray(String),
|
InvalidSendPropArray(String),
|
||||||
/// Expected amount of data left after parsing an object
|
/// Expected amount of data left after parsing an object
|
||||||
DataRemaining(usize),
|
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 {
|
impl From<ReadError> for ParseError {
|
||||||
|
|
@ -34,21 +46,12 @@ pub type Result<T> = std::result::Result<T, ParseError>;
|
||||||
|
|
||||||
pub trait Parse: Sized {
|
pub trait Parse: Sized {
|
||||||
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self>;
|
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 {
|
impl<T: BitRead<LittleEndian>> Parse for T {
|
||||||
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> {
|
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> {
|
||||||
Self::read(stream).map_err(ParseError::from)
|
Self::read(stream).map_err(ParseError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip(stream: &mut Stream) -> Result<()> {
|
|
||||||
let _ = Self::parse(stream, &ParserState::new())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DemoParser {
|
pub struct DemoParser {
|
||||||
|
|
@ -68,10 +71,6 @@ impl DemoParser {
|
||||||
T::parse(&mut self.stream, &self.state)
|
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 {
|
pub fn stream_pos(&self) -> usize {
|
||||||
self.stream.pos()
|
self.stream.pos()
|
||||||
}
|
}
|
||||||
|
|
@ -81,19 +80,6 @@ impl DemoParser {
|
||||||
Ok(())
|
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>)> {
|
pub fn parse_demo(mut self) -> Result<(Header, Vec<Packet>)> {
|
||||||
let header = self.read::<Header>()?;
|
let header = self.read::<Header>()?;
|
||||||
let mut packets = vec![];
|
let mut packets = vec![];
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@ use crate::demo::gamevent::GameEventDefinition;
|
||||||
use crate::demo::sendprop::SendProp;
|
use crate::demo::sendprop::SendProp;
|
||||||
use crate::Stream;
|
use crate::Stream;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use crate::demo::packet::stringtable::StringTable;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
pub struct ParserState {
|
pub struct ParserState {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub static_baselines: HashMap<u32, StaticBaseline>,
|
pub static_baselines: HashMap<u32, StaticBaseline>,
|
||||||
pub event_definitions: HashMap<u32, GameEventDefinition>,
|
pub event_definitions: HashMap<u32, GameEventDefinition>,
|
||||||
|
pub string_tables: Vec<StringTable>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct StaticBaseline {
|
pub struct StaticBaseline {
|
||||||
class_id: u32,
|
class_id: u32,
|
||||||
raw: Stream,
|
raw: Stream,
|
||||||
|
|
@ -17,10 +21,6 @@ pub struct StaticBaseline {
|
||||||
|
|
||||||
impl ParserState {
|
impl ParserState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
ParserState {
|
ParserState::default()
|
||||||
version: 0,
|
|
||||||
static_baselines: HashMap::new(),
|
|
||||||
event_definitions: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ impl BitRead<LittleEndian> for SendPropFlags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum SendPropValue {
|
pub enum SendPropValue {
|
||||||
Vector(Vector),
|
Vector(Vector),
|
||||||
VectorXY(VectorXY),
|
VectorXY(VectorXY),
|
||||||
|
|
@ -193,7 +194,23 @@ pub enum SendPropValue {
|
||||||
Array(Vec<SendPropValue>),
|
Array(Vec<SendPropValue>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct SendProp {
|
pub struct SendProp {
|
||||||
definition: SendPropDefinition,
|
definition: SendPropDefinition,
|
||||||
value: SendPropValue,
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use bitstream_reader::BitRead;
|
use bitstream_reader::BitRead;
|
||||||
|
|
||||||
#[derive(BitRead, Debug)]
|
#[derive(BitRead, Debug, Clone, Copy)]
|
||||||
pub struct Vector {
|
pub struct Vector {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
pub z: f32,
|
pub z: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BitRead, Debug)]
|
#[derive(BitRead, Debug, Clone, Copy)]
|
||||||
pub struct VectorXY {
|
pub struct VectorXY {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,3 @@ pub use crate::demo::{
|
||||||
pub use bitstream_reader::Result as ReadResult;
|
pub use bitstream_reader::Result as ReadResult;
|
||||||
|
|
||||||
mod demo;
|
mod demo;
|
||||||
mod state;
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue