use std::{
alloc::{GlobalAlloc, Layout},
borrow::Cow,
ffi::{c_char, c_void, CStr},
mem,
ptr::addr_of_mut,
};
use super::EngineData;
#[cfg(doc)]
use crate::bindings::cvar::RawCVar;
use crate::{
bindings::cvar::convar::{ConVar, FnChangeCallback_t, FCVAR_NEVER_AS_STRING},
errors::{CStringPtrError, CVarQueryError, RegisterError},
mid::{
engine::{get_engine_data, ENGINE_DATA},
source_alloc::SOURCE_ALLOC,
utils::{to_cstring, try_cstring},
},
prelude::EngineToken,
};
#[derive(Debug, PartialEq)]
pub struct ConVarValues {
pub value: String,
pub value_float: f32,
pub value_int: i32,
}
pub struct ConVarRegister {
pub name: String,
pub default_value: String,
pub flags: i32,
pub help_string: &'static str,
pub bmin: bool,
pub fmin: f32,
pub bmax: bool,
pub fmax: f32,
pub callback: FnChangeCallback_t,
}
impl ConVarRegister {
pub fn new(
name: impl Into<String>,
default_value: impl Into<String>,
flags: i32,
help_string: &'static str,
) -> Self {
Self::mandatory(name, default_value, flags, help_string)
}
pub fn mandatory(
name: impl Into<String>,
default_value: impl Into<String>,
flags: i32,
help_string: &'static str,
) -> Self {
Self {
name: name.into(),
default_value: default_value.into(),
flags,
help_string,
bmin: bool::default(),
fmin: f32::default(),
bmax: bool::default(),
fmax: f32::default(),
callback: None,
}
}
}
pub struct ConVarStruct {
inner: &'static mut ConVar,
}
impl ConVarStruct {
pub fn try_new(register_info: &ConVarRegister, _: EngineToken) -> Result<Self, RegisterError> {
get_engine_data()
.map(move |engine| unsafe { Self::internal_try_new(engine, register_info) })
.unwrap_or_else(|| Err(RegisterError::NoneFunction))
}
#[inline]
unsafe fn internal_try_new(
engine_data: &EngineData,
register_info: &ConVarRegister,
) -> Result<Self, RegisterError> {
let convar_classes = engine_data.convar;
let convar = unsafe {
let convar = SOURCE_ALLOC.alloc_zeroed(Layout::new::<ConVar>()) as *mut ConVar;
addr_of_mut!((*convar).m_ConCommandBase.m_pConCommandBaseVTable)
.write(convar_classes.convar_vtable);
addr_of_mut!((*convar).m_ConCommandBase.s_pConCommandBases)
.write(convar_classes.iconvar_vtable);
#[allow(clippy::crosspointer_transmute)] (convar_classes.convar_malloc)(addr_of_mut!((*convar).m_pMalloc).cast(), 0, 0);
convar
};
debug_assert!(!register_info.name.is_empty());
let name = try_cstring(®ister_info.name)?.into_bytes_with_nul();
let name_ptr =
unsafe {
SOURCE_ALLOC.alloc(Layout::array::<c_char>(name.len()).expect(
"the Layout for a char array became too large : string allocation failed",
))
};
unsafe { name_ptr.copy_from_nonoverlapping(name.as_ptr(), name.len()) };
let default_value = try_cstring(®ister_info.default_value)?.into_bytes_with_nul();
let default_value_ptr =
unsafe {
SOURCE_ALLOC.alloc(Layout::array::<c_char>(default_value.len()).expect(
"the Layout for a char array became too large : string allocation failed",
))
};
unsafe {
default_value_ptr.copy_from_nonoverlapping(default_value.as_ptr(), default_value.len())
};
let help_string = try_cstring(register_info.help_string)?.into_bytes_with_nul();
let help_string_ptr =
unsafe {
SOURCE_ALLOC.alloc(Layout::array::<c_char>(help_string.len()).expect(
"the Layout for a char array became too large : string allocation failed",
))
};
unsafe {
help_string_ptr.copy_from_nonoverlapping(help_string.as_ptr(), help_string.len())
};
unsafe {
(convar_classes.convar_register)(
convar,
name_ptr as *const i8,
default_value_ptr as *const i8,
register_info.flags,
help_string_ptr as *const i8,
register_info.bmin,
register_info.fmin,
register_info.bmax,
register_info.fmax,
register_info.callback,
)
}
log::info!("Registering ConVar {}", register_info.name);
Ok(Self {
inner: unsafe { &mut *convar }, })
}
pub fn find_convar_by_name(name: &str, _: EngineToken) -> Result<Self, CVarQueryError> {
let name = try_cstring(name)?;
Ok(Self {
inner: unsafe {
ENGINE_DATA
.get()
.ok_or(CVarQueryError::NoCVarInterface)?
.cvar
.find_convar(name.as_ptr())
.as_mut()
.ok_or(CVarQueryError::NotFound)?
},
})
}
pub fn get_name(&self) -> String {
unsafe {
CStr::from_ptr(self.inner.m_ConCommandBase.m_pszName)
.to_string_lossy()
.to_string()
}
}
pub fn get_value(&self) -> ConVarValues {
unsafe {
let value = &self.inner.m_Value;
let string = if !value.m_pszString.is_null()
&& !self.has_flags(
FCVAR_NEVER_AS_STRING
.try_into()
.expect("supposed to always work"),
) {
CStr::from_ptr(value.m_pszString)
.to_string_lossy()
.to_string()
} else {
"".to_string()
};
ConVarValues {
value: string,
value_float: value.m_fValue,
value_int: value.m_nValue,
}
}
}
fn get_value_c_str(&self) -> Option<&CStr> {
unsafe {
let value = &self.inner.m_Value;
if value.m_pszString.is_null()
|| self.has_flags(
FCVAR_NEVER_AS_STRING
.try_into()
.expect("supposed to always work"),
)
{
return None;
}
Some(CStr::from_ptr(value.m_pszString))
}
}
pub fn get_value_cow(&'_ self) -> Cow<'_, str> {
self.get_value_c_str()
.map(|cstr| cstr.to_string_lossy())
.unwrap_or_default()
}
pub fn get_value_string(&self) -> String {
self.get_value_cow().to_string()
}
pub fn get_value_str(&self) -> Result<&str, CStringPtrError> {
self.get_value_c_str()
.ok_or(CStringPtrError::None)?
.to_str()
.map_err(|err| err.into())
}
pub fn get_value_i32(&self) -> i32 {
self.inner.m_Value.m_nValue
}
pub fn get_value_bool(&self) -> bool {
self.inner.m_Value.m_nValue != 0
}
pub fn get_value_f32(&self) -> f32 {
self.inner.m_Value.m_fValue
}
pub fn set_value_i32(&self, new_value: i32, _: EngineToken) {
unsafe {
let value = &self.inner.m_Value;
if value.m_nValue == new_value {
return;
}
let vtable_adr = self.inner.m_ConCommandBase.m_pConCommandBaseVTable as usize;
let vtable_array = *(vtable_adr as *const [*const std::ffi::c_void; 21]);
let set_value_int = vtable_array[14];
let func = mem::transmute::<*const c_void, fn(*const ConVar, i32)>(set_value_int);
func(self.inner, new_value)
}
}
pub fn set_value_f32(&self, new_value: f32, _: EngineToken) {
unsafe {
let value = &self.inner.m_Value;
if value.m_fValue == new_value {
return;
}
let vtable_adr = self.inner.m_ConCommandBase.m_pConCommandBaseVTable as usize;
let vtable_array = *(vtable_adr as *const [*const std::ffi::c_void; 21]);
let set_value_float = vtable_array[13];
let func = mem::transmute::<*const c_void, fn(*const ConVar, f32)>(set_value_float);
func(self.inner, new_value)
}
}
pub fn set_value_string(&self, new_value: impl AsRef<str>, _: EngineToken) {
unsafe {
if self.has_flags(FCVAR_NEVER_AS_STRING.try_into().unwrap()) {
return;
}
let vtable_adr = self.inner.m_ConCommandBase.m_pConCommandBaseVTable as usize;
let vtable_array = *(vtable_adr as *const [*const std::ffi::c_void; 21]);
let set_value_string = vtable_array[12];
let func =
mem::transmute::<*const c_void, fn(*const ConVar, *const c_char)>(set_value_string);
let string_value = to_cstring(new_value.as_ref());
func(self.inner, string_value.as_ptr())
}
}
pub fn get_help_text(&self) -> String {
let help = self.inner.m_ConCommandBase.m_pszHelpString;
unsafe { CStr::from_ptr(help).to_string_lossy().to_string() }
}
pub fn is_registered(&self) -> bool {
self.inner.m_ConCommandBase.m_bRegistered
}
pub fn has_flags(&self, flags: i32) -> bool {
self.inner.m_ConCommandBase.m_nFlags & flags != 0
}
pub fn add_flags(&mut self, flags: i32, _: EngineToken) {
self.inner.m_ConCommandBase.m_nFlags |= flags
}
pub fn remove_flags(&mut self, flags: i32, _: EngineToken) {
self.inner.m_ConCommandBase.m_nFlags &= !flags }
pub unsafe fn get_raw_convar_ptr(&mut self) -> *mut ConVar {
self.inner
}
}
unsafe impl Sync for ConVarStruct {}
unsafe impl Sync for ConVar {}
unsafe impl Send for ConVarStruct {}
unsafe impl Send for ConVar {}