use std::ffi::{CStr, c_char, c_void};
pub(crate) unsafe fn subobject_offset(object: *const c_void, class: &str) -> Option<isize> {
let (offset, name) = unsafe { dynamic_type(object) }?;
match_class_name(name.to_bytes(), class).then_some(offset)
}
#[cfg(target_os = "windows")]
fn match_class_name(name: &[u8], class: &str) -> bool {
name.strip_prefix(b".?AV")
.and_then(|name| name.strip_suffix(b"@@"))
.is_some_and(|name| name == class.as_bytes())
}
#[cfg(not(target_os = "windows"))]
fn match_class_name(name: &[u8], class: &str) -> bool {
let length = class.len().to_string();
name.strip_prefix(length.as_bytes())
.is_some_and(|name| name == class.as_bytes())
}
#[cfg(target_os = "windows")]
#[repr(C)]
#[derive(Clone, Copy)]
struct CompleteObjectLocator {
signature: u32,
offset: u32,
constructor_displacement: u32,
type_descriptor: u32,
class_descriptor: u32,
this: u32,
}
#[cfg(target_os = "windows")]
unsafe fn dynamic_type<'a>(object: *const c_void) -> Option<(isize, &'a CStr)> {
let locator = unsafe { object.cast::<*const *const c_void>().read().sub(1).read() }
.cast::<CompleteObjectLocator>();
if locator.is_null() || !locator.is_aligned() {
return None;
}
let located = unsafe { locator.read() };
if located.signature != 1 {
return None;
}
let image = (locator as usize).checked_sub(located.this as usize)?;
let name = image
.checked_add(located.type_descriptor as usize)?
.checked_add(16)?;
Some((located.offset as isize, unsafe {
CStr::from_ptr(name as *const c_char)
}))
}
#[cfg(not(target_os = "windows"))]
unsafe fn dynamic_type<'a>(object: *const c_void) -> Option<(isize, &'a CStr)> {
let (offset_to_top, type_info) = unsafe {
let vtable = object.cast::<*const isize>().read();
(
vtable.sub(2).read(),
vtable.sub(1).read() as *const *const c_char,
)
};
if type_info.is_null() {
return None;
}
let name = unsafe { type_info.add(1).read() };
(!name.is_null()).then(|| (-offset_to_top, unsafe { CStr::from_ptr(name) }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_follow_the_abi() {
#[cfg(target_os = "windows")]
{
assert!(match_class_name(b".?AVCGameClient@@", "CGameClient"));
assert!(!match_class_name(b".?AVCGameClientX@@", "CGameClient"));
assert!(!match_class_name(b"11CGameClient", "CGameClient"));
}
#[cfg(not(target_os = "windows"))]
{
assert!(match_class_name(b"11CGameClient", "CGameClient"));
assert!(!match_class_name(b"12CGameClientX", "CGameClient"));
assert!(!match_class_name(b".?AVCGameClient@@", "CGameClient"));
}
}
}