Skip to content

Commit

Permalink
Parse entities.
Browse files Browse the repository at this point in the history
  • Loading branch information
abenea committed Feb 4, 2024
1 parent e44b38d commit b2dcad4
Show file tree
Hide file tree
Showing 21 changed files with 1,499 additions and 278 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cs2-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
bitstream-io.workspace = true
demo-format = { path = "../demo-format" }
protobuf = { version = "3.2.0", features = ["with-bytes"] }
serde = { version = "1.0" }
snap = "1.1"
thiserror = "1.0"
tracing = "0.1"
paste = "1.0"
smallvec = "1.13.1"

[build-dependencies]
protobuf-codegen = "3.2"
24 changes: 24 additions & 0 deletions cs2-demo/proto/netmessages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,27 @@ message CSVCMsg_PacketEntities {
optional .CSVCMsg_PacketEntities.command_queue_info_t command_queue_info = 14;
repeated .CSVCMsg_PacketEntities.alternate_baseline_t alternate_baselines = 15;
}

message CSVCMsg_CreateStringTable {
optional string name = 1;
optional int32 num_entries = 2;
optional bool user_data_fixed_size = 3;
optional int32 user_data_size = 4;
optional int32 user_data_size_bits = 5;
optional int32 flags = 6;
optional bytes string_data = 7;
optional int32 uncompressed_size = 8;
optional bool data_compressed = 9;
optional bool using_varint_bitcounts = 10;
}

message CSVCMsg_UpdateStringTable {
optional int32 table_id = 1;
optional int32 num_changed_entries = 2;
optional bytes string_data = 3;
}

message CSVCMsg_ClearAllStringTables {
optional string mapname = 1;
optional bool create_tables_skipped = 3;
}
14 changes: 5 additions & 9 deletions cs2-demo/src/demo_command.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use demo_format::Tick;
use protobuf::CodedInputStream;
use protobuf::Message;
use std::fmt;
Expand All @@ -8,7 +7,7 @@ use crate::proto::demo::{
CDemoClassInfo, CDemoFileHeader, CDemoFullPacket, CDemoPacket, CDemoSendTables,
CDemoStringTables, EDemoCommands,
};
use crate::string_table::{parse_string_tables, StringTable};
use crate::Tick;
use crate::{Error, Result};

