#![feature( const_trait_impl, const_ptr_as_ref, const_nonnull_new, let_chains, if_let_guard )] // mod core_types; pub mod fname; pub mod global_tables; pub mod tarray; // pub mod types; 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, 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_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() }); 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(_) => Type::Primitive(PrimitiveType::Bool), 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: prop .get_name() .context("failed to get parameter name")?, ty, }; 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, }) } 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)) .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()) { Cow::Owned(format!("_{}", &valid)) } else { valid } } #[derive(Debug, Clone, Copy)] pub enum PrimitiveType { Bool, 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 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, } #[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, } #[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())); 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") } } }