#[cfg(test)]
#[path = "../tests/net/outgoing.rs"]
mod tests;
use super::messages::{
ANGLE_BITS, DECAL_INDEX_BITS, MODEL_INDEX_BITS, PAYLOAD_LENGTH_BITS, SERVER_CLASS_BITS,
SOUND_INDEX_BITS, VOICE_LENGTH_BITS,
};
use super::{MESSAGE_TYPE_BITS, MessageId};
use crate::NotThreadSafe;
use crate::bitbuf::{BitReader, BitWriter, Overflow};
use sdk_raw::bitbuf::BfWrite;
use sdk_raw::edicts::MAX_EDICT_BITS;
use sdk_raw::interfaces::game_event::MAX_EVENT_BITS;
use sdk_raw::util::cstr::copy_cstr;
use sdk_raw::vcall;
use std::ffi::{CString, c_int};
use std::marker::PhantomData;
use std::ptr::NonNull;
const DELTA_SIZE_BITS: u32 = 20;
const EVENT_LIST_LENGTH_BITS: u32 = 20;
const MAX_ENCODED_WORDS: usize = 1 << 17;
const SMALL_ENCODED_WORDS: usize = 1 << 12;
const SOUND_LENGTH_BITS: u32 = 8;
const SOUNDS_LENGTH_BITS: u32 = 16;
const STRING_TABLE_LENGTH_BITS: u32 = 20;
const TABLE_ID_BITS: u32 = 5;
#[derive(Debug, Clone, PartialEq)]
pub enum Details {
Other,
EntityMessage {
entity: u16,
class_id: u16,
data: BitWriter,
},
GameEvent {
data: BitWriter,
},
PacketEntities {
delta_from: Option<i32>,
updated: u16,
},
Print {
text: CString,
},
Sounds {
count: u8,
reliable: bool,
},
TempEntities {
count: u8,
reliable: bool,
},
Tick {
tick: i32,
},
UpdateStringTable {
table: u8,
changed: u16,
},
UserMessage {
message_type: u8,
data: BitWriter,
},
Unparsed,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EncodedMessage {
pub id: MessageId,
pub bits: usize,
pub details: Details,
}
#[doc(alias("INetMessage"))]
#[derive(Debug, Clone, Copy)]
pub struct OutgoingMessage<'s> {
raw: NonNull<sys::INetMessage>,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> OutgoingMessage<'s> {
pub const unsafe fn from_raw(raw: NonNull<sys::INetMessage>) -> Self {
Self {
raw,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
const fn as_const(self) -> *const sys::INetMessage {
self.raw.as_ptr().cast_const()
}
pub const fn as_ptr(self) -> *mut sys::INetMessage {
self.raw.as_ptr()
}
#[doc(alias("ToString"))]
pub fn describe(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_const() => INetMessage_ToString())) }
}
#[doc(alias("WriteToBuffer"))]
pub fn encode(self) -> Option<BitWriter> {
let mut storage = [0; SMALL_ENCODED_WORDS];
match self.encode_into(&mut storage) {
Err(Overflow) => self
.encode_into(&mut vec![0; MAX_ENCODED_WORDS])
.ok()
.flatten(),
Ok(bits) => bits,
}
}
fn encode_into(self, storage: &mut [u32]) -> Result<Option<BitWriter>, Overflow> {
let mut buffer = BfWrite::empty(storage);
let written =
unsafe { vcall!(self.as_ptr() => INetMessage_WriteToBuffer(buffer.as_raw())) };
if buffer.overflow != 0 {
return Err(Overflow);
}
Ok(written
.then(|| unsafe { BfWrite::read_back(NonNull::from(&mut buffer)) })
.flatten()
.map(BitWriter::from))
}
#[doc(alias("GetGroup"))]
pub fn group(self) -> c_int {
unsafe { vcall!(self.as_const() => INetMessage_GetGroup()) }
}
#[doc(alias("GetType"))]
pub fn id(self) -> Option<MessageId> {
let id = unsafe { vcall!(self.as_const() => INetMessage_GetType()) };
u8::try_from(id).ok().and_then(MessageId::new)
}
#[doc(alias("IsReliable"))]
pub fn is_reliable(self) -> bool {
unsafe { vcall!(self.as_const() => INetMessage_IsReliable()) }
}
#[doc(alias("GetName"))]
pub fn name(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_const() => INetMessage_GetName())) }
}
}
#[derive(Debug, Clone)]
pub struct Walk<'a> {
reader: BitReader<'a>,
}
impl Walk<'_> {
pub const fn remaining(&self) -> usize {
self.reader.remaining()
}
}
impl Iterator for Walk<'_> {
type Item = EncodedMessage;
fn next(&mut self) -> Option<EncodedMessage> {
if self.reader.remaining() < MESSAGE_TYPE_BITS as usize {
return None;
}
let start = self.reader.position();
let id = MessageId::new(self.reader.read_ubits(MESSAGE_TYPE_BITS).ok()? as u8)?;
let mut fields = self.reader.clone();
let details = match read_fields(id, &mut fields) {
Ok(Some(details)) => {
self.reader = fields;
details
}
Ok(None) | Err(Overflow) => {
self.reader.skip(self.reader.remaining()).ok()?;
Details::Unparsed
}
};
Some(EncodedMessage {
id,
bits: self.reader.position() - start,
details,
})
}
}
fn read_fields(id: MessageId, reader: &mut BitReader<'_>) -> Result<Option<Details>, Overflow> {
let details = match id {
MessageId::NOP => Details::Other,
MessageId::FILE => {
reader.skip(32)?;
skip_string(reader)?;
reader.skip(1)?;
Details::Other
}
MessageId::TICK => {
let tick = reader.read_i32()?;
reader.skip(16 + 16)?;
Details::Tick { tick }
}
MessageId::STRING_CMD => {
skip_string(reader)?;
Details::Other
}
MessageId::SET_CONVAR => {
for _ in 0..reader.read_u8()? {
skip_string(reader)?;
skip_string(reader)?;
}
Details::Other
}
MessageId::SIGNON_STATE => {
reader.skip(8 + 32)?;
Details::Other
}
MessageId::PRINT => Details::Print {
text: reader.read_cstring()?,
},
MessageId::SERVER_INFO => {
reader.skip(16 + 32 + 1 + 1 + 32 + 16)?;
reader.skip(16 * 8)?;
reader.skip(8 + 8 + 32 + 8)?;
for _ in 0..4 {
skip_string(reader)?;
}
reader.skip(1)?;
Details::Other
}
MessageId::CLASS_INFO => {
reader.skip(16)?;
if !reader.read_bit()? {
return Ok(None);
}
Details::Other
}
MessageId::SET_PAUSE => {
reader.skip(1)?;
Details::Other
}
MessageId::CREATE_STRING_TABLE => {
skip_string(reader)?;
let most = reader.read_u16()?;
reader.skip(most.checked_ilog2().unwrap_or(0) as usize + 1)?;
let length = reader.read_var_u32()?;
if reader.read_bit()? {
reader.skip(12 + 4)?;
}
reader.skip(1)?;
reader.skip(length as usize)?;
Details::Other
}
MessageId::UPDATE_STRING_TABLE => {
let table = reader.read_ubits(TABLE_ID_BITS)? as u8;
let changed = match reader.read_bit()? {
true => reader.read_u16()?,
false => 1,
};
let length = reader.read_ubits(STRING_TABLE_LENGTH_BITS)?;
reader.skip(length as usize)?;
Details::UpdateStringTable { table, changed }
}
MessageId::VOICE_INIT => {
skip_string(reader)?;
if reader.read_u8()? == 255 {
reader.skip(16)?;
}
Details::Other
}
MessageId::VOICE_DATA => {
reader.skip(8 + 8)?;
let length = reader.read_ubits(VOICE_LENGTH_BITS)?;
reader.skip(length as usize)?;
Details::Other
}
MessageId::SOUNDS => {
let reliable = reader.read_bit()?;
let (count, length) = match reliable {
true => (1, reader.read_ubits(SOUND_LENGTH_BITS)?),
false => (reader.read_u8()?, reader.read_ubits(SOUNDS_LENGTH_BITS)?),
};
reader.skip(length as usize)?;
Details::Sounds { count, reliable }
}
MessageId::SET_VIEW => {
reader.skip(MAX_EDICT_BITS as usize)?;
Details::Other
}
MessageId::FIX_ANGLE => {
reader.skip(1 + 3 * ANGLE_BITS as usize)?;
Details::Other
}
MessageId::BSP_DECAL => {
reader.read_bit_vec3_coord()?;
reader.skip(DECAL_INDEX_BITS as usize)?;
if reader.read_bit()? {
reader.skip((MAX_EDICT_BITS + MODEL_INDEX_BITS) as usize)?;
}
reader.skip(1)?;
Details::Other
}
MessageId::USER_MESSAGE => {
let message_type = reader.read_u8()?;
Details::UserMessage {
message_type,
data: read_payload(reader)?,
}
}
MessageId::ENTITY_MESSAGE => {
let entity = reader.read_ubits(MAX_EDICT_BITS)? as u16;
let class_id = reader.read_ubits(SERVER_CLASS_BITS)? as u16;
Details::EntityMessage {
entity,
class_id,
data: read_payload(reader)?,
}
}
MessageId::GAME_EVENT => Details::GameEvent {
data: read_payload(reader)?,
},
MessageId::PACKET_ENTITIES => {
reader.skip(MAX_EDICT_BITS as usize)?;
let delta_from = match reader.read_bit()? {
true => Some(reader.read_i32()?),
false => None,
};
reader.skip(1)?;
let updated = reader.read_ubits(MAX_EDICT_BITS)? as u16;
let length = reader.read_ubits(DELTA_SIZE_BITS)?;
reader.skip(1)?;
reader.skip(length as usize)?;
Details::PacketEntities {
delta_from,
updated,
}
}
MessageId::TEMP_ENTITIES => {
let count = reader.read_u8()?;
let length = reader.read_var_u32()?;
reader.skip(length as usize)?;
Details::TempEntities {
count: count.max(1),
reliable: count == 0,
}
}
MessageId::PREFETCH => {
reader.skip(SOUND_INDEX_BITS as usize)?;
Details::Other
}
MessageId::MENU => {
reader.skip(16)?;
let length = reader.read_u16()?;
reader.skip(usize::from(length) * 8)?;
Details::Other
}
MessageId::GAME_EVENT_LIST => {
reader.skip(MAX_EVENT_BITS as usize)?;
let length = reader.read_ubits(EVENT_LIST_LENGTH_BITS)?;
reader.skip(length as usize)?;
Details::Other
}
MessageId::GET_CVAR_VALUE => {
reader.skip(32)?;
skip_string(reader)?;
Details::Other
}
MessageId::CMD_KEY_VALUES => {
let length = reader.read_u32()? as usize;
reader.skip(length.checked_mul(8).ok_or(Overflow)?)?;
Details::Other
}
_ => return Ok(None),
};
Ok(Some(details))
}
fn read_payload(reader: &mut BitReader<'_>) -> Result<BitWriter, Overflow> {
let length = reader.read_ubits(PAYLOAD_LENGTH_BITS)?;
reader.read_bits(length as usize)
}
fn skip_string(reader: &mut BitReader<'_>) -> Result<(), Overflow> {
while reader.read_u8()? != 0 {}
Ok(())
}
pub fn walk(reader: BitReader<'_>) -> Walk<'_> {
Walk { reader }
}