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

sendprop decoding

This commit is contained in:
Robin Appelman 2019-08-10 22:30:12 +02:00
commit ff18680a02
25 changed files with 389 additions and 135 deletions

View file

@ -5,7 +5,7 @@ extern crate test;
use std::fs; use std::fs;
use test::Bencher; use test::Bencher;
use tf_demo_parser::{Demo, Stream, DemoParser}; use tf_demo_parser::{Demo, DemoParser, Stream};
fn bench_file(input_file: &str, b: &mut Bencher) { fn bench_file(input_file: &str, b: &mut Bencher) {
let file = fs::read(input_file).expect("Unable to read file"); let file = fs::read(input_file).expect("Unable to read file");

View file

@ -5,17 +5,17 @@
extern crate test; extern crate test;
use std::fs;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::fs;
use tf_demo_parser::{Demo, DemoParser, MatchState, MessageTypeAnalyser, MessageType, ParserState}; use std::collections::{HashMap, HashSet};
use test::Bencher;
use tf_demo_parser::demo::message::Message;
use tf_demo_parser::demo::packet::datatable::{ParseSendTable, SendTableName}; use tf_demo_parser::demo::packet::datatable::{ParseSendTable, SendTableName};
use tf_demo_parser::demo::packet::stringtable::StringTableEntry; use tf_demo_parser::demo::packet::stringtable::StringTableEntry;
use tf_demo_parser::demo::message::Message;
use tf_demo_parser::demo::sendprop::SendPropDefinition;
use std::collections::{HashMap, HashSet};
use tf_demo_parser::demo::parser::MessageHandler; use tf_demo_parser::demo::parser::MessageHandler;
use test::Bencher; use tf_demo_parser::demo::sendprop::SendPropDefinition;
use tf_demo_parser::{Demo, DemoParser, MatchState, MessageType, MessageTypeAnalyser, ParserState};
pub struct SendPropAnalyser; pub struct SendPropAnalyser;
@ -31,11 +31,15 @@ impl MessageHandler for SendPropAnalyser {
fn handle_string_entry(&mut self, table: &String, _index: usize, entry: &StringTableEntry) {} fn handle_string_entry(&mut self, table: &String, _index: usize, entry: &StringTableEntry) {}
fn get_output(self, state: ParserState) -> Self::Output { fn get_output(self, state: ParserState) -> Self::Output {
state.send_tables.into_iter().map(|(_k, v)| ParseSendTable { state
name: v.name, .send_tables
props: v.props, .into_iter()
needs_decoder: v.needs_decoder .map(|(_k, v)| ParseSendTable {
}).collect() name: v.name,
props: v.props,
needs_decoder: v.needs_decoder,
})
.collect()
} }
} }
@ -43,9 +47,13 @@ fn flatten_bench(input_file: &str, b: &mut Bencher) {
let file = fs::read(input_file).expect("Unable to read file"); let file = fs::read(input_file).expect("Unable to read file");
let demo = Demo::new(file); let demo = Demo::new(file);
let stream = demo.get_stream(); let stream = demo.get_stream();
let (_, send_tables) = DemoParser::parse_with_analyser(stream.clone(), SendPropAnalyser).unwrap(); let (_, send_tables) =
DemoParser::parse_with_analyser(stream.clone(), SendPropAnalyser).unwrap();
b.iter(|| { b.iter(|| {
let flat: Vec<_> = send_tables.iter().map(|table| table.flatten_props(&send_tables)).collect(); let flat: Vec<_> = send_tables
.iter()
.map(|table| table.flatten_props(&send_tables))
.collect();
test::black_box(flat); test::black_box(flat);
}); });
} }

View file

@ -1,4 +1,4 @@
use bitstream_reader::{BitRead, LittleEndian, BitSkip}; use bitstream_reader::{BitRead, BitSkip, LittleEndian};
use crate::demo::sendprop::read_bit_coord; use crate::demo::sendprop::read_bit_coord;
use crate::demo::vector::Vector; use crate::demo::vector::Vector;
@ -45,4 +45,4 @@ impl BitRead<LittleEndian> for BSPDecalMessage {
} }
} }
impl BitSkip<LittleEndian> for BSPDecalMessage{} impl BitSkip<LittleEndian> for BSPDecalMessage {}

View file

@ -1,4 +1,4 @@
use bitstream_reader::{BitRead, BitReadSized, LittleEndian, BitSkip}; use bitstream_reader::{BitRead, BitReadSized, BitSkip, LittleEndian};
use crate::demo::message::stringtable::log_base2; use crate::demo::message::stringtable::log_base2;
use crate::{ReadResult, Stream}; use crate::{ReadResult, Stream};
@ -41,4 +41,4 @@ impl BitRead<LittleEndian> for ClassInfoMessage {
} }
} }
impl BitSkip<LittleEndian> for ClassInfoMessage{} impl BitSkip<LittleEndian> for ClassInfoMessage {}

View file

