mirror of
https://codeberg.org/demostf/parser.git
synced 2026-10-01 08:44:05 +02:00
CreateStringTable and TempEntities both read their payload length as a fixed width field on protocol 23 and older and as a var int after that, but the encoders always write the var int. Reencoding an older demo therefore hands the game a length it can't read: string tables come back as "bogus substring index", and any packet carrying temp entities is malformed from there on, taking whatever messages follow it in that packet with it. The threshold and both widths are taken from the read paths a few lines up in these same two files rather than worked out separately, so the two sides can't drift: > 23, with 20 bits for CreateStringTable and 17 for TempEntities. The engine agrees, for what it's worth - netmessages.cpp reads both of these under > PROTOCOL_VERSION_23 at NET_MAX_PAYLOAD_BITS_V23 + 3 and NET_MAX_PAYLOAD_BITS_V23 respectively, and that constant is 17. serverinfo and prefetch already branch on the protocol on both sides, these two were the only ones that didn't. On a protocol 15 demo that was 28909 of 92518 reencoded packets failing to parse back; on test_data/protocol23.dem, 13220 of 43327. Both are zero now, so protocol23.dem can join the reencode round trip test. Protocol 24 output is unchanged, byte for byte.
152 lines
5.1 KiB
Rust
152 lines
5.1 KiB
Rust
use super::stringtable::read_var_int;
|
|
use crate::demo::message::packetentities::PacketEntitiesMessage;
|
|
#[cfg(feature = "write")]
|
|
use crate::demo::message::stringtable::encode_var_int_fixed;
|
|
use crate::demo::packet::datatable::ClassId;
|
|
#[cfg(feature = "write")]
|
|
use crate::demo::parser::Encode;
|
|
use crate::demo::parser::ParseBitSkip;
|
|
use crate::demo::sendprop::SendProp;
|
|
use crate::Result;
|
|
use crate::{Parse, ParseError, ParserState, Stream};
|
|
#[cfg(feature = "write")]
|
|
use bitbuffer::{BitWrite, BitWriteSized, BitWriteStream, LittleEndian};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
|
pub struct TempEntitiesMessage {
|
|
pub events: Vec<EventInfo>,
|
|
}
|
|
|
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
pub struct EventInfo {
|
|
pub class_id: ClassId,
|
|
pub fire_delay: f32,
|
|
pub reliable: bool,
|
|
pub props: Vec<SendProp>,
|
|
}
|
|
|
|
impl Parse<'_> for TempEntitiesMessage {
|
|
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> {
|
|
let count: u8 = stream.read()?;
|
|
let length = if state.protocol_version > 23 {
|
|
read_var_int(stream)?
|
|
} else {
|
|
stream.read_sized(17)?
|
|
};
|
|
let data = stream.read_bits(length as usize)?;
|
|
let mut stream = data.clone();
|
|
let stream = &mut stream;
|
|
|
|
let (count, reliable) = if count == 0 {
|
|
(1, true)
|
|
} else {
|
|
(count, false)
|
|
};
|
|
|
|
let mut events: Vec<EventInfo> = Vec::with_capacity(count as usize);
|
|
|
|
for _ in 0..count {
|
|
let delay = if stream.read()? {
|
|
let raw: u8 = stream.read()?;
|
|
raw as f32 / 100.0
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let class_id = if stream.read()? {
|
|
let bits = state.server_class_bits;
|
|
(stream.read_sized::<u16>(bits)?.saturating_sub(1)).into()
|
|
} else {
|
|
let last = events.last().ok_or(ParseError::InvalidDemo(
|
|
"temp entity update without previous",
|
|
))?;
|
|
|
|
last.class_id
|
|
};
|
|
let send_table = state
|
|
.send_tables
|
|
.get(usize::from(class_id))
|
|
.ok_or(ParseError::UnknownServerClass(class_id))?;
|
|
|
|
let mut props = Vec::new();
|
|
PacketEntitiesMessage::read_update(stream, send_table, &mut props, 0u32.into())?;
|
|
|
|
events.push(EventInfo {
|
|
class_id,
|
|
fire_delay: delay,
|
|
reliable,
|
|
props,
|
|
});
|
|
}
|
|
|
|
Ok(TempEntitiesMessage { events })
|
|
}
|
|
}
|
|
|
|
impl ParseBitSkip<'_> for TempEntitiesMessage {
|
|
fn parse_skip(stream: &mut Stream, state: &ParserState) -> Result<()> {
|
|
let _: u8 = stream.read()?;
|
|
let length = if state.protocol_version > 23 {
|
|
read_var_int(stream)?
|
|
} else {
|
|
stream.read_sized(17)?
|
|
};
|
|
stream.skip_bits(length as usize)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "write")]
|
|
impl Encode for TempEntitiesMessage {
|
|
fn encode(&self, stream: &mut BitWriteStream<LittleEndian>, state: &ParserState) -> Result<()> {
|
|
let count = match (self.events.len(), self.events.first()) {
|
|
(1, Some(event)) if event.reliable => 0,
|
|
(1, _) => 1,
|
|
(len, _) => len as u8,
|
|
};
|
|
count.write(stream)?;
|
|
|
|
// threshold and both widths are the ones used by the read side above
|
|
let (length_bits, encode_length): (usize, fn(u32) -> u64) =
|
|
if state.protocol_version > 23 {
|
|
(40, encode_var_int_fixed)
|
|
} else {
|
|
(17, |length| length as u64)
|
|
};
|
|
stream.reserve_int::<ParseError, _>(length_bits, |stream| {
|
|
let start = stream.bit_len();
|
|
let mut last_class_id = u16::MAX.into();
|
|
|
|
for event in self.events.iter() {
|
|
if event.fire_delay > 0.0 {
|
|
true.write(stream)?;
|
|
((event.fire_delay * 100.0) as u8).write(stream)?;
|
|
} else {
|
|
false.write(stream)?;
|
|
}
|
|
|
|
if event.class_id != last_class_id {
|
|
true.write(stream)?;
|
|
let bits = state.server_class_bits;
|
|
let id: u16 = event.class_id.into();
|
|
(id + 1).write_sized(stream, bits)?;
|
|
} else {
|
|
false.write(stream)?;
|
|
}
|
|
last_class_id = event.class_id;
|
|
|
|
let send_table = state
|
|
.send_tables
|
|
.get(usize::from(event.class_id))
|
|
.ok_or(ParseError::UnknownServerClass(event.class_id))?;
|
|
PacketEntitiesMessage::write_update(&event.props, stream, send_table, 0u32.into())?;
|
|
}
|
|
let end = stream.bit_len();
|
|
Ok(encode_length((end - start) as u32))
|
|
})?;
|
|
Ok(())
|
|
}
|
|
}
|