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 fname;
|
||||||
pub mod global_tables;
|
pub mod global_tables;
|
||||||
pub mod helper_types;
|
pub mod helper_types;
|
||||||
|
pub mod sdk;
|
||||||
pub mod tarray;
|
pub mod tarray;
|
||||||
pub mod v2_types;
|
pub mod v2_types;
|
||||||
|
|
||||||
pub mod sdk {
|
pub mod any_type;
|
||||||
pub mod output;
|
pub mod types;
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
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).
|
//! that the contents might very well change under its feet (which they might).
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
cell::UnsafeCell,
|
cell::UnsafeCell,
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
|
@ -24,6 +25,7 @@ impl VTbl {}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EPropertyFlags: u64 {
|
pub struct EPropertyFlags: u64 {
|
||||||
const None = 0x0000000000000000;
|
const None = 0x0000000000000000;
|
||||||
const Edit = 0x0000000000000001;
|
const Edit = 0x0000000000000001;
|
||||||
|
@ -80,6 +82,7 @@ bitflags! {
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EFunctionFlags: u32 {
|
pub struct EFunctionFlags: u32 {
|
||||||
const None = 0x00000000;
|
const None = 0x00000000;
|
||||||
const Final = 0x00000001;
|
const Final = 0x00000001;
|
||||||
|
@ -118,6 +121,7 @@ bitflags! {
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EObjectFlags: u32 {
|
pub struct EObjectFlags: u32 {
|
||||||
const NoFlags = 0x00000000;
|
const NoFlags = 0x00000000;
|
||||||
const Public = 0x00000001;
|
const Public = 0x00000001;
|
||||||
|
|
Loading…
Reference in a new issue