use super::CommandFlags;
use super::error::{
CommandBaseKind, InvalidCommandName, RegisterCommandError, UnregisterCommandError,
validate_name,
};
use super::object::{base_is_registered, register_base, unregister_base};
use super::registrar::{CommandRegistrar, UnlinksBeforeUnload};
use super::route::drop_payload;
use crate::abi::CppDestructors;
use crate::ffi::{NotThreadSafe, borrow_cstr};
use crate::interfaces::Cvar;
use crate::server::{Server, ServerBinding};
use std::cell::{Cell, UnsafeCell};
use std::ffi::{CStr, CString, c_char, c_int, c_void};
use std::marker::{PhantomData, PhantomPinned};
use std::mem::{MaybeUninit, offset_of, size_of};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::pin::Pin;
use std::ptr::{self, NonNull};
const _: () = {
assert!(offset_of!(ConsoleVariable, raw) == 0);
assert!(offset_of!(sys::ConVar, _base) == 0);
assert!(INTERFACE_OFFSET == size_of::<sys::ConCommandBase>());
};
const _: () = {
use sys::ConCommandBase__bindgen_vtable as Base;
assert!(offset_of!(PrimaryVtable, is_command) == offset_of!(Base, ConCommandBase_IsCommand));
assert!(offset_of!(PrimaryVtable, is_flag_set) == offset_of!(Base, ConCommandBase_IsFlagSet));
assert!(offset_of!(PrimaryVtable, add_flags) == offset_of!(Base, ConCommandBase_AddFlags));
assert!(offset_of!(PrimaryVtable, get_name) == offset_of!(Base, ConCommandBase_GetName));
assert!(
offset_of!(PrimaryVtable, get_help_text) == offset_of!(Base, ConCommandBase_GetHelpText)
);
assert!(
offset_of!(PrimaryVtable, is_registered) == offset_of!(Base, ConCommandBase_IsRegistered)
);
assert!(
offset_of!(PrimaryVtable, get_dll_identifier)
== offset_of!(Base, ConCommandBase_GetDLLIdentifier)
);
assert!(offset_of!(PrimaryVtable, create_base) == offset_of!(Base, ConCommandBase_CreateBase));
assert!(offset_of!(PrimaryVtable, init) == offset_of!(Base, ConCommandBase_Init));
assert!(offset_of!(PrimaryVtable, init) + SLOT == size_of::<Base>());
#[cfg(target_os = "windows")]
assert!(size_of::<PrimaryVtable>() == 17 * SLOT);
#[cfg(not(target_os = "windows"))]
assert!(size_of::<PrimaryVtable>() == 21 * SLOT);
assert!(size_of::<sys::IConVar__bindgen_vtable>() == 5 * SLOT);
#[cfg(target_os = "windows")]
{
assert!(offset_of!(Vtables, primary) == offset_of!(Vtables, primary_locator) + SLOT);
assert!(offset_of!(Vtables, secondary) == offset_of!(Vtables, secondary_locator) + SLOT);
}
#[cfg(not(target_os = "windows"))]
{
assert!(offset_of!(Vtables, primary) == offset_of!(Vtables, primary_type_info) + SLOT);
assert!(offset_of!(Vtables, secondary) == offset_of!(Vtables, secondary_type_info) + SLOT);
assert!(offset_of!(Vtables, primary_type_info) == offset_of!(Vtables, primary_top) + SLOT);
assert!(
offset_of!(Vtables, secondary_type_info) == offset_of!(Vtables, secondary_top) + SLOT
);
}
};
const _: () = assert!(size_of::<VtablePage>() == 4096);
const INTERFACE_OFFSET: usize = offset_of!(sys::ConVar, _base_1);
const NEVER_AS_STRING: c_int = 1 << 12;
const SLOT: usize = size_of::<*const ()>();
static VTABLES: VtablePage = VtablePage(UnsafeCell::new(Vtables {
#[cfg(target_os = "windows")]
primary_locator: type_information::locator(type_information::PRIMARY),
#[cfg(not(target_os = "windows"))]
primary_top: 0,
#[cfg(not(target_os = "windows"))]
primary_type_info: type_information::type_info(),
primary: PrimaryVtable {
destructors: CppDestructors::new_noop(),
is_command,
is_flag_set,
add_flags,
get_name,
get_help_text,
is_registered,
get_dll_identifier,
create_base,
init,
#[cfg(not(target_os = "windows"))]
set_value_string,
#[cfg(not(target_os = "windows"))]
set_value_float,
#[cfg(not(target_os = "windows"))]
set_value_int,
internal_set_value: set_value_string,
internal_set_float_value: set_value_float,
internal_set_int_value: set_value_int,
clamp_value,
change_string_value,
create_vtbl,
internal_set_float_value2,
},
#[cfg(target_os = "windows")]
secondary_locator: type_information::locator(type_information::SECONDARY),
#[cfg(not(target_os = "windows"))]
secondary_top: -(INTERFACE_OFFSET as isize),
#[cfg(not(target_os = "windows"))]
secondary_type_info: type_information::type_info(),
secondary: sys::IConVar__bindgen_vtable {
IConVar_SetValue: interface_set_value_string,
IConVar_SetValue1: interface_set_value_float,
IConVar_SetValue2: interface_set_value_int,
IConVar_GetName: interface_get_name,
IConVar_IsFlagSet: interface_is_flag_set,
},
}));
#[repr(C)]
pub struct ConsoleVariable {
raw: UnsafeCell<sys::ConVar>,
name: &'static CStr,
help: &'static CStr,
default: &'static CStr,
flags: CommandFlags,
min: Option<f32>,
max: Option<f32>,
string: Cell<Option<CString>>,
binding: Cell<Option<ServerBinding>>,
dll_identifier: Cell<sys::CVarDLLIdentifier_t>,
_pinned: PhantomPinned,
_not_thread_safe: NotThreadSafe,
}
impl ConsoleVariable {
pub const fn new(name: &'static CStr, default: &'static CStr) -> Self {
match validate_name(name) {
Ok(()) => {}
Err(InvalidCommandName::Empty) => panic!("console variable names cannot be empty"),
Err(InvalidCommandName::TooLong) => {
panic!("console variable names cannot be longer than 63 bytes")
}
Err(InvalidCommandName::InvalidByte { .. }) => {
panic!("console variable names may only contain ASCII letters, digits, and `_`")
}
Err(InvalidCommandName::Reserved) => {
panic!("the engine reserves this name for its own client commands")
}
}
let text = default.to_bytes();
let mut raw: sys::ConVar = unsafe { MaybeUninit::zeroed().assume_init() };
raw.m_pszDefaultValue = default.as_ptr();
raw.m_pszString = default.as_ptr().cast_mut();
raw.m_StringLength = text.len() as c_int + 1;
raw.m_fValue = parse_float(text) as f32;
raw.m_nValue = parse_int(text);
Self {
raw: UnsafeCell::new(raw),
name,
help: c"",
default,
flags: CommandFlags::NONE,
min: None,
max: None,
string: Cell::new(None),
binding: Cell::new(None),
dll_identifier: Cell::new(0),
_pinned: PhantomPinned,
_not_thread_safe: PhantomData,
}
}
fn as_base(&self) -> NonNull<sys::ConCommandBase> {
NonNull::from(self).cast()
}
pub fn bool(&self, server: Server<'_>) -> bool {
self.int(server) != 0
}
fn change_callbacks<'s>(&self, server: Option<Server<'s>>) -> Option<Cvar<'s>> {
if !unsafe { base_is_registered(self.as_base()) } {
return None;
}
server?.cvar().ok()
}
fn change_string(&self, callbacks: Option<Cvar<'_>>, value: CString, old_float: f32) {
let raw = self.raw.get();
let length = value.as_bytes_with_nul().len();
let old = unsafe { (&raw const (*raw).m_pszString).read() };
let old = unsafe { CStr::from_ptr(old) };
let changed = old != value.as_c_str();
unsafe {
(&raw mut (*raw).m_pszString).write(value.as_ptr().cast_mut());
(&raw mut (*raw).m_StringLength).write(length as c_int);
}
let previous = self.string.replace(Some(value));
if let (true, Some(cvar)) = (changed, callbacks) {
let callback = unsafe { (&raw const (*raw).m_fnChangeCallback).read() };
if let Some(callback) = callback {
unsafe {
callback(
raw.byte_add(INTERFACE_OFFSET).cast(),
old.as_ptr(),
old_float,
)
};
}
unsafe {
cvar.call_global_change_callbacks(NonNull::from(self).cast(), old, old_float)
};
}
drop(previous);
}
fn clamp(&self, value: &mut f32) -> bool {
if let Some(min) = self.min
&& *value < min
{
*value = min;
return true;
}
if let Some(max) = self.max
&& *value > max
{
*value = max;
return true;
}
false
}
fn current_flags(&self) -> c_int {
unsafe { (&raw const (*self.raw.get())._base.m_nFlags).read() }
}
fn current_float(&self) -> f32 {
unsafe { (&raw const (*self.raw.get()).m_fValue).read() }
}
fn current_int(&self) -> c_int {
unsafe { (&raw const (*self.raw.get()).m_nValue).read() }
}
pub const fn default_value(&self) -> &'static CStr {
self.default
}
pub const fn flags(mut self, flags: CommandFlags) -> Self {
self.flags = flags;
self
}
pub fn float(&self, _server: Server<'_>) -> f32 {
self.current_float()
}
pub const fn help(mut self, help: &'static CStr) -> Self {
self.help = help;
self
}
pub fn int(&self, _server: Server<'_>) -> c_int {
self.current_int()
}
pub const fn max(mut self, max: f32) -> Self {
self.max = Some(max);
self
}
pub const fn min(mut self, min: f32) -> Self {
self.min = Some(min);
self
}
pub const fn name(&self) -> &'static CStr {
self.name
}
fn prepare(&self, binding: ServerBinding, dll_identifier: sys::CVarDLLIdentifier_t) {
let raw = self.raw.get();
unsafe {
(&raw mut (*raw)._base.vtable_).write(primary_vtable().cast());
(&raw mut (*raw)._base_1.vtable_).write(secondary_vtable());
(&raw mut (*raw)._base.m_pNext).write(ptr::null_mut());
(&raw mut (*raw)._base.m_pszName).write(self.name.as_ptr());
(&raw mut (*raw)._base.m_pszHelpString).write(self.help.as_ptr());
(&raw mut (*raw)._base.m_nFlags).write(self.flags.bits());
(&raw mut (*raw).m_pParent).write(raw);
(&raw mut (*raw).m_bHasMin).write(self.min.is_some());
(&raw mut (*raw).m_fMinVal).write(self.min.unwrap_or_default());
(&raw mut (*raw).m_bHasMax).write(self.max.is_some());
(&raw mut (*raw).m_fMaxVal).write(self.max.unwrap_or_default());
}
self.binding.set(Some(binding));
self.dll_identifier.set(dll_identifier);
}
pub fn register(
self: Pin<&'static Self>,
server: Server<'_>,
binding: ServerBinding,
registrar: &impl UnlinksBeforeUnload,
) -> Result<(), RegisterCommandError> {
unsafe { self.register_unmanaged(server, binding, registrar) }
}
pub unsafe fn register_unmanaged(
self: Pin<&'static Self>,
server: Server<'_>,
binding: ServerBinding,
registrar: &impl CommandRegistrar,
) -> Result<(), RegisterCommandError> {
let variable = self.get_ref();
unsafe {
register_base(
variable.as_base(),
variable.name,
CommandBaseKind::Variable,
server,
registrar,
|dll_identifier| variable.prepare(binding, dll_identifier),
)
}
}
#[doc(alias = "Revert")]
pub fn revert(&self, server: Server<'_>) {
self.set_string(server, self.default);
}
pub fn set_float(&self, server: Server<'_>, value: f32) {
self.set_float_value(self.change_callbacks(Some(server)), value, false);
}
fn set_float_value(&self, callbacks: Option<Cvar<'_>>, value: f32, force: bool) {
if value == self.current_float() && !force {
return;
}
let mut value = value;
self.clamp(&mut value);
self.set_values(callbacks, value, value as c_int, || format_float(value));
}
pub fn set_int(&self, server: Server<'_>, value: c_int) {
self.set_int_value(self.change_callbacks(Some(server)), value);
}
fn set_int_value(&self, callbacks: Option<Cvar<'_>>, value: c_int) {
if value == self.current_int() {
return;
}
let mut float = value as f32;
let int = if self.clamp(&mut float) {
float as c_int
} else {
value
};
self.set_values(callbacks, float, int, || format_int(int));
}
pub fn set_string(&self, server: Server<'_>, value: &CStr) {
self.set_string_value(self.change_callbacks(Some(server)), Some(value));
}
fn set_string_value(&self, callbacks: Option<Cvar<'_>>, value: Option<&CStr>) {
let mut float = value.map_or(0.0, |value| parse_float(value.to_bytes()) as f32);
let clamped = self.clamp(&mut float);
self.set_values(callbacks, float, float as c_int, || match value {
_ if clamped => format_float(float),
Some(value) => value.to_owned(),
None => CString::default(),
});
}
fn set_values(
&self,
callbacks: Option<Cvar<'_>>,
float: f32,
int: c_int,
text: impl FnOnce() -> CString,
) {
let raw = self.raw.get();
let old_float = self.current_float();
unsafe {
(&raw mut (*raw).m_fValue).write(float);
(&raw mut (*raw).m_nValue).write(int);
}
if self.current_flags() & NEVER_AS_STRING == 0 {
self.change_string(callbacks, text(), old_float);
}
}
pub fn string(&self, _server: Server<'_>) -> CString {
unsafe { CStr::from_ptr((&raw const (*self.raw.get()).m_pszString).read()) }.to_owned()
}
pub fn unregister(
&self,
server: Server<'_>,
registrar: &impl CommandRegistrar,
) -> Result<(), UnregisterCommandError> {
unsafe {
unregister_base(
self.as_base(),
self.name,
CommandBaseKind::Variable,
server,
registrar,
)
}
}
}
impl std::fmt::Debug for ConsoleVariable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConsoleVariable")
.field("name", &self.name)
.field("default", &self.default)
.field("help", &self.help)
.field("flags", &self.flags)
.field("min", &self.min)
.field("max", &self.max)
.finish_non_exhaustive()
}
}
unsafe impl Sync for ConsoleVariable {}
#[repr(C)]
struct PrimaryVtable {
destructors: CppDestructors,
is_command: unsafe extern "C" fn(this: *const sys::ConVar) -> bool,
is_flag_set: unsafe extern "C" fn(this: *const sys::ConVar, flag: c_int) -> bool,
add_flags: unsafe extern "C" fn(this: *mut sys::ConVar, flags: c_int),
get_name: unsafe extern "C" fn(this: *const sys::ConVar) -> *const c_char,
get_help_text: unsafe extern "C" fn(this: *const sys::ConVar) -> *const c_char,
is_registered: unsafe extern "C" fn(this: *const sys::ConVar) -> bool,
get_dll_identifier: unsafe extern "C" fn(this: *const sys::ConVar) -> sys::CVarDLLIdentifier_t,
create_base: unsafe extern "C" fn(
this: *mut sys::ConVar,
name: *const c_char,
help: *const c_char,
flags: c_int,
),
init: unsafe extern "C" fn(this: *mut sys::ConVar),
#[cfg(not(target_os = "windows"))]
set_value_string: unsafe extern "C" fn(this: *mut sys::ConVar, value: *const c_char),
#[cfg(not(target_os = "windows"))]
set_value_float: unsafe extern "C" fn(this: *mut sys::ConVar, value: f32),
#[cfg(not(target_os = "windows"))]
set_value_int: unsafe extern "C" fn(this: *mut sys::ConVar, value: c_int),
internal_set_value: unsafe extern "C" fn(this: *mut sys::ConVar, value: *const c_char),
internal_set_float_value: unsafe extern "C" fn(this: *mut sys::ConVar, value: f32),
internal_set_int_value: unsafe extern "C" fn(this: *mut sys::ConVar, value: c_int),
clamp_value: unsafe extern "C" fn(this: *mut sys::ConVar, value: *mut f32) -> bool,
change_string_value:
unsafe extern "C" fn(this: *mut sys::ConVar, value: *const c_char, old_float: f32),
create_vtbl: unsafe extern "C" fn(
this: *mut sys::ConVar,
name: *const c_char,
default: *const c_char,
flags: c_int,
help: *const c_char,
has_min: bool,
min: f32,
has_max: bool,
max: f32,
callback: sys::FnChangeCallback_t,
),
internal_set_float_value2:
unsafe extern "C" fn(this: *mut sys::ConVar, value: f32, force: bool),
}
#[repr(C, align(4096))]
struct VtablePage(UnsafeCell<Vtables>);
unsafe impl Sync for VtablePage {}
#[repr(C)]
struct Vtables {
#[cfg(target_os = "windows")]
primary_locator: *const c_void,
#[cfg(not(target_os = "windows"))]
primary_top: isize,
#[cfg(not(target_os = "windows"))]
primary_type_info: *const c_void,
primary: PrimaryVtable,
#[cfg(target_os = "windows")]
secondary_locator: *const c_void,
#[cfg(not(target_os = "windows"))]
secondary_top: isize,
#[cfg(not(target_os = "windows"))]
secondary_type_info: *const c_void,
secondary: sys::IConVar__bindgen_vtable,
}
unsafe extern "C" fn add_flags(this: *mut sys::ConVar, flags: c_int) {
unsafe {
let field = &raw mut (*this)._base.m_nFlags;
field.write(field.read() | flags);
}
}
unsafe extern "C" fn change_string_value(
this: *mut sys::ConVar,
value: *const c_char,
old_float: f32,
) {
unsafe {
from_engine(this, |variable, callbacks| {
let value = borrow_cstr(value).unwrap_or_default().to_owned();
variable.change_string(callbacks, value, old_float)
})
};
}
unsafe extern "C" fn clamp_value(this: *mut sys::ConVar, value: *mut f32) -> bool {
unsafe {
from_engine(this, |variable, _| match value.as_mut() {
Some(value) => variable.clamp(value),
None => false,
})
}
.unwrap_or(false)
}
unsafe extern "C" fn create_base(
_this: *mut sys::ConVar,
_name: *const c_char,
_help: *const c_char,
_flags: c_int,
) {
}
#[allow(clippy::too_many_arguments, reason = "the engine's signature")]
unsafe extern "C" fn create_vtbl(
_this: *mut sys::ConVar,
_name: *const c_char,
_default: *const c_char,
_flags: c_int,
_help: *const c_char,
_has_min: bool,
_min: f32,
_has_max: bool,
_max: f32,
_callback: sys::FnChangeCallback_t,
) {
}
fn format_float(value: f32) -> CString {
let mut text = match value {
value if value.is_nan() && value.is_sign_negative() => "-nan".to_owned(),
value if value.is_nan() => "nan".to_owned(),
value => format!("{:.6}", f64::from(value)),
};
text.truncate(31);
unsafe { CString::from_vec_unchecked(text.into_bytes()) }
}
fn format_int(value: c_int) -> CString {
unsafe { CString::from_vec_unchecked(value.to_string().into_bytes()) }
}
unsafe fn from_engine<R>(
this: *const sys::ConVar,
f: impl FnOnce(&ConsoleVariable, Option<Cvar<'_>>) -> R,
) -> Option<R> {
let outcome = catch_unwind(AssertUnwindSafe(|| {
let variable = unsafe { &*this.cast::<ConsoleVariable>() };
let scope = ();
let server = variable
.binding
.get()
.map(|binding| unsafe { binding.server(&scope) });
f(variable, variable.change_callbacks(server))
}));
outcome.map_err(drop_payload).ok()
}
unsafe fn from_interface(this: *const sys::IConVar) -> *mut sys::ConVar {
unsafe { this.byte_sub(INTERFACE_OFFSET) }
.cast::<sys::ConVar>()
.cast_mut()
}
unsafe extern "C" fn get_dll_identifier(this: *const sys::ConVar) -> sys::CVarDLLIdentifier_t {
unsafe { &*this.cast::<ConsoleVariable>() }
.dll_identifier
.get()
}
unsafe extern "C" fn get_help_text(this: *const sys::ConVar) -> *const c_char {
unsafe { (&raw const (*this)._base.m_pszHelpString).read() }
}
unsafe extern "C" fn get_name(this: *const sys::ConVar) -> *const c_char {
unsafe { (&raw const (*this)._base.m_pszName).read() }
}
unsafe extern "C" fn init(_this: *mut sys::ConVar) {}
unsafe extern "C" fn interface_get_name(this: *const sys::IConVar) -> *const c_char {
unsafe { get_name(from_interface(this)) }
}
unsafe extern "C" fn interface_is_flag_set(this: *const sys::IConVar, flag: c_int) -> bool {
unsafe { is_flag_set(from_interface(this), flag) }
}
unsafe extern "C" fn interface_set_value_float(this: *mut sys::IConVar, value: f32) {
unsafe { set_value_float(from_interface(this), value) };
}
unsafe extern "C" fn interface_set_value_int(this: *mut sys::IConVar, value: c_int) {
unsafe { set_value_int(from_interface(this), value) };
}
unsafe extern "C" fn interface_set_value_string(this: *mut sys::IConVar, value: *const c_char) {
unsafe { set_value_string(from_interface(this), value) };
}
unsafe extern "C" fn internal_set_float_value2(this: *mut sys::ConVar, value: f32, force: bool) {
unsafe {
from_engine(this, |variable, callbacks| {
variable.set_float_value(callbacks, value, force)
})
};
}
unsafe extern "C" fn is_command(_this: *const sys::ConVar) -> bool {
false
}
unsafe extern "C" fn is_flag_set(this: *const sys::ConVar, flag: c_int) -> bool {
unsafe { (&raw const (*this)._base.m_nFlags).read() & flag != 0 }
}
unsafe extern "C" fn is_registered(this: *const sys::ConVar) -> bool {
unsafe { (&raw const (*this)._base.m_bRegistered).read() }
}
pub(super) const fn parse_float(text: &[u8]) -> f64 {
const MAX_DIGITS: u64 = 1_000_000_000_000_000_000;
let mut index = skip_space(text);
let negative = index < text.len() && text[index] == b'-';
if index < text.len() && (text[index] == b'-' || text[index] == b'+') {
index += 1;
}
let mut mantissa: u64 = 0;
let mut exponent: i64 = 0;
let mut digits = false;
while index < text.len() && text[index].is_ascii_digit() {
if mantissa < MAX_DIGITS {
mantissa = mantissa * 10 + (text[index] - b'0') as u64;
} else {
exponent += 1;
}
digits = true;
index += 1;
}
if index < text.len() && text[index] == b'.' {
index += 1;
while index < text.len() && text[index].is_ascii_digit() {
if mantissa < MAX_DIGITS {
mantissa = mantissa * 10 + (text[index] - b'0') as u64;
exponent -= 1;
}
digits = true;
index += 1;
}
}
if !digits {
return 0.0;
}
if index < text.len() && (text[index] == b'e' || text[index] == b'E') {
let mut cursor = index + 1;
let exponent_negative = cursor < text.len() && text[cursor] == b'-';
if cursor < text.len() && (text[cursor] == b'-' || text[cursor] == b'+') {
cursor += 1;
}
let mut written: i64 = 0;
let mut exponent_digits = false;
while cursor < text.len() && text[cursor].is_ascii_digit() {
written = written
.saturating_mul(10)
.saturating_add((text[cursor] - b'0') as i64);
exponent_digits = true;
cursor += 1;
}
if exponent_digits {
exponent += if exponent_negative { -written } else { written };
}
}
let exponent = if exponent > 400 {
400
} else if exponent < -400 {
-400
} else {
exponent
};
let mut value = mantissa as f64;
let mut scale = 1.0;
let mut steps = exponent.unsigned_abs();
while steps > 0 {
scale *= 10.0;
steps -= 1;
}
if exponent < 0 {
value /= scale;
} else {
value *= scale;
}
if negative { -value } else { value }
}
pub(super) const fn parse_int(text: &[u8]) -> c_int {
let mut index = skip_space(text);
let negative = index < text.len() && text[index] == b'-';
if index < text.len() && (text[index] == b'-' || text[index] == b'+') {
index += 1;
}
let mut value: i64 = 0;
while index < text.len() && text[index].is_ascii_digit() {
let digit = (text[index] - b'0') as i64;
value = value.saturating_mul(10).saturating_add(digit);
index += 1;
}
if negative {
value = -value;
}
if value > c_int::MAX as i64 {
c_int::MAX
} else if value < c_int::MIN as i64 {
c_int::MIN
} else {
value as c_int
}
}
fn primary_vtable() -> *const PrimaryVtable {
unsafe { &raw const (*VTABLES.0.get()).primary }
}
fn secondary_vtable() -> *const sys::IConVar__bindgen_vtable {
unsafe { &raw const (*VTABLES.0.get()).secondary }
}
unsafe extern "C" fn set_value_float(this: *mut sys::ConVar, value: f32) {
unsafe {
from_engine(this, |variable, callbacks| {
variable.set_float_value(callbacks, value, false)
})
};
}
unsafe extern "C" fn set_value_int(this: *mut sys::ConVar, value: c_int) {
unsafe {
from_engine(this, |variable, callbacks| {
variable.set_int_value(callbacks, value)
})
};
}
unsafe extern "C" fn set_value_string(this: *mut sys::ConVar, value: *const c_char) {
unsafe {
from_engine(this, |variable, callbacks| {
variable.set_string_value(callbacks, borrow_cstr(value))
})
};
}
const fn skip_space(text: &[u8]) -> usize {
let mut index = 0;
while index < text.len() && matches!(text[index], b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r') {
index += 1;
}
index
}
#[cfg(target_os = "windows")]
mod type_information {
use super::INTERFACE_OFFSET;
use crate::rtti::CompleteObjectLocator;
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::mem::offset_of;
use std::ptr;
const HAS_HIERARCHY: u32 = 0x40;
pub(super) const PRIMARY: usize = offset_of!(TypeInformation, primary);
pub(super) const SECONDARY: usize = offset_of!(TypeInformation, secondary);
static TYPE_INFORMATION: TypeInformationCell =
TypeInformationCell(UnsafeCell::new(TypeInformation {
_image_start: 0,
type_descriptor: TypeDescriptor {
vtable: ptr::null(),
undecorated_name: ptr::null_mut(),
name: *b".?AVRustConsoleVariable@@\0",
},
primary: CompleteObjectLocator {
signature: 1,
offset: 0,
constructor_displacement: 0,
type_descriptor: offset_of!(TypeInformation, type_descriptor) as u32,
class_descriptor: offset_of!(TypeInformation, hierarchy) as u32,
this: PRIMARY as u32,
},
secondary: CompleteObjectLocator {
signature: 1,
offset: INTERFACE_OFFSET as u32,
constructor_displacement: 0,
type_descriptor: offset_of!(TypeInformation, type_descriptor) as u32,
class_descriptor: offset_of!(TypeInformation, hierarchy) as u32,
this: SECONDARY as u32,
},
hierarchy: ClassHierarchyDescriptor {
signature: 0,
attributes: 0,
base_classes: 1,
base_class_array: offset_of!(TypeInformation, base_class_array) as u32,
},
base_class_array: [offset_of!(TypeInformation, base_class) as u32],
base_class: BaseClassDescriptor {
type_descriptor: offset_of!(TypeInformation, type_descriptor) as u32,
contained_bases: 0,
member_displacement: 0,
vbtable_displacement: -1,
vbtable_offset: 0,
attributes: HAS_HIERARCHY,
class_descriptor: offset_of!(TypeInformation, hierarchy) as u32,
},
}));
#[repr(C)]
struct BaseClassDescriptor {
type_descriptor: u32,
contained_bases: u32,
member_displacement: i32,
vbtable_displacement: i32,
vbtable_offset: i32,
attributes: u32,
class_descriptor: u32,
}
#[repr(C)]
struct ClassHierarchyDescriptor {
signature: u32,
attributes: u32,
base_classes: u32,
base_class_array: u32,
}
#[repr(C)]
struct TypeDescriptor {
vtable: *const c_void,
undecorated_name: *mut c_void,
name: [u8; 26],
}
#[repr(C)]
struct TypeInformation {
_image_start: u64,
type_descriptor: TypeDescriptor,
primary: CompleteObjectLocator,
secondary: CompleteObjectLocator,
hierarchy: ClassHierarchyDescriptor,
base_class_array: [u32; 1],
base_class: BaseClassDescriptor,
}
#[repr(transparent)]
struct TypeInformationCell(UnsafeCell<TypeInformation>);
unsafe impl Sync for TypeInformationCell {}
pub(super) const fn locator(offset: usize) -> *const c_void {
(&raw const TYPE_INFORMATION)
.cast::<u8>()
.wrapping_add(offset)
.cast()
}
}
#[cfg(not(target_os = "windows"))]
mod type_information {
use std::ffi::{CStr, c_char, c_void};
const MANGLED_NAME: &CStr = c"19RustConsoleVariable";
static TYPE_INFO: TypeInfo = TypeInfo {
vtable: (&raw const CLASS_TYPE_INFO_VTABLE)
.cast::<*const c_void>()
.wrapping_add(2),
name: MANGLED_NAME.as_ptr(),
};
#[repr(C)]
struct TypeInfo {
vtable: *const *const c_void,
name: *const c_char,
}
unsafe impl Sync for TypeInfo {}
#[link(name = "stdc++")]
unsafe extern "C" {
#[link_name = "_ZTVN10__cxxabiv117__class_type_infoE"]
static CLASS_TYPE_INFO_VTABLE: [*const c_void; 0];
}
#[cfg(test)]
pub(super) const fn class_type_info_vtable() -> *const *const c_void {
(&raw const CLASS_TYPE_INFO_VTABLE)
.cast::<*const c_void>()
.wrapping_add(2)
}
pub(super) const fn type_info() -> *const c_void {
(&raw const TYPE_INFO).cast()
}
}
#[cfg(test)]
pub(super) mod test_support {
use super::*;
#[cfg(not(target_os = "windows"))]
pub(crate) fn class_type_info_vtable() -> *const *const c_void {
type_information::class_type_info_vtable()
}
pub(crate) fn interface_of(variable: &ConsoleVariable) -> *mut sys::IConVar {
unsafe { variable.raw.get().byte_add(INTERFACE_OFFSET) }.cast()
}
#[cfg(not(target_os = "windows"))]
pub(crate) fn type_info() -> *const c_void {
type_information::type_info()
}
}