#[derive(Debug)]
Expand All @@ -23,13 +22,13 @@ pub enum DemoCommand {
SyncTick,
SendTables(CDemoSendTables),
ClassInfo(CDemoClassInfo),
StringTables(Vec<StringTable>),
StringTables(CDemoStringTables),
Packet(Packet),
ConsoleCmd,
CustomData,
CustomDataCallbacks,
UserCmd,
FullPacket(Vec<StringTable>, Packet),
FullPacket(CDemoStringTables, Packet),
SaveGame,
SpawnGroups,
AnimationData,
Expand All @@ -44,9 +43,7 @@ impl DemoCommand {
3 => DemoCommand::SyncTick,
4 => DemoCommand::SendTables(CDemoSendTables::parse_from_bytes(data)?),
5 => DemoCommand::ClassInfo(CDemoClassInfo::parse_from_bytes(data)?),
6 => DemoCommand::StringTables(parse_string_tables(
CDemoStringTables::parse_from_bytes(data)?,
)?),
6 => DemoCommand::StringTables(CDemoStringTables::parse_from_bytes(data)?),
// SignonPacket seems to be identical to Packet.
7 | 8 => DemoCommand::Packet(Packet::try_new(CDemoPacket::parse_from_bytes(data)?)?),
9 => DemoCommand::ConsoleCmd,
Expand All @@ -55,8 +52,7 @@ impl DemoCommand {
12 => DemoCommand::UserCmd,
13 => {
let mut fp = CDemoFullPacket::parse_from_bytes(data)?;
let string_tables =
parse_string_tables(fp.string_table.take().ok_or(Error::MissingStringTable)?)?;
let string_tables = fp.string_table.take().ok_or(Error::MissingStringTable)?;
let packet = Packet::try_new(fp.packet.take().ok_or(Error::MissingPacket)?)?;
DemoCommand::FullPacket(string_tables, packet)
}
Expand Down
180 changes: 136 additions & 44 deletions cs2-demo/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@ mod send_tables;
use std::rc::Rc;

use bitstream_io::BitRead;
use demo_format::read::ValveBitReader;
use demo_format::BitReader;
use tracing::trace;

use self::fieldpath::FieldPath;
use self::send_tables::{Field, Serializer};
use crate::proto::netmessages::CSVCMsg_PacketEntities;
use crate::read::ValveBitReader;
use crate::BitReader;
use crate::{Error, Result};

pub use self::class::Classes;
pub use self::property::{Properties, Property};
pub use self::property::{Object, Property};
pub use self::send_tables::SendTables;

#[derive(Default)]
pub struct Entities {
entities: Vec<Option<Entity>>,
pub entities: Vec<Option<Entity>>,
/// Only used by read_props to avoid allocations.
field_paths: Vec<FieldPath>,
}

impl Entities {
Expand All @@ -42,25 +45,25 @@ impl Entities {
match (remove, new) {
(false, false) => {
if let Some(entity) = self.entities[entity_id as usize].as_mut() {
entity.read_props(&mut reader)?;
entity.read_props(&mut reader, &mut self.field_paths)?;
} else {
return Err(Error::InvalidEntityId);
}
}
(false, true) => {
let class_id = reader.read::<u32>(classes.class_id_bits)?;
let _serial = reader.read::<u32>(17)?;
reader.read_varint32()?; // Don't know what this is.

// TODO: read baseline
println!("== new entity {entity_id} class={class_id}");
let serializer = Rc::clone(&classes.class(class_id).serializer);
let mut entity = Entity::new(serializer);
entity.read_props(&mut reader)?;
reader.read_varuint32()?; // Don't know what this is.
let class = classes.class(class_id);
let mut entity = Entity::new(Rc::clone(&class.serializer));
if let Some(baseline) = &class.instance_baseline {
entity.read_props(&mut BitReader::new(baseline), &mut self.field_paths)?;
};
entity.read_props(&mut reader, &mut self.field_paths)?;
self.entities[entity_id as usize] = Some(entity);
}
(true, _) => {
todo!()
self.entities[entity_id as usize] = None;
}
};
}
Expand All @@ -69,51 +72,85 @@ impl Entities {
}

pub struct Entity {
object: Object,
serializer: Rc<Serializer>,
properties: Properties,
}

impl Entity {
fn new(serializer: Rc<Serializer>) -> Self {
let properties = vec![None; serializer.fields.len()];
Self {
object: Object::new(&serializer),
serializer,
properties,
}
}

pub fn get_property(&self, fp: &[i32]) -> (Option<&Property>, &Field, PathName) {
let prop = self.object.properties[fp[0] as usize].as_ref();
let field = &self.serializer.fields[fp[0] as usize];
let name = PathName {
items: vec![PathNameItem::Field(field.name())],
};
fp[1..]
.iter()
.fold((prop, field, name), |(prop, field, name), &i| {
let i = i as usize;
let is_array = matches!(field, Field::Array(_) | Field::Vector(_));
let (prop, field) = match (prop, field) {
(Some(Property::Object(o)), Field::Object(f)) => {
(o.properties[i].as_ref(), &f.serializer.fields[i])
}
(Some(Property::Array(a)), Field::Array(f)) => {
(a[i].as_ref(), f.element.as_ref())
}
(Some(Property::Array(a)), Field::Vector(f)) => {
(a[i].as_ref(), f.element.as_ref())
}
(None, f) => (None, f),
(Some(p), f) => unreachable!("{p:?} {f:?}"),
};
let name = if is_array {
name.push_index(i)
} else {
name.push_field(field.name())
};
(prop, field, name)
})
}

fn property(&mut self, fp: &[i32]) -> (&mut Option<Property>, &Field) {
let mut properties = &mut self.properties;
let mut fields = &self.serializer.fields;
for i in 0..fp.len() {
let prop = &mut properties[fp[i] as usize];
let field = &fields[fp[i] as usize];
println!("{field:?}");
match &field.serializer {
Some(ser) => {
let prop =
prop.get_or_insert_with(|| Property::Object(vec![None; ser.fields.len()]));
match prop {
Property::Object(o) => {
properties = o;
fields = &ser.fields;
}
let prop = &mut self.object.properties[fp[0] as usize];
let field = &self.serializer.fields[fp[0] as usize];
fp[1..]
.iter()
.fold((prop, field), |(prop, field), &i| match (prop, field) {
(Some(Property::Object(o)), Field::Object(f)) => (
&mut o.properties[i as usize],
&f.serializer.fields[i as usize],
),
(Some(Property::Array(a)), Field::Array(f)) => {
(&mut a[i as usize], f.element.as_ref())
}
(Some(Property::Array(a)), Field::Vector(f)) => {
(&mut a[i as usize], f.element.as_ref())
}
(Some(p), f) => unreachable!("{p:?} {f:?}"),
(p, Field::Array(f)) if p.is_none() => {
*p = Some(Property::Array(
vec![None; f.size as usize].into_boxed_slice(),
));
match p {
Some(Property::Array(a)) => (&mut a[i as usize], &f.element.as_ref()),
_ => unreachable!(),
}
}
None => {
assert!(i == fp.len() - 1);
return (prop, field);
}
}
}
unreachable!()
(None, f) => unreachable!("{f:?}"),
})
}

/// Read props from `reader`, creating new props or overwriting existing ones.
fn read_props(&mut self, reader: &mut BitReader) -> Result<()> {
fn read_props(&mut self, reader: &mut BitReader, fps: &mut Vec<FieldPath>) -> Result<()> {
let mut fp = FieldPath::new();
let mut fps = Vec::with_capacity(256);
fps.clear();
loop {
fp.read(reader)?;
if fp.finished {
Expand All @@ -123,9 +160,57 @@ impl Entity {
}
for fp in fps {
let (prop, field) = self.property(&fp.data);
println!("{fp} {}: {}", field.var_name, field.var_type);
*prop = Some((field.decoder)(reader)?);
println!("{fp} {} = {:?}", field.var_name, prop.as_ref().unwrap());
*prop = (field.decoder())(reader)?;

if false {
let (prop, field, name) = self.get_property(&fp.data);
match field {
Field::Value(_) | Field::Array(_) | Field::Vector(_) => {
trace!("{fp} {}: {} = {}", name, field.ctype(), prop.unwrap())
}
Field::Object(_) => {
trace!("{fp} {}: {} = {}", name, field.ctype(), prop.is_some())
}
}
}
}
Ok(())
}
}

enum PathNameItem {
Field(Rc<str>),
Index(usize),
}

pub struct PathName {
items: Vec<PathNameItem>,
}

impl PathName {
fn push_field(mut self, field: Rc<str>) -> Self {
self.items.push(PathNameItem::Field(field));
self
}

fn push_index(mut self, index: usize) -> Self {
self.items.push(PathNameItem::Index(index));
self
}
}

impl std::fmt::Display for PathName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (idx, item) in self.items.iter().enumerate() {
match item {
PathNameItem::Field(field) => {
if idx > 0 {
write!(f, ".")?
}
write!(f, "{field}")?;
}
PathNameItem::Index(index) => write!(f, ".{index:04}")?,
}
}
Ok(())
}
Expand All @@ -139,7 +224,14 @@ mod tests {
#[test]
fn test() -> Result<()> {
let send_tables = SendTables::try_new(testdata::send_tables())?;
let classes = Classes::try_new(testdata::class_info(), send_tables)?;
let mut classes = Classes::try_new(testdata::class_info(), send_tables)?;
for table in testdata::string_tables().tables {
if table.table_name() == "instancebaseline" {
let items = table.items.into_iter().map(|mut e| (e.take_str(), e.take_data())).collect();
classes.update_instance_baselines(items);
}
}

let mut entities = Entities::default();
entities.read_packet_entities(testdata::packet_entities(), &classes)?;
Ok(())
Expand Down
Loading

0 comments on commit b2dcad4

Please sign in to comment.