Skip to content

Commit

Permalink
Parse PacketEntities wip
Browse files Browse the repository at this point in the history
  • Loading branch information
abenea committed Jan 29, 2024
1 parent 0e4829d commit 9694b8f
Show file tree
Hide file tree
Showing 17 changed files with 1,136 additions and 46 deletions.
31 changes: 31 additions & 0 deletions cs2-demo/proto/netmessages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,34 @@ message CSVCMsg_FlattenedSerializer {
repeated string symbols = 2;
repeated .ProtoFlattenedSerializerField_t fields = 3;
}

message CSVCMsg_PacketEntities {
message command_queue_info_t {
optional uint32 commands_queued = 1;
optional uint32 command_queue_desired_size = 2;
optional uint32 starved_command_ticks = 3;
optional float time_dilation_percent = 4;
optional uint32 discarded_command_ticks = 5;
}

message alternate_baseline_t {
optional int32 entity_index = 1;
optional int32 baseline_index = 2;
}

optional int32 max_entries = 1;
optional int32 updated_entries = 2;
optional bool is_delta = 3;
optional bool update_baseline = 4;
optional int32 baseline = 5;
optional int32 delta_from = 6;
optional bytes entity_data = 7;
optional bool pending_full_frame = 8;
optional uint32 active_spawngroup_handle = 9;
optional uint32 max_spawngroup_creationsequence = 10;
optional uint32 last_cmd_number = 11;
optional uint32 server_tick = 12;
optional bytes serialized_entities = 13;
optional .CSVCMsg_PacketEntities.command_queue_info_t command_queue_info = 14;
repeated .CSVCMsg_PacketEntities.alternate_baseline_t alternate_baselines = 15;
}
13 changes: 5 additions & 8 deletions cs2-demo/src/demo_command.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use super::packet::Packet;
use super::proto::demo::{CDemoFileHeader, CDemoPacket, CDemoSendTables};
use super::send_tables::SendTables;
use super::{Error, Result};
use crate::proto::demo::{CDemoFullPacket, CDemoStringTables};
use crate::proto::demo::{CDemoClassInfo, CDemoFullPacket, CDemoStringTables};
use crate::string_table::{parse_string_tables, StringTable};
use protobuf::Message;
use std::fmt;
Expand All @@ -17,8 +16,8 @@ pub enum DemoCommand {
FileInfo,
/// A sync tick. It contains no data.
SyncTick,
SendTables(SendTables),
ClassInfo,
SendTables(CDemoSendTables),
ClassInfo(CDemoClassInfo),
StringTables(Vec<StringTable>),
Packet(Packet),
ConsoleCmd,
Expand All @@ -38,10 +37,8 @@ impl DemoCommand {
1 => DemoCommand::FileHeader(CDemoFileHeader::parse_from_bytes(data)?),
2 => DemoCommand::FileInfo,
3 => DemoCommand::SyncTick,
4 => DemoCommand::SendTables(SendTables::try_new(CDemoSendTables::parse_from_bytes(
data,
)?)?),
5 => DemoCommand::ClassInfo,
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)?,
)?),
Expand Down
149 changes: 149 additions & 0 deletions cs2-demo/src/entity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
mod class;
mod decoder;
mod fieldpath;
mod property;
mod send_tables;

use std::rc::Rc;

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

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

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

#[derive(Default)]
pub struct Entities {
entities: Vec<Option<Entity>>,
}

impl Entities {
pub fn read_packet_entities(
&mut self,
msg: CSVCMsg_PacketEntities,
classes: &Classes,
) -> Result<()> {
let mut next_entity_id = 0;
let mut reader = BitReader::new(msg.entity_data());
let max_entries = msg.max_entries() as usize;
self.entities.resize_with(max_entries, || None);
for _ in 0..msg.updated_entries() {
let entity_id = next_entity_id + reader.read_ubitvar()?;
next_entity_id = entity_id + 1;
let remove = reader.read_bit()?;
let new = reader.read_bit()?;
match (remove, new) {
(false, false) => {
if let Some(entity) = self.entities[entity_id as usize].as_mut() {
entity.read_props(&mut reader)?;
} 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)?;
self.entities[entity_id as usize] = Some(entity);
}
(true, _) => {
todo!()
}
};
}
Ok(())
}
}

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

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

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;
}
_ => unreachable!(),
}
}
None => {
assert!(i == fp.len() - 1);
return (prop, field);
}
}
}
unreachable!()
}

/// Read props from `reader`, creating new props or overwriting existing ones.
fn read_props(&mut self, reader: &mut BitReader) -> Result<()> {
let mut fp = FieldPath::new();
let mut fps = Vec::with_capacity(256);
loop {
fp.read(reader)?;
if fp.finished {
break;
}
fps.push(fp.clone());
}
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());
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use std::rc::Rc;

use super::*;
use crate::testdata;

#[test]
fn test() -> Result<()> {
let send_tables = SendTables::try_new(testdata::send_tables())?;
let classes = Classes::try_new(testdata::class_info(), Rc::new(send_tables))?;
let mut entities = Entities::default();
entities.read_packet_entities(testdata::packet_entities(), &classes)?;
Ok(())
}
}
76 changes: 76 additions & 0 deletions cs2-demo/src/entity/class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::collections::HashMap;
use std::rc::Rc;

use super::send_tables::Serializer;
use super::SendTables;
use crate::proto::demo::{cdemo_class_info, CDemoClassInfo};
use crate::{Error, Result};

type ClassId = u32;

#[derive(Debug)]
pub(super) struct Class {
class_id: ClassId,
pub(super) serializer: Rc<Serializer>,
}

impl Class {
fn try_new(
msg: &cdemo_class_info::Class_t,
serializers: &HashMap<String, Rc<Serializer>>,
) -> Result<Self> {
let name = msg.network_name.as_ref().ok_or(Error::MissingClassName)?;
let class_id = msg.class_id.ok_or(Error::MissingClassId)? as ClassId;
Ok(Self {
class_id,
serializer: Rc::clone(&serializers[&name.clone()]),
})
}
}

#[derive(Debug, Default)]
pub struct Classes {
classes: Vec<Class>,
pub(super) class_id_bits: u32,
}

impl Classes {
pub fn try_new(msg: CDemoClassInfo, send_tables: Rc<SendTables>) -> Result<Self> {
let serializers = send_tables
.serializers
.iter()
.map(|s| (s.name.clone(), Rc::clone(s)))
.collect::<HashMap<_, _>>();
let classes = msg
.classes
.iter()
.map(|m| Class::try_new(m, &serializers))
.collect::<Result<Vec<_>>>()?;
for (i, c) in classes.iter().enumerate() {
if i as ClassId != c.class_id {
return Err(Error::SkippedClassId);
}
}
let class_id_bits = u32::BITS - (classes.len() as u32).leading_zeros();
Ok(Classes {
classes,
class_id_bits,
})
}

pub(super) fn class(&self, class_id: ClassId) -> &Class {
&self.classes[class_id as usize]
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::testdata;

#[test]
fn test() {
let send_tables = SendTables::try_new(testdata::send_tables()).unwrap();
Classes::try_new(testdata::class_info(), Rc::new(send_tables)).unwrap();
}
}
Loading

0 comments on commit 9694b8f

Please sign in to comment.