diff --git a/Cargo.lock b/Cargo.lock index 4bc1bbe..6741c65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,12 +47,20 @@ version = "0.1.0" dependencies = [ "bitter", "phf", + "pretty_assertions", "prost", "prost-build", "prost-types", + "regex", "snap", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "either" version = "1.13.0" @@ -206,6 +214,16 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.22" @@ -467,3 +485,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index 6c60385..b874784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,10 @@ snap = "1.1.1" bitter = "0.7" phf = { version = "0.11", features = ["macros"] } +regex = "1.10.6" + +[dev-dependencies] +pretty_assertions = { version = "1.4" } [build-dependencies] prost-build = { version = "0.13.2" } diff --git a/src/huf.b b/src/huf.b new file mode 100644 index 0000000..52322f7 Binary files /dev/null and b/src/huf.b differ diff --git a/src/parser.rs b/src/parser.rs index 89d9a4e..f19723e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,12 @@ use crate::{packet::DemoEvent, DemoCommand, Frame, UserId}; +mod fieldpath; +pub use fieldpath::{FieldPath, Paths}; + +mod decoder; +mod sendtables; +mod variant; + #[derive(Debug)] pub enum FirstPassError { DecompressFrame, @@ -35,6 +42,11 @@ pub struct Player { pub color: i32, } +#[derive(Debug)] +pub struct Entity { + pub cls: u32, +} + #[derive(Debug)] pub struct FirstPassOutput { pub header: crate::csgo_proto::CDemoFileHeader, @@ -54,6 +66,13 @@ struct GameEventMapping { >, } +#[derive(Debug)] +pub struct Class { + class_id: i32, + name: String, + serializer: sendtables::Serializer, +} + pub fn parse<'b, FI>(frames: FI) -> Result where FI: IntoIterator>, @@ -66,6 +85,16 @@ where mapping: std::collections::HashMap::new(), }; let mut player_info = std::collections::HashMap::new(); + let mut entities = std::collections::HashMap::new(); + let mut cls_to_class = std::collections::HashMap::::new(); + let mut paths = Paths::new(); + let mut qf_mapper = decoder::QfMapper { + idx: 0, + map: std::collections::HashMap::new(), + }; + let mut serializers = std::collections::HashMap::new(); + + let mut baselines = std::collections::HashMap::new(); for mut frame in frames.into_iter() { frame @@ -83,15 +112,86 @@ where file_info = Some(raw); } DemoCommand::SignonPacket | DemoCommand::Packet => { - parse_packet(data, &mut events, &mut event_mapping, &mut player_info)?; + parse_packet( + data, + &mut events, + &mut event_mapping, + &mut player_info, + &mut entities, + &mut cls_to_class, + &mut paths, + &mut qf_mapper, + &mut baselines, + )?; } DemoCommand::FullPacket => { - parse_fullpacket(data, &mut events, &mut event_mapping, &mut player_info)?; + parse_fullpacket( + data, + &mut events, + &mut event_mapping, + &mut player_info, + &mut entities, + &mut cls_to_class, + &mut paths, + &mut qf_mapper, + &mut baselines, + )?; } // TODO DemoCommand::AnimationData => {} DemoCommand::AnimationHeader => {} - _ => {} + DemoCommand::StringTables => { + let raw: crate::csgo_proto::CDemoStringTables = prost::Message::decode(data)?; + + for table in raw.tables.iter() { + if table.table_name() == "instancebaseline" { + for item in table.items.iter() { + let k = item.str().parse::().unwrap_or(u32::MAX); + baselines.insert(k, item.data().to_vec()); + } + } + } + } + DemoCommand::SendTables => { + let tables: crate::csgo_proto::CDemoSendTables = prost::Message::decode(data)?; + + let mut bitreader = crate::bitreader::Bitreader::new(tables.data()); + + let n_bytes = bitreader.read_varint()?; + let bytes = bitreader.read_n_bytes(n_bytes as usize)?; + + let serializer_msg: crate::csgo_proto::CsvcMsgFlattenedSerializer = + prost::Message::decode(bytes.as_slice())?; + + // std::fs::write("send_table.b", bytes.as_slice()); + + assert!(serializers.is_empty()); + serializers = sendtables::get_serializers(&serializer_msg, &mut qf_mapper)?; + } + DemoCommand::ClassInfo => { + let raw: crate::csgo_proto::CDemoClassInfo = prost::Message::decode(data)?; + + cls_to_class.clear(); + + for class_t in raw.classes { + let cls_id = class_t.class_id(); + let network_name = class_t.network_name(); + + if let Some(ser) = serializers.remove(network_name) { + cls_to_class.insert( + cls_id as u32, + Class { + name: network_name.to_owned(), + class_id: cls_id, + serializer: ser, + }, + ); + } + } + } + other => { + dbg!(other); + } } } @@ -111,15 +211,33 @@ fn parse_fullpacket( events: &mut Vec, event_mapper: &mut GameEventMapping, player_info: &mut std::collections::HashMap, + entities: &mut std::collections::HashMap, + cls_to_class: &mut std::collections::HashMap, + paths: &mut Paths, + qf_mapper: &mut decoder::QfMapper, + baselines: &mut std::collections::HashMap>, ) -> Result<(), FirstPassError> { let raw: crate::csgo_proto::CDemoFullPacket = prost::Message::decode(data)?; // TODO // Handle string table stuff + for item in raw.string_table.iter().flat_map(|st| st.tables.iter()) { + // dbg!(&item.table_name); + } match raw.packet { Some(packet) => { - inner_parse_packet(&packet, events, event_mapper, player_info)?; + inner_parse_packet( + &packet, + events, + event_mapper, + player_info, + entities, + cls_to_class, + paths, + qf_mapper, + baselines, + )?; Ok(()) } @@ -132,10 +250,25 @@ fn parse_packet( events: &mut Vec, event_mapper: &mut GameEventMapping, player_info: &mut std::collections::HashMap, + entities: &mut std::collections::HashMap, + cls_to_class: &mut std::collections::HashMap, + paths: &mut Paths, + qf_mapper: &mut decoder::QfMapper, + baselines: &mut std::collections::HashMap>, ) -> Result<(), FirstPassError> { let raw: crate::csgo_proto::CDemoPacket = prost::Message::decode(data)?; - inner_parse_packet(&raw, events, event_mapper, player_info)?; + inner_parse_packet( + &raw, + events, + event_mapper, + player_info, + entities, + cls_to_class, + paths, + qf_mapper, + baselines, + )?; Ok(()) } @@ -145,6 +278,11 @@ fn inner_parse_packet( events: &mut Vec, event_mapper: &mut GameEventMapping, player_info: &mut std::collections::HashMap, + entities: &mut std::collections::HashMap, + cls_to_class: &mut std::collections::HashMap, + paths: &mut Paths, + qf_mapper: &mut decoder::QfMapper, + baselines: &mut std::collections::HashMap>, ) -> Result<(), FirstPassError> { let mut bitreader = crate::bitreader::Bitreader::new(raw.data()); @@ -153,6 +291,8 @@ fn inner_parse_packet( let size = bitreader.read_varint()?; let msg_bytes = bitreader.read_n_bytes(size as usize)?; + assert_eq!(msg_bytes.len(), size as usize); + let net_msg_type = match crate::netmessagetypes::NetmessageType::try_from(msg_type as i32) { Ok(v) => v, Err(e) => { @@ -196,7 +336,70 @@ fn inner_parse_packet( crate::netmessagetypes::NetmessageType::net_SetConVar => {} crate::netmessagetypes::NetmessageType::svc_ClassInfo => {} crate::netmessagetypes::NetmessageType::svc_VoiceInit => {} - crate::netmessagetypes::NetmessageType::svc_PacketEntities => {} + crate::netmessagetypes::NetmessageType::svc_PacketEntities => { + let raw: crate::csgo_proto::CsvcMsgPacketEntities = + prost::Message::decode(msg_bytes.as_slice())?; + + let mut bitreader = crate::bitreader::Bitreader::new(raw.entity_data()); + let mut entity_id: i32 = -1; + for _ in 0..raw.updated_entries() { + entity_id += 1 + (bitreader.read_u_bit_var()? as i32); + + match bitreader.read_nbits(2)? { + 0b01 | 0b11 => { + entities.remove(&entity_id); + } + 0b10 => { + let (id, entity) = create_entity(entity_id, &mut bitreader, baselines)?; + let cls = entity.cls; + + entities.insert(entity_id, entity); + if let Some(baseline_bytes) = baselines.get(&cls) { + let mut br = crate::bitreader::Bitreader::new(&baseline_bytes); + update_entity( + entity_id, + &mut br, + entities, + cls_to_class, + paths, + qf_mapper, + )?; + } + + + update_entity( + entity_id, + &mut bitreader, + entities, + cls_to_class, + paths, + qf_mapper, + )?; + } + 0b00 => { + if raw.has_pvs_vis_bits() > 0 { + if bitreader.read_nbits(2)? & 0x01 == 1 { + continue; + } + } + + update_entity( + entity_id, + &mut bitreader, + entities, + cls_to_class, + paths, + qf_mapper, + )?; + } + unknown => { + panic!("{:?}", unknown); + } + }; + } + + // dbg!("PacketEntities"); + } crate::netmessagetypes::NetmessageType::svc_UserCmds => {} crate::netmessagetypes::NetmessageType::GE_SosStartSoundEvent => {} crate::netmessagetypes::NetmessageType::GE_SosStopSoundEvent => {} @@ -243,7 +446,11 @@ fn inner_parse_packet( crate::netmessagetypes::NetmessageType::CS_UM_RadioText => {} crate::netmessagetypes::NetmessageType::TE_WorldDecal => {} crate::netmessagetypes::NetmessageType::TE_EffectDispatch => {} - crate::netmessagetypes::NetmessageType::CS_UM_PlayerStatsUpdate => {} + crate::netmessagetypes::NetmessageType::CS_UM_PlayerStatsUpdate => { + let raw: crate::csgo_proto::CcsUsrMsgPlayerStatsUpdate = + prost::Message::decode(msg_bytes.as_slice())?; + // dbg!(&raw); + } crate::netmessagetypes::NetmessageType::CS_UM_EndOfMatchAllPlayersData => { let raw: crate::csgo_proto::CcsUsrMsgEndOfMatchAllPlayersData = prost::Message::decode(msg_bytes.as_slice())?; @@ -274,3 +481,143 @@ fn inner_parse_packet( Ok(()) } + +fn create_entity( + entity_id: i32, + bitreader: &mut crate::bitreader::Bitreader, + baselines: &mut std::collections::HashMap>, +) -> Result<(i32, Entity), FirstPassError> { + let cls_id: u32 = bitreader.read_nbits(8)?; + let _serial = bitreader.read_nbits(17)?; + let _unknown = bitreader.read_varint()?; + + Ok((entity_id, Entity { cls: cls_id })) +} + +fn update_entity( + entity_id: i32, + bitreader: &mut crate::bitreader::Bitreader, + entities: &mut std::collections::HashMap, + cls_to_class: &mut std::collections::HashMap, + paths: &mut Paths, + qf_mapper: &mut decoder::QfMapper, +) -> Result<(), FirstPassError> { + let n_updates = fieldpath::parse_paths(bitreader, paths)?; + let n_updated_values = decode_entity_update( + entity_id, + bitreader, + n_updates, + entities, + cls_to_class, + paths, + qf_mapper, + )?; + if n_updated_values > 0 { + gather_extra_info()?; + } + + Ok(()) +} + +fn gather_extra_info() -> Result<(), FirstPassError> { + // TODO + + Ok(()) +} + +fn decode_entity_update( + entity_id: i32, + bitreader: &mut crate::bitreader::Bitreader, + n_updates: usize, + entities: &mut std::collections::HashMap, + cls_to_class: &mut std::collections::HashMap, + paths: &mut Paths, + qf_mapper: &mut decoder::QfMapper, +) -> Result { + let entity = match entities.get_mut(&entity_id) { + Some(e) => e, + None => panic!("ID: {:?} - Entities: {:?}", entity_id, entities), + }; + let class = match cls_to_class.get_mut(&entity.cls) { + Some(c) => c, + None => panic!(), + }; + + // dbg!(&class.name); + for path in paths.paths().take(n_updates) { + // dbg!(&path); + + let field = path.find(&class.serializer)?; + let field_info = field.get_propinfo(path); + let decoder = field.get_decoder()?; + let result = decoder.decode(bitreader, qf_mapper)?; + + // dbg!(&field, &field_info, &decoder, &result); + + if let Some(fi) = field_info { + // dbg!(&fi); + } + } + + Ok(n_updates) +} + +static HUFFMAN_LOOKUP_TABLE: std::sync::LazyLock> = std::sync::LazyLock::new(|| { + let buf = include_bytes!("huf.b"); + let mut huf2 = Vec::with_capacity((1 << 17) - 1); + for chunk in buf.chunks_exact(2) { + huf2.push((chunk[0], chunk[1])); + } + huf2 +}); + +fn do_op( + symbol: u8, + bitreader: &mut crate::bitreader::Bitreader, + field_path: &mut FieldPath, +) -> Result<(), FirstPassError> { + use fieldpath::ops::*; + + match symbol { + 0 => plus_one(bitreader, field_path), + 1 => plus_two(bitreader, field_path), + 2 => plus_three(bitreader, field_path), + 3 => plus_four(bitreader, field_path), + 4 => plus_n(bitreader, field_path), + 5 => push_one_left_delta_zero_right_zero(bitreader, field_path), + 6 => push_one_left_delta_zero_right_non_zero(bitreader, field_path), + 7 => push_one_left_delta_one_right_zero(bitreader, field_path), + 8 => push_one_left_delta_one_right_non_zero(bitreader, field_path), + 9 => push_one_left_delta_n_right_zero(bitreader, field_path), + 10 => push_one_left_delta_n_right_non_zero(bitreader, field_path), + 11 => push_one_left_delta_n_right_non_zero_pack6_bits(bitreader, field_path), + 12 => push_one_left_delta_n_right_non_zero_pack8_bits(bitreader, field_path), + 13 => push_two_left_delta_zero(bitreader, field_path), + 14 => push_two_pack5_left_delta_zero(bitreader, field_path), + 15 => push_three_left_delta_zero(bitreader, field_path), + 16 => push_three_pack5_left_delta_zero(bitreader, field_path), + 17 => push_two_left_delta_one(bitreader, field_path), + 18 => push_two_pack5_left_delta_one(bitreader, field_path), + 19 => push_three_left_delta_one(bitreader, field_path), + 20 => push_three_pack5_left_delta_one(bitreader, field_path), + 21 => push_two_left_delta_n(bitreader, field_path), + 22 => push_two_pack5_left_delta_n(bitreader, field_path), + 23 => push_three_left_delta_n(bitreader, field_path), + 24 => push_three_pack5_left_delta_n(bitreader, field_path), + 25 => push_n(bitreader, field_path), + 26 => push_n_and_non_topological(bitreader, field_path), + 27 => pop_one_plus_one(bitreader, field_path), + 28 => pop_one_plus_n(bitreader, field_path), + 29 => pop_all_but_one_plus_one(bitreader, field_path), + 30 => pop_all_but_one_plus_n(bitreader, field_path), + 31 => pop_all_but_one_plus_n_pack3_bits(bitreader, field_path), + 32 => pop_all_but_one_plus_n_pack6_bits(bitreader, field_path), + 33 => pop_n_plus_one(bitreader, field_path), + 34 => pop_n_plus_n(bitreader, field_path), + 35 => pop_n_and_non_topographical(bitreader, field_path), + 36 => non_topo_complex(bitreader, field_path), + 37 => non_topo_penultimate_plus_one(bitreader, field_path), + 38 => non_topo_complex_pack4_bits(bitreader, field_path), + other => todo!("Other OP: {:?}", other), + } +} diff --git a/src/parser/decoder.rs b/src/parser/decoder.rs new file mode 100644 index 0000000..37648f0 --- /dev/null +++ b/src/parser/decoder.rs @@ -0,0 +1,370 @@ +mod quantizedfloat; +pub use quantizedfloat::{QfMapper, QuantalizedFloat}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Decoder { + QuantalizedFloatDecoder(u8), + VectorNormalDecoder, + VectorNoscaleDecoder, + VectorFloatCoordDecoder, + Unsigned64Decoder, + CentityHandleDecoder, + NoscaleDecoder, + BooleanDecoder, + StringDecoder, + SignedDecoder, + UnsignedDecoder, + ComponentDecoder, + FloatCoordDecoder, + FloatSimulationTimeDecoder, + Fixed64Decoder, + QanglePitchYawDecoder, + Qangle3Decoder, + QangleVarDecoder, + BaseDecoder, + AmmoDecoder, + QanglePresDecoder, + GameModeRulesDecoder, +} +use Decoder::*; + +pub static BASETYPE_DECODERS: phf::Map<&'static str, Decoder> = phf::phf_map! { + "bool" => BooleanDecoder, + "char" => StringDecoder, + "int16" => SignedDecoder, + "int32" => SignedDecoder, + "int64" => SignedDecoder, + "int8" => SignedDecoder, + "uint16" => UnsignedDecoder, + "uint32" => UnsignedDecoder, + "uint8" => UnsignedDecoder, + "color32" => UnsignedDecoder, + "GameTime_t" => NoscaleDecoder, + "CBodyComponent" => ComponentDecoder, + "CGameSceneNodeHandle" => UnsignedDecoder, + "Color" => UnsignedDecoder, + "CPhysicsComponent" => ComponentDecoder, + "CRenderComponent" => ComponentDecoder, + "CUtlString" => StringDecoder, + "CUtlStringToken" => UnsignedDecoder, + "CUtlSymbolLarge" => StringDecoder, + "Quaternion" => NoscaleDecoder, + "CTransform" => NoscaleDecoder, + "HSequence" => Unsigned64Decoder, + "AttachmentHandle_t"=> Unsigned64Decoder, + "CEntityIndex"=> Unsigned64Decoder, + "MoveCollide_t"=> Unsigned64Decoder, + "MoveType_t"=> Unsigned64Decoder, + "RenderMode_t"=> Unsigned64Decoder, + "RenderFx_t"=> Unsigned64Decoder, + "SolidType_t"=> Unsigned64Decoder, + "SurroundingBoundsType_t"=> Unsigned64Decoder, + "ModelConfigHandle_t"=> Unsigned64Decoder, + "NPC_STATE"=> Unsigned64Decoder, + "StanceType_t"=> Unsigned64Decoder, + "AbilityPathType_t"=> Unsigned64Decoder, + "WeaponState_t"=> Unsigned64Decoder, + "DoorState_t"=> Unsigned64Decoder, + "RagdollBlendDirection"=> Unsigned64Decoder, + "BeamType_t"=> Unsigned64Decoder, + "BeamClipStyle_t"=> Unsigned64Decoder, + "EntityDisolveType_t"=> Unsigned64Decoder, + "tablet_skin_state_t" => Unsigned64Decoder, + "CStrongHandle" => Unsigned64Decoder, + "CSWeaponMode" => Unsigned64Decoder, + "ESurvivalSpawnTileState"=> Unsigned64Decoder, + "SpawnStage_t"=> Unsigned64Decoder, + "ESurvivalGameRuleDecision_t"=> Unsigned64Decoder, + "RelativeDamagedDirection_t"=> Unsigned64Decoder, + "CSPlayerState"=> Unsigned64Decoder, + "MedalRank_t"=> Unsigned64Decoder, + "CSPlayerBlockingUseAction_t"=> Unsigned64Decoder, + "MoveMountingAmount_t"=> Unsigned64Decoder, + "QuestProgress::Reason"=> Unsigned64Decoder, +}; + +pub fn find_decoder(field: &super::sendtables::ConstructorField, qf_map: &mut QfMapper) -> Decoder { + if field.var_name.as_str() == "m_iClip1" { + return Decoder::AmmoDecoder; + } + + match BASETYPE_DECODERS.get(field.field_type.base_type.as_str()) { + Some(d) => d.clone(), + None => match field.field_type.base_type.as_str() { + "float32" => float_decoder(field, qf_map), + "Vector" => find_vector_type(3, field, qf_map), + "Vector2D" => find_vector_type(2, field, qf_map), + "Vector4D" => find_vector_type(4, field, qf_map), + "uint64" => find_uint_decoder(field), + "QAngle" => find_qangle_decoder(field), + "CHandle" => Decoder::UnsignedDecoder, + "CNetworkedQuantizedFloat" => float_decoder(field, qf_map), + "CStrongHandle" => find_uint_decoder(field), + "CEntityHandle" => find_uint_decoder(field), + _ => Decoder::UnsignedDecoder, + }, + } +} + +fn find_qangle_decoder(field: &super::sendtables::ConstructorField) -> Decoder { + match field.var_name.as_str() { + "m_angEyeAngles" => Decoder::QanglePitchYawDecoder, + _ => { + if field.bitcount != 0 { + Decoder::Qangle3Decoder + } else { + Decoder::QangleVarDecoder + } + } + } +} + +fn find_uint_decoder(field: &super::sendtables::ConstructorField) -> Decoder { + match field.encoder.as_str() { + "fixed64" => Decoder::Fixed64Decoder, + _ => Decoder::Unsigned64Decoder, + } +} + +fn float_decoder(field: &super::sendtables::ConstructorField, qf_map: &mut QfMapper) -> Decoder { + match field.var_name.as_str() { + "m_flSimulationTime" => return Decoder::FloatSimulationTimeDecoder, + "m_flAnimTime" => return Decoder::FloatSimulationTimeDecoder, + _ => {} + }; + + match field.encoder.as_str() { + "coord" => Decoder::FloatCoordDecoder, + "m_flSimulationTime" => Decoder::FloatSimulationTimeDecoder, + _ => { + if field.bitcount <= 0 || field.bitcount >= 32 { + return Decoder::NoscaleDecoder; + } else { + let qf = QuantalizedFloat::new( + field.bitcount as u32, + Some(field.encode_flags), + Some(field.low_value), + Some(field.high_value), + ); + let idx = qf_map.idx; + qf_map.map.insert(idx, qf); + qf_map.idx += 1; + Decoder::QuantalizedFloatDecoder(idx as u8) + } + } + } +} + +fn find_vector_type( + dimensions: usize, + field: &super::sendtables::ConstructorField, + qf_map: &mut QfMapper, +) -> Decoder { + if dimensions == 3 && field.encoder.as_str() == "normal" { + return Decoder::VectorNormalDecoder; + } + + let float_type = float_decoder(field, qf_map); + match float_type { + Decoder::NoscaleDecoder => Decoder::VectorNoscaleDecoder, + Decoder::FloatCoordDecoder => Decoder::VectorFloatCoordDecoder, + _ => Decoder::VectorNormalDecoder, + } +} + +impl Decoder { + pub fn decode( + &self, + bitreader: &mut crate::bitreader::Bitreader, + qf_map: &mut QfMapper, + ) -> Result { + use super::variant::Variant; + + match self { + Self::NoscaleDecoder => Ok(Variant::F32(f32::from_bits(bitreader.read_nbits(32)?))), + Self::FloatSimulationTimeDecoder => Ok(Variant::F32(bitreader.decode_simul_time()?)), + Self::UnsignedDecoder => Ok(Variant::U32(bitreader.read_varint()?)), + Self::QuantalizedFloatDecoder(qf_idx) => Ok(bitreader.decode_qfloat(*qf_idx, qf_map)?), + Self::Qangle3Decoder => Ok(Variant::VecXYZ(bitreader.decode_qangle_all_3()?)), + Self::SignedDecoder => Ok(Variant::I32(bitreader.read_varint32()?)), + Self::VectorNoscaleDecoder => Ok(Variant::VecXYZ(bitreader.decode_vector_noscale()?)), + Self::BooleanDecoder => Ok(Variant::Bool(bitreader.read_boolean()?)), + Self::BaseDecoder => Ok(Variant::U32(bitreader.read_varint()?)), + Self::CentityHandleDecoder => Ok(Variant::U32(bitreader.read_varint()?)), + Self::ComponentDecoder => Ok(Variant::Bool(bitreader.read_boolean()?)), + Self::FloatCoordDecoder => Ok(Variant::F32(bitreader.read_bit_coord()?)), + Self::StringDecoder => Ok(Variant::String(bitreader.read_string()?)), + Self::QanglePitchYawDecoder => { + Ok(Variant::VecXYZ(bitreader.decode_qangle_pitch_yaw()?)) + } + Self::QangleVarDecoder => Ok(Variant::VecXYZ(bitreader.decode_qangle_variant()?)), + Self::VectorNormalDecoder => Ok(Variant::VecXYZ(bitreader.decode_normal_vec()?)), + Self::Unsigned64Decoder => Ok(Variant::U64(bitreader.read_varint_u_64()?)), + Self::Fixed64Decoder => Ok(Variant::U64(bitreader.decode_uint64()?)), + Self::VectorFloatCoordDecoder => { + Ok(Variant::VecXYZ(bitreader.decode_vector_float_coord()?)) + } + Self::AmmoDecoder => Ok(Variant::U32(bitreader.decode_ammo()?)), + Self::QanglePresDecoder => Ok(Variant::VecXYZ(bitreader.decode_qangle_variant_pres()?)), + Self::GameModeRulesDecoder => Ok(Variant::U32(bitreader.read_nbits(7)?)), + } + } +} + +impl<'b> crate::bitreader::Bitreader<'b> { + pub fn read_bit_coord_pres(&mut self) -> Result { + return Ok(self.read_nbits(20)? as f32 * 360.0 / (1 << 20) as f32 - 180.0); + } + + pub fn decode_qfloat( + &mut self, + qf_idx: u8, + qf_map: &QfMapper, + ) -> Result { + match qf_map.map.get(&(qf_idx as u32)) { + Some(qf) => Ok(super::variant::Variant::F32(qf.decode(self)?)), + None => panic!(), + } + } + + pub fn decode_ammo(&mut self) -> Result { + let ammo = self.read_varint()?; + if ammo > 0 { + return Ok(ammo - 1); + } + return Ok(ammo); + } + + pub fn decode_uint64(&mut self) -> Result { + let bytes = self.read_n_bytes(8)?; + match bytes.try_into() { + Err(_) => panic!(), + Ok(arr) => Ok(u64::from_ne_bytes(arr)), + } + } + + pub fn decode_noscale(&mut self) -> Result { + Ok(f32::from_le_bytes(self.read_nbits(32)?.to_le_bytes())) + } + + pub fn read_string(&mut self) -> Result { + let mut s: Vec = vec![]; + loop { + let c = self.read_nbits(8)? as u8; + if c == 0 { + break; + } + s.push(c); + } + Ok(String::from_utf8_lossy(&s).to_string()) + } + pub fn decode_float_coord(&mut self) -> Result { + Ok(self.read_bit_coord()?) + } + + fn decode_simul_time(&mut self) -> Result { + Ok(self.read_varint()? as f32 * (1.0 / 30.0)) + } + + pub fn decode_vector_noscale(&mut self) -> Result<[f32; 3], super::FirstPassError> { + let mut v = [0.0; 3]; + for idx in 0..3 { + v[idx] = self.decode_noscale()?; + } + Ok(v) + } + + pub fn decode_qangle_pitch_yaw(&mut self) -> Result<[f32; 3], super::FirstPassError> { + let mut v = [0.0; 3]; + v[0] = self.read_angle(32)?; + v[1] = self.read_angle(32)?; + v[2] = self.read_angle(32)?; + Ok(v) + } + pub fn decode_qangle_all_3(&mut self) -> Result<[f32; 3], super::FirstPassError> { + // Used by aimpunch props (not exposed atm) maybe wrong format? correct number of bits anyhow. + let mut v = [0.0; 3]; + v[0] = self.decode_noscale()?; + v[1] = self.decode_noscale()?; + v[2] = self.decode_noscale()?; + Ok(v) + } + pub fn decode_qangle_variant(&mut self) -> Result<[f32; 3], super::FirstPassError> { + let mut v = [0.0; 3]; + let has_x = self.read_boolean()?; + let has_y = self.read_boolean()?; + let has_z = self.read_boolean()?; + if has_x { + v[0] = self.read_bit_coord()?; + } + if has_y { + v[1] = self.read_bit_coord()?; + } + if has_z { + v[2] = self.read_bit_coord()?; + } + Ok(v) + } + pub fn read_angle(&mut self, n: usize) -> Result { + return Ok(self.decode_noscale()? / ((1 << n) as f32)); + } + + pub fn decode_normal(&mut self) -> Result { + let is_neg = self.read_boolean()?; + let len = self.read_nbits(11)?; + let result = (len as f64 * (1.0 / ((1 << 11) as f64) - 1.0)) as f32; + match is_neg { + true => Ok(-result), + false => Ok(result), + } + } + pub fn decode_normal_vec(&mut self) -> Result<[f32; 3], super::FirstPassError> { + let mut v = [0.0; 3]; + let has_x = self.read_boolean()?; + let has_y = self.read_boolean()?; + if has_x { + v[0] = self.decode_normal()?; + } + if has_y { + v[1] = self.decode_normal()?; + } + let neg_z = self.read_boolean()?; + let prod_sum = v[0] * v[0] + v[1] * v[1]; + if prod_sum < 1.0 { + v[2] = (1.0 - prod_sum).sqrt() as f32; + } else { + v[2] = 0.0; + } + if neg_z { + v[2] = -v[2]; + } + Ok(v) + } + + pub fn decode_vector_float_coord(&mut self) -> Result<[f32; 3], super::FirstPassError> { + let mut v = [0.0; 3]; + for idx in 0..3 { + v[idx] = self.decode_float_coord()?; + } + Ok(v) + } + + pub fn decode_qangle_variant_pres(&mut self) -> Result<[f32; 3], super::FirstPassError> { + let mut v = [0.0; 3]; + + let has_x = self.read_boolean()?; + let has_y = self.read_boolean()?; + let has_z = self.read_boolean()?; + + if has_x { + v[0] = self.read_bit_coord_pres()?; + } + if has_y { + v[1] = self.read_bit_coord_pres()?; + } + if has_z { + v[2] = self.read_bit_coord_pres()?; + } + Ok(v) + } +} diff --git a/src/parser/decoder/quantizedfloat.rs b/src/parser/decoder/quantizedfloat.rs new file mode 100644 index 0000000..efabece --- /dev/null +++ b/src/parser/decoder/quantizedfloat.rs @@ -0,0 +1,208 @@ +use crate::{bitreader::Bitreader, parser::FirstPassError}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct QuantalizedFloat { + low: f32, + high: f32, + high_low_mul: f32, + dec_mul: f32, + offset: f32, + bit_count: u32, + flags: u32, + no_scale: bool, +} + +#[derive(Debug, Clone)] +pub struct QfMapper { + pub idx: u32, + pub map: std::collections::HashMap, +} + +const QFF_ROUNDDOWN: u32 = 1 << 0; +const QFF_ROUNDUP: u32 = 1 << 1; +const QFF_ENCODE_ZERO: u32 = 1 << 2; +const QFF_ENCODE_INTEGERS: u32 = 1 << 3; + +impl QuantalizedFloat { + // More or less directly translated from here: + // https://github.com/dotabuff/manta/blob/09a1d60ef77f68eef84b79e9ca519caf76a1f291/quantizedfloat.go + fn validate_flags(&mut self) { + if self.flags == 0 { + return; + } + if (self.low == 0.0 && (self.flags & QFF_ROUNDDOWN) != 0) + || (self.high == 0.0 && (self.flags & QFF_ROUNDUP) != 0) + { + self.flags &= !QFF_ENCODE_ZERO; + } + if self.low == 0.0 && (self.flags & QFF_ENCODE_ZERO) != 0 { + self.flags |= QFF_ROUNDDOWN; + self.flags &= !QFF_ENCODE_ZERO; + } + if self.high == 0.0 && (self.flags & QFF_ENCODE_ZERO) != 0 { + self.flags |= QFF_ROUNDUP; + self.flags &= !QFF_ENCODE_ZERO; + } + if self.low > 0.0 || self.high < 0.0 { + self.flags &= !QFF_ENCODE_ZERO; + } + if (self.flags & QFF_ENCODE_INTEGERS) != 0 { + self.flags &= !(QFF_ROUNDUP | QFF_ROUNDDOWN | QFF_ENCODE_ZERO); + } + } + fn assign_multipliers(&mut self, steps: u32) { + self.high_low_mul = 0.0; + let range = self.high - self.low; + + let high: u32; + if self.bit_count == 32 { + high = 0xFFFFFFFE; + } else { + high = (1 << self.bit_count) - 1; + } + + let mut high_mul: f32; + // Xd? + if range.abs() <= 0.0 { + high_mul = high as f32; + } else { + high_mul = (high as f32) / range; + } + + if (high_mul * range > (high as f32)) + || (((high_mul * range) as f64) > ((high as f32) as f64)) + { + let multipliers = vec![0.9999, 0.99, 0.9, 0.8, 0.7]; + for multiplier in multipliers { + high_mul = (high as f32) / range * multiplier; + if (high_mul * range > (high as f32)) + || (((high_mul * range) as f64) > (high as f32) as f64) + { + continue; + } + break; + } + } + self.high_low_mul = high_mul; + self.dec_mul = 1.0 / (steps - 1) as f32; + } + pub fn quantize(&mut self, val: f32) -> f32 { + if val < self.low { + return self.low; + } else if val > self.high { + return self.high; + } + let i = ((val - self.low) * self.high_low_mul) as u32; + self.low + (self.high - self.low) * ((i as f32) * self.dec_mul) + } + pub fn decode(&self, bitreader: &mut Bitreader) -> Result { + if self.flags & QFF_ROUNDDOWN != 0 && bitreader.read_boolean()? { + return Ok(self.low); + } + if self.flags & QFF_ROUNDUP != 0 && bitreader.read_boolean()? { + return Ok(self.high); + } + if self.flags & QFF_ENCODE_ZERO != 0 && bitreader.read_boolean()? { + return Ok(0.0); + } + let bits = bitreader.read_nbits(self.bit_count)?; + Ok(self.low + (self.high - self.low) * bits as f32 * self.dec_mul) + } + pub fn new( + bitcount: u32, + flags: Option, + low_value: Option, + high_value: Option, + ) -> Self { + let mut qf = QuantalizedFloat { + no_scale: false, + bit_count: 0, + dec_mul: 0.0, + low: 0.0, + high: 0.0, + high_low_mul: 0.0, + offset: 0.0, + flags: 0, + }; + + if bitcount == 0 || bitcount >= 32 { + qf.no_scale = true; + qf.bit_count = 32; + return qf; + } else { + qf.no_scale = false; + qf.bit_count = bitcount; + qf.offset = 0.0; + + if low_value.is_some() { + qf.low = low_value.unwrap_or(0.0); + } else { + qf.low = 0.0; + } + if high_value.is_some() { + qf.high = high_value.unwrap_or(0.0); + } else { + qf.high = 1.0; + } + } + if flags.is_some() { + qf.flags = flags.unwrap_or(0) as u32; + } else { + qf.flags = 0; + } + qf.validate_flags(); + let mut steps = 1 << qf.bit_count; + + if (qf.flags & QFF_ROUNDDOWN) != 0 { + let range = qf.high - qf.low; + qf.offset = range / (steps as f32); + qf.high -= qf.offset; + } else if (qf.flags & QFF_ROUNDUP) != 0 { + let range = qf.high - qf.low; + qf.offset = range / (steps as f32); + qf.low += qf.offset; + } + if (qf.flags & QFF_ENCODE_INTEGERS) != 0 { + let mut delta = qf.high - qf.low; + if delta < 1.0 { + delta = 1.0; + } + let delta_log2 = delta.log2().ceil(); + let range_2: u32 = 1 << delta_log2 as u32; + let mut bit_count = qf.bit_count; + loop { + if (1 << bit_count) > range_2 { + break; + } else { + bit_count += 1; + } + } + if bit_count > qf.bit_count { + qf.bit_count = bit_count; + steps = 1 << qf.bit_count; + } + qf.offset = range_2 as f32 / steps as f32; + qf.high = qf.low + ((range_2 as f32 - qf.offset) as f32); + } + + qf.assign_multipliers(steps); + + if (qf.flags & QFF_ROUNDDOWN) != 0 { + if qf.quantize(qf.low) == qf.low { + qf.flags &= !QFF_ROUNDDOWN; + } + } + if (qf.flags & QFF_ROUNDUP) != 0 { + if qf.quantize(qf.high) == qf.high { + qf.flags &= !QFF_ROUNDUP + } + } + if (qf.flags & QFF_ENCODE_ZERO) != 0 { + if qf.quantize(0.0) == 0.0 { + qf.flags &= !QFF_ENCODE_ZERO; + } + } + + qf + } +} diff --git a/src/parser/fieldpath.rs b/src/parser/fieldpath.rs new file mode 100644 index 0000000..0a026c3 --- /dev/null +++ b/src/parser/fieldpath.rs @@ -0,0 +1,411 @@ +#[derive(Debug)] +pub struct Paths(Vec); + +#[derive(Debug, Clone, Copy)] +pub struct FieldPath { + pub path: [i32; 7], + pub last: usize, +} + +impl Paths { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn new_path() -> FieldPath { + FieldPath { + path: [-1, 0, 0, 0, 0, 0, 0], + last: 0, + } + } + + pub fn write(&mut self, fp: &FieldPath, idx: usize) { + match self.0.get_mut(idx) { + Some(entry) => { + *entry = *fp; + } + None => { + self.0.resize(idx + 1, Self::new_path()); + let entry = self.0.get_mut(idx).expect("We just resized the Vec"); + *entry = *fp; + } + }; + } + + pub fn paths(&self) -> impl Iterator { + self.0.iter() + } +} + +pub fn parse_paths( + bitreader: &mut crate::bitreader::Bitreader, + paths: &mut Paths, +) -> Result { + let mut path = Paths::new_path(); + let mut idx = 0; + loop { + if bitreader.bits_left < 17 { + bitreader.refill(); + } + + let peeked_bits = bitreader.peek(17); + let (symbol, code_len) = super::HUFFMAN_LOOKUP_TABLE[peeked_bits as usize]; + bitreader.consume(code_len as u32); + + if symbol == 39 { + break; + } + + super::do_op(symbol, bitreader, &mut path)?; + paths.write(&path, idx); + idx += 1; + } + + Ok(idx) +} + +impl FieldPath { + pub fn pop_special(&mut self, n: usize) -> Result<(), super::FirstPassError> { + for _ in 0..n { + *self.get_entry_mut(self.last)? = 0; + self.last -= 1; + } + Ok(()) + } + + pub fn get_entry_mut(&mut self, idx: usize) -> Result<&mut i32, super::FirstPassError> { + match self.path.get_mut(idx) { + Some(e) => Ok(e), + None => panic!(), + } + } + + pub fn find<'ser>( + &self, + ser: &'ser super::sendtables::Serializer, + ) -> Result<&'ser super::sendtables::Field, super::FirstPassError> { + let f = match ser.fields.get(self.path[0] as usize) { + Some(entry) => entry, + None => panic!("Field-Len: {:?} - Path: {:?}", ser.fields.len(), self.path), + }; + + match self.last { + 0 => Ok(f), + 1 => Ok(f.get_inner(self.path[1] as usize)?), + 2 => Ok(f.get_inner(self.path[1] as usize)?.get_inner(self.path[2] as usize)?), + 3 => Ok(f.get_inner(self.path[1] as usize)?.get_inner(self.path[2] as usize)?.get_inner(self.path[3] as usize)?), + other => panic!("{:?}", other), + } + } +} + +pub mod ops { + use super::FieldPath; + use crate::{bitreader::Bitreader, parser::FirstPassError}; + +pub fn plus_one(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + Ok(()) +} + +pub fn plus_two(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 2; + Ok(()) +} + +pub fn plus_three(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 3; + Ok(()) +} + +pub fn plus_four(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 4; + Ok(()) +} + +pub fn plus_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32 + 5; + Ok(()) +} + +pub fn push_one_left_delta_zero_right_zero(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = 0; + Ok(()) +} + +pub fn push_one_left_delta_zero_right_non_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_one_left_delta_one_right_zero(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = 0; + Ok(()) +} + +pub fn push_one_left_delta_one_right_non_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_one_left_delta_n_right_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = 0; + Ok(()) +} + +pub fn push_one_left_delta_n_right_non_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32 + 2; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_ubit_var_fp()? as i32 + 1; + Ok(()) +} + +pub fn push_one_left_delta_n_right_non_zero_pack6_bits( + bitreader: &mut Bitreader, + field_path: &mut FieldPath, +) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += (bitreader.read_nbits(3)? + 2) as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = (bitreader.read_nbits(3)? + 1) as i32; + Ok(()) +} + +pub fn push_one_left_delta_n_right_non_zero_pack8_bits( + bitreader: &mut Bitreader, + field_path: &mut FieldPath, +) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += (bitreader.read_nbits(4)? + 2) as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = (bitreader.read_nbits(4)? + 1) as i32; + Ok(()) +} + +pub fn push_two_left_delta_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_two_pack5_left_delta_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_nbits(5)? as i32; + Ok(()) +} + +pub fn push_three_left_delta_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_three_pack5_left_delta_zero(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_nbits(5)? as i32; + Ok(()) +} + +pub fn push_two_left_delta_one(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_two_pack5_left_delta_one(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + Ok(()) +} + +pub fn push_three_left_delta_one(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_three_pack5_left_delta_one(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += 1; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + Ok(()) +} + +pub fn push_two_left_delta_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += (bitreader.read_u_bit_var()? + 2) as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_two_pack5_left_delta_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += (bitreader.read_u_bit_var()? + 2) as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + Ok(()) +} + +pub fn push_three_left_delta_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += (bitreader.read_u_bit_var()? + 2) as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + Ok(()) +} + +pub fn push_three_pack5_left_delta_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last)? += (bitreader.read_u_bit_var()? + 2) as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_nbits(5)? as i32; + Ok(()) +} + +pub fn push_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + let n = bitreader.read_u_bit_var()? as i32; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_u_bit_var()? as i32; + for _ in 0..n { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32; + } + Ok(()) +} + +pub fn push_n_and_non_topological(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + for i in 0..field_path.last + 1 { + if bitreader.read_boolean()? { + *field_path.get_entry_mut(i)? += bitreader.read_varint32()? + 1; + } + } + let count = bitreader.read_u_bit_var()?; + for _ in 0..count { + field_path.last += 1; + *field_path.get_entry_mut(field_path.last)? = bitreader.read_ubit_var_fp()? as i32; + } + Ok(()) +} + +pub fn pop_one_plus_one(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(1)?; + *field_path.get_entry_mut(field_path.last)? += 1; + Ok(()) +} + +pub fn pop_one_plus_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(1)?; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_ubit_var_fp()? as i32 + 1; + Ok(()) +} + +pub fn pop_all_but_one_plus_one(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(field_path.last)?; + *field_path.get_entry_mut(0)? += 1; + Ok(()) +} + +pub fn pop_all_but_one_plus_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(field_path.last)?; + *field_path.get_entry_mut(0)? += bitreader.read_ubit_var_fp()? as i32 + 1; + Ok(()) +} + +pub fn pop_all_but_one_plus_n_pack3_bits(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(field_path.last)?; + *field_path.get_entry_mut(0)? += bitreader.read_nbits(3)? as i32 + 1; + Ok(()) +} + +pub fn pop_all_but_one_plus_n_pack6_bits(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(field_path.last)?; + *field_path.get_entry_mut(0)? += bitreader.read_nbits(6)? as i32 + 1; + Ok(()) +} + +pub fn pop_n_plus_one(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(bitreader.read_ubit_var_fp()? as usize)?; + *field_path.get_entry_mut(field_path.last)? += 1; + Ok(()) +} + +pub fn pop_n_plus_n(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(bitreader.read_ubit_var_fp()? as usize)?; + *field_path.get_entry_mut(field_path.last)? += bitreader.read_varint32()?; + Ok(()) +} + +pub fn pop_n_and_non_topographical(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + field_path.pop_special(bitreader.read_ubit_var_fp()? as usize)?; + for i in 0..field_path.last + 1 { + if bitreader.read_boolean()? { + *field_path.get_entry_mut(i)? += bitreader.read_varint32()?; + } + } + Ok(()) +} + +pub fn non_topo_complex(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + for i in 0..field_path.last + 1 { + if bitreader.read_boolean()? { + *field_path.get_entry_mut(i)? += bitreader.read_varint32()?; + } + } + Ok(()) +} + +pub fn non_topo_penultimate_plus_one(_bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + *field_path.get_entry_mut(field_path.last - 1)? += 1; + Ok(()) +} + +pub fn non_topo_complex_pack4_bits(bitreader: &mut Bitreader, field_path: &mut FieldPath) -> Result<(), FirstPassError> { + for i in 0..field_path.last + 1 { + if bitreader.read_boolean()? { + *field_path.get_entry_mut(i)? += bitreader.read_nbits(4)? as i32 - 7; + } + } + Ok(()) +} +} diff --git a/src/parser/sendtables.rs b/src/parser/sendtables.rs new file mode 100644 index 0000000..3be4fab --- /dev/null +++ b/src/parser/sendtables.rs @@ -0,0 +1,1449 @@ +use super::decoder; + +#[derive(Debug)] +pub enum ParseSendTables {} + +#[derive(Debug, Clone, PartialEq)] +pub struct Serializer { + pub name: String, + pub fields: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub struct FieldInfo { + pub decoder: decoder::Decoder, + pub should_parse: bool, + pub prop_id: u32, +} + +// Design from https://github.com/skadistats/clarity +#[derive(Debug, Clone, PartialEq)] +pub enum Field { + Array(ArrayField), + Vector(VectorField), + Serializer(SerializerField), + Pointer(PointerField), + Value(ValueField), + None, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ArrayField { + pub field_enum: Box, + pub length: usize, +} +#[derive(Debug, Clone, PartialEq)] +pub struct VectorField { + pub field_enum: Box, + pub decoder: decoder::Decoder, +} +#[derive(Debug, Clone, PartialEq)] +pub struct ValueField { + pub decoder: decoder::Decoder, + pub name: String, + pub should_parse: bool, + pub prop_id: u32, + pub full_name: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SerializerField { + pub serializer: Serializer, +} +#[derive(Debug, Clone, PartialEq)] +pub struct PointerField { + pub decoder: decoder::Decoder, + pub serializer: Serializer, +} + +impl ArrayField { + pub fn new(field_enum: Field, length: usize) -> ArrayField { + ArrayField { + field_enum: Box::new(field_enum), + length, + } + } +} +impl PointerField { + pub fn new(serializer: &Serializer) -> PointerField { + let decoder = if serializer.name == "CCSGameModeRules" { + decoder::Decoder::GameModeRulesDecoder + } else { + decoder::Decoder::BooleanDecoder + }; + PointerField { + serializer: serializer.clone(), + decoder, + } + } +} +impl SerializerField { + pub fn new(serializer: &Serializer) -> SerializerField { + SerializerField { + serializer: serializer.clone(), + } + } +} +impl ValueField { + pub fn new(decoder: decoder::Decoder, name: &str) -> ValueField { + ValueField { + decoder, + name: name.to_string(), + prop_id: 0, + should_parse: false, + full_name: "None ".to_string() + name, + } + } +} +impl VectorField { + pub fn new(field_enum: Field) -> VectorField { + VectorField { + field_enum: Box::new(field_enum), + decoder: decoder::Decoder::UnsignedDecoder, + } + } +} + +pub fn get_serializers( + msg: &crate::csgo_proto::CsvcMsgFlattenedSerializer, + qf_mapper: &mut decoder::QfMapper, +) -> Result, super::FirstPassError> { + let mut fields: Vec> = vec![None; msg.fields.len()]; + let mut field_type_map: std::collections::HashMap = + std::collections::HashMap::new(); + let mut serializers: std::collections::HashMap = + std::collections::HashMap::new(); + + for (field, msg_field) in fields.iter_mut().zip(msg.fields.iter()) { + let field_data = generate_field_data(msg_field, msg, &mut field_type_map, qf_mapper)?; + *field = Some(field_data); + } + + for serializer in msg.serializers.iter() { + let ser = generate_serializer(serializer, &mut fields, msg, &mut serializers)?; + serializers.insert(ser.name.clone(), ser); + } + + Ok(serializers) +} + +fn generate_field_data( + field: &crate::csgo_proto::ProtoFlattenedSerializerFieldT, + msg: &crate::csgo_proto::CsvcMsgFlattenedSerializer, + field_type_map: &mut std::collections::HashMap, + qf_mapper: &mut decoder::QfMapper, +) -> Result { + let name = msg.symbols.get(field.var_type_sym() as usize).unwrap(); + + let ft = find_field_type(&name, field_type_map)?; + let mut field = field_from_msg(field, msg, ft.clone())?; + + field.category = find_category(&field); + field.decoder = decoder::find_decoder(&field, qf_mapper); + + match field.var_name.as_str() { + "m_PredFloatVariables" | "m_OwnerOnlyPredNetFloatVariables" => { + field.decoder = decoder::Decoder::NoscaleDecoder + } + "m_OwnerOnlyPredNetVectorVariables" | "m_PredVectorVariables" => { + field.decoder = decoder::Decoder::VectorNoscaleDecoder + } + "m_pGameModeRules" => field.decoder = decoder::Decoder::GameModeRulesDecoder, + _ => {} + }; + + if field.encoder == "qangle_precise" { + field.decoder = decoder::Decoder::QanglePresDecoder; + } + + field.field_type = ft; + + Ok(field) +} + +fn generate_serializer( + serializer: &crate::csgo_proto::ProtoFlattenedSerializerT, + field_data: &mut Vec>, + msg: &crate::csgo_proto::CsvcMsgFlattenedSerializer, + serializers: &mut std::collections::HashMap, +) -> Result { + let symbol = match msg.symbols.get(serializer.serializer_name_sym() as usize) { + Some(s) => s, + None => panic!(), + }; + + let mut fields_this_ser: Vec = vec![Field::None; serializer.fields_index.len()]; + for (idx, field_this_ser) in fields_this_ser.iter_mut().enumerate() { + let fi: usize = match serializer.fields_index.get(idx).map(|i| *i as usize) { + Some(f) => f, + None => continue, + }; + + let f = match field_data.get_mut(fi) { + Some(Some(f)) => f, + _ => continue, + }; + + if f.field_enum_type.is_none() { + f.field_enum_type = Some(create_field(&symbol, f, serializers)?); + } + if let Some(Some(f)) = &field_data.get(fi) { + if let Some(field) = &f.field_enum_type { + *field_this_ser = field.clone(); + } + } + } + + Ok(Serializer { + name: symbol.to_owned(), + fields: fields_this_ser, + }) +} + +#[derive(Debug, Clone)] +pub struct FieldType { + pub base_type: String, + pub generic_type: Option>, + pub pointer: bool, + pub count: Option, + pub element_type: Option>, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum FieldCategory { + Pointer, + Vector, + Array, + Value, +} + +#[derive(Debug, Clone)] +pub struct ConstructorField { + pub var_name: String, + pub var_type: String, + pub send_node: String, + pub serializer_name: Option, + pub encoder: String, + pub encode_flags: i32, + pub bitcount: i32, + pub low_value: f32, + pub high_value: f32, + pub field_type: FieldType, + + pub decoder: decoder::Decoder, + pub category: FieldCategory, + pub field_enum_type: Option, + pub serializer: Option<()>, + pub base_decoder: Option<()>, + pub child_decoder: Option<()>, +} + +static RE: std::sync::LazyLock = std::sync::LazyLock::new(|| { + regex::Regex::new(r"([^<\[\*]+)(<\s(.*)\s>)?(\*)?(\[(.*)\])?").unwrap() +}); + +const POINTER_TYPES: &'static [&'static str] = &[ + "CBodyComponent", + "CLightComponent", + "CPhysicsComponent", + "CRenderComponent", + "CPlayerLocalData", +]; + +fn find_field_type( + name: &str, + field_type_map: &mut std::collections::HashMap, +) -> Result { + let captures = match RE.captures(name) { + Some(c) => c, + None => panic!("No captures found"), + }; + + let base_type = match captures.get(1) { + Some(s) => s.as_str().to_owned(), + None => String::new(), + }; + + let pointer = match captures.get(4) { + Some(s) => { + if s.as_str() == "*" { + true + } else { + POINTER_TYPES.contains(&name) + } + } + None => POINTER_TYPES.contains(&name), + }; + + let mut ft = FieldType { + base_type, + pointer, + count: None, + generic_type: None, + element_type: None, + }; + + if let Some(generic) = captures.get(3) { + ft.generic_type = Some(Box::new(find_field_type(generic.as_str(), field_type_map)?)); + } + if let Some(count) = captures.get(6) { + ft.count = Some(count.as_str().parse::().unwrap_or(0)); + } + + if ft.count.is_some() { + let ft_string = ft.to_string(true); + let for_string_res = for_string(field_type_map, ft_string)?; + ft.element_type = Some(Box::new(for_string_res)); + } + + Ok(ft) +} + +fn field_from_msg( + field: &crate::csgo_proto::ProtoFlattenedSerializerFieldT, + msg: &crate::csgo_proto::CsvcMsgFlattenedSerializer, + ft: FieldType, +) -> Result { + let ser_name = match field.field_serializer_name_sym { + Some(idx) => match msg.symbols.get(idx as usize) { + Some(entry) => Some(entry.to_owned()), + None => panic!(), + }, + None => None, + }; + + let enc_name = match field.var_encoder_sym { + Some(idx) => match msg.symbols.get(idx as usize) { + Some(enc_name) => enc_name.to_owned(), + None => panic!(), + }, + None => String::new(), + }; + + let var_name = match msg.symbols.get(field.var_name_sym() as usize) { + Some(n) => n.to_owned(), + None => panic!(), + }; + let var_type = match msg.symbols.get(field.var_type_sym() as usize) { + Some(n) => n.to_owned(), + None => panic!(), + }; + let send_node = match msg.symbols.get(field.send_node_sym() as usize) { + Some(n) => n.to_owned(), + None => panic!(), + }; + + Ok(ConstructorField { + field_enum_type: None, + bitcount: field.bit_count(), + var_name, + var_type, + send_node, + serializer_name: ser_name, + encoder: enc_name, + encode_flags: field.encode_flags(), + low_value: field.low_value(), + high_value: field.high_value(), + + field_type: ft, + serializer: None, + decoder: decoder::Decoder::BaseDecoder, + base_decoder: None, + child_decoder: None, + + category: FieldCategory::Value, + }) +} + +fn find_category(field: &ConstructorField) -> FieldCategory { + if field.is_pointer() { + return FieldCategory::Pointer; + } + + if field.is_vector() { + return FieldCategory::Vector; + } + + if field.is_array() { + return FieldCategory::Array; + } + + FieldCategory::Value +} + +impl ConstructorField { + pub fn is_pointer(&self) -> bool { + if self.field_type.pointer { + return true; + } + + matches!( + self.field_type.base_type.as_str(), + "CBodyComponent" + | "CLightComponent" + | "CPhysicsComponent" + | "CRenderComponent" + | "CPlayerLocalData" + ) + } + + pub fn is_array(&self) -> bool { + self.field_type + .count + .map(|_| self.field_type.base_type.as_str() != "char") + .unwrap_or(false) + } + + pub fn is_vector(&self) -> bool { + if self.serializer_name.is_some() { + return true; + } + + matches!( + self.field_type.base_type.as_str(), + "CUtlVector" | "CNetworkUtlVectorBase" + ) + } +} + +impl FieldType { + fn to_string(&self, omit_count: bool) -> String { + let mut s = String::new(); + + s += &self.base_type; + + if let Some(gt) = self.generic_type.as_ref() { + s += "< "; + s += &FieldType::to_string(>, true); + s += "< "; + } + if self.pointer { + s += "*"; + } + if !omit_count && self.count.is_some() { + if let Some(c) = self.count { + s += "["; + s += &c.to_string(); + s += "]"; + } + } + + s + } +} + +fn for_string( + field_type_map: &mut std::collections::HashMap, + field_type_string: String, +) -> Result { + match field_type_map.get(&field_type_string) { + Some(s) => Ok(s.clone()), + None => { + let result = find_field_type(&field_type_string, field_type_map)?; + field_type_map.insert(field_type_string, result.clone()); + Ok(result) + } + } +} + +fn create_field( + symbol: &String, + fd: &mut ConstructorField, + serializers: &mut std::collections::HashMap, +) -> Result { + let element_field = match fd.serializer_name.as_ref() { + Some(name) => { + let ser = match serializers.get(name.as_str()) { + Some(ser) => ser, + None => panic!(), + }; + if fd.category == FieldCategory::Pointer { + Field::Pointer(PointerField::new(ser)) + } else { + Field::Serializer(SerializerField::new(ser)) + } + } + None => Field::Value(ValueField::new(fd.decoder, &fd.var_name)), + }; + + match fd.category { + FieldCategory::Array => Ok(Field::Array(ArrayField::new( + element_field, + fd.field_type.count.unwrap_or(0) as usize, + ))), + FieldCategory::Vector => Ok(Field::Vector(VectorField::new(element_field))), + _ => Ok(element_field), + } +} + +impl Field { + pub fn get_inner(&self, idx: usize) -> Result<&Field, super::FirstPassError> { + match self { + Field::Array(inner) => Ok(&inner.field_enum), + Field::Vector(inner) => Ok(&inner.field_enum), + Field::Serializer(inner) => match inner.serializer.fields.get(idx) { + Some(f) => Ok(f), + None => panic!(), + }, + Field::Pointer(inner) => match inner.serializer.fields.get(idx) { + Some(f) => Ok(f), + None => panic!(), + }, + // Illegal + Field::Value(_) => panic!("Can not get inner of Field::Value"), + Field::None => panic!("Can not get inner of Field::None"), + } + } + + pub fn get_propinfo(&self, path: &super::FieldPath) -> Option { + const MY_WEAPONS_OFFSET: u32 = 500000; + const WEAPON_SKIN_ID: u32 = 10000000; + const ITEM_PURCHASE_COUNT: u32 = 200000000; + const FLATTENED_VEC_MAX_LEN: u32 = 100000; + const ITEM_PURCHASE_DEF_IDX: u32 = 300000000; + const ITEM_PURCHASE_NEW_DEF_IDX: u32 = 600000000; + const ITEM_PURCHASE_COST: u32 = 400000000; + const ITEM_PURCHASE_HANDLE: u32 = 500000000; + + let mut fi = match self { + Self::Value(v) => FieldInfo { + decoder: v.decoder, + should_parse: v.should_parse, + prop_id: v.prop_id, + }, + Self::Vector(v) => match self.get_inner(0) { + Ok(Field::Value(inner)) => FieldInfo { + decoder: v.decoder, + prop_id: inner.prop_id, + should_parse: inner.should_parse, + }, + _ => return None, + }, + _ => return None, + }; + + if fi.prop_id == MY_WEAPONS_OFFSET { + if path.last == 1 { + } else { + fi.prop_id = MY_WEAPONS_OFFSET + path.path[2] as u32 + 1; + } + } + if fi.prop_id == WEAPON_SKIN_ID { + fi.prop_id = WEAPON_SKIN_ID + path.path[1] as u32; + } + if path.path[1] != 1 { + if fi.prop_id >= ITEM_PURCHASE_COUNT + && fi.prop_id < ITEM_PURCHASE_COUNT + FLATTENED_VEC_MAX_LEN + { + fi.prop_id = ITEM_PURCHASE_COUNT + path.path[2] as u32; + } + if fi.prop_id >= ITEM_PURCHASE_DEF_IDX + && fi.prop_id < ITEM_PURCHASE_DEF_IDX + FLATTENED_VEC_MAX_LEN + { + fi.prop_id = ITEM_PURCHASE_DEF_IDX + path.path[2] as u32; + } + if fi.prop_id >= ITEM_PURCHASE_COST + && fi.prop_id < ITEM_PURCHASE_COST + FLATTENED_VEC_MAX_LEN + { + fi.prop_id = ITEM_PURCHASE_COST + path.path[2] as u32; + } + if fi.prop_id >= ITEM_PURCHASE_HANDLE + && fi.prop_id < ITEM_PURCHASE_HANDLE + FLATTENED_VEC_MAX_LEN + { + fi.prop_id = ITEM_PURCHASE_HANDLE + path.path[2] as u32; + } + if fi.prop_id >= ITEM_PURCHASE_NEW_DEF_IDX + && fi.prop_id < ITEM_PURCHASE_NEW_DEF_IDX + FLATTENED_VEC_MAX_LEN + { + fi.prop_id = ITEM_PURCHASE_NEW_DEF_IDX + path.path[2] as u32; + } + } + return Some(fi); + } + + pub fn get_decoder(&self) -> Result { + match self { + Self::Value(inner) => Ok(inner.decoder), + Self::Pointer(inner) => Ok(inner.decoder), + Self::Vector(_) => Ok(decoder::Decoder::UnsignedDecoder), + _ => panic!(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parse_ancient_example_msg() { + use Field::*; + use decoder::Decoder::*; + + let data: &[u8] = include_bytes!("../../testfiles/ancient_sendtables.b"); + + let mut qf_mapper = crate::parser::decoder::QfMapper { + idx: 0, + map: std::collections::HashMap::new(), + }; + + let serializer_msg: crate::csgo_proto::CsvcMsgFlattenedSerializer = + prost::Message::decode(data).unwrap(); + + let result = get_serializers(&serializer_msg, &mut qf_mapper).unwrap(); + + let cworld_parser = result.get("CWorld").unwrap(); + dbg!(&cworld_parser); + + let expected_parser = super::Serializer { + name: "CWorld".to_string(), + fields: [ + Value( + ValueField { + decoder: FloatSimulationTimeDecoder, + name: "m_flAnimTime".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flAnimTime".to_string(), + }, + ), + Value( + ValueField { + decoder: FloatSimulationTimeDecoder, + name: "m_flSimulationTime".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flSimulationTime".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_hOwnerEntity".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_hOwnerEntity".to_string(), + }, + ), + Pointer( + PointerField { + decoder: BooleanDecoder, + serializer: super::Serializer { + name: "CBodyComponentBaseModelEntity".to_string(), + fields: [ + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_cellX".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_cellX".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_cellY".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_cellY".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_cellZ".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_cellZ".to_string(), + }, + ), + Value( + ValueField { + decoder: QuantalizedFloatDecoder( + 0, + ), + name: "m_vecX".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecX".to_string(), + }, + ), + Value( + ValueField { + decoder: QuantalizedFloatDecoder( + 1, + ), + name: "m_vecY".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecY".to_string(), + }, + ), + Value( + ValueField { + decoder: QuantalizedFloatDecoder( + 2, + ), + name: "m_vecZ".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecZ".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_hParent".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_hParent".to_string(), + }, + ), + Value( + ValueField { + decoder: QanglePresDecoder, + name: "m_angRotation".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_angRotation".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flScale".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flScale".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_name".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_name".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_hierarchyAttachName".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_hierarchyAttachName".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_hModel".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_hModel".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bClientClothCreationSuppressed".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bClientClothCreationSuppressed".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_MeshGroupMask".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_MeshGroupMask".to_string(), + }, + ), + Value( + ValueField { + decoder: SignedDecoder, + name: "m_nIdealMotionType".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nIdealMotionType".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bIsAnimationEnabled".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bIsAnimationEnabled".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bUseParentRenderBounds".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bUseParentRenderBounds".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_materialGroup".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_materialGroup".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nHitboxSet".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nHitboxSet".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nOutsideWorld".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nOutsideWorld".to_string(), + }, + ), + ].to_vec(), + }, + }, + ), + Pointer( + PointerField { + decoder: BooleanDecoder, + serializer: super::Serializer { + name: "CEntityIdentity".to_string(), + fields: [ + Value( + ValueField { + decoder: SignedDecoder, + name: "m_nameStringableIndex".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nameStringableIndex".to_string(), + }, + ), + ].to_vec(), + }, + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bVisibleinPVS".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bVisibleinPVS".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bIsPlatform".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bIsPlatform".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_MoveCollide".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_MoveCollide".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_MoveType".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_MoveType".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nSubclassID".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nSubclassID".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flCreateTime".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flCreateTime".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_ubInterpolationFrame".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_ubInterpolationFrame".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_iTeamNum".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_iTeamNum".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_hEffectEntity".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_hEffectEntity".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_fEffects".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_fEffects".to_string(), + }, + ), + Value( + ValueField { + decoder: FloatCoordDecoder, + name: "m_flElasticity".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flElasticity".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bAnimatedEveryTick".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bAnimatedEveryTick".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flNavIgnoreUntilTime".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flNavIgnoreUntilTime".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nBloodType".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nBloodType".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nRenderMode".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nRenderMode".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nRenderFX".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nRenderFX".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_clrRender".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_clrRender".to_string(), + }, + ), + Vector( + VectorField { + field_enum: Box::new(Serializer( + SerializerField { + serializer: super::Serializer { + name: "EntityRenderAttribute_t".to_string(), + fields: [ + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_ID".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_ID".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_Values".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_Values".to_string(), + }, + ), + ].to_vec(), + }, + }, + )), + decoder: UnsignedDecoder, + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bRenderToCubemaps".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bRenderToCubemaps".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nInteractsAs".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nInteractsAs".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nInteractsWith".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nInteractsWith".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nInteractsExclude".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nInteractsExclude".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nEntityId".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nEntityId".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nOwnerId".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nOwnerId".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nHierarchyId".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nHierarchyId".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nCollisionGroup".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nCollisionGroup".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nCollisionFunctionMask".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nCollisionFunctionMask".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vecMins".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecMins".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vecMaxs".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecMaxs".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_usSolidFlags".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_usSolidFlags".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nSolidType".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nSolidType".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_triggerBloat".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_triggerBloat".to_string(), + }, + ), + Value( + ValueField { + decoder: Unsigned64Decoder, + name: "m_nSurroundType".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nSurroundType".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_CollisionGroup".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_CollisionGroup".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nEnablePhysics".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nEnablePhysics".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vecSpecifiedSurroundingMins".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecSpecifiedSurroundingMins".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vecSpecifiedSurroundingMaxs".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vecSpecifiedSurroundingMaxs".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vCapsuleCenter1".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vCapsuleCenter1".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vCapsuleCenter2".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vCapsuleCenter2".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flCapsuleRadius".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flCapsuleRadius".to_string(), + }, + ), + Value( + ValueField { + decoder: SignedDecoder, + name: "m_iGlowType".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_iGlowType".to_string(), + }, + ), + Value( + ValueField { + decoder: SignedDecoder, + name: "m_iGlowTeam".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_iGlowTeam".to_string(), + }, + ), + Value( + ValueField { + decoder: SignedDecoder, + name: "m_nGlowRange".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nGlowRange".to_string(), + }, + ), + Value( + ValueField { + decoder: SignedDecoder, + name: "m_nGlowRangeMin".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nGlowRangeMin".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_glowColorOverride".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_glowColorOverride".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bFlashing".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bFlashing".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flGlowTime".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flGlowTime".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flGlowStartTime".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flGlowStartTime".to_string(), + }, + ), + Value( + ValueField { + decoder: BooleanDecoder, + name: "m_bEligibleForScreenHighlight".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bEligibleForScreenHighlight".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flGlowBackfaceMult".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flGlowBackfaceMult".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_fadeMinDist".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_fadeMinDist".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_fadeMaxDist".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_fadeMaxDist".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flFadeScale".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flFadeScale".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flShadowStrength".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flShadowStrength".to_string(), + }, + ), + Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_nObjectCulling".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nObjectCulling".to_string(), + }, + ), + Value( + ValueField { + decoder: SignedDecoder, + name: "m_nAddDecal".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_nAddDecal".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vDecalPosition".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vDecalPosition".to_string(), + }, + ), + Value( + ValueField { + decoder: VectorNoscaleDecoder, + name: "m_vDecalForwardAxis".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_vDecalForwardAxis".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flDecalHealBloodRate".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flDecalHealBloodRate".to_string(), + }, + ), + Value( + ValueField { + decoder: NoscaleDecoder, + name: "m_flDecalHealHeightRate".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_flDecalHealHeightRate".to_string(), + }, + ), + Vector( + VectorField { + field_enum: Box::new(Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_ConfigEntitiesToPropagateMaterialDecalsTo".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_ConfigEntitiesToPropagateMaterialDecalsTo".to_string(), + }, + )), + decoder: UnsignedDecoder, + }, + ), + Array( + ArrayField { + field_enum: Box::new(Value( + ValueField { + decoder: UnsignedDecoder, + name: "m_bvDisabledHitGroups".to_string(), + should_parse: false, + prop_id: 0, + full_name: "None m_bvDisabledHitGroups".to_string(), + }, + )), + length: 1, + }, + ), + Pointer( + PointerField { + decoder: BooleanDecoder, + serializer: super::Serializer { + name: "CRenderComponent".to_string(), + fields: [].to_vec(), + }, + }, + ), + ].to_vec() + }; + + assert_eq!(&expected_parser, cworld_parser); + } +} diff --git a/src/parser/variant.rs b/src/parser/variant.rs new file mode 100644 index 0000000..ec4aba9 --- /dev/null +++ b/src/parser/variant.rs @@ -0,0 +1,26 @@ +#[derive(Debug, Clone, PartialEq)] +pub enum Variant { + Bool(bool), + U32(u32), + I32(i32), + I16(i16), + F32(f32), + U64(u64), + U8(u8), + String(String), + VecXY([f32; 2]), + VecXYZ([f32; 3]), + // Todo change to Vec + StringVec(Vec), + U32Vec(Vec), + U64Vec(Vec), + Stickers(Vec), +} +#[derive(Debug, Clone, PartialEq)] +pub struct Sticker { + pub name: String, + pub wear: f32, + pub id: u32, + pub x: f32, + pub y: f32, +} diff --git a/testfiles/ancient_sendtables.b b/testfiles/ancient_sendtables.b new file mode 100644 index 0000000..2142484 Binary files /dev/null and b/testfiles/ancient_sendtables.b differ diff --git a/tests/parse.rs b/tests/parse.rs index 3c0c855..11d54d6 100644 --- a/tests/parse.rs +++ b/tests/parse.rs @@ -27,13 +27,15 @@ fn mirage_1() { .player_info .get(death.userid.as_ref().unwrap()) .unwrap(); - dbg!(died_user); + // dbg!(died_user); } _ => {} }, _ => {} }; } + + todo!() } #[test]