use super::super::interfaces::cvar::{mock_command, mock_cvar, mock_var};
use super::super::server::{export, mock_server};
use crate::bitbuf::BitWriter;
use crate::commands::CommandFlags;
use crate::interfaces::{Cvar, ValveEngine};
use crate::net::MESSAGE_TYPE_BITS;
use crate::net::incoming::Incoming;
use crate::server::{Module, Server};
use sdk_raw::bitbuf::BfWrite;
use sdk_raw::test_support::{mock_vtable, unexpected_call};
use std::cell::{Cell, RefCell};
use std::ffi::{CStr, CString, c_int};
use std::ptr::{NonNull, null_mut};
#[repr(C)]
struct ChannelObject {
interface: sys::INetChannel,
loopback: bool,
room: Cell<usize>,
sent: RefCell<Vec<BitWriter>>,
}
#[repr(C)]
struct ClientObject {
interface: sys::IClient,
channel: *mut ChannelObject,
spec: Cell<MockClient>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decoded {
Query(c_int, CString),
SetConVar(Vec<(CString, CString)>),
StringCmd(CString),
}
#[repr(C)]
struct EngineObject {
interface: sys::IVEngineServer,
server: *mut ServerObject,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct MockClient {
pub user_id: c_int,
pub active: bool,
pub fake: bool,
pub hltv: bool,
pub loopback: bool,
pub no_channel: bool,
}
impl MockClient {
pub const fn active(user_id: c_int) -> Self {
Self {
user_id,
active: true,
fake: false,
hltv: false,
loopback: false,
no_channel: false,
}
}
}
pub struct MockEngine {
cheats: *mut sys::ConVar,
clients: Vec<*mut ClientObject>,
}
impl MockEngine {
pub fn new(clients: &[MockClient], cheats: &'static CStr, vars: &[MockVar]) -> Self {
let mut next: *mut sys::ConCommandBase = null_mut();
let mut listed = Vec::new();
for var in vars.iter().rev() {
let raw = match var.flags {
Some(flags) => {
let raw = mock_var(var.name, var.default, var.value, flags, next);
listed.push(raw);
raw.cast()
}
None => mock_command(var.name, next),
};
next = raw;
}
let flags = CommandFlags::REPLICATED | CommandFlags::NOTIFY;
let cheats_var = mock_var(c"sv_cheats", c"0", cheats, flags, next);
unsafe { (*cheats_var).m_nValue = c_int::from(is_set(cheats)) };
listed.push(cheats_var);
let cvar = mock_cvar(cheats_var.cast(), listed);
let client_vtable: &'static _ = Box::leak(unsafe {
mock_vtable::<sys::IClient__bindgen_vtable>(unexpected_call as *const (), |vtable| {
(&raw mut (*vtable).IClient_GetNetChannel).write(get_net_channel);
(&raw mut (*vtable).IClient_GetUserID).write(get_user_id);
(&raw mut (*vtable).IClient_IsActive).write(is_active);
(&raw mut (*vtable).IClient_IsConnected).write(is_connected);
(&raw mut (*vtable).IClient_IsFakeClient).write(is_fake_client);
(&raw mut (*vtable).IClient_IsHLTV).write(is_hltv);
})
});
let channel_vtable: &'static _ = Box::leak(unsafe {
mock_vtable::<sys::INetChannel__bindgen_vtable>(
unexpected_call as *const (),
|vtable| {
(&raw mut (*vtable).INetChannel_IsLoopback).write(is_loopback);
(&raw mut (*vtable).INetChannel_SendData).write(send_data);
},
)
});
let clients: Vec<*mut ClientObject> = clients
.iter()
.map(|&spec| {
let channel = Box::into_raw(Box::new(ChannelObject {
interface: sys::INetChannel {
vtable_: channel_vtable,
},
loopback: spec.loopback,
room: Cell::new(usize::MAX),
sent: RefCell::new(Vec::new()),
}));
Box::into_raw(Box::new(ClientObject {
interface: sys::IClient {
vtable_: client_vtable,
},
channel,
spec: Cell::new(spec),
}))
})
.collect();
let server_vtable = unsafe {
mock_vtable::<sys::IServer__bindgen_vtable>(unexpected_call as *const (), |vtable| {
(&raw mut (*vtable).IServer_GetClient).write(get_client);
(&raw mut (*vtable).IServer_GetClientCount).write(get_client_count);
})
};
let server = Box::into_raw(Box::new(ServerObject {
interface: sys::IServer {
vtable_: Box::leak(server_vtable),
},
clients: clients.clone(),
}));
let engine_vtable = unsafe {
mock_vtable::<sys::IVEngineServer__bindgen_vtable>(
unexpected_call as *const (),
|vtable| {
(&raw mut (*vtable).IVEngineServer_GetIServer).write(get_iserver);
},
)
};
let engine = Box::into_raw(Box::new(EngineObject {
interface: sys::IVEngineServer {
vtable_: Box::leak(engine_vtable),
},
server,
}));
export(Module::Engine, Cvar::VERSION, cvar);
export(Module::Engine, ValveEngine::VERSION, engine);
Self {
cheats: cheats_var,
clients,
}
}
fn client(&self, slot: usize) -> &ClientObject {
unsafe { &*self.clients[slot] }
}
#[cfg(test)]
pub(crate) fn game_client(&self, slot: usize) -> crate::interfaces::GameClient<'_> {
let client = NonNull::new(self.clients[slot].cast()).unwrap();
unsafe { crate::interfaces::GameClient::from_raw(client) }
}
pub fn room(&self, slot: usize, bits: usize) {
unsafe { (*self.client(slot).channel).room.set(bits) };
}
pub fn server(&self) -> Server<'_> {
mock_server(self)
}
pub fn set_cheats(&self, value: &'static CStr) {
unsafe {
(*self.cheats).m_pszString = value.as_ptr().cast_mut();
(*self.cheats).m_nValue = c_int::from(is_set(value));
}
}
pub fn set_client(&self, slot: usize, spec: MockClient) {
self.client(slot).spec.set(spec);
}
pub fn take_sent(&self, slot: usize) -> Vec<Vec<Decoded>> {
let sent = unsafe { (*self.client(slot).channel).sent.take() };
sent.iter().map(decode).collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct MockVar {
pub name: &'static CStr,
pub default: &'static CStr,
pub value: &'static CStr,
pub flags: Option<CommandFlags>,
}
impl MockVar {
pub const fn command(name: &'static CStr) -> Self {
Self {
name,
default: c"",
value: c"",
flags: None,
}
}
pub const fn var(
name: &'static CStr,
default: &'static CStr,
value: &'static CStr,
flags: CommandFlags,
) -> Self {
Self {
name,
default,
value,
flags: Some(flags),
}
}
}
#[repr(C)]
struct ServerObject {
interface: sys::IServer,
clients: Vec<*mut ClientObject>,
}
pub fn decode(bits: &BitWriter) -> Vec<Decoded> {
let mut reader = bits.reader();
let mut messages = Vec::new();
while reader.remaining() > 0 {
let message = match reader.read_ubits(MESSAGE_TYPE_BITS).unwrap() {
4 => Decoded::StringCmd(reader.read_cstring().unwrap()),
5 => {
let count = reader.read_u8().unwrap();
let mut convars = Vec::new();
for _ in 0..count {
let name = reader.read_cstring().unwrap();
convars.push((name, reader.read_cstring().unwrap()));
}
Decoded::SetConVar(convars)
}
31 => {
let cookie = reader.read_i32().unwrap();
Decoded::Query(cookie, reader.read_cstring().unwrap())
}
other => panic!("unexpected message type {other}"),
};
messages.push(message);
}
messages
}
unsafe extern "C" fn get_client(this: *mut sys::IServer, slot: c_int) -> *mut sys::IClient {
let server = unsafe { &*this.cast::<ServerObject>() };
server.clients[usize::try_from(slot).unwrap()].cast()
}
unsafe extern "C" fn get_client_count(this: *const sys::IServer) -> c_int {
unsafe { (*this.cast::<ServerObject>()).clients.len() as c_int }
}
unsafe extern "C" fn get_iserver(this: *mut sys::IVEngineServer) -> *mut sys::IServer {
unsafe { (*this.cast::<EngineObject>()).server.cast() }
}
unsafe extern "C" fn get_net_channel(this: *mut sys::IClient) -> *mut sys::INetChannel {
let client = unsafe { &*this.cast::<ClientObject>() };
let spec = client.spec.get();
match spec.user_id == 0 || spec.fake || spec.hltv || spec.no_channel {
true => null_mut(),
false => client.channel.cast(),
}
}
unsafe extern "C" fn get_user_id(this: *const sys::IClient) -> c_int {
unsafe { spec(this) }.user_id
}
unsafe extern "C" fn is_active(this: *const sys::IClient) -> bool {
unsafe { spec(this) }.active
}
unsafe extern "C" fn is_connected(this: *const sys::IClient) -> bool {
unsafe { spec(this) }.user_id != 0
}
unsafe extern "C" fn is_fake_client(this: *const sys::IClient) -> bool {
let spec = unsafe { spec(this) };
spec.fake || spec.hltv
}
unsafe extern "C" fn is_hltv(this: *const sys::IClient) -> bool {
unsafe { spec(this) }.hltv
}
unsafe extern "C" fn is_loopback(this: *const sys::INetChannel) -> bool {
unsafe { (*this.cast::<ChannelObject>()).loopback }
}
fn is_set(value: &CStr) -> bool {
value
.to_str()
.ok()
.and_then(|value| value.trim().parse::<f64>().ok())
.is_some_and(|value| value.trunc() != 0.0)
}
pub fn query_cookie(sent: &[Decoded]) -> c_int {
let mut cookies = sent.iter().filter_map(|message| match message {
Decoded::Query(cookie, _) => Some(*cookie),
_ => None,
});
let cookie = cookies.next().expect("a query");
assert_eq!(cookies.next(), None, "a single query");
cookie
}
pub fn response(cookie: c_int, status: c_int, value: &CStr) -> Incoming {
Incoming::RespondCvarValue {
cookie,
status,
name: c"sv_cheats".to_owned(),
value: value.to_owned(),
}
}
unsafe extern "C" fn send_data(
this: *mut sys::INetChannel,
buffer: *mut sys::bf_write,
reliable: bool,
) -> bool {
let (channel, bits) = unsafe {
(
&*this.cast::<ChannelObject>(),
BfWrite::read_back(NonNull::new(buffer.cast()).unwrap()).map(BitWriter::from),
)
};
let bits = bits.expect("a readable buffer");
assert!(reliable, "leases only use the reliable stream");
if bits.len() > channel.room.get() {
return false;
}
channel.sent.borrow_mut().push(bits);
true
}
unsafe fn spec(client: *const sys::IClient) -> MockClient {
unsafe { (*client.cast::<ClientObject>()).spec.get() }
}