rust generation

This commit is contained in:
Janis 2023-04-21 17:21:07 +02:00
parent d1c0520316
commit 32bcc30b96
4 changed files with 681 additions and 32 deletions

View file

@ -14,6 +14,7 @@ pub mod tarray;
pub mod v2_types; pub mod v2_types;
pub mod sdk { pub mod sdk {
pub mod output;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
@ -22,11 +23,13 @@ pub mod sdk {
}; };
use anyhow::Context; use anyhow::Context;
use itertools::Itertools;
use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
use crate::{ use crate::{
global_tables::objects::{FindClass, GOBJECTS}, global_tables::objects::{FindClass, GOBJECTS},
v2_types::{ v2_types::{
actor_static_class,
any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct}, any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct},
traits::{ traits::{
AsUObject, StaticClass, UArrayPropertyTrait, UBytePropertyTrait, AsUObject, StaticClass, UArrayPropertyTrait, UBytePropertyTrait,
@ -34,10 +37,12 @@ pub mod sdk {
UObjectPropertyBaseTrait, UObjectTrait, UPropertyTrait, UStructNonConst, UObjectPropertyBaseTrait, UObjectTrait, UPropertyTrait, UStructNonConst,
UStructPropertyTrait, UStructTrait, 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 { struct ClassCache {
classes: Mutex<HashMap<String, UClass>>, classes: Mutex<HashMap<String, UClass>>,
} }
@ -152,9 +157,14 @@ pub mod sdk {
let name = strct let name = strct
.get_name() .get_name()
.context("failed to get struct or class name")?; .context("failed to get struct or class name")?;
let full_name = strct let full_name = strct
.get_full_name() .get_full_name()
.context("failed to get struct or class 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 is_class = strct.is_a(&UClass::static_class().unwrap());
let super_struct = if let Some(spr) = *strct.super_field() { let super_struct = if let Some(spr) = *strct.super_field() {
@ -172,7 +182,16 @@ pub mod sdk {
Ok(Class { Ok(Class {
is_class, is_class,
size: *strct.property_size() as u32, 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, super_class: super_struct,
fields, fields,
methods, methods,
@ -363,34 +382,33 @@ pub mod sdk {
match Self::find_type(any_type::AnyProperty::from_prop(prop)) { match Self::find_type(any_type::AnyProperty::from_prop(prop)) {
Ok(ty) => { Ok(ty) => {
log::debug!("field: {ty:?}: {prop}"); 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() .property_flags()
.contains(EPropertyFlags::ReturnParm) .contains(EPropertyFlags::ReturnParm)
{ {
Some(ParameterKind::Return) return_types.push(param.clone());
Some(ParameterKind::Return(param))
} else if prop.property_flags().contains(EPropertyFlags::OutParm) { } else if prop.property_flags().contains(EPropertyFlags::OutParm) {
if prop.property_flags().contains(EPropertyFlags::ConstParm) { if prop.property_flags().contains(EPropertyFlags::ConstParm) {
Some(ParameterKind::Default) Some(ParameterKind::Default(param))
} else { } else {
Some(ParameterKind::Out) Some(ParameterKind::Out(param))
} }
} else if prop.property_flags().contains(EPropertyFlags::Parm) { } else if prop.property_flags().contains(EPropertyFlags::Parm) {
Some(ParameterKind::Default) Some(ParameterKind::Default(param))
} else { } else {
None None
} { };
match kind {
ParameterKind::Return => { params.extend(param);
return_types.push(ty);
}
ParameterKind::Default => {
params.push(MethodParameter { ty, is_out: false });
}
ParameterKind::Out => {
params.push(MethodParameter { ty, is_out: true });
}
}
}
} }
Err(e) => { Err(e) => {
log::warn!("skipping field with offset {}: {e}", prop.offset()); log::warn!("skipping field with offset {}: {e}", prop.offset());
@ -507,6 +525,50 @@ pub mod sdk {
Self { packages } 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> { pub fn package_objects() -> Vec<Package> {
let gobjects = GOBJECTS.read().unwrap(); let gobjects = GOBJECTS.read().unwrap();
let objects = gobjects.as_objects().unwrap(); let objects = gobjects.as_objects().unwrap();
@ -634,7 +696,7 @@ pub mod sdk {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub enum PrimitiveType { pub enum PrimitiveType {
Bool, Bool,
U8, U8,
@ -647,9 +709,10 @@ pub mod sdk {
I64, I64,
F32, F32,
F64, F64,
Custom(&'static str),
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Type { pub enum Type {
Ptr(Box<Type>), Ptr(Box<Type>),
Ref(Box<Type>), Ref(Box<Type>),
@ -697,10 +760,10 @@ pub mod sdk {
pub ty: Type, pub ty: Type,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct MethodParameter { pub struct MethodParameter {
pub name: String,
pub ty: Type, pub ty: Type,
pub is_out: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -709,8 +772,8 @@ pub mod sdk {
pub full_name: String, pub full_name: String,
pub is_native: bool, pub is_native: bool,
pub is_static: bool, pub is_static: bool,
pub parameters: Vec<MethodParameter>, pub parameters: Vec<ParameterKind>,
pub return_type: Option<Type>, pub return_type: Option<MethodParameter>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -725,6 +788,10 @@ pub mod sdk {
} }
impl Class { impl Class {
pub fn rust_name(&self) -> &String {
&self.name
}
pub fn referenced_types(&self) -> Vec<UObject> { pub fn referenced_types(&self) -> Vec<UObject> {
let mut types = Vec::new(); let mut types = Vec::new();
self.super_class.map(|obj| types.push(obj.as_uobject())); self.super_class.map(|obj| types.push(obj.as_uobject()));
@ -738,13 +805,13 @@ pub mod sdk {
method method
.return_type .return_type
.as_ref() .as_ref()
.and_then(|ty| ty.referenced_type()), .and_then(|param| param.ty.referenced_type()),
); );
types.extend( types.extend(
method method
.parameters .parameters
.iter() .iter()
.map(|param| &param.ty) .map(|param| &param.as_param().ty)
.filter_map(|ty| ty.referenced_type()), .filter_map(|ty| ty.referenced_type()),
); );
} }
@ -759,10 +826,21 @@ pub mod sdk {
Enum(Enum), Enum(Enum),
} }
enum ParameterKind { #[derive(Debug)]
Return, pub enum ParameterKind {
Default, Return(MethodParameter),
Out, 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)] #[derive(Debug)]

1
src/sdk/output/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod rust;

545
src/sdk/output/rust.rs Normal file
View file

@ -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<W: Write>(&self, sdk: &Sdk, w: &mut W) -> anyhow::Result<()>;
}
impl<T> RustType for T
where
T: AsUObject,
{
fn rust_type<W: Write>(&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<W: Write>(&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<W: Write>(&self, sdk: &Sdk, w: &mut W) -> anyhow::Result<()> {
match self {
Type::Ptr(ptr) => {
write!(w, "Option<NonNull<")?;
ptr.rust_type(sdk, w)?;
write!(w, ">>")?;
}
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<W: Write>(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::<Self>::zeroed().assume_init() }}
}}
}}
"#
)?;
} else {
writeln!(w, "impl StaticClass for {name} {{")?;
writeln!(w, "fn get_static_class() -> Option<UClass> {{")?;
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<W: Write>(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<W: Write>(name: &str, w: &mut W) -> anyhow::Result<()> {
write!(
w,
r#"
{{
static OBJECT: OnceCell<Option<UObject>> = 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<W: Write>(
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<W: Write>(
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::<Self>::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<W: Write>(
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<NonNull<UClass>>")),
},
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<NonNull<UObject>>")),
},
],
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<NonNull<UField>>")),
}],
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<NonNull<UStruct>>")),
},
ClassField {
offset: 56,
size: 8,
name: "children".to_string(),
ty: Type::Primitive(PrimitiveType::Custom("Option<NonNull<UField>>")),
},
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");
}

View file

@ -152,6 +152,31 @@ bitflags! {
} }
} }
pub fn actor_static_class() -> Option<UClass> {
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<Option<UClass>> = 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 { macro_rules! define_utypes {
($($ty:ident),+) => { ($($ty:ident),+) => {
$( $(