diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 1938d78..f33c0d8 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta 0.3.0", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -151,7 +151,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -227,7 +227,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -301,9 +301,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ "either", ] @@ -335,7 +335,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -356,9 +356,9 @@ checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "log" -version = "0.4.28" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "main_error" @@ -397,7 +397,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -430,11 +430,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ - "num_enum_derive 0.7.4", + "num_enum_derive 0.7.6", "rustversion", ] @@ -452,14 +452,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -487,13 +487,13 @@ dependencies = [ [[package]] name = "parse-display" -version = "0.9.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +checksum = "e78deb158fb1d73b29efb4b7e9b9860b78059c670de06bd28df8d0b458ded0eb" dependencies = [ - "parse-display-derive 0.9.1", + "parse-display-derive 0.11.0", "regex", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", ] [[package]] @@ -513,16 +513,16 @@ dependencies = [ [[package]] name = "parse-display-derive" -version = "0.9.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +checksum = "8e95a50d1084dab562913062c4c34bb204b68fc6ec38a1395909ff5aaaf4f10a" dependencies = [ "proc-macro2", "quote", "regex", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", "structmeta 0.3.0", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -578,48 +578,48 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.11.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", ] [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", ] [[package]] @@ -630,9 +630,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustversion" @@ -640,12 +640,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -657,9 +651,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -678,46 +672,46 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 3.0.5", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serde_repr" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 3.0.5", ] [[package]] @@ -728,9 +722,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "snap" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" +checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886" [[package]] name = "strsim" @@ -759,7 +753,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive 0.3.0", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -781,7 +775,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -797,9 +791,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -808,7 +813,7 @@ dependencies = [ [[package]] name = "tf-demo-parser" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bitbuffer", "const-fnv1a-hash", @@ -818,8 +823,8 @@ dependencies = [ "log", "main_error", "num-traits", - "num_enum 0.7.4", - "parse-display 0.9.1", + "num_enum 0.7.6", + "parse-display 0.11.0", "serde", "serde_json", "serde_repr", @@ -829,22 +834,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 3.0.5", ] [[package]] @@ -911,7 +916,7 @@ checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -959,7 +964,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -994,7 +999,7 @@ checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1029,7 +1034,7 @@ checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.119", ] [[package]] @@ -1191,3 +1196,9 @@ checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/wasm/src/cut/mod.rs b/wasm/src/cut/mod.rs index 6846b2f..2cea2a0 100644 --- a/wasm/src/cut/mod.rs +++ b/wasm/src/cut/mod.rs @@ -39,7 +39,9 @@ pub fn cut(input: &[u8], options: EditOptions) -> Vec { let mut stream = demo.get_stream(); let mut header = Header::read(&mut stream).unwrap(); - let mut mutators = options.as_mutator(spectator_id); + let mut mutators = options + .as_mutator(spectator_id, &header, &stream) + .expect("invalid demo"); let start_tick = options.cut.unwrap().from; let end_tick = options.cut.unwrap().to; @@ -105,9 +107,9 @@ pub fn cut(input: &[u8], options: EditOptions) -> Vec { } // create the net ticks needed for later deltas - let fill_ticks = (delta_tick + 1) - .range_inclusive(start_state.server_tick) - .map(net_tick); + let next_tick = delta_tick + 1; + let tick_range = next_tick.range_inclusive(start_state.server_tick); + let fill_ticks = tick_range.map(net_tick); let fill_packets = fill_ticks.map(|msg| { Packet::Message(MessagePacket { messages: vec![ diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 1eefcbe..8507f0e 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -4,8 +4,8 @@ mod cut; pub mod missing_preserve; mod mutate; mod options; -mod pov; mod player_count; +mod pov; use bitbuffer::{BitRead, BitWriteStream, LittleEndian}; use tf_demo_parser::demo::header::Header; @@ -92,12 +92,15 @@ fn no_cut(input: &[u8], options: EditOptions) -> Vec { let mut output_handler = DemoHandler::default(); output_handler.handle_header(&header); - let mutators = options.as_mutator(spectator_id); + let mut mutators = options + .as_mutator(spectator_id, &header, &stream) + .expect("invalid demo"); while let Some(mut packet) = packets.next(&handler.state_handler).unwrap() { - handler.handle_packet(packet.clone()).expect("failed to handle source packet"); + handler + .handle_packet(packet.clone()) + .expect("failed to handle source packet"); mutators.mutate_packet(&mut packet, &handler.state_handler); - output_handler.handle_packet(packet.clone()).expect("failed to handle mutated packet"); if packet.packet_type() != PacketType::ConsoleCmd && packet.packet_type() != PacketType::UserCmd @@ -106,6 +109,10 @@ fn no_cut(input: &[u8], options: EditOptions) -> Vec { .encode(&mut out_stream, &output_handler.state_handler) .unwrap(); } + + output_handler + .handle_packet(packet) + .expect("failed to handle mutated packet"); } } out_buffer diff --git a/wasm/src/mutate.rs b/wasm/src/mutate.rs index ada4dc1..2389c7a 100644 --- a/wasm/src/mutate.rs +++ b/wasm/src/mutate.rs @@ -4,7 +4,7 @@ use tf_demo_parser::demo::packet::Packet; use tf_demo_parser::ParserState; pub trait PacketMutator { - fn mutate_packet(&self, packet: &mut Packet, state: &ParserState); + fn mutate_packet(&mut self, packet: &mut Packet, state: &ParserState); } pub trait MessageMutator { @@ -20,7 +20,7 @@ pub struct PacketMessageMutator { } impl PacketMutator for PacketMessageMutator { - fn mutate_packet(&self, packet: &mut Packet, state: &ParserState) { + fn mutate_packet(&mut self, packet: &mut Packet, state: &ParserState) { match packet { Packet::Message(msg_packet) | Packet::Signon(msg_packet) => { msg_packet @@ -34,7 +34,7 @@ impl PacketMutator for PacketMessageMutator { } impl PacketMutator for F { - fn mutate_packet(&self, packet: &mut Packet, _state: &ParserState) { + fn mutate_packet(&mut self, packet: &mut Packet, _state: &ParserState) { self(packet) } } @@ -56,7 +56,7 @@ pub struct PacketMessageFilter { } impl PacketMutator for PacketMessageFilter { - fn mutate_packet(&self, packet: &mut Packet, _state: &ParserState) { + fn mutate_packet(&mut self, packet: &mut Packet, _state: &ParserState) { match packet { Packet::Message(msg_packet) | Packet::Signon(msg_packet) => { let messages = take(&mut msg_packet.messages); @@ -108,8 +108,8 @@ impl MutatorList { } impl PacketMutator for MutatorList { - fn mutate_packet(&self, packet: &mut Packet, state: &ParserState) { - for mutator in self.mutators.iter() { + fn mutate_packet(&mut self, packet: &mut Packet, state: &ParserState) { + for mutator in self.mutators.iter_mut() { mutator.mutate_packet(packet, state); } } diff --git a/wasm/src/options.rs b/wasm/src/options.rs index 498c2fc..c708265 100644 --- a/wasm/src/options.rs +++ b/wasm/src/options.rs @@ -1,10 +1,12 @@ use crate::missing_preserve::removed_invalid_deletes; +use crate::player_count::fix_player_count; use crate::{clean_demo, strip_cond, unlock_pov, MutatorList}; use serde::{Deserialize, Serialize}; use tf_demo_parser::demo::data::DemoTick; +use tf_demo_parser::demo::header::Header; use tf_demo_parser::demo::message::packetentities::EntityId; use tf_demo_parser::demo::message::Message; -use crate::player_count::fix_player_count; +use tf_demo_parser::{ParseError, Stream}; #[derive(Debug, Serialize, Deserialize, Default)] #[non_exhaustive] @@ -27,7 +29,12 @@ pub struct EditOptions { } impl EditOptions { - pub fn as_mutator(&self, spectator_id: EntityId) -> MutatorList { + pub fn as_mutator( + &self, + spectator_id: EntityId, + header: &Header, + stream: &Stream, + ) -> Result { let mut mutators = MutatorList::new(); mutators.push_message_mutator(|message: &mut Message| { @@ -56,10 +63,10 @@ impl EditOptions { } if self.fix_player_count { - fix_player_count(&mut mutators); + fix_player_count(&mut mutators, header, stream)?; } - mutators + Ok(mutators) } } diff --git a/wasm/src/player_count.rs b/wasm/src/player_count.rs index 436531b..324b53d 100644 --- a/wasm/src/player_count.rs +++ b/wasm/src/player_count.rs @@ -1,57 +1,43 @@ +use std::collections::HashSet; + use crate::mutate::{MutatorList, PacketMutator}; +use tf_demo_parser::demo::header::Header; +use tf_demo_parser::demo::message::Message; +use tf_demo_parser::demo::packet::datatable::{DataTablePacket, SendTable}; +use tf_demo_parser::demo::packet::stringtable::{ExtraData, StringTable, StringTableEntry}; use tf_demo_parser::demo::packet::Packet; -use tf_demo_parser::ParserState; +use tf_demo_parser::demo::parser::state::StaticBaseline; +use tf_demo_parser::demo::parser::{DemoHandler, RawPacketStream}; +use tf_demo_parser::{ParseError, ParserState, Stream}; -#[derive(Default)] -pub struct PlayerCount {} +pub struct PlayerCount { + baselines: Option, + string_table_names: Vec, +} -/// property arrays that got renamed and new items -const PLAYER_ARRAY_PROPS_RENAMED: &[(&str, u16, u16)] = &[ - ("m_aBonusPlayerRoll_33", 33, 101), - ("m_ConditionData_128", 128, 131), -]; +fn find_data_tables(header: &Header, stream: Stream) -> Result { + let mut packets = RawPacketStream::new(stream); + let mut handler = DemoHandler::default(); + handler.handle_header(header); + while let Some(packet) = packets.next(&handler.state_handler)? { + if let Packet::DataTables(tables) = &packet { + return Ok(tables.clone()); + } + handler.handle_packet(packet)?; + } + Err(ParseError::InvalidDemo("missing data tables")) +} -/// property arrays that just got new items -const PLAYER_ARRAY_PROPS: &[(&str, u16)] = &[ - ("m_iPing", 101), - ("m_iScore", 101), - ("m_iDeaths", 101), - ("m_bConnected", 101), - ("m_iTeam", 101), - ("m_bAlive", 101), - ("m_iHealth", 101), - ("m_iAccountID", 101), - ("m_bValid", 101), - ("m_iUserID", 101), - ("m_bPlayerReady", 101), - ("m_ePlayerWantsRematch", 101), - ("m_bPlayerIsPackMember", 101), - ("m_bPlayerDominated", 101), - ("m_bPlayerDominatingMe", 101), - ("m_iTotalScore", 101), - ("m_iMaxHealth", 101), - ("m_iMaxBuffedHealth", 101), - ("m_iPlayerClass", 101), - ("m_bArenaSpectator", 101), - ("m_iActiveDominations", 101), - ("m_flNextRespawnTime", 101), - ("m_iChargeLevel", 101), - ("m_iDamage", 101), - ("m_iDamageAssist", 101), - ("m_iDamageBoss", 101), - ("m_iHealing", 101), - ("m_iHealingAssist", 101), - ("m_iDamageBlocked", 101), - ("m_iCurrencyCollected", 101), - ("m_iBonusPoints", 101), - ("m_iPlayerLevel", 101), - ("m_iUpgradeRefundCredits", 101), - ("m_iBuybackCredits", 101), - ("m_iPlayerClassWhenKilled", 101), - ("m_iConnectionState", 101), - ("m_flConnectTime", 101), - ("m_iStreaks", 407), -]; +impl PlayerCount { + pub fn new(header: &Header, stream: Stream) -> Result { + let data_tables = find_data_tables(header, stream)?; + let baselines = Baselines::new(&header, &data_tables)?; + Ok(PlayerCount { + baselines, + string_table_names: Vec::new(), + }) + } +} // property values that got changed const PLAYER_PROPS_VALUES: &[(&str, &str, u16, u16)] = &[ @@ -60,61 +46,163 @@ const PLAYER_PROPS_VALUES: &[(&str, &str, u16, u16)] = &[ ("DT_TFTeam", r#""team_object_array""#, 198, 606), ]; +fn new_prop_size(table: &str, prop: &str) -> Option { + for (changed_table, changed_prop, _, new_size) in PLAYER_PROPS_VALUES.iter().copied() { + if changed_table == table && changed_prop == prop { + return Some(new_size); + } + } + None +} + impl PacketMutator for PlayerCount { - fn mutate_packet(&self, packet: &mut Packet, _state: &ParserState) { - if let Packet::DataTables(data_tables) = packet { - for table in &mut data_tables.tables { - let table_name = table.name.as_str(); - if let Some(new_length) = PLAYER_ARRAY_PROPS.iter().copied().find_map(|(name, new_length)| (name == table_name).then_some(new_length)) { - let base = table.props.last().unwrap().clone(); - let old_length = table.props.len() as u16; - for i in old_length ..= new_length { - let mut prop = base.clone(); - prop.name = format!("{i:03}").into(); - table.props.push(prop); - } - } else if let Some((old_length, new_length)) = PLAYER_ARRAY_PROPS_RENAMED.iter().copied().find_map(|(name, old_length, new_length)| table_name.ends_with(name).then_some((old_length, new_length))) { - if table_name.starts_with("_ST_") { - let base = table.props.last().unwrap().clone(); - for i in old_length.. new_length { // non-inclusive range, because for some reasons these arrays are one short - let mut prop = base.clone(); - prop.name = format!("{i:03}").into(); - table.props.push(prop); - } - } else if table_name.starts_with("_LPT_") { - table.props[0].name = format!("lengthprop{new_length}").into(); - } - let name_prefix = table_name.rsplit_once('_').unwrap().0; - table.name = format!("{name_prefix}_{}", new_length).into(); - } else if let Some((prop_name, _, new_length)) = PLAYER_PROPS_VALUES.iter().copied().find_map(|(name, prop_name, old_length, new_length)| table_name.eq(name).then_some((prop_name, old_length, new_length))) { - for prop in &mut table.props { - if prop.name == prop_name { - prop.element_count = new_length.into(); - } - } - } - for prop in &mut table.props { - if let Some(target_name) = &mut prop.table_name { - if let Some((_, new_length)) = PLAYER_ARRAY_PROPS_RENAMED.iter().copied().find_map(|(name, old_length, new_length)| target_name.ends_with(name).then_some((old_length, new_length))) { - let name_prefix = target_name.rsplit_once('_').unwrap().0; - *target_name = format!("{name_prefix}_{}", new_length).into(); - } - } - } - // let table_name = table.name.as_str(); - // for prop in &mut table.props { - // println!( - // "{}.{}: {}", - // table_name, - // prop.name, - // prop.element_count.unwrap_or_default() - // ); - // } + fn mutate_packet(&mut self, packet: &mut Packet, _state: &ParserState) { + match packet { + Packet::DataTables(data_tables) => { + fixup_tables(data_tables); } + Packet::StringTables(tables_packet) => { + if let Some(baselines) = self.baselines.as_mut() { + for table in tables_packet.tables.iter_mut() { + self.string_table_names.push(table.name.as_ref().into()); + baselines.rewrite_table(table); + } + } + } + Packet::Signon(message_packet) | Packet::Message(message_packet) => { + for msg in message_packet.messages.iter_mut() { + if let Some(baselines) = self.baselines.as_mut() { + baselines.rewrite_message(msg, &self.string_table_names); + } + } + } + _ => {} } } } -pub fn fix_player_count(mutators: &mut MutatorList) { - mutators.push_packet_mutator(PlayerCount::default()) +pub fn fix_player_count( + mutators: &mut MutatorList, + header: &Header, + stream: &Stream, +) -> Result<(), ParseError> { + PlayerCount::new(header, stream.clone()) + .map(|player_count| mutators.push_packet_mutator(player_count)) +} + +/// Raise the MAX_PLAYERS arrays from the pre-100-player sizes, which a current +/// client rejects with "RecvProp type doesn't match server type". +/// +/// Also changes the encoding: the length field is log2(element_count) + 1 bits. +/// See `Baselines`. +fn fixup_tables(tables: &mut DataTablePacket) -> bool { + let mut changed = false; + for table in tables.tables.iter_mut() { + for prop in table.props.iter_mut() { + if let Some(size) = new_prop_size(table.name.as_str(), prop.name.as_str()) { + if prop.element_count != Some(size) { + prop.element_count = Some(size); + changed = true; + } + } + } + } + changed +} + +/// Re-encodes instance baselines to match the tables `fixup_tables` rewrote. +/// +/// Left stale, SourceTV demos decode team data wrongly and POV demos overflow +/// the baseline merge buffer. +struct Baselines { + old: Vec, + new: Vec, + /// Classes whose props encode differently. The rest are left untouched. + affected: HashSet, +} + +impl Baselines { + fn new(header: &Header, tables: &DataTablePacket) -> Result, ParseError> { + let mut fixed = tables.clone(); + if !fixup_tables(&mut fixed) { + return Ok(None); + } + + let mut before = DemoHandler::default(); + before.handle_header(header); + before.handle_packet(Packet::DataTables(tables.clone()))?; + + let mut after = DemoHandler::default(); + after.handle_header(header); + after.handle_packet(Packet::DataTables(fixed))?; + + let old = before.state_handler.send_tables.clone(); + let new = after.state_handler.send_tables.clone(); + let affected = old + .iter() + .zip(new.iter()) + .enumerate() + .filter(|(_, (o, n))| o.flattened_props != n.flattened_props) + .map(|(id, _)| id as u32) + .collect(); + + Ok(Some(Baselines { old, new, affected })) + } + + /// `table_names` is the handler's `string_table_names`, which maps the id + /// an UpdateStringTable carries back to the table it belongs to. + fn rewrite_message(&mut self, message: &mut Message, string_tables: &[String]) { + match message { + Message::CreateStringTable(create) => self.rewrite_table(&mut create.table), + Message::UpdateStringTable(update) => { + let name = string_tables + .get(update.table_id as usize) + .map(String::as_str); + if name == Some("instancebaseline") { + for (_, entry) in update.entries.iter_mut() { + self.rewrite_entry(entry); + } + } + } + _ => {} + } + } + + fn rewrite_table(&mut self, table: &mut StringTable) { + if table.name.as_ref() != "instancebaseline" { + return; + } + for (_, entry) in table.entries.iter_mut() { + self.rewrite_entry(entry); + } + } + + fn rewrite_entry(&mut self, entry: &mut StringTableEntry) { + let Some(class_id) = entry + .text + .as_ref() + .and_then(|t| t.trim().parse::().ok()) + else { + return; + }; + if !self.affected.contains(&class_id) { + return; + } + let (Some(from), Some(to), Some(extra)) = ( + self.old.get(class_id as usize), + self.new.get(class_id as usize), + entry.extra_data.as_ref(), + ) else { + return; + }; + + let baseline = StaticBaseline { + class_id: (class_id as u16).into(), + raw: extra.data.to_owned(), + }; + // A blob we cannot read is better left alone than half written. + if let Ok(raw) = baseline.reencode(from, to) { + entry.extra_data = Some(ExtraData::new(raw)); + } + } }