diff --git a/src/lib.rs b/src/lib.rs index 684c581..2df09b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ pub mod tarray; pub mod v2_types; pub mod sdk { + pub mod output; use std::{ borrow::Cow, collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, @@ -22,11 +23,13 @@ pub mod sdk { }; 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, @@ -34,10 +37,12 @@ pub mod sdk { UObjectPropertyBaseTrait, UObjectTrait, UPropertyTrait, UStructNonConst, UStructPropertyTrait, UStructTrait, }, - EFunctionFlags, EPropertyFlags, UAnyType, UClass, UEnum, UFunction, UObject, UStruct, + EFunctionFlags, EPropertyFlags, UClass, UEnum, UFunction, UObject, UStruct, }, }; + use self::output::rust::inject_coreuobject_types; + struct ClassCache { classes: Mutex>, } @@ -152,9 +157,14 @@ pub mod sdk { let name = strct .get_name() .context("failed to get struct or class name")?; + let full_name = strct .get_full_name() .context("failed to get struct or class full name")?; + + let is_actor = strct + .iter_super_structs() + .contains(&unsafe { actor_static_class().unwrap().cast() }); let is_class = strct.is_a(&UClass::static_class().unwrap()); let super_struct = if let Some(spr) = *strct.super_field() { @@ -172,7 +182,16 @@ pub mod sdk { Ok(Class { is_class, size: *strct.property_size() as u32, - name, + name: format!( + "{}{name}", + if is_actor { + "A" + } else if is_class { + "U" + } else { + "F" + } + ), super_class: super_struct, fields, methods, @@ -363,34 +382,33 @@ pub mod sdk { match Self::find_type(any_type::AnyProperty::from_prop(prop)) { Ok(ty) => { log::debug!("field: {ty:?}: {prop}"); - if let Some(kind) = if prop + + let param = MethodParameter { + name: prop + .get_name() + .context("failed to get parameter name")?, + ty, + }; + + let param = if prop .property_flags() .contains(EPropertyFlags::ReturnParm) { - Some(ParameterKind::Return) + 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) + Some(ParameterKind::Default(param)) } else { - Some(ParameterKind::Out) + Some(ParameterKind::Out(param)) } } else if prop.property_flags().contains(EPropertyFlags::Parm) { - Some(ParameterKind::Default) + Some(ParameterKind::Default(param)) } else { None - } { - match kind { - ParameterKind::Return => { - return_types.push(ty); - } - ParameterKind::Default => { - params.push(MethodParameter { ty, is_out: false }); - } - ParameterKind::Out => { - params.push(MethodParameter { ty, is_out: true }); - } - } - } + }; + + params.extend(param); } Err(e) => { log::warn!("skipping field with offset {}: {e}", prop.offset()); @@ -507,6 +525,50 @@ pub mod sdk { Self { packages } } + pub fn patch(mut self) -> Self { + inject_coreuobject_types(&mut self); + self + } + + pub fn find_type(&self, obj: UObject) -> Option<&Types> { + self.packages + .get(&obj.package_object()) + .and_then(|pkg| pkg.types.get(&obj)) + } + + pub fn inject_type(&mut self, class: Class) -> Option { + if let Some(class_obj) = GOBJECTS + .read() + .unwrap() + .as_objects() + .unwrap() + .find_class(&class.full_name) + { + let package = class_obj.package_object(); + match self.packages.entry(package.clone()) { + Entry::Occupied(mut entry) => { + entry + .get_mut() + .types + .insert(class_obj.as_uobject(), Types::Class(class)); + } + Entry::Vacant(entry) => { + let mut types = HashMap::new(); + types.insert(class_obj.as_uobject(), Types::Class(class)); + entry.insert(ProcessedPackage { + package, + types, + package_dependencies: HashMap::new(), + }); + } + } + + Some(class_obj) + } else { + None + } + } + pub fn package_objects() -> Vec { let gobjects = GOBJECTS.read().unwrap(); let objects = gobjects.as_objects().unwrap(); @@ -634,7 +696,7 @@ pub mod sdk { } } - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] pub enum PrimitiveType { Bool, U8, @@ -647,9 +709,10 @@ pub mod sdk { I64, F32, F64, + Custom(&'static str), } - #[derive(Debug)] + #[derive(Debug, Clone)] pub enum Type { Ptr(Box), Ref(Box), @@ -697,10 +760,10 @@ pub mod sdk { pub ty: Type, } - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct MethodParameter { + pub name: String, pub ty: Type, - pub is_out: bool, } #[derive(Debug)] @@ -709,8 +772,8 @@ pub mod sdk { pub full_name: String, pub is_native: bool, pub is_static: bool, - pub parameters: Vec, - pub return_type: Option, + pub parameters: Vec, + pub return_type: Option, } #[derive(Debug)] @@ -725,6 +788,10 @@ pub mod sdk { } impl Class { + pub fn rust_name(&self) -> &String { + &self.name + } + pub fn referenced_types(&self) -> Vec { let mut types = Vec::new(); self.super_class.map(|obj| types.push(obj.as_uobject())); @@ -738,13 +805,13 @@ pub mod sdk { method .return_type .as_ref() - .and_then(|ty| ty.referenced_type()), + .and_then(|param| param.ty.referenced_type()), ); types.extend( method .parameters .iter() - .map(|param| ¶m.ty) + .map(|param| ¶m.as_param().ty) .filter_map(|ty| ty.referenced_type()), ); } @@ -759,10 +826,21 @@ pub mod sdk { Enum(Enum), } - enum ParameterKind { - Return, - Default, - Out, + #[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)] diff --git a/src/sdk/output/mod.rs b/src/sdk/output/mod.rs new file mode 100644 index 0000000..0ad9e7d --- /dev/null +++ b/src/sdk/output/mod.rs @@ -0,0 +1 @@ +pub mod rust; diff --git a/src/sdk/output/rust.rs b/src/sdk/output/rust.rs new file mode 100644 index 0000000..5f163d5 --- /dev/null +++ b/src/sdk/output/rust.rs @@ -0,0 +1,545 @@ +use std::io::{BufWriter, Write}; + +use anyhow::Context; + +use crate::{ + sdk::{ + canonicalize_name, Class, ClassField, ClassMethod, PrimitiveType, ProcessedPackage, Sdk, + Type, Types, + }, + v2_types::traits::{AsUObject, UObjectNonConst, UStructNonConst}, +}; + +pub trait RustType { + fn rust_type(&self, sdk: &Sdk, w: &mut W) -> anyhow::Result<()>; +} + +impl RustType for T +where + T: AsUObject, +{ + fn rust_type(&self, sdk: &Sdk, w: &mut W) -> anyhow::Result<()> { + sdk.find_type(self.as_uobject()) + .context("could not find type")? + .rust_type(sdk, w) + } +} + +impl RustType for Types { + fn rust_type(&self, _sdk: &Sdk, w: &mut W) -> anyhow::Result<()> { + match self { + Types::Class(class) => { + write!(w, "{}", class.rust_name())?; + } + Types::Enum(enm) => { + // FIXME: this? + write!(w, "{}", enm.name)?; + } + } + + Ok(()) + } +} + +impl RustType for Type { + fn rust_type(&self, sdk: &Sdk, w: &mut W) -> anyhow::Result<()> { + match self { + Type::Ptr(ptr) => { + write!(w, "Option>")?; + } + Type::Ref(_) => todo!(), + Type::WeakPtr(ptr) => { + write!(w, "TWeakObjectPtr<")?; + ptr.rust_type(sdk, w)?; + write!(w, ">")?; + } + Type::SoftPtr(ptr) => { + write!(w, "TSoftObjectPtr<")?; + ptr.rust_type(sdk, w)?; + write!(w, ">")?; + } + Type::LazyPtr(ptr) => { + write!(w, "TLazyObjectPtr<")?; + ptr.rust_type(sdk, w)?; + write!(w, ">")?; + } + Type::AssetPtr(ptr) => { + write!(w, "TAssetPtr<")?; + ptr.rust_type(sdk, w)?; + write!(w, ">")?; + } + Type::Array(array) => { + write!(w, "TArray<")?; + array.rust_type(sdk, w)?; + write!(w, ">")?; + } + Type::Primitive(ty) => { + write!( + w, + "{}", + match ty { + PrimitiveType::Bool => "bool", + PrimitiveType::U8 => "u8", + PrimitiveType::U16 => "u16", + PrimitiveType::U32 => "u32", + PrimitiveType::U64 => "u64", + PrimitiveType::I8 => "i8", + PrimitiveType::I16 => "i16", + PrimitiveType::I32 => "i32", + PrimitiveType::I64 => "i64", + PrimitiveType::F32 => "f32", + PrimitiveType::F64 => "f64", + PrimitiveType::Custom(custom) => custom, + } + )?; + } + Type::RawArray { ty, len } => { + write!(w, "[")?; + ty.rust_type(sdk, w)?; + write!(w, "; {}]", len)?; + } + Type::Name => { + write!(w, "FName")?; + } + Type::String => { + write!(w, "FString")?; + } + Type::Text => { + write!(w, "FText")?; + } + Type::Enum { enum_type, .. } => { + write!(w, "TEnumAsByte<")?; + enum_type.rust_type(sdk, w)?; + write!(w, ">")?; + } + Type::Class(class) => { + class.rust_type(sdk, w)?; + } + } + Ok(()) + } +} + +pub fn generate_class(class: &Class, _sdk: &Sdk, w: &mut W) -> anyhow::Result<()> { + let Class { + is_class, + size, + full_name, + .. + } = class; + + let name = class.rust_name(); + + writeln!(w, "#[repr(transparent)]")?; + writeln!(w, "#[derive(Debug)]")?; + if !is_class { + writeln!(w, "#[derive(Debug, Eq, PartialEq, Copy, Clone)]")?; + } + writeln!(w, "pub struct {name}(UnsafeCell<[u8; {size}]>);")?; + + write!( + w, + r#" +impl AsPtr for {name} {{ +fn as_ptr(&self) -> *const u8 {{ +self.0.get() as _ +}} +fn as_mut_ptr(&self) -> *mut u8 {{ +self.0.get() +}} +}} +"# + )?; + + if !is_class { + write!( + w, + r#" +impl {name} {{ + fn zeroed() -> Self {{ + unsafe {{ core::mem::MaybeUninit::::zeroed().assume_init() }} + }} +}} +"# + )?; + } else { + writeln!(w, "impl StaticClass for {name} {{")?; + writeln!(w, "fn get_static_class() -> Option {{")?; + write!(w, "let class: UClass = ")?; + generate_find_object(&full_name, w)?; + writeln!(w, ";")?; + writeln!(w, "class")?; + writeln!(w, "}}")?; + writeln!(w, "}}")?; + } + + Ok(()) +} + +pub fn generate_class_impl(class: &Class, sdk: &Sdk, w: &mut W) -> anyhow::Result<()> { + let Class { + super_class, + fields, + methods, + .. + } = class; + + let name = class.rust_name(); + + writeln!(w, "pub trait {}_Fields {{", name)?; + for field in fields { + write!(w, "fn get_{}(&self) -> &", field.name)?; + field.ty.rust_type(sdk, w)?; + write!( + w, + " {{unsafe {{ &*self.as_ptr().offset({}) }} }}", + field.offset + )?; + + write!(w, "fn get_{}_mut(&mut self) -> &mut ", field.name)?; + field.ty.rust_type(sdk, w)?; + write!( + w, + " {{unsafe {{ &mut *self.as_mut_ptr().offset({}) }} }}", + field.offset + )?; + } + writeln!(w, "}}")?; + writeln!(w, "impl {name}_Fields for {name} {{}}")?; + + for method in methods { + generate_method_params(class, method, sdk, w)?; + } + + writeln!(w, "pub trait {}_Methods {{", name)?; + + for method in methods { + generate_method(class, method, sdk, w)?; + } + writeln!(w, "}}")?; + writeln!(w, "impl {name}_Methods for {name} {{}}")?; + + if let Some(supr) = super_class { + let iter = core::iter::once(*supr).chain(supr.iter_super_structs()); + for parent in iter { + if let Some(parent) = sdk.find_type(parent.as_uobject()) { + match parent { + Types::Class(class) => { + writeln!(w, "impl {}_Methods for {name} {{}}", class.rust_name())?; + writeln!(w, "impl {}_Fields for {name} {{}}", class.rust_name())?; + } + _ => {} + } + } + } + } + + // TODO: functions + + Ok(()) +} + +fn generate_find_object(name: &str, w: &mut W) -> anyhow::Result<()> { + write!( + w, + r#" +{{ + static OBJECT: OnceCell> = OnceCell::new(); + *OBJECT.get_or_init(|| {{ + match GOBJECTS + .read() + .unwrap() + .as_objects() + .unwrap() + .find_class(&"{name}") {{ + Some(class) => {{Some(class)}}, + None => {{ + log::error!("static object {name} not found!"); + None + }} + }} + }}).cast() +}} +"# + )?; + Ok(()) +} + +pub fn generate_method( + class: &Class, + method: &ClassMethod, + sdk: &Sdk, + w: &mut W, +) -> anyhow::Result<()> { + write!(w, "fn {}(&self", method.name)?; + + for param in &method.parameters { + match param { + crate::sdk::ParameterKind::Default(parm) => { + write!(w, ",{}: &", parm.name)?; + parm.ty.rust_type(sdk, w)?; + } + crate::sdk::ParameterKind::Out(parm) => { + write!(w, ",{}: &mut ", parm.name)?; + parm.ty.rust_type(sdk, w)?; + write!(w, "/*(OutParm)*/")?; + } + _ => {} + } + } + + write!(w, ") -> ")?; + match &method.return_type { + Some(param) => { + param.ty.rust_type(sdk, w)?; + } + None => write!(w, "()")?, + } + + writeln!(w, " {{")?; + + write!(w, "let func: UFunction = ")?; + generate_find_object(&method.full_name, w)?; + writeln!(w, ";")?; + + let params_name = format!("{}_{}_Params", class.rust_name(), method.name); + writeln!(w, "let mut params = {params_name}::zeroed();")?; + + for param in &method.parameters { + let param = param.as_param(); + writeln!(w, "params.{0} = *{0};", param.name)?; + } + + writeln!(w, "let flags = *func.function_flags();")?; + writeln!(w, "process_event(&self as *const _, func, &mut params);")?; + writeln!(w, "*func.function_flags_mut() = flags;")?; + + for param in &method.parameters { + match param { + crate::sdk::ParameterKind::Out(param) => { + writeln!(w, "*{0} = params.{0};", param.name)?; + } + _ => {} + } + } + + match &method.return_type { + Some(param) => { + writeln!(w, "params.{}", param.name)?; + } + None => {} + } + + writeln!(w, "}}")?; + + Ok(()) +} + +pub fn generate_method_params( + class: &Class, + method: &ClassMethod, + sdk: &Sdk, + w: &mut W, +) -> anyhow::Result<()> { + let name = format!("{}_{}_Params", class.rust_name(), method.name); + write!(w, "#[repr(C)]\n#[derive(Debug)]\nstruct {name} {{\n")?; + + for param in &method.parameters { + write!(w, "\t{}: ", param.as_param().name)?; + param.as_param().ty.rust_type(sdk, w)?; + write!(w, ",\n")?; + } + + write!(w, "}}\n")?; + + write!( + w, + r#" +impl {name} {{ + fn zeroed() -> Self {{ + unsafe {{ core::mem::MaybeUninit::::zeroed().assume_init() }} + }} +}} +"# + )?; + + Ok(()) +} + +pub fn generate_sdk_to_tmp(sdk: &Sdk) -> anyhow::Result<()> { + let file = std::fs::File::create("z:/tmp/ark_sdk/mod.rs")?; + let mut writer = BufWriter::new(file); + + for (_, pkg) in &sdk.packages { + let pkg_name = canonicalize_name( + &pkg.package + .get_full_name() + .context("could not get package name")?, + ) + .to_string(); + + writeln!(writer, "pub mod {pkg_name};")?; + + let file = std::fs::File::create(format!("z:/tmp/ark_sdk/{pkg_name}.rs"))?; + let mut writer = BufWriter::new(file); + generate_package_rust_module(pkg, sdk, &mut writer)?; + } + + Ok(()) +} + +pub fn generate_package_rust_module( + pkg: &ProcessedPackage, + sdk: &Sdk, + w: &mut W, +) -> anyhow::Result<()> { + writeln!( + w, + "pub mod {} {{", + canonicalize_name( + &pkg.package + .get_full_name() + .context("could not get package name")? + ) + )?; + + writeln!(w, "use core::ptr::NonNull;\nuse core::cell::UnsafeCell;\n")?; + for (pkg, _) in &pkg.package_dependencies { + writeln!( + w, + "use super::{};", + canonicalize_name(&pkg.get_full_name().context("could not get package name")?) + )?; + } + + for (_, ty) in &pkg.types { + match ty { + Types::Class(class) => { + generate_class(&class, sdk, w)?; + generate_class_impl(&class, sdk, w)?; + } + Types::Enum(_) => {} + } + } + + writeln!(w, "}}")?; + Ok(()) +} + +pub(crate) fn inject_coreuobject_types(sdk: &mut Sdk) { + let uobject = Class { + is_class: true, + size: 40, + name: "Object".to_string(), + full_name: "Class CoreUObject.Object".to_string(), + super_class: None, + fields: vec![ + ClassField { + offset: 0, + size: 8, + name: "vtbl".to_string(), + ty: Type::Ptr(Box::new(Type::Primitive(PrimitiveType::U8))), + }, + ClassField { + offset: 8, + size: 4, + name: "object_flags".to_string(), + ty: Type::Primitive(PrimitiveType::Custom("EObjectFlags")), + }, + ClassField { + offset: 12, + size: 4, + name: "internal_index".to_string(), + ty: Type::Primitive(PrimitiveType::U32), + }, + ClassField { + offset: 16, + size: 8, + name: "class".to_string(), + ty: Type::Primitive(PrimitiveType::Custom("Option>")), + }, + ClassField { + offset: 24, + size: 8, + name: "name".to_string(), + ty: Type::Name, + }, + ClassField { + offset: 32, + size: 8, + name: "outer".to_string(), + ty: Type::Primitive(PrimitiveType::Custom("Option>")), + }, + ], + methods: vec![], + }; + + let uobject = sdk.inject_type(uobject).expect("uobject"); + let ufield = Class { + is_class: true, + size: 48, + name: "Field".to_string(), + full_name: "Class CoreUObject.Field".to_string(), + super_class: Some(unsafe { uobject.cast() }), + fields: vec![ClassField { + offset: 40, + size: 8, + name: "next".to_string(), + ty: Type::Primitive(PrimitiveType::Custom("Option>")), + }], + methods: vec![], + }; + let ufield = sdk.inject_type(ufield).expect("ufield"); + let ustruct = Class { + is_class: true, + size: 144, + name: "Struct".to_string(), + full_name: "Class CoreUObject.Struct".to_string(), + super_class: Some(unsafe { ufield.cast() }), + fields: vec![ + ClassField { + offset: 48, + size: 8, + name: "super_struct".to_string(), + ty: Type::Primitive(PrimitiveType::Custom("Option>")), + }, + ClassField { + offset: 56, + size: 8, + name: "children".to_string(), + ty: Type::Primitive(PrimitiveType::Custom("Option>")), + }, + ClassField { + offset: 64, + size: 4, + name: "property_size".to_string(), + ty: Type::Primitive(PrimitiveType::U32), + }, + ClassField { + offset: 68, + size: 4, + name: "min_alignment".to_string(), + ty: Type::Primitive(PrimitiveType::U32), + }, + ], + methods: vec![], + }; + let ustruct = sdk.inject_type(ustruct).expect("ustruct"); + let ufunction = Class { + is_class: true, + size: 176, + name: "Function".to_string(), + full_name: "Class CoreUObject.Function".to_string(), + super_class: Some(unsafe { ustruct.cast() }), + fields: vec![ClassField { + offset: 144, + size: 4, + name: "function_flags".to_string(), + ty: Type::Primitive(PrimitiveType::U32), + }], + methods: vec![], + }; + let _ufunction = sdk.inject_type(ufunction).expect("ufunction"); +} diff --git a/src/v2_types/mod.rs b/src/v2_types/mod.rs index bd2b1c1..1328173 100644 --- a/src/v2_types/mod.rs +++ b/src/v2_types/mod.rs @@ -152,6 +152,31 @@ bitflags! { } } +pub fn actor_static_class() -> Option { + use crate::global_tables::objects::{FindClass, GOBJECTS}; + use once_cell::sync::OnceCell; + // this is something that we always do in C/C++ but unfortinately requires a blanket impl Sync + Send on all UObject types.. + static CLASS: OnceCell> = OnceCell::new(); + + *CLASS.get_or_init(|| { + let class = match GOBJECTS + .read() + .unwrap() + .as_objects() + .unwrap() + .find_class("Class Engine.Actor") + { + Some(class) => Some(class), + None => { + log::error!("static class \"Class Engine.Actor\" not found!"); + None + } + }; + log::info!("class for {} is {:?}", "Class Engine.Actor", class); + class + }) +} + macro_rules! define_utypes { ($($ty:ident),+) => { $(