commit b7803bd4539da1fe5d22c9ad810bfa426afed990 Author: Janis Date: Mon Apr 17 18:21:42 2023 +0200 can properly iterate over fnames diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..50ab91e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "unreal-sdk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.0" +thiserror = "1.0.0" +itertools = "0.10.0" +rayon = "1.0.0" +bitflags = "1.0.0" +anyhow = "1.0" +widestring = "1.0" +lazy_static = "1.4.0" + +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..282bb04 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1358 @@ +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) + } + } + } +}