From eef7ce3cfc2be71c8023b7e291d2c370f041dfc4 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 25 May 2023 19:56:24 +0200 Subject: [PATCH] refactor in progress --- src/lib.rs | 929 +------------------------------------------ src/sdk/mod.rs | 945 ++++++++++++++++++++++++++++++++++++++++++++ src/sdk/process.rs | 227 +++++++++++ src/sdk/repr.rs | 197 +++++++++ src/v2_types/mod.rs | 4 + 5 files changed, 1376 insertions(+), 926 deletions(-) create mode 100644 src/sdk/mod.rs create mode 100644 src/sdk/process.rs create mode 100644 src/sdk/repr.rs diff --git a/src/lib.rs b/src/lib.rs index 70c99f4..4f487a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,932 +10,9 @@ pub mod fname; pub mod global_tables; pub mod helper_types; +pub mod sdk; pub mod tarray; pub mod v2_types; -pub mod sdk { - pub mod output; - use std::{ - borrow::Cow, - collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, - fmt::Display, - sync::Mutex, - }; - - use anyhow::Context; - use itertools::Itertools; - use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; - - use crate::{ - global_tables::objects::{FindClass, GOBJECTS}, - v2_types::{ - actor_static_class, - any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct}, - traits::{ - AsUObject, StaticClass, UArrayPropertyTrait, UBytePropertyTrait, - UEnumPropertyTrait, UEnumTrait, UFunctionTrait, UObjectNonConst, - UObjectPropertyBaseTrait, UObjectTrait, UPropertyTrait, UStructNonConst, - UStructPropertyTrait, UStructTrait, - }, - EFunctionFlags, EPropertyFlags, UBoolProperty, UClass, UEnum, UFunction, UObject, - UStruct, - }, - }; - - use self::output::rust::inject_coreuobject_types; - - struct ClassCache { - classes: Mutex>, - } - - impl ClassCache { - #[allow(dead_code)] - pub fn new() -> Self { - Self { - classes: Mutex::new(HashMap::new()), - } - } - } - - impl FindClass for ClassCache { - fn find_class(&self, class_name: S) -> Option - where - S: Into, - { - let class_name = class_name.into(); - - let class = self.classes.lock().unwrap().get(&class_name).cloned(); - - match class { - class @ Some(_) => class, - None => match GOBJECTS - .read() - .unwrap() - .as_objects() - .unwrap() - .find_class(&class_name) - { - Some(class) => { - self.classes - .lock() - .unwrap() - .insert(class_name, class.clone()); - Some(class) - } - None => None, - }, - } - } - } - - pub struct Package { - package: UObject, - objects: Vec, - } - - #[derive(Debug)] - pub struct ProcessedPackage { - pub package: UObject, - pub types: HashMap, - pub package_dependencies: HashMap>, - } - - impl Package { - pub fn new(package: UObject, objects: Vec) -> Self { - Self { package, objects } - } - - pub fn process(self) -> ProcessedPackage { - let types = self - .objects - .par_iter() - .filter(|obj| { - // filter out default and anonymous objects/types - !obj.get_name() - .map(|name| { - name.contains("Default__") - || name.contains("") - || name.contains("PLACEHOLDER-CLASS") - }) - .unwrap_or(true) - }) - .filter_map(|&object| { - match AnyObject::from_object(object) { - AnyObject::Field(field) => match AnyField::from_field(field) { - AnyField::Enum(enm) => { - if let Ok(enm) = Self::process_enum(enm) { - return Some((object, Types::Enum(enm))); - } - } - AnyField::Struct(strt) => match AnyStruct::from_struct(strt) { - strt @ AnyStruct::ScriptStruct(_) | strt @ AnyStruct::Class(_) => { - if let Ok(class) = self.process_struct(unsafe { strt.cast() }) { - return Some((object, Types::Class(class))); - } - } - _ => {} - }, - _ => {} - }, - _ => {} - } - - None - }) - .collect::>(); - - let mut dependencies = types - .iter() - .filter_map(|(_, ty)| match ty { - Types::Class(class) => Some(class.referenced_types()), - _ => None, - }) - .map(|refs| refs.into_iter()) - .flatten() - .fold_into_packages(); - - // remove any objects in Self - dependencies.remove(&self.package); - - ProcessedPackage { - package: self.package, - types, - package_dependencies: dependencies, - } - } - - fn process_struct(&self, strct: UStruct) -> anyhow::Result { - let name = strct - .get_name() - .context("failed to get struct or class name")?; - - let full_name = strct - .get_full_name() - .context("failed to get struct or class full name")?; - - let is_actor = strct - .iter_super_structs() - .contains(&unsafe { actor_static_class().unwrap().cast() }) - || strct.is_a_maybe(&actor_static_class()); - let is_class = strct.is_a(&UClass::static_class().unwrap()); - - let super_struct = if let Some(spr) = *strct.super_field() { - if spr != strct { - Some(spr) - } else { - None - } - } else { - None - }; - - let (fields, methods) = self.process_children(strct)?; - - Ok(Class { - is_class, - size: *strct.property_size() as u32, - name: format!( - "{}{name}", - if is_actor { - "A" - } else if is_class { - "U" - } else { - "F" - } - ), - super_class: super_struct, - fields, - methods, - full_name, - }) - } - - fn find_type(prop: AnyProperty) -> anyhow::Result { - let ty = match prop.clone() { - numeric @ AnyProperty::Numeric(_) => { - match numeric.as_any_numeric_property().unwrap() { - any_type::AnyNumericProperty::U8(prop) => match prop.uenum() { - Some(enm) => Type::Enum { - underlying: Box::new(Type::Primitive(PrimitiveType::U8)), - enum_type: *enm, - }, - None => Type::Primitive(PrimitiveType::U8), - }, - any_type::AnyNumericProperty::U16(_) => Type::Primitive(PrimitiveType::U16), - any_type::AnyNumericProperty::U32(_) => Type::Primitive(PrimitiveType::U32), - any_type::AnyNumericProperty::U64(_) => Type::Primitive(PrimitiveType::U64), - any_type::AnyNumericProperty::I8(_) => Type::Primitive(PrimitiveType::I8), - any_type::AnyNumericProperty::I16(_) => Type::Primitive(PrimitiveType::I16), - any_type::AnyNumericProperty::I32(_) => Type::Primitive(PrimitiveType::I32), - any_type::AnyNumericProperty::I64(_) => Type::Primitive(PrimitiveType::I64), - any_type::AnyNumericProperty::F32(_) => Type::Primitive(PrimitiveType::F32), - any_type::AnyNumericProperty::F64(_) => Type::Primitive(PrimitiveType::F64), - any_type::AnyNumericProperty::Other(_) => { - return Err(anyhow::anyhow!("unhandled numeric property")); - } - } - } - AnyProperty::Bool(prop) => Type::Primitive(PrimitiveType::Bool(prop)), - AnyProperty::Interface(_) => { - return Err(anyhow::anyhow!("skipping interfaces for now")) - } - object @ AnyProperty::ObjectBase(_) => { - match object.as_any_object_base_property().unwrap() { - any_type::AnyObjectBaseProperty::Object(obj) => Type::Class( - obj.property_class() - .context("object property missing properry class")?, - ), - any_type::AnyObjectBaseProperty::WeakObject(obj) => Type::WeakPtr( - obj.property_class() - .context("weak ptr property missing property class.")?, - ), - any_type::AnyObjectBaseProperty::LazyObject(obj) => Type::LazyPtr( - obj.property_class() - .context("lazy ptr property missing property class.")?, - ), - any_type::AnyObjectBaseProperty::SoftObject(obj) => Type::SoftPtr( - obj.property_class() - .context("soft ptr property missing property class.")?, - ), - asset @ any_type::AnyObjectBaseProperty::AssetObject(_) => { - match asset.as_any_asset_object_property().unwrap() { - any_type::AnyAssetObjectProperty::Class(class) => Type::AssetPtr( - class - .property_class() - .context("asset object property missing properry class")?, - ), - any_type::AnyAssetObjectProperty::Object(_) => { - return Err(anyhow::anyhow!( - "unhandled asset object property (NOT AN ERROR)" - )); - } - } - } - any_type::AnyObjectBaseProperty::Other(_) => { - return Err(anyhow::anyhow!("unhandled object base property")); - } - } - } - AnyProperty::Array(array) => { - Type::Array(Box::new(Self::find_type(AnyProperty::from_prop( - array - .inner() - .context("array property inner type missing.")?, - ))?)) - } - AnyProperty::Map(_) => unreachable!("not used in ARK"), - AnyProperty::Str(_) => Type::String, - AnyProperty::Text(_) => Type::Text, - AnyProperty::Name(_) => Type::Name, - AnyProperty::Delegate(_) => { - return Err(anyhow::anyhow!("skipping delegates for now")); - } - AnyProperty::MulticastDelegate(_) => { - return Err(anyhow::anyhow!("skipping multicast delegates for now")); - } - AnyProperty::Enum(enm) => Type::Enum { - underlying: Box::new(Self::find_type(AnyProperty::from_prop( - enm.underlying_type() - .context("enum property was missing underlying type")?, - ))?), - enum_type: enm.uenum().context("enum property missing enum type")?, - }, - AnyProperty::Struct(class) => Type::Struct( - class - .ustruct() - .context("struct property had no inner struct")?, - ), - AnyProperty::Other(_) => { - return Err(anyhow::anyhow!("unhandled property.")); - } - }; - - if *prop.array_dim() > 1 { - Ok(Type::RawArray { - ty: Box::new(ty), - len: *prop.array_dim() as u32, - }) - } else { - Ok(ty) - } - } - - fn process_children( - &self, - strct: UStruct, - ) -> anyhow::Result<(Vec, Vec)> { - log::debug!("{} children:", strct.get_full_name_or_default()); - - let mut field_names = HashMap::new(); - let mut method_names = HashMap::new(); - let mut fields = Vec::new(); - let mut methods = Vec::new(); - - for child in strct - .iter_children() - .map(|field| any_type::AnyField::from_field(field)) - { - match child { - any_type::AnyField::Property(prop) => { - match Self::find_type(any_type::AnyProperty::from_prop(prop)) { - Ok(ty) => { - log::debug!("field: {ty:?}: {prop}"); - let field_name = canonicalize_name(&prop.name().get_name().context("failed to retrieve field name")?).to_string(); - - let name = match field_names.entry(field_name.clone()) { - Entry::Occupied(mut entry) => { - *entry.get_mut() += 1; - format!("{}{}", entry.key(), entry.get()) - }, - Entry::Vacant(entry) => { - let name = format!("{}", entry.key()); - entry.insert(1); - name - }, - }; - - - fields.push( - ClassField { - offset: *prop.offset() as u32, - size: *prop.element_size() as u32, - name, - ty, - }); - }, - Err(e) => { - log::warn!("skipping field with offset {}: {e}", prop.offset()); - }, - } - }, - strt @ any_type::AnyField::Struct(_) if let Some(any_type::AnyStruct::Function(func)) = strt.as_any_struct() => { - log::debug!("function: {func}"); - - if let Ok(method) = Self::process_function(func) { - let name = match method_names.entry(method.name.clone()) { - Entry::Occupied(mut entry) => { - *entry.get_mut() += 1; - format!("{}{}", entry.key(), entry.get()) - }, - Entry::Vacant(entry) => { - let name = format!("{}", entry.key()); - entry.insert(1); - name - }, - }; - methods.push(ClassMethod { name ,..method }); - } - }, - _ => { - } - } - } - - Ok((fields, methods)) - } - - fn process_function(func: UFunction) -> anyhow::Result { - let full_name = func - .get_full_name() - .context("could not get full function name")?; - let rust_name = - canonicalize_name(&func.get_name().context("could not get function name")?) - .to_string(); - - let is_static = func.function_flags().contains(EFunctionFlags::Static); - let is_native = func.function_flags().contains(EFunctionFlags::Native); - - let mut params = Vec::new(); - let mut return_types = Vec::new(); - - for child in func - .iter_children() - .map(|field| any_type::AnyField::from_field(field)) - { - match child { - any_type::AnyField::Property(prop) => { - match Self::find_type(any_type::AnyProperty::from_prop(prop)) { - Ok(ty) => { - log::debug!("field: {ty:?}: {prop}"); - - let param = MethodParameter { - name: format!( - "Arg{}", - canonicalize_name( - &prop - .get_name() - .context("failed to get parameter name")? - ) - ), - ty, - flags: *prop.property_flags(), - }; - - let param = if prop - .property_flags() - .contains(EPropertyFlags::ReturnParm) - { - return_types.push(param.clone()); - Some(ParameterKind::Return(param)) - } else if prop.property_flags().contains(EPropertyFlags::OutParm) { - if prop.property_flags().contains(EPropertyFlags::ConstParm) { - Some(ParameterKind::Default(param)) - } else { - Some(ParameterKind::Out(param)) - } - } else if prop.property_flags().contains(EPropertyFlags::Parm) { - Some(ParameterKind::Default(param)) - } else { - None - }; - - params.extend(param); - } - Err(e) => { - log::warn!("skipping field with offset {}: {e}", prop.offset()); - } - } - } - _ => {} - } - } - - Ok(ClassMethod { - name: rust_name, - parameters: params, - return_type: return_types.into_iter().next(), - full_name, - is_native, - is_static, - flags: *func.function_flags(), - }) - } - - fn process_enum(enm: UEnum) -> anyhow::Result { - // get all the variants - let values = enm - .names() - .iter() - .enumerate() - // canonicalize the names into valid rust - .map(|(value, name)| { - let name = &name.get_name().unwrap_or("AnonymousVariant".to_string()); - - // ignore everything preceeding the last double colon - let name = canonicalize_name( - name.rsplit_once("::").map(|(_, name)| name).unwrap_or(name), - ) - .to_string(); - (value, name) - }) - // store conflicts next to each other - .fold( - HashMap::>::new(), - |mut acc, (value, name)| { - match acc.entry(name) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(value); - } - Entry::Vacant(entry) => { - entry.insert(vec![value]); - } - } - acc - }, - ); - - // sort by value - let mut variants = BTreeMap::new(); - values.into_iter().for_each(|(name, values)| { - if values.len() > 1 { - for (i, value) in values.into_iter().enumerate() { - variants.insert(value, format!("{name}{i}")); - } - } else { - variants.insert(values.into_iter().next().unwrap(), name); - } - }); - - let name = enm - .as_uobject() - .get_name() - .context("enum name could not be found")?; - let name = canonicalize_name(&name).to_string(); - - Ok(Enum { - name, - values: variants.into_iter().map(|(_, name)| name).collect(), - }) - } - - pub fn package_object(&self) -> &UObject { - &self.package - } - } - - trait FoldIntoPackages: Iterator + Sized { - fn fold_into_packages(self) -> HashMap> { - self.map(|obj| (obj.package_object(), obj)).fold( - HashMap::>::new(), - |mut acc, (pkg, obj)| { - match acc.entry(pkg) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(obj); - } - Entry::Vacant(entry) => { - entry.insert(vec![obj]); - } - } - - acc - }, - ) - } - } - - impl FoldIntoPackages for T where T: Iterator {} - - #[derive(Debug)] - pub struct Sdk { - pub packages: HashMap, - } - - impl Sdk { - pub fn new() -> Self { - let packages = Self::package_objects(); - - let packages = packages - .into_par_iter() - .map(|pkg| pkg.process()) - .map(|pkg| (pkg.package, pkg)) - // filter out empty packages - .filter(|(_, pkg)| !pkg.types.is_empty()) - .collect(); - - Self { packages } - } - - pub fn patch(mut self) -> Self { - inject_coreuobject_types(&mut self); - self - } - - pub fn find_type(&self, obj: UObject) -> Option<&Types> { - self.packages - .get(&obj.package_object()) - .and_then(|pkg| pkg.types.get(&obj)) - } - - pub fn inject_type(&mut self, class: Class) -> Option { - if let Some(class_obj) = GOBJECTS - .read() - .unwrap() - .as_objects() - .unwrap() - .find_class(&class.full_name) - { - let package = class_obj.package_object(); - match self.packages.entry(package.clone()) { - Entry::Occupied(mut entry) => { - entry - .get_mut() - .types - .insert(class_obj.as_uobject(), Types::Class(class)); - } - Entry::Vacant(entry) => { - let mut types = HashMap::new(); - types.insert(class_obj.as_uobject(), Types::Class(class)); - entry.insert(ProcessedPackage { - package, - types, - package_dependencies: HashMap::new(), - }); - } - } - - Some(class_obj) - } else { - None - } - } - - pub fn package_objects() -> Vec { - let gobjects = GOBJECTS.read().unwrap(); - let objects = gobjects.as_objects().unwrap(); - - let sorted_objects = objects - .iter() - // ensure item contains object - .filter_map(|item| item.object()) - // get package object - .fold_into_packages(); - - sorted_objects - .into_iter() - .map(|(package, objects)| Package::new(package, objects)) - .collect::>() - } - } - - fn keywords() -> HashSet<&'static str> { - let mut keywords = HashSet::new(); - - // rust keywords - keywords.extend([ - "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", - "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", - "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", - "unsafe", "use", "where", "while", "async", "await", "dyn", "abstract", "become", - "box", "do", "final", "macro", "override", "priv", "typeof", "unsized", "virtual", - "yield", "try", - ]); - - // rust types - keywords.extend([ - "bool", "f64", "f32", "str", "char", "u8", "u16", "u32", "u64", "u128", "i8", "i16", - "i32", "i64", "i128", "usize", "isize", - ]); - - keywords - } - - fn token_chars() -> HashSet { - let mut chars = HashSet::new(); - - chars.extend([ - ' ', '?', '+', '-', ':', '/', '^', '(', ')', '[', ']', '<', '>', '&', '.', '#', '\'', - '"', '%', - ]); - - chars - } - - struct SplitResult<'a> { - start: Option<&'a str>, - middle: usize, - end: Option<&'a str>, - } - - impl<'a> SplitResult<'a> { - pub fn is_valid(&self) -> bool { - self.start.is_some() && self.middle == 0 && self.end.is_none() - } - - pub fn into_valid(self) -> Cow<'a, str> { - if self.is_valid() { - Cow::Borrowed(self.start.unwrap()) - } else { - let mut valid = self.start.map(|s| s.to_string()).unwrap_or_default(); - valid.extend(core::iter::repeat('_').take(self.middle)); - - match self.end { - Some(end) => { - valid.push_str(&split_at_illegal_char(end).into_valid()); - } - None => {} - } - - Cow::Owned(valid) - } - } - } - - fn empty_or_some(s: &str) -> Option<&str> { - if s.is_empty() { - None - } else { - Some(s) - } - } - - fn split_at_illegal_char(input: &str) -> SplitResult { - let illegal_chars = token_chars(); - if let Some(pos) = input.find(|c| illegal_chars.contains(&c)) { - let start = empty_or_some(&input[..pos]); - // skip the illegal char - let rest = &input[pos + 1..]; - - if let Some(pos2) = rest.find(|c| !illegal_chars.contains(&c)) { - SplitResult { - start, - middle: pos2 + 1, - end: empty_or_some(&rest[pos2..]), - } - } else { - SplitResult { - start, - middle: 1, - end: empty_or_some(rest), - } - } - } else { - SplitResult { - start: empty_or_some(input), - middle: 0, - end: None, - } - } - } - - fn canonicalize_name(name: &str) -> Cow { - let valid = split_at_illegal_char(name).into_valid(); - if keywords().contains(valid.as_ref()) || valid.starts_with(|c: char| !c.is_alphabetic()) { - Cow::Owned(format!("_{}", &valid)) - } else { - valid - } - } - - #[derive(Debug, Clone, Copy)] - pub enum PrimitiveType { - Bool(UBoolProperty), - U8, - U16, - U32, - U64, - I8, - I16, - I32, - I64, - F32, - F64, - Custom(&'static str), - } - - #[derive(Debug, Clone)] - pub enum Type { - Ptr(Box), - Ref(Box), - WeakPtr(UClass), - SoftPtr(UClass), - LazyPtr(UClass), - AssetPtr(UClass), - Array(Box), - Primitive(PrimitiveType), - RawArray { - ty: Box, - len: u32, - }, - Name, - String, - Text, - Enum { - underlying: Box, - enum_type: UEnum, - }, - Class(UClass), - Struct(UStruct), - } - - impl Type { - pub fn referenced_type(&self) -> Option { - match self { - Type::Ptr(t) | Type::Ref(t) | Type::Array(t) | Type::RawArray { ty: t, .. } => { - t.referenced_type() - } - Type::WeakPtr(o) | Type::SoftPtr(o) | Type::LazyPtr(o) | Type::AssetPtr(o) => { - Some(o.as_uobject()) - } - Type::Class(o) => Some(o.as_uobject()), - Type::Struct(o) => Some(o.as_uobject()), - Type::Primitive(_) | Type::Name | Type::String | Type::Text => None, - Type::Enum { enum_type, .. } => Some(enum_type.as_uobject()), - } - } - } - - #[derive(Debug)] - pub enum TypeKind { - Object, - Actor, - Enum, - Struct, - } - - #[derive(Debug)] - pub struct ClassField { - pub offset: u32, - pub size: u32, - pub name: String, - pub ty: Type, - } - - #[derive(Debug, Clone)] - pub struct MethodParameter { - pub name: String, - pub ty: Type, - pub flags: EPropertyFlags, - } - - #[derive(Debug)] - pub struct ClassMethod { - pub name: String, - pub full_name: String, - pub is_native: bool, - pub is_static: bool, - pub parameters: Vec, - pub return_type: Option, - pub flags: EFunctionFlags, - } - - impl ClassMethod { - pub fn return_tuple(&self) -> Vec<&MethodParameter> { - self.parameters - .iter() - .filter_map(|param| match param { - ParameterKind::Out(param) => Some(param), - _ => None, - }) - .chain(self.return_type.iter()) - .collect() - } - } - - #[derive(Debug)] - pub struct Class { - pub is_class: bool, - pub size: u32, - pub name: String, - pub full_name: String, - pub super_class: Option, - pub fields: Vec, - pub methods: Vec, - } - - impl Class { - pub fn rust_name(&self) -> &String { - &self.name - } - - pub fn referenced_types(&self) -> Vec { - let mut types = Vec::new(); - self.super_class.map(|obj| { - types.push(obj.as_uobject()); - let iter = obj.iter_super_structs(); - types.extend(iter.map(|strct| strct.as_uobject())); - }); - - for field in &self.fields { - types.extend(field.ty.referenced_type()); - } - - for method in &self.methods { - types.extend( - method - .return_type - .as_ref() - .and_then(|param| param.ty.referenced_type()), - ); - types.extend( - method - .parameters - .iter() - .map(|param| ¶m.as_param().ty) - .filter_map(|ty| ty.referenced_type()), - ); - } - - types - } - } - - #[derive(Debug)] - pub enum Types { - Class(Class), - Enum(Enum), - } - - #[derive(Debug)] - pub enum ParameterKind { - Return(MethodParameter), - Default(MethodParameter), - Out(MethodParameter), - } - - impl ParameterKind { - pub fn as_param(&self) -> &MethodParameter { - match self { - ParameterKind::Return(param) => param, - ParameterKind::Default(param) => param, - ParameterKind::Out(param) => param, - } - } - } - - #[derive(Debug)] - pub struct Enum { - pub name: String, - pub values: Vec, - } - - impl Display for Enum { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "enum {} {{", self.name)?; - for (i, name) in self.values.iter().enumerate() { - writeln!(f, "\t{} = {},", name, i)?; - } - writeln!(f, "}}\n") - } - } -} +pub mod any_type; +pub mod types; diff --git a/src/sdk/mod.rs b/src/sdk/mod.rs new file mode 100644 index 0000000..06df7b8 --- /dev/null +++ b/src/sdk/mod.rs @@ -0,0 +1,945 @@ +pub mod output; +pub mod process; +pub mod repr; + +use std::{ + borrow::Cow, + collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, + fmt::Display, + sync::Mutex, +}; + +use anyhow::Context; +use itertools::Itertools; +use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use serde::{Deserialize, Serialize}; + +use crate::{ + global_tables::objects::{FindClass, GOBJECTS}, + v2_types::{ + actor_static_class, + any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct}, + traits::{ + AsUObject, StaticClass, UArrayPropertyTrait, UBoolPropertyTrait, UBytePropertyTrait, + UEnumPropertyTrait, UEnumTrait, UFunctionTrait, UObjectNonConst, + UObjectPropertyBaseTrait, UObjectTrait, UPropertyTrait, UStructNonConst, + UStructPropertyTrait, UStructTrait, + }, + EFunctionFlags, EPropertyFlags, UBoolProperty, UClass, UEnum, UFunction, UObject, UStruct, + }, +}; + +use self::output::rust::inject_coreuobject_types; + +struct ClassCache { + classes: Mutex>, +} + +impl ClassCache { + #[allow(dead_code)] + pub fn new() -> Self { + Self { + classes: Mutex::new(HashMap::new()), + } + } +} + +impl FindClass for ClassCache { + fn find_class(&self, class_name: S) -> Option + where + S: Into, + { + let class_name = class_name.into(); + + let class = self.classes.lock().unwrap().get(&class_name).cloned(); + + match class { + class @ Some(_) => class, + None => match GOBJECTS + .read() + .unwrap() + .as_objects() + .unwrap() + .find_class(&class_name) + { + Some(class) => { + self.classes + .lock() + .unwrap() + .insert(class_name, class.clone()); + Some(class) + } + None => None, + }, + } + } +} + +pub struct Package { + package: UObject, + objects: Vec, +} + +#[derive(Debug)] +pub struct ProcessedPackage { + pub package: UObject, + pub types: HashMap, + pub package_dependencies: HashMap>, +} + +impl Package { + pub fn new(package: UObject, objects: Vec) -> Self { + Self { package, objects } + } + + pub fn process(self) -> ProcessedPackage { + let types = self + .objects + .par_iter() + .filter(|obj| { + // filter out default and anonymous objects/types + !obj.get_name() + .map(|name| { + name.contains("Default__") + || name.contains("") + || name.contains("PLACEHOLDER-CLASS") + }) + .unwrap_or(true) + }) + .filter_map(|&object| { + match AnyObject::from_object(object) { + AnyObject::Field(field) => match AnyField::from_field(field) { + AnyField::Enum(enm) => { + if let Ok(enm) = Self::process_enum(enm) { + return Some((object, Types::Enum(enm))); + } + } + AnyField::Struct(strt) => match AnyStruct::from_struct(strt) { + strt @ AnyStruct::ScriptStruct(_) | strt @ AnyStruct::Class(_) => { + if let Ok(class) = self.process_struct(unsafe { strt.cast() }) { + return Some((object, Types::Class(class))); + } + } + _ => {} + }, + _ => {} + }, + _ => {} + } + + None + }) + .collect::>(); + + let mut dependencies = types + .iter() + .filter_map(|(_, ty)| match ty { + Types::Class(class) => Some(class.referenced_types()), + _ => None, + }) + .map(|refs| refs.into_iter()) + .flatten() + .fold_into_packages(); + + // remove any objects in Self + dependencies.remove(&self.package); + + ProcessedPackage { + package: self.package, + types, + package_dependencies: dependencies, + } + } + + fn process_struct(&self, strct: UStruct) -> anyhow::Result { + let name = strct + .get_name() + .context("failed to get struct or class name")?; + + let full_name = strct + .get_full_name() + .context("failed to get struct or class full name")?; + + let is_actor = strct + .iter_super_structs() + .contains(&unsafe { actor_static_class().unwrap().cast() }) + || Some(strct) == actor_static_class().map(|class| unsafe { class.cast() }); + let is_class = strct.is_a(&UClass::static_class().unwrap()); + + let super_struct = if let Some(spr) = *strct.super_field() { + if spr != strct { + Some(spr) + } else { + None + } + } else { + None + }; + + let properties_size = *strct.property_size(); + let min_alignment = *strct.min_alignment(); + + let (fields, methods) = self.process_children(strct)?; + + Ok(Class { + is_class, + size: *strct.property_size() as u32, + super_class: super_struct, + fields, + min_alignment, + properties_size, + kind: if is_actor { + StructKind::Actor + } else if is_class { + StructKind::Object + } else { + StructKind::Struct + }, + name, + methods, + full_name, + }) + } + + fn find_type(prop: AnyProperty) -> anyhow::Result { + let ty = match prop.clone() { + numeric @ AnyProperty::Numeric(_) => match numeric.as_any_numeric_property().unwrap() { + any_type::AnyNumericProperty::U8(prop) => match prop.uenum() { + Some(enm) => Type::Enum { + underlying: Box::new(Type::Primitive(PrimitiveType::U8)), + enum_type: *enm, + }, + None => Type::Primitive(PrimitiveType::U8), + }, + any_type::AnyNumericProperty::U16(_) => Type::Primitive(PrimitiveType::U16), + any_type::AnyNumericProperty::U32(_) => Type::Primitive(PrimitiveType::U32), + any_type::AnyNumericProperty::U64(_) => Type::Primitive(PrimitiveType::U64), + any_type::AnyNumericProperty::I8(_) => Type::Primitive(PrimitiveType::I8), + any_type::AnyNumericProperty::I16(_) => Type::Primitive(PrimitiveType::I16), + any_type::AnyNumericProperty::I32(_) => Type::Primitive(PrimitiveType::I32), + any_type::AnyNumericProperty::I64(_) => Type::Primitive(PrimitiveType::I64), + any_type::AnyNumericProperty::F32(_) => Type::Primitive(PrimitiveType::F32), + any_type::AnyNumericProperty::F64(_) => Type::Primitive(PrimitiveType::F64), + any_type::AnyNumericProperty::Other(_) => { + return Err(anyhow::anyhow!("unhandled numeric property")); + } + }, + AnyProperty::Bool(prop) => Type::Primitive(PrimitiveType::Bool { + byte_mask: *prop.byte_mask(), + field_mask: *prop.field_mask(), + byte_offset: *prop.byte_offset(), + field_size: *prop.field_size(), + }), + AnyProperty::Interface(_) => { + return Err(anyhow::anyhow!("skipping interfaces for now")) + } + object @ AnyProperty::ObjectBase(_) => { + match object.as_any_object_base_property().unwrap() { + any_type::AnyObjectBaseProperty::Object(obj) => Type::Class( + obj.property_class() + .context("object property missing properry class")?, + ), + any_type::AnyObjectBaseProperty::WeakObject(obj) => Type::WeakPtr( + obj.property_class() + .context("weak ptr property missing property class.")?, + ), + any_type::AnyObjectBaseProperty::LazyObject(obj) => Type::LazyPtr( + obj.property_class() + .context("lazy ptr property missing property class.")?, + ), + any_type::AnyObjectBaseProperty::SoftObject(obj) => Type::SoftPtr( + obj.property_class() + .context("soft ptr property missing property class.")?, + ), + asset @ any_type::AnyObjectBaseProperty::AssetObject(_) => { + match asset.as_any_asset_object_property().unwrap() { + any_type::AnyAssetObjectProperty::Class(class) => Type::AssetPtr( + class + .property_class() + .context("asset object property missing properry class")?, + ), + any_type::AnyAssetObjectProperty::Object(_) => { + return Err(anyhow::anyhow!( + "unhandled asset object property (NOT AN ERROR)" + )); + } + } + } + any_type::AnyObjectBaseProperty::Other(_) => { + return Err(anyhow::anyhow!("unhandled object base property")); + } + } + } + AnyProperty::Array(array) => { + Type::Array(Box::new(Self::find_type(AnyProperty::from_prop( + array + .inner() + .context("array property inner type missing.")?, + ))?)) + } + AnyProperty::Map(_) => unreachable!("not used in ARK"), + AnyProperty::Str(_) => Type::String, + AnyProperty::Text(_) => Type::Text, + AnyProperty::Name(_) => Type::Name, + AnyProperty::Delegate(_) => { + return Err(anyhow::anyhow!("skipping delegates for now")); + } + AnyProperty::MulticastDelegate(_) => { + return Err(anyhow::anyhow!("skipping multicast delegates for now")); + } + AnyProperty::Enum(enm) => Type::Enum { + underlying: Box::new(Self::find_type(AnyProperty::from_prop( + enm.underlying_type() + .context("enum property was missing underlying type")?, + ))?), + enum_type: enm.uenum().context("enum property missing enum type")?, + }, + AnyProperty::Struct(class) => Type::Struct( + class + .ustruct() + .context("struct property had no inner struct")?, + ), + AnyProperty::Other(_) => { + return Err(anyhow::anyhow!("unhandled property.")); + } + }; + + if *prop.array_dim() > 1 { + Ok(Type::RawArray { + ty: Box::new(ty), + len: *prop.array_dim() as u32, + }) + } else { + Ok(ty) + } + } + + fn process_children( + &self, + strct: UStruct, + ) -> anyhow::Result<(Vec, Vec)> { + log::debug!("{} children:", strct.get_full_name_or_default()); + + let mut field_names = HashMap::new(); + let mut method_names = HashMap::new(); + let mut fields = Vec::new(); + let mut methods = Vec::new(); + + for child in strct + .iter_children() + .map(|field| any_type::AnyField::from_field(field)) + { + match child { + any_type::AnyField::Property(prop) => { + match Self::find_type(any_type::AnyProperty::from_prop(prop)) { + Ok(ty) => { + log::debug!("field: {ty:?}: {prop}"); + let field_name = canonicalize_name(&prop.name().get_name().context("failed to retrieve field name")?).to_string(); + + let name = match field_names.entry(field_name.clone()) { + Entry::Occupied(mut entry) => { + *entry.get_mut() += 1; + format!("{}{}", entry.key(), entry.get()) + }, + Entry::Vacant(entry) => { + let name = format!("{}", entry.key()); + entry.insert(1); + name + }, + }; + + + fields.push( + ClassField { + offset: *prop.offset() as u32, + size: *prop.element_size() as u32, + flags: *prop.property_flags(), + name, + ty, + }); + }, + Err(e) => { + log::warn!("skipping field with offset {}: {e}", prop.offset()); + }, + } + }, + strt @ any_type::AnyField::Struct(_) if let Some(any_type::AnyStruct::Function(func)) = strt.as_any_struct() => { + log::debug!("function: {func}"); + + if let Ok(method) = Self::process_function(func) { + let name = match method_names.entry(method.name.clone()) { + Entry::Occupied(mut entry) => { + *entry.get_mut() += 1; + format!("{}{}", entry.key(), entry.get()) + }, + Entry::Vacant(entry) => { + let name = format!("{}", entry.key()); + entry.insert(1); + name + }, + }; + methods.push(ClassMethod { name ,..method }); + } + }, + _ => { + } + } + } + + Ok((fields, methods)) + } + + fn process_function(func: UFunction) -> anyhow::Result { + let full_name = func + .get_full_name() + .context("could not get full function name")?; + let rust_name = + canonicalize_name(&func.get_name().context("could not get function name")?).to_string(); + + let is_static = func.function_flags().contains(EFunctionFlags::Static); + let is_native = func.function_flags().contains(EFunctionFlags::Native); + + let mut params = Vec::new(); + let mut return_types = Vec::new(); + + for child in func + .iter_children() + .map(|field| any_type::AnyField::from_field(field)) + { + match child { + any_type::AnyField::Property(prop) => { + match Self::find_type(any_type::AnyProperty::from_prop(prop)) { + Ok(ty) => { + log::debug!("field: {ty:?}: {prop}"); + + let param = MethodParameter { + name: format!( + "Arg{}", + canonicalize_name( + &prop.get_name().context("failed to get parameter name")? + ) + ), + ty, + flags: *prop.property_flags(), + }; + + let param = + if prop.property_flags().contains(EPropertyFlags::ReturnParm) { + return_types.push(param.clone()); + Some(ParameterKind::Return(param)) + } else if prop.property_flags().contains(EPropertyFlags::OutParm) { + if prop.property_flags().contains(EPropertyFlags::ConstParm) { + Some(ParameterKind::Default(param)) + } else { + Some(ParameterKind::Out(param)) + } + } else if prop.property_flags().contains(EPropertyFlags::Parm) { + Some(ParameterKind::Default(param)) + } else { + None + }; + + params.extend(param); + } + Err(e) => { + log::warn!("skipping field with offset {}: {e}", prop.offset()); + } + } + } + _ => {} + } + } + + Ok(ClassMethod { + name: rust_name, + parameters: params, + return_type: return_types.into_iter().next(), + full_name, + is_native, + is_static, + flags: *func.function_flags(), + }) + } + + fn process_enum(enm: UEnum) -> anyhow::Result { + // get all the variants + let values = enm + .names() + .iter() + .enumerate() + // canonicalize the names into valid rust + .map(|(value, name)| { + let name = &name.get_name().unwrap_or("AnonymousVariant".to_string()); + + // ignore everything preceeding the last double colon + let name = + canonicalize_name(name.rsplit_once("::").map(|(_, name)| name).unwrap_or(name)) + .to_string(); + (value, name) + }) + // store conflicts next to each other + .fold( + HashMap::>::new(), + |mut acc, (value, name)| { + match acc.entry(name) { + Entry::Occupied(mut entry) => { + entry.get_mut().push(value); + } + Entry::Vacant(entry) => { + entry.insert(vec![value]); + } + } + acc + }, + ); + + // sort by value + let mut variants = BTreeMap::new(); + values.into_iter().for_each(|(name, values)| { + if values.len() > 1 { + for (i, value) in values.into_iter().enumerate() { + variants.insert(value, format!("{name}{i}")); + } + } else { + variants.insert(values.into_iter().next().unwrap(), name); + } + }); + + let name = enm + .as_uobject() + .get_name() + .context("enum name could not be found")?; + let name = canonicalize_name(&name).to_string(); + + Ok(Enum { + name, + values: variants.into_iter().map(|(_, name)| name).collect(), + }) + } + + pub fn package_object(&self) -> &UObject { + &self.package + } +} + +trait FoldIntoPackages: Iterator + Sized { + /// folds an iterator over UObjects into a `HashMap` of Packages and a `Vec` + /// of UObjects in that package. + fn fold_into_packages(self) -> HashMap> { + self.map(|obj| (obj.package_object(), obj)).fold( + HashMap::>::new(), + |mut acc, (pkg, obj)| { + match acc.entry(pkg) { + Entry::Occupied(mut entry) => { + entry.get_mut().push(obj); + } + Entry::Vacant(entry) => { + entry.insert(vec![obj]); + } + } + + acc + }, + ) + } +} + +impl FoldIntoPackages for T where T: Iterator {} + +#[derive(Debug)] +pub struct Sdk { + pub packages: HashMap, +} + +impl Sdk { + pub fn new() -> Self { + let packages = Self::package_objects(); + + let packages = packages + .into_par_iter() + .map(|pkg| pkg.process()) + .map(|pkg| (pkg.package, pkg)) + // filter out empty packages + .filter(|(_, pkg)| !pkg.types.is_empty()) + .collect(); + + Self { packages } + } + + pub fn patch(mut self) -> Self { + inject_coreuobject_types(&mut self); + self + } + + pub fn find_type(&self, obj: UObject) -> Option<&Types> { + self.packages + .get(&obj.package_object()) + .and_then(|pkg| pkg.types.get(&obj)) + } + + pub fn inject_type(&mut self, class: Class) -> Option { + if let Some(class_obj) = GOBJECTS + .read() + .unwrap() + .as_objects() + .unwrap() + .find_class(&class.full_name) + { + let package = class_obj.package_object(); + match self.packages.entry(package.clone()) { + Entry::Occupied(mut entry) => { + entry + .get_mut() + .types + .insert(class_obj.as_uobject(), Types::Class(class)); + } + Entry::Vacant(entry) => { + let mut types = HashMap::new(); + types.insert(class_obj.as_uobject(), Types::Class(class)); + entry.insert(ProcessedPackage { + package, + types, + package_dependencies: HashMap::new(), + }); + } + } + + Some(class_obj) + } else { + None + } + } + + pub fn package_objects() -> Vec { + let gobjects = GOBJECTS.read().unwrap(); + let objects = gobjects.as_objects().unwrap(); + + let sorted_objects = objects + .iter() + // ensure item contains object + .filter_map(|item| item.object()) + // get package object + .fold_into_packages(); + + sorted_objects + .into_iter() + .map(|(package, objects)| Package::new(package, objects)) + .collect::>() + } +} + +fn keywords() -> HashSet<&'static str> { + let mut keywords = HashSet::new(); + + // rust keywords + keywords.extend([ + "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", + "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", + "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", + "use", "where", "while", "async", "await", "dyn", "abstract", "become", "box", "do", + "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try", + ]); + + // rust types + keywords.extend([ + "bool", "f64", "f32", "str", "char", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", + "i64", "i128", "usize", "isize", + ]); + + keywords +} + +fn token_chars() -> HashSet { + let mut chars = HashSet::new(); + + chars.extend([ + ' ', '?', '+', '-', ':', '/', '^', '(', ')', '[', ']', '<', '>', '&', '.', '#', '\'', '"', + '%', + ]); + + chars +} + +struct SplitResult<'a> { + start: Option<&'a str>, + middle: usize, + end: Option<&'a str>, +} + +impl<'a> SplitResult<'a> { + pub fn is_valid(&self) -> bool { + self.start.is_some() && self.middle == 0 && self.end.is_none() + } + + pub fn into_valid(self) -> Cow<'a, str> { + if self.is_valid() { + Cow::Borrowed(self.start.unwrap()) + } else { + let mut valid = self.start.map(|s| s.to_string()).unwrap_or_default(); + valid.extend(core::iter::repeat('_').take(self.middle)); + + match self.end { + Some(end) => { + valid.push_str(&split_at_illegal_char(end).into_valid()); + } + None => {} + } + + Cow::Owned(valid) + } + } +} + +fn empty_or_some(s: &str) -> Option<&str> { + if s.is_empty() { + None + } else { + Some(s) + } +} + +fn split_at_illegal_char(input: &str) -> SplitResult { + let illegal_chars = token_chars(); + if let Some(pos) = input.find(|c| illegal_chars.contains(&c)) { + let start = empty_or_some(&input[..pos]); + // skip the illegal char + let rest = &input[pos + 1..]; + + if let Some(pos2) = rest.find(|c| !illegal_chars.contains(&c)) { + SplitResult { + start, + middle: pos2 + 1, + end: empty_or_some(&rest[pos2..]), + } + } else { + SplitResult { + start, + middle: 1, + end: empty_or_some(rest), + } + } + } else { + SplitResult { + start: empty_or_some(input), + middle: 0, + end: None, + } + } +} + +fn canonicalize_name(name: &str) -> Cow { + let valid = split_at_illegal_char(name).into_valid(); + if keywords().contains(valid.as_ref()) || valid.starts_with(|c: char| !c.is_alphabetic()) { + Cow::Owned(format!("_{}", &valid)) + } else { + valid + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum PrimitiveType { + Bool { + byte_mask: u8, + field_mask: u8, + byte_offset: u8, + field_size: u8, + }, + U8, + U16, + U32, + U64, + I8, + I16, + I32, + I64, + F32, + F64, + Custom(&'static str), +} + +#[derive(Debug, Clone)] +pub enum Type { + Ptr(Box), + Ref(Box), + WeakPtr(UClass), + SoftPtr(UClass), + LazyPtr(UClass), + AssetPtr(UClass), + Array(Box), + Primitive(PrimitiveType), + RawArray { + ty: Box, + len: u32, + }, + Name, + String, + Text, + Enum { + underlying: Box, + enum_type: UEnum, + }, + Class(UClass), + Struct(UStruct), +} + +impl Type { + pub fn referenced_type(&self) -> Option { + match self { + Type::Ptr(t) | Type::Ref(t) | Type::Array(t) | Type::RawArray { ty: t, .. } => { + t.referenced_type() + } + Type::WeakPtr(o) | Type::SoftPtr(o) | Type::LazyPtr(o) | Type::AssetPtr(o) => { + Some(o.as_uobject()) + } + Type::Class(o) => Some(o.as_uobject()), + Type::Struct(o) => Some(o.as_uobject()), + Type::Primitive(_) | Type::Name | Type::String | Type::Text => None, + Type::Enum { enum_type, .. } => Some(enum_type.as_uobject()), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub enum StructKind { + Object, + Actor, + Struct, +} + +#[derive(Debug)] +pub struct ClassField { + pub offset: u32, + pub size: u32, + pub name: String, + pub flags: EPropertyFlags, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub struct MethodParameter { + pub name: String, + pub ty: Type, + pub flags: EPropertyFlags, +} + +#[derive(Debug)] +pub struct ClassMethod { + pub name: String, + pub full_name: String, + pub is_native: bool, + pub is_static: bool, + pub parameters: Vec, + pub return_type: Option, + pub flags: EFunctionFlags, +} + +impl ClassMethod { + pub fn return_tuple(&self) -> Vec<&MethodParameter> { + self.parameters + .iter() + .filter_map(|param| match param { + ParameterKind::Out(param) => Some(param), + _ => None, + }) + .chain(self.return_type.iter()) + .collect() + } +} + +#[derive(Debug)] +pub struct Class { + pub is_class: bool, + pub size: u32, + pub name: String, + pub full_name: String, + pub super_class: Option, + pub properties_size: u32, + pub min_alignment: u32, + pub kind: StructKind, + pub fields: Vec, + pub methods: Vec, +} + +impl Class { + pub fn rust_name(&self) -> String { + format!( + "{}{}", + match self.kind { + StructKind::Object => "U", + StructKind::Actor => "A", + StructKind::Struct => "F", + }, + canonicalize_name(&self.name) + ) + } + + pub fn referenced_types(&self) -> Vec { + let mut types = Vec::new(); + self.super_class.map(|obj| { + types.push(obj.as_uobject()); + let iter = obj.iter_super_structs(); + types.extend(iter.map(|strct| strct.as_uobject())); + }); + + for field in &self.fields { + types.extend(field.ty.referenced_type()); + } + + for method in &self.methods { + types.extend( + method + .return_type + .as_ref() + .and_then(|param| param.ty.referenced_type()), + ); + types.extend( + method + .parameters + .iter() + .map(|param| ¶m.as_param().ty) + .filter_map(|ty| ty.referenced_type()), + ); + } + + types + } +} + +#[derive(Debug)] +pub enum Types { + Class(Class), + Enum(Enum), +} + +#[derive(Debug)] +pub enum ParameterKind { + Return(MethodParameter), + Default(MethodParameter), + Out(MethodParameter), +} + +impl ParameterKind { + pub fn as_param(&self) -> &MethodParameter { + match self { + ParameterKind::Return(param) => param, + ParameterKind::Default(param) => param, + ParameterKind::Out(param) => param, + } + } +} + +#[derive(Debug)] +pub struct Enum { + pub name: String, + pub values: Vec, +} + +impl Display for Enum { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "enum {} {{", self.name)?; + for (i, name) in self.values.iter().enumerate() { + writeln!(f, "\t{} = {},", name, i)?; + } + writeln!(f, "}}\n") + } +} diff --git a/src/sdk/process.rs b/src/sdk/process.rs new file mode 100644 index 0000000..acea9ae --- /dev/null +++ b/src/sdk/process.rs @@ -0,0 +1,227 @@ +use std::collections::{btree_map::Entry, BTreeMap}; + +use anyhow::Context; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; + +use crate::v2_types::{ + any_type::{AnyField, AnyObject, AnyStruct}, + traits::{UEnumTrait, UObjectTrait, UPropertyTrait}, + UClass, UEnum, UFunction, UProperty, UScriptStruct, UStruct, +}; + +use super::repr::{ + Class, ClassField, ClassMethod, Enum, Package, ProcessedPackage, Type, UnrealType, +}; + +fn default_or_anon(name: &str) -> bool { + name.contains("Default__") + || name.contains(" anyhow::Result { + let values = value + .names() + .iter() + .enumerate() + .map(|(value, name)| { + let name = name + .get_name() + .unwrap_or(format!("AnonymousVariant{value}")); + (value, name) + }) + .fold(BTreeMap::new(), |mut acc, (value, name)| { + match acc.entry(name) { + Entry::Vacant(entry) => { + entry.insert(vec![value]); + } + Entry::Occupied(mut entry) => { + entry.get_mut().push(value); + } + } + acc + }); + + let values = + values + .into_iter() + .fold(BTreeMap::::new(), |mut acc, (name, values)| { + if values.len() > 1 { + acc.extend(values.into_iter().map(|i| (i as u32, format!("{name}{i}")))); + } else { + acc.insert(values.into_iter().next().unwrap() as u32, name) + } + acc + }); + + Ok(Enum { + name: value.name().get_name().context("could not get name")?, + full_name: value.name().get_full_name().context("could not get name")?, + values, + }) + } +} + +impl Class { + pub fn from_uclass(value: UClass) -> anyhow::Result { + todo!() + } + pub fn from_uscriptstruct(value: UScriptStruct) -> anyhow::Result { + todo!() + } +} + +impl ClassField { + pub fn from_uprop(prop: UProperty) -> anyhow::Result { + Ok(Self { + offset: *prop.offset() as u32, + size: *prop.element_size() as u32, + flags: *prop.property_flags(), + name: prop + .name() + .get_name() + .context("failed to retrieve field name")?, + ty: resolve_type(prop).context("failed to get field type")?, + }) + } +} + +impl ClassMethod { + pub fn from_ufunction(value: UFunction) -> anyhow::Result { + todo!() + } +} + +impl Package { + pub fn process(self) -> anyhow::Result { + self.children.par_iter().filter_map(|&object| { + match AnyObject::from_object(object) { + AnyObject::Field(field) => match AnyField::from_field(field) { + AnyField::Enum(my_enum) => {} + AnyField::Struct(my_struct) => match AnyStruct::from_struct(my_struct) { + my_struct @ AnyStruct::Class(_) + | my_struct @ AnyStruct::ScriptStruct(_) => {} + _ => {} + }, + _ => {} + }, + _ => {} + } + + todo!() + }); + todo!() + } +} + +fn resolve_type(prop: UProperty) -> anyhow::Result { + let ty = match AnyField::from(prop.clone()) { + numeric @ AnyProperty::Numeric(_) => match numeric.as_any_numeric_property().unwrap() { + any_type::AnyNumericProperty::U8(prop) => match prop.uenum() { + Some(enm) => Type::Enum { + underlying: Box::new(Type::Primitive(PrimitiveType::U8)), + enum_type: *enm, + }, + None => Type::Primitive(PrimitiveType::U8), + }, + any_type::AnyNumericProperty::U16(_) => Type::Primitive(PrimitiveType::U16), + any_type::AnyNumericProperty::U32(_) => Type::Primitive(PrimitiveType::U32), + any_type::AnyNumericProperty::U64(_) => Type::Primitive(PrimitiveType::U64), + any_type::AnyNumericProperty::I8(_) => Type::Primitive(PrimitiveType::I8), + any_type::AnyNumericProperty::I16(_) => Type::Primitive(PrimitiveType::I16), + any_type::AnyNumericProperty::I32(_) => Type::Primitive(PrimitiveType::I32), + any_type::AnyNumericProperty::I64(_) => Type::Primitive(PrimitiveType::I64), + any_type::AnyNumericProperty::F32(_) => Type::Primitive(PrimitiveType::F32), + any_type::AnyNumericProperty::F64(_) => Type::Primitive(PrimitiveType::F64), + any_type::AnyNumericProperty::Other(_) => { + return Err(anyhow::anyhow!("unhandled numeric property")); + } + }, + AnyProperty::Bool(prop) => Type::Primitive(PrimitiveType::Bool { + byte_mask: *prop.byte_mask(), + field_mask: *prop.field_mask(), + byte_offset: *prop.byte_offset(), + field_size: *prop.field_size(), + }), + AnyProperty::Interface(_) => return Err(anyhow::anyhow!("skipping interfaces for now")), + object @ AnyProperty::ObjectBase(_) => { + match object.as_any_object_base_property().unwrap() { + any_type::AnyObjectBaseProperty::Object(obj) => Type::Class( + obj.property_class() + .context("object property missing properry class")?, + ), + any_type::AnyObjectBaseProperty::WeakObject(obj) => Type::WeakPtr( + obj.property_class() + .context("weak ptr property missing property class.")?, + ), + any_type::AnyObjectBaseProperty::LazyObject(obj) => Type::LazyPtr( + obj.property_class() + .context("lazy ptr property missing property class.")?, + ), + any_type::AnyObjectBaseProperty::SoftObject(obj) => Type::SoftPtr( + obj.property_class() + .context("soft ptr property missing property class.")?, + ), + asset @ any_type::AnyObjectBaseProperty::AssetObject(_) => { + match asset.as_any_asset_object_property().unwrap() { + any_type::AnyAssetObjectProperty::Class(class) => Type::AssetPtr( + class + .property_class() + .context("asset object property missing properry class")?, + ), + any_type::AnyAssetObjectProperty::Object(_) => { + return Err(anyhow::anyhow!( + "unhandled asset object property (NOT AN ERROR)" + )); + } + } + } + any_type::AnyObjectBaseProperty::Other(_) => { + return Err(anyhow::anyhow!("unhandled object base property")); + } + } + } + AnyProperty::Array(array) => { + Type::Array(Box::new(Self::find_type(AnyProperty::from_prop( + array + .inner() + .context("array property inner type missing.")?, + ))?)) + } + AnyProperty::Map(_) => unreachable!("not used in ARK"), + AnyProperty::Str(_) => Type::String, + AnyProperty::Text(_) => Type::Text, + AnyProperty::Name(_) => Type::Name, + AnyProperty::Delegate(_) => { + return Err(anyhow::anyhow!("skipping delegates for now")); + } + AnyProperty::MulticastDelegate(_) => { + return Err(anyhow::anyhow!("skipping multicast delegates for now")); + } + AnyProperty::Enum(enm) => Type::Enum { + underlying: Box::new(Self::find_type(AnyProperty::from_prop( + enm.underlying_type() + .context("enum property was missing underlying type")?, + ))?), + enum_type: enm.uenum().context("enum property missing enum type")?, + }, + AnyProperty::Struct(class) => Type::Struct( + class + .ustruct() + .context("struct property had no inner struct")?, + ), + AnyProperty::Other(_) => { + return Err(anyhow::anyhow!("unhandled property.")); + } + }; + + if *prop.array_dim() > 1 { + Ok(Type::RawArray { + ty: Box::new(ty), + len: *prop.array_dim() as u32, + }) + } else { + Ok(ty) + } +} diff --git a/src/sdk/repr.rs b/src/sdk/repr.rs new file mode 100644 index 0000000..c610cda --- /dev/null +++ b/src/sdk/repr.rs @@ -0,0 +1,197 @@ +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +use serde::{Deserialize, Serialize}; + +use crate::v2_types::{ + traits::{UObjectNonConst, UObjectTrait}, + EFunctionFlags, EPropertyFlags, UObject, +}; + +impl UObject { + pub fn object_ref(&self) -> ObjectRef { + ObjectRef { + package: PackageRef(self.package_object().internal_index()), + object: self.internal_index(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct PackageRef(u32); + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct ObjectRef { + package: PackageRef, + object: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Sdk { + packages: BTreeMap, +} + +/// A package represents some group of packages that are related to another, +/// examples in ARK are `Engine`, `ShooterGame`, `SlateCore` or +/// `Buff_Companion_HLNA`. +#[derive(Debug)] +pub struct Package { + /// the package object is the root object of the package. + pub package_object: UObject, + /// each package may contain other objects and classes which can be + /// referenced by an `ObjectRef`, which is a unique identifier for each + /// object. + pub children: Vec, +} + +#[derive(Debug)] +pub struct ProcessedPackage { + /// the package object. + pub package_object: UObject, + /// all types extracted from this package referenced by their `ObjectRef`. + pub types: BTreeMap, + /// All other packages that types in this package depend on directly. + pub dependencies: Vec, +} + +#[derive(Debug)] +pub enum UnrealType { + Class(Class), + Struct(Class), + Actor(Class), + Enum(Enum), +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum StructKind { + Object, + Actor, + Struct, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Enum { + pub name: String, + pub full_name: String, + pub values: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Class { + pub kind: StructKind, + pub size: u32, + pub name: String, + pub full_name: String, + pub super_class: Option, + pub properties_size: u32, + pub min_alignment: u32, + pub fields: Vec, + pub methods: Vec, + /// types this class depends on; includes super types, types of fields and + /// types of function parameters. + pub dependencies: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ClassField { + pub offset: u32, + pub size: u32, + pub name: String, + pub flags: EPropertyFlags, + pub ty: Type, +} + +impl ClassField { + pub fn is_return_param(&self) -> bool { + self.flags.contains(EPropertyFlags::ReturnParm) + } + pub fn is_param(&self) -> bool { + self.flags.contains(EPropertyFlags::Parm) + } + pub fn is_const_param(&self) -> bool { + self.flags.contains(EPropertyFlags::ConstParm) + } + pub fn is_out_param(&self) -> bool { + !self.is_const_param() && self.flags.contains(EPropertyFlags::OutParm) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ClassMethod { + pub name: String, + pub full_name: String, + pub flags: EFunctionFlags, + pub parameters: Vec, + pub return_param: Option, +} + +impl ClassMethod { + pub fn is_static(&self) -> bool { + self.flags.contains(EFunctionFlags::Static) + } + pub fn is_native(&self) -> bool { + self.flags.contains(EFunctionFlags::Native) + } + /// this function is replicated to the server, might be exploitable. + pub fn is_net_server(&self) -> bool { + self.flags.contains(EFunctionFlags::NetServer) + } + pub fn out_params(&self) -> Vec<&ClassField> { + self.parameters + .iter() + .filter(|¶m| param.is_out_param() || param.is_return_param()) + .collect::>() + } + + pub fn in_params(&self) -> Vec<&ClassField> { + self.parameters + .iter() + .filter(|param| param.is_param() && !(param.is_out_param() || param.is_return_param())) + .collect::>() + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum PrimitiveType { + Bool { + byte_mask: u8, + field_mask: u8, + byte_offset: u8, + field_size: u8, + }, + U8, + U16, + U32, + U64, + I8, + I16, + I32, + I64, + F32, + F64, + Custom(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Type { + Ptr(Box), + Ref(Box), + WeakPtr(ObjectRef), + SoftPtr(ObjectRef), + LazyPtr(ObjectRef), + AssetPtr(ObjectRef), + Array(Box), + Primitive(PrimitiveType), + RawArray { + ty: Box, + len: u32, + }, + Name, + String, + Text, + Enum { + underlying: Box, + enum_type: UEnum, + }, + Class(ObjectRef), + Struct(ObjectRef), +} diff --git a/src/v2_types/mod.rs b/src/v2_types/mod.rs index fcd5490..ca0b18b 100644 --- a/src/v2_types/mod.rs +++ b/src/v2_types/mod.rs @@ -6,6 +6,7 @@ //! that the contents might very well change under its feet (which they might). #![allow(non_upper_case_globals)] use bitflags::bitflags; +use serde::{Deserialize, Serialize}; use std::{ cell::UnsafeCell, fmt::{Debug, Display}, @@ -24,6 +25,7 @@ impl VTbl {} bitflags! { #[repr(C)] + #[derive(Serialize, Deserialize)] pub struct EPropertyFlags: u64 { const None = 0x0000000000000000; const Edit = 0x0000000000000001; @@ -80,6 +82,7 @@ bitflags! { bitflags! { #[repr(C)] + #[derive(Serialize, Deserialize)] pub struct EFunctionFlags: u32 { const None = 0x00000000; const Final = 0x00000001; @@ -118,6 +121,7 @@ bitflags! { bitflags! { #[repr(C)] + #[derive(Serialize, Deserialize)] pub struct EObjectFlags: u32 { const NoFlags = 0x00000000; const Public = 0x00000001;