refactor in progress
This commit is contained in:
parent
4af21ba271
commit
eef7ce3cfc
929
src/lib.rs
929
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<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| ¶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<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
945
src/sdk/mod.rs
Normal 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| ¶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<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
227
src/sdk/process.rs
Normal 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
197
src/sdk/repr.rs
Normal 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(|¶m| 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),
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue