Skip to content

Commit

Permalink
Add basic TMap implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
trumank committed Jun 7, 2024
1 parent 5896f61 commit ab00454
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 2 deletions.
241 changes: 241 additions & 0 deletions hook/src/ue/map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
use std::fmt::Debug;

use super::TArray;

pub trait UEHash {
fn ue_hash(&self) -> u32;
}

#[derive(Default, Debug)]
#[repr(C)]
struct TSetElement<V> {
value: V,
hash_next_id: FSetElementId,
hash_index: i32,
}
impl<K: UEHash, V> UEHash for TSetElement<TTuple<K, V>> {
fn ue_hash(&self) -> u32 {
self.value.a.ue_hash()
}
}

#[derive(Default, Clone, Copy)]
#[repr(C)]
pub struct FSetElementId {
index: i32,
}
impl FSetElementId {
pub fn is_valid(self) -> bool {
self.index != -1
}
}
impl Debug for FSetElementId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FSetElementId({:?})", self.index)
}
}

#[derive(Default, Debug)]
#[repr(C)]
struct TTuple<A, B> {
a: A,
b: B,
}

#[repr(C)]
union TSparseArrayElementOrFreeListLink<E> {
element: std::mem::ManuallyDrop<E>,
list_link: ListLink,
}

#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct ListLink {
next_free_index: i32,
prev_free_index: i32,
}

#[derive(Debug)]
#[repr(C)]
struct TInlineAllocator<const N: usize, V> {
inline_data: [V; N],
secondary_data: *const V, // TSizedHeapAllocator<32>::ForElementType<unsigned int>,
}
impl<const N: usize, V: Default + Copy> Default for TInlineAllocator<N, V> {
fn default() -> Self {
Self {
inline_data: [Default::default(); N],
secondary_data: std::ptr::null(),
}
}
}

impl<const N: usize, V> TInlineAllocator<N, V> {
fn get_allocation(&self) -> *const V {
if !self.secondary_data.is_null() {
self.secondary_data
} else {
self.inline_data.as_ptr()
}
}
}

#[derive(Default, Debug)]
#[repr(C)]
struct TBitArray {
allocator_instance: TInlineAllocator<4, u32>,
num_bits: i32,
max_bits: i32,
}
impl TBitArray {
fn get_data(&self) -> *const u32 {
self.allocator_instance.get_allocation()
}

fn index(&self, index: usize) -> FBitReference<'_> {
assert!(index < self.num_bits as usize);
let num_bits_per_dword = 32;
FBitReference {
data: unsafe { &*self.get_data().add(index / num_bits_per_dword) },
mask: 1 << (index & (num_bits_per_dword - 1)),
}
}
}

#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct FBitReference<'data> {
data: &'data u32,
mask: u32,
}
impl FBitReference<'_> {
fn bool(self) -> bool {
(self.data & self.mask) != 0
}
}

#[repr(C)]
struct TSparseArray<E> {
data: TArray<TSparseArrayElementOrFreeListLink<E>>,
allocation_flags: TBitArray,
first_free_index: i32,
num_free_indices: i32,
}
impl<E> Default for TSparseArray<E> {
fn default() -> Self {
Self {
data: Default::default(),
allocation_flags: Default::default(),
first_free_index: 0,
num_free_indices: 0,
}
}
}
impl<E> TSparseArray<E> {
fn index(&self, index: usize) -> &E {
assert!(index < self.data.len() && index < self.allocation_flags.num_bits as usize);
assert!(self.allocation_flags.index(index).bool());
unsafe { &self.data.as_slice()[index].element }
}
}

struct DbgTSparseArrayData<'a, E>(&'a TSparseArray<E>);
impl<E: Debug> Debug for DbgTSparseArrayData<'_, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_list();
for i in 0..(self.0.allocation_flags.num_bits as usize) {
if self.0.allocation_flags.index(i).bool() {
dbg.entry(self.0.index(i));
}
}
dbg.finish()
}
}

impl<E: Debug> Debug for TSparseArray<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TSparseArray")
.field("data", &DbgTSparseArrayData(self))
.field("allocation_flags", &self.allocation_flags)
.field("first_free_index", &self.first_free_index)
.field("num_free_indices", &self.num_free_indices)
.finish()
}
}

#[repr(C)]
pub struct TMap<K: UEHash, V> {
elements: TSparseArray<TSetElement<TTuple<K, V>>>,
hash: TInlineAllocator<1, FSetElementId>,
hash_size: i32,
}
impl<K: UEHash, V> TMap<K, V> {
fn hash(&self) -> &[FSetElementId] {
unsafe { std::slice::from_raw_parts(self.hash.get_allocation(), self.hash_size as usize) }
}
}
impl<K: UEHash, V> Default for TMap<K, V> {
fn default() -> Self {
Self {
elements: Default::default(),
hash: Default::default(),
hash_size: 0,
}
}
}
impl<K: UEHash + Debug, V: Debug> Debug for TMap<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TMap")
.field("elements", &self.elements)
.field("hash", &self.hash())
.finish()
}
}

impl<K: PartialEq + UEHash, V> TMap<K, V> {
pub fn find(&self, key: K) -> Option<&V> {
let id = self.find_id(key);
if id.is_valid() {
Some(&self.elements.index(id.index as usize).value.b)
} else {
None
}
}
pub fn find_id(&self, key: K) -> FSetElementId {
if self.elements.data.len() != self.elements.num_free_indices as usize {
let key_hash = key.ue_hash();
let hash = &self.hash();

let mut i: FSetElementId =
hash[(((self.hash_size as i64) - 1) & (key_hash as i64)) as usize];

if i.is_valid() {
loop {
let elm = self.elements.index(i.index as usize);

if elm.value.a == key {
return i;
}

i = elm.hash_next_id;
if !i.is_valid() {
break;
}
}
}
}

FSetElementId { index: -1 }
}
}

#[cfg(test)]
mod test {
use crate::ue::FName;

use super::*;
const _: [u8; 0x50] = [0; std::mem::size_of::<TMap<FName, [u8; 0x20]>>()];
const _: [u8; 0x38] =
[0; std::mem::size_of::<TSparseArray<TSetElement<TTuple<FName, [u8; 0x20]>>>>()];
const _: [u8; 0x10] = [0; std::mem::size_of::<TInlineAllocator<1, FSetElementId>>()];
}
2 changes: 2 additions & 0 deletions hook/src/ue/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod array;
pub mod kismet;
mod malloc;
mod map;
mod name;
mod object;
mod string;

pub use array::*;
pub use malloc::*;
pub use map::*;
pub use name::*;
pub use object::*;
pub use string::*;
Expand Down
18 changes: 16 additions & 2 deletions hook/src/ue/name.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{globals, ue::FString};

use super::UEHash;

#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum EFindName {
Expand All @@ -8,7 +10,7 @@ pub enum EFindName {
ReplaceNotSafeForThreading,
}

#[derive(Default, Clone, Copy)]
#[derive(Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
#[repr(C)]
pub struct FName {
pub comparison_index: FNameEntryId,
Expand All @@ -32,7 +34,7 @@ impl std::fmt::Debug for FName {
}
}

#[derive(Default, Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
#[repr(C)]
pub struct FNameEntryId {
pub value: u32,
Expand All @@ -47,3 +49,15 @@ impl std::fmt::Display for FName {
write!(f, "{string}")
}
}
impl UEHash for FNameEntryId {
fn ue_hash(&self) -> u32 {
let value = self.value;
(value >> 4) + value.wrapping_mul(0x10001) + (value >> 0x10).wrapping_mul(0x80001)
}
}

impl UEHash for FName {
fn ue_hash(&self) -> u32 {
self.comparison_index.ue_hash() + self.number
}
}

0 comments on commit ab00454

Please sign in to comment.