refactor in progress

This commit is contained in:
Janis 2023-05-25 19:56:24 +02:00
parent 4af21ba271
commit eef7ce3cfc
5 changed files with 1376 additions and 926 deletions

View file

@ -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<HashMap<String, UClass>>,
}
impl ClassCache {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
classes: Mutex::new(HashMap::new()),
}
}
}
impl FindClass for ClassCache {
fn find_class<S>(&self, class_name: S) -> Option<UClass>
where
S: Into<String>,
{
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<UObject>,
}
#[derive(Debug)]
pub struct ProcessedPackage {
pub package: UObject,
pub types: HashMap<UObject, Types>,
pub package_dependencies: HashMap<UObject, Vec<UObject>>,
}
impl Package {
pub fn new(package: UObject, objects: Vec<UObject>) -> 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("<uninitialized>")
|| 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::<HashMap<_, _>>();
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<Class> {
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<Type> {
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<ClassField>, Vec<ClassMethod>)> {
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<ClassMethod> {
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<Enum> {
// 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::<String, Vec<usize>>::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<Item = UObject> + Sized {
fn fold_into_packages(self) -> HashMap<UObject, Vec<UObject>> {
self.map(|obj| (obj.package_object(), obj)).fold(
HashMap::<UObject, Vec<UObject>>::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<T> FoldIntoPackages for T where T: Iterator<Item = UObject> {}
#[derive(Debug)]
pub struct Sdk {
pub packages: HashMap<UObject, ProcessedPackage>,
}
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<UClass> {
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<Package> {
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::<Vec<_>>()
}
}
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<char> {
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<str> {
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<Type>),
Ref(Box<Type>),
WeakPtr(UClass),
SoftPtr(UClass),
LazyPtr(UClass),
AssetPtr(UClass),
Array(Box<Type>),
Primitive(PrimitiveType),
RawArray {
ty: Box<Type>,
len: u32,
},
Name,
String,
Text,
Enum {
underlying: Box<Type>,
enum_type: UEnum,
},
Class(UClass),
Struct(UStruct),
}
impl Type {
pub fn referenced_type(&self) -> Option<UObject> {
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<ParameterKind>,
pub return_type: Option<MethodParameter>,
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<UStruct>,
pub fields: Vec<ClassField>,
pub methods: Vec<ClassMethod>,
}
impl Class {
pub fn rust_name(&self) -> &String {
&self.name
}
pub fn referenced_types(&self) -> Vec<UObject> {
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| &param.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<String>,
}
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;

945
src/sdk/mod.rs Normal file
View file

@ -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<HashMap<String, UClass>>,
}
impl ClassCache {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
classes: Mutex::new(HashMap::new()),
}
}
}
impl FindClass for ClassCache {
fn find_class<S>(&self, class_name: S) -> Option<UClass>
where
S: Into<String>,
{
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<UObject>,
}
#[derive(Debug)]
pub struct ProcessedPackage {
pub package: UObject,
pub types: HashMap<UObject, Types>,
pub package_dependencies: HashMap<UObject, Vec<UObject>>,
}
impl Package {
pub fn new(package: UObject, objects: Vec<UObject>) -> 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("<uninitialized>")
|| 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::<HashMap<_, _>>();
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<Class> {
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<Type> {
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<ClassField>, Vec<ClassMethod>)> {
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<ClassMethod> {
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<Enum> {
// 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::<String, Vec<usize>>::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<Item = UObject> + 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<UObject, Vec<UObject>> {
self.map(|obj| (obj.package_object(), obj)).fold(
HashMap::<UObject, Vec<UObject>>::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<T> FoldIntoPackages for T where T: Iterator<Item = UObject> {}
#[derive(Debug)]
pub struct Sdk {
pub packages: HashMap<UObject, ProcessedPackage>,
}
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<UClass> {
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<Package> {
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::<Vec<_>>()
}
}
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<char> {
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<str> {
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<Type>),
Ref(Box<Type>),
WeakPtr(UClass),
SoftPtr(UClass),
LazyPtr(UClass),
AssetPtr(UClass),
Array(Box<Type>),
Primitive(PrimitiveType),
RawArray {
ty: Box<Type>,
len: u32,
},
Name,
String,
Text,
Enum {
underlying: Box<Type>,
enum_type: UEnum,
},
Class(UClass),
Struct(UStruct),
}
impl Type {
pub fn referenced_type(&self) -> Option<UObject> {
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<ParameterKind>,
pub return_type: Option<MethodParameter>,
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<UStruct>,
pub properties_size: u32,
pub min_alignment: u32,
pub kind: StructKind,
pub fields: Vec<ClassField>,
pub methods: Vec<ClassMethod>,
}
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<UObject> {
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| &param.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<String>,
}
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")
}
}

227
src/sdk/process.rs Normal file
View file

@ -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("<uninitialized")
|| name.contains("PLACEHOLDER-CLASS")
}
impl Enum {
pub fn from_uenum(value: UEnum) -> anyhow::Result<UnrealType> {
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::<u32, String>::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<UnrealType> {
todo!()
}
pub fn from_uscriptstruct(value: UScriptStruct) -> anyhow::Result<UnrealType> {
todo!()
}
}
impl ClassField {
pub fn from_uprop(prop: UProperty) -> anyhow::Result<Self> {
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<Self> {
todo!()
}
}
impl Package {
pub fn process(self) -> anyhow::Result<ProcessedPackage> {
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<Type> {
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)
}
}

197
src/sdk/repr.rs Normal file
View file

@ -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<PackageRef, ProcessedPackage>,
}
/// 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<UObject>,
}
#[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<ObjectRef, UnrealType>,
/// All other packages that types in this package depend on directly.
pub dependencies: Vec<ObjectRef>,
}
#[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<u32, String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Class {
pub kind: StructKind,
pub size: u32,
pub name: String,
pub full_name: String,
pub super_class: Option<ObjectRef>,
pub properties_size: u32,
pub min_alignment: u32,
pub fields: Vec<ClassField>,
pub methods: Vec<ClassMethods>,
/// types this class depends on; includes super types, types of fields and
/// types of function parameters.
pub dependencies: Vec<ObjectRef>,
}
#[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<ClassField>,
pub return_param: Option<ClassField>,
}
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(|&param| param.is_out_param() || param.is_return_param())
.collect::<Vec<_>>()
}
pub fn in_params(&self) -> Vec<&ClassField> {
self.parameters
.iter()
.filter(|param| param.is_param() && !(param.is_out_param() || param.is_return_param()))
.collect::<Vec<_>>()
}
}
#[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<Type>),
Ref(Box<Type>),
WeakPtr(ObjectRef),
SoftPtr(ObjectRef),
LazyPtr(ObjectRef),
AssetPtr(ObjectRef),
Array(Box<Type>),
Primitive(PrimitiveType),
RawArray {
ty: Box<Type>,
len: u32,
},
Name,
String,
Text,
Enum {
underlying: Box<Type>,
enum_type: UEnum,
},
Class(ObjectRef),
Struct(ObjectRef),
}

View file

@ -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;