use crate::ffi::NotThreadSafe;
use crate::interfaces::{
BotManager, Cvar, EngineSound, EngineTrace, GameEventManager, ModelInfo, NetworkStringTables,
PlayerInfoManager, PluginHelpers, ServerGameClients, ServerGameDll, ServerGameEnts,
ServerTools, ValveEngine, VoiceServer,
};
use std::ffi::{CStr, CString, c_char, c_int, c_void};
use std::fmt::{self, Debug, Display, Formatter};
use std::marker::PhantomData;
use std::ptr::NonNull;
pub type RawInterfaceFactory =
unsafe extern "C" fn(name: *const c_char, return_code: *mut c_int) -> *mut c_void;
#[doc(alias = "CreateInterfaceFn")]
#[derive(Clone, Copy)]
pub struct InterfaceFactory(RawInterfaceFactory);
impl InterfaceFactory {
pub const fn new(factory: RawInterfaceFactory) -> Self {
Self(factory)
}
pub const fn from_raw(factory: sys::CreateInterfaceFn) -> Option<Self> {
match factory {
Some(factory) => Some(Self(factory)),
None => None,
}
}
pub const fn as_raw(self) -> RawInterfaceFactory {
self.0
}
}
impl Debug for InterfaceFactory {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "InterfaceFactory({:p})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Module {
Engine,
GameServer,
}
impl Display for Module {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Engine => "engine",
Self::GameServer => "game server",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("the {module} does not export `{}`; the server may not match the SDK the bindings were generated from", .version.to_string_lossy())]
pub struct InterfaceError {
module: Module,
version: CString,
}
impl InterfaceError {
pub const fn module(&self) -> Module {
self.module
}
pub fn version(&self) -> &CStr {
&self.version
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Game {
TeamFortress2,
SourceSdk2013,
}
impl Game {
pub(crate) const fn teleport_vtable_slot(self) -> usize {
match self {
Self::TeamFortress2 => sys::CBASEENTITY_TF2_TELEPORT_VTABLE_SLOT,
Self::SourceSdk2013 => sys::CBASEENTITY_TELEPORT_VTABLE_SLOT,
}
}
}
pub(crate) unsafe trait Interface<'s>: Sized {
type Raw;
const MODULE: Module;
const VERSION: &'static CStr;
unsafe fn bind(raw: NonNull<Self::Raw>, server: &Server<'s>) -> Self;
}
#[derive(Debug, Clone, Copy)]
pub struct ServerBinding {
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
_not_thread_safe: NotThreadSafe,
}
impl ServerBinding {
pub const unsafe fn new(
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
) -> Self {
Self {
engine,
game_server,
game,
_not_thread_safe: PhantomData,
}
}
pub const fn game(&self) -> Game {
self.game
}
pub const unsafe fn server<'s, S: ?Sized>(&self, scope: &'s S) -> Server<'s> {
unsafe { Server::new(self.engine, self.game_server, self.game, scope) }
}
}
#[derive(Debug, Clone, Copy)]
pub struct Server<'s> {
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> Server<'s> {
pub const unsafe fn new<S: ?Sized>(
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
_scope: &'s S,
) -> Self {
Self {
engine,
game_server,
game,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
pub const fn game(&self) -> Game {
self.game
}
pub const fn engine_factory(&self) -> InterfaceFactory {
self.engine
}
pub const fn game_server_factory(&self) -> InterfaceFactory {
self.game_server
}
#[doc(alias = "Msg")]
pub fn console_print(&self, message: &CStr) {
if crate::tier0::print(message) {
return;
}
if let Ok(cvar) = self.cvar() {
cvar.console_printf(message);
}
}
pub fn find_interface<T>(
&self,
module: Module,
version: &CStr,
) -> Result<NonNull<T>, InterfaceError> {
let factory = match module {
Module::Engine => self.engine,
Module::GameServer => self.game_server,
};
let mut return_code = 0;
let interface = unsafe { factory.as_raw()(version.as_ptr(), &mut return_code) };
NonNull::new(interface.cast()).ok_or_else(|| InterfaceError {
module,
version: version.to_owned(),
})
}
fn interface<I: Interface<'s>>(&self) -> Result<I, InterfaceError> {
let raw = self.find_interface::<I::Raw>(I::MODULE, I::VERSION)?;
Ok(unsafe { I::bind(raw, self) })
}
pub fn valve_engine(&self) -> Result<ValveEngine<'s>, InterfaceError> {
self.interface()
}
pub fn game_events(&self) -> Result<GameEventManager<'s>, InterfaceError> {
self.interface()
}
pub fn cvar(&self) -> Result<Cvar<'s>, InterfaceError> {
self.interface()
}
pub fn engine_trace(&self) -> Result<EngineTrace<'s>, InterfaceError> {
self.interface()
}
pub fn engine_sound(&self) -> Result<EngineSound<'s>, InterfaceError> {
self.interface()
}
pub fn network_string_tables(&self) -> Result<NetworkStringTables<'s>, InterfaceError> {
self.interface()
}
pub fn plugin_helpers(&self) -> Result<PluginHelpers<'s>, InterfaceError> {
self.interface()
}
pub fn model_info(&self) -> Result<ModelInfo<'s>, InterfaceError> {
self.interface()
}
pub fn voice_server(&self) -> Result<VoiceServer<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_dll(&self) -> Result<ServerGameDll<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_ents(&self) -> Result<ServerGameEnts<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_clients(&self) -> Result<ServerGameClients<'s>, InterfaceError> {
self.interface()
}
pub fn player_info_manager(&self) -> Result<PlayerInfoManager<'s>, InterfaceError> {
self.interface()
}
pub fn bot_manager(&self) -> Result<BotManager<'s>, InterfaceError> {
self.interface()
}
pub fn server_tools(&self) -> Result<ServerTools<'s>, InterfaceError> {
self.interface()
}
}
#[cfg(test)]
pub(crate) mod test_support {
use super::*;
use std::cell::RefCell;
thread_local! {
static INTERFACES: RefCell<Vec<(Module, CString, usize)>> = const { RefCell::new(Vec::new()) };
}
fn find(module: Module, name: *const c_char) -> *mut c_void {
let name = unsafe { CStr::from_ptr(name) };
INTERFACES.with_borrow(|interfaces| {
interfaces
.iter()
.find(|(owner, version, _)| *owner == module && version.as_c_str() == name)
.map_or(std::ptr::null_mut(), |&(_, _, address)| {
address as *mut c_void
})
})
}
unsafe extern "C" fn engine_factory(
name: *const c_char,
_return_code: *mut c_int,
) -> *mut c_void {
find(Module::Engine, name)
}
unsafe extern "C" fn game_server_factory(
name: *const c_char,
_return_code: *mut c_int,
) -> *mut c_void {
find(Module::GameServer, name)
}
pub(crate) fn export<T>(module: Module, version: &CStr, interface: *mut T) {
INTERFACES.with_borrow_mut(|interfaces| {
interfaces.push((module, version.to_owned(), interface as usize))
});
}
pub(crate) fn mock_server<S: ?Sized>(scope: &S) -> Server<'_> {
unsafe { mock_binding().server(scope) }
}
pub(crate) fn mock_binding() -> ServerBinding {
unsafe {
ServerBinding::new(
InterfaceFactory::new(engine_factory),
InterfaceFactory::new(game_server_factory),
Game::TeamFortress2,
)
}
}
}
#[cfg(test)]
mod tests {
use super::test_support::{export, mock_server};
use super::*;
#[test]
fn interfaces_resolve_by_exact_version_from_their_own_module() {
let mut engine = sys::IVEngineServer {
vtable_: std::ptr::null(),
};
let engine_pointer = &raw mut engine;
let scope = ();
export(Module::GameServer, c"VEngineServer023", engine_pointer);
export(Module::Engine, c"VEngineServer024", engine_pointer);
let server = mock_server(&scope);
let error = server.valve_engine().unwrap_err();
assert_eq!(error.module(), Module::Engine);
assert_eq!(error.version(), c"VEngineServer023");
assert_eq!(
error.to_string(),
"the engine does not export `VEngineServer023`; the server may not match the SDK the bindings were generated from"
);
export(Module::Engine, c"VEngineServer023", engine_pointer);
assert_eq!(server.valve_engine().unwrap().as_ptr(), engine_pointer);
assert_eq!(
server
.find_interface::<sys::IVEngineServer>(Module::Engine, c"VEngineServer023")
.unwrap()
.as_ptr(),
engine_pointer
);
assert!(server.server_game_dll().is_err());
}
}