From 3d3b1b20c91aabd58290021027dcaf15038d620b Mon Sep 17 00:00:00 2001 From: Janis Date: Mon, 17 Apr 2023 18:46:27 +0200 Subject: [PATCH] split into multiple files --- src/core_types.rs | 237 ++++++ src/global_tables/mod.rs | 3 + src/global_tables/names.rs | 178 +++++ src/global_tables/objects.rs | 254 +++++++ src/lib.rs | 1361 +--------------------------------- src/types.rs | 671 +++++++++++++++++ 6 files changed, 1346 insertions(+), 1358 deletions(-) create mode 100644 src/core_types.rs create mode 100644 src/global_tables/mod.rs create mode 100644 src/global_tables/names.rs create mode 100644 src/global_tables/objects.rs create mode 100644 src/types.rs diff --git a/src/core_types.rs b/src/core_types.rs new file mode 100644 index 0000000..a140afb --- /dev/null +++ b/src/core_types.rs @@ -0,0 +1,237 @@ +#![allow(non_upper_case_globals)] +use bitflags::bitflags; +use std::ptr::NonNull; + +#[derive(Debug)] +#[repr(transparent)] +pub struct VTbl(NonNull<()>); + +impl VTbl {} + +bitflags! { + #[repr(C)] + pub struct EObjectFlags: u32 { + const RF_NoFlags = 0x00000000; + const RF_Public = 0x00000001; + const RF_Standalone = 0x00000002; + const RF_MarkAsNative = 0x00000004; + const RF_Transactional = 0x00000008; + const RF_ClassDefaultObject = 0x00000010; + const RF_ArchetypeObject = 0x00000020; + const RF_Transient = 0x00000040; + const RF_MarkAsRootSet = 0x00000080; + const RF_TagGarbageTemp = 0x00000100; + const RF_NeedInitialization = 0x00000200; + const RF_NeedLoad = 0x00000400; + const RF_KeepForCooker = 0x00000800; + const RF_NeedPostLoad = 0x00001000; + const RF_NeedPostLoadSubobjects = 0x00002000; + const RF_NewerVersionExists = 0x00004000; + const RF_BeginDestroyed = 0x00008000; + const RF_FinishDestroyed = 0x00010000; + const RF_BeingRegenerated = 0x00020000; + const RF_DefaultSubObject = 0x00040000; + const RF_WasLoaded = 0x00080000; + const RF_TextExportTransient = 0x00100000; + const RF_LoadCompleted = 0x00200000; + const RF_InheritableComponentTemplate = 0x00400000; + const RF_DuplicateTransient = 0x00800000; + const RF_StrongRefOnFrame = 0x01000000; + const RF_NonPIEDuplicateTransient = 0x02000000; + const RF_Dynamic = 0x04000000; + const RF_WillBeLoaded = 0x08000000; + const RF_HasExternalPackage = 0x10000000; + } +} + +pub use tarray::{FString, TArray}; + +use crate::global_tables::names::GNAMES; + +#[repr(C)] +#[derive(Debug)] +pub struct UObject { + pub vtbl: VTbl, + pub object_flags: EObjectFlags, //EObjectFlags, + pub internal_index: u32, + pub class: Option>, + pub name: FName, + pub outer: Option>, +} + +#[repr(C)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub struct FName { + pub comparison_index: u32, + pub number: u32, +} + +impl FName { + pub fn get_name(&self) -> anyhow::Result { + GNAMES + .read() + .unwrap() + .as_names() + .unwrap() + .fname_to_string(self) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct UField { + pub uobject: UObject, + pub next: Option>, +} + +#[repr(C)] +#[derive(Debug)] +pub struct UEnum { + pub ufield: UField, + pub cpp_type: FString, + pub names: TArray, + pub cpp_form: u32, +} + +#[repr(C)] +#[derive(Debug)] +pub struct UStruct { + pub(crate) ufield: UField, + pub(crate) super_field: Option>, + pub(crate) children: Option>, + pub(crate) property_size: u32, + pub(crate) min_alignment: u32, + padding1: [u8; 0x48], +} + +#[repr(C)] +#[derive(Debug)] +pub struct UClass { + pub(crate) ustruct: UStruct, + padding1: [u8; 0xF8], +} + +#[repr(C)] +#[derive(Debug)] +pub struct UProperty { + pub(crate) ufield: UField, + array_dim: i32, + element_size: i32, + property_flags: u64, + rep_index: i16, + rep_notify_function: FName, + offset: i32, + property_link_next: Option>, + next_ref: Option>, + destructor_link_next: Option>, + post_construct_link_next: Option>, +} + +#[repr(C)] +#[derive(Debug)] +pub struct UFunction { + pub(crate) ustruct: UStruct, + function_flags: u32, + rep_offset: u16, + num_params: u8, + params_size: u16, + return_value_offset: u16, + rpc_id: u16, + rpc_response_id: u16, + first_property_to_init: Option>, + function: Option>, +} + +pub mod tarray { + use std::{ops::Index, ptr::NonNull, slice::SliceIndex}; + + #[repr(C)] + #[derive(Debug)] + pub struct TArray { + data: Option>, + count: u32, + max: u32, + } + + unsafe impl Send for TArray where T: Send {} + unsafe impl Sync for TArray where T: Sync {} + + pub type FString = TArray; + + impl ToString for FString { + fn to_string(&self) -> String { + widestring::U16CStr::from_slice(&self) + .expect("invalid utf16 string") + .to_string_lossy() + } + } + + impl TArray { + pub fn len(&self) -> usize { + self.count as usize + } + + pub fn capacity(&self) -> usize { + self.max as usize + } + } + + impl> Index for TArray { + type Output = I::Output; + + fn index(&self, i: I) -> &Self::Output { + let data = + unsafe { std::slice::from_raw_parts(self.data.unwrap().as_ptr(), self.len()) }; + + &data[i] + } + } + + impl std::ops::Deref for TArray { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + unsafe { std::slice::from_raw_parts(self.data.unwrap().as_ptr(), self.len()) } + } + } + + pub struct IntoIter { + //array_ref: &'a TArray, + ptr: *const T, + end: *const T, + } + + impl<'a, T> IntoIter { + pub fn new(array: &'a TArray) -> Self { + let ptr = array.data.unwrap().as_ptr(); + Self { + ptr, + end: unsafe { ptr.offset(array.count as isize) }, + } + } + } + + impl<'a, T> Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + if self.ptr == self.end { + None + } else { + let old = self.ptr; + self.ptr = unsafe { self.ptr.offset(1) }; + + Some(unsafe { std::ptr::read(old) }) + } + } + } + + impl<'a, T> IntoIterator for &'a TArray { + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self) + } + } +} diff --git a/src/global_tables/mod.rs b/src/global_tables/mod.rs new file mode 100644 index 0000000..491218c --- /dev/null +++ b/src/global_tables/mod.rs @@ -0,0 +1,3 @@ +pub mod names; + +pub mod objects; diff --git a/src/global_tables/names.rs b/src/global_tables/names.rs new file mode 100644 index 0000000..15d21e9 --- /dev/null +++ b/src/global_tables/names.rs @@ -0,0 +1,178 @@ +use std::{ops::Index, ptr::NonNull, sync::RwLock}; + +use crate::core_types::FName; + +lazy_static::lazy_static! { + pub static ref GNAMES: RwLock = RwLock::new(GNames::new()); +} + +#[derive(Debug, Default)] +pub struct GNames { + names: Option>, +} + +impl GNames { + pub const fn new() -> Self { + Self { names: None } + } + + pub fn set_names(&mut self, names: NonNull) { + self.names = Some(names); + } + + pub fn as_names(&self) -> Option<&NameEntryArray> { + self.names.map(|names| unsafe { names.as_ref() }) + } +} + +unsafe impl Send for GNames {} +unsafe impl Sync for GNames {} + +#[repr(C)] +#[derive(Debug)] +pub struct FNameEntry { + pub index: u32, + pub hash_next: *const FNameEntry, + pub ansi_name: [u8; 1024], +} + +impl FNameEntry { + /// panics if the name cant be turned into a string + pub fn as_string(&self) -> String { + self.try_into().unwrap() + } + + pub fn as_str(&self) -> &str { + let end = self.ansi_name.iter().position(|&c| c == 0); + let bytes = &self.ansi_name[..end.unwrap_or(self.ansi_name.len())]; + // TODO: debug assert this + unsafe { std::str::from_utf8_unchecked(bytes) } + } +} + +impl<'a> TryInto for &'a FNameEntry { + type Error = std::string::FromUtf8Error; + + fn try_into(self) -> Result { + let bytes = self + .ansi_name + .iter() + .take_while(|&&b| b != 0) + .map(|&b| b) + .collect::>(); + String::from_utf8(bytes) + } +} + +const NAMES_ELEMENTS_PER_CHUNK: usize = 16 * 1024; +const NAMES_CHUNK_TABLE_SIZE: usize = + (2 * 1024 * 1024 + NAMES_ELEMENTS_PER_CHUNK - 1) / NAMES_ELEMENTS_PER_CHUNK; + +#[repr(C)] +#[derive(Debug)] +pub struct NameEntryArray { + chunks: [Option>>>; NAMES_CHUNK_TABLE_SIZE], + num_elements: u32, + num_chunks: u32, +} + +unsafe impl Send for NameEntryArray {} +unsafe impl Sync for NameEntryArray {} + +impl NameEntryArray { + /// this does not mean that the name at this index is actually present!, just that looking it up is sound, because all the indices point at allocated memory + pub fn is_valid_index(&self, index: usize) -> bool { + index < self.num_elements as usize + } + + /// panics if chunk_index is out of bounds + fn chunk_as_slice(&self, chunk_index: usize) -> Option<&[Option>]> { + // TODO: probably make this unchecked for release? + self.chunks + // get chunk + [chunk_index] + // cast chunk pointer into slice of FNameEntry pointers + .map(|ptr| unsafe { + core::slice::from_raw_parts(ptr.as_ptr(), NAMES_ELEMENTS_PER_CHUNK) + }) + } + + pub fn get_index(&self, index: usize) -> Option<&FNameEntry> { + if index < self.num_elements as usize { + let chunk_index = index / NAMES_ELEMENTS_PER_CHUNK; + let within_chunk_index = index % NAMES_ELEMENTS_PER_CHUNK; + + // safety: we know chunk_index and within_chunk_index are valid (or do we?); + // TODO: make sure this is actually the case + // it is because index is < than self.num_elements + self.chunk_as_slice(chunk_index)?[within_chunk_index].map(|ptr| unsafe { ptr.as_ref() }) + } else { + None + } + } + + pub fn iter(&self) -> NamesIterator { + NamesIterator::new(self) + } + + pub fn len(&self) -> usize { + self.num_elements as usize + } + + pub fn fname_to_string(&self, name: &FName) -> anyhow::Result { + use anyhow::Context; + Ok(self + .get_index(name.comparison_index as usize) + .context("invalid comparison index")? + .try_into()?) + } +} + +impl Index for NameEntryArray { + type Output = FNameEntry; + + fn index(&self, i: usize) -> &Self::Output { + self.get_index(i).expect("Out of bounds access") + } +} + +pub struct NamesIterator<'a> { + names: &'a NameEntryArray, + index: usize, +} + +impl<'a> NamesIterator<'a> { + pub fn new(names: &'a NameEntryArray) -> Self { + Self { names, index: 0 } + } +} + +impl<'a> Iterator for NamesIterator<'a> { + type Item = &'a FNameEntry; + + fn next(&mut self) -> Option { + loop { + if !self.names.is_valid_index(self.index) { + break None; + } + let item = self.names.get_index(self.index); + self.index += 1; + // skip empty entries, we dont care about them + if item.is_some() { + break item; + } + } + } +} + +impl<'a> IntoIterator for &'a NameEntryArray { + type Item = &'a FNameEntry; + type IntoIter = NamesIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + NamesIterator { + names: self, + index: 0, + } + } +} diff --git a/src/global_tables/objects.rs b/src/global_tables/objects.rs new file mode 100644 index 0000000..a289315 --- /dev/null +++ b/src/global_tables/objects.rs @@ -0,0 +1,254 @@ +use std::{ops::Index, ptr::NonNull, sync::RwLock}; + +use rayon::prelude::ParallelIterator; + +use crate::types; + +lazy_static::lazy_static! { + pub static ref GOBJECTS: RwLock = RwLock::new(GObjects::new()); +} + +#[derive(Debug, Default)] +pub struct GObjects { + objects: Option>, +} + +impl GObjects { + pub const fn new() -> Self { + Self { objects: None } + } + + pub fn set_objects(&mut self, objects: NonNull) { + self.objects = Some(objects); + } + + pub fn as_objects(&self) -> Option<&ObjectArray> { + self.objects.map(|objects| unsafe { objects.as_ref() }) + } +} + +unsafe impl Send for GObjects {} +unsafe impl Sync for GObjects {} + +#[repr(C)] +#[derive(Debug)] +pub struct ObjectArrayItem { + object: Option>, + sn: u32, +} + +unsafe impl Send for ObjectArrayItem {} +unsafe impl Sync for ObjectArrayItem {} + +impl ObjectArrayItem { + pub fn object(&self) -> Option { + types::UObject::maybe_new(self.object) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct ObjectArrayInner { + objects: *const *const ObjectArrayItem, + pre_allocated_objects: Option>, + max_elements: u32, + num_elements: u32, + max_chunks: u32, + num_chunks: u32, +} + +impl ObjectArrayInner { + const ELEMENTS_PER_CHUNK: usize = 64 * 1024; + + fn chunks_as_slice(&self) -> &[*const ObjectArrayItem] { + unsafe { std::slice::from_raw_parts(self.objects, self.num_chunks as usize) } + } + + pub fn get_chunks(&self) -> Vec<&[ObjectArrayItem]> { + (0..self.num_chunks as usize) + .map(|i| self.chunk_as_slice(i)) + .collect() + } + + fn get_chunk_size(&self, chunk_index: usize) -> Option { + if chunk_index < self.num_chunks as usize { + if chunk_index * Self::ELEMENTS_PER_CHUNK <= self.num_elements as usize { + Some(Self::ELEMENTS_PER_CHUNK) + } else { + Some(self.num_elements as usize % Self::ELEMENTS_PER_CHUNK) + } + } else { + None + } + } + + fn chunk_as_slice(&self, chunk_index: usize) -> &[ObjectArrayItem] { + let chunks = self.chunks_as_slice(); + let chunk = unsafe { + std::slice::from_raw_parts( + chunks[chunk_index], + self.get_chunk_size(chunk_index).unwrap(), + ) + }; + + chunk + } + + /// returns the chunk index and the index inside that chunk, or none if the index is out of range + fn get_chunked_index(&self, i: usize) -> Option<(usize, usize)> { + if i < self.num_elements as usize { + let chunk_index = i / Self::ELEMENTS_PER_CHUNK; + let within_chunk_index = i % Self::ELEMENTS_PER_CHUNK; + + Some((chunk_index, within_chunk_index)) + } else { + None + } + } + + pub fn get_index(&self, i: usize) -> Option<&ObjectArrayItem> { + self.get_chunked_index(i) + .map(|(chunk_index, within_chunk_index)| { + &self.chunk_as_slice(chunk_index)[within_chunk_index] + }) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct ObjectArray { + first_gc_index: u32, + last_non_gc_index: u32, + max_objects_not_considered_by_gc: u32, + open_for_disregard_for_gc: bool, + inner: ObjectArrayInner, +} + +unsafe impl Send for ObjectArray {} +unsafe impl Sync for ObjectArray {} + +impl ObjectArray { + pub fn len(&self) -> usize { + self.inner.num_elements as usize + } + + pub fn iter(&self) -> iter::ObjectArrayIterator<'_> { + iter::ObjectArrayIterator::new(self) + } + + pub fn iter_package_objects(&self) -> impl Iterator { + self.iter() + .filter(|entry| matches!(entry.object(), Some(object) if object.is_package_object())) + } + + pub fn par_iter(&self) -> par_iter::ObjectArrayChunkedParallelIterator<'_> { + par_iter::ObjectArrayChunkedParallelIterator::new(self.inner.get_chunks()) + } + + pub fn par_iter_package_objects(&self) -> impl ParallelIterator { + self.par_iter() + .filter(|entry| matches!(entry.object(), Some(object) if object.is_package_object())) + } +} + +impl Index for ObjectArray { + type Output = ObjectArrayItem; + + fn index(&self, i: usize) -> &Self::Output { + self.inner.get_index(i).expect("Out of bounds access") + } +} + +pub mod iter { + use super::{ObjectArray, ObjectArrayItem}; + + pub struct ObjectArrayIterator<'a> { + objects: &'a ObjectArray, + index: usize, + } + + impl<'a> ObjectArrayIterator<'a> { + pub(super) fn new(objects: &'a ObjectArray) -> Self { + Self { objects, index: 0 } + } + } + + impl<'a> Iterator for ObjectArrayIterator<'a> { + type Item = &'a ObjectArrayItem; + + fn next(&mut self) -> Option { + let item = if self.index < self.objects.len() { + Some(&self.objects[self.index]) + } else { + None + }; + + self.index += 1; + + item + } + } + + impl<'a> IntoIterator for &'a ObjectArray { + type Item = &'a ObjectArrayItem; + type IntoIter = ObjectArrayIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + ObjectArrayIterator { + objects: self, + index: 0, + } + } + } +} + +pub mod par_iter { + pub use rayon::iter::{IntoParallelIterator, ParallelIterator}; + + use super::ObjectArrayItem; + + pub struct ObjectArrayChunkedParallelIterator<'a> { + chunks: Vec<&'a [ObjectArrayItem]>, + } + + impl<'a> ObjectArrayChunkedParallelIterator<'a> { + pub fn new(chunks: Vec<&'a [ObjectArrayItem]>) -> Self { + Self { chunks } + } + } + + impl<'a> ParallelIterator for ObjectArrayChunkedParallelIterator<'a> { + type Item = &'a ObjectArrayItem; + + fn drive_unindexed(self, consumer: C) -> C::Result + where + C: rayon::iter::plumbing::UnindexedConsumer, + { + self.chunks + .into_par_iter() + .flatten_iter() + .drive_unindexed(consumer) + } + } +} + +pub trait FindClass { + fn find_class(&self, class_name: S) -> Option + where + S: Into; +} + +impl FindClass for ObjectArray { + fn find_class(&self, class_name: S) -> Option + where + S: Into, + { + let class_name = class_name.into(); + + self.iter() + .filter_map(|item| item.object()) + .filter(|object| object.get_full_name().unwrap() == class_name) + .map(|object| types::UClass::new(unsafe { object.cast() })) + .next() + } +} diff --git a/src/lib.rs b/src/lib.rs index 282bb04..326e123 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,1358 +1,3 @@ -pub mod global_tables { - pub mod names { - use std::{ops::Index, ptr::NonNull, sync::RwLock}; - - use crate::core_types::FName; - - lazy_static::lazy_static! { - pub static ref GNAMES: RwLock = RwLock::new(GNames::new()); - } - - #[derive(Debug, Default)] - pub struct GNames { - names: Option>, - } - - impl GNames { - pub const fn new() -> Self { - Self { names: None } - } - - pub fn set_names(&mut self, names: NonNull) { - self.names = Some(names); - } - - pub fn as_names(&self) -> Option<&NameEntryArray> { - self.names.map(|names| unsafe { names.as_ref() }) - } - } - - unsafe impl Send for GNames {} - unsafe impl Sync for GNames {} - - #[repr(C)] - #[derive(Debug)] - pub struct FNameEntry { - pub index: u32, - pub hash_next: *const FNameEntry, - pub ansi_name: [u8; 1024], - } - - impl FNameEntry { - /// panics if the name cant be turned into a string - pub fn as_string(&self) -> String { - self.try_into().unwrap() - } - - pub fn as_str(&self) -> &str { - let end = self.ansi_name.iter().position(|&c| c == 0); - let bytes = &self.ansi_name[..end.unwrap_or(self.ansi_name.len())]; - // TODO: debug assert this - unsafe { std::str::from_utf8_unchecked(bytes) } - } - } - - impl<'a> TryInto for &'a FNameEntry { - type Error = std::string::FromUtf8Error; - - fn try_into(self) -> Result { - let bytes = self - .ansi_name - .iter() - .take_while(|&&b| b != 0) - .map(|&b| b) - .collect::>(); - String::from_utf8(bytes) - } - } - - const NAMES_ELEMENTS_PER_CHUNK: usize = 16 * 1024; - const NAMES_CHUNK_TABLE_SIZE: usize = - (2 * 1024 * 1024 + NAMES_ELEMENTS_PER_CHUNK - 1) / NAMES_ELEMENTS_PER_CHUNK; - - #[repr(C)] - #[derive(Debug)] - pub struct NameEntryArray { - chunks: [Option>>>; NAMES_CHUNK_TABLE_SIZE], - num_elements: u32, - num_chunks: u32, - } - - unsafe impl Send for NameEntryArray {} - unsafe impl Sync for NameEntryArray {} - - impl NameEntryArray { - /// this does not mean that the name at this index is actually present!, just that looking it up is sound, because all the indices point at allocated memory - pub fn is_valid_index(&self, index: usize) -> bool { - index < self.num_elements as usize - } - - /// panics if chunk_index is out of bounds - fn chunk_as_slice(&self, chunk_index: usize) -> Option<&[Option>]> { - // TODO: probably make this unchecked for release? - self.chunks - // get chunk - [chunk_index] - // cast chunk pointer into slice of FNameEntry pointers - .map(|ptr| unsafe { - core::slice::from_raw_parts(ptr.as_ptr(), NAMES_ELEMENTS_PER_CHUNK) - }) - } - - pub fn get_index(&self, index: usize) -> Option<&FNameEntry> { - if index < self.num_elements as usize { - let chunk_index = index / NAMES_ELEMENTS_PER_CHUNK; - let within_chunk_index = index % NAMES_ELEMENTS_PER_CHUNK; - - // safety: we know chunk_index and within_chunk_index are valid (or do we?); - // TODO: make sure this is actually the case - // it is because index is < than self.num_elements - self.chunk_as_slice(chunk_index)?[within_chunk_index] - .map(|ptr| unsafe { ptr.as_ref() }) - } else { - None - } - } - - pub fn iter(&self) -> NamesIterator { - NamesIterator::new(self) - } - - pub fn len(&self) -> usize { - self.num_elements as usize - } - - pub fn fname_to_string(&self, name: &FName) -> anyhow::Result { - use anyhow::Context; - Ok(self - .get_index(name.comparison_index as usize) - .context("invalid comparison index")? - .try_into()?) - } - } - - impl Index for NameEntryArray { - type Output = FNameEntry; - - fn index(&self, i: usize) -> &Self::Output { - self.get_index(i).expect("Out of bounds access") - } - } - - pub struct NamesIterator<'a> { - names: &'a NameEntryArray, - index: usize, - } - - impl<'a> NamesIterator<'a> { - pub fn new(names: &'a NameEntryArray) -> Self { - Self { names, index: 0 } - } - } - - impl<'a> Iterator for NamesIterator<'a> { - type Item = &'a FNameEntry; - - fn next(&mut self) -> Option { - loop { - if !self.names.is_valid_index(self.index) { - break None; - } - let item = self.names.get_index(self.index); - self.index += 1; - // skip empty entries, we dont care about them - if item.is_some() { - break item; - } - } - } - } - - impl<'a> IntoIterator for &'a NameEntryArray { - type Item = &'a FNameEntry; - type IntoIter = NamesIterator<'a>; - - fn into_iter(self) -> Self::IntoIter { - NamesIterator { - names: self, - index: 0, - } - } - } - } - - pub mod objects { - use std::{ops::Index, ptr::NonNull, sync::RwLock}; - - use rayon::prelude::ParallelIterator; - - use crate::types; - - lazy_static::lazy_static! { - pub static ref GOBJECTS: RwLock = RwLock::new(GObjects::new()); - } - - #[derive(Debug, Default)] - pub struct GObjects { - objects: Option>, - } - - impl GObjects { - pub const fn new() -> Self { - Self { objects: None } - } - - pub fn set_objects(&mut self, objects: NonNull) { - self.objects = Some(objects); - } - - pub fn as_objects(&self) -> Option<&ObjectArray> { - self.objects.map(|objects| unsafe { objects.as_ref() }) - } - } - - unsafe impl Send for GObjects {} - unsafe impl Sync for GObjects {} - - #[repr(C)] - #[derive(Debug)] - pub struct ObjectArrayItem { - object: *mut crate::core_types::UObject, - sn: u32, - } - - unsafe impl Send for ObjectArrayItem {} - unsafe impl Sync for ObjectArrayItem {} - - impl ObjectArrayItem { - pub fn object(&self) -> Option { - types::UObject::maybe_with_raw(self.object) - } - } - - #[repr(C)] - #[derive(Debug)] - pub struct ObjectArrayInner { - objects: *const *const ObjectArrayItem, - pre_allocated_objects: *const ObjectArrayItem, - max_elements: u32, - num_elements: u32, - max_chunks: u32, - num_chunks: u32, - } - - impl ObjectArrayInner { - const ELEMENTS_PER_CHUNK: usize = 64 * 1024; - - fn chunks_as_slice(&self) -> &[*const ObjectArrayItem] { - unsafe { std::slice::from_raw_parts(self.objects, self.num_chunks as usize) } - } - - pub fn get_chunks(&self) -> Vec<&[ObjectArrayItem]> { - (0..self.num_chunks as usize) - .map(|i| self.chunk_as_slice(i)) - .collect() - } - - fn get_chunk_size(&self, chunk_index: usize) -> Option { - if chunk_index < self.num_chunks as usize { - if chunk_index * Self::ELEMENTS_PER_CHUNK <= self.num_elements as usize { - Some(Self::ELEMENTS_PER_CHUNK) - } else { - Some(self.num_elements as usize % Self::ELEMENTS_PER_CHUNK) - } - } else { - None - } - } - - fn chunk_as_slice(&self, chunk_index: usize) -> &[ObjectArrayItem] { - let chunks = self.chunks_as_slice(); - let chunk = unsafe { - std::slice::from_raw_parts( - chunks[chunk_index], - self.get_chunk_size(chunk_index).unwrap(), - ) - }; - - chunk - } - - /// returns the chunk index and the index inside that chunk, or none if the index is out of range - fn get_chunked_index(&self, i: usize) -> Option<(usize, usize)> { - if i < self.num_elements as usize { - let chunk_index = i / Self::ELEMENTS_PER_CHUNK; - let within_chunk_index = i % Self::ELEMENTS_PER_CHUNK; - - Some((chunk_index, within_chunk_index)) - } else { - None - } - } - - pub fn get_index(&self, i: usize) -> Option<&ObjectArrayItem> { - self.get_chunked_index(i) - .map(|(chunk_index, within_chunk_index)| { - &self.chunk_as_slice(chunk_index)[within_chunk_index] - }) - } - } - - #[repr(C)] - #[derive(Debug)] - pub struct ObjectArray { - first_gc_index: u32, - last_non_gc_index: u32, - max_objects_not_considered_by_gc: u32, - open_for_disregard_for_gc: bool, - inner: ObjectArrayInner, - } - - unsafe impl Send for ObjectArray {} - unsafe impl Sync for ObjectArray {} - - impl ObjectArray { - pub fn len(&self) -> usize { - self.inner.num_elements as usize - } - - pub fn iter(&self) -> iter::ObjectArrayIterator<'_> { - iter::ObjectArrayIterator::new(self) - } - - pub fn iter_package_objects(&self) -> impl Iterator { - self.iter().filter( - |entry| matches!(entry.object(), Some(object) if object.is_package_object()), - ) - } - - pub fn par_iter(&self) -> par_iter::ObjectArrayChunkedParallelIterator<'_> { - par_iter::ObjectArrayChunkedParallelIterator::new(self.inner.get_chunks()) - } - - pub fn par_iter_package_objects( - &self, - ) -> impl ParallelIterator { - self.par_iter().filter( - |entry| matches!(entry.object(), Some(object) if object.is_package_object()), - ) - } - } - - impl Index for ObjectArray { - type Output = ObjectArrayItem; - - fn index(&self, i: usize) -> &Self::Output { - self.inner.get_index(i).expect("Out of bounds access") - } - } - - pub mod iter { - use super::{ObjectArray, ObjectArrayItem}; - - pub struct ObjectArrayIterator<'a> { - objects: &'a ObjectArray, - index: usize, - } - - impl<'a> ObjectArrayIterator<'a> { - pub(super) fn new(objects: &'a ObjectArray) -> Self { - Self { objects, index: 0 } - } - } - - impl<'a> Iterator for ObjectArrayIterator<'a> { - type Item = &'a ObjectArrayItem; - - fn next(&mut self) -> Option { - let item = if self.index < self.objects.len() { - Some(&self.objects[self.index]) - } else { - None - }; - - self.index += 1; - - item - } - } - - impl<'a> IntoIterator for &'a ObjectArray { - type Item = &'a ObjectArrayItem; - type IntoIter = ObjectArrayIterator<'a>; - - fn into_iter(self) -> Self::IntoIter { - ObjectArrayIterator { - objects: self, - index: 0, - } - } - } - } - - pub mod par_iter { - pub use rayon::iter::{IntoParallelIterator, ParallelIterator}; - - use super::ObjectArrayItem; - - pub struct ObjectArrayChunkedParallelIterator<'a> { - chunks: Vec<&'a [ObjectArrayItem]>, - } - - impl<'a> ObjectArrayChunkedParallelIterator<'a> { - pub fn new(chunks: Vec<&'a [ObjectArrayItem]>) -> Self { - Self { chunks } - } - } - - impl<'a> ParallelIterator for ObjectArrayChunkedParallelIterator<'a> { - type Item = &'a ObjectArrayItem; - - fn drive_unindexed(self, consumer: C) -> C::Result - where - C: rayon::iter::plumbing::UnindexedConsumer, - { - self.chunks - .into_par_iter() - .flatten_iter() - .drive_unindexed(consumer) - } - } - } - - pub trait FindClass { - fn find_class(&self, class_name: S) -> Option - where - S: Into; - } - - impl FindClass for ObjectArray { - fn find_class(&self, class_name: S) -> Option - where - S: Into, - { - let class_name = class_name.into(); - - self.iter() - .filter_map(|item| item.object()) - .filter(|object| object.get_full_name().unwrap() == class_name) - .map(|object| types::UClass::new(unsafe { object.cast() })) - .next() - } - } - } -} - -pub mod types { - use anyhow::Context; - use itertools::Itertools; - use std::{hash::Hash, ops::Deref, ptr::NonNull}; - - use crate::core_types::{self, FName, TArray}; - - use self::traits::{AsUObject, AsUStruct, FromRaw}; - - #[repr(transparent)] - #[derive(Debug, Clone)] - pub struct UObject { - inner: NonNull, - } - - impl Eq for UObject {} - - impl PartialEq for UObject { - fn eq(&self, other: &Self) -> bool { - unsafe { - self.inner.as_ref().internal_index == other.inner.as_ref().internal_index - && self.inner.as_ref().name == other.inner.as_ref().name - } - } - } - - impl Hash for UObject { - fn hash(&self, state: &mut H) { - self.internal_index().hash(state); - self.name().hash(state); - } - } - - #[repr(transparent)] - #[derive(Debug, Clone)] - pub struct UClass { - inner: NonNull, - } - - impl Deref for UClass { - type Target = UObject; - - fn deref(&self) -> &Self::Target { - unsafe { std::mem::transmute(&self) } - } - } - - impl Eq for UClass {} - - impl PartialEq for UClass { - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other) - } - } - - impl Hash for UClass { - fn hash(&self, state: &mut H) { - self.deref().hash(state); - } - } - - #[repr(transparent)] - #[derive(Debug, Clone)] - pub struct UField { - inner: NonNull, - } - - impl Deref for UField { - type Target = UObject; - - fn deref(&self) -> &Self::Target { - unsafe { std::mem::transmute(&self) } - } - } - - impl Eq for UField {} - - impl PartialEq for UField { - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other) - } - } - - impl Hash for UField { - fn hash(&self, state: &mut H) { - self.deref().hash(state); - } - } - - impl UObject { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub fn maybe_new(inner: Option>) -> Option { - inner.map(|inner| Self { inner }) - } - - pub fn to_inner(self) -> NonNull { - self.inner - } - - pub const unsafe fn cast(self) -> NonNull { - self.inner.cast::() - } - - /// returns a `Self` with the provided raw pointer, if the pointer is not null - pub fn maybe_with_raw(raw: *mut core_types::UObject) -> Option { - Self::maybe_new(NonNull::new(raw)) - } - - pub fn class(&self) -> Option { - unsafe { self.inner.as_ref().class.map(|inner| UClass::new(inner)) } - } - - pub fn internal_index(&self) -> u32 { - unsafe { self.inner.as_ref().internal_index } - } - - pub fn name(&self) -> core_types::FName { - unsafe { self.inner.as_ref().name } - } - - pub fn get_name(&self) -> anyhow::Result { - let name = self.name(); - name.get_name() - .map(|name_str| { - if name.number > 0 { - format!("{}_{}", name_str, name.number) - } else { - name_str - } - }) - .map(|name_str| { - name_str - .rfind("/") - .map(|pos| name_str[(pos + 1)..].to_string()) - .unwrap_or_else(|| name_str) - }) - } - - pub fn get_name_or_default(&self) -> String { - self.get_name() - .unwrap_or_else(|_| "DefaultObjectName".to_string()) - } - - pub fn get_full_name(&self) -> anyhow::Result { - let tmp = self - .iter_outer_objects() - .map(|obj| obj.get_name()) - .fold_ok(String::new(), |acc, obj_name| { - format!("{}.{}", obj_name, acc) - })?; - - Ok(format!( - "{} {}{}", - self.class() - .context("invalid class pointer")? - .as_uobject() - .get_name_or_default(), - tmp, - self.get_name_or_default() - )) - } - - pub fn get_full_name_or_default(&self) -> String { - self.get_full_name() - .unwrap_or_else(|_| "DefaultObjectFullName".to_string()) - } - - pub fn outer(&self) -> Option { - unsafe { self.inner.as_ref().outer.map(|inner| UObject::new(inner)) } - } - - /// returns the package object of this object - pub fn outermost(&self) -> Option { - self.iter_outer_objects().last() - } - - pub fn is_package_object(&self) -> bool { - unsafe { self.inner.as_ref().outer.is_none() } - } - - pub fn iter_outer_objects(&self) -> OuterObjectIterator { - OuterObjectIterator::new(self.clone()) - } - - pub fn is_a(&self, other: &UClass) -> bool { - self.class() - .map(|class| class.iter_super_classes().contains(other)) - .unwrap_or(false) - } - } - - pub struct OuterObjectIterator { - object: UObject, - } - - impl OuterObjectIterator { - pub fn new(object: UObject) -> Self { - Self { object } - } - } - - impl Iterator for OuterObjectIterator { - type Item = UObject; - - fn next(&mut self) -> Option { - if let Some(outer) = self.object.outer() { - self.object = outer.clone(); - - Some(outer) - } else { - None - } - } - } - - impl From for UClass { - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl AsUStruct for UClass { - fn as_ustruct(&self) -> UStruct { - UStruct::new(self.inner.clone().cast()) - } - } - - impl AsRef for UClass { - fn as_ref(&self) -> &core_types::UClass { - unsafe { self.inner.as_ref() } - } - } - - impl FromRaw for UClass { - fn from_non_null(inner: NonNull) -> Self { - Self::new(inner) - } - } - - impl UClass { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - pub fn iter_super_classes(&self) -> SuperClassIter { - SuperClassIter::new(self.clone()) - } - } - - pub struct SuperClassIter { - class: UClass, - } - - impl SuperClassIter { - pub fn new(class: UClass) -> Self { - Self { class } - } - } - - impl Iterator for SuperClassIter { - type Item = UClass; - - fn next(&mut self) -> Option { - if let Some(next) = self - .class - .as_ref() - .ustruct - .super_field - .map(|inner| inner.cast::()) - .map(|inner| UClass::new(inner)) - { - self.class = next.clone(); - Some(next) - } else { - None - } - } - } - - impl From for UField { - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl UField { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub fn maybe_new(inner: Option>) -> Option { - inner.map(|inner| Self { inner }) - } - - pub fn maybe_with_raw(raw: *mut core_types::UField) -> Option { - NonNull::new(raw).map(|inner| Self::new(inner)) - } - - pub fn as_uobject(&self) -> UObject { - UObject::new(self.inner.clone().cast()) - } - - pub unsafe fn as_ref(&self) -> &core_types::UField { - self.inner.as_ref() - } - - pub fn next(&self) -> Option { - Self::maybe_new(unsafe { self.as_ref().next }) - } - } - - #[derive(Debug, Clone)] - pub struct UEnum { - inner: NonNull, - } - - impl From for UEnum { - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl UEnum { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub fn maybe_new(inner: Option>) -> Option { - inner.map(|inner| Self { inner }) - } - - pub fn maybe_with_raw(raw: *mut core_types::UEnum) -> Option { - NonNull::new(raw).map(|inner| Self::new(inner)) - } - - pub fn as_uobject(&self) -> UObject { - UObject::new(self.inner.clone().cast()) - } - - pub unsafe fn as_ref(&self) -> &core_types::UEnum { - self.inner.as_ref() - } - - pub fn get_names(&self) -> &TArray { - unsafe { &self.as_ref().names } - } - } - - #[derive(Debug, Clone)] - pub struct UStruct { - inner: NonNull, - } - - impl From for UStruct { - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl FromRaw for UStruct { - fn from_non_null(inner: NonNull) -> Self { - Self::new(inner) - } - } - - impl UStruct { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub unsafe fn as_raw(&self) -> &core_types::UStruct { - self.inner.as_ref() - } - - fn super_struct(&self) -> Option { - Self::from_maybe_non_null(unsafe { self.as_raw().super_field }) - } - - fn children(&self) -> Option { - UField::maybe_new(unsafe { self.as_raw().children }) - } - - fn iter_fields(&self) -> Option { - self.children() - .map(|field| UStructFieldIterator::new(field)) - } - } - - impl traits::AsUStruct for UStruct { - fn as_ustruct(&self) -> UStruct { - self.clone() - } - } - - pub struct UStructFieldIterator { - field: UField, - } - - impl UStructFieldIterator { - pub fn new(field: UField) -> Self { - Self { field } - } - } - - impl Iterator for UStructFieldIterator { - type Item = UField; - - fn next(&mut self) -> Option { - if let Some(mut next) = self.field.next() { - std::mem::swap(&mut self.field, &mut next); - - Some(next) - } else { - None - } - } - } - - #[derive(Debug, Clone)] - pub struct UScriptStruct { - inner: NonNull, - } - - impl From for UScriptStruct { - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl UScriptStruct { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub fn maybe_new(inner: Option>) -> Option { - inner.map(|inner| Self { inner }) - } - - pub fn maybe_with_raw(raw: *mut core_types::UStruct) -> Option { - NonNull::new(raw).map(|inner| Self::new(inner)) - } - - pub fn as_uobject(&self) -> UObject { - UObject::new(self.inner.clone().cast()) - } - - pub unsafe fn as_ref(&self) -> &core_types::UStruct { - self.inner.as_ref() - } - - pub fn iter_fields(&self) -> Option { - UField::maybe_new(unsafe { self.as_ref().children }) - .map(|field| UStructFieldIterator::new(field)) - } - } - - #[derive(Debug, Clone)] - pub struct UProperty { - inner: NonNull, - } - - impl From for UProperty { - /// WARNING: There is no type check on this conversion, so it is most certainly unsafe - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl UProperty { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub fn maybe_new(inner: Option>) -> Option { - inner.map(|inner| Self { inner }) - } - - pub fn maybe_with_raw(raw: *mut core_types::UProperty) -> Option { - NonNull::new(raw).map(|inner| Self::new(inner)) - } - - pub fn as_uobject(&self) -> UObject { - UObject::new(self.inner.clone().cast()) - } - - pub unsafe fn as_ref(&self) -> &core_types::UProperty { - self.inner.as_ref() - } - } - - #[derive(Debug, Clone)] - pub struct UFunction { - inner: NonNull, - } - - impl From for UFunction { - /// WARNING: There is no type check on this conversion, so it is most certainly unsafe - fn from(obj: UObject) -> Self { - Self::new(obj.inner.cast()) - } - } - - impl UFunction { - pub fn new(inner: NonNull) -> Self { - Self { inner } - } - - pub fn maybe_new(inner: Option>) -> Option { - inner.map(|inner| Self { inner }) - } - - pub fn maybe_with_raw(raw: *mut core_types::UFunction) -> Option { - NonNull::new(raw).map(|inner| Self::new(inner)) - } - - pub fn as_uobject(&self) -> UObject { - UObject::new(self.inner.clone().cast()) - } - - pub unsafe fn as_ref(&self) -> &core_types::UFunction { - self.inner.as_ref() - } - } - - #[derive(Debug, Clone)] - pub enum UAnyType { - UObject(UObject), - UClass(UClass), - UField(UField), - UScriptStruct(UScriptStruct), - UProperty(UProperty), - UEnum(UEnum), - UStruct(UStruct), - UFunction(UFunction), - } - - impl UAnyType { - pub fn as_uobject(&self) -> UObject { - match self { - UAnyType::UObject(rep) => rep.clone(), - UAnyType::UClass(rep) => rep.as_uobject(), - UAnyType::UField(rep) => rep.as_uobject(), - UAnyType::UScriptStruct(rep) => rep.as_uobject(), - UAnyType::UProperty(rep) => rep.as_uobject(), - UAnyType::UEnum(rep) => rep.as_uobject(), - UAnyType::UStruct(rep) => rep.as_uobject(), - UAnyType::UFunction(rep) => rep.as_uobject(), - } - } - } - - impl Hash for UAnyType { - fn hash(&self, state: &mut H) { - self.as_uobject().hash(state); - } - } - - pub mod traits { - use crate::core_types::FName; - use std::ptr::NonNull; - - use itertools::Itertools; - - use super::{OuterObjectIterator, UClass, UField, UObject, UStruct, UStructFieldIterator}; - - pub unsafe fn null_ptr_cast<'a, T, U>(ptr: &'a NonNull) -> &'a NonNull { - std::mem::transmute(ptr) - } - - pub trait AsUObject: Sized { - fn as_uobject(&self) -> UObject; - - fn class(&self) -> Option { - self.as_uobject().class() - } - - fn internal_index(&self) -> u32 { - self.as_uobject().internal_index() - } - - fn name(&self) -> FName { - self.as_uobject().name() - } - - fn get_name(&self) -> anyhow::Result { - self.as_uobject().get_name() - } - - fn get_name_or_default(&self) -> String { - self.as_uobject().get_name_or_default() - } - - fn get_full_name(&self) -> anyhow::Result { - self.as_uobject().get_full_name() - } - - fn get_full_name_or_default(&self) -> String { - self.as_uobject().get_full_name_or_default() - } - - fn outer(&self) -> Option { - self.as_uobject().outer() - } - - fn outermost(&self) -> Option { - self.as_uobject().outermost() - } - - fn is_package_object(&self) -> bool { - self.as_uobject().is_package_object() - } - - fn iter_outer_objects(&self) -> OuterObjectIterator { - OuterObjectIterator::new(self.as_uobject()) - } - - fn is_a(&self, other: &UClass) -> bool { - self.class() - .map(|class| class.iter_super_classes().contains(other)) - .unwrap_or(false) - } - } - - pub trait AsUStruct { - fn as_ustruct(&self) -> UStruct; - - #[inline] - fn children(&self) -> Option { - self.as_ustruct().children() - } - - #[inline] - fn super_struct(&self) -> Option { - self.as_ustruct().super_struct() - } - - #[inline] - fn iter_fields(&self) -> Option { - self.children() - .map(|field| UStructFieldIterator::new(field)) - } - } - - impl AsUObject for T - where - T: AsUStruct, - { - fn as_uobject(&self) -> UObject { - UObject::new(self.as_ustruct().inner.cast()) - } - } - - pub trait FromRaw { - fn from_non_null(inner: NonNull) -> Self; - - fn from_raw(raw: *mut R) -> Option - where - Self: Sized, - { - NonNull::new(raw).map(|inner| Self::from_non_null(inner)) - } - - fn from_maybe_non_null(inner: Option>) -> Option - where - Self: Sized, - { - inner.map(|inner| Self::from_non_null(inner)) - } - } - } -} - -mod core_types { - #![allow(non_upper_case_globals)] - use bitflags::bitflags; - use std::ptr::NonNull; - - #[derive(Debug)] - #[repr(transparent)] - pub struct VTbl(NonNull<()>); - - impl VTbl {} - - bitflags! { - #[repr(C)] - pub struct EObjectFlags: u32 { - const RF_NoFlags = 0x00000000; - const RF_Public = 0x00000001; - const RF_Standalone = 0x00000002; - const RF_MarkAsNative = 0x00000004; - const RF_Transactional = 0x00000008; - const RF_ClassDefaultObject = 0x00000010; - const RF_ArchetypeObject = 0x00000020; - const RF_Transient = 0x00000040; - const RF_MarkAsRootSet = 0x00000080; - const RF_TagGarbageTemp = 0x00000100; - const RF_NeedInitialization = 0x00000200; - const RF_NeedLoad = 0x00000400; - const RF_KeepForCooker = 0x00000800; - const RF_NeedPostLoad = 0x00001000; - const RF_NeedPostLoadSubobjects = 0x00002000; - const RF_NewerVersionExists = 0x00004000; - const RF_BeginDestroyed = 0x00008000; - const RF_FinishDestroyed = 0x00010000; - const RF_BeingRegenerated = 0x00020000; - const RF_DefaultSubObject = 0x00040000; - const RF_WasLoaded = 0x00080000; - const RF_TextExportTransient = 0x00100000; - const RF_LoadCompleted = 0x00200000; - const RF_InheritableComponentTemplate = 0x00400000; - const RF_DuplicateTransient = 0x00800000; - const RF_StrongRefOnFrame = 0x01000000; - const RF_NonPIEDuplicateTransient = 0x02000000; - const RF_Dynamic = 0x04000000; - const RF_WillBeLoaded = 0x08000000; - const RF_HasExternalPackage = 0x10000000; - } - } - - pub use tarray::{FString, TArray}; - - use crate::global_tables::names::GNAMES; - - #[repr(C)] - #[derive(Debug)] - pub struct UObject { - pub vtbl: VTbl, - pub object_flags: EObjectFlags, //EObjectFlags, - pub internal_index: u32, - pub class: Option>, - pub name: FName, - pub outer: Option>, - } - - #[repr(C)] - #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] - pub struct FName { - pub comparison_index: u32, - pub number: u32, - } - - impl FName { - pub fn get_name(&self) -> anyhow::Result { - GNAMES - .read() - .unwrap() - .as_names() - .unwrap() - .fname_to_string(self) - } - } - - #[repr(C)] - #[derive(Debug)] - pub struct UField { - pub uobject: UObject, - pub next: Option>, - } - - #[repr(C)] - #[derive(Debug)] - pub struct UEnum { - pub ufield: UField, - pub cpp_type: FString, - pub names: TArray, - pub cpp_form: u32, - } - - #[repr(C)] - #[derive(Debug)] - pub struct UStruct { - pub(crate) ufield: UField, - pub(crate) super_field: Option>, - pub(crate) children: Option>, - pub(crate) property_size: u32, - pub(crate) min_alignment: u32, - padding1: [u8; 0x48], - } - - #[repr(C)] - #[derive(Debug)] - pub struct UClass { - pub(crate) ustruct: UStruct, - padding1: [u8; 0xF8], - } - - #[repr(C)] - #[derive(Debug)] - pub struct UProperty { - pub(crate) ufield: UField, - array_dim: i32, - element_size: i32, - property_flags: u64, - rep_index: i16, - rep_notify_function: FName, - offset: i32, - property_link_next: Option>, - next_ref: Option>, - destructor_link_next: Option>, - post_construct_link_next: Option>, - } - - #[repr(C)] - #[derive(Debug)] - pub struct UFunction { - pub(crate) ustruct: UStruct, - function_flags: u32, - rep_offset: u16, - num_params: u8, - params_size: u16, - return_value_offset: u16, - rpc_id: u16, - rpc_response_id: u16, - first_property_to_init: Option>, - function: Option>, - } - - pub mod tarray { - use std::{ops::Index, ptr::NonNull, slice::SliceIndex}; - - #[repr(C)] - #[derive(Debug)] - pub struct TArray { - data: Option>, - count: u32, - max: u32, - } - - unsafe impl Send for TArray where T: Send {} - unsafe impl Sync for TArray where T: Sync {} - - pub type FString = TArray; - - impl ToString for FString { - fn to_string(&self) -> String { - widestring::U16CStr::from_slice(&self) - .expect("invalid utf16 string") - .to_string_lossy() - } - } - - impl TArray { - pub fn len(&self) -> usize { - self.count as usize - } - - pub fn capacity(&self) -> usize { - self.max as usize - } - } - - impl> Index for TArray { - type Output = I::Output; - - fn index(&self, i: I) -> &Self::Output { - let data = - unsafe { std::slice::from_raw_parts(self.data.unwrap().as_ptr(), self.len()) }; - - &data[i] - } - } - - impl std::ops::Deref for TArray { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - unsafe { std::slice::from_raw_parts(self.data.unwrap().as_ptr(), self.len()) } - } - } - - pub struct IntoIter { - //array_ref: &'a TArray, - ptr: *const T, - end: *const T, - } - - impl<'a, T> IntoIter { - pub fn new(array: &'a TArray) -> Self { - let ptr = array.data.unwrap().as_ptr(); - Self { - ptr, - end: unsafe { ptr.offset(array.count as isize) }, - } - } - } - - impl<'a, T> Iterator for IntoIter { - type Item = T; - - fn next(&mut self) -> Option { - if self.ptr == self.end { - None - } else { - let old = self.ptr; - self.ptr = unsafe { self.ptr.offset(1) }; - - Some(unsafe { std::ptr::read(old) }) - } - } - } - - impl<'a, T> IntoIterator for &'a TArray { - type Item = T; - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - IntoIter::new(self) - } - } - } -} +mod core_types; +pub mod global_tables; +pub mod types; diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..f068710 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,671 @@ +use anyhow::Context; +use itertools::Itertools; +use std::{hash::Hash, ops::Deref, ptr::NonNull}; + +use crate::core_types::{self, FName, TArray}; + +use self::traits::{AsUObject, AsUStruct, FromRaw}; + +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct UObject { + inner: NonNull, +} + +impl Eq for UObject {} + +impl PartialEq for UObject { + fn eq(&self, other: &Self) -> bool { + unsafe { + self.inner.as_ref().internal_index == other.inner.as_ref().internal_index + && self.inner.as_ref().name == other.inner.as_ref().name + } + } +} + +impl Hash for UObject { + fn hash(&self, state: &mut H) { + self.internal_index().hash(state); + self.name().hash(state); + } +} + +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct UClass { + inner: NonNull, +} + +impl Deref for UClass { + type Target = UObject; + + fn deref(&self) -> &Self::Target { + unsafe { std::mem::transmute(&self) } + } +} + +impl Eq for UClass {} + +impl PartialEq for UClass { + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other) + } +} + +impl Hash for UClass { + fn hash(&self, state: &mut H) { + self.deref().hash(state); + } +} + +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct UField { + inner: NonNull, +} + +impl Deref for UField { + type Target = UObject; + + fn deref(&self) -> &Self::Target { + unsafe { std::mem::transmute(&self) } + } +} + +impl Eq for UField {} + +impl PartialEq for UField { + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other) + } +} + +impl Hash for UField { + fn hash(&self, state: &mut H) { + self.deref().hash(state); + } +} + +impl UObject { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub fn maybe_new(inner: Option>) -> Option { + inner.map(|inner| Self { inner }) + } + + pub fn to_inner(self) -> NonNull { + self.inner + } + + pub const unsafe fn cast(self) -> NonNull { + self.inner.cast::() + } + + /// returns a `Self` with the provided raw pointer, if the pointer is not null + pub fn maybe_with_raw(raw: *mut core_types::UObject) -> Option { + Self::maybe_new(NonNull::new(raw)) + } + + pub fn class(&self) -> Option { + unsafe { self.inner.as_ref().class.map(|inner| UClass::new(inner)) } + } + + pub fn internal_index(&self) -> u32 { + unsafe { self.inner.as_ref().internal_index } + } + + pub fn name(&self) -> core_types::FName { + unsafe { self.inner.as_ref().name } + } + + pub fn get_name(&self) -> anyhow::Result { + let name = self.name(); + name.get_name() + .map(|name_str| { + if name.number > 0 { + format!("{}_{}", name_str, name.number) + } else { + name_str + } + }) + .map(|name_str| { + name_str + .rfind("/") + .map(|pos| name_str[(pos + 1)..].to_string()) + .unwrap_or_else(|| name_str) + }) + } + + pub fn get_name_or_default(&self) -> String { + self.get_name() + .unwrap_or_else(|_| "DefaultObjectName".to_string()) + } + + pub fn get_full_name(&self) -> anyhow::Result { + let tmp = self + .iter_outer_objects() + .map(|obj| obj.get_name()) + .fold_ok(String::new(), |acc, obj_name| { + format!("{}.{}", obj_name, acc) + })?; + + Ok(format!( + "{} {}{}", + self.class() + .context("invalid class pointer")? + .as_uobject() + .get_name_or_default(), + tmp, + self.get_name_or_default() + )) + } + + pub fn get_full_name_or_default(&self) -> String { + self.get_full_name() + .unwrap_or_else(|_| "DefaultObjectFullName".to_string()) + } + + pub fn outer(&self) -> Option { + unsafe { self.inner.as_ref().outer.map(|inner| UObject::new(inner)) } + } + + /// returns the package object of this object + pub fn outermost(&self) -> Option { + self.iter_outer_objects().last() + } + + pub fn is_package_object(&self) -> bool { + unsafe { self.inner.as_ref().outer.is_none() } + } + + pub fn iter_outer_objects(&self) -> OuterObjectIterator { + OuterObjectIterator::new(self.clone()) + } + + pub fn is_a(&self, other: &UClass) -> bool { + self.class() + .map(|class| class.iter_super_classes().contains(other)) + .unwrap_or(false) + } +} + +pub struct OuterObjectIterator { + object: UObject, +} + +impl OuterObjectIterator { + pub fn new(object: UObject) -> Self { + Self { object } + } +} + +impl Iterator for OuterObjectIterator { + type Item = UObject; + + fn next(&mut self) -> Option { + if let Some(outer) = self.object.outer() { + self.object = outer.clone(); + + Some(outer) + } else { + None + } + } +} + +impl From for UClass { + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl AsUStruct for UClass { + fn as_ustruct(&self) -> UStruct { + UStruct::new(self.inner.clone().cast()) + } +} + +impl AsRef for UClass { + fn as_ref(&self) -> &core_types::UClass { + unsafe { self.inner.as_ref() } + } +} + +impl FromRaw for UClass { + fn from_non_null(inner: NonNull) -> Self { + Self::new(inner) + } +} + +impl UClass { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + pub fn iter_super_classes(&self) -> SuperClassIter { + SuperClassIter::new(self.clone()) + } +} + +pub struct SuperClassIter { + class: UClass, +} + +impl SuperClassIter { + pub fn new(class: UClass) -> Self { + Self { class } + } +} + +impl Iterator for SuperClassIter { + type Item = UClass; + + fn next(&mut self) -> Option { + if let Some(next) = self + .class + .as_ref() + .ustruct + .super_field + .map(|inner| inner.cast::()) + .map(|inner| UClass::new(inner)) + { + self.class = next.clone(); + Some(next) + } else { + None + } + } +} + +impl From for UField { + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl UField { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub fn maybe_new(inner: Option>) -> Option { + inner.map(|inner| Self { inner }) + } + + pub fn maybe_with_raw(raw: *mut core_types::UField) -> Option { + NonNull::new(raw).map(|inner| Self::new(inner)) + } + + pub fn as_uobject(&self) -> UObject { + UObject::new(self.inner.clone().cast()) + } + + pub unsafe fn as_ref(&self) -> &core_types::UField { + self.inner.as_ref() + } + + pub fn next(&self) -> Option { + Self::maybe_new(unsafe { self.as_ref().next }) + } +} + +#[derive(Debug, Clone)] +pub struct UEnum { + inner: NonNull, +} + +impl From for UEnum { + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl UEnum { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub fn maybe_new(inner: Option>) -> Option { + inner.map(|inner| Self { inner }) + } + + pub fn maybe_with_raw(raw: *mut core_types::UEnum) -> Option { + NonNull::new(raw).map(|inner| Self::new(inner)) + } + + pub fn as_uobject(&self) -> UObject { + UObject::new(self.inner.clone().cast()) + } + + pub unsafe fn as_ref(&self) -> &core_types::UEnum { + self.inner.as_ref() + } + + pub fn get_names(&self) -> &TArray { + unsafe { &self.as_ref().names } + } +} + +#[derive(Debug, Clone)] +pub struct UStruct { + inner: NonNull, +} + +impl From for UStruct { + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl FromRaw for UStruct { + fn from_non_null(inner: NonNull) -> Self { + Self::new(inner) + } +} + +impl UStruct { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub unsafe fn as_raw(&self) -> &core_types::UStruct { + self.inner.as_ref() + } + + fn super_struct(&self) -> Option { + Self::from_maybe_non_null(unsafe { self.as_raw().super_field }) + } + + fn children(&self) -> Option { + UField::maybe_new(unsafe { self.as_raw().children }) + } + + fn iter_fields(&self) -> Option { + self.children() + .map(|field| UStructFieldIterator::new(field)) + } +} + +impl traits::AsUStruct for UStruct { + fn as_ustruct(&self) -> UStruct { + self.clone() + } +} + +pub struct UStructFieldIterator { + field: UField, +} + +impl UStructFieldIterator { + pub fn new(field: UField) -> Self { + Self { field } + } +} + +impl Iterator for UStructFieldIterator { + type Item = UField; + + fn next(&mut self) -> Option { + if let Some(mut next) = self.field.next() { + std::mem::swap(&mut self.field, &mut next); + + Some(next) + } else { + None + } + } +} + +#[derive(Debug, Clone)] +pub struct UScriptStruct { + inner: NonNull, +} + +impl From for UScriptStruct { + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl UScriptStruct { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub fn maybe_new(inner: Option>) -> Option { + inner.map(|inner| Self { inner }) + } + + pub fn maybe_with_raw(raw: *mut core_types::UStruct) -> Option { + NonNull::new(raw).map(|inner| Self::new(inner)) + } + + pub fn as_uobject(&self) -> UObject { + UObject::new(self.inner.clone().cast()) + } + + pub unsafe fn as_ref(&self) -> &core_types::UStruct { + self.inner.as_ref() + } + + pub fn iter_fields(&self) -> Option { + UField::maybe_new(unsafe { self.as_ref().children }) + .map(|field| UStructFieldIterator::new(field)) + } +} + +#[derive(Debug, Clone)] +pub struct UProperty { + inner: NonNull, +} + +impl From for UProperty { + /// WARNING: There is no type check on this conversion, so it is most certainly unsafe + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl UProperty { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub fn maybe_new(inner: Option>) -> Option { + inner.map(|inner| Self { inner }) + } + + pub fn maybe_with_raw(raw: *mut core_types::UProperty) -> Option { + NonNull::new(raw).map(|inner| Self::new(inner)) + } + + pub fn as_uobject(&self) -> UObject { + UObject::new(self.inner.clone().cast()) + } + + pub unsafe fn as_ref(&self) -> &core_types::UProperty { + self.inner.as_ref() + } +} + +#[derive(Debug, Clone)] +pub struct UFunction { + inner: NonNull, +} + +impl From for UFunction { + /// WARNING: There is no type check on this conversion, so it is most certainly unsafe + fn from(obj: UObject) -> Self { + Self::new(obj.inner.cast()) + } +} + +impl UFunction { + pub fn new(inner: NonNull) -> Self { + Self { inner } + } + + pub fn maybe_new(inner: Option>) -> Option { + inner.map(|inner| Self { inner }) + } + + pub fn maybe_with_raw(raw: *mut core_types::UFunction) -> Option { + NonNull::new(raw).map(|inner| Self::new(inner)) + } + + pub fn as_uobject(&self) -> UObject { + UObject::new(self.inner.clone().cast()) + } + + pub unsafe fn as_ref(&self) -> &core_types::UFunction { + self.inner.as_ref() + } +} + +#[derive(Debug, Clone)] +pub enum UAnyType { + UObject(UObject), + UClass(UClass), + UField(UField), + UScriptStruct(UScriptStruct), + UProperty(UProperty), + UEnum(UEnum), + UStruct(UStruct), + UFunction(UFunction), +} + +impl UAnyType { + pub fn as_uobject(&self) -> UObject { + match self { + UAnyType::UObject(rep) => rep.clone(), + UAnyType::UClass(rep) => rep.as_uobject(), + UAnyType::UField(rep) => rep.as_uobject(), + UAnyType::UScriptStruct(rep) => rep.as_uobject(), + UAnyType::UProperty(rep) => rep.as_uobject(), + UAnyType::UEnum(rep) => rep.as_uobject(), + UAnyType::UStruct(rep) => rep.as_uobject(), + UAnyType::UFunction(rep) => rep.as_uobject(), + } + } +} + +impl Hash for UAnyType { + fn hash(&self, state: &mut H) { + self.as_uobject().hash(state); + } +} + +pub mod traits { + use crate::core_types::FName; + use std::ptr::NonNull; + + use itertools::Itertools; + + use super::{OuterObjectIterator, UClass, UField, UObject, UStruct, UStructFieldIterator}; + + pub unsafe fn null_ptr_cast<'a, T, U>(ptr: &'a NonNull) -> &'a NonNull { + std::mem::transmute(ptr) + } + + pub trait AsUObject: Sized { + fn as_uobject(&self) -> UObject; + + fn class(&self) -> Option { + self.as_uobject().class() + } + + fn internal_index(&self) -> u32 { + self.as_uobject().internal_index() + } + + fn name(&self) -> FName { + self.as_uobject().name() + } + + fn get_name(&self) -> anyhow::Result { + self.as_uobject().get_name() + } + + fn get_name_or_default(&self) -> String { + self.as_uobject().get_name_or_default() + } + + fn get_full_name(&self) -> anyhow::Result { + self.as_uobject().get_full_name() + } + + fn get_full_name_or_default(&self) -> String { + self.as_uobject().get_full_name_or_default() + } + + fn outer(&self) -> Option { + self.as_uobject().outer() + } + + fn outermost(&self) -> Option { + self.as_uobject().outermost() + } + + fn is_package_object(&self) -> bool { + self.as_uobject().is_package_object() + } + + fn iter_outer_objects(&self) -> OuterObjectIterator { + OuterObjectIterator::new(self.as_uobject()) + } + + fn is_a(&self, other: &UClass) -> bool { + self.class() + .map(|class| class.iter_super_classes().contains(other)) + .unwrap_or(false) + } + } + + pub trait AsUStruct { + fn as_ustruct(&self) -> UStruct; + + #[inline] + fn children(&self) -> Option { + self.as_ustruct().children() + } + + #[inline] + fn super_struct(&self) -> Option { + self.as_ustruct().super_struct() + } + + #[inline] + fn iter_fields(&self) -> Option { + self.children() + .map(|field| UStructFieldIterator::new(field)) + } + } + + impl AsUObject for T + where + T: AsUStruct, + { + fn as_uobject(&self) -> UObject { + UObject::new(self.as_ustruct().inner.cast()) + } + } + + pub trait FromRaw { + fn from_non_null(inner: NonNull) -> Self; + + fn from_raw(raw: *mut R) -> Option + where + Self: Sized, + { + NonNull::new(raw).map(|inner| Self::from_non_null(inner)) + } + + fn from_maybe_non_null(inner: Option>) -> Option + where + Self: Sized, + { + inner.map(|inner| Self::from_non_null(inner)) + } + } +}