@ -1,15 +1,15 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::FromIterator; use std::iter::FromIterator;
use bitstream_reader::{BitRead, LittleEndian, BitSkip}; use bitstream_reader::{BitRead, BitSkip, LittleEndian};
use crate::demo::gameevent_gen::GameEventType; use crate::demo::gameevent_gen::GameEventType;
use crate::demo::gamevent::{ use crate::demo::gamevent::{
GameEvent, GameEventDefinition, GameEventEntry, GameEventValue, GameEventValueType, GameEvent, GameEventDefinition, GameEventEntry, GameEventValue, GameEventValueType,
RawGameEvent, RawGameEvent,
}; };
use crate::{Parse, ParserState, ReadResult, Result, Stream, ParseError};
use crate::demo::parser::ParseBitSkip; use crate::demo::parser::ParseBitSkip;
use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
fn read_event_value(stream: &mut Stream, definition: &GameEventEntry) -> Result<GameEventValue> { fn read_event_value(stream: &mut Stream, definition: &GameEventEntry) -> Result<GameEventValue> {
Ok(match definition.kind { Ok(match definition.kind {
@ -111,4 +111,4 @@ impl BitRead<LittleEndian> for GameEventListMessage {
} }
} }
impl BitSkip<LittleEndian> for GameEventListMessage{} impl BitSkip<LittleEndian> for GameEventListMessage {}

View file

@ -1,6 +1,6 @@
use crate::Stream; use crate::Stream;
/// Messages that consists only of primitives and string and can be derived /// Messages that consists only of primitives and string and can be derived
use bitstream_reader::{BitRead, LittleEndian, BitSize, BitSkip}; use bitstream_reader::{BitRead, BitSize, BitSkip, LittleEndian};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(BitRead, Debug)] #[derive(BitRead, Debug)]
@ -10,7 +10,7 @@ pub struct FileMessage {
pub requested: bool, pub requested: bool,
} }
impl BitSkip<LittleEndian> for FileMessage{} impl BitSkip<LittleEndian> for FileMessage {}
#[derive(BitRead, BitSize, Debug)] #[derive(BitRead, BitSize, Debug)]
pub struct NetTickMessage { pub struct NetTickMessage {
@ -24,7 +24,7 @@ pub struct StringCmdMessage {
pub command: String, pub command: String,
} }
impl BitSkip<LittleEndian> for StringCmdMessage{} impl BitSkip<LittleEndian> for StringCmdMessage {}
#[derive(BitRead, BitSize, Debug)] #[derive(BitRead, BitSize, Debug)]
pub struct SigOnStateMessage { pub struct SigOnStateMessage {
@ -37,7 +37,7 @@ pub struct PrintMessage {
pub value: String, pub value: String,
} }
impl BitSkip<LittleEndian> for PrintMessage{} impl BitSkip<LittleEndian> for PrintMessage {}
#[derive(BitRead, Debug)] #[derive(BitRead, Debug)]
pub struct ServerInfoMessage { pub struct ServerInfoMessage {
@ -60,7 +60,7 @@ pub struct ServerInfoMessage {
pub replay: bool, pub replay: bool,
} }
impl BitSkip<LittleEndian> for ServerInfoMessage{} impl BitSkip<LittleEndian> for ServerInfoMessage {}
#[derive(BitRead, BitSize, Debug)] #[derive(BitRead, BitSize, Debug)]
pub struct SetPauseMessage { pub struct SetPauseMessage {
@ -94,7 +94,7 @@ pub struct EntityMessage {
pub data: Stream, pub data: Stream,
} }
impl BitSkip<LittleEndian> for EntityMessage{} impl BitSkip<LittleEndian> for EntityMessage {}
#[derive(BitRead, BitSize, Debug)] #[derive(BitRead, BitSize, Debug)]
pub struct PreFetchMessage { pub struct PreFetchMessage {
@ -111,7 +111,7 @@ pub struct MenuMessage {
pub index: Stream, pub index: Stream,
} }
impl BitSkip<LittleEndian> for MenuMessage{} impl BitSkip<LittleEndian> for MenuMessage {}
#[derive(BitRead, Debug)] #[derive(BitRead, Debug)]
pub struct GetCvarValueMessage { pub struct GetCvarValueMessage {
@ -119,7 +119,7 @@ pub struct GetCvarValueMessage {
pub value: String, pub value: String,
} }
impl BitSkip<LittleEndian> for GetCvarValueMessage{} impl BitSkip<LittleEndian> for GetCvarValueMessage {}
#[derive(BitRead, Debug)] #[derive(BitRead, Debug)]
#[endianness = "LittleEndian"] #[endianness = "LittleEndian"]
@ -129,4 +129,4 @@ pub struct CmdKeyValuesMessage {
pub data: Stream, pub data: Stream,
} }
impl BitSkip<LittleEndian> for CmdKeyValuesMessage{} impl BitSkip<LittleEndian> for CmdKeyValuesMessage {}

View file

@ -5,28 +5,28 @@ pub use generated::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{Parse, ParseError, ParserState, Result, Stream};
use crate::demo::message::bspdecal::*; use crate::demo::message::bspdecal::*;
use crate::demo::message::classinfo::*; use crate::demo::message::classinfo::*;
use crate::demo::message::gameevent::*; use crate::demo::message::gameevent::*;
use crate::demo::message::packetentities::*; use crate::demo::message::packetentities::*;
use crate::demo::message::setconvar::*;
use crate::demo::message::stringtable::*; use crate::demo::message::stringtable::*;
use crate::demo::message::tempentities::*; use crate::demo::message::tempentities::*;
use crate::demo::message::usermessage::*; use crate::demo::message::usermessage::*;
use crate::demo::message::voice::*; use crate::demo::message::voice::*;
use crate::demo::message::setconvar::*;
use crate::demo::parser::ParseBitSkip; use crate::demo::parser::ParseBitSkip;
use crate::{Parse, ParseError, ParserState, Result, Stream};
pub mod bspdecal; pub mod bspdecal;
pub mod classinfo; pub mod classinfo;
pub mod gameevent; pub mod gameevent;
pub mod generated; pub mod generated;
pub mod packetentities; pub mod packetentities;
pub mod setconvar;
pub mod stringtable; pub mod stringtable;
pub mod tempentities; pub mod tempentities;
pub mod usermessage; pub mod usermessage;
pub mod voice; pub mod voice;
pub mod setconvar;
#[derive(Primitive, Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)] #[derive(Primitive, Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)] #[repr(u8)]
@ -142,7 +142,11 @@ impl Message {
} }
} }
pub fn from_type(message_type: MessageType, stream: &mut Stream, state: &ParserState) -> Result<Self> { pub fn from_type(
message_type: MessageType,
stream: &mut Stream,
state: &ParserState,
) -> Result<Self> {
Ok(match message_type { Ok(match message_type {
MessageType::Empty => Message::Empty, MessageType::Empty => Message::Empty,
MessageType::File => Message::File(FileMessage::parse(stream, state)?), MessageType::File => Message::File(FileMessage::parse(stream, state)?),

View file

@ -1,10 +1,10 @@
use bitstream_reader::BitRead; use bitstream_reader::BitRead;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use crate::{Parse, ParserState, Result, Stream, ParseError};
use crate::demo::packet::datatable::ServerClass; use crate::demo::packet::datatable::ServerClass;
use crate::demo::sendprop::SendProp;
use crate::demo::parser::ParseBitSkip; use crate::demo::parser::ParseBitSkip;
use crate::demo::sendprop::SendProp;
use crate::{Parse, ParseError, ParserState, Result, Stream};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EntityId(u32); pub struct EntityId(u32);
@ -81,13 +81,11 @@ impl ParseBitSkip for PacketEntitiesMessage {
} }
pub struct EntityUpdate { pub struct EntityUpdate {
props: Vec<SendProp> props: Vec<SendProp>,
} }
impl Parse for EntityUpdate { impl Parse for EntityUpdate {
fn parse(stream: &mut Stream, _state: &ParserState) -> Result<Self> { fn parse(stream: &mut Stream, _state: &ParserState) -> Result<Self> {
Ok(EntityUpdate { Ok(EntityUpdate { props: Vec::new() })
props: Vec::new()
})
} }
} }

View file

@ -1,9 +1,8 @@
use bitstream_reader::{BitRead, BitReadSized, LittleEndian, BitSkip}; use bitstream_reader::{BitRead, BitReadSized, BitSkip, LittleEndian};
use crate::demo::message::stringtable::log_base2; use crate::demo::message::stringtable::log_base2;
use crate::{ReadResult, Stream}; use crate::{ReadResult, Stream};
#[derive(Debug)] #[derive(Debug)]
pub struct SetConVarMessage { pub struct SetConVarMessage {
vars: Vec<(String, String)>, vars: Vec<(String, String)>,
@ -14,13 +13,15 @@ impl BitRead<LittleEndian> for SetConVarMessage {
let count: u8 = stream.read()?; let count: u8 = stream.read()?;
let mut vars: Vec<(String, String)> = Vec::with_capacity(count as usize); let mut vars: Vec<(String, String)> = Vec::with_capacity(count as usize);
for _ in 0..count { for _ in 0..count {
let key = stream.read().unwrap_or_else(|_| "Malformed cvar name".to_string()); let key = stream
let value = stream.read().unwrap_or_else(|_| "Malformed cvar value".to_string()); .read()
.unwrap_or_else(|_| "Malformed cvar name".to_string());
let value = stream
.read()
.unwrap_or_else(|_| "Malformed cvar value".to_string());
vars.push((key, value)); vars.push((key, value));
} }
Ok(SetConVarMessage { Ok(SetConVarMessage { vars })
vars
})
} }
} }

View file

@ -2,11 +2,11 @@ use bitstream_reader::{BitBuffer, BitStream, LittleEndian};
use num_traits::{PrimInt, Unsigned}; use num_traits::{PrimInt, Unsigned};
use snap::Decoder; use snap::Decoder;
use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
use crate::demo::packet::stringtable::{ use crate::demo::packet::stringtable::{
ExtraData, FixedUserdataSize, StringTable, StringTableEntry, ExtraData, FixedUserdataSize, StringTable, StringTableEntry,
}; };
use crate::demo::parser::ParseBitSkip; use crate::demo::parser::ParseBitSkip;
use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
#[derive(Debug)] #[derive(Debug)]
pub struct CreateStringTableMessage { pub struct CreateStringTableMessage {
@ -105,7 +105,6 @@ impl ParseBitSkip for CreateStringTableMessage {
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UpdateStringTableMessage { pub struct UpdateStringTableMessage {
pub entries: Vec<(u16, StringTableEntry)>, pub entries: Vec<(u16, StringTableEntry)>,
@ -246,7 +245,7 @@ fn read_table_entry(
} else { } else {
None None
} }
.map(ExtraData::new); .map(ExtraData::new);
Ok(StringTableEntry { text, extra_data }) Ok(StringTableEntry { text, extra_data })
} }

View file

@ -1,4 +1,4 @@
use crate::{Parse, ParserState, Result, Stream, ParseError}; use crate::{Parse, ParseError, ParserState, Result, Stream};
use super::packetentities::PacketEntity; use super::packetentities::PacketEntity;
use super::stringtable::read_var_int; use super::stringtable::read_var_int;

View file

@ -1,10 +1,10 @@
use bitstream_reader::{BitRead, LittleEndian}; use bitstream_reader::{BitRead, LittleEndian};
use enum_primitive_derive::Primitive; use enum_primitive_derive::Primitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use crate::{ReadResult, Stream, Result, ParseError};
use crate::demo::parser::ParseBitSkip; use crate::demo::parser::ParseBitSkip;
use crate::{ParseError, ReadResult, Result, Stream};
#[derive(Primitive, Clone, Copy, Debug)] #[derive(Primitive, Clone, Copy, Debug)]
pub enum UserMessageType { pub enum UserMessageType {
@ -102,12 +102,11 @@ impl BitRead<LittleEndian> for UserMessage {
impl ParseBitSkip for UserMessage { impl ParseBitSkip for UserMessage {
fn parse_skip(stream: &mut Stream) -> Result<()> { fn parse_skip(stream: &mut Stream) -> Result<()> {
let _ = stream.skip_bits(8)?; let _ = stream.skip_bits(8)?;
let length:u32 = stream.read_int(11)?; let length: u32 = stream.read_int(11)?;
stream.skip_bits(length as usize).map_err(ParseError::from) stream.skip_bits(length as usize).map_err(ParseError::from)
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ChatMessageKind { pub enum ChatMessageKind {
#[serde(rename = "TF_Chat_All")] #[serde(rename = "TF_Chat_All")]

View file

@ -1,4 +1,4 @@
use bitstream_reader::{BitRead, LittleEndian, BitSkip}; use bitstream_reader::{BitRead, BitSkip, LittleEndian};
use crate::{ReadResult, Stream}; use crate::{ReadResult, Stream};
@ -30,7 +30,7 @@ impl BitRead<LittleEndian> for VoiceInitMessage {
} }
} }
impl BitSkip<LittleEndian> for VoiceInitMessage{} impl BitSkip<LittleEndian> for VoiceInitMessage {}
#[derive(BitRead, Debug, Clone)] #[derive(BitRead, Debug, Clone)]
#[endianness = "LittleEndian"] #[endianness = "LittleEndian"]
@ -42,7 +42,7 @@ pub struct VoiceDataMessage {
data: Stream, data: Stream,
} }
impl BitSkip<LittleEndian> for VoiceDataMessage{} impl BitSkip<LittleEndian> for VoiceDataMessage {}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ParseSoundsMessage { pub struct ParseSoundsMessage {
@ -72,4 +72,4 @@ impl BitRead<LittleEndian> for ParseSoundsMessage {
} }
} }
impl BitSkip<LittleEndian> for ParseSoundsMessage{} impl BitSkip<LittleEndian> for ParseSoundsMessage {}

View file

@ -1,12 +1,12 @@
use bitstream_reader::{BitRead, LittleEndian}; use bitstream_reader::{BitRead, LittleEndian};
use crate::demo::sendprop::{SendPropDefinition, SendPropFlag, SendPropType}; use crate::demo::sendprop::{SendPropDefinition, SendPropFlag, SendPropType};
use crate::{Parse, ParseError, ParserState, Result, Stream, ReadResult}; use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
use std::fmt; use serde::{Deserialize, Serialize};
use serde::{Serialize, Deserialize};
use std::rc::Rc;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::fmt;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc;
#[derive(BitRead, Debug)] #[derive(BitRead, Debug)]
pub struct ServerClass { pub struct ServerClass {
@ -60,20 +60,17 @@ impl Parse for ParseSendTable {
let mut props = Vec::with_capacity(prop_count); let mut props = Vec::with_capacity(prop_count);
for _ in 0..prop_count { for _ in 0..prop_count {
let prop: SendPropDefinition = let prop: SendPropDefinition = SendPropDefinition::read(stream, name.clone())?;
SendPropDefinition::read(stream, name.clone())?;
if prop.flags.contains(SendPropFlag::InsideArray) { if prop.flags.contains(SendPropFlag::InsideArray) {
if array_element_prop.is_some() if array_element_prop.is_some() || prop.flags.contains(SendPropFlag::ChangesOften) {
|| prop.flags.contains(SendPropFlag::ChangesOften) return Err(ParseError::InvalidSendProp(
{
return Err(ParseError::InvalidSendPropArray(
"Array contents can't have the 'ChangesOften' flag".to_owned(), "Array contents can't have the 'ChangesOften' flag".to_owned(),
)); ));
} }
array_element_prop = Some(prop); array_element_prop = Some(prop);
} else if let Some(array_element) = array_element_prop { } else if let Some(array_element) = array_element_prop {
if prop.prop_type != SendPropType::Array { if prop.prop_type != SendPropType::Array {
return Err(ParseError::InvalidSendPropArray( return Err(ParseError::InvalidSendProp(
"Array contents can without array".to_owned(), "Array contents can without array".to_owned(),
)); ));
} }
@ -131,14 +128,25 @@ impl ParseSendTable {
} }
// TODO: below is a direct port from the js which is a direct port from C++ and not very optimal // TODO: below is a direct port from the js which is a direct port from C++ and not very optimal
fn get_all_props<'a>(&'a self, tables: &'a [ParseSendTable], excludes: &[Exclude], props: &mut Vec<&'a SendPropDefinition>) { fn get_all_props<'a>(
&'a self,
tables: &'a [ParseSendTable],
excludes: &[Exclude],
props: &mut Vec<&'a SendPropDefinition>,
) {
let mut local_props = Vec::new(); let mut local_props = Vec::new();
self.get_all_props_iterator_props(tables, excludes, &mut local_props, props); self.get_all_props_iterator_props(tables, excludes, &mut local_props, props);
props.extend_from_slice(&local_props); props.extend_from_slice(&local_props);
} }
fn get_all_props_iterator_props<'a>(&'a self, tables: &'a [ParseSendTable], excludes: &[Exclude], local_props: &mut Vec<&'a SendPropDefinition>, props: &mut Vec<&'a SendPropDefinition>) { fn get_all_props_iterator_props<'a>(
&'a self,
tables: &'a [ParseSendTable],
excludes: &[Exclude],
local_props: &mut Vec<&'a SendPropDefinition>,
props: &mut Vec<&'a SendPropDefinition>,
) {
for prop in self.props.iter() { for prop in self.props.iter() {
if prop.is_exclude() { if prop.is_exclude() {
continue; continue;
@ -202,24 +210,22 @@ impl Parse for DataTablePacket {
parse_tables.push(table); parse_tables.push(table);
} }
let flat_props: Vec<_> = parse_tables.iter() let flat_props: Vec<_> = parse_tables
.iter()
.map(|table| table.flatten_props(&parse_tables)) .map(|table| table.flatten_props(&parse_tables))
.collect(); .collect();
let tables = parse_tables.into_iter() let tables = parse_tables
.into_iter()
.zip(flat_props.into_iter()) .zip(flat_props.into_iter())
.map(|(parse_table, flat)| { .map(|(parse_table, flat)| SendTable {
SendTable { name: parse_table.name,
name: parse_table.name, props: parse_table.props,
props: parse_table.props, needs_decoder: parse_table.needs_decoder,
needs_decoder: parse_table.needs_decoder, flattened_props: flat,
flattened_props: flat,
}
}) })
.collect(); .collect();
// TODO linked tables?
let server_class_count = packet_data.read_int(16)?; let server_class_count = packet_data.read_int(16)?;
let server_classes = packet_data.read_sized(server_class_count)?; let server_classes = packet_data.read_sized(server_class_count)?;

View file

@ -1,8 +1,8 @@
use bitstream_reader::{BitRead, BitSize, LazyBitRead, LittleEndian}; use bitstream_reader::{BitRead, BitSize, LazyBitRead, LittleEndian};
use crate::{Parse, ParserState, ReadResult, Result, Stream};
use crate::demo::message::{Message, MessageType}; use crate::demo::message::{Message, MessageType};
use crate::demo::vector::Vector; use crate::demo::vector::Vector;
use crate::{Parse, ParserState, ReadResult, Result, Stream};
#[derive(Debug)] #[derive(Debug)]
pub struct MessagePacket { pub struct MessagePacket {

View file

@ -3,16 +3,16 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{ParserState, ReadResult, Stream};
use crate::demo::gameevent_gen::{ use crate::demo::gameevent_gen::{
GameEvent, PlayerDeathEvent, PlayerSpawnEvent, TeamPlayRoundWinEvent, GameEvent, PlayerDeathEvent, PlayerSpawnEvent, TeamPlayRoundWinEvent,
}; };
use crate::demo::message::{Message, MessageType};
use crate::demo::message::packetentities::EntityId; use crate::demo::message::packetentities::EntityId;
use crate::demo::message::usermessage::{ChatMessageKind, SayText2Message, UserMessage}; use crate::demo::message::usermessage::{ChatMessageKind, SayText2Message, UserMessage};
use crate::demo::message::{Message, MessageType};
use crate::demo::packet::stringtable::StringTableEntry; use crate::demo::packet::stringtable::StringTableEntry;
use crate::demo::parser::handler::MessageHandler; use crate::demo::parser::handler::MessageHandler;
use crate::demo::vector::Vector; use crate::demo::vector::Vector;
use crate::{ParserState, ReadResult, Stream};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ChatMassage { pub struct ChatMassage {

View file

@ -1,7 +1,7 @@
use crate::demo::message::{Message, MessageType}; use crate::demo::message::{Message, MessageType};
use crate::demo::packet::datatable::{SendTable, ServerClass}; use crate::demo::packet::datatable::{SendTable, ServerClass};
use crate::demo::packet::Packet;
use crate::demo::packet::stringtable::{StringTable, StringTableEntry}; use crate::demo::packet::stringtable::{StringTable, StringTableEntry};
use crate::demo::packet::Packet;
use crate::demo::parser::analyser::Analyser; use crate::demo::parser::analyser::Analyser;
use crate::ParserState; use crate::ParserState;

View file

@ -3,20 +3,20 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{ParserState, ReadResult, Stream};
use crate::demo::gameevent_gen::{ use crate::demo::gameevent_gen::{
GameEvent, PlayerDeathEvent, PlayerSpawnEvent, TeamPlayRoundWinEvent, GameEvent, PlayerDeathEvent, PlayerSpawnEvent, TeamPlayRoundWinEvent,
}; };
use crate::demo::message::{Message, MessageType};
use crate::demo::message::packetentities::EntityId; use crate::demo::message::packetentities::EntityId;
use crate::demo::message::usermessage::{ChatMessageKind, SayText2Message, UserMessage}; use crate::demo::message::usermessage::{ChatMessageKind, SayText2Message, UserMessage};
use crate::demo::packet::PacketType; use crate::demo::message::{Message, MessageType};
use crate::demo::packet::stringtable::StringTableEntry; use crate::demo::packet::stringtable::StringTableEntry;
use crate::demo::packet::PacketType;
use crate::demo::parser::handler::MessageHandler; use crate::demo::parser::handler::MessageHandler;
use crate::demo::vector::Vector; use crate::demo::vector::Vector;
use crate::{ParserState, ReadResult, Stream};
pub struct MessageTypeAnalyser { pub struct MessageTypeAnalyser {
packet_types: Vec<MessageType> packet_types: Vec<MessageType>,
} }
impl MessageHandler for MessageTypeAnalyser { impl MessageHandler for MessageTypeAnalyser {
@ -40,7 +40,7 @@ impl MessageHandler for MessageTypeAnalyser {
impl MessageTypeAnalyser { impl MessageTypeAnalyser {
pub fn new() -> Self { pub fn new() -> Self {
MessageTypeAnalyser { MessageTypeAnalyser {
packet_types: Vec::with_capacity(1024) packet_types: Vec::with_capacity(1024),
} }
} }
} }

View file

@ -1,5 +1,6 @@
use bitstream_reader::{BitRead, BitSkip, LittleEndian, ReadError}; use bitstream_reader::{BitRead, BitSkip, LittleEndian, ReadError};
pub use self::messagetypeanalyser::MessageTypeAnalyser;
use crate::demo::gamevent::{GameEventValue, GameEventValueType}; use crate::demo::gamevent::{GameEventValue, GameEventValueType};
use crate::demo::header::Header; use crate::demo::header::Header;
use crate::demo::packet::Packet; use crate::demo::packet::Packet;
@ -8,12 +9,11 @@ pub use crate::demo::parser::analyser::MatchState;
pub use crate::demo::parser::handler::{DemoHandler, MessageHandler}; pub use crate::demo::parser::handler::{DemoHandler, MessageHandler};
pub use crate::demo::parser::state::ParserState; pub use crate::demo::parser::state::ParserState;
use crate::Stream; use crate::Stream;
pub use self::messagetypeanalyser::MessageTypeAnalyser;
mod analyser; mod analyser;
mod handler; mod handler;
mod state;
mod messagetypeanalyser; mod messagetypeanalyser;
mod state;
/// Errors that can occur during parsing /// Errors that can occur during parsing
#[derive(Debug)] #[derive(Debug)]
@ -26,8 +26,8 @@ pub enum ParseError {
InvalidMessageType(u8), InvalidMessageType(u8),
/// SendProp type is invalid /// SendProp type is invalid
InvalidSendPropType(u8), InvalidSendPropType(u8),
/// Invalid structure found while creating array SendProp /// Invalid SendProp
InvalidSendPropArray(String), InvalidSendProp(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 /// String table that was send for update doesn't exist
@ -95,7 +95,10 @@ impl DemoParser {
Self::parse_with_analyser(stream, Analyser::new()) Self::parse_with_analyser(stream, Analyser::new())
} }
pub fn parse_with_analyser<T: MessageHandler>(mut stream: Stream, analyser: T) -> Result<(Header, T::Output)> { pub fn parse_with_analyser<T: MessageHandler>(
mut stream: Stream,
analyser: T,
) -> Result<(Header, T::Output)> {
let mut handler = DemoHandler::with_analyser(analyser); let mut handler = DemoHandler::with_analyser(analyser);
let header = Header::read(&mut stream)?; let header = Header::read(&mut stream)?;
loop { loop {

View file

@ -1,11 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::demo::gamevent::GameEventDefinition; use crate::demo::gamevent::GameEventDefinition;
use crate::demo::message::{Message, MessageType};
use crate::demo::message::gameevent::GameEventTypeId; use crate::demo::message::gameevent::GameEventTypeId;
use crate::demo::message::packetentities::EntityId; use crate::demo::message::packetentities::EntityId;
use crate::demo::message::stringtable::StringTableMeta; use crate::demo::message::stringtable::StringTableMeta;
use crate::demo::packet::datatable::{SendTable, ServerClass, SendTableName}; use crate::demo::message::{Message, MessageType};
use crate::demo::packet::datatable::{SendTable, SendTableName, ServerClass};
use crate::demo::packet::stringtable::StringTableEntry; use crate::demo::packet::stringtable::StringTableEntry;
use crate::demo::parser::analyser::Analyser; use crate::demo::parser::analyser::Analyser;
use crate::demo::parser::handler::MessageHandler; use crate::demo::parser::handler::MessageHandler;

View file

@ -2,11 +2,13 @@ use bitstream_reader::{BitRead, LittleEndian};
use enumflags2::BitFlags; use enumflags2::BitFlags;
use enumflags2_derive::EnumFlags; use enumflags2_derive::EnumFlags;
use crate::{ReadResult, Result, Stream, Parse}; use crate::{Parse, ParseError, ReadResult, Result, Stream};
use super::packet::datatable::ParseSendTable; use super::packet::datatable::ParseSendTable;
use super::vector::{Vector, VectorXY}; use super::vector::{Vector, VectorXY};
use crate::demo::message::stringtable::log_base2;
use crate::demo::packet::datatable::SendTableName; use crate::demo::packet::datatable::SendTableName;
use std::convert::TryInto;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct SendPropDefinition { pub struct SendPropDefinition {
@ -42,7 +44,8 @@ impl SendPropDefinition {
/// ///
/// Note that this is not the owner table /// Note that this is not the owner table
pub fn get_data_table<'a>(&self, tables: &'a [ParseSendTable]) -> Option<&'a ParseSendTable> { pub fn get_data_table<'a>(&self, tables: &'a [ParseSendTable]) -> Option<&'a ParseSendTable> {
self.table_name.as_ref() self.table_name
.as_ref()
.and_then(|name| tables.iter().find(|table| table.name == *name)) .and_then(|name| tables.iter().find(|table| table.name == *name))
} }
@ -200,12 +203,173 @@ pub enum SendPropValue {
Array(Vec<SendPropValue>), Array(Vec<SendPropValue>),
} }
impl SendPropValue {
pub fn parse(stream: &mut Stream, definition: &SendPropDefinition) -> Result<Self> {
match definition.prop_type {
SendPropType::Int => Self::read_int(stream, definition).map(SendPropValue::from),
SendPropType::Float => Self::read_float(stream, definition).map(SendPropValue::from),
SendPropType::String => Self::read_string(stream, definition).map(SendPropValue::from),
SendPropType::Vector => Self::read_vector(stream, definition).map(SendPropValue::from),
SendPropType::VectorXY => {
Self::read_vector_xy(stream, definition).map(SendPropValue::from)
}
SendPropType::Array => Self::read_array(stream, definition).map(SendPropValue::from),
_ => Err(ParseError::InvalidSendProp(
"Prop type not allowed in entity".to_string(),
)),
}
}
fn read_int(stream: &mut Stream, definition: &SendPropDefinition) -> Result<i32> {
if definition.flags.contains(SendPropFlag::NormalVarInt) {
read_var_int(stream, !definition.flags.contains(SendPropFlag::Unsigned))
.map_err(ParseError::from)
} else {
if definition.flags.contains(SendPropFlag::Unsigned) {
let unsigned: u32 = stream.read()?;
unsigned.try_into().map_err(|_| {
ParseError::InvalidSendProp("SendProp value out of range".to_string())
})
} else {
stream.read().map_err(ParseError::from)
}
}
}
fn read_array(
stream: &mut Stream,
definition: &SendPropDefinition,
) -> Result<Vec<SendPropValue>> {
let num_bits = log_base2(definition.element_count.unwrap_or_default());
let count = stream.read_int(num_bits as usize)?;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
let value = Self::parse(
stream,
definition
.array_property
.as_ref()
.ok_or_else(|| ParseError::InvalidSendProp("Untyped array".to_string()))?,
)?;
values.push(value);
}
Ok(values)
}
fn read_string(stream: &mut Stream, definition: &SendPropDefinition) -> Result<String> {
let length = stream.read_int(9)?;
stream.read_sized(length).map_err(ParseError::from)
}
fn read_vector(stream: &mut Stream, definition: &SendPropDefinition) -> Result<Vector> {
Ok(Vector {
x: Self::read_float(stream, definition)?,
y: Self::read_float(stream, definition)?,
z: Self::read_float(stream, definition)?,
})
}
fn read_vector_xy(stream: &mut Stream, definition: &SendPropDefinition) -> Result<VectorXY> {
Ok(VectorXY {
x: Self::read_float(stream, definition)?,
y: Self::read_float(stream, definition)?,
})
}
fn read_float(stream: &mut Stream, definition: &SendPropDefinition) -> Result<f32> {
if definition.flags.contains(SendPropFlag::Coord) {
read_bit_coord(stream).map_err(ParseError::from)
} else if definition.flags.contains(SendPropFlag::CoordMP) {
read_bit_coord_mp(stream, false, false).map_err(ParseError::from)
} else if definition.flags.contains(SendPropFlag::CoordMPLowPercision) {
read_bit_coord_mp(stream, false, true).map_err(ParseError::from)
} else if definition.flags.contains(SendPropFlag::CoordMPIntegral) {
read_bit_coord_mp(stream, true, false).map_err(ParseError::from)
} else if definition.flags.contains(SendPropFlag::NoScale) {
stream.read().map_err(ParseError::from)
} else if definition.flags.contains(SendPropFlag::NormalVarInt) {
read_bit_normal(stream).map_err(ParseError::from)
} else {
let bit_count = definition
.bit_count
.ok_or_else(|| ParseError::InvalidSendProp("Unsized float".to_string()))?;
let high = definition
.high_value
.ok_or_else(|| ParseError::InvalidSendProp("Unsized float".to_string()))?;
let low = definition
.low_value
.ok_or_else(|| ParseError::InvalidSendProp("Unsized float".to_string()))?;
let raw: u32 = stream.read_int(bit_count as usize)?;
let percentage = (raw as f32) * get_frac_factor(bit_count as usize);
Ok(low + ((high - low) * percentage))
}
}
}
impl From<i32> for SendPropValue {
fn from(value: i32) -> Self {
SendPropValue::Integer(value)
}
}
impl From<Vector> for SendPropValue {
fn from(value: Vector) -> Self {
SendPropValue::Vector(value)
}
}
impl From<VectorXY> for SendPropValue {
fn from(value: VectorXY) -> Self {
SendPropValue::VectorXY(value)
}
}
impl From<f32> for SendPropValue {
fn from(value: f32) -> Self {
SendPropValue::Float(value)
}
}
impl From<String> for SendPropValue {
fn from(value: String) -> Self {
SendPropValue::String(value)
}
}
impl From<Vec<SendPropValue>> for SendPropValue {
fn from(value: Vec<SendPropValue>) -> Self {
SendPropValue::Array(value)
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct SendProp { pub struct SendProp {
definition: SendPropDefinition, definition: SendPropDefinition,
value: SendPropValue, value: SendPropValue,
} }
pub fn read_var_int(stream: &mut Stream, signed: bool) -> ReadResult<i32> {
let mut result: i32 = 0;
for i in (0..35).step_by(7) {
let byte: u8 = stream.read()?;
result |= ((byte & 0x7F) << i) as i32;
if (byte >> 7) == 0 {
break;
}
}
if signed {
Ok((result >> 1) ^ -(result & 1))
} else {
Ok(result)
}
}
pub fn read_bit_coord(stream: &mut Stream) -> ReadResult<f32> { pub fn read_bit_coord(stream: &mut Stream) -> ReadResult<f32> {
let has_int = stream.read()?; let has_int = stream.read()?;
let has_frac = stream.read()?; let has_frac = stream.read()?;
@ -224,3 +388,54 @@ pub fn read_bit_coord(stream: &mut Stream) -> ReadResult<f32> {
0f32 0f32
}) })
} }
fn get_frac_factor(bits: usize) -> f32 {
1.0 / ((1 << bits) as f32)
}
pub fn read_bit_coord_mp(
stream: &mut Stream,
is_integral: bool,
low_precision: bool,
) -> ReadResult<f32> {
let mut value = 0.0;
let mut is_negative = false;
let in_bounds = stream.read()?;
let has_int_val = stream.read()?;
if is_integral {
if has_int_val {
is_negative = stream.read()?;
let int_val = stream.read_sized::<u32>(if in_bounds { 11 } else { 14 })? + 1;
value = int_val as f32;
}
} else {
is_negative = stream.read()?;
if has_int_val {
let int_val = stream.read_sized::<u32>(if in_bounds { 11 } else { 14 })? + 1;
value = int_val as f32;
}
let frac_bits = if low_precision { 3 } else { 5 };
let frac_val: u32 = stream.read_sized(frac_bits)?;
value += (frac_val as f32) * get_frac_factor(frac_bits);
}
if is_negative {
value = -value;
}
Ok(value)
}
pub fn read_bit_normal(stream: &mut Stream) -> ReadResult<f32> {
let is_negative = stream.read()?;
let frac_val: u16 = stream.read_sized(11)?;
let value = (frac_val as f32) * get_frac_factor(11);
if is_negative {
Ok(-value)
} else {
Ok(value)
}
}

View file

@ -1,5 +1,5 @@
use bitstream_reader::{BitRead, BitSize}; use bitstream_reader::{BitRead, BitSize};
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(BitRead, BitSize, Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] #[derive(BitRead, BitSize, Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
pub struct Vector { pub struct Vector {

View file

@ -5,11 +5,9 @@
pub use bitstream_reader::Result as ReadResult; pub use bitstream_reader::Result as ReadResult;
pub use crate::demo::{ pub use crate::demo::{
Demo, message::MessageType,
message::MessageType, parser::{ parser::{DemoParser, MatchState, MessageTypeAnalyser, Parse, ParseError, ParserState, Result},
DemoParser, MatchState, MessageTypeAnalyser, Parse, ParseError, ParserState, Result, Demo, Stream,
},
Stream,
}; };
pub mod demo; pub mod demo;

View file

@ -2,16 +2,16 @@
#![allow(unused_imports)] #![allow(unused_imports)]
#![allow(unused_variables)] #![allow(unused_variables)]
use std::fs;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::fs;
use tf_demo_parser::{Demo, DemoParser, MatchState, MessageTypeAnalyser, MessageType, ParserState}; use std::collections::{HashMap, HashSet};
use tf_demo_parser::demo::message::Message;
use tf_demo_parser::demo::packet::datatable::{ParseSendTable, SendTableName}; use tf_demo_parser::demo::packet::datatable::{ParseSendTable, SendTableName};
use tf_demo_parser::demo::packet::stringtable::StringTableEntry; use tf_demo_parser::demo::packet::stringtable::StringTableEntry;
use tf_demo_parser::demo::message::Message;
use tf_demo_parser::demo::sendprop::SendPropDefinition;
use std::collections::{HashMap, HashSet};
use tf_demo_parser::demo::parser::MessageHandler; use tf_demo_parser::demo::parser::MessageHandler;
use tf_demo_parser::demo::sendprop::SendPropDefinition;
use tf_demo_parser::{Demo, DemoParser, MatchState, MessageType, MessageTypeAnalyser, ParserState};
pub struct SendPropAnalyser; pub struct SendPropAnalyser;
@ -27,31 +27,43 @@ impl MessageHandler for SendPropAnalyser {
fn handle_string_entry(&mut self, table: &String, _index: usize, entry: &StringTableEntry) {} fn handle_string_entry(&mut self, table: &String, _index: usize, entry: &StringTableEntry) {}
fn get_output(self, state: ParserState) -> Self::Output { fn get_output(self, state: ParserState) -> Self::Output {
state.send_tables.into_iter().map(|(_k, v)| ParseSendTable { state
name: v.name, .send_tables
props: v.props, .into_iter()
needs_decoder: v.needs_decoder .map(|(_k, v)| ParseSendTable {
}).collect() name: v.name,
props: v.props,
needs_decoder: v.needs_decoder,
})
.collect()
} }
} }
fn flatten_test(input_file: &str, snapshot_file: &str) { fn flatten_test(input_file: &str, snapshot_file: &str) {
let file = fs::read(input_file).expect("Unable to read file"); let file = fs::read(input_file).expect("Unable to read file");
let demo = Demo::new(file); let demo = Demo::new(file);
let (_, send_tables) = DemoParser::parse_with_analyser(demo.get_stream(), SendPropAnalyser).unwrap(); let (_, send_tables) =
let flat_props: HashMap<SendTableName, Vec<String>> = send_tables.iter() DemoParser::parse_with_analyser(demo.get_stream(), SendPropAnalyser).unwrap();
let flat_props: HashMap<SendTableName, Vec<String>> = send_tables
.iter()
.map(|table| { .map(|table| {
( (
table.name.clone(), table.name.clone(),
table.flatten_props(&send_tables) table
.flatten_props(&send_tables)
.into_iter() .into_iter()
.map(|prop| format!("{}.{}", prop.owner_table, prop.name)) .map(|prop| format!("{}.{}", prop.owner_table, prop.name))
.collect() .collect(),
) )
}) })
.collect(); .collect();
let expected: HashMap<SendTableName, Vec<String>> = serde_json::from_slice(fs::read(snapshot_file).expect("Unable to read file").as_slice()).unwrap(); let expected: HashMap<SendTableName, Vec<String>> = serde_json::from_slice(
fs::read(snapshot_file)
.expect("Unable to read file")
.as_slice(),
)
.unwrap();
let expected_tables: HashSet<_> = expected.keys().collect(); let expected_tables: HashSet<_> = expected.keys().collect();
let actual_tables: HashSet<_> = flat_props.keys().collect(); let actual_tables: HashSet<_> = flat_props.keys().collect();

View file

@ -1,30 +1,42 @@
use std::fs;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::fs;
use tf_demo_parser::{Demo, DemoParser, MatchState, MessageTypeAnalyser, MessageType}; use tf_demo_parser::{Demo, DemoParser, MatchState, MessageType, MessageTypeAnalyser};
fn snapshot_test(input_file: &str, snapshot_file: &str) { fn snapshot_test(input_file: &str, snapshot_file: &str) {
let file = fs::read(input_file).expect("Unable to read file"); let file = fs::read(input_file).expect("Unable to read file");
let demo = Demo::new(file); let demo = Demo::new(file);
let (_, state) = DemoParser::parse_demo(demo.get_stream()).unwrap(); let (_, state) = DemoParser::parse_demo(demo.get_stream()).unwrap();
let expected: MatchState = serde_json::from_slice(fs::read(snapshot_file).expect("Unable to read file").as_slice()).unwrap(); let expected: MatchState = serde_json::from_slice(
fs::read(snapshot_file)
.expect("Unable to read file")
.as_slice(),
)
.unwrap();
assert_eq!(expected, state); assert_eq!(expected, state);
} }
fn test_message_types(input_file: &str, snapshot_file: &str) { fn test_message_types(input_file: &str, snapshot_file: &str) {
let file = fs::read(input_file).expect("Unable to read file"); let file = fs::read(input_file).expect("Unable to read file");
let demo = Demo::new(file); let demo = Demo::new(file);
let (_, message_types) = DemoParser::parse_with_analyser(demo.get_stream(), MessageTypeAnalyser::new()).unwrap(); let (_, message_types) =
DemoParser::parse_with_analyser(demo.get_stream(), MessageTypeAnalyser::new()).unwrap();
let expected: Vec<MessageType> = serde_json::from_slice(fs::read(snapshot_file).expect("Unable to read file").as_slice()).unwrap(); let expected: Vec<MessageType> = serde_json::from_slice(
fs::read(snapshot_file)
.expect("Unable to read file")
.as_slice(),
)
.unwrap();
assert_eq!(expected, message_types); assert_eq!(expected, message_types);
} }
fn dump_message_types(input_file: &str, snapshot_file: &str) { fn dump_message_types(input_file: &str, snapshot_file: &str) {
let file = fs::read(input_file).expect("Unable to read file"); let file = fs::read(input_file).expect("Unable to read file");
let demo = Demo::new(file); let demo = Demo::new(file);
let (_, message_types) = DemoParser::parse_with_analyser(demo.get_stream(), MessageTypeAnalyser::new()).unwrap(); let (_, message_types) =
DemoParser::parse_with_analyser(demo.get_stream(), MessageTypeAnalyser::new()).unwrap();
fs::write(snapshot_file, serde_json::to_vec(&message_types).unwrap()).unwrap(); fs::write(snapshot_file, serde_json::to_vec(&message_types).unwrap()).unwrap();
} }
@ -44,7 +56,6 @@ fn snapshot_test_comp() {
snapshot_test("data/comp.dem", "data/comp.json"); snapshot_test("data/comp.dem", "data/comp.json");
} }
#[test] #[test]
fn snapshot_test_malformed_cvar() { fn snapshot_test_malformed_cvar() {
snapshot_test("data/malformed_cvar.dem", "data/malformed_cvar.json"); snapshot_test("data/malformed_cvar.dem", "data/malformed_cvar.json");