Compare commits

...

5 commits

5 changed files with 350 additions and 65 deletions

View file

@ -157,24 +157,9 @@ pub mod rust {
.packages
.iter()
.flat_map(|(_, pkg)| {
pkg.types.values().map(|ty| {
let name = match ty {
UnrealType::Class(class) => {
format!("U{}", canonicalize_name(&class.name))
}
UnrealType::Struct(class) => {
format!("F{}", canonicalize_name(&class.name))
}
UnrealType::Actor(class) => {
format!("A{}", canonicalize_name(&class.name))
}
UnrealType::Enum(class) => {
format!("E{}", canonicalize_name(&class.name))
}
};
(ty.obj_ref(), name)
})
pkg.types
.values()
.map(|ty| (ty.obj_ref(), Self::get_prefixed_name(&ty)))
})
.collect::<BTreeMap<_, _>>();
@ -184,6 +169,38 @@ pub mod rust {
}
}
/// returns the absolute path of a type with the assumption that all
/// packages are children of the path `::crate::sdk`
fn get_type_path(&self, key: &ObjectRef) -> Option<String> {
let pkg = &self.sdk.packages.get(&key.package)?.name;
self.get_type_name(key)
.map(|name| format!("::crate::sdk::{pkg}::{name}"))
}
/// returns the precached, prefixed and cannonicalized (for this
/// language, Rust) name for this object-ref
fn get_type_name(&self, key: &ObjectRef) -> Option<String> {
self.type_name_cache.get(key).cloned()
}
/// prefixes the typename according to its kind (UObject, AActor, FStruct, EEnum)
fn get_prefixed_name(ty: &UnrealType) -> String {
match ty {
UnrealType::Class(_) => {
format!("U{}", canonicalize_name(&ty.unique_name()))
}
UnrealType::Struct(_) => {
format!("F{}", canonicalize_name(&ty.unique_name()))
}
UnrealType::Actor(_) => {
format!("A{}", canonicalize_name(&ty.unique_name()))
}
UnrealType::Enum(_) => {
format!("E{}", canonicalize_name(&ty.unique_name()))
}
}
}
pub fn build(self) -> anyhow::Result<()> {
for pkg in self.sdk.packages.values() {
self.generate_package(pkg)?;
@ -195,64 +212,61 @@ pub mod rust {
fn type_name(&self, ty: &Type) -> anyhow::Result<String> {
let type_name = match ty {
Type::Ptr(inner) | Type::Ref(inner) => {
format!("Option<NonNull<{}>>", self.type_name(&inner)?)
format!(
"::core::option::Option<NonNull<{}>>",
self.type_name(&inner)?
)
}
Type::WeakPtr(inner) => {
format!(
"TWeakObjectPtr<{}>",
self.type_name_cache
.get(inner)
"::crate::engine::TWeakObjectPtr<{}>",
self.get_type_path(inner)
.context("type name was not cached.")?
)
}
Type::SoftPtr(inner) => {
format!(
"TSoftObjectPtr<{}>",
self.type_name_cache
.get(inner)
"::crate::engine::TSoftObjectPtr<{}>",
self.get_type_path(inner)
.context("type name was not cached.")?
)
}
Type::LazyPtr(inner) => {
format!(
"TLazyObjectPtr<{}>",
self.type_name_cache
.get(inner)
"::crate::engine::TLazyObjectPtr<{}>",
self.get_type_path(inner)
.context("type name was not cached.")?
)
}
Type::AssetPtr(inner) => format!(
"TAssetPtr<{}>",
self.type_name_cache
.get(inner)
"::crate::engine::TAssetPtr<{}>",
self.get_type_path(inner)
.context("type name was not cached.")?
),
Type::Array(inner) => format!("TArray<{}>", self.type_name(&inner)?),
Type::Array(inner) => {
format!("::crate::engine::TArray<{}>", self.type_name(&inner)?)
}
Type::Primitive(prim) => {
format!("{prim}")
}
Type::RawArray { ty, len } => {
format!("[{}; {}]", self.type_name(&ty)?, len)
}
Type::Name => "FName".to_string(),
Type::String => "FString".to_string(),
Type::Text => "FText".to_string(),
Type::Name => "::crate::engine::FName".to_string(),
Type::String => "::crate::engine::FString".to_string(),
Type::Text => "::crate::engine::FText".to_string(),
Type::Enum { enum_type, .. } => self
.type_name_cache
.get(enum_type)
.context("type name was not cached.")?
.clone(),
.get_type_path(enum_type)
.context("type name was not cached.")?,
Type::Class(class) => {
format!(
"::core::option::Option<{}>",
self.type_name_cache
.get(class)
self.get_type_path(class)
.context("type name was not cached.")?
)
}
Type::Struct(class) => self
.type_name_cache
.get(class)
.get_type_path(class)
.context("type name was not cached.")?
.clone(),
};
@ -262,8 +276,7 @@ pub mod rust {
fn generate_enum(&self, enum0: &Enum) -> anyhow::Result<TokenStream> {
let name = self
.type_name_cache
.get(&enum0.obj_ref)
.get_type_name(&enum0.obj_ref)
.context("enum name was not previously canonicalized and cached.")?;
let variants = enum0.values.iter().map(|(&value, name)| {
@ -411,13 +424,13 @@ pub mod rust {
struct_name: &str,
method: &ClassMethod,
) -> anyhow::Result<(TokenStream, TokenStream)> {
let method_name = canonicalize_name(&method.name);
let method_name = canonicalize_name(&method.unique_name());
let parameters = method
.parameters
.iter()
.map(|parameter| {
let name = canonicalize_name(&parameter.name);
let name = canonicalize_name(&parameter.unique_name());
let type_name = self.type_name(&parameter.ty)?;
anyhow::Ok((parameter, name, type_name))
@ -577,14 +590,16 @@ pub mod rust {
fn generate_struct_ctor(
&self,
_class: &Class,
_name: &str,
type_name: &str,
fields: &Vec<(&ClassField, Cow<str>, String)>,
) -> TokenStream {
let fields_defs = fields.iter().map(|(_, name, ty)| quote! {#name: #ty});
let this_field_asignments = fields.iter().map(|(_, name, _ty)| {
let setter = format_ident!("set_{}", name);
quote! {this.#setter(#name);}
let field_trait = format_ident!("{type_name}Fields");
quote! {<Self as #field_trait>::#setter(this, #name);}
});
// FIXME: handle super struct fields aswell, ARK doesnt seem to have those anyways.
@ -611,7 +626,7 @@ pub mod rust {
.fields
.iter()
.map(|field| {
let name = canonicalize_name(&field.name);
let name = canonicalize_name(&field.unique_name());
let ty = self.type_name(&field.ty)?;
anyhow::Ok((field, name, ty))
@ -658,9 +673,8 @@ pub mod rust {
}
fn generate_class(&self, class: &Class) -> anyhow::Result<TokenStream> {
let name = self
.type_name_cache
.get(&class.obj_ref)
let name = &self
.get_type_name(&class.obj_ref)
.context("enum name was not previously canonicalized and cached.")?;
let (field_trait, ctor) = self.generate_struct_fields(class, name)?;
@ -684,8 +698,6 @@ pub mod rust {
impl #method_trait for #name {}
};
// TODO: impl super-type fields and methods.
let mut sup = class.super_class;
let super_traits = core::iter::from_fn(|| {
if let Some(key) = sup {
@ -696,13 +708,12 @@ pub mod rust {
None
}
})
.map(|ty| ty.unique_name())
// SAFETY: we already got this type by its obj_ref, so it must be there.
.map(|ty| self.get_type_path(&ty.obj_ref()).unwrap())
.map(|super_name| {
let fields = format_ident!("{super_name}Fields");
let methods = format_ident!("{super_name}Methods");
// FIXME: full path here? meaning I need the package name aswell.
quote! {
impl #fields for #name {}
impl #methods for #name {}
@ -729,8 +740,7 @@ pub mod rust {
}
fn generate_package(&self, pkg: &ProcessedPackage) -> anyhow::Result<()> {
// TODO: canonicalize_name(&pkg.name);
let pkg_name = "PACKAGE_NAME_PLACEHOLDER".to_string();
let pkg_name = canonicalize_name(&pkg.name);
for (_id, ty) in &pkg.types {
let tokens = match ty {

View file

@ -16,3 +16,4 @@ pub mod v2_types;
pub mod any_type;
pub mod types;
pub mod util;

View file

@ -8,6 +8,7 @@ use ron::ser::PrettyConfig;
use crate::global_tables::objects::GOBJECTS;
use crate::sdk::repr::*;
use crate::util::DedupIter;
use crate::v2_types::{
any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct},
traits::{
@ -238,6 +239,28 @@ impl Class {
},
);
let fields = fields
.into_iter()
.dedup_with_by(
|n, field| ClassField {
override_name: Some(format!("{}{}", field.name, n)),
..field
},
|field: &ClassField| -> String { field.name.clone() },
)
.collect();
let methods = methods
.into_iter()
.dedup_with_by(
|n, field| ClassMethod {
override_name: Some(format!("{}{}", field.name, n)),
..field
},
|field: &ClassMethod| -> String { field.name.clone() },
)
.collect();
(fields, methods)
}
@ -257,14 +280,16 @@ impl Class {
impl ClassField {
pub fn from_uprop(prop: UProperty) -> anyhow::Result<Self> {
let name = prop
.name()
.get_name()
.context("failed to retrieve field name")?;
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")?,
override_name: None,
name,
ty: resolve_type(prop).context("failed to get field type")?,
})
}
@ -288,11 +313,21 @@ impl ClassMethod {
_ => None,
})
.map(|prop| ClassField::from_uprop(prop))
.collect::<anyhow::Result<Vec<_>>>()?;
.collect::<anyhow::Result<Vec<_>>>()?
.into_iter()
.dedup_with_by(
|n, field| ClassField {
override_name: Some(format!("{}{}", field.name, n)),
..field
},
|field: &ClassField| -> String { field.name.clone() },
)
.collect();
Ok(ClassMethod {
name,
full_name,
override_name: None,
flags: *func.function_flags(),
parameters: params,
})
@ -359,6 +394,14 @@ impl Package {
.context("not actually a package object")?,
types,
dependencies,
name: self
.package_object
.get_name()
.context("failed to get package object name.")?,
full_name: self
.package_object
.get_full_name()
.context("failed to get package object name.")?,
})
}
}

View file

@ -55,6 +55,8 @@ pub struct Package {
pub struct ProcessedPackage {
/// the package object ref.
pub package_object: PackageRef,
pub name: String,
pub full_name: String,
/// 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.
@ -142,6 +144,7 @@ pub struct ClassField {
pub offset: u32,
pub size: u32,
pub name: String,
pub override_name: Option<String>,
pub flags: EPropertyFlags,
pub ty: Type,
}
@ -166,11 +169,19 @@ impl ClassField {
pub fn is_rep(&self) -> bool {
!self.flags.contains(EPropertyFlags::RepSkip)
}
pub fn unique_name(&self) -> &str {
self.override_name
.as_ref()
.map(|s| s.as_str())
.unwrap_or(self.name.as_str())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClassMethod {
pub name: String,
pub override_name: Option<String>,
pub full_name: String,
pub flags: EFunctionFlags,
pub parameters: Vec<ClassField>,
@ -180,13 +191,23 @@ 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 unique_name(&self) -> &str {
self.override_name
.as_ref()
.map(|s| s.as_str())
.unwrap_or(self.name.as_str())
}
pub fn out_params(&self) -> Vec<&ClassField> {
self.parameters
.iter()

210
unreal-sdk/src/util.rs Normal file
View file

@ -0,0 +1,210 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::Hash;
pub struct DedupIdentity;
pub trait DedupGetHash<T> {
type Hashed: Hash + Eq;
fn get_hash<'a>(&mut self, a: &'a T) -> Self::Hashed;
}
impl<T> DedupGetHash<T> for DedupIdentity
where
T: Hash + Eq + Clone,
{
type Hashed = T;
fn get_hash<'a>(&mut self, a: &'a T) -> Self::Hashed {
a.clone()
}
}
impl<F, T, H> DedupGetHash<T> for F
where
F: FnMut(&T) -> H,
H: Hash + Eq,
{
type Hashed = H;
fn get_hash<'a>(&mut self, a: &'a T) -> Self::Hashed {
self(a)
}
}
pub struct DedupWithBy<I, F, H>
where
I: Iterator,
F: FnMut(usize, <I as Iterator>::Item) -> <I as Iterator>::Item,
H: DedupGetHash<<I as Iterator>::Item>,
{
iter: I,
/// makes item unique, is told how many elements of this equality.
cb_fn: F,
/// returns the borrowed element by which to compare the iterators elements
hash_fn: H,
cache: HashMap<H::Hashed, usize>,
}
pub trait DedupIter: Iterator {
fn dedup_with<'a, F>(self, f: F) -> DedupWithBy<Self, F, DedupIdentity>
where
F: FnMut(usize, <Self as Iterator>::Item) -> <Self as Iterator>::Item,
Self: Sized,
<Self as Iterator>::Item: Eq + Hash + Clone,
{
DedupWithBy {
iter: self,
cb_fn: f,
hash_fn: DedupIdentity,
cache: HashMap::new(),
}
}
fn dedup_with_by<'a, F, H>(self, f: F, h: H) -> DedupWithBy<Self, F, H>
where
F: FnMut(usize, <Self as Iterator>::Item) -> <Self as Iterator>::Item,
H: DedupGetHash<<Self as Iterator>::Item>,
Self: Sized,
{
DedupWithBy {
iter: self,
cb_fn: f,
hash_fn: h,
cache: HashMap::new(),
}
}
}
impl<I> DedupIter for I where I: Iterator {}
impl<I, F, H> Iterator for DedupWithBy<I, F, H>
where
I: Iterator,
F: FnMut(usize, <I as Iterator>::Item) -> <I as Iterator>::Item,
H: DedupGetHash<<I as Iterator>::Item>,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<Self::Item> {
let mut next = self.iter.next()?;
let next = loop {
let hash = self.hash_fn.get_hash(&next);
match self.cache.entry(hash) {
Entry::Occupied(mut entry) => {
*entry.get_mut() += 1;
next = (self.cb_fn)(*entry.get(), next);
}
Entry::Vacant(entry) => {
entry.insert(0);
break next;
}
};
};
Some(next)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn numbers() {
let numbers = [1, 2, 2, 2, 3, 4, 5, 6, 22];
let unique = numbers
.into_iter()
.dedup_with(|n, i| (i * 10 + n) as usize)
.collect::<Vec<_>>();
assert_eq!(&unique, &[1, 2, 21, 22, 3, 4, 5, 6, 221]);
}
#[derive(Debug, PartialEq, Clone)]
struct Key {
number: usize,
other: f32,
}
#[test]
fn keys() {
let keys = [
Key {
number: 1,
other: 1.0,
},
Key {
number: 2,
other: 1.0,
},
Key {
number: 2,
other: 1.0,
},
Key {
number: 2,
other: 1.0,
},
Key {
number: 3,
other: 1.0,
},
Key {
number: 4,
other: 1.0,
},
Key {
number: 5,
other: 1.0,
},
];
let expected = [
Key {
number: 1,
other: 1.0,
},
Key {
number: 2,
other: 1.0,
},
Key {
number: 21,
other: 1.0,
},
Key {
number: 22,
other: 1.0,
},
Key {
number: 3,
other: 1.0,
},
Key {
number: 4,
other: 1.0,
},
Key {
number: 5,
other: 1.0,
},
];
let unique = keys
.into_iter()
.dedup_with_by(
|n, key| Key {
number: (key.number * 10 + n),
..key
},
|key: &Key| -> usize { key.number },
)
.collect::<Vec<_>>();
assert_eq!(&unique, &expected);
}
}