pub mod cheats;
pub mod incoming;
pub mod messages;
#[cfg(test)]
#[path = "../tests/net.rs"]
mod tests;
use crate::NotThreadSafe;
use crate::bitbuf::BitWriter;
use sdk_raw::bitbuf::BfWrite;
use sdk_raw::net::{FLOW_INCOMING, FLOW_OUTGOING};
use sdk_raw::util::cstr::copy_cstr;
use sdk_raw::vcall;
use std::ffi::{CString, c_int};
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::ptr::NonNull;
#[doc(alias("NETMSG_TYPE_BITS"))]
pub const MESSAGE_TYPE_BITS: u32 = 6;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EncodeError {
#[error("{field} is {len} bytes long, but at most {max} fit")]
TooLong {
field: &'static str,
len: usize,
max: usize,
},
#[error("{field} is {value}, which exceeds its maximum of {max}")]
OutOfRange {
field: &'static str,
value: u64,
max: u64,
},
}
impl EncodeError {
pub(crate) fn check_bits(field: &'static str, value: u64, bits: u32) -> Result<(), Self> {
let max = (1u64 << bits) - 1;
match value <= max {
true => Ok(()),
false => Err(Self::OutOfRange { field, value, max }),
}
}
pub(crate) fn check_len(field: &'static str, len: usize, max: usize) -> Result<(), Self> {
match len <= max {
true => Ok(()),
false => Err(Self::TooLong { field, len, max }),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Flow {
#[doc(alias("FLOW_OUTGOING"))]
Outgoing,
#[doc(alias("FLOW_INCOMING"))]
Incoming,
}
impl Flow {
const fn raw(self) -> c_int {
match self {
Self::Outgoing => FLOW_OUTGOING,
Self::Incoming => FLOW_INCOMING,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MessageId(u8);
impl MessageId {
#[doc(alias("svc_BSPDecal"))]
pub const BSP_DECAL: Self = Self(21);
#[doc(alias("svc_ClassInfo"))]
pub const CLASS_INFO: Self = Self(10);
#[doc(alias("svc_CmdKeyValues"))]
pub const CMD_KEY_VALUES: Self = Self(32);
#[doc(alias("svc_CreateStringTable"))]
pub const CREATE_STRING_TABLE: Self = Self(12);
#[doc(alias("svc_CrosshairAngle"))]
pub const CROSSHAIR_ANGLE: Self = Self(20);
#[doc(alias("net_Disconnect"))]
pub const DISCONNECT: Self = Self(1);
#[doc(alias("svc_EntityMessage"))]
pub const ENTITY_MESSAGE: Self = Self(24);
#[doc(alias("net_File"))]
pub const FILE: Self = Self(2);
#[doc(alias("svc_FixAngle"))]
pub const FIX_ANGLE: Self = Self(19);
#[doc(alias("svc_GameEvent"))]
pub const GAME_EVENT: Self = Self(25);
#[doc(alias("svc_GameEventList"))]
pub const GAME_EVENT_LIST: Self = Self(30);
#[doc(alias("svc_GetCvarValue"))]
pub const GET_CVAR_VALUE: Self = Self(31);
#[doc(alias("svc_Menu"))]
pub const MENU: Self = Self(29);
#[doc(alias("net_NOP"))]
pub const NOP: Self = Self(0);
#[doc(alias("svc_PacketEntities"))]
pub const PACKET_ENTITIES: Self = Self(26);
#[doc(alias("svc_Prefetch"))]
pub const PREFETCH: Self = Self(28);
#[doc(alias("svc_Print"))]
pub const PRINT: Self = Self(7);
#[doc(alias("svc_SendTable"))]
pub const SEND_TABLE: Self = Self(9);
#[doc(alias("svc_ServerInfo"))]
pub const SERVER_INFO: Self = Self(8);
#[doc(alias("net_SetConVar"))]
pub const SET_CONVAR: Self = Self(5);
#[doc(alias("svc_SetPause"))]
pub const SET_PAUSE: Self = Self(11);
#[doc(alias("svc_SetView"))]
pub const SET_VIEW: Self = Self(18);
#[doc(alias("net_SignonState"))]
pub const SIGNON_STATE: Self = Self(6);
#[doc(alias("svc_Sounds"))]
pub const SOUNDS: Self = Self(17);
#[doc(alias("net_StringCmd"))]
pub const STRING_CMD: Self = Self(4);
#[doc(alias("svc_TempEntities"))]
pub const TEMP_ENTITIES: Self = Self(27);
#[doc(alias("net_Tick"))]
pub const TICK: Self = Self(3);
#[doc(alias("svc_UpdateStringTable"))]
pub const UPDATE_STRING_TABLE: Self = Self(13);
#[doc(alias("svc_UserMessage"))]
pub const USER_MESSAGE: Self = Self(23);
#[doc(alias("svc_VoiceData"))]
pub const VOICE_DATA: Self = Self(15);
#[doc(alias("svc_VoiceInit"))]
pub const VOICE_INIT: Self = Self(14);
pub const fn new(id: u8) -> Option<Self> {
match id < 1 << MESSAGE_TYPE_BITS {
true => Some(Self(id)),
false => None,
}
}
pub const fn get(self) -> u8 {
self.0
}
pub const fn name(self) -> Option<&'static str> {
Some(match self.0 {
0 => "net_NOP",
1 => "net_Disconnect",
2 => "net_File",
3 => "net_Tick",
4 => "net_StringCmd",
5 => "net_SetConVar",
6 => "net_SignonState",
7 => "svc_Print",
8 => "svc_ServerInfo",
9 => "svc_SendTable",
10 => "svc_ClassInfo",
11 => "svc_SetPause",
12 => "svc_CreateStringTable",
13 => "svc_UpdateStringTable",
14 => "svc_VoiceInit",
15 => "svc_VoiceData",
17 => "svc_Sounds",
18 => "svc_SetView",
19 => "svc_FixAngle",
20 => "svc_CrosshairAngle",
21 => "svc_BSPDecal",
23 => "svc_UserMessage",
24 => "svc_EntityMessage",
25 => "svc_GameEvent",
26 => "svc_PacketEntities",
27 => "svc_TempEntities",
28 => "svc_Prefetch",
29 => "svc_Menu",
30 => "svc_GameEventList",
31 => "svc_GetCvarValue",
32 => "svc_CmdKeyValues",
_ => return None,
})
}
}
impl Display for MessageId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.name() {
Some(name) => f.write_str(name),
None => write!(f, "message {}", self.0),
}
}
}
#[doc(alias("INetChannel"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NetChannel<'s> {
raw: NonNull<sys::INetChannel>,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> NetChannel<'s> {
pub(crate) const unsafe fn from_raw(raw: NonNull<sys::INetChannel>) -> Self {
Self {
raw,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
#[doc(alias("GetAddress"))]
pub fn address(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_const() => INetChannel_GetAddress())) }
}
const fn as_const(self) -> *const sys::INetChannel {
self.raw.as_ptr().cast_const()
}
pub const fn as_ptr(self) -> *mut sys::INetChannel {
self.raw.as_ptr()
}
#[doc(alias("GetAvgChoke"))]
pub fn average_choke(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgChoke(flow.raw())) }
}
#[doc(alias("GetAvgData"))]
pub fn average_data(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgData(flow.raw())) }
}
#[doc(alias("GetAvgLatency"))]
pub fn average_latency(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgLatency(flow.raw())) }
}
#[doc(alias("GetAvgLoss"))]
pub fn average_loss(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgLoss(flow.raw())) }
}
#[doc(alias("GetAvgPackets"))]
pub fn average_packets(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgPackets(flow.raw())) }
}
#[doc(alias("GetNumBitsWritten"))]
pub fn bits_waiting(self, reliability: Reliability) -> c_int {
let reliable = reliability == Reliability::Reliable;
unsafe { vcall!(self.as_ptr() => INetChannel_GetNumBitsWritten(reliable)) }
}
#[doc(alias("GetDataRate"))]
pub fn data_rate(self) -> c_int {
unsafe { vcall!(self.as_const() => INetChannel_GetDataRate()) }
}
#[doc(alias("HasPendingReliableData"))]
pub fn has_pending_reliable_data(self) -> bool {
unsafe { vcall!(self.as_ptr() => INetChannel_HasPendingReliableData()) }
}
#[doc(alias("IsLoopback"))]
pub fn is_loopback(self) -> bool {
unsafe { vcall!(self.as_const() => INetChannel_IsLoopback()) }
}
#[doc(alias("IsOverflowed"))]
pub fn is_overflowed(self) -> bool {
unsafe { vcall!(self.as_const() => INetChannel_IsOverflowed()) }
}
#[doc(alias("IsTimingOut"))]
pub fn is_timing_out(self) -> bool {
unsafe { vcall!(self.as_const() => INetChannel_IsTimingOut()) }
}
#[doc(alias("GetLatency"))]
pub fn latency(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetLatency(flow.raw())) }
}
#[doc(alias("GetName"))]
pub fn name(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_const() => INetChannel_GetName())) }
}
#[doc(alias("GetProtocolVersion"))]
pub fn protocol_version(self) -> c_int {
unsafe { vcall!(self.as_ptr() => INetChannel_GetProtocolVersion()) }
}
pub fn send(self, message: &impl NetMessage) -> Result<(), SendError> {
self.send_with(message, message.reliability())
}
#[doc(alias("SendData"))]
pub fn send_encoded(self, bits: &BitWriter, reliability: Reliability) -> Result<(), SendError> {
let mut buffer = BfWrite::written(bits.as_words(), bits.len());
let reliable = reliability == Reliability::Reliable;
let sent =
unsafe { vcall!(self.as_ptr() => INetChannel_SendData(buffer.as_raw(), reliable)) };
match sent {
true => Ok(()),
false => Err(SendError::TooLarge { bits: bits.len() }),
}
}
pub fn send_with(
self,
message: &impl NetMessage,
reliability: Reliability,
) -> Result<(), SendError> {
self.send_encoded(&message.encode()?, reliability)
}
#[doc(alias("GetSequenceData"))]
pub fn sequence_data(self) -> SequenceData {
let mut data = SequenceData {
outgoing: 0,
incoming: 0,
outgoing_acknowledged: 0,
};
unsafe {
vcall!(self.as_ptr() => INetChannel_GetSequenceData(
&raw mut data.outgoing,
&raw mut data.incoming,
&raw mut data.outgoing_acknowledged,
))
};
data
}
#[doc(alias("GetTimeConnected"))]
pub fn time_connected(self) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetTimeConnected()) }
}
#[doc(alias("GetTimeSinceLastReceived"))]
pub fn time_since_last_received(self) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetTimeSinceLastReceived()) }
}
#[doc(alias("GetTimeoutSeconds"))]
pub fn timeout_seconds(self) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetTimeoutSeconds()) }
}
#[doc(alias("GetTotalData"))]
pub fn total_data(self, flow: Flow) -> c_int {
unsafe { vcall!(self.as_const() => INetChannel_GetTotalData(flow.raw())) }
}
}
pub trait NetMessage {
fn encode(&self) -> Result<BitWriter, EncodeError> {
let mut out = BitWriter::new();
out.write_ubits(self.id().get().into(), MESSAGE_TYPE_BITS);
self.write_body(&mut out)?;
Ok(out)
}
fn id(&self) -> MessageId;
fn reliability(&self) -> Reliability {
Reliability::Reliable
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Reliability {
#[default]
Reliable,
Unreliable,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SendError {
#[error(transparent)]
Encode(#[from] EncodeError),
#[error("the channel has no room for a message of {bits} bits")]
TooLarge {
bits: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SequenceData {
pub outgoing: c_int,
pub incoming: c_int,
pub outgoing_acknowledged: c_int,